AS5600
介绍
AS5600
是一个易于编程的磁旋转位置传感器,具有高分辨率的12位模拟或PWM输出。该传感器采用非接触式系统,可测量磁化轴上直径的绝对角度,并适用于应用于非接触式电位器的稳健设计,消除了外部杂散磁场的影响。
AS5600
的设计目的是为了满足工业标准,并通过I²C接口支持简单的用户编程,无需专门的程序员即可设置非易失性参数。默认情况下,输出表示0到360度的范围。此外,用户还可以定义一个较小的输出范围,并通过编程设置零角(起始位置)和最大值角度(停止位置)。
此外,AS5600
还配备了智能低功耗模式,可自动降低功耗。在使用时,输入引脚(DIR)可选择输出的极性以反映旋转方向。如果DIR接地,则输出值随顺时针旋转而增加;如果DIR连接到VDD,则输出值逆时针旋转而增加。
芯片硬件
查看芯片手册得到芯片的引脚定义和电源管理。
引脚定义
电源管理
AS5600
支持两种供电方案:AS5600
由片上5.0V电源供电LDO稳压器,或者它可以直接从3.3V电源供电。内部LDO不用于为其他外部ic供电,需要1 μF的电容接地,如下图所示。3.3V工作时,VDD5V和VDD3V3引脚必须绑扎在一起。
应用电路
我这里选择3.3V供电,DIR引脚接VDD,选择方向为逆时针时增加。大致电路如下:
代码编写
设计完硬件电路之后,可以开始编写驱动程序了。
我使用的单片机是STM32F103C8T6,开发环境是Keil5+STM32CubeMX使用HAL库。
创建工程
这里我只会简单说明工程的创建,不会详细说明CubeMX的使用。(ps:或许后面我会专门写一篇吧( ̄︶ ̄)↗)
首先配置高速时钟。
记得把调试串口线配置好。(ps:不然会出奇怪的问题,比如读不到芯片什么的)
将PB8和PB9设置为Output模式并改名为SCL和SDA,再将引脚速度设置为High。
开启USART1方便使用串口调试。
设置时钟树为72Mhz。
配置完成之后设置下工程生成,选择为每个外设都生成.c/h文件。
打开创建好的工程,手动创建Hardware文件夹。
在Hardware文件夹中创建文本文档手动改后缀为我们需要的.c/h文件。
Keil5的使用同样不详细说明,打开新建好的工程添加Hardware文件夹和创建的.c/h文件。
别忘了在魔术棒里添加Hardware文件夹的路径。
再为我们创建的.c/h文件声明头文件。
最后编译一下没问题工程就创建好了。
工程创建完成后就可以开始正式编写代码了。
IIC通讯
AS5600有PWM输出和IIC通讯两种输出方式,我们使用IIC通讯方式。
IIC简介
IIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。
在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。
IIC一共有只有两个总线: 一条是双向的串行数据线SDA,一条是串行时钟线SCL
SDA(Serial data)是数据线,D代表Data也就是数据,Send Data 也就是用来传输数据的
SCL(Serial clock line)是时钟线,C代表Clock 也就是时钟 也就是控制数据发送的时序的
引脚配置
基本了解IIC后我们知道IIC是两线式串行总线,其中SDA是数据线,SCL是时钟线。要想单片机使用IIC通讯那么首先我们需要先定义IIC的两条总线。
这里我使用的是HAl库所以我直接使用HAL库的函数。
void IIC_SCL(uint8_t BitValue)
{
if(BitValue==1) HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_SET);
else HAL_GPIO_WritePin(GPIOB,GPIO_PIN_8,GPIO_PIN_RESET);
/*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
}
void IIC_SDA(uint8_t BitValue)
{
if(BitValue) HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET);
else HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET);
/*如果单片机速度过快,可在此添加适量延时,以避免超出I2C通信的最大速度*/
}
因为SDA总线需要写和读数据,所以SDA引脚需要切换输出和输入模式。这里需要写一下SDA引脚输入和输出模式切换的函数。
#define SDA_IN() {SDA_GPIO_Port->CRH&=0XFFFFFF0F;SDA_GPIO_Port->CRH|=8<<4;}//输入模式上拉输入模式
#define SDA_OUT() {SDA_GPIO_Port->CRH&=0XFFFFFF0F;SDA_GPIO_Port->CRH|=3<<4;}//输出模式(50Mhz)通用推挽输出模式
这里我使用配置GPIO寄存器的方式切换SDA引脚的输入输出模式,看不懂的朋友自行访问:STM32 GPIO的配置寄存器(CRL、CRH)输入输出模式配置
因为IIC通讯有速度上限,所以我们还需要写一个简单的延时函数。
void I2C_Delay(uint32_t delay)
{
while(--delay); //dly=100: 8.75us;(SYSCLK=72MHz)
}
#define i2c_delay I2C_Delay(50);
定义完总线和延时函数之后就可以去看芯片的时序图了,下图是时序图。
看完时序图后我们先编写基本的IIC通讯。
起始信号
起始信号:SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。
通过代码来实现:
void IIC_Start(void)
{
SDA_OUT(); //SDA设置为输出模式
IIC_SDA(1); //释放SDA,确保SDA为高电平
IIC_SCL(1); //释放SCL,确保SCL为高电平
IIC_SDA(0); //在SCL高电平期间,拉低SDA,产生起始信号
IIC_SCL(0); //起始后把SCL也拉低,即为了占用总线,也为了方便总线时序的拼接
}
停止信号
停止信号:SCL保持高电平。SDA由低电平变为高电平。
通过代码来实现:
void IIC_Stop(void)
{
SDA_OUT(); //SDA设置为输出模式
IIC_SDA(0); //拉低SDA,确保SDA为低电平
IIC_SCL(1); //释放SCL,使SCL呈现高电平
IIC_SDA(1); //在SCL高电平期间,释放SDA,产生终止信号
}
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。
应答信号
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答
- 应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
- 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
每发送一个字节(8个bit)在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须回一个ACK应答信号给发送器,这样才能进行数据传输。
应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,低电平0表示应答,1表示非应答,
用代码实现:
/**
* 函 数:主机产生应答信号
* 参 数:无
* 返 回 值:无
*/
void IIC_Ack(void)
{
IIC_SCL(0);
SDA_OUT();
IIC_SDA(0);
IIC_SCL(1);
IIC_SCL(0);
}
/**
* 函 数:主机不产生应答信号
* 参 数:无
* 返 回 值:无
*/
void IIC_NAck(void)
{
IIC_SCL(0);
SDA_OUT();
IIC_SDA(1);
IIC_SCL(1);
IIC_SCL(0);
}
等待从机的应答信号:
#define READ_SDA (SDA_GPIO_Port->IDR & SDA_Pin) //读取SDA引脚状态
/**
* @brief 等待应答信号
* @param 无
* @retval 1,接受应答失败
* @retval 0,接受应答成功
*/
unsigned char IIC_Wait_Ack(void)
{
volatile unsigned char ucErrTime=0;
SDA_IN();
IIC_SDA(1);
IIC_SCL(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL(0);
return 0x00;
}
因为要读取来自从机的应答信号,所以为了方便还需要写一个读取SDA状态的宏定义。
发送一个字节
发送一个字节数据:
/**
* 函 数:IIC发送一个字节
* 参 数:Byte 要发送的一个字节
* 返 回 值:无
*/
void IIC_SendByte(uint8_t Byet)
{
uint8_t i;
SDA_OUT();
IIC_SCL(0); //拉低时钟线开始发数据
for(i=0; i<8; i++)
{
if(Byet&0x80){IIC_SDA(1);}
else IIC_SDA(0);
Byet <<=1;
IIC_SCL(1);
IIC_SCL(0);
}
}
读取一个字节
读取一个字节数据:
/**
* 函 数:IIC读取一个字节
* 参 数:ack,ack=1时发送ACK,ack=0时发送NACK
* 返 回 值:读取的字节
*/
uint8_t IIC_RendByte(unsigned char ack)
{
uint8_t i,iic_data;
SDA_IN();
iic_data=0;
for(i=0;i<8;i++)
{
IIC_SCL(0);
IIC_SCL(1);
iic_data<<=1;
if(READ_SDA){iic_data++;}
}
if(!ack)
IIC_NAck();
else
IIC_Ack();
return iic_data;
}
IIC数据格式
IIC的每一帧数据由9bit组成,
如果是发送数据,则包含 8bit数据+1bit ACK,
如果是设备地址数据,则8bit包含7bit设备地址 1bit方向
要想从设备通讯需在起始信号后发送从设备地址信号(7位),第八位是读写位,为“0”表示主机发送数据(W),“1”表示主机接收数据 (R),第9位是ACK应答位,随后就是第一个数据字节,之后又是1位应答,再随后是第二个数据字节。
向从机发送多个字节
这里就不赘述那么多了,直接上代码。
/**
* 函 数:向设备写多字节
* 参 数:dev_addr 设备地址,reg_addr 寄存器地址,i2c_len 字节数,*i2c_databuf 发送的数据
* 返 回 值:0x00
*/
uint8_t IIC_Write8(uint8_t dev_addr, uint8_t reg_addr, uint8_t iic_len, uint8_t *iic_databuf)
{
uint8_t i;
IIC_Start();
IIC_SendByte(dev_addr << 1 | 0x00); //发送从机地址,设主机为写数据模式
IIC_Wait_Ack(); //等待从机应答信号
IIC_SendByte(reg_addr); //发送寄存器地址
IIC_Wait_Ack(); //等待从机应答信号
for(i=0; i<iic_len; i++)
{
IIC_SendByte(iic_databuf[i]); //发送数据
IIC_Wait_Ack(); //等待从机应答信号
}
IIC_Stop();
return 0x00;
}
从从机读取多个字节
同样直接上代码。
/**
* 函 数:从设备读取多字节
* 参 数:dev_addr 设备地址,reg_addr 寄存器地址,iic_len 读取字节数,*iic_databuf 读取的数据
* 返 回 值:0x00
*/
uint8_t IIC_Read8(uint8_t dev_addr, uint8_t reg_addr, uint8_t iic_len, uint8_t *iic_databuf)
{
IIC_Start();
IIC_SendByte(dev_addr << 1 | 0x00); //发送从机地址,设主机为写数据模式
IIC_Wait_Ack(); //等待从机应答信号
IIC_SendByte(reg_addr); //发送要读取的地址
IIC_Wait_Ack(); //等待从机应答信号
IIC_Start();
IIC_SendByte(dev_addr << 1 | 0x01); //发送从机地址,设主机为读数据模式
IIC_Wait_Ack(); //等待从机应答信号
while(iic_len)
{
if(iic_len==1){*iic_databuf=IIC_RendByte(0);} //只读一个字节数据不发声应答信号
else *iic_databuf =IIC_RendByte(1); //读多个字节数据发送应答信号
iic_databuf++; //将指针指向下一个内存位置
iic_len--;
}
IIC_Stop();
return 0x00;
}
好了,IIC通讯部分就结束了,接下来可以正式使用AS5600了。
读取AS5600角度数据
AS5600的7位设备地址永久是0x36(二进制为0110110)
这是AS5600的寄存器表。
为了方便使用,我们在程序中建一个枚举类型来存储AS5600的寄存器地址。
typedef enum
{
Slave_Addr = 0x36, //设备地址
_zmco = 0x00, //零点偏移寄存器
_zpos_hi = 0x01, //零点位置高低位寄存器
_zpos_lo = 0x02,
_mpos_hi = 0x03, //终点位置高低位寄存器
_mpos_lo = 0x04,
_mang_hi = 0x05, //角度范围高低位寄存器
_mang_lo = 0x06,
_conf_hi = 0x07, //配置寄存器的高低位
_conf_lo = 0x08,
_raw_ang_hi = 0x0c, //原始角度高低位寄存器
_raw_ang_lo = 0x0d,
_ang_hi = 0x0e, //角度高低位寄存器
_ang_lo = 0x0f,
_stat = 0x0b, //状态寄存器
_agc = 0x1a, //自动增益控制寄存器
_mag_hi = 0x1b, //磁场强度高低位寄存器
_mag_lo = 0x1c,
_burn = 0xff //烧录寄存器
}AMS5600Registers_t;
如果要读取角度数据,访问角度高低位寄存器即可。
为了方便读取寄存器数据,写一个从指定寄存器读取字节的函数。
/**
* 函 数:从寄存器读取一个字节
* 参 数:in_addr 寄存器地址
* 返 回 值:数据
*/
uint8_t ReadOneByte(uint8_t in_addr)
{
uint8_t retval=0;
IIC_Read8(Slave_Addr,in_addr,1,&retval);
return retval;
}
/**
* 函 数:从寄存器读取2个字节
* 参 数:addr_hi 寄存器地址,addr_lo 寄存器地址
* 返 回 值:数据
*/
uint16_t ReadTowByte(uint8_t addr_hi, uint8_t addr_lo)
{
uint16_t retval=0;
uint8_t hi=0, lo=0;
lo=ReadOneByte(addr_lo);
hi=ReadOneByte(addr_hi);
retval=hi<<8;
retval=retval|lo;
return retval;
}
这里我们获得的角度数据是0-4095(12bit)之间的值,所以还需给他换算成角度。
/**
* 函 数:将角度数据转换为角度值
* 参 数:newAngle 角度数据
* 返 回 值:角度值
*/
float RawAngle(uint16_t newAngle)
{
float retval = newAngle * 0.08789;//将12位数据转化360°,即0-4095每上升一点增加0.087°
return retval;
}
再写一个串口输出函数,试着从芯片中读取角度数据再由串口发送出去。
要直接使用printf()
函数打印串口数据需先重定向,不懂的自行访问:STM32-HAL库-printf函数重定向(USART应用实例)
/**
* 函 数:输出AS5600读取的角度值
* 参 数:无
* 返 回 值:无
*/
uint16_t rawdata=0;
float degress =0;
void AS5600_Angle(void)
{
rawdata = ReadTowByte(0x0e,0x0f);
// printf("%d\n",rawdata);
degress = RawAngle(rawdata);
printf("%f\n",degress);
}
写完后在AS5600.h文件中声明AS5600_Angle()
函数,之后在main.c中包含AS5600.h就可以调用了。
在程序主循环中调用AS5600_Angle()
函数并加上延时。
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
AS5600_Angle();
HAL_Delay(10);
}
传感器正确和单片机连接之后烧录程序,在串口调试助手中就可以看到角度数据。
获取AS5600状态
AS5600内部有存储状态的寄存器STAUS
。
STAUS
寄存器中的MD、ML、MH代表了以下状态:
- MH:AGC最小增益溢出,磁体太强。
- ML:AGC最大增益溢出,磁体太弱。
- MD:检测到磁体。
代码使用:
/**
* 函 数:检查AS5600状态
* 参 数:无
* 返 回 值:retVal 返回0表示没检测到磁体;1表示检测到磁体,磁体较弱;2表示磁体较强
*/
uint8_t TestStatus(void)
{
uint8_t magStatus;
uint8_t retVal;
magStatus = ReadOneByte(_stat);//读取stat寄存器值
if(magStatus & 0x20)//检查第3位是否为1
{
if(magStatus & 0x10) retVal=1;//检查第4位
else if(magStatus & 0x08) retVal=2;//检查第5位
}
else retVal=0;//第三位为0则返回0
return retVal;
}
设置零点位置和终点位置
AS5600支持设定测量角度的零点和终点。