简介: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时钟;
- 芯片在SDO(MISO)线上按高位先出的顺序,把当前绝对角度数据送出来;
- 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 | 地 | GND |
| SCK | SPI时钟 | 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来说非常稳妥。之前我把预分频调到4,SCK跑到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; }解释一下为什么发送的两个字节都是0x00:MT6835在默认模式下不需要命令字,只要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或全0x3FFF | MISO虚焊、引脚配置错误 | 检查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编码器角度读取示例代码的梳理能帮你少走点弯路,如果有更好的调试思路,欢迎交流。
本文还有配套的精品资源,点击获取