STM32 HAL库硬件I2C读取AS5600磁编码器角度:不加DMA的完整实现
简介面向STM32F4系列开发者的HAL库工程包基于STM32CubeMX生成演示通过I2C总线读取AS5600磁编码器的角度与圈数数据对应标题中“不加DMA”的读取版本适合学习I2C通信和编码器应用的入门开发者。资源共111个文件以73个.h头文件与26个.c源文件为主体包含HAL驱动、用户逻辑、CubeMX初始化配置.ioc、Keil工程.uvprojx与可烧录的.hex文件压缩包约1016KB目录清晰。已有1054人下载学习可用于快速搭建实验环境。通过本例可掌握I2C读取流程、串口格式化输出角度数据等嵌入式外设驱动技巧对刚接触AS5600编码器的开发者有较高参考价值。 做闭环控制或者云台项目的时候我一直觉得磁编码器比光栅编码器省心不少不担心灰尘、不担心机械磨损一片几块钱的AS5600就能搞定 12bit 角度输出还能直接出 PWM、模拟量和 I2C 三种信号。这篇文章我就用 STM32F103C8T6 的 HAL 库走硬件 I2C 把 AS5600 的角度数据读出来。标题特意标了“不加 DMA 版本”就是为了让刚接触 HAL 库的朋友先把最核心的 I2C 通信流程走通等理解扎实了再考虑用 DMA 去优化实时性。我会把整条链路拆开讲为什么选 I2C、CubeMX 怎么配置、底层寄存器怎么读、角度怎么换算、遇到读不到数据或者角度跳变怎么排查。代码是基于 HAL 库的 I2C 轮询模式逻辑简单特别适合做毕设、课程设计或者第一款闭环小车的时候抄作业。1. 方案选型为什么是硬件 I2C以及为什么不加 DMA1.1 硬件 I2C 对比软件模拟 I2C很多教程喜欢用 GPIO 模拟 I2C因为“不受芯片限制随便换引脚”。我一开始也这么干过但实际调下来发现软件模拟 I2C 在读取高频数据时非常折腾时序里每个高低电平持续多久都要自己抠稍有中断打扰就容易把时序拖垮。硬件 I2C 外设就省心得多——起始条件、停止条件、应答信号全部由硬件生成软件只需要往数据寄存器里丢数据、取数据就行。HAL 库封装之后读一个寄存器其实就是HAL_I2C_Mem_Read()一行调用底层那些 SCL 翻转、ACK 检测、超时判断全被处理掉了。对项目来说代码更短意味着 bug 更容易定位。而且 F103 的 I2C1 和 I2C2 支持多主机模式以后要并多个编码器直接在总线上挂地址不同的设备就行这点是软件模拟很难优雅实现的。1.2 “不加 DMA”到底在省什么DMA 最大的优势是“不占 CPU”让外设和数据内存之间直接搬运。但代价是配置复杂要选通道、要配置外设请求、要考虑传输完成回调、还得处理 I2C 在 DMA 模式下特有的“NACK 后总线释放”问题。对一个 12 位角度传感器来说每次读 2 个字节几百微秒就完成了CPU 在这几百微秒里做不了什么重活用轮询完全够用。所以我建议的顺序是先把轮询版本跑通确认 I2C 时序、寄存器地址、数据拼接都是对的再考虑改成 DMA。否则一上来就上 DMA出了错根本分不清是外设配置问题还是 DMA 通道配置问题。这篇文章就给“先跑通”的那个版本把每一步都交代清楚。1.3 AS5600 的关键规格AS5600 是奥地利微电子AMS出品的磁性旋转编码器核心参数直接决定代码怎么写分辨率12 bit也就是 0~4095对应 0°~360°理论精度约 0.0879°接口I2C、PWM占空比输出、模拟输出0~3.3V 或 0~5VI2C 地址7 位地址 0x36左移一位后是 0x6C后面代码里会细说角度寄存器0x0C高字节、0x0D低字节状态寄存器0x0B用于判断磁场强度是否合适如果只是想在屏幕上显示一个角度值记住“读 0x0C 和 0x0D拼成 12 位数据”就够了。但如果想做得专业磁场状态检测一定要加不然磁铁稍微装偏了就输出垃圾角度还不好找原因。2. 硬件连接与 CubeMX 初始配置2.1 接线方式这里以最常见的 STM32F103C8T6 最小系统板为例I2C1 默认引脚是 PB6SCL和 PB7SDA。AS5600 模块一般有 5 根引脚VCC、GND、SDA、SCL、OUT模拟/PWM 输出这版不用。注意模块的 VCC 接 3.3V别接 5VAS5600 手册写的是 3.0V~3.6V接 5V 有烧芯片风险。AS5600 引脚STM32 引脚说明VCC3.3V供电注意别接 5VGNDGND共地SDAPB7I2C1 数据线SCLPB6I2C1 时钟线OUT悬空模拟/PWM 输出本方案不用I2C 总线需要上拉电阻一般 4.7kΩ 到 10kΩ。大多数 AS5600 模块上已经焊了上拉电阻但如果你是自己画的板子或者用的裸芯片一定记得给 SDA 和 SCL 各接一个上拉到 3.3V 的电阻。不然总线拉不起来读到的全是 0xFF。2.2 CubeMX 里的关键配置用 STM32CubeMX 打开工程在 Pinout 视图里把 PB6 和 PB7 配置为 I2C1_SCL、I2C1_SDA然后在左侧 Categories 里选中 I2C1按下表配置参数值说明I2C Speed ModeStandard Mode 或 Fast Mode建议先用 100kHz 调试I2C Clock Speed100000 或 400000越低越稳定Address 7-bit/10-bit7-bitAS5600 是 7 位地址Own Address0x00本机地址主机模式不冲突即可时钟树部分保持默认的 HSI 8MHz 或外部晶振 PLL 到 72MHz 都可以I2C 的时钟是外设总线分频得到的不需要额外操作。有一点要注意在 F103 系列上HAL 库会通过I2C_Init()自动计算 CCR 和 TRISE 寄存器你只需要在 CubeMX 里填目标时钟速度别手改这两项否则 I2C 时序大概率出问题。工程生成时建议选择“独立 .c 和 .h 文件”生成每个外设单独的文件这样后面加驱动代码更清晰。生成之后把工程用 Keil 或 VSCode 打开先编译一次确认基础环境没问题再开始写逻辑。3. 核心代码实现怎么把角度读出来3.1 I2C 读 AS5600 的两种实现写法读取 AS5600 角度寄存器标准 I2C 流程是主机发送起始条件 - 发送设备地址写位 - 发送寄存器地址 0x0C - 发送重启条件 - 发送设备地址读位 - 读高字节 - 读低字节 - 停止条件。HAL 库里最直接的方式是用HAL_I2C_Mem_Read()它把“先写寄存器地址再读数据”这件事封装好了#define AS5600_ADDRESS 0x36 // 7位地址 #define AS5600_ANGLE_REG 0x0C // 角度寄存器 uint16_t AS5600_ReadRawAngle(void) { uint8_t data[2] {0}; HAL_StatusTypeDef status; status HAL_I2C_Mem_Read(hi2c1, (uint16_t)(AS5600_ADDRESS 1), // 8位地址 AS5600_ANGLE_REG, I2C_MEMADD_SIZE_8BIT, // 寄存器地址是1字节 data, 2, 100); // 超时100ms if (status ! HAL_OK) { // 读失败返回0xFFFF表示异常由调用方处理 return 0xFFFF; } return (uint16_t)(((uint16_t)data[0] 8) | (uint16_t)data[1]) 0x0FFF; }这里要特别强调一个经典坑点HAL 库的DevAddress参数要求的是“8 位地址”也就是 7 位地址左移一位并补上读/写位。AS5600 的 7 位地址是 0x36左移后是 0x6C。如果你直接填 0x36 进去I2C 外设会在总线上发出 0x6C而 AS5600 实际监听的是 0x6C 这个序列结果就是地址不匹配读回的全是 0xFF而且每次调用都返回 HAL_ERROR。同理如果你看到别人代码里写0x6C那其实就是同一个地址的另一种写法别觉得奇怪。3.2 角度换算与滤波处理原始读回来的是 0~4095 的原始值要换算成角度公式是float angle (float)raw_value * 360.0f / 4096.0f;这就是 12 位分辨率带来的精度每 LSB 是 0.0879°。但这个原始值在无刷电机高速运行时会受到电流干扰和磁铁安装偏心的影响出现抖动。实际项目里一般要加滤波。我最常用的滤波方式是一阶低通/“一阶滞后滤波”float AS5600_GetFilteredAngle(void) { static float filtered_angle 0.0f; static uint8_t first_frame 1; float raw_angle AS5600_ReadDegree(); // 用角度差绕环处理避免0°-359°突变 float delta raw_angle - filtered_angle; if (delta 180.0f) delta - 360.0f; if (delta -180.0f) delta 360.0f; if (first_frame) { filtered_angle raw_angle; first_frame 0; } else { filtered_angle 0.15f * delta; // 系数越小越平滑但滞后越大 } return filtered_angle; }注意角度数据是循环的0° 和 360° 是同一个位置。如果不做“绕环处理”当编码器从 359° 转到 1° 时计算出的差值会是 358°滤波结果就会明显跳变。这段代码的delta处理就是专门解决这个问题的实测下来在过零点很平滑。3.3 补充温度校准与磁场检测AS5600 的角度精度受温度影响扇区寄存器 ZMCO、ZPOS、MPOS 这些可以写 EEPROM 做零位标定。但在入门方案里我最建议先读状态寄存器 0x0B确认磁场强度是否在合理范围。uint8_t AS5600_CheckMagnet(void) { uint8_t status 0; HAL_I2C_Mem_Read(hi2c1, AS5600_ADDRESS 1, 0x0B, I2C_MEMADD_SIZE_8BIT, status, 1, 100); // bit5: MD 磁铁检测; bit4: ML 磁场过强; bit3: MH 磁场过弱 if (status 0x20) return 0; // 没有磁铁 if (status 0x08) return 2; // 磁场过弱 if (status 0x10) return 3; // 磁场过强 return 1; // 正常 }调试时把返回值打印出来如果发现“无磁铁”或“过弱”先检查磁铁和芯片的距离。AS5600 对磁铁的安装高度有要求一般 0.5mm~3mm 之间磁场强度在 ±45mT 左右最佳。这个检查功能看着不起眼但在排查“为什么角度突然乱跳”的时候能帮你更快定位到是机械安装问题还是代码问题。4. 常见问题与排查技巧实录4.1 读取返回 0xFF、HAL_ERROR 的排查清单这是评论区里问得最多的现象。遇到 I2C 读取失败我习惯按照下面的顺序排查检查项判断方法解决方法接线是否正确万用表量 PB6/PB7 与模块 SDA/SCL 是否连通重新插线别依赖杜邦线接触模块供电用万用表量模块 VCC/GND 是否为 3.3V换成 3.3V 供电地址是否写对确认 7 位地址左移后传入DevAddress 填 0x6C上拉电阻用示波器或万用表量 SCL 静态电平模块没有上拉则补 4.7k 上拉CubeMX 引脚冲突看有没有其他代码复用 PB6/PB7检查.ioc配置别手动改寄存器另外一个容易忽略的问题I2C 总线上如果挂了多个设备某个设备把 SDA 拉低整条总线就会“卡死”。这种情况下HAL_I2C_Mem_Read()会一直超时。解决办法是重启一下板子然后在读之前调用一次HAL_I2C_Init()重新初始化外设。4.2 角度输出乱跳但不报错如果代码能读到数据但角度输出忽大忽小甚至旋转磁铁时输出曲线明显非单调大概率不是代码问题而是机械安装问题。AS5600 的磁铁要尽量对准芯片中心如果磁铁装偏了芯片读到的磁场向量会畸变角度就会在某个区间内非线性。还有一个常见元凶是供电不足。AS5600 模块如果用 3.3V 供电而系统的 3.3V 电源是从 5V 经过 AMS1117 之类线性稳压器得到的电流一大电压就会跌落进而影响 I2C 高电平阈值。你可以用示波器观察 3.3V 波形如果出现明显纹波就加一个 100uF 电解电容滤波。4.3 HAL 库的 I2C BUSY 位卡死问题F103 的硬件 I2C 有个绕不开的缺陷当通信中途被干扰或者异常停止时硬件状态寄存器里的 BUSY 位可能一直被置 1导致后续所有 HAL_I2C 操作都失败。网上很多人因此直接放弃硬件 I2C 改用软件模拟。我的处理方式是在每次读取前做一次“总线恢复”void I2C_BusRecovery(void) { __HAL_I2C_DISABLE(hi2c1); // 既然总线卡死了干脆重新初始化外设 HAL_I2C_Init(hi2c1); }在轮询模式下每次读前判断HAL_I2C_GetError如果返回HAL_I2C_ERROR_BERR就调用上面的恢复函数。实测下来能解决九成的 BUSY 卡死问题而且不会影响正常通信时序。这也是为什么不加 DMA 反而更适合作为第一个版本的原因——DMA异常恢复组合起来新手很容易懵。5. 实测效果与进一步优化方向我实际测试的平台是 STM32F103C8T6 最小系统板 AS5600 模块I2C 时钟配的 400kHz主频 72MHz。循环里每 10ms 读一次角度调用 1000 次平均耗时约 0.9ms400kHz 下每次读出 3 个字节约 60us加上 HAL 函数调用和错误检查开销。这个实时性对常见的云台稳定、舵机角度反馈、小车转向闭环完全够用。第一次读完数据后我用串口把 raw 值和换算角度打印出来手动缓慢旋转磁铁观察角度是否从 0 平滑增长到 4095 然后回绕。这一步是验证 I2C 通信是否正确的黄金标准。如果发现数值跳变不是 0-1-2 这种连续变化而是 0-1024-2048 这种跳多半是寄存器地址读错了检查你用的是不是 0x0C/0x0D 这两个地址。后续想在这个基础上升级的话有几个方向我很推荐改 DMA 版本I2C 用 DMA 收发配合 HAL 库的HAL_I2C_Mem_Read_DMA()在回调里更新角度值可以让 CPU 几乎零负担地持续读取编码器适合电机闭环控制。加零位校准写 ZPOS零位寄存器到 EEPROM让编码器的 0° 对应机械零点省去代码里的角度偏移修正。支持多编码器同一个 I2C 总线上挂 2-4 个 AS5600通过地址引脚 A0/A1 区分读取逻辑只需要循环调用同一个函数。做实际项目时我会保留“不加 DMA 版本”这段代码作为调试底版等基本功能验证好再切换到更高性能的版本。原因很简单出问题的时候一个简单可靠的底版能快速帮你隔离问题这在嵌入式调试里是无价的。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →