AS5600 驱动(HAL库400K硬件IIC+DMA、1MHZ软件IIC)
芯片:Stm32F103C8T6
AS5600读编码器角度主要流程:
1、先用一次带寄存器地址的 mem_read
把设备的内部地址指针定位到 RAW_ANGLE 的高字节,此次通讯一共五个字节,然后后续只做纯 read
(START
+ Addr|R
+ 2 字节 + STOP
)就能连续读取角度,省去了每次都写寄存器地址的开销。AS5600资料里明确说明 ANGLE / RAW ANGLE / MAGNITUDE 这些输出寄存器可以“重复读取而不用再写地址;
2、只做纯 read,
此次通讯一共三个字节
然后就是读编码器的方法了,AS5600最高支持1MHZ的IIC通讯,但是Stm32F103C8T6的IIC最大只有400KHZ,因此我们有HAL库400K硬件IIC阻塞读、HAL库400K硬件IIC+DMA非阻塞读和1MHZ软件IIC阻塞读这几种方法,实测发现HAL库400K硬件IIC阻塞读差不多80-90us,1MHZ软件IIC阻塞读差不多44-48us,而HAL库400K硬件IIC+DMA非阻塞读只需要6-7us左右,因此这里重点介绍DMA读取的方法,关于软件IIC实现可以看下面的代码链接
完整代码链接:AS5600驱动: 关于AS5600驱动以及软件IIC实现 - Gitee.com
AS5600驱动代码:
.c:
#include "AS5600.h"
#include "arm_math.h"
#include "Soft_IIC.h"
#define abs(x) ((x)>0?(x):-(x))
#define _2PI 6.28318530718f/********************* IIC Adapter *************************/
static int i2cWrite(uint8_t dev_addr, uint8_t *pData, uint32_t count) {int status;int i2c_time_out = I2C_TIME_OUT_BASE + count * I2C_TIME_OUT_BYTE; status = HAL_I2C_Master_Transmit(&AS5600_I2C_HANDLE, (dev_addr<<1), pData, count, i2c_time_out);//status = softi2c_master_write(&g_softi2c, dev_addr, pData, count);return status;
}static int i2cRead(uint8_t dev_addr, uint8_t *pData, uint32_t count) {int status;int i2c_time_out = I2C_TIME_OUT_BASE + count * I2C_TIME_OUT_BYTE;status = HAL_I2C_Master_Receive(&AS5600_I2C_HANDLE, ((dev_addr<<1) | 1), pData, count, i2c_time_out);//status = softi2c_master_read(&g_softi2c, dev_addr, pData, count);return status;
}static inline int i2c_Mem_Read(uint8_t dev_addr, uint8_t *pData, uint32_t count) {int status;int i2c_time_out = I2C_TIME_OUT_BASE + count * I2C_TIME_OUT_BYTE;status = HAL_I2C_Mem_Read(&AS5600_I2C_HANDLE, (dev_addr<<1), AS5600_RAW_ANGLE_REGISTER, I2C_MEMADD_SIZE_8BIT, pData, count, i2c_time_out);//status = softi2c_master_mem_read(&g_softi2c, dev_addr, AS5600_RAW_ANGLE_REGISTER, 1, pData, count);return status;
}/******************* General Function **********************/
static inline int bsp_as5600GetRawAngle(uint16_t *raw_angle) {int ret;uint8_t buffer[2] = {0};
// uint8_t raw_angle_register = AS5600_RAW_ANGLE_REGISTER;
// ret = i2cWrite(AS5600_RAW_ADDR, &raw_angle_register, 1);ret = i2cRead(AS5600_RAW_ADDR, buffer, 2);*raw_angle = ((uint16_t)buffer[0] << 8) | (uint16_t)buffer[1];return ret;
}int bsp_as5600GetAngle(volatile float *Angle) {int ret;uint16_t angle_data;ret = bsp_as5600GetRawAngle(&angle_data);*Angle = (angle_data / (float)AS5600_RESOLUTION)*_2PI;return ret;
}// Read STATUS register (1 byte)
int AS5600_ReadStatus(uint8_t *status)
{int ret;uint8_t reg = AS5600_STATUS_REGISTER;ret = i2cWrite(AS5600_RAW_ADDR, ®, 1);if (ret != HAL_OK) return ret;ret = i2cRead(AS5600_RAW_ADDR, status, 1);return ret;
}// Execute BURN_ANGLE (write 0x80 into 0xFF). Caller must ensure MD=1 and hardware power requirements.
// WARNING: irreversible and limited number of times. Check datasheet before using.
int AS5600_BurnAngle(void)
{uint8_t buf[2];int ret;buf[0] = AS5600_BURN_REGISTER;buf[1] = 0x80U; // BURN_ANGLE commandret = i2cWrite(AS5600_RAW_ADDR, buf, 2);if (ret != HAL_OK) return ret;// datasheet recommends waiting a bit and optionally verifying; wait small timeHAL_Delay(5);return HAL_OK;
}//配置AS5600的起始位置(ZPOS)
int bsp_AS5600_SetZero(uint8_t Burn)
{int ret;uint16_t RAWangle, ZPOS_RAWangle; uint8_t buf[3];ret = bsp_as5600GetRawAngle(&RAWangle);if (ret != HAL_OK) return ret;uint8_t msb = (RAWangle >> 8) & 0x0F; // ZPOS(11:8)uint8_t lsb = RAWangle & 0xFF; // ZPOS(7:0)buf[0] = AS5600_ZPOS11_8_REGISTER; // start registerbuf[1] = msb;buf[2] = lsb;ret = i2cWrite(AS5600_RAW_ADDR, buf, 3);if (ret != HAL_OK) return ret;HAL_Delay(2);/******* Verify *******/uint8_t buffer[2] = {0};uint8_t raw_angle_register = AS5600_ZPOS11_8_REGISTER;ret = i2cWrite(AS5600_RAW_ADDR, &raw_angle_register, 1);ret = i2cRead(AS5600_RAW_ADDR, buffer, 2);ZPOS_RAWangle = ((uint16_t)buffer[0] << 8) | (uint16_t)buffer[1];if((Burn == 1)&&(ret == HAL_OK)&&(ZPOS_RAWangle == RAWangle)){uint8_t status;if (AS5600_ReadStatus(&status) == HAL_OK) {if (status & AS5600_STATUS_MD_MASK) {// magnet detected -> allowed to burn// double-check power, caps, magnet position before calling:ret = AS5600_BurnAngle();} else {// magnet not detected: do not burn}}} return ret;
}/******************* User Function **********************/Encoder_AS5600_t Encoder_AS5600;void bsp_as5600Init(void) {/* init i2c interface */uint8_t buffer[2] = {0};i2c_Mem_Read(AS5600_RAW_ADDR, buffer, 2); //先定位到RAWangle寄存器,后续直接iic_read/* init var */Encoder_AS5600.rotation_circles = 0;bsp_as5600GetAngle(&Encoder_AS5600.old_angle);Encoder_AS5600.n_timeout = 4;Encoder_AS5600.dma_transfer_complete = true;
}int bsp_as5600_DMAGetAngle(volatile float *Angle) {HAL_StatusTypeDef st;/*DMA 通讯*/if(Encoder_AS5600.dma_transfer_complete){Encoder_AS5600.DMA_cnt = 0;Encoder_AS5600.dma_transfer_complete = false;st = HAL_I2C_Master_Receive_DMA(&AS5600_I2C_HANDLE, AS5600_READ_ADDR, Encoder_AS5600.DMA_rxbuffer, 2);}else{Encoder_AS5600.DMA_cnt++;}/*DMA 超时判断*/if(Encoder_AS5600.DMA_cnt >= Encoder_AS5600.n_timeout){Encoder_AS5600.DMA_cnt = 0;// 简单复位 I2C 状态(依 HAL 版本而异)__HAL_I2C_DISABLE(&AS5600_I2C_HANDLE);__HAL_I2C_ENABLE(&AS5600_I2C_HANDLE);st = HAL_I2C_Master_Receive_DMA(&AS5600_I2C_HANDLE, AS5600_READ_ADDR, Encoder_AS5600.DMA_rxbuffer, 2);}*Angle = Encoder_AS5600.angle;/*获取编码器角度*/return st;
}// DMA 传输完成回调函数
void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c)
{if (hi2c == &AS5600_I2C_HANDLE) // 确保是我们的 I²C 设备{uint16_t raw_angle = ((uint16_t)Encoder_AS5600.DMA_rxbuffer[0] << 8) | (uint16_t)Encoder_AS5600.DMA_rxbuffer[1];Encoder_AS5600.angle = (raw_angle/(float)AS5600_RESOLUTION)*_2PI;Encoder_AS5600.dma_transfer_complete = true;}
}.h:
#ifndef __BSP_AS5600_H
#define __BSP_AS5600_H#include "i2c.h"
#include <stdbool.h>#define AS5600_I2C_HANDLE hi2c1#define I2C_TIME_OUT_BASE 10
#define I2C_TIME_OUT_BYTE 1/*
注意:AS5600的地址0x36是指的是原始7位设备地址,而ST I2C库中的设备地址是指原始设备地址左移一位得到的设备地址
*/#define AS5600_RAW_ADDR 0x36
#define AS5600_ADDR (AS5600_RAW_ADDR << 1)
#define AS5600_WRITE_ADDR (AS5600_RAW_ADDR << 1)
#define AS5600_READ_ADDR ((AS5600_RAW_ADDR << 1) | 1)#define AS5600_RESOLUTION 4096 //12bit Resolution
#define AS5600_RAW_ANGLE_REGISTER 0x0C //only read#define AS5600_STATUS_REGISTER 0x0B //only read
// Many implementations use 0x20 for MD (bit5). If unsure, confirm with your datasheet revision.
#define AS5600_STATUS_MD_MASK 0x20U#define AS5600_ZPOS11_8_REGISTER 0x01
#define AS5600_ZPOS7_0_REGISTER 0x02#define AS5600_BURN_REGISTER 0xFF //only write
#define Burn_Angle 0x80
#define Burn_Setting 0x80
//在寄存器0xFF内写入值0x80来执行烧录角度命令。烧录角度命令最多可以执行三次
//在寄存器0xFF内写入值0x40来执行烧录设置命令。只有在ZPOS和MPOS从未被永久性写入时(ZMCO = 00)才可对MANG进行写入。 烧录设置命令只能被执行一次。 typedef struct {volatile float angle; volatile float old_angle;float angle_init; //初始角度float angle_zero_offset; //机械零点和电气零点偏移量float eleangle; //根据电机极对数换算的电角度float Speed_angle_deta; //速度角度值(计算速度),即两次角度之差float Speed_RPM; //电机旋转速度 uint8_t Move_State; //电机旋转状态 1为正转 0为反转uint8_t Sample_Mode; //采样模式,分为速度采样和位置采样、uint32_t rotation_circles; //转过的整圈数 只算一个方向,正转一圈加1,反转一圈减1float rotation_angle;uint32_t rotation_circles_absolute; //转过的整圈数,正反转都增加,为正值float rotation_angle_absolute;/*** DMA variable***/volatile bool dma_transfer_complete;uint8_t DMA_rxbuffer[2];uint8_t DMA_cnt; // 等待DMA获取次数uint8_t n_timeout; // 超时阈值(n)}Encoder_AS5600_t;
extern Encoder_AS5600_t Encoder_AS5600;#define AS5600_DEFAULTS {0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0, 2,0,0,0,0,0} // 初始化参数int AS5600_ReadAngle_SoftI2C(volatile float *Angle); void bsp_as5600Init(void);
int bsp_as5600GetRawAngle(uint16_t *raw_angle) ;
int bsp_as5600GetAngle(volatile float *Angle) ;
int bsp_AS5600_SetZero(uint8_t Burn);int bsp_as5600_DMAGetAngle(volatile float *Angle) ;
#endif /* __BSP_AS5600_H */
我们通过在/********************* IIC Adapter *************************/中可以更换我们的IIC具体实现,/******************* General Function **********************/中就是HAL库IIC阻塞读,/******************* User Function **********************/中就是IIC+DMA非阻塞读
使用示例:
我们上电先强拖电机到电角度零点,获取电角度偏移量(AS5600也支持写入电角度零点,这里不做具体介绍),然后在main或者自己的foc中断中调用bsp_as5600GetAngle(&Encoder_AS5600.angle);或者bsp_as5600_DMAGetAngle(&Encoder_AS5600.angle);获取AS5600编码器角度即可
void main_user(void)
{.../******* 用户区初始化 **********///SoftI2C_RegisterAndInit(1000000, SystemCoreClock);bsp_as5600Init();//校准传感器与电机零点偏移角度HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_2);HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_3);TIM1->CCR1 = PWM_HalfPerMax * 13/10; // A U // d轴强拖,形成SVPWM模型中的基础矢量1,即对应转子零度位置TIM1->CCR2 = PWM_HalfPerMax; // B VTIM1->CCR3 = PWM_HalfPerMax; // C WHAL_Delay(200); float angle_zero_offset;bsp_as5600GetAngle(&angle_zero_offset);Encoder_AS5600.angle_zero_offset = - angle_zero_offset;TIM1->CCR1 = PWM_HalfPerMax; // A U // 松开电机TIM1->CCR2 = PWM_HalfPerMax; // B VTIM1->CCR3 = PWM_HalfPerMax; // C Wbsp_as5600Init();/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */uint32_t previousMillis = 0;const uint32_t interval = 10; // 单位 ms 间隔while (1){// 获取当前的系统时间uint32_t currentMillis = HAL_GetTick();// 检查是否已达到间隔时间if (currentMillis - previousMillis >= interval) {previousMillis = currentMillis; // 更新上次执行的时间bsp_as5600GetAngle(&Encoder_AS5600.angle);//bsp_as5600_DMAGetAngle(&Encoder_AS5600.angle);}}
}
上面几种方法都试了一下,开环强拖电机用IIC读AS5600编码器角度,能正常读取,截图如下: