资讯详情

MT6835 SPI磁编码器调试实战:STM32读取角度与防抖指南

📅 2026/10/5 1:15:44 | 华诺云谱 👁 阅读
MT6835 SPI磁编码器调试实战:STM32读取角度与防抖指南
上个月调一个云台的角度环低速时电机像打摆子一样来回抖串口打印出来的编码器角度值在某个区间跳了几十个码滤波、屏蔽、降压全都折腾了一遍最后才发现问题出在CS拉低之后SCK起得太快MT6835的MISO还没建立稳定主机就已经采样了。这种问题在STM32读SPI磁编码器的项目里非常典型。MT6835本身是一个很稳定的芯片难调的地方全在SPI时序细节和数据处理上。这篇我就按自己的调试顺序把怎么读、怎么验证、读出来怎么用以及几个容易掉进去的坑一次说清楚。1. 先把MT6835的输出机制吃透它是一颗SPI从机不是模拟传感器1.1 磁编码器为什么能直接读出绝对角度MT6835属于磁角度编码器芯片内部有磁阻或霍尔阵列可以感知贴在转轴上的磁铁产生的旋转磁场。磁铁转动时芯片内部会得到两路正交的正余弦信号再通过CORDIC算法算出当前角度。这里的关键是“绝对角度”——上电那一刻你就能读到0到360度范围内的一个确定位置不需要找零位、不需要增量累积。这个特性对电机FOC控制、云台、机器人关节非常友好。增量式编码器断电后再上电就不知道转子在哪必须做一次找零动作而磁编码器只要磁铁装好一上电就什么都知道。还有一个容易被忽略的优势磁编码器对灰尘、油污不敏感结构上允许一定程度的偏心比光栅编码器耐造。MT6835的分辨率一般是14位也就是一圈有16384个码每个码对应的角度是360 / 16384 ≈ 0.02197度这个精度对大多数运动控制场景已经够用了。追求更高分辨率当然可以上18位、20位的芯片但控制环路能不能用上那么多位还要看机械刚度和采样周期不是位数越高越好。1.2 SPI接口的访问方式一次16位时钟换一个角度MT6835把自己当作一个SPI从机主控要拉低片选CS然后给出16个SCK时钟芯片就会把当前角度通过MISO线送回来。也就是说读一次角度本质上就是一次完整的16位SPI全双工交换。MOSI线上发送什么对于标准的读角度操作通常是发送0x0000命令。从机在收到命令后把角度数据放在MISO线上返回。整个交互过程就是一问一答非常直接。这里要特别说一下返回数据的位定义。按我手头这批MT6835的数据手册返回的16位数据大致是这样分布的bit15偶校验位bit14状态位或保留位正常读出来是0bit13到bit014位角度数据不同批次、不同固件版本对高位定义可能会有细微差别所以代码里不要写死先对照手册确认一遍。我见过有人把高位状态位当成角度位参与运算结果角度输出像被人为加了偏移一样怎么校准都校不回来。处理方式很简单每次拿到原始帧先做掩码操作只保留低14位uint16_t angle_raw frame 0x3FFF; /* 只留 bit13:0 */这一步看起来不起眼但能省掉后面一大堆莫名其妙的调试时间。1.3 为什么选SPI而不是PWM或ABZ输出MT6835这类芯片通常还支持PWM输出、ABZ正交输出等接口。有些工程师图省事直接用PWM读角度接一个定时器输入捕获就能算。但从控制角度讲SPI有几个不可替代的优势读取速度快几十MHz的SPI时钟足够支撑高频率的角度采样能够直接读到绝对角度不需要PWM周期内的高电平计时可以附带校验位和状态位方便判断数据是否有效一次SPI事务能同时传递命令和数据扩展性强ABZ接口适合那些已经有正交解码器硬件的系统但它本质上是增量式的断电后位置信息丢失。SPI则没有这个问题。所以如果项目对成本不太敏感SPI接口基本是磁编码器场景下的最优解。2. 硬件环节的三个高频翻车点电源、引脚、磁铁2.1 电源和电平匹配MT6835的供电电压通常是3.3VSTM32也是3.3V系统两者直连问题不大。但有些电机驱动板上为了迁就驱动芯片把逻辑电平拉到了5V这时候直接连STM32的SPI引脚就有风险。需要确认一点STM32的大多数IO是5V容忍的但“5V容忍”不等于“推荐长期工作在5V”。更稳妥的做法是加电平转换芯片或者用分压电阻把MT6835侧的MOSI、SCK、CS信号从5V降到3.3V。MISO方向因为是芯片输出3.3V到STM32这边一般没问题。另外注意芯片的电源引脚如果VDD和VIO是分开的两个都要接对。很多人只给VDD供电忘了VIO结果芯片怎么都不工作MISO上永远是高阻态。这种低级错误用万用表量一下引脚电压就能发现。2.2 SPI引脚分配和丝印确认MT6835需要四根线SCK、MOSI、MISO、CS。这个通用说法没问题但具体到芯片丝印上要注意不同厂家的命名习惯不一样有的叫SDI/SDO有的叫SDIN/SDOUT。SDI是芯片的串行数据输入对应主机的MOSISDO是芯片的串行数据输出对应主机的MISO。接反的话读回来的数据要么全是0xFF要么全是0x00或者是毫无规律的乱码。我自己的经验是上电后先写一个简单的SPI读函数把返回的原始帧通过串口打印出来然后用手慢慢转动磁铁。如果读出来的数值在某个范围变化说明信号路径基本通了如果纹丝不动大概率是MOSI和MISO交叉了这时候去查原理图比在代码里猜更快。还有一点CS片选脚不要图省事直接接到GND。有一些从机确实支持CS一直拉低但那样做会失去片选控制能力以后想扩展成多片编码器或者读其他寄存器就很麻烦。用STM32的一个普通GPIO做软件片选是最灵活的做法。2.3 磁铁选型与安装是很多人忽视的“软件问题”SPI读不到正常数据很多时候不是SPI的问题而是磁铁根本没装好。MT6835需要的是轴向充磁的圆盘磁铁直径5到8毫米比较常见。充磁方向一定要是轴向的也就是磁铁的N极和S极分布在上下两个端面而不是分布在内外径。搞反了的话芯片输出的角度信号会很弱甚至读出来是乱的。安装时注意三点磁铁中心尽量对准芯片封装中心的感磁区域气隙控制在0.5到2毫米左右不要太远也不要贴死在芯片表面磁铁和转轴之间不能有可见的偏心偏心会导致角度信号谐波误差增大直观表现是每转360度角度误差出现周期性变化某些位置线性度特别差。程序上可以查表和校准但最好还是在机械安装阶段就把问题解决软件校准只是补救手段。3. CubeMX里的SPI配置按这样填成功率最高3.1 关键参数数据宽度、时钟极性、分频系数我用STM32F103C8T6加SPI1举例其他系列配置思路完全一样。在CubeMX里新建工程后把SPI1的Mode选为Full-Duplex Master然后重点设置以下几个参数Data Size16 BitsFirst BitMSB FirstPrescaler先选大分频比如32分频算下来SPI时钟大约2.25MHzCPOLLowCPHA1 Edge对应SPI Mode 1CPOL和CPHA这里是重点中的重点。按照MT6835手册给出的时序数据在SCK的下降沿更新在上升沿采样对应的就是CPOL0、CPHA1这种配置。实际使用中你把CPOL配成High、CPHA保持1 Edge也能工作但一旦采样的沿选错读回来的数据会整体移位角度值看起来像是真实值的一半或者两倍很难第一时间反应过来。分频系数不要一上来就追求高速。先把SPI时钟压到2到4MHz跑通整个流程再用示波器看MISO波形质量确认没有明显振铃和毛刺以后再逐步提高时钟。SPI通信速率不是越快越好尤其在电机驱动板这种电磁环境恶劣的地方过高的速率只会带来更多误码。3.2 MISO引脚配置的真坑不要手动改成输入模式CubeMX在初始化SPI外设后会自动把SCK、MOSI、MISO配置成复用推挽输出即AF_PP模式。MISO虽然是接收数据的引脚但在SPI外设工作模式下引脚控制权已经交给外设从机推挽驱动MISO主机侧用复用功能接收即可。有些参考代码里会把MISO设成GPIO输入上拉模式理由是想让空闲电平稳定在高电平。这在某些场合有效但在STM32的SPI外设接管下这种配置反而可能导致采样异常。我自己的建议是完全信任CubeMX生成的引脚配置不要在代码里额外改MISO的模式。如果真的怀疑空闲电平不稳定先查从机芯片的MISO驱动能力和外部上拉电阻不要在引脚模式上乱动。3.3 CS片选时序要自己在软件里补CubeMX不会帮你管理软件片选。CS拉低之后SCK什么时候开始取决于你调HAL_SPI_TransmitReceive的时机。如果CS刚拉低就立刻启动SPI传输MT6835的MISO可能还没来得及从高阻态切换到驱动态导致第一个bit采到不确定的电平。我通常会在CS拉低后加一段极短的延时哪怕几个空指令也行ENC_CS_LOW(); for (volatile uint8_t i 0; i 4; i); HAL_SPI_TransmitReceive(hspi1, (uint8_t*)tx, (uint8_t*)rx, 1, 10); ENC_CS_HIGH();这里的延时不是随便加的它的本质是满足从机的输出建立时间tCSD。不同手册给的参数不一样没有示波器的情况下先给足余量跑稳了再去压缩时间。CS拉高之后两次读取之间也要留一小段时间让芯片内部更新角度数据。连续高速读的时候如果发现相邻两次读到的值是同一个数或者跳变很诡异多半是CS释放得太快芯片还没来得及刷新。4. 代码实现的两种方式以及数据拼装的坑4.1 16位数据宽度代码最简洁但要注意两个细节配置成16位Data Size以后一组数据就是16个SCKHAL库的写法很简洁uint16_t MT6835_ReadRaw(void) { uint16_t tx 0x0000; uint16_t rx 0; ENC_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, (uint8_t*)tx, (uint8_t*)rx, 1, 10); ENC_CS_HIGH(); return rx; }两个容易错的点第一HAL_SPI_TransmitReceive的第三个参数是“数据个数”不是“字节数”。配置成16位宽时传1就表示传输1个16位的数据。很多人惯性写成2结果一次传输了32个SCK数据读出来完全错位。这是一个非常隐蔽的坑因为函数不会报错数据也有变化但角度值就是不对。第二字节序。STM32是小端模式而SPI外设是按MSB先移出的方式工作。正常情况下rx直接就是bit15到bit0不需要手动交换字节。但如果发现转轴慢慢转时角度值不是连续变化而是每256个码一跳那说明高位字节和低位字节对调了。这时可以用一个宏交换高低字节uint16_t swapped (rx 8) | (rx 8);判断到底是哪种字节序用“角度值是否以256为单位跳变”这个方法最快比对着原理图猜效率高多了。4.2 8位数据宽度更保守的兼容性写法如果你不确定芯片是哪一种位序或者想兼容后续换芯片的情况用8位数据宽度分两次收发更直观uint8_t tx[2] {0x00, 0x00}; uint8_t rx[2] {0x00, 0x00}; ENC_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 10); ENC_CS_HIGH(); uint16_t raw ((uint16_t)rx[0] 8) | rx[1];这个写法假设芯片先发高位字节、再发低位字节。如果发现角度值跳变异常就把拼装顺序反过来uint16_t raw ((uint16_t)rx[1] 8) | rx[0];8位方式的优势在于即使以后换了一颗数据格式类似的芯片只需要修改拼装逻辑不需要改动SPI外设配置。对于量产项目要预留方案变更余地的场景这种方式更稳。4.3 角度换算与掩码处理拿到raw值以后第一件事是掩码只保留低14位然后换算成角度#define MT6835_RESOLUTION 16384.0f uint16_t angle_raw raw 0x3FFF; float angle_deg (float)angle_raw / MT6835_RESOLUTION * 360.0f;如果你发现角度值始终偏离真实角度一个固定数值而且怎么校准都校不回来回头检查一下是不是没有掩码把状态位和保留位都当成角度位了。这个原因在排查列表里出现的频率非常高。5. 数据乱跳时的完整排查链路从波形到校验5.1 不要急着改代码先上示波器看波形读回来的角度不对第一反应往往是回头改代码但很多问题在时序上不在代码逻辑里。最有效的做法是直接把示波器探头夹在CS、SCK、MISO三个引脚上条件是CS下降沿触发单次抓取16个SCK周期。看什么看三点CS下降沿到SCK第一个上升沿之间MISO是否已经变成有效驱动电平MISO上的数据在整个传输过程中是否稳定有没有在SCK采样沿附近翻转CS上升沿是否在最后一个SCK结束之后才出现如果MISO在SCK采样沿附近还在变化说明从机输出建立时间不够或者SPI时钟频率太高。这时候优先降低分频系数把SPI时钟降下来再观察波形是否改善。很多“随机跳变”的问题本质上是时钟太快导致采样点落在数据变化沿上属于亚稳态问题不是磁铁问题。5.2 常见故障现象和排查方向我把实际调试中遇到的几种典型现象整理成一个表方便快速定位故障现象可能原因优先排查方向读回数据全是0xFFFF或0x0000MOSI和MISO接反、从机未供电用万用表量引脚电平对照原理图查线角度值是实际值的一半或两倍CPHA采样沿选错数据移位修改CPHA观察角度变化角度值以256为单位跳变高低字节序颠倒交换高低字节固定区间内来回跳几十个码CS拉低后SCK启动太快在CS之后加建立延时随机跳变且校验失败率很高线路干扰、SPI时钟过快降速、缩短连线、改善地线每转一周出现固定位置的误差磁铁偏心或气隙不均机械校准重新装磁铁5.3 用奇偶校验判断帧是否有效MT6835的返回帧带奇偶校验位这是排查干扰的最有力工具。不要只在数据乱的时候才去看校验而是要把它设计到每个读取流程里。按偶校验来写一个简单的判断函数bool MT6835_CheckFrame(uint16_t frame) { uint8_t cnt 0; for (uint8_t i 0; i 16; i) { if (frame (1U i)) { cnt; } } return (cnt % 2) 0; }注意校验位到底是奇校验还是偶校验不同的资料可能会给出不同说法。上面这个函数是参考实现实际用之前一定要拿手册确认校验算法。更稳妥的做法是先用手缓慢转动磁铁采集几十帧数据观察校验结果是否和你预期一致再决定用奇校验还是偶校验。校验函数的价值在于定性判断。如果校验失败率很低偶发一次失败那可以直接丢弃该帧重读。如果连续失败几十帧那说明硬件链路有严重干扰光靠软件丢弃是遮羞布必须回到示波器找根因。5.4 机械因素导致的“伪干扰”还有一种情况SPI数据一切正常校验也全过但角度值就是有周期性波动。这时候要怀疑磁铁和芯片的机械安装。一个典型的例子磁铁中心偏离芯片感磁中心0.5毫米结果每个转动周期里会出现两次明显的角度误差凸起。这种误差用示波器看不出来因为SPI时序完全正确纯粹是磁场分布畸变导致的。处理方式是重新校准机械结构或者在软件里做误差查表补偿。对于量产项目软件补偿能省下不少机械装配成本但前提是先确认误差是重复性的、可复现的。如果每次装完误差都不一样补偿表就没有意义。6. 工程化使用技巧滤波、多圈累计、FOC对齐6.1 滑动平均滤波以及360度跳变陷阱对随机噪声滑动平均是最直接的降噪手段#define FILTER_N 5 uint16_t mt6835_buf[FILTER_N]; uint8_t mt6835_idx 0; uint32_t mt6835_sum 0; uint16_t MT6835_FilterRead(void) { uint16_t raw MT6835_ReadRaw(); mt6835_sum - mt6835_buf[mt6835_idx]; mt6835_sum raw; mt6835_buf[mt6835_idx] raw; mt6835_idx (mt6835_idx 1) % FILTER_N; return (uint16_t)(mt6835_sum / FILTER_N); }但这个实现有个经典的坑当角度在358度和1度之间切换时平均结果会趋近于180度左右产生一个完全错误的“穿越值”。这是因为角度信号本身的性质决定的普通的算术平均不适用于圆形变量。解决办法有两个方向。一个简单的方式是做角度差值判断如果当前值和上一值的差超过半圈的一半就认为是跨越了0度边界把当前值加上或减去16384后再参与平均。另一个更通用的方式是转成sin和cos再平均float sin_sum 0.0f; float cos_sum 0.0f; for (int i 0; i FILTER_N; i) { float rad angle_deg[i] * 3.14159265f / 180.0f; sin_sum sinf(rad); cos_sum cosf(rad); } float avg_deg atan2f(sin_sum / FILTER_N, cos_sum / FILTER_N) * 180.0f / 3.14159265f; if (avg_deg 0) { avg_deg 360.0f; }这个办法计算量稍大但在0度边界附近不会出错适合位置控制里的慢速高精度场景。如果是做速度环更推荐直接用差分值滤波而不是对绝对角度做平均。6.2 多圈角度的累计方法14位分辨率只有一圈。如果机器要处理多圈场景比如机器人关节就需要在软件里自己扩展圈数计数。思路很简单记录上一次的角度值用当前值减上一次值判断是否跨过零点int32_t mt6835_total 0; uint16_t mt6835_last 0; void MT6835_UpdateTotal(uint16_t current) { int16_t delta (int16_t)(current - mt6835_last); if (delta 8192) { delta - 16384; } else if (delta -8192) { delta 16384; } mt6835_total delta; mt6835_last current; }加上圈数以后多圈位置可以用下面的式子表示int32_t multi_turn_position mt6835_total;这个方案的前提是单次采样间隔内磁铁转过的角度不能超过半圈也就是8192个码。对于大多数运动控制场景这个条件都成立。如果转速非常快采样频率又跟不上就只能上更高分辨率或者更快读取周期否则无法恢复整圈计数。6.3 在FOC控制里怎么用MT6835这类绝对角度磁编码器在FOC里最大的价值是上电就能拿到转子机械角度配合电机极对数就能计算出电角度省去了传统增量编码器上电找零的过程。要特别注意一点磁铁安装在转轴上的角度和电机三相绕组的电角度零点通常不是天然对齐的。所以量产程序里一般会做一次标定把读到的机械角度和电角度之间的固定偏置存到Flash里每次上电后减去这个偏置再参与FOC计算。以极对数为2的电机为例电角度等于机械角度乘以2再减去标定偏置float elec_angle fmodf(mech_angle * 2.0f - offset_angle, 360.0f); if (elec_angle 0) { elec_angle 360.0f; }如果这个偏置标定不准最明显的现象是电机出力变小电流波形不对称。最后一个经验不要把SPI读取放到定时器中断里用阻塞方式跑。HAL_SPI_TransmitReceive内部有等待超时机制在中断里调用会占用大量CPU时间。我最初把读取放到1kHz定时器中断里结果中断执行时间太长电机反而更抖。后来改成在RTOS任务里用5kHz频率读取或者用SPI DMA配合双缓冲读取周期稳定了控制效果立刻改善。MT6835本身不是一个难伺候的芯片真正决定成败的是你有没有把握住SPI那些看起来不起眼的时序细节。这里再分享一个验证的小技巧调试时用串口把原始角度值打印出来打印频率控制在5到10Hz就够了然后手动缓慢转动磁铁观察输出是不是从0线性增加到16383再回到0。如果这一关能稳定通过再上PID调试效率会高很多。我第一次调的时候就因为太心急一头扎进控制参数堆里绕了很大的弯路。串口打印看到的数据曲线很多时候比示波器更能说明问题。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑