4. I2C实战:使用STM32硬件I2C驱动电机编码器(AS5600),读取角度数据
好,咱们直接进入正题。前面讲了I2C协议的原理和时序,今天就来点实在的——用STM32的硬件I2C外设,驱动一个非常常见的磁编码器AS5600,把电机的绝对角度读出来。
为什么选AS5600?说白了,这芯片在直流无刷电机和舵机项目里太常见了。12位分辨率,0.0879°的精度,价格还便宜。我最早在做一个云台稳定器的时候用过它,当时被I2C时序坑了一整天,后来才发现是STM32的I2C硬件模块有个“坑”。嗯,今天我会把这个坑也讲清楚。
4.1 AS5600的关键特性与寄存器映射
先看一眼AS5600的“身份证”。这芯片是奥地利微电子(ams)出的,内部有一个霍尔传感器阵列,检测径向磁铁的磁场角度。你只需要在电机转轴上粘一个径向充磁的磁铁,芯片正对着它,就能读出绝对角度。
| 参数 | 值 | 备注 |
|---|---|---|
| 分辨率 | 12 bit (0~4095) | 对应0~360° |
| 角度精度 | ±0.0879° | 理论值,实际受安装影响 |
| I2C地址 | 0x36 (7位) | 左对齐后为0x6C |
| 最大I2C速率 | 400 kHz (Fast Mode) | 实测100kHz更稳定 |
| 工作电压 | 3.3V / 5V | 注意电平匹配 |
我们需要关注的寄存器其实不多,核心就两个:
- 0x0C – RAW ANGLE (高8位):角度数据的高字节
- 0x0D – RAW ANGLE (低8位):角度数据的低字节
把这两个字节拼起来,得到一个0~4095的数值,乘以360/4096就是实际角度。简单吧?
4.2 硬件连接与注意事项
STM32和AS5600的连接,标准I2C接线:SCL、SDA、VCC、GND。但有几个细节,我吃过亏,必须说清楚。
- 上拉电阻: AS5600模块通常自带4.7kΩ上拉,但如果你自己画板子,记得加。没有上拉,I2C直接罢工。
- 电平匹配: STM32F103是3.3V,AS5600也是3.3V,直接连。但如果你用5V的AS5600模块,SDA/SCL必须加电平转换,否则烧IO口。
- 磁铁安装: 径向充磁的磁铁,必须正对芯片中心。偏移超过0.5mm,角度误差就会明显增大。我曾在项目里用3D打印做了一个对中工装,才解决了批量一致性问题。
4.3 STM32硬件I2C配置(以HAL库为例)
我个人习惯用STM32CubeMX生成初始化代码,然后手动优化。下面给出关键配置步骤:
- 时钟: I2C时钟源选择APB1,通常设为36MHz或72MHz分频。
- 速率: 设为100kHz(Standard Mode)。虽然AS5600支持400kHz,但100kHz更稳定,抗干扰能力更强。
- 地址: 7位地址0x36,注意HAL库需要左移一位,即0x6C。
- DMA: 如果只是单次读取,用轮询模式就够了。如果要在控制循环里高频读取,建议用DMA+中断。
// I2C句柄定义
I2C_HandleTypeDef hi2c1;
// MX_I2C1_Init() 中的关键参数
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000; // 100kHz
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
HAL_I2C_Init(&hi2c1);
4.4 读取角度数据的核心代码
下面这段代码,是我在多个项目里验证过的。直接复制就能用,但建议你理解每一行的含义。
#include "as5600.h"
#define AS5600_ADDR 0x36 // 7位地址
#define AS5600_ADDR_WRITE (AS5600_ADDR << 1) // 0x6C
#define AS5600_ADDR_READ ((AS5600_ADDR << 1) | 0x01) // 0x6D
#define REG_RAW_ANGLE_HIGH 0x0C
#define REG_RAW_ANGLE_LOW 0x0D
/**
* @brief 读取AS5600原始角度值 (0~4095)
* @param hi2c: I2C句柄指针
* @retval 12位角度值,失败返回0xFFFF
*/
uint16_t AS5600_ReadRawAngle(I2C_HandleTypeDef *hi2c)
{
uint8_t reg = REG_RAW_ANGLE_HIGH;
uint8_t buf[2] = {0};
uint16_t raw = 0;
// 1. 发送寄存器地址 (写操作)
if (HAL_I2C_Master_Transmit(hi2c, AS5600_ADDR_WRITE, ®, 1, 100) != HAL_OK)
{
return 0xFFFF; // 通信失败
}
// 2. 连续读取2个字节 (高字节在前)
if (HAL_I2C_Master_Receive(hi2c, AS5600_ADDR_READ, buf, 2, 100) != HAL_OK)
{
return 0xFFFF;
}
// 3. 拼接数据
raw = ((uint16_t)buf[0] << 8) | buf[1];
raw &= 0x0FFF; // 只保留低12位
return raw;
}
/**
* @brief 将原始值转换为角度 (0.0 ~ 359.9°)
*/
float AS5600_RawToDegree(uint16_t raw)
{
if (raw > 4095) return -1.0f;
return (float)raw * 360.0f / 4096.0f;
}
4.5 主循环调用与数据验证
在主循环里,每10ms读一次角度,并通过串口打印出来。你可以用手慢慢转动磁铁,观察角度是否连续变化。
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
uint16_t raw;
float degree;
char msg[64];
while (1)
{
raw = AS5600_ReadRawAngle(&hi2c1);
if (raw != 0xFFFF)
{
degree = AS5600_RawToDegree(raw);
sprintf(msg, "Raw: %4d, Degree: %5.1f°\r\n", raw, degree);
HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), 100);
}
else
{
HAL_UART_Transmit(&huart1, (uint8_t*)"I2C Error!\r\n", 13, 100);
}
HAL_Delay(10); // 100Hz读取频率
}
}
4.6 常见问题与排查思路
如果你发现读到的数据不对,别慌。按下面的顺序排查,90%的问题都能解决。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 一直返回0xFFFF | I2C通信失败 | 用示波器看SCL/SDA波形;检查上拉电阻 |
| 返回0x0FFF或0x0000 | 寄存器地址错误 | 确认0x0C/0x0D是否正确;检查地址左移 |
| 角度跳变很大 | 磁铁安装偏移 | 重新对中磁铁;检查是否径向充磁 |
| 数据偶尔卡住 | I2C时钟延展问题 | 降低速率到50kHz;开启NoStretch模式 |
4.7 本章知识结构图
下面这张图,把AS5600读取的整个流程串起来了。从硬件连接到软件调用,一目了然。
4.8 小结与经验分享
好了,这一章的内容就这些。总结一下核心要点:
- AS5600的I2C地址是0x36,读写时记得左移一位。
- 角度寄存器在0x0C~0x0D,连续读取两个字节,拼成12位数据。
- STM32硬件I2C配置为100kHz,轮询模式最简单,适合初学者。
- 遇到数据异常,先查硬件连接,再看波形,最后怀疑代码。
我记得第一次调通AS5600的时候,看着串口里连续变化的角度值,心里还是挺有成就感的。这个芯片虽然简单,但它是很多电机控制项目的“眼睛”。没有准确的角度反馈,FOC、速度环、位置环都无从谈起。