公司动态
STM32下MT6835磁编码器SPI角度读取与零点标定全流程解析
简介MT6835编码器角度读取示例代码是一份面向嵌入式开发者的完整工程资源适用于电机位置检测、工业伺服、机器人关节等需要精确角度反馈的场景示例基于Keil MDK工程环境可直接迁移到ARM Cortex-M4平台调试编码器。资源包共1042个文件解压后约7.35MB以C源码575个.c、头文件260个.h、汇编文件51个.s为主同时包含Keil工程文件、调试配置文件、链接脚本、ARM数学库.a/.lib及编译生成的.hex、.map、.lst等产物覆盖从源码到镜像生成的完整流程。该资源已有1446人学习对于正在调试编码器或开发FOC控制器的工程师具有较高参考价值资源内附带的ARM Cortex-M4数学库可用于角度数据滤波、坐标变换等运算启动文件与链接脚本能帮助理解处理器初始化与内存布局减少重复配置工作。整体来看这份工程示例既可作为学习MT6835驱动原理的入门材料也可作为电机位置检测项目的代码模板有助于缩短前期开发与排错时间。 最近在调一套BLDC伺服位置环电机尾部装的是MT6835磁编码器角度读取这块虽然代码不算难但里面坑确实不少。MT6835是一颗14位分辨率的磁性角度编码器通过SPI接口可以直接读出绝对角度单圈分辨率16384折合下来大概是0.022度。这篇文章就把我从硬件接线、SPI初始化、角度解析到零点标定的完整流程整理出来示例代码是基于STM32 HAL库平台写的但思路对所有带SPI主机的MCU都通用。适合正在做电机FOC控制、云台角度反馈或者机械臂关节闭环的工程师参考刚接触磁编码器的新手也能照着一步步跑通。1. 整体方案与设计思路1.1 为什么是MT6835而不是ABZ增量编码器项目选定MT6835核心原因是它的“绝对角度”特性。普通增量式ABZ编码器断电后会丢失位置每次上电都要先找零点或者靠霍尔换向信号辅助定位而MT6835是绝对式磁编码器上电读一次SPI就能拿到当前机械角度对伺服控制来说省掉了复杂的找零流程。和光电绝对编码器比磁性方案的一个突出优势是不怕油污、灰尘和轻微振动。光电编码器的码盘如果被污染轻则误码重则整个反馈信号中断磁编码器只要磁铁和芯片之间的气隙在合理范围表面盖点东西也不太影响读数。另一个优势是体积小芯片加一颗小磁铁就能干活特别适合空间受限的舵机、云台关节和轮毂电机。我对标过同类的AS5047P那颗芯片确实性能很强带DAEC动态角度误差补偿但价格也明显高一截。MT6835走的是性价比路线14位分辨率、SPI/ABZ/PWM多种输出对大多数电机控制场景已经绰绰有余。如果你做的是高转速主轴或者对角度精度有极端要求的设备可以再考虑AS5047P如果是常规的FOC伺服、云台稳定、协作机械臂关节MT6835完全够用。1.2 角度读取的核心链路拆解MT6835的角度读取流程本质上就是一次SPI主从通信主机把CS片选拉低主机输出16个SCK时钟芯片在SDOMISO线上按高位先出的顺序把当前绝对角度数据送出来CS拉高一次读取结束。这16位数据里有用的角度信息在低14位高2位一般是状态标识或者未使用位。也就是说收到原始数据后要做一次raw 0x3FFF的掩码操作把高两位清掉剩下的值就是0到16383的绝对角度计数。很多磁编码器的SPI读取都不需要发送寄存器地址CS一拉低直接吐数据MT6835的默认输出模式就是这样。如果你手里的批次需要先发一个8位命令再读那把SPI发送的第一字节改成对应寄存器地址通常是0x00即可后面的解析逻辑不用变。14位分辨率换算成角度很简单角度 raw / 16384 × 360°。1个LSB对应约0.0219度。这个值对大多数电机位置环足够了FOC电角度插值也顺滑不会出现明显的阶梯感。1.3 代码结构规划我在项目里把MT6835相关代码单独拆成一个模块不跟主循环耦合太深。整个模块分成三层驱动层负责SPI读写、CS控制、读取原始14位数据解析层负责角度换算、零点补偿、多圈累加应用层对外提供角度、速度、单圈位置接口。这样做的目的是方便移植。今天用STM32明天换GD32或者ESP32只需要改驱动层的SPI函数上层控制逻辑完全不用动。下面各个小节就按这个分层往下写。2. 硬件接线与机械安装2.1 最小系统接线MT6835的引脚不多典型四线SPI接法加电源和地就能跑起来。我用的STM32F103平台接线表如下MT6835引脚引脚功能STM32连接目标VCC电源输入3.3V并联0.1uF去耦电容GND地GNDSCKSPI时钟SPI1_SCK (PA5)SDI/MOSI串行数据输入SPI1_MOSI (PA7)SDO/MISO串行数据输出SPI1_MISO (PA6)CS片选任意GPIO我用PA4这里有个容易踩的坑MB6835的SDI和SDO是分开的两根线接的时候不要跟I2C的SDA/SCL混在一起SPI是全双工MOSI和MISO必须单独接。之前见过有人把SDI悬空不接结果芯片一直不输出有效数据因为芯片端没有收到正确的命令字。CS引脚建议接到普通GPIO上用软件控制拉高拉低不要用SPI的硬件NSS。硬件NSS在HAL库配置里容易因为管理模式切换产生额外电平变化导致芯片误以为片选被拉起读取时序错乱。电源方面MT6835一般支持3.3V供电5V供电需要查一下具体型号手册。我统一用3.3V和STM32同一个电源域避免电平不匹配。VCC旁边加0.1uF去耦电容如果电机驱动干扰大再加一个10uF的电解电容。2.2 磁铁安装与磁场屏蔽磁编码器能不能量得准机械安装比代码更重要。MT6835需要配一颗径向磁化也叫直径方向磁化的圆柱磁铁N-S极在磁铁的直径方向上而不是轴向充磁。轴向磁化的磁铁装在芯片正上方芯片感应的磁场变化非常弱角度读数会乱跑甚至根本不动。我这边用的是直径6mm、厚度3mm的径向磁化钕铁硼磁铁用铝合金支架固定在电机转轴末端。磁铁底面到芯片封装表面的气隙控制在0.5到1.5mm之间。气隙太近容易蹭到芯片太远磁场强度衰减角度噪声会变大实测超过2mm后噪声明显上升。安装同心度也要注意。磁铁中心和芯片中心尽量对准偏差太大时读出来的角度会带有明显的正弦性误差。要求高的话可以做个简单的定位工装或者用带台阶的转轴固定磁铁我见过很多样机直接用胶粘结果转子一转起来磁铁偏心最后只能重新拆装。另外提醒一句MT6835对周围的铁磁材料比较敏感。电机轴如果离芯片太近轴本身的剩磁或导磁特性会叠加到工作磁场里造成读数畸变。设计时尽量让芯片远离电机铁芯和永磁体或者用非导磁的不锈钢轴套做过渡。3. 完整示例代码实现3.1 SPI外设初始化我用STM32CubeMX生成基础工程然后手动调整SPI参数。关键配置如下void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_2EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); }时钟极性设成低相位设成第二个边沿也就是SPI Mode 1这是不少磁编码器推荐的采样时序。如果你的芯片读出来数据不对可以把相位改成第一个边沿Mode 0再试多数情况下两者会有一个能正常工作。波特率预分频这里我选了64分频。以STM32F103的72MHz SPI时钟来算实际SCK是1.125MHz这个速度对MT6835来说非常稳妥。之前我把预分频调到4SCK跑到18MHz读取偶发乱码降到8分频还偶尔跳变最后定在64分频长时间运行再没出过问题。磁编码器不是高速通信器件没必要追求SPI带宽稳定优先。CS引脚的GPIO配置#define MT6835_CS_GPIO_Port GPIOA #define MT6835_CS_Pin GPIO_PIN_4 void MT6835_CS_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin MT6835_CS_Pin; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(MT6835_CS_GPIO_Port, GPIO_InitStruct); HAL_GPIO_WritePin(MT6835_CS_GPIO_Port, MT6835_CS_Pin, GPIO_PIN_SET); }上电初始化后CS默认拉高避免芯片在SPI没配置好之前误认为是片选有效。3.2 单圈角度读取与角度换算核心读取函数我封装成直接返回浮点角度uint16_t MT6835_ReadRawAngle(void) { uint8_t tx_buf[2] {0x00, 0x00}; uint8_t rx_buf[2] {0x00, 0x00}; uint16_t raw 0; HAL_GPIO_WritePin(MT6835_CS_GPIO_Port, MT6835_CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 2, 10); HAL_GPIO_WritePin(MT6835_CS_GPIO_Port, MT6835_CS_Pin, GPIO_PIN_SET); raw ((uint16_t)rx_buf[0] 8) | rx_buf[1]; return (raw 0x3FFF); } float MT6835_GetAngleDeg(void) { uint16_t raw MT6835_ReadRawAngle(); return (float)raw * 360.0f / 16384.0f; }解释一下为什么发送的两个字节都是0x00MT6835在默认模式下不需要命令字只要CS有效并有时钟芯片就会把角度数据推出来。HAL库的HAL_SPI_TransmitReceive是同时收发主机会一边发送0x00一边接收正好产生16个SCK时钟读回两个字节。返回值经过raw 0x3FFF之后就是0到16383的整数。换算成角度就是乘以360再除以16384因为满量程是16384。举个例子读回4096对应90度读回8192对应180度读回12288对应270度。这里要说一个细节就是浮点运算在角度环里如果调用频率很高可以考虑用定点数表示避免除法消耗太多CPU。我遇到的情况是10kHz FOC电流环如果每个周期都调用一次浮点除法STM32F103就有点吃力。所以实际项目里我保存的是原始uint16_t值到需要人机交互或者上位机显示时才转成浮点角度。3.3 零点标定与回绕处理MT6835上电后的0度是芯片内部定义的物理零位不一定是机械原点。实际使用中需要做一次软件零点标定把当前机械位置设为0度。static uint16_t g_zero_offset 0; static int32_t g_angle_accum 0; static int16_t g_last_raw 0; static uint8_t g_is_first 1; void MT6835_CalibrateZero(void) { g_zero_offset MT6835_ReadRawAngle(); g_angle_accum 0; g_last_raw g_zero_offset; g_is_first 0; } float MT6835_GetDegWithOffset(void) { uint16_t raw MT6835_ReadRawAngle(); int32_t diff (int32_t)raw - (int32_t)g_zero_offset; if (diff 0) diff 16384; return (float)diff * 360.0f / 16384.0f; }这个零点补偿的核心是只做一次偏移量的减法不改变角度量程。比如标定零点时原始值是8192之后读到4096差值是-4096加回16384后得到12288对应270度。逻辑上就是沿着角度环从零点顺时针转到了270度位置。多圈累加又是另一层逻辑。伺服控制里经常需要知道转子转了多少圈而单圈绝对角度只有0到16383无法表达超过一圈的累积角度。我在代码里维护一个int32_t的累加器每相邻两次读取算一次差值然后累加int32_t MT6835_GetAccumAngleRaw(void) { uint16_t raw MT6835_ReadRawAngle(); int16_t curr (int16_t)raw; int16_t delta; if (g_is_first) { g_last_raw curr; g_is_first 0; return 0; } delta (int16_t)(curr - g_last_raw); if (delta 8192) delta - 16384; if (delta -8192) delta 16384; g_angle_accum delta; g_last_raw curr; return g_angle_accum; }这里的关键点是回绕判断当两次采样之间角度跨过0点直接相减会得到一个很大的差值。例如上一次是16380这一次是10直接减得到-16370逻辑上不对。我用阈值8192判断超过半圈就认为是回绕给差值加上或减去一个完整量程16384修正后的delta就是真实的角度增量。8192正好是14位量程的一半只要两次采样的时间间隔内转子转动不超过半圈这个算法就是可靠的。这个前提在实际项目中很重要。如果控制周期太慢或者转速极高相邻两次采样之间转过了半圈以上回绕算法就会丢失方向信息。我遇到过的极限情况是3000转每分也就是每毫秒转18度完全没问题但如果你跑到超过1万转每分就要考虑缩短采样周期或者换更高分辨率的编码器。3.4 基于角度增量的测速多圈累加除了位置闭环用顺带还能得到速度信息。利用相邻两次读数的角度差和间隔时间float MT6835_GetSpeedDegPerSec(void) { static uint32_t last_tick 0; static float last_angle_deg 0; float dt; float speed; float cur_angle_deg; cur_angle_deg MT6835_GetDegWithOffset(); dt (HAL_GetTick() - last_tick) / 1000.0f; if (dt 0.001f) { return 0.0f; } speed (cur_angle_deg - last_angle_deg) / dt; if (speed 180.0f) speed - 360.0f; if (speed -180.0f) speed 360.0f; last_tick HAL_GetTick(); last_angle_deg cur_angle_deg; return speed; }速度单位是度每秒除以6就得到转每分。这个测速方法在低速时误差偏大因为单位时间内角度变化太小量化噪声占的比例高。如果要做高精度的转速闭环建议用专门的定时器捕获或者对位置做低通滤波后再差分。4. 调试过程中的经典问题与排查4.1 典型故障速查表故障现象可能原因处理办法角度值固定不变CS没有真正拉低或SCK没有时钟输出用示波器看CS和SCK波形确认GPIO模式正确读回数据全0或全0x3FFFMISO虚焊、引脚配置错误检查SDO到MCU MISO的连接可加10k上拉角度时好时坏偶尔跳变SPI时钟太快时序余量不足降低SPI预分频比如64分频起步0度附近来回跳359/0回绕判断没做或阈值不对检查是否有delta范围修正逻辑角度非线性波动像正弦磁铁安装偏心或轴向充磁换成径向磁化磁铁重新安装保证同心噪声比规格大气隙过大或电源纹波严重缩小气隙到1mm内VCC加去耦电容这套速查表是我反复试错后沉淀下来的大部分问题都是机械或电气连接闪存代码反而不是重灾区。4.2 排查实例一度以为芯片是坏的有次调试读取角度始终是固定值而且是0x3FFF换了芯片也一样。最后发现是MISO和MOSI两根线接反了。板子上芯片的SDO确实接到了MCU的MISO引脚但PCB布线时丝印标错实际线序反了。这种问题用逻辑分析仪最清晰直接看SDO上有没有数据脉冲如果没有倒查硬件连接。另一个常见问题是SPI初始化时把CS配成了复用推挽。当时CubeMX默认把NSS脚设成复用功能结果CS一直被硬件控制跟我软件里拉高拉低冲突。解决办法是把CS脚配成普通GPIO输出确保软件能完全控制片选信号时序。还有个经验是关于机械振动的。样机阶段磁铁是用双面胶固定的跑了一段时间后角度噪声越来越大最后拆开发现磁铁已经松动移位。所以量产设计里磁铁必须用结构件压紧或者高强度胶固定不能只靠双面胶振动环境下绝对撑不住。4.3 给新手的两个调试建议第一个建议是先打开上位机或者串口把原始角度值以16位十六进制格式打印出来。这样能快速区分是通信问题还是数据解析问题。如果每次读回都是固定值或者全FF肯定是通信链路的问题如果数值变化但乱序再检查掩码和大小端。第二个建议是写一个简单的转动测试程序让电机匀速转动或者手动缓慢旋转磁铁观察角度输出是否能从0连续增长到16383再回绕到0。这个测试能一次性验证SPI通信、回绕算法、零点偏移三块逻辑比直接上位置环调参数省事得多。最后分享一点实际操作体会MT6835这个芯片给我的整体感受就是“便宜大碗”14位绝对角度加SPI接口对绝大多数反馈应用来说性能完全够。整个项目踩过的坑主要集中在机械安装和SPI时序上反而代码逻辑只占了一小部分。如果让我重新做一遍我会先花时间把磁铁安装工装设计好确认磁场和气隙达标后再写代码这样调试周期能缩短一半以上。另外建议保存一份读回原始值的demo方便现场快速判断硬件排线有没有问题。希望这篇关于MT6835编码器角度读取示例代码的梳理能帮你少走点弯路如果有更好的调试思路欢迎交流。本文还有配套的精品资源点击获取