ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

MSPM0G3507 TIMER-QEI配置实战:编码器接口从原理到调试

2026/10/3 1:20:41 拓冰建站 浏览量
MSPM0G3507 TIMER-QEI配置实战:编码器接口从原理到调试 这篇是CCS配置MSPM0G3507系列的第七篇。前几篇把GPIO、UART、PWM这些基础外设过了一遍从这篇开始进入“更接近真实产品”的环节——编码器接口。做电机控制、小车底盘、云台角度反馈、机械臂关节闭环这些应用编码器是绕不开的传感器。如果你之前习惯用普通GPIO外部中断去读编码器输出或者用定时器捕获硬数脉冲那么这篇内容会让你省掉一大半软件负担。这篇的主题是TIMER-QEI也就是MSPM0G3507定时器模块里的正交编码器接口模式。我会从编码器输出信号开始讲说清楚QEI到底帮你做了哪些事然后带你在CCS的SysConfig里完成配置最后给出可以直接抄的代码框架以及我实际调试中踩过的坑和排查方法。适合正在用MSPM0G3507做闭环控制项目、或者准备把编码器接入到现有工程里的开发者。1. 编码器基础与QEI原理为什么不用普通GPIO中断很多初学者第一次处理编码器信号时第一反应是用外部中断A相接GPIO中断B相在中断里读电平判断方向。这个方案在低速、低精度场景下能跑但你很快就会遇到两个问题一是脉冲频繁时中断开销极大CPU大部分时间都在响应中断二是方向判断、倍频计数这些逻辑即使实现了也容易出bug。QEI模块就是解决这件事的硬件方案。1.1 增量式编码器输出什么信号增量式编码器最常见的输出是两路方波习惯上叫A相和B相也有叫PHA/PHB的。两路信号的频率与转速成正比相位差固定为90度。正转时A相领先B相90度反转时B相领先A相90度这个相位关系是判断方向的关键。除了A/B两相不少编码器还有第三路输出也叫Z相、Index信号每转一圈输出一个脉冲可以用来找机械零点。QEI模式下通常把这三个信号都接到定时器模块的专用输入引脚上。一个典型的编码器输出是这样的A相方波频率随转速变化B相方波与A相相差90度Z相每圈一个窄脉冲用于零位校准你可能会问90度相位差能用来干什么答案是通过对A/B两相的电平组合进行解码不仅知道当前位置还能知道方向。每次A相或B相跳变状态组合都会按固定顺序变化正转和反转对应的状态变化顺序刚好相反。QEI模块内部有一张状态表专门用来做这件事。1.2 QEI模块到底帮你干了什么如果不用QEI靠GPIO中断读编码器你需要做这些事在A相上升沿、下降沿各触发一次中断在中断里读B相电平判断正转还是反转维护一个计数器正转加一反转减一这套逻辑看起来不难但编码器一转快就知道问题了电机高速旋转时每秒可能产生几千甚至几万个中断CPU被完全拖死。而且在中断服务函数里去读另一个引脚电平还要考虑信号边沿时序是否已经稳定很容易误判。QEI模块通过硬件完成了三件事一是自动判向。硬件根据A/B相的状态变化顺序自动判定正反转。二是倍频计数。常见的增量式编码器都支持2倍频和4倍频两种计数方式。2倍频只看A相或B相一路信号的边沿4倍频则是A/B两相的上升沿和下降沿都计数。采用4倍频后编码器每圈输出的脉冲数乘以4分辨率直接翻四倍。三是方向可逆计数。正转时计数器向上加反转时向下减当前计数值就是相对位置。这个值可以直接读出来也可以软件随时清零。除了这些核心功能MSPM0G3507的QEI还自带数字滤波可以滤掉信号上的毛刺避免计数乱跳。后面我会详细说滤波到底该怎么配。1.3 MSPM0G3507的TIMER-QEI能力MSPM0G3507的定时器资源比较丰富部分定时器实例支持QEI模式。实际用的时候你不需要记住每个定时器是否支持——打开SysConfig选一个定时器看模式下拉框里有没有QEI选项就清楚了。我用的SDK和CCS版本下TIMG0等实例提供了QEI模式选项支持以下能力位置计数器宽度可选16位或32位支持2倍频和4倍频计数输入信号数字滤波滤波窗口可配置位置比较中断当计数值到达预设阈值时触发中断支持与PWM/捕获等模式之间的切换配置最后那句话很重要。MSPM0G3507的定时器不是每路都锁定固定功能而是同一个物理模块可以配置成PWM输出、输入捕获、QEI输入等不同模式。这意味着你可以用一个定时器实例搞定编码器读取同时用另一个定时器实例输出PWM给电机驱动这在做电机闭环控制时非常方便。2. 硬件连接与SysConfig可视化配置在CCS中给MSPM0G3507做外设配置核心工具是SysConfig。它不仅是图形化界面还会在保存时自动生成初始化代码省掉大量手写寄存器的工作。前提是你得知道每个配置项背后的含义不然容易配出一个“能编译但跑不对”的工程。2.1 引脚分配与电气接线先明确一点MSPM0系列大部分引脚是功能可复用的同一个定时器的QEI输入可以映射到多个引脚具体用哪几个引脚以SysConfig的PinMux界面为准。我这次以LP-MSPM0G3507开发板为例选择了PA6和PA7作为A相和B相输入Z相分配到PA5。电气上要注意几个细节都是实际容易踩的坑编码器通常是开漏或推挽输出。如果编码器模块是开漏输出必须在A/B/Z三根信号线上加上拉电阻否则电平浮空QEI计数会疯狂乱跳。开发板上虽然有板载上拉电阻但从外部引线长距离接入时建议在编码器侧就近补上4.7k到10k的上拉。编码器电源和MCU必须共地。这个看起来是常识但外接电机驱动板时经常被忽略。编码器的地如果不和MCU的地连在一起A/B相电平参考点不一致轻则方向判断错误重则直接损伤引脚。接线长度尽量短。实验环境下一米以内问题不大如果超过半米尤其是电机线在旁边建议A/B/Z信号用双绞线或屏蔽线屏蔽层单端接地。我第一次做电机转速闭环时编码器线从电机旁边走了过去转速低时计数正常速度一拉高位置值就开始乱跳排查了半天才发现是电机PWM线串扰到编码器线上了。2.2 SysConfig中一步步配置QEI打开CCS的syscfg文件后左侧Peripherals区域会列出当前工程可用的外设。新建工程时默认会有定时器实例但默认模式通常是PWM。你需要在Timer配置界面里做几件事。第一步把Operation Mode从默认的PWM模式切换到QEI模式。切换后界面会自动显示QEI相关配置项同时PinMux那边也会要求你为PHA、PHB、IDX三个信号分配引脚。注意有些定时器实例不支持QEI如果模式下拉框中找不到QEI选项换一个定时器实例试试。第二步配置Position Counter Width也就是位置计数器宽度。通常选32位因为我做位置累计时不喜欢频繁处理溢出32位计数范围足够大。如果只测一圈内的角度且计数器会周期性清零16位也够用。第三步配置Quadrature Mode也就是倍频方式。倍频模式计数逻辑分辨率提升2xA相或B相的单边沿计数每圈脉冲数×24xA/B两相所有边沿计数每圈脉冲数×4我通常直接选4x。因为编码器的物理分辨率是固定的4倍频可以在不换硬件的情况下把位置分辨率提高一倍。代价是计数频率也翻倍但对于电机应用场景完全在MSPM0G3507的能力范围内。第四步配置Input Filter。这个配置项的功能是对输入信号做数字滤波只有信号稳定持续特定时间后引脚状态才被认为有效。滤波器窗口值需要根据编码器最高输出频率来定。这里有个取舍滤波窗口太宽高速时容易漏掉真实脉冲太窄又滤不掉毛刺。我一般这样估算先算编码器最高频率。假设编码器每圈360线最高转速3000转/分钟那么每秒脉冲数为360×3000/6018000个即18kHz。A相信号周期约55微秒4倍频时单位边沿间隔约13.75微秒。滤波窗口至少要小于这个间隔的三分之一才能保证高速时信号不丢失建议配到3到4微秒左右。SysConfig里这个参数通常以系统时钟周期数的形式配置具体值需要根据你工程里的时钟频率换算。第五步确认PinMux。在SysConfig的PinMux界面里把PHA、PHB、IDX分别绑定到你实际接线的引脚上。选完引脚后SysConfig会自动检查引脚复用冲突如果有其他外设占用了同一个引脚这里会直接报错。这一步能省掉你对着数据手册查引脚复用表的功夫。配置完成后保存syscfg文件CCS会自动生成对应的初始化代码。你会在ti_msp_dl_config.c里看到类似SYSCFG_DL_TimerGQEI_init()这样的函数syscfg生成的所有初始化都会在这里汇总。2.3 生成代码的工程结构SysConfig会把QEI的初始化逻辑封在ti_msp_dl_config.c里对你的工程直接影响不大。你在main函数里要做的事情很简单先调用SYSCFG_DL_init()这一步会把所有在SysConfig里配置好的外设包括QEI初始化到位。然后调用QEI启动相关函数这里不同SDK版本的API命名会有差异例如部分版本是start函数部分版本通过enable计数来实现具体以你安装的SDK头文件为准。启动之后硬件就开始自动捕捉A/B相脉冲并更新位置计数器了CPU完全不需要参与。这就是我推荐用硬件QEI而不是GPIO中断的主要原因。3. 代码实现与API调用初始化代码生成后真正的难点在于怎么用好QEI模块提供的数据。这一节我会把位置读取、速度计算、位置比较中断三个最常用的功能代码写出来并解释每一段的计算逻辑方便你直接套到自己的工程里。3.1 读取位置计数读取当前相对位置是QEI最基础的操作。以TI的driverlib为例接口风格通常长这样#include ti_msp_dl_config.h volatile int32_t motor_position 0; int main(void) { SYSCFG_DL_init(); // 启动QEI计数 SYSCFG_DL_TimerGQEI_start(); while (1) { // 读取当前相对位置 motor_position SYSCFG_DL_TimerGQEI_getPosition(); delay_ms(10); } }这里注意一点获取到的位置值是可以为负的。电机反转时计数器会向下减所以这个变量要定义成有符号类型不要用uint32_t去接。我记得第一次调的时候这里犯过错把位置读数直接赋给了unsigned变量电机反转后读出的值看起来特别大误以为是方向判断错排查了好一会。如果你需要“绝对角度”而不是累计位置可以在每次获取到值后对单圈脉冲总数取模。假设编码器每圈360线4倍频后每圈计数总量是1440int32_t raw_pos SYSCFG_DL_TimerGQEI_getPosition(); int32_t angle_count raw_pos % 1440; if (angle_count 0) { angle_count 1440; } float angle_deg (float)angle_count * 360.0f / 1440.0f;这段代码把相对位置限制在0到1439的范围内对应0到360度。为什么要加if (angle_count 0)因为C语言里负数取模的结果是负数不处理的话角度会出现负值。清零操作用得也很频繁。电机每次回到零点或者你启动一个运动流程时需要把历史累积清掉SYSCFG_DL_TimerGQEI_clearPosition();3.2 用定时器中断实现转速测量位置是速度的积分在一定时间间隔内读取两次位置做差分再除以时间就得到了速度。这个方法在控制领域叫M法测速适合中高速场景。低速时两个采样周期内脉冲变化太小误差会很大需要配合T法测脉冲间隔使用这个后面细节我再展开。这里给出一个基于定时器中断的M法实现。假设你已经在SysConfig里配置了一个固定周期10ms的定时器中断用来做速度采样volatile int32_t last_position 0; volatile float motor_speed_rpm 0.0f; #define ENCODER_LINES 360 #define QUADRATURE_MODE 4 #define PULSE_PER_REV (ENCODER_LINES * QUADRATURE_MODE) #define SAMPLE_SEC 0.01f void TIMER_INTERRUPT_HANDLER(void) { int32_t cur_position SYSCFG_DL_TimerGQEI_getPosition(); int32_t delta cur_position - last_position; last_position cur_position; // 每秒转数 (delta / PULSE_PER_REV) / SAMPLE_SEC // 转每分钟RPM 每秒转数 * 60 motor_speed_rpm (float)delta * 60.0f / ((float)PULSE_PER_REV * SAMPLE_SEC); }代入具体数值看一下如果电机正转且10ms内位置计数增加了50那么每秒转数等于50除以1440再除以0.01约3.47转每秒转速约208RPM。这个计算过程建议你推一遍后面换不同线数的编码器时只需要修改PULSE_PER_REV这一个宏就行。中断里唯一要注意的是读取位置计数和清零操作不要在多个上下文里混着调用。比如主循环里清了计数器中断又读了计数器时序对不上就会出现速度跳变。3.3 速度测量方法的选择M法适合转速较高的场合。转速太低时一个采样周期内位置增量只有几个或者零个计数算出的速度离散且不平滑。如果你做低速性能要求高的控制比如云台稳定、伺服低速爬行建议采用T法测量两个脉冲边沿之间的时间间隔间隔越短速度越高。T法的实现思路是利用另一个定时器的捕获功能去捕获编码器脉冲的边沿时间戳。MSPM0的定时器捕获功能通常和QEI不在同一个定时器实例上所以你需要在SysConfig里再开一个定时器配置成输入捕获模式。这个做法代码量不大但是需要额外占用一个定时器资源。实际项目中也可以M/T法结合低速用T法高速用M法切换点通常选在两种方法误差曲线交点附近。对于大部分电机控制入门场景M法加一个稍微短一点的采样周期比如5ms到10ms已经足够了。3.4 位置比较中断做限位QEI模块的位置比较中断很有意思它可以在位置计数达到预设值时自动触发中断不需要CPU一直轮询比较。这个功能做机械限位保护非常合适。假设你的设备正转时位置不断增加希望在位置计数达到5000时触发一个中断触发后把电机停下来#define LIMIT_POSITION 5000 int main(void) { SYSCFG_DL_init(); SYSCFG_DL_TimerGQEI_start(); // 设置位置比较值 SYSCFG_DL_TimerGQEI_setCompareValue(LIMIT_POSITION); // 使能位置比较中断 SYSCFG_DL_TimerGQEI_enableInterrupt( DL_TIMER_QEI_INTERRUPT_POSITION_COMPARE); NVIC_EnableIRQ(TIMERQEI_INT_IRQn); } void TIMERQEI_INT_IRQHandler(void) { if (SYSCFG_DL_TimerGQEI_getEnabledInterruptStatus() DL_TIMER_QEI_INTERRUPT_POSITION_COMPARE) { // 清除中断标志 SYSCFG_DL_TimerGQEI_clearInterruptStatus( DL_TIMER_QEI_INTERRUPT_POSITION_COMPARE); // 到达限位位置停止电机 motor_stop(); } }注意位置比较中断触发后位置计数器不会自动停止或清零需要你在中断里手动处理不然它还会继续累加。我一般在使用这个功能做限位时还会同时设置一个软件层面的冗余判断比如在中断里再确认一下当前读取的实际位置确实超过了阈值防止比较值刚好落在脉冲边沿导致误触发。4. 常见问题与踩坑记录这部分是重头戏。QEI的代码逻辑并不复杂恰恰是硬件接线、参数匹配、时序配合这些“看不到的问题”让人调一遍调一天。我把自己实际踩过的坑和排查思路整理出来遇到同样现象时可以少走弯路。4.1 方向反了现象电机正转位置读数却在减小或者闭环控制里位置误差越来越大。原因和解决办法都很直接。如果是A/B两相接反了把A相和B相对调一下或者在软件里把读取到的位置值取负motor_position -SYSCFG_DL_TimerGQEI_getPosition();交换接线是硬件层面的处理但有些场景不太方便改线软件取负一个符号就够了。这里要注意的是取负之后位置比较阈值、速度计算的符号都要对应调整否则其他地方会跟着出错。4.2 位置值乱跳、毛刺多现象电机静止不动时位置值偶尔会自己增加或减少几个计数电机转动后读数跳动幅度明显大于正常范围。先检查共地。编码器地和MCU地没有连通这是最常见的原因。再检查编码器输出形式。如果是开漏输出外部上拉电阻缺失信号沿会变缓QEI在边沿处反复判定产生额外计数。补上4.7k到10k上拉电阻后问题通常会消失。最后检查滤波配置。如果此时毛刺仍在就把SysConfig里的Input Filter宽度调大一点。但也要注意我前面说过的滤波窗口不能超过脉冲间隔的三分之一否则高速时会漏脉冲。这个参数要综合最高转速和滤波器精度一起调。4.3 速度计算波动大现象位置计数是对的但速度值忽大忽小闭环控制里转速抖。先看采样周期是否太短。10ms周期下低速电机每个周期只增加几个计数差分结果的量化误差会被放大。可以让采样周期长一些比如20ms到50ms但这样速度环的响应频率会变低。更常用的办法是软件滤波。我对速度值做了一阶低通滤波motor_speed_rpm motor_speed_rpm * 0.8f new_speed_rpm * 0.2f;系数0.8和0.2可以根据实际响应调整。这个式子本质上是让新的测量值只更新20%的影响历史值占大头速度曲线会平滑很多。4.4 常见问题速查表现象可能原因排查与解决位置值不动引脚接错、QEI未启动核对PinMux里PHA/PHB映射确认调用了启动函数正转读数减小A/B相反接交换A/B接线或软件取负静止时计数跳动共地不良、缺上拉、毛刺检查共地补4.7k上拉加大滤波窗口高速丢脉冲滤波窗口太宽缩短滤波窗口保证小于最小脉冲间隔的1/3速度波动大采样周期太短、量化误差调整采样周期或者对速度做一阶低通滤波位置值溢出计数器宽度不足改用32位计数模式避免外部软件覆盖其数值4.5 和电机驱动/PID配合的心得编码器最常用的场景还是配合电机做闭环控制。MSPM0G3507一个定时器输出PWM给电机驱动另一个定时器做QEI读取编码器这种结构很适合做位置环和速度环。我做速度环时速度反馈就是上面提到的定时器中断里的M法速度值PWM占空比通过PID输出控制周期和速度采样周期保持一致。需要提醒的是控制周期不能比速度采样周期还短否则PID拿到的反馈值长期不更新调节效果会很差。位置环则直接用QEI位置计数作为反馈量。因为位置计数是累加的如果做的是有限行程运动记得在运动开始前清零位置或者在中断里把当前位置记录为原点用当前位置减去原点值作为实际位置。还有一点建议如果编码器有Z相上电后做一次找零操作很有必要。Z相每圈一个脉冲可以消除增量编码器没有绝对位置的劣势。找零的逻辑可以简单粗暴——开机后低速正转等Z相中断到来时把位置计数器清零这样后续的角度计算就有了绝对参考点。Z相中断在QEI里是单独的一个中断源需要你在SysConfig里使能然后在中断处理函数里做清零动作。5. 实操记录从配置到读出稳定转速最后放一段我自己的调试流水账把整个流程串起来。当时用的编码器是每圈360线的增量式带Z相输出电机额定转速3000转/分钟。第一步接线。A相接到PA6B相接到PA7Z相接到PA5。编码器模块用5V供电输出信号是3.3V兼容所以直接进MCU引脚没问题。第二步SysConfig配置。定时器选TIMG0模式切到QEI32位位置计数器4倍频滤波窗口选择了对应3微秒的参数PinMux把三个信号绑到对应引脚。第三步代码。main里调用SYSCFG_DL_init后启动QEI计数使能Z相中断然后用定时器中断做10ms速度采样。Z相中断里做了一件事把位置计数器清零同时点亮板载LED作为找零完成的提示。第四步上电测试。电机空转串口打印位置值和速度值。低速时位置值单方向递增速度值有轻微波动高速时曲线平滑没有再出现跳变。这个过程中遇到的两个小问题都和硬件有关第一次因为编码器线从电机PWM线旁边穿过转速拉高后位置值开始跳变把编码器线换走之后恢复正常第二次是编码器供电用了调试器的3.3V电机一启动电压跌落编码器输出异常换独立5V供电后解决。这两件事都不复杂但很典型。QEI本身在硬件上已经很可靠了剩下的问题基本都出在信号完整性和供电上。调试时如果发现数据异常先不要怀疑代码逻辑拿示波器或者万用表量一下A/B两路波形确认信号干净、幅值正确再回头看配置。这是一条非常省时间的排查路径。