玩转AS5600磁编码器
本文最后更新于28 天前,其中的信息可能已经过时,如有错误请发送邮件到canke137@foxmail.com

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支持设定测量角度的零点和终点。

后续施工中……

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇