
简介面向电机位置检测与编码器应用开发者MT6835编码器角度读取示例代码提供了一套完整的固件工程可帮助用户快速完成角度数据采集、寄存器配置与通信调试。资源共1042个文件以C源文件与H头文件为主体同时包含工程配置、链接脚本、IAR与Keil相关库文件以及可烧录的Hex输出等压缩包仅7.35MB便于在嵌入式环境下直接参考或移植。工程内已集成官方DSP数学库及角度插值与滤波相关算法便于提高角度读取的精度与稳定性代码分层清晰目录结构完整适合具有一定STM32基础、需要快速上手MT6835传感器的开发者。目前已有1446人学习浏览借助这套示例代码可大幅缩短编码器调试周期是电机位置感知类项目的有力参考。 做电机控制这些年编码器这块我从光电换到磁编码器踩了不少坑。前阵子项目里需要换一颗角度传感器原来的AS5047P供货紧张评估了一圈选了MT6835这颗TMR磁编码器。它的数据手册和示例代码不算多网上讨论也零散我把自己从“第一次读角度”到“跑通位置闭环”整个过程整理出来重点放在SPI方式读取角度数据代码可以直接抄坑也一并排好。1. 先搞懂MT6835这颗芯片再谈读角度1.1 TMR磁编码器到底强在哪MT6835是麦歌恩MagnTek推出的一款基于TMR隧道磁阻效应的磁编码器芯片。传统霍尔编码器检测的是磁场强度TMR检测的是磁场方向这决定了它天生更适合做角度传感。做个不严谨但好懂的类比霍尔方案像用温度计判断你在南半球还是北半球TMR方案像直接看指南针指向哪里精度和稳定性完全不在一个量级。这颗芯片内部集成了TMR传感器阵列、信号调理电路、ADC和多种输出接口外部只需要一个径向充磁的磁铁放在芯片正上方旋转就能得到0到360度的绝对角度。分辨率14位也就是一圈分16384份每份大约0.022度做机器人关节、云台、伺服电机的位置反馈都够用。还有个容易被忽略的点TMR对磁场强度的适应范围比较宽磁铁和芯片之间的距离留一点余量角度输出依然稳定。我试过把磁铁抬高0.3毫米读数偏差很小。换作霍尔方案这个安装公差已经会让输出明显漂移了。1.2 输出接口有好几种为什么推荐SPIMT6835同时支持SPI、ABZ增量输出、UVW换向输出和PWM输出。不同输出对应的使用场景完全不同选错会非常难受。ABZ输出本质是增量信号上电后你不知道转子当前在哪个角度必须配合Z信号或者外部初始化流程做一次“找零”动作。UVW是给无刷电机换相用的只有6个扇区位置精度对位置环来说不够细。PWM输出好一些是绝对角度但角度信息调制在占空比里MCU测脉宽需要额外占用定时器而且更新率通常比SPI低不少。SPI是这几条路里最直接的CSB拉低SCK给时钟MISO上就吐出来一个14位绝对角度。MCU在几微秒内拿到完整位置延迟低、精度不打折非常适合做电流环、速度环、位置环这种实时性要求高的控制。这篇文章后面所有内容都围绕SPI方式展开。2. 硬件准备引脚连接和最容易忽略的时序前提2.1 供电、磁铁安装和CSB上拉的几个细节MT6835常见的供电范围是3.3V到5V这给接入不同系统留了很大方便。不过有一点必须注意芯片电源引脚旁边一定要放一个0.1uF的去耦电容并且尽量贴近芯片引脚。我在第一版测试板上偷懒电容放得远了5毫米结果角度数据偶发跳变排查了半天才找到是电源纹波干扰。这种问题在原理图阶段就规避不要指望后面软件滤波能救回来。磁铁安装是另一个大坑。MT6835要求磁铁的旋转轴心和芯片封装中心对齐磁铁端面尽量平行于芯片表面。稍微偏一点角度误差就会出现两个周期的正弦波动也就是转子转一圈误差波动两次。怎么快速判断不要急着看精度先手动缓慢转一圈把读出来的角度值连成曲线如果看到明显的正弦形状基本就是磁铁装偏了。对心安装座在这里不是可有可无的是保证精度的刚需。CSB片选引脚建议用一个10k电阻上拉到高电平。芯片上电初期如果CSB处于不确定电平有可能误进入配置模式导致后续SPI通信错乱。很多示例代码不会写这一条但我在批量测试里确实遇到过几次初始化失败加上上拉之后问题再也没出现过。2.2 SPI模式选择Mode 1还是Mode 3MT6835的数据手册里对SPI时钟极性和相位的描述不同批次或烧录版本可能有差异最稳妥的做法是拿到手册先查“SPI Mode”那一节。我这边测试下来常用的是Mode 3即CPOL1、CPHA1SCK空闲时为高电平在第二个边沿采样。但也有工程师反映某些型号按Mode 1也能读其实是因为部分配置下时序窗口比较宽。这里给出一个实测建议先用1MHz的低SPI时钟跑通通信确认能稳定读出角度再慢慢把时钟频率往上提。直接上10MHz往往不是芯片不行而是杜邦线太长、接触不良或者地线回路太大导致信号质量变差。低速跑通了至少能证明SPI配置和寄存器地址是对的后面再优化速度。另外注意读角度数据是一个“最少两个字节”的传输过程。CSB拉低后MCU发送时钟的同时接收数据发出去的数据内容是0x00即可。这个特征和很多普通SPI外设不一样容易让人困惑下一节详细展开。3. 角度数据藏在哪个寄存器SPI读时序怎么组织3.1 寄存器地图角度位置和状态字MT6835内部寄存器不多但读角度之前最好花两分钟把地址和位宽理清楚。最核心的是一个14位的角度寄存器通常手册里标注为Angle Data Register地址类似0x3FFF这样的只读区域14位数据铺在寄存器的高14位或低14位具体布局看手册的“Register Map”表。我的习惯是先把数据手册寄存器章节截图存下来写代码时对照着看避免记混。除了角度值芯片还提供磁场状态信息比如磁场过弱、过强报警。角度数据可信的前提是磁场强度正常所以正式项目里不要只读角度建议顺带把状态位也读出来一旦磁场异常可以马上切到安全逻辑。自己玩可以先忽略但项目越复杂这个状态字越值钱。角度值换算成角度很简单raw范围是0到16383实际角度 raw * 360 / 16384。写代码时浮点运算要留意效率如果是在MCU里做控制可以预先算好刻度因子或者直接用raw做差值和闭环运算最后输出角度时再转成标准单位。3.2 一次完整的SPI读取命令字节、MISO返回和角度合并MT6835的SPI读操作CSB拉低之后SCK开始工作芯片在MISO线上按高位在前的方式把数据推出来。所谓“命令字节”其实在纯读角度这个场景里可以不纠结因为要的不是写寄存器角度数据是芯片主动输出的。但为了兼容性和扩展性发送两个0x00字节是通用做法。第一次收到的高字节和第二次收到的低字节拼接起来就得到一个16位数据。这里要特别说清楚为什么会写“MOSI发0就行”SPI是全双工协议MCU要接收数据就必须同时发送数据发的内容对这个读操作本身没有意义所以写0是最自然的。有人在这里卡住觉得“我发错了命令芯片不理我”其实读到的一直稳定的变化值就是角度数据本身。合并代码我用的是uint16_t raw ((uint16_t)rx[0] 8) | rx[1]; uint16_t angle_raw raw 2; // 14位有效数据为什么要右移2位因为14位数据在16位帧里通常占据高14位低2位要么是0要么是状态位或校验位。具体低2位是什么同样以手册为准但右移2位拿到纯角度值这个思路在多数MT系列芯片上是通用的。4. 示例代码逐行拆解从STM32 HAL到裸机移植4.1 初始化部分CSB引脚和SPI外设配置下面这套代码我在STM32F103和GD32E230上都跑过逻辑是通用的。CSB用普通推挽GPIO控制这样比硬件NSS更可控。SPI初始化按Mode 3设置时钟先给1MHz验证后续再调速。HAL库的写法如下void MT6835_SPI_Init(void) { GPIO_InitTypeDef gpio {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); gpio.Pin GPIO_PIN_4; // CSB gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_PULLUP; gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, gpio); gpio.Pin GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; // SCK, MISO, MOSI gpio.Mode GPIO_MODE_AF_PP; gpio.Alternate GPIO_AF0_SPI1; HAL_GPIO_Init(GPIOA, gpio); 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_HIGH; // CPOL1 hspi1.Init.CLKPhase SPI_PHASE_2EDGE; // CPHA1 hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CSB默认高 }这里有个值得说的细节CSB用GPIO_PULLUP是因为前面提到的上拉需求但如果你外部已经接了10k上拉电阻内部上拉开不开都行。另外SPI的NSS配置成软件模式不要让硬件NSS自动控制不然CSB时序会乱。4.2 读角度函数核心就这么几行#define MT6835_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define MT6835_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) uint16_t MT6835_ReadAngleRaw(void) { uint8_t tx[2] {0x00, 0x00}; uint8_t rx[2] {0x00, 0x00}; MT6835_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 2, 10); MT6835_CS_HIGH(); return ((uint16_t)rx[0] 8) | rx[1]; }重点说一下为什么用TransmitReceive而不是单独的Receive。SPI从机要输出数据必须有主机提供的时钟才能移出来所以即使我们不需要发送任何有意义的命令也要写寄存器并触发SCK。发送缓冲区放0x00接收缓冲区拿到的就是芯片返回的16位数据。HAL_SPI_TransmitReceive一句搞定发送和接收比手动先发送再接收更稳定。CSB的拉低和拉高时机也值得注意。必须在SCK第一个边沿之前拉低并且在最后一个SCK边沿结束后才能拉高否则芯片可能把这次传输当成无效帧。代码里的顺序很简单但如果你用DMA或者中断务必保证CSB操作和SPI传输之间的时序不能被其他任务插入。4.3 角度合并、零位校准和简单的滤波读取到的16位数据低2位按前面说的方式处理。如果不想在每次读取时都做一遍右移可以在初始化时把角度值直接对齐到16位范围即左移2位存起来这样后续做差值和比例运算更方便。实际项目中还有一个绕不开的需求把当前机械位置清零作为零位。实现上不需要改芯片配置MCU里记住一个零位偏移就行。每上电时让设备转到机械零位然后调用一次采集零位的函数。后续角度 (当前raw 16384 - 零位raw) 0x3FFF这个写法能正确处理角度从16383回绕到0的情况比简单的相减更稳妥。滤波方面要分场景。如果你只是看角度给上位机显示滑动平均或者一阶低通都可以。但如果在电流环里做位置反馈滤波反而会引入相位延迟不建议对原始数据做深度滤波。我个人的习惯是闭环控制用原始值显示和诊断用轻滤波之后的值。5. 实测中的几个坑启动时间、非正交安装和零点漂移5.1 上电之后立刻读会读到什么MT6835上电之后并不是瞬间就能输出有效角度数据芯片内部有上电自检和稳定流程。我第一次用的时候在MCU初始化之后立刻读角度发现输出偶尔是0xFFFF或者看起来随机的跳变值一度以为是通信配置有问题。后来看了手册里关于启动时间的描述又用示波器对比了CSB和MISO波形才确认是读取时机不对。芯片从上电到内部ADC稳定通常需要几个毫秒到几十毫秒具体数值受电源质量和磁铁状态影响。稳妥做法上电后至少延时50到100毫秒再开始读角度或者在初始化流程里先连续读几次丢弃前几个值直到数据稳定。这个坑在带电机驱动的系统里更明显。电机驱动上电瞬间会产生很大的电流冲击如果编码器和功率电路共用一个电源即使芯片已经进入稳定状态也有可能被拉出异常。有条件的话编码器电源和驱动电源分开或者至少加磁珠和电容隔离。5.2 磁铁偏心和非正交安装的故障特征磁铁的安装问题不会直接导致通信失败但会让角度精度变得很差。典型特征是你手动匀速转动磁铁读到的角度值却不是线性增长而是有一个周期性的非线性波动每转一圈波动两个完整的正弦周期这是TMR传感器检测方向夹角导致的固有特性。我遇到过一种更隐蔽的情况磁铁和芯片表面不平行也就是磁铁有一侧翘起来了造成角度误差在小角度区域尤其严重误差曲线像被掰弯的S形。排查方法还是那个土办法记录一整圈的raw值在PC上画出来和理想的直线做差误差曲线的形状会告诉你安装问题的类型。修正方式有两种一是改机械结构用带定位台阶的转轴和安装座保证同心度和垂直度这是治本二是在软件里做误差补偿表通过实际标定获得误差曲线在算法里查表修正。如果精度要求不是极端苛刻机械修正优先软件补偿表是最后的手段。5.3 零位偏移和温度漂移零位偏移是每个绝对编码器系统都会遇到的问题。MT6835的电气零点通常和磁铁角度零点不重合你装上去之后读到的0度未必是机械意义上的0度。解决办法就是前面说的软件零位校准把当前角度记录为偏移量。但温度上来之后磁铁和芯片的相对位置会因为材料热胀冷缩发生微小变化角度输出的零点也会跟着漂。要求高的系统除了选用低温漂磁铁还会在结构上让芯片和磁铁的固定方式尽量对称减小热应力带来的位移。如果应用场景温度变化很大可以定期做校零或者每次上电都重新校准。比如协作机械臂每次上电做一次零点回归就是这个原因。6. 从“能读角度”到“能用角度”多圈、速度和控制闭环6.1 角度回绕和圈数计数单圈绝对角度很好用但实际控制系统经常需要知道累计旋转了多少圈。MT6835本身输出的是单圈绝对角度没有内部多圈计数器所以多圈逻辑要放在MCU里做。核心思路是每次采样把当前raw和上一次raw做差如果差值大于半圈比如大于8192说明发生了正转回绕圈数加一如果差值小于负半圈说明发生了反转回绕圈数减一。用代码表示static int16_t rotation_count 0; static uint16_t last_raw 0; int32_t MT6835_GetMultiTurnRaw(void) { uint16_t cur_raw MT6835_ReadAngleRaw(); int32_t delta (int32_t)cur_raw - (int32_t)last_raw; if (delta 8192) rotation_count--; else if (delta -8192) rotation_count; last_raw cur_raw; return (int32_t)rotation_count * 16384 (int32_t)cur_raw; }这个算法的前提是相邻两次采样之间转过的角度不超过半圈也就是180度。对采样周期来说这个要求通常很容易满足。但如果MCU被高优先级中断卡了很久错过一次采样圈数就可能算错。对实时性要求高的场景建议把角度读取放在定时器中断里保证采样的连续性。6.2 速度计算和滤波的工程取舍从角度信号计算速度最直接的方法是位置差分速度 角度差 / 时间差。这里建议直接对raw做差不要先转成角度再算。raw的差值是整数MCU跑起来快也不会引入浮点误差控制环直接用这个差值作为速度环的比例项输入非常方便。差分测速有个问题角度量化误差会被放大。如果转速很低相邻两次采样之间角度只变化几个LSB差分结果就会出现阶梯状跳变控制效果不好。我的做法是低速时把时间窗口拉长一点比如每隔固定时间累加一次角度增量再做平均高速时缩短窗口保证响应速度。窗口大小要根据控制周期和电机极对数实测调整没有通吃全场的固定值。最后说一个关于滤波的经验不要在位置环反馈路径上放一个很强的低通滤波器哪怕它能让你在示波器上看到平滑的曲线。滤波带来的延迟会让系统的相位裕度下降表现出来就是电机嗡嗡响或者轻微震荡。我习惯把滤波放在速度计算之后给速度环用位置反馈保持原始值这样系统既稳定又不丢失分辨率。MT6835这颗芯片的SPI读取链路并不复杂真正的功夫都在硬件细节和实际调试上。如果你也正在把磁编码器集成进控制系统建议先做一块最简单的转接板低速跑通SPI读角度再装到电机上验证最后才敢放量。这条路我替你走了一遍能跳过不少坑。本文还有配套的精品资源点击获取