STM32硬件I2C读取AS5600角度传感器:寄存器时序与驱动源码解析
发布时间:2026/9/13 9:08:02
分类:文化教育
浏览:1234

简介这是面向STM32F103RCT6的AS5600角度编码器硬件I2C驱动源码适合正在学习I2C通信或需要读取角度数据的单片机开发者。程序基于标准外设库实现包含I2C1初始化、设备地址宏定义、角度寄存器读写函数封装并配合定时器1ms调度、串口打印和LED闪烁指示可直观观察模块工作状态。压缩包共197个文件以C语言源文件、头文件和Keil工程文件为主同时附带编译生成的.o/.axf/.hex等输出文件整体约5.38MB目录内容完整便于参考或直接移植。已有506人学习适合快速验证AS5600基本功能也可作为STM32硬件I2C时序与多文件工程组织的示例。对于入门者这套源码能省去查手册和调试时序的重复工作直接关注角度数据应用。1. 用 STM32F103RCT6 硬件 I2C 读 AS5600先从 0x36 地址和 12 位数据说起AS5600 角度编码器输出 12 位角度数据单圈分辨率约 0.088°通过 I2C 或 PWM 都能读回磁铁旋转位置STM32F103RCT6 自带两个硬件 I2C 外设完全可以直接把 AS5600 的寄存器内容读出来不必像很多工程那样改成 GPIO 模拟 I2C。实际项目里把硬件 I2C 写废掉的情况多半是 F1 的事件标志清理顺序不对或者在读多字节时 NACK/STOP 的时机没卡准问题并不在外设本身。这篇按照 AS5600 的寄存器映射给出一套能直接改用的驱动程序源码同时把从机地址、上拉电阻、总线复位这些交叉点讲透适合云台、关节电机、旋钮输入这类需要先把角度读准再谈闭环控制的项目。2. AS5600 寄存器映射与 I2C 多字节读取时序0x36 从机地址和 12 位角度2.1 默认 I2C 地址 0x36 和角度寄存器选型AS5600 的 7 位从机地址出厂固定为 0x36I2C 总线上实际发送的字节要把地址左移一位写方向是0x36 1 | 0也就是 0x6C读方向是0x36 1 | 1也就是 0x6D。调试 ACK 失败时最常见的原因是直接把 0x36 塞进数据寄存器少做了一次左移从机自然不应答。角度相关的寄存器主要有三个表格里列出的是最常用的一组寄存器地址名称位宽作用0x0BSTATUS8磁场强度指示MH/ML/MD 三个状态位0x0C~0x0DRAWANGLE12AGC 补偿后的原始角度0x0E~0x0FANGLE12经过 ZPOS/MPOS 映射后的角度通常直接用于控制0x07~0x08CONF14输出模式、方向、零位使能等配置读取时寄存器地址放在第一个数据字节之后AS5600 内部地址指针会自动递增所以从 0x0C 开始连续读 4 个字节能得到 RAWANGLE 和 ANGLE 两个值。12 位数据存在两个字节里读回来的 16 位数值要 0x0FFF才能去掉高位无效位。RAWANGLE 和 ANGLE 的区别容易被忽略0x0C 是不做映射的原始位置0x0E 会先应用 ZPOS 零点位置和 MPOS 最大位置再输出映射后的角度。不写这两个寄存器时二者数值一致但闭环控制里建议固定读 0x0E这样换向和限位逻辑都建立在一个稳定语义上。2.2 一次读多个字节的完整时序START、寄存器地址、重复开始用文本把一次两字节读的完整时序展开方便和逻辑分析仪波形对照S 0x6C A 0x0E A Sr 0x6D A [高字节] A [低字节] N PS 表示起始位Sr 表示重复起始位A 表示 ACK 应答N 表示主设备在第 2 个字节前关闭 ACKP 表示停止位。先发写方向地址和寄存器地址 0x0E等这两个字节都得到 ACK 后再发一次 START 并把方向切成读AS5600 才会把 ANGLE 的高字节和低字节依次送到总线上。注意重复起始必须在写寄存器地址的 TXE 事件产生后再发不能上一个字节还没移出去就拉 START否则 F1 的硬件 I2C 会进入非法状态。读两个字节时主设备收到第 1 个字节后要主动把 ACK 清掉并从此刻准备 STOP第 2 个字节到达 RXNE 后立刻读走数据再完成 STOP。好多从 GPIO 模拟 I2C 转过来的人习惯读完两个字节才拉 STOP这在硬件 I2C 上会多收一个字节且总线状态容易错乱。3. STM32F103RCT6 硬件 I2C 初始化引脚、上拉和 100kHz/400kHz 参数3.1 引脚分配与重映射STM32F103RCT6 的 I2C1 默认映射在 PB6/PB7其中 PB6 是 SCLPB7 是 SDA如果 PCB 把线布到了 PB8/PB9需要设置 AFIO 重映射位。I2C2 固定在 PB10/PB11没有重映射选项。具体对应关系如下外设默认引脚重映射后重映射开关I2C1PB6SCLPB7SDAPB8SCLPB9SDAAFIO-MAPR 的 I2C1_REMAP 位I2C2PB10SCLPB11SDA无无GPIO 要配置成复用开漏输出因为 I2C 总线靠外部上拉电阻把电平拉高开漏结构才能实现“线与”和应答传输。F1 内部虽然有上拉但开漏输出模式下内部上拉并不能提供足够的驱动强度稳妥做法是在 PCB 上给 SCL 和 SDA 各放一颗 4.7kΩ 到 10kΩ 的上拉电阻100kHz 标准模式用 10kΩ 就行400kHz 快速模式且总线电容较大时换 4.7kΩ 或 2.2kΩ。3.2 寄存器版初始化代码下面是一段寄存器级初始化不依赖 HAL 库逻辑顺序是开时钟、配引脚、复位外设、设速度、使能。void AS5600_I2C1_Init(void) { /* 开 GPIOB、AFIO、I2C1 的时钟 */ RCC-APB2ENR | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN; RCC-APB1ENR | RCC_APB1ENR_I2C1EN; /* PB6SCL, PB7SDA复用开漏输出速度 10MHz */ GPIOB-CRL ~(0xFFUL 24); GPIOB-CRL | (0xDDUL 24); /* 每个端口字段: CNF11 复用开漏, MODE01 10MHz */ /* 硬件复位 I2C1 外设清掉上电后的不确定状态 */ I2C1-CR1 | I2C_CR1_SWRST; I2C1-CR1 ~I2C_CR1_SWRST; /* PCLK1 是 36MHz保持 I2C1-CR2 和外设实际时钟一致 */ I2C1-CR2 36; /* 100kHz: CCR 36MHz / (2 * 100kHz) 180 */ I2C1-CCR 180; /* TRISE 上升时间(us) * PCLK1(MHz) 1 1 * 36 1 37 */ I2C1-TRISE 37; /* 使能 I2C 外设 */ I2C1-CR1 | I2C_CR1_PE; }代码里的 SWRST 不是必须每次上电都做但保留它能避开一种现场问题主控复位后从机状态机还在等数据I2C 外设认为总线忙这时直接发 START 会卡在 BUSY 判断上。先复位外设、再配置、再使能是更干净的上电路径。MODE 选 10MHz 而不是 50MHz是因为 I2C 是开漏总线引脚斜率由外部上拉电阻决定MCU 输出驱动能力快了反而容易引入振铃10MHz 挡位在大多数 F103 板子上比 50MHz 更稳。3.3 PCLK1 与 CCR/TRISE 参数表F103 的系统时钟一般是 72MHzAPB1 最大 36MHzI2C 挂在这条总线上。CCR 的取值直接决定 SCL 频率标准模式公式是CCR PCLK1 / (2 * fSCL)TRISE 表示 SCL 上升沿允许的最长时间单位是 I2C 时钟周期加 1。目标 SCLCCR 计算方式36MHz 下的 CCRTRISE 计算100kHz 标准模式PCLK1 / (2 * fSCL)1801us * 36 1 37400kHz 快速模式PCLK1 / (2 * fSCL)450.3us * 36 1 12400kHz 快速模式DUTY1PCLK1 / (3 * fSCL)30同上DUTY 位改成 1 后SCL 低电平时间和高电平时间变成 2:1适合低频段附近对占空比有要求的从机。AS5600 数据手册给出的 I2C 最大速率是 1MHzF1 硬件 I2C 在快速模式下跑到 400kHz 是常规用法再往上就得靠 DUTY 和高驱动能力的电阻了。4. 读写 AS5600 的驱动源码应答处理与两字节读取4.1 基础读函数单字节状态读取先把单字节读函数写出来寄存器地址由参数传入读取 STATUS 和 CONF 时都会用到。#define AS5600_ADDR 0x36 #define AS5600_STATUS 0x0B #define AS5600_RAWANGLE 0x0C #define AS5600_ANGLE 0x0E uint8_t I2C1_ReadReg(uint8_t reg) { uint8_t val; /* 总线忙则等待避免外设还挂着上一次未完成的事务 */ while (I2C1-SR2 I2C_SR2_BUSY); /* 起始条件等待 EV5 */ I2C1-CR1 | I2C_CR1_START; while (!(I2C1-SR1 I2C_SR1_SB)); /* 写方向地址 0x6C等待 EV6 */ I2C1-DR (AS5600_ADDR 1); while (!(I2C1-SR1 I2C_SR1_ADDR)); /* 读 SR2 清除 ADDR 标志 */ (void)I2C1-SR2; /* 写寄存器地址等待 EV8 */ while (!(I2C1-SR1 I2C_SR1_TXE)); I2C1-DR reg; while (!(I2C1-SR1 I2C_SR1_TXE)); /* 重复起始切到读方向 */ I2C1-CR1 | I2C_CR1_START; while (!(I2C1-SR1 I2C_SR1_SB)); I2C1-DR (AS5600_ADDR 1) | 0x01; while (!(I2C1-SR1 I2C_SR1_ADDR)); (void)I2C1-SR2; /* 单字节接收先关 ACK、先发 STOP再等 RXNE */ I2C1-CR1 ~I2C_CR1_ACK; I2C1-CR1 | I2C_CR1_STOP; while (!(I2C1-SR1 I2C_SR1_RXNE)); val I2C1-DR; /* 恢复 ACK为下一次多字节读做准备 */ I2C1-CR1 | I2C_CR1_ACK; return val; }这段代码每跑完一次函数I2C 外设会被正确收回到空闲状态下一次进入前 BUSY 判断基本不会生效。ADDR 标志必须通过读 SR2 清除如果只读 SR1 不清 SR2后续 EV6 会一直触发表现为收发数据错位。单字节接收里两个顺序很关键先关 ACK、先发 STOP再等 RXNE。要是反过来等到了数据才发 STOP停止条件晚了一个位周期从机可能把下一个字节也送出来。4.2 读 ANGLE 两字节NACK 和 POS 的时序位置角度数据是 12 位读 0x0E 和 0x0F 两个连续地址才能取回完整角度。两字节收发的到来主要在最后两个字节处理方式和单字节略有区别uint16_t AS5600_ReadAngle(void) { uint8_t hi, lo; uint16_t angle; /* 先走写方向把寄存器指针定位到 0x0E */ while (I2C1-SR2 I2C_SR2_BUSY); I2C1-CR1 | I2C_CR1_START; while (!(I2C1-SR1 I2C_SR1_SB)); I2C1-DR (AS5600_ADDR 1); while (!(I2C1-SR1 I2C_SR1_ADDR)); (void)I2C1-SR2; I2C1-DR AS5600_ANGLE; while (!(I2C1-SR1 I2C_SR1_TXE)); /* 重复起始切读方向 */ I2C1-CR1 | I2C_CR1_START; while (!(I2C1-SR1 I2C_SR1_SB)); I2C1-DR (AS5600_ADDR 1) | 0x01; while (!(I2C1-SR1 I2C_SR1_ADDR)); (void)I2C1-SR2; /* 关键参数ACK0 且 POS1硬件在第 1 个数据字节后自动产生 NACK */ I2C1-CR1 ~I2C_CR1_ACK; I2C1-CR1 | I2C_CR1_POS; while (!(I2C1-SR1 I2C_SR1_RXNE)); hi I2C1-DR; I2C1-CR1 | I2C_CR1_STOP; I2C1-CR1 ~I2C_CR1_POS; while (!(I2C1-SR1 I2C_SR1_RXNE)); lo I2C1-DR; angle ((uint16_t)hi 8) | lo; return angle 0x0FFF; }POS 位是 STM32F1 硬件 I2C 处理两字节接收的特殊机制。置位后硬件在第一个接收字节结束时不立即回 NACK而是等两个字节都接收完在第二个字节的应答位上回 NACK这样从机刚好送完两个字节主控也能在第 2 个字节前把 STOP 发出去。手动 GPIO 模拟 I2C 没有这个位得自己控制每一位这就是很多移植代码在硬件 I2C 上读 12 位角度读不对的根因。读完最后把结果 0x0FFF因为 AS5600 的 ANGLE 寄存器只有低 12 位有意义高 4 位会在某些批次芯片上读到不确定值。4.3 写 CONF 配置寄存器只改 RAM 不改 EEPROM写方向驱动要处理的是 CONF、ZPOS、MPOS 这几个寄存器它们写进 RAM 立即生效掉电丢失。对 CONF 寄存器做一次写配置如下void AS5600_WriteConf(uint16_t conf) { /* 地址 0x07 是 CONF 高字节地址0x08 是低字节地址 */ while (I2C1-SR2 I2C_SR2_BUSY); I2C1-CR1 | I2C_CR1_START; while (!(I2C1-SR1 I2C_SR1_SB)); I2C1-DR (AS5600_ADDR 1); while (!(I2C1-SR1 I2C_SR1_ADDR)); (void)I2C1-SR2; I2C1-DR 0x07; while (!(I2C1-SR1 I2C_SR1_TXE)); /* 先后写高字节、低字节ASCII 段是 CONF[13:8] 和 CONF[7:0] */ I2C1-DR (uint8_t)(conf 8); while (!(I2C1-SR1 I2C_SR1_TXE)); I2C1-DR (uint8_t)(conf 0xFF); while (!(I2C1-SR1 I2C_SR1_TXE)); I2C1-CR1 | I2C_CR1_STOP; }这里只改 RAM 里的配置值适合方向、停止位这些控制位需要频繁切换的场景。如果需要永久保存AS5600 还有专门的烧写流程数据手册里的 ZMCO 和烧写命令就是干这个用的。量产产品不要在电机运行中反复烧写 EEPROM寿命和时序都不允许。5. 硬件 I2C 总线卡死与 ACK 失败定位方法和多从机场景5.1 从现象区分原因先别怀疑芯片读 AS5600 遇到 ACK 失败先分清楚是“从机没响应”还是“总线被拉死”。用示波器或万用表量 SCL/SDA 静态电平按下面表格对照现象SCL/SDA 电平大概率原因发 0x6C 后一直没有 ADDR 事件总线空闲时 SCL 高、SDA 高地址方向写错、AS5600 供电没起来SDA 常低SCL 正常翻转SDA 低从机状态机卡住或 PC 端设备把总线锁死偶发 NACK不是每次都出现电平正常速率过快上拉电阻偏大、总线电容大、线太长地址方向写错最典型用 0x36 直接写 DR总线变成 0x6C 的右移值从机地址对不上。供电问题也常见AS5600 的 VDD 引脚纹波大时会间歇性不应答建议在 VDD 管脚附近放 100nF 去耦电容。5.2 AS5600 地址固定一条总线上怎么挂多路AS5600 的 7 位地址固定是 0x36不像 AT24C02 那样有 A0/A1/A2 地址脚所以两颗 AS5600 不能直接并联在同一个硬件 I2C 接口上。常见的做法是按需切换总线域或者用带地址通道的开关把多颗芯片隔离在不同总线段上另外同一系列的 AS5600L 支持可配地址新设计可以直接换料。I2C 扩展器接在 F103 和 AS5600 之间时要注意 AS5600 挂在扩展器下游地址不会冲突但读时序要经过两级速度会打折扣。实际调试中我把 SCL 频率降到 100kHz 后扩展器场景下的 ACK 失败率明显下降。5.3 SCL 九个脉冲复位从机状态机如果 SDA 被从机拉低常见做法是先切 GPIO 手动恢复总线再切回硬件 I2C。具体方法是把 PB6/PB7 临时配成推挽输出手动翻转 SCL 9 次让从机内部的位计数器回到帧起始位置void I2C1_BusRecover(void) { /* 先把两个脚切成推挽输出速度 50MHz */ GPIOB-CRL ~(0xFFUL 24); GPIOB-CRL | (0x33UL 24); /* CNF00 通用推挽, MODE11 */ /* SCL 和 SDA 都先释放为高 */ GPIOB-BSRR GPIO_BSRR_BS6 | GPIO_BSRR_BS7; /* 手动拉 9 个 SCL 脉冲让从机复位 */ for (int i 0; i 9; i) { GPIOB-BRR GPIO_BRR_BR6; for (volatile int d 0; d 100; d); GPIOB-BSRR GPIO_BSRR_BS6; for (volatile int d 0; d 100; d); } /* 恢复成复用开漏配置 */ GPIOB-CRL ~(0xFFUL 24); GPIOB-CRL | (0xDDUL 24); }这段代码在每次初始化 I2C 前调用一次能消化绝大多数的总线卡死。恢复正常后还要确认 AS5600 的 VDD 和 GND 单点接地部分不稳定现象是共地电阻造成的地弹而不是协议问题。6. AS5600 角度进闭环控制系统前滤波、过零和零点校准6.1 处理 4095→0 跳变一阶低通滤波AS5600 的 12 位角度在一个完整旋转中从 4095 回到 0如果在差值计算时不处理回绕普通减法会得到 4095 的假跳变闭环里就是一次巨大的位置阶跃。常用做法是把两个 uint16 相减后按有符号处理再根据差值大小修正到 ±2048 范围内static uint16_t last_angle; static int32_t angle_acc; int16_t Angle_GetDelta(uint16_t current) { int16_t delta (int16_t)(current - last_angle); last_angle current; if (delta 2048) delta - 4096; else if (delta -2048) delta 4096; return delta; }(int16_t)(current - last_angle)这一步利用无符号减法回绕的特性把 4095→0 的变化解释成 -1再通过两次边界判断修正就能得到连续的角度增量。把增量累加到angle_acc上可以得到多圈累计角度适合做关节电机这类需要多圈位置的应用。对单圈位置再做一阶低通时建议把滤波放在增量之后而不是原始值之上否则 4095 和 0 两个相邻值会被滤波器拉出中间位置产生不该有的过渡角度。滤波器系数可以根据控制频率调整增量路径上用右移代替浮点除法整型运算在 F103 上更高效。6.2 磁场强度诊断和零点校准放在产线流程里做闭环控制跑起来之前先读一次 STATUS 寄存器确认磁铁安装位置是否在安全窗口内。STATUS 的位 3 是 MD表示磁场强度是否达到检测阈值位 5 是 MH 表示磁场太强位 4 是 ML 表示磁场太弱。检查逻辑直接放在初始化末尾st I2C1_ReadReg(AS5600_STATUS); if (st 0x20) { /* 磁场过强磁铁离芯片太近或直径过大 */ } if (st 0x10) { /* 磁场过弱磁铁太远或偏心太大 */ }这两个状态位在生产中很有用结构件装配偏差、磁铁充磁不足都会先反映在 MH/ML 上。正常运行中我还会周期性读一次 STATUS一旦 ML 出现说明硬件已经处于失效边缘闭环输出不能继续信任。零点校准的常见做法是在机械装配到位后把当前角度作为基准位置然后写 ZPOS 寄存器将基准点映射成 0。写 ZPOS 的过程和写 CONF 相同但有一个前提机械零点必须在磁铁完全固定的状态下标定不能在电机通电状态下手持磁铁去校零。如果想保存到永久存储还要按 AS5600 手册的 EEPROM 烧写命令执行一次这个操作只放在产线工位里跑运行时逻辑里不要频繁调用。调试时可以先读到原始角度和 ANGLE 两个值对比它们之间的差值如果差值正好等于 ZPOS 的配置值说明映射逻辑生效了。把读到的 12 位数据换算成角度用360.0f * raw / 4096.0f验证一下输出确认硬件 I2C 整个链路已经稳定可用。本文还有配套的精品资源点击获取