尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

AS5047P磁编码器SPI调试实战:从CubeMX配置到角度读取

发布时间:2026/9/24 8:02:23

资讯中心
01
ARTICLE

AS5047P磁编码器SPI调试实战:从CubeMX配置到角度读取

AS5047P磁编码器SPI调试实战:从CubeMX配置到角度读取
做磁编码器这个项目之前我本来以为最难的地方是机械安装或者磁场标定结果真正上手之后才发现把SPI调通、把寄存器读对才是第一道坎。尤其是AS5047P这种带“响应滞后”特性的芯片很多人在CubeMX里配好SPI代码写了一堆读出来的角度却是0x3FFF、0或者乱七八糟的跳变最后排查一圈发现根本不是芯片坏了而是压根没读懂它的SPI时序。这篇东西我尽量把从CubeMX配置到寄存器读写再到角度换算的每个环节都写清楚顺手把我踩过的坑也一并列出来给后面做舵机反馈、云台、机器人关节的朋友省点时间。1. 项目背景与整体思路1.1 AS5047P 是什么为什么选它AS5047P 是 ams 家的 14 位磁角编码器核心卖点是分辨率和接口。14 位分辨率意味着每圈被分成 16384 个计数每个 LSB 对应 360 / 16384 0.02197 度也就是大约 0.022 度的角度分辨率用于舵机反馈、云台稳定、机器人关节这类应用完全够用。它内部集成了霍尔阵列和 CORDIC 反正切运算单元你只需要在转轴端放一颗合适的径向磁铁芯片就能直接算出绝对角度不需要码盘、不需要光栅天然抗灰尘、抗油污、抗振动安装余量也大。和其它磁编码器比AS5047P 的优势在于三点第一SPI 接口最快支持 10MHz 时钟读一次角度只要几微秒第二内置自动增益控制AGC和诊断寄存器 DIAAGC可以直接通过寄存器判断磁铁安装是否健康第三支持零点编程ZPOS可以软件标定机械零点省去安装时反复对位。相比常见的 AS560012 位、MT670114 位但寄存器体系完全不同AS5047P 的文档和开源方案最全出了问题也相对好查。1.2 整体方案CubeMX HAL 库 软件片选这套方案我选了 STM32F103C8T6 作为主控理由是量大、便宜、资料多而且 CubeMX 对它的支持非常成熟。软件开发流程是 STM32CubeMX 生成初始化工程再在 HAL 库基础上自己封装 AS5047P 的驱动。为什么用 CubeMX 而不是裸寄存器因为时钟树、SPI 外设、GPIO 复用这些配置手写很容易出错CubeMX 图形化点几下就把初始化代码生成好了后面只需要在生成框架里填业务逻辑省心且不容易翻车。片选策略上这里明确建议用软件片选GPIO 手动拉 CS不要让 SPI 硬件自动控制 NSS。原因后面会详细说——AS5047P 读取角度需要两段式操作中间必须把 CS 拉高一次再拉低硬件 NSS 在这种特殊时序面前不够灵活容易把帧边界搞乱。软件片选虽然多占一个 GPIO但控制力直接拉满出问题也好排查。2. 理解 AS5047P 的寄存器与 SPI 通信机制2.1 关键寄存器速查AS5047P 的寄存器是 14 位地址空间SPI 命令帧和响应帧都是 16 位。第一次调这块芯片不需要把几十个寄存器全背下来先把下面这几个常用的搞清楚就够上路了。寄存器名地址读写作用ANGLECOM0x3FFF读综合角度包含零点偏移补偿后的 14 位角度ANGLERAW0x3FFE读原始角度不包含 ZPOS 零点偏移DIAAGC0x3FFC读诊断寄存器低 8 位是 AGC 增益值ERRFL0x3FFD读错误标志寄存器PROG0x3FFB读/写OTP 编程控制非必要不要碰ZPOSM0x0016读/写零点位置高 8 位ZPOSL0x0017读/写零点位置低 6 位UNLOCK10x3FE8写解锁密钥 1写入 0x5F11UNLOCK20x3FE9写解锁密钥 2写入 0x6D2EPROT0x3FEC写写保护寄存器ANGLECOM 和 ANGLERAW 的区别要理解清楚。ANGLECOM 是经过 ZPOS 零点补偿之后的角度值ANGLERAW 是传感器原始角度。上电默认 ZPOS0所以这两个寄存器读出来的值是一样的如果你写了 ZPOSANGLECOM 会跟着零点偏移变ANGLERAW 不变。实际工程里大多数场景直接读 ANGLECOM 就行做闭环控制也是用这个值。2.2 命令格式与“流水线响应”特性这一节是整个项目最核心的坑理解了它AS5047P 调通就完成了一半。SPI 通信时主机通过 MOSI 发送 16 位命令命令格式是bit15 必须为 0正常模式标志bit14 是读写位1读0写bit13~bit0 是寄存器地址。举个例子读 ANGLECOM 的命令就是 0x4000 | 0x3FFF 0x7FFF写 UNLOCK1 的命令就是 0x3FE8bit140高两位不用管。芯片通过 MISO 返回 16 位数据帧数据帧格式是bit15 是偶校验位bit14 是错误标志位数据无效时置 1正常工作时为 0bit13~bit0 才是真正的有效数据。所以读角度的时候拿到的原始数据要先跟 0x3FFF 做 mask把状态位清掉剩下的低 14 位才是角度。真正的坑在于 AS5047P 的响应不是“即时”的而是流水线式的芯片在“当前这一帧”的 MISO 上返回的是“上一帧命令”的结果。换句话说你确实发出去了读 ANGLECOM 的命令但此时 MISO 上回来的数据是上一条命令或者是上电默认值的响应不是当前命令的响应。非要类比的话就像你隔着门问问题你问第一句的时候门里的人回答的是你上一句的问题只有你再问一次才能拿到当前问题的答案。对应到代码上业界有两种常用读法。第一种是两段式也是最稳妥的CS 拉低发读命令CS 拉高然后 CS 拉低再发一次相同的读命令或者任意非法读命令此时 MISO 返回的才是第一次命令对应的数据CS 拉高。第二种是一次性 32 拍CS 拉低后连续发送 16 位命令 16 位任意数据实际发 0x0000取后面这 16 位返回因为芯片在第二个 16 位周期把命令结果放到 MISO 上了。两种方法都能用但两段式跟数据手册的时序图最接近也最容易理解本文代码按这个来。写寄存器则不太一样。写操作必须在同一个 CS 低电平周期内连续发送 16 位写命令 16 位写入数据芯片在第二个 16 位阶段锁存数据中途 CS 不能拉高。如果按读操作的“两段式”去写CS 在命令发完就拉高了芯片会认为这是一个不完整的帧写入直接失败。所以驱动里读和写要分开写别共用一套时序。3. STM32CubeMX 配置 SPI 与工程搭建3.1 硬件连接与 CubeMX 时钟配置硬件连接其实很简单。AS5047P 是 3.3V 器件STM32F103 的 SPI1 引脚正好也是 3.3V 电平可以直接对接不需要电平转换。我这里用的是 SPI1引脚分配如下AS5047P 引脚STM32F103 引脚说明VDD3V33.3V电源建议加一个 100nF 去耦电容GNDGND共地SCLKPA5SPI1_SCKMOSIPA7SPI1_MOSIMISOPA6SPI1_MISOCSNPA4软件片选GPIO 输出CubeMX 里新建工程后第一步先配 RCC外部高速时钟 HSE 选 Crystal/Ceramic Resonator给后面的倍频提供基准。然后配 SYSDebug 选 Serial Wire这个非常重要——如果你选的是 No Debug程序烧进去之后 SWD 引脚会被复用掉第二次就下载不进程序了C8T6 最小系统板的经典翻车现场。时钟树里外部 8MHz 晶振PLL 倍频到 72MHz。注意 STM32F103 的 SPI1 挂在 APB2 总线上APB2 的时钟就是 72MHz后面算 SPI 预分频要用到这个数。3.2 SPI 参数设置与片选策略CubeMX 的 SPI1 配置页面关键参数按下面这份来ModeFull-Duplex MasterAS5047P 是全双工 SPI 从机Data Size16 Bits命令和数据帧都是 16 位用 16 位模式最直接Clock Prescaler/16SPI 时钟72/164.5MHzCPOLLowCPHA2 EdgeFirst BitMSB FirstNSSDisableCPOLLow 和 CPHA2 Edge 合起来就是 SPI Mode 1AS5047P 数据手册推荐的 SPI 模式。在实际调试中如果你发现数据读数始终不对波形时序看起来又正常可以把 CPHA 换一下试另一个边沿但不建议一开始就用其它模式会给自己制造不必要的变量。预分频这里解释一下为什么选 /16 而不是选 /89MHz。AS5047P 的 SPI 时钟上限是 10MHz9MHz 确实没超但项目里如果用杜邦线连接、或者 PCB 走线不太规整高频下容易出现信号振铃和码间干扰MISO 上的返回数据就会偶发错误。4.5MHz 对读取一个 16 位帧来说只需要 3.5 微秒左右绝大多数控制场景根本感知不到这个速度差异。我的原则是先稳定再谈速度。NSS 选择 Disable然后单独把 PA4 配置成 GPIO_Output初始电平设为 High。这就是软件片选的实现方式——CS 默认高电平表示芯片未选中需要通信时手动拉低。在 CubeMX 的 GPIO 标签页里可以把 PA4 的 Maximum output speed 设为 Low 或 Medium其它保持默认。生成代码之后可以在 main.c 里看到 SPI1 的初始化参数。有个细节值得确认如果 CubeMX 生成的是 HAL_SPI_Init参数里会出现CLKPolarity SPI_POLARITY_LOW和CLKPhase SPI_PHASE_2EDGE这是对的别手滑改成别的。4. 代码实现从寄存器读写到角度输出4.1 底层读写函数我用两个文件封装 AS5047P 驱动as5047p.h 里放寄存器地址宏和函数声明as5047p.c 里放实现。先看头文件#ifndef AS5047P_H #define AS5047P_H #include main.h #define AS5047P_PROG 0x3FFB #define AS5047P_DIAAGC 0x3FFC #define AS5047P_ERRFL 0x3FFD #define AS5047P_ANGLERAW 0x3FFE #define AS5047P_ANGLECOM 0x3FFF #define AS5047P_ZPOSM 0x0016 #define AS5047P_ZPOSL 0x0017 #define AS5047P_UNLOCK1 0x3FE8 #define AS5047P_UNLOCK2 0x3FE9 #define AS5047P_PROT 0x3FEC #define AS5047P_CMD_READ(reg) (0x4000 | ((reg) 0x3FFF)) #define AS5047P_CMD_WRITE(reg) ((reg) 0x3FFF) uint16_t AS5047P_ReadReg(uint16_t reg); void AS5047P_WriteReg(uint16_t reg, uint16_t value); uint16_t AS5047P_GetRawAngle(void); float AS5047P_GetAngleDeg(void); #endifCS 控制直接用 HAL 的 GPIO 操作代码里可以这样定义#define AS5047P_CS_LOW() HAL_GPIO_WritePin(AS5047P_CS_GPIO_Port, AS5047P_CS_Pin, GPIO_PIN_RESET) #define AS5047P_CS_HIGH() HAL_GPIO_WritePin(AS5047P_CS_GPIO_Port, AS5047P_CS_Pin, GPIO_PIN_SET)这里的AS5047P_CS_GPIO_Port和AS5047P_CS_Pin在 CubeMX 生成的头文件里会自动定义PA4 对应的是 GPIOA 和 GPIO_PIN_4直接用就行。然后是读寄存器函数。根据前面讲的两段式原理代码长这样uint16_t AS5047P_ReadReg(uint16_t reg) { uint16_t cmd AS5047P_CMD_READ(reg); uint16_t rx1 0, rx2 0; // 第一段发送读命令此时 MISO 返回的是上一次命令的数据丢弃 AS5047P_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, (uint8_t *)cmd, (uint8_t *)rx1, 1, 10); AS5047P_CS_HIGH(); // 第二段再发一次相同命令此时 MISO 返回的才是第一段命令的结果 AS5047P_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, (uint8_t *)cmd, (uint8_t *)rx2, 1, 10); AS5047P_CS_HIGH(); return rx2; }这里有个细节容易被新手忽略CubeMX 生成工程默认 SPI 数据大小是 8 位如果你在代码里直接传 uint16_t 而不改配置HAL 会按两个 8 位帧发送CS 控制就乱套了。所以务必在 CubeMX 里把 Data Size 改成 16 Bits这一步我没少提醒人。写寄存器函数则不一样必须在一个 CS 低电平周期内连续发送命令和数据void AS5047P_WriteReg(uint16_t reg, uint16_t value) { uint16_t cmd AS5047P_CMD_WRITE(reg); uint16_t data value; uint8_t tx_buffer[4]; tx_buffer[0] (uint8_t)(cmd 8); tx_buffer[1] (uint8_t)(cmd 0xFF); tx_buffer[2] (uint8_t)(data 8); tx_buffer[3] (uint8_t)(data 0xFF); AS5047P_CS_LOW(); HAL_SPI_Transmit(hspi1, (uint8_t *)tx_buffer, 4, 10); AS5047P_CS_HIGH(); }为什么这里用HAL_SPI_Transmit而不是TransmitReceive因为写操作不需要关心 MISO 返回的数据发送 4 个字节即 16 位命令 16 位数据就够了。这里HAL_SPI_Transmit的第三个参数 4 是字节数它是按字节传输的与前面 16 位模式下TransmitReceive的 size 单位是 16 位字不同别搞混。4.2 角度读取与换算角度数据的读取本质上就是读 ANGLECOM 或者 ANGLERAW然后处理返回的原始值。上电后建议先做一次“热身读”丢弃第一次结果因为芯片刚上电时内部状态可能还没有稳定流水线响应也还没有建立起有效的前置命令。驱动里可以在初始化阶段主动读一次 ANGLERAW 并忽略结果。获取原始角度的函数uint16_t AS5047P_GetRawAngle(void) { uint16_t data AS5047P_ReadReg(AS5047P_ANGLERAW); // 高两位是状态位低 14 位才是有效数据 return data 0x3FFF; }角度换算成度就是#define AS5047P_RESOLUTION 16384.0f // 2^14 float AS5047P_GetAngleDeg(void) { uint16_t raw AS5047P_GetRawAngle(); return (float)raw * 360.0f / AS5047P_RESOLUTION; }举个例子读到的原始数据是 4000那么角度就是 4000 * 360 / 16384 87.89 度。这个换算过程不需要浮点也能做定点的话用raw * 360 / 16384注意溢出问题——raw 最大 16383乘以 360 后是 5,897,880int32 完全装得下。在主循环里这样调用就行float angle AS5047P_GetAngleDeg(); printf(angle: %.2f deg\r\n, angle); HAL_Delay(100);实际测试中只要磁铁安装正常、供电稳定读到的角度应该是平滑变化的不会出现大幅度跳动。如果每 100ms 测一次手动缓慢旋转轴头角度数值会单调增减这就是基本调通了。4.3 零点标定写 ZPOS机械安装时磁铁旋钮和电机轴的零点不可能严格对齐。AS5047P 比普通编码器方便的地方在于它可以在软件里设零点不需要动机械结构。具体做法是写 ZPOS 寄存器它一共 14 位高 8 位存在 ZPOSM0x0016低 6 位存在 ZPOSL0x0017。写 ZPOS 前必须先解锁写保护官方解锁流程是依次写 UNLOCK1 和 UNLOCK2void AS5047P_Unlock(void) { AS5047P_WriteReg(AS5047P_UNLOCK1, 0x5F11); AS5047P_WriteReg(AS5047P_UNLOCK2, 0x6D2E); HAL_Delay(1); } void AS5047P_SetZero(uint16_t zero) { AS5047P_Unlock(); AS5047P_WriteReg(AS5047P_ZPOSM, (uint8_t)((zero 6) 0xFF)); AS5047P_WriteReg(AS5047P_ZPOSL, (uint8_t)(zero 0x3F)); }比如想设 5000 作为新的零点调用AS5047P_SetZero(5000)即可。写完之后读 ANGLECOM 验证转到这个位置时它应该输出 0或者在 16383~0 的边界跳动因为 14 位角度的回绕特性。有一点需要特别提醒如果只是想临时指定零点直接写 ZPOSM/ZPOSL 就够了这是 RAM 配置掉电会丢失。绝对不要手滑去碰 PROG0x3FFB相关的熔断流程OTP 编程是烧断内部熔丝的操作一旦执行是不可逆的。如果你不确定自己在干嘛只写 ZPOS 即可。5. 常见问题与避坑实录5.1 数据不对先查时序和接线调试过程中遇到最多的就是“读数固定不变”或者“读数要么 0 要么 65535”。我建议先别急着改代码按下面顺序排查。第一量电压。AS5047P 必须稳定供 3.3V地线要和 STM32 共地不然 SPI 逻辑电平根本没有参考基准。第二抓波形。有逻辑分析仪就抓 CS、SCLK、MOSI、MISO 四个信号确认每一帧确实是 16 位CS 拉低之后 SCK 才开始抖动且在两次读命令之间 CS 有完整的高电平恢复期。没有逻辑分析仪的话用示波器至少能看到 16 个完整的 SCLK 脉冲。第三确认 SPI 模式。很多读数和时序问题最后都是 CPOL/CPHA 配错了AS5047P 是 Mode 1CPOLLowCPHA2 Edge如果你发现 MISO 在 SCLK 第一个沿就输出数据、时序总是反的可以把这两项改成 Mode 0 或 Mode 2 交叉验证。第四检查命令字。读 ANGLECOM 的命令是 0x7FFF不是 0xBFFF更不是 0xFFFF。命令字最高位 bit15 必须是 0有些网友从网上抄的代码里写成了 0x8000 开头那种命令会让芯片进入特殊模式返回的数据就会对不上。第五如果 MISO 一直输出 0xFFFF大概率是 CS 引脚根本没拉低或者 MISO 引脚接反了。SPI 的 MOSI/MISO 容易在接线阶段对调排查时先把这个排除。5.2 角度跳变与磁铁安装如果波形正常、寄存器也能读但角度总是跳变那问题基本出在磁铁安装和供电上。AS5047P 需要一颗径向充磁的钕铁硼圆片磁铁直径 6mm、厚度 3mm 是常见选择居中安装在电机轴末端芯片中心正对磁铁圆心距离芯片表面大约 0.5~1.5mm。怎么判断磁铁装得好不好用 DIAAGC 寄存器。读取 0x3FFC 的值低 8 位就是 AGC 自动增益控制值它反映磁场强度正常工作时 AGC 值应该落在一个健康区间大概在 100~200 之间。如果 AGC 接近 0说明磁铁太近、磁场过强如果 AGC 接近 255说明磁铁太远或偏心信号太弱。这个值读出来非常直接比看角度跳变再去猜原因高效得多。另外供电稳定性也要注意。AS5047P 内部有模拟电路和 ADC如果电源纹波大角度读数会周期性抖动。我试过在面包板上用杜邦线供电电机一转起来角度就跳后来给 VDD3V3 和 GND 之间加了 100nF 10uF 去耦电容情况立刻好转。主控和编码器尽量用同一路 3.3V避免压差。5.3 DMA 与性能优化建议很多朋友看到热词里“SPI DMA”就想上 DMA实际上在这个项目里DMA 的优先级并不高。AS5047P 在 4.5MHz 时钟下读一次 16 位帧大约需要 3.5 微秒加上 CS 切换和两段式操作一次角度读取也就 7 微秒左右。就算控制周期是 1kHz一次读取占 CPU 的时间也就是 0.7%这点开销对绝大多数 MCU 来说完全可以忽略。真正需要考虑 DMA 的反而是读时序复杂度。AS5047P 的两段式读需要中间拉高 CS而 DMA 传输一旦启动中途停止或者精确控制 CS 就变得麻烦。我见过有人在 DMA 中断回调里翻转 CS最终把代码搞得巨复杂调试难度直线上升性能收益却微乎其微。所以我的建议是先用阻塞式把功能跑通如果项目确实有高频采样需求再考虑把“命令数据”合并成一次 32 位 DMA 传输用一次 CS 周期读完而不是纠结两段式。5.4 一个实操检查清单最后我把整个调试过程浓缩成一张检查表照着过一遍能省掉大量排查时间。检查项期望结果异常处理VDD3V3稳定 3.3V去耦电容 100nF换供电、加电容SCLK/MOSI/MISO/CS 接线无虚焊无对调用万用表通断档逐根量SPI 时钟≤10MHz推荐 4.5MHz调预分频SPI 模式Mode 1CPOLLow, CPHA2 Edge交叉验证其它模式数据帧长度16 Bits检查 Data Size 配置读命令0x4000 | 寄存器地址检查命令字位数据有效位mask 低 14 位后使用检查 0x3FFF流水线响应第一次读丢弃初始化时热身读AGC 值100~200 左右调整磁铁位置和距离角度换算raw * 360 / 16384检查除数不要写成 36000我个人在实际操作中习惯把这个检查表放在手边每次新焊一块板子先看 AGC 再读角度。这套流程帮我区分了“芯片没工作”和“SPI 没调通”这两类问题调试效率高了特别多。最后再分享一个小细节AS5047P 焊接时温度不要太高、时间不要过长芯片内部有磁感应元件过热的返修过程可能会导致零点漂移正常焊接时用低温烙铁快速完成焊完先读 DIAAGC 确认芯片没“受伤”再继续调后续逻辑。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。