4. I2C实战:使用STM32硬件I2C驱动电机编码器(AS5600),读取角度数据

好,咱们直接进入正题。前面讲了I2C协议的原理和时序,今天就来点实在的——用STM32的硬件I2C外设,驱动一个非常常见的磁编码器AS5600,把电机的绝对角度读出来。

为什么选AS5600?说白了,这芯片在直流无刷电机和舵机项目里太常见了。12位分辨率,0.0879°的精度,价格还便宜。我最早在做一个云台稳定器的时候用过它,当时被I2C时序坑了一整天,后来才发现是STM32的I2C硬件模块有个“坑”。嗯,今天我会把这个坑也讲清楚。

本章目标: 从零搭建一个STM32 + AS5600的I2C读取工程,输出0~360°的角度值,并验证数据稳定性。

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就是实际角度。简单吧?

我的习惯: 每次读角度时,连续读两个字节(0x0C和0x0D),不要分两次单字节读。因为电机在转,分两次读可能读到“跨边界”的数据,角度会跳变。这在高速电机上尤其明显。

4.2 硬件连接与注意事项

STM32和AS5600的连接,标准I2C接线:SCL、SDA、VCC、GND。但有几个细节,我吃过亏,必须说清楚。

  1. 上拉电阻: AS5600模块通常自带4.7kΩ上拉,但如果你自己画板子,记得加。没有上拉,I2C直接罢工。
  2. 电平匹配: STM32F103是3.3V,AS5600也是3.3V,直接连。但如果你用5V的AS5600模块,SDA/SCL必须加电平转换,否则烧IO口。
  3. 磁铁安装: 径向充磁的磁铁,必须正对芯片中心。偏移超过0.5mm,角度误差就会明显增大。我曾在项目里用3D打印做了一个对中工装,才解决了批量一致性问题。
我曾经踩过的坑: 第一次调试时,角度数据死活读出来全是0xFF。查了半天,发现是SDA线虚焊了。I2C的SDA如果浮空,上拉会把电平拉高,读到的数据就是0xFF。所以,读到0xFF先别怀疑代码,拿示波器看波形。

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;
}
为什么先发寄存器地址,再收数据? 这是I2C复合操作的典型用法。先写寄存器指针,然后主机重新发送起始条件,切换为读模式,从机就会从该寄存器开始连续输出数据。HAL库的Transmit/Receive分开调用,内部会自动处理重复起始条件。

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模式
关于STM32硬件I2C的“坑”: 早期STM32F1系列的硬件I2C确实有bug,在总线繁忙时可能卡死。如果你用的是F103,建议在每次通信前加一个I2C总线复位:拉低SCL时钟9个周期,再发一个停止条件。或者,直接换用软件I2C模拟,虽然占用CPU,但绝对可靠。

4.7 本章知识结构图

下面这张图,把AS5600读取的整个流程串起来了。从硬件连接到软件调用,一目了然。

AS5600 I2C读取流程结构图 AS5600 I2C 角度读取流程 STM32 硬件I2C SCL / SDA / 上拉电阻 AS5600 磁编码器 I2C地址: 0x36 CubeMX 配置 I2C1 100kHz / 7位地址 / 轮询模式 写寄存器指针 发送 0x0C 连续读2字节 高8位 + 低8位 拼接 & 掩码 raw & 0x0FFF 角度转换 degree = raw * 360 / 4096 串口打印 / 控制算法使用 注意:先写指针,再连续读 避免分两次单字节读取

4.8 小结与经验分享

好了,这一章的内容就这些。总结一下核心要点:

  • AS5600的I2C地址是0x36,读写时记得左移一位。
  • 角度寄存器在0x0C~0x0D,连续读取两个字节,拼成12位数据。
  • STM32硬件I2C配置为100kHz,轮询模式最简单,适合初学者。
  • 遇到数据异常,先查硬件连接,再看波形,最后怀疑代码。

我记得第一次调通AS5600的时候,看着串口里连续变化的角度值,心里还是挺有成就感的。这个芯片虽然简单,但它是很多电机控制项目的“眼睛”。没有准确的角度反馈,FOC、速度环、位置环都无从谈起。