❤ 2019.3.16

事情是这样的。

在很多天以前,老师接了个项目,问我有没有意向,我谨慎的表达了我对新事物的好奇心,对新知识的求知欲,同时又委婉的表达了我的能力有限的实际情况,然后我以为事情就这么过去了,直到半个多月之前,老师又找到我,我才知道,该来的总会来的。。。

具体来说,我这次要做的事情其实很简单,就是读一个旋转编码器的度数,并且实现能设置零点的功能。

至于编码器,就是这货:

没什么难度嘛~哈哈哈~so easy~

然后就piapiapia打脸,事情远远没有我想象的那么简单!

首先老师给我的编码器是安装好的,我并看不到里面芯片的型号,同时对于其机械结构的畏惧,我又没敢拆开(当时还不知道传感器的型号),于是只好问老师,老师给了我一些资料,大概是这样的:

这样的:

和这样的:

然后就是我把它当成5600调了大概两三天,其间把IIC通讯从头到尾学了一遍,我甚至一度认为是我能力不行(难道不是么?),后来和朱工反复确认,才发现原来是型号不对,人家是不支持iic总线的,(as5048b支持iic总线),只能用spi总线,然而商家只有as5600的iic例程,并不提供5048的spi例程,所以只好自己写了。

我:“¥……@……*#……%#&”

❤ 2019.3.16

好了下面终于进入正文了。

● 致谢

我的调试平台是秉火STM32霸道开发板,程序模板用的是秉火开发板自带的spi总线读取串行flash的例程,在调试期间以下的文章对我帮助很大,先行致谢~

【STM32F407 SPI配置并读取磁角度传感器AS5048a笔记】

文章里详细的记录了在STM32F4上调试AS5048A的重点内容,对于了解spi总线但是不了解as5048a的童鞋非常有帮助。我也是有好几个问题在这篇文章里找到了答案,比心~

【AS5048A SPI 14位磁旋转编码器】

文章里记录了一个新手可能会范的小错误(虽然我没有范),同时解释了一下各个寄存器的功能,对于我等英文渣非常有帮助,比心+1~

我之前对spi总线是不了解的,除了知道他是个串行总线之外一无所知(主要是因为懒没有把stm32的课程学完)。于是我首先做的就是先去学习了下spi总线。

(这里是秉火的学习资料)

因为不是这篇笔记的重点,所以就简要记录一下。

〇  SPI总线简要介绍

●  SPI物理层

SSn:片选信号,主机控制,低电平有效。

SCK:时钟信号,主机控制。

MOSI:主机输出从机输入。

MISO:主机输入从机输出。

● 协议层

通过配置CPOL位(时钟极性)和CPHA位(时钟相位),SPI总线有四种工作模式:

● STM32的SPI特性

架构剖析

通讯引脚

● SPI初始化结构体

● 几个比较重要的库函数

SPI初始化函数

SPI使能函数

获取SPI状态标记函数

SPI发送数据函数

SPI接收数据函数

好了关于SPI的基本信息就是这样,下面真的开始正文了。

〇  AS5048A调试过程

● 硬件连接

这个是真的as5048a的接线的定义:

我选择了stm32的spi1口进行调试,对应的接口:

CSn----------PC13

CLK----------PA5

MOSI--------PA7

MISO--------PA6

● IO口初始化

首先定义各个功能对应的IO口,顺带定义了一下片选指令

bsp_spi_AS5048A.h

/*SPI接口定义-开头****************************/
#define      AS5048A_SPIx                        SPI1
#define      AS5048A_SPI_APBxClock_FUN           RCC_APB2PeriphClockCmd
#define      AS5048A_SPI_CLK                     RCC_APB2Periph_SPI1//CS(NSS)引脚 片选选普通GPIO即可
#define      AS5048A_SPI_CS_APBxClock_FUN        RCC_APB2PeriphClockCmd
#define      AS5048A_SPI_CS_CLK                  RCC_APB2Periph_GPIOC
#define      AS5048A_SPI_CS_PORT                 GPIOC
#define      AS5048A_SPI_CS_PIN                  GPIO_Pin_13//SCK引脚
#define      AS5048A_SPI_SCK_APBxClock_FUN       RCC_APB2PeriphClockCmd
#define      AS5048A_SPI_SCK_CLK                 RCC_APB2Periph_GPIOA
#define      AS5048A_SPI_SCK_PORT                GPIOA
#define      AS5048A_SPI_SCK_PIN                 GPIO_Pin_5
//MISO引脚
#define      AS5048A_SPI_MISO_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      AS5048A_SPI_MISO_CLK                RCC_APB2Periph_GPIOA
#define      AS5048A_SPI_MISO_PORT               GPIOA
#define      AS5048A_SPI_MISO_PIN                GPIO_Pin_6
//MOSI引脚
#define      AS5048A_SPI_MOSI_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define      AS5048A_SPI_MOSI_CLK                RCC_APB2Periph_GPIOA
#define      AS5048A_SPI_MOSI_PORT               GPIOA
#define      AS5048A_SPI_MOSI_PIN                GPIO_Pin_7#define      SPI_AS5048A_CS_LOW()                GPIO_ResetBits( AS5048A_SPI_CS_PORT, AS5048A_SPI_CS_PIN )
#define      SPI_AS5048A_CS_HIGH()               GPIO_SetBits( AS5048A_SPI_CS_PORT, AS5048A_SPI_CS_PIN )/*SPI接口定义-结尾****************************/

然后使能SPI时钟,使能GPIO口时钟,配置GPIO口属性。

bsp_spi_AS5048A.c

/* 使能SPI时钟 */AS5048A_SPI_APBxClock_FUN ( AS5048A_SPI_CLK, ENABLE );/* 使能SPI引脚相关的时钟 */
AS5048A_SPI_CS_APBxClock_FUN ( AS5048A_SPI_CS_CLK|AS5048A_SPI_SCK_CLK|AS5048A_SPI_MISO_CLK|AS5048A_SPI_MOSI_CLK, ENABLE );/* 配置SPI的 CS引脚,普通IO即可 */GPIO_InitStructure.GPIO_Pin = AS5048A_SPI_CS_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(AS5048A_SPI_CS_PORT, &GPIO_InitStructure);/* 配置SPI的 SCK引脚*///【为什么注释掉这几个端口配置就好了?】GPIO_InitStructure.GPIO_Pin = AS5048A_SPI_SCK_PIN;
//  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(AS5048A_SPI_SCK_PORT, &GPIO_InitStructure);/* 配置SPI的 MISO引脚*/GPIO_InitStructure.GPIO_Pin = AS5048A_SPI_MISO_PIN;
//  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(AS5048A_SPI_MISO_PORT, &GPIO_InitStructure);/* 配置SPI的 MOSI引脚*/GPIO_InitStructure.GPIO_Pin = AS5048A_SPI_MOSI_PIN;
//  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
//  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(AS5048A_SPI_MOSI_PORT, &GPIO_InitStructure);

在这里我有个疑问,一开始我定义了SPI的端口的属性,结果通讯不成功,然后我注释掉了,就可以了,不知道是怎么回事。

● SPI初始化结构体配置

配置SPI初始化结构体,需要根据AS5048A的属性来设置相关的参数。

从这段话可以得知,AS5048A需要16位SPI数据,在下降沿读数据,在上升沿写数据,每发送一次指令(16位数据)后片选信号需要拉高一次。

○ SPI时序图

从这里可以看出SPI总线工作在模式1,即CPOL=0,CPHA=1。

另外还有就是高位字节优先(MSB模式)。

时间特性

这个图的重点大概是两个350ns的延时,但是我还没有验证过。

○    由上面的信息可以知道,SPI初始化结构体需要配置的参数了,代码如下。

bsp_spi_AS5048A.c

  /* SPI 模式配置 */// AS5048A芯片 支持SPI模式0及模式3,据此设置CPOL CPHASPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;    //双线全双工模式SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //SPI主模#式SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;    //16位数据SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;  //CPOL=0SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;  //CPHA=1SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //软件控制片选信号SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;  //时钟16分频(这个分频主要看as5048a的最高工作频率,我在datasheet里并没有找到,//我根据时间特性计算了一下大概是10M以下,所以选了个速度比较低的模式)SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;    //高位字节优先模式SPI_InitStructure.SPI_CRCPolynomial = 15;    //CRC位数,好像没用SPI_Init(AS5048A_SPIx , &SPI_InitStructure);/* SPI使能 */SPI_Cmd(AS5048A_SPIx,ENABLE);

● 发送/接收函数

OK初始化工作基本上就完成了,下面是发送接收函数。

指令的发送和数据的接收本来是很重要的部分,但是其实也挺简单的,SPI总线的特点是发送接收同时进行,所以发送函数同时也是接收函数。

需要注意的是,发送函数的实质是向发送寄存器里写入数据,同理接收函数也是,所以在发送之前需要检测发送寄存器的状态,然而判断数据是否发送完成却要看接受寄存器的状态,因为发送接收是同时进行的。在发送完成之后实际上也完成了数据的接收,所以顺带return一个接收到的数据。

所以代码如下:

bsp_spi_AS5048A.c

/*** @brief  SPI_AS5048A读写函数,16位* @param  无* @retval 有*/
u16 SPI_AS5048A_ReadWriteWord(u16 data)
{/* 等待发送缓冲区为空,TXE事件 */while(SPI_I2S_GetFlagStatus(AS5048A_SPIx, SPI_I2S_FLAG_TXE) == RESET);/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(AS5048A_SPIx,data);/* 等待接收缓冲区非空,RXNE事件 */while(SPI_I2S_GetFlagStatus(AS5048A_SPIx, SPI_I2S_FLAG_RXNE) == RESET);/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(AS5048A_SPIx);
}

这段代码实现了数据的发送和接收,但是有个问题,因为里面有两个while循环等待,根据墨菲定理,死循环的情况是一定会发生的,这点在秉火的例程里通过加入了一个超时函数来解决,代码是这样的:

static __IO uint32_t  SPITimeout = SPIT_LONG_TIMEOUT;
static uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);/*** @brief  SPI_AS5048A读写函数,16位* @param  无* @retval 有*/
u16 SPI_AS5048A_ReadWriteWord(u16 data)
{SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待发送缓冲区为空,TXE事件 */while(SPI_I2S_GetFlagStatus(AS5048A_SPIx, SPI_I2S_FLAG_TXE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(2);}/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */SPI_I2S_SendData(AS5048A_SPIx,data);SPITimeout = SPIT_FLAG_TIMEOUT;/* 等待接收缓冲区非空,RXNE事件 */while(SPI_I2S_GetFlagStatus(AS5048A_SPIx, SPI_I2S_FLAG_RXNE) == RESET){if((SPITimeout--) == 0) return SPI_TIMEOUT_UserCallback(3);}/* 读取数据寄存器,获取接收缓冲区数据 */return SPI_I2S_ReceiveData(AS5048A_SPIx);
}/*** @brief  等待超时回调函数* @param  None.* @retval None.*/
static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{/* 等待超时后的处理,输出错误信息 */AS5048A_ERROR("SPI 等待超时!errorCode = %d",errorCode);return 0;
}

学习了学习了。

● 读取数据函数

发送/接收函数只是对SPI寄存器的底层操作,并不能读取到传感器的数据,这里专门为读取传感器数据编写一个函数。

读取数据的逻辑是首先发出片选信号,然后发送一段指令指定读取那个寄存器数据,然后再发送一段任意指令或者下一个读取指令,在发送的同时接收到上一个指令中指定的寄存器数据。

○ 发送指令格式

首先需要知道发送指令的格式。

这个是AS5048A的SPI指令包格式。指令由一个校验位(偶校验),一个读写控制位,和14位寄存器地址构成。

寄存器地址如下:

作为读数据指令,指令的内容是固定的,因此我们可以定义几个指令,需要的时候直接发送。

【更新↓↓↓】

○ 接收数据格式

接收到的数据最高位是校验位,第二位是错误标记位,所以需要对接收到的数据进行处理。

【更新↑↑↑】

于是读取数据函数的代码:

bsp_spi_AS5048A.h

/*命令定义-开头*******************************///这是附加了偶校验位和读写标志位的指令
#define CMD_ANGLE            0xffff
#define CMD_AGC              0x7ffd
#define CMD_MAG              0x7ffe
#define CMD_CLAER            0x4001
#define CMD_NOP              0xc000
/*命令定义-结尾*******************************/

bsp_spi_AS5048A.c

 /*** @brief  SPI_AS5048A读取接收函数,通过发送相应指令读取AS5048A中寄存器的数值* @param  无* @retval 返回接收到的数据*/
u16 SPI_AS5048A_ReadData(u16 TxData)
{u16 data;//delay_us(10);   //datasheet里面说两个信号之间要间隔350ns,不知道这样可不可以SPI_AS5048A_CS_LOW();//delay_us(10);   //datasheet里面说片选信号和时钟信号要间隔350ns,不知道这样可不可以SPI_AS5048A_ReadWriteWord(TxData);SPI_AS5048A_CS_HIGH();delay_us(10);   //datasheet里面说两个信号之间要间隔350ns,不知道这样可不可以SPI_AS5048A_CS_LOW();data = SPI_AS5048A_ReadWriteWord(CMD_NOP);SPI_AS5048A_CS_HIGH();data = data & 0x3fff;   //屏蔽高两位【更新】return data;
}

main.c

 while(1){Value = SPI_AS5048A_ReadData(CMD_ANGLE);printf("%d\n",Value);delay_ms(1000);}

● 遇到了问题

到这里,理论上来说就可以正常的读取编码器的角度值了。但是!

但是!

出问题了!

○ 问题描述

问题是这样的。

在程序编译成功之后,我用串口调试助手接收stm32读取到的数据,结果出现了这样的情况:

简单来说,就是当旋钮位置不变时,理论上读取到的数值应该是不变的(实际上会有很小的变化),但是我读到的数值却有两种,一种是看起来比较正常的值,另一种是一个特别大的数值。而且两种数值随机出现,并没有什么规律。

○ 问题分析

首先我用万用表测量传感器的模拟量输出端(其实是PWM信号输出),确定了比较正常的那个值确实是正确的读数。也就是说在某个环节出现了干扰,使我读到的数据发生了某种变化。

我首先排除了是指令发送过程中出现的错误,因为在发送NOP指令后读到的数据都是0(至于为什么我也不知道),然后我换了其他的输出格式,输出的数据依然是有两种,所以不是显示的问题。

后来我分析了读到的这两种数据。我发现首先对应同一个旋钮的位置,这两种数据是确定的,他们之间总是相差一个几乎确定的数字,大概是30000多,所以我怀疑错误的数据是在正确的数据上叠加了一个确定的数。于是我灵机一动,把接收到的十进制数转换成了二进制,于是发现了真相:

真相应该已经很清晰了,因为我设置的是十进制显示,所以没有在第一时间发现问题,还因为这个苦想了大半夜,熬到了将近4点才睡觉。。。。

为什么会出现这种情况呢?

我查看了传感器的register map,我觉得应该是传感器里的寄存器是14位的,但是通过SPI发送的数据是16位的,也就是说虽然stm32接收到了一个14位的数据,但是存在寄存器里的依然是个16位的数据,没有定义的两位可能会因为某些原因随机的表现出0或者1的状态,具体是不是这样我也不知道,不过知道问题出在哪,就知道该怎样去避免了。

【更新↓↓↓】

我仔细查了查,发现其实这并不是随机出现的,因为最高位是校验位,所以根据读到的数据不同有规律的置0置1(受教了)。读回来的数据格式如下:

【更新↑↑↑】

○ 解决方法

我觉得最直观的解决方法就是屏蔽接收到的数据的高两位,其实后来我在《STM32F407 SPI配置并读取磁角度传感器AS5048a笔记》这篇文章里看到了对数据进行的处理,主要是刚开始没意识到这个问题,文中的程序也没给出注释,所以没有及时发现问题。

解决方式是给读取数据函数加一行:

 data = data & 0x3fff;  //屏蔽高两位

代码已更新到上面读取数据函数中。

● 清除错误标记函数

OK搞定了读取数据函数,下面还有清除错误标记函数,因为在通讯过程中难免出现错误,(根据墨菲定理。。。),所以清除错误标记是很重要的。

大概的意思是当出现错误时,返回值的错误表位会被置1,然后通过读取错误标记寄存器可以清零错误标记位。

不过至于错误标记位有什么用呢?我的理解是在调试过程中判断通讯是否正常(可是不正常的话不就收不到信息了么。。。),调好之后在使用中就用不到了,毕竟前两位是被屏蔽的。。。

所以代码如下:

bsp_spi_AS5048A.c

 * 函数名:ClearAndNop* 描述  :清除错误标记位* 输入  :无* 输出  :无*/
u16 ClearAndNop(void)
{SPI_AS5048A_CS_LOW();SPI_AS5048A_ReadWriteWord(CMD_CLAER);              // 附加偶校验的错误标志位清除命令SPI_AS5048A_CS_HIGH();delay_us(10);              // 两次命令之间有350ns的间隔,源自官方datasheetSPI_AS5048A_CS_LOW();;SPI_AS5048A_ReadWriteWord(CMD_NOP);               // 附加偶校验的错误标志位清除命令SPI_AS5048A_CS_HIGH();
}

● 写入寄存器函数

到了这里虽然完成了寄存器角度信息的读取,但是还有个功能需要实现就是通过按键充值编码器的零点,这个好像还有点复杂,而且没找到相关的资料(懒。。。),所以研究下As5048A的写指令。

首先是

STM32环境下AS5048A14位磁旋转编码器SPI通讯调试记录——我学到的东西、遇到的问题、解决的过程相关推荐

  1. 基于stm32平台上的IC-MU磁绝对值编码器SPI通讯和码盘数据处理

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 IC-MU磁绝对值编码器的SPI通讯和码盘数据处理 前言 一.硬件连接 二.STM32 cubmx配置模式 三.软件代码及思路 1.寄 ...

  2. AS5048A SPI 14位磁旋转编码器

    请加扣扣技术交流群:460189483 在使用AS5048A的过程中出现的问题,分享一下,大家共勉! 1. SPI读取寄存器没有数据输出 根据数据手册SPI时序,如下 可知SPI工作在模式1,CLK的 ...

  3. 无接触式磁旋转编码器AS5040介绍

    无接触式磁旋转编码器AS5040简介 AS5040 是一款无接触式磁旋转编码器,用于精确测量整个360°内的角度.此产品是一个片上系统,在单个封装内整合了集成式Hall 元件.模拟前端和数据信号处理功 ...

  4. vivado环境下用Verilog语言实现编码器

    ** vivado环境下用Verilog语言实现编码器 ** 编码器的分类 编码器通常分为两大类: 普通编码器和优先编码器. 其中,普通编码器对某一个给定时刻只能对一个输入信号进行编码的编码器, 它的 ...

  5. Windows环境下32位汇编语言程序设计(典藏版)(含CD光盘1张)

    Windows环境下32位汇编语言程序设计(典藏版)(含CD光盘1张)(畅销10年,经典再现!) 罗云彬 著 ISBN 978-7-121-20759-4 2013年7月出版 定价:99.00元 75 ...

  6. 琢石成器――windows环境下32位汇编语言程序设计(第三版)笔记

    琢石成器――windows环境下32位汇编语言程序设计(第三版)笔记 2011年12月20日 基础篇 第1章 背景知识 1 1.1 Win32的软硬件平台 1.1.1 80x86系列处理器简史 1.1 ...

  7. Windows环境下32位汇编语言程序设计 相关资料

    Windows环境下32位汇编语言程序设计.pdf:https://474b.com/file/15153148-465076702 <Windows环境下32位汇编语言程序设计>随书光盘 ...

  8. Windows环境下32位汇编语言程序设计(典藏版)

    <Windows环境下32位汇编语言程序设计(典藏版) > 基本信息 作者: 罗云彬 出版社:电子工业出版社 ISBN:9787121207594 上架时间:2013-7-8 出版日期:2 ...

  9. windows环境下32位汇编语言程序设计 90盘_程序设计作业题汇总

    C语言程序的基本单位是函数 程序设计语言经历了"机器语言"-"汇编语言"-"高级语言"的发展过程. 编写C语言代码文件的拓展名为.c/编写C ...

最新文章

  1. java怎么打开_java开不了怎么办?java怎么打开?
  2. R语言ggplot2地理信息可视化(下)
  3. Single Page Application概览
  4. python电脑配置大概要多少钱-学python最电脑配置有要求么
  5. 关于如何防范Ⅱ、Ⅲ类银行结算账户风险
  6. 矩阵分析与多元统计12 0-1矩阵 交换矩阵与Kronecker乘积
  7. hdu 5491 The Next(数学模拟)
  8. java 2wei shuzu_JavaScript 2维数组(JavaScript 2 dimension array)
  9. 小女也爱葵花宝典---读懂编译原理(1)
  10. python3中的property使用方法
  11. Ubuntu 14.04/16.04 (使用apt-get进行安装) 安装Docker
  12. pythontransform详解_Python自定义聚合函数merge与transform区别详解
  13. CentOS 6系统FreeSwitch和RTMP服务 安装及演示(二)
  14. ArcView GIS 应用与开发技术(1)-ViewTheme
  15. 微信小程序开发-软件外包平台案例
  16. 网络访问计算机无法访问,电脑已连接网络却无法访问互联网怎么办
  17. XP系统开机显示“NTDETECT失败”
  18. 配置、账户-Windows 8学习总结 -by小雨
  19. JS 计算年龄为几岁几月几天
  20. 关于如何在WPS中生成附图索引

热门文章

  1. win10不用虚拟机部署伪分布式集群(服务部署+客户端访问)
  2. AcWing 273. 分级 (推论,DP)
  3. 【操作系统】第三章:内存管理
  4. cf游戏进不去计算机,cf更新之后进不去 穿越火线进不去解决方法
  5. leetcode cf各类比赛技巧
  6. 【收藏】B站科普硬核节目
  7. 【ESP 保姆级教程】玩转emqx认证篇③ ——认证安全之使用 MySQL 的密码认证
  8. Boboniu Plays Chess
  9. 懒汉延迟加载设计模式反射注解
  10. 2020ciscn 部分二进制WP(持续更新)