多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

MSPM0G3507定时器QEI模式实现增量编码器测速与位置检测

MSPM0G3507定时器QEI模式实现增量编码器测速与位置检测 老读者应该知道这个系列前面几篇已经把MSPM0G3507的GPIO、定时器、中断这些基础外设过了一遍。今天这篇轮到编码器准确说是用芯片内部的TIMER模块配置QEIQuadrature Encoder Interface正交编码器接口去接增量式编码器。做电机控制、小车底盘测速、云台角度反馈、甚至机械臂关节位置检测编码器都是绕不开的传感器。MSPM0G3507这颗M0内核的MCU主频80MHz片内集成多个TIMG定时器其中一部分可以配置成Encoder模式来替代外置的解码芯片。这篇就把怎么在CCS里配合SysConfig把QEI跑起来讲透同时把配置过程里容易踩的坑也一并列出来。先说清楚适用人群手里有MSPM0G3507的LaunchPad或自研板想接带A/B两路正交输出的增量编码器用来测速度或相对位置那就非常适合往下看。如果用的是绝对值编码器走的是SPI/SSI这类通信接口那QEI模式就用不上了等下会简单说两句区别。1. 为什么用TIMER做QEI增量编码器的读数原理1.1 正交信号和四倍频的本质增量式编码器内部是一个码盘加光电或磁感应元件转动时输出两路方波信号工程上通常叫A相和B相有的也叫PHA和PHB。这两路信号的关键特征就是相位差正好是90度也就是正交。正转的时候A相上升沿总是比B相提前四分之一周期反转的时候反过来B相领先A相。MCU就是靠检测这两路信号的边沿先后顺序来判断方向靠统计边沿数量来计算转过的角度或圈数。这就引出了编码器分辨率的一个重要概念四倍频。一个完整正交周期里包含四个边沿时刻分别是A上升沿、B上升沿、A下降沿、B下降沿。如果只数A相的上升沿一圈下来计数值等于码盘线数PPR如果四个边沿都数一圈下来计数值就是4倍的PPR。所以很多编码器标称500线在四倍频模式下实际一圈能产生2000个计数脉冲分辨率一下就上去了。MSPM0的TIMG模块在Encoder模式下硬件会自动完成这个边沿检测和方向判断不需要CPU在中断里去比对波形。这也是为什么QEI模式比普通的外部中断GPIO扫描方案靠谱得多脉冲再密也不会丢CPU压力也小。1.2 TIMG8/QEI模块与独立编码器接口的取舍有些MCU会带一个专门的正交编码器接口外设叫QEI或者eQEP独立于定时器存在。MSPM0G3507这颗芯片的资源布局不太一样TI的做法是复用TIMG定时器资源让其中一个定时器工作在Encoder模式从功能上讲就等效于一个硬件QEI接口。这么设计的好处是外设资源不浪费。同一颗TIMG既可以做普通定时器、PWM输出、输入捕获也可以切换成Encoder模式按需分配。坏处就是配置的时候要稍微注意一下引脚复用和中断资源的占用比如你用TIMG8做了编码器接口那TIMG8的捕获比较通道就不能再拿去做PWM输出了这个要在工程规划阶段就考虑清楚。从MSPM0G3507的型号命名看这个系列内部有多个TIMG实例常见的有TIMG0、TIMG1、TIMG6、TIMG7、TIMG8、TIMG9等。具体哪个TIMG支持Encoder模式不同封装、不同型号会有差异。稳妥的做法是打开SysConfig之后看外设列表里哪个TIMG下拉选项里有Encoder Mode可选项那就是能用的。一般来说TIMG8和TIMG9很多型号都支持但还是要以你手上芯片的数据手册为准。如果接的是EC11那一类机械旋转编码器或者带霍尔传感器的电机编码器原理都一样都是A/B两路正交方波所以下面这套配置流程完全通用。2. SysConfig一步一步配好QEI2.1 创建工程和选择TIMG外设在CCS里新建MSPM0G3507工程后工程里会有一个.syscfg配置文件双击打开就能进SysConfig图形化配置界面。这个工具的本质是生成初始化代码你不用手写寄存器配置那一大坨但要清楚它帮你做了什么。先把要用的TIMG外设加进来。在SysConfig左侧的Software组件里找到TIMER添加一个定时器实例。比如添加Timer_8对应的是TIMG8。然后在Timer_8的配置页面里把Mode从默认的PWM模式改成Encoder Mode。这时候页面会弹出跟编码器相关的配置项包括输入引脚选择、计数模式、计数方向、Top值、滤波参数等。引脚选择这一步要特别留意MSPM0G3507引脚复用不是随便选的必须参考数据手册里的引脚复用表在SysConfig里也要勾选对应的引脚。通常QEI模式需要两个输入引脚分别接A相和B相如果要用Z相做零点复位还得再选一个Index引脚。改完引脚后SysConfig会自动在PINMUX里把引脚复用关系配好。这个环节最容易出的问题就是引脚冲突比如你之前已经用了PA0做串口或者PWM输出现在又要拿它做编码器输入SysConfig会直接标红报错。解决办法就是换引脚或者调整之前的外设分配。2.2 关键参数计数模式、方向、Top值和滤波进入Encoder模式后有几个参数对实际测量结果影响很大一定得理解每一项的含义再动手。第一个是Counting Mode也就是计数模式。里面有Quadrature 1x、Quadrature 2x、Quadrature 4x这几个选项。1x模式下只在A相上升沿计数2x模式下A相和B相的上升沿都计数4x模式下四个边沿都计数。我这里做转速和位置测量普遍选4x模式分辨率最高。如果编码器本身线数已经很高或者CPU后续要处理的运算量比较大也可以用1x模式降低计数频率具体情况具体取舍。第二个是Count Direction计数方向。一般选Bidirectional也就是双向计数。正转时计数值增加反转时计数值减少。这个参数其实由硬件根据A/B相位差自动判断不需要软件干预。但你要知道计数方向只代表编码器转动的方向不等于电机的正转或反转因为电机接线和编码器安装方式会影响这个对应关系。第三个是计数器范围。MSPM0的TIMG计数器默认是16位也就是计数范围从0到65535。在Encoder模式下你要设置Position Count Bottom和Position Count Top两个值。通常就是Bottom0Top0xFFFF。但这里有个实际问题如果电机转速很高或者编码器线数很多65535个计数可能不够用。比如1000线的编码器在4x模式下一圈就是4000个计数65535大概只能代表16.4圈如果设备行程超过这个范围计数值就会溢出回绕。解决溢出问题有两个思路一是把多个16位计数扩展成32位在中断里维护软件计数值二是如果应用场景单一圈位置比如云台、机械臂关节可以在Top值上做文章让计数器转满就停止或者回零。后面第4节会详细讲32位扩展的处理方法。第四个是Input Filter输入滤波。编码器信号在工业现场容易受到干扰电机电刷火花、PWM驱动线缆串扰都可能在A/B信号上叠加毛刺。如果毛刺被当成有效边沿计数位置数据就废了。SysConfig里可以给编码器输入配置一个数字滤波器本质是连续采样输入信号只有信号稳定超过设定的采样窗口后才认为是一次有效电平变化。滤波器的采样窗口可以按系统时钟周期数来配置。窗口越大抗干扰能力越强但同时会增加信号延迟高速场合下可能导致相位检测滞后。实际项目里要根据编码器最高输出频率来折算别盲目把滤波窗口调到最大。比如系统时钟80MHz滤波窗口设成8个时钟周期对应延迟0.1微秒对大多数应用来说影响可以忽略。2.3 中断和事件什么时候该打断CPUEncoder模式下有几个事件可以产生中断最常用的是Load Event也就是计数值更新事件。可以配置成每检测到一定数量的计数边沿就触发一次中断比如每次计数匹配某个预设值就通知CPU读取当前计数值。这个机制在做位置采样时特别有用。另外一个实用的事件是Direction Change方向改变事件。电机正转突然变成反转的时候触发中断。在位置控制场景里这个事件可以用来判断运动方向是否发生意外变化及时停止驱动或者记录换向时刻。还有一个跟Index信号相关的事件。如果编码器有Z相每转一圈输出一个脉冲在Encoder模式下可以配置成检测到Index输入时清零计数器或者触发中断。这个功能在回零、找原点时非常关键。中断配置在SysConfig里勾选对应选项就行实际代码里写ISR时再从状态寄存器里判断是哪个事件触发的。不要把多个事件的中断逻辑都塞在一个ISR里然后分不清来源项目大了会非常难查问题。3. 代码实现初始化、读位置、算速度3.1 初始化代码和寄存器行为SysConfig配置完成后CCS会自动生成初始化代码。在main.c里调用SYSCFG_DL_init()所有外设初始化就都完成了。这时定时器还没开始跑需要手动调用启动计数函数。以TIMG8为例初始化之后启动代码如下#include ti_msp_dl_config.h int main(void) { SYSCFG_DL_init(); DL_TimerG_startCounter(TIMER_8); while (1) { // 主循环任务 } }DL_TimerG_startCounter()的作用是让编码器接口开始工作此后A/B引脚上的正交信号就会驱动计数器自动增减。读取当前计数用DL_TimerG_getTimerCount(TIMER_8)返回的就是当前计数值。这里要讲一个实际使用中的细节DL_TimerG_getTimerCount()读回来的RAW计数值是无符号16位。如果你在调试窗口里看到数值在0到65535之间来回跳不代表位置在反复变化而是计数器本身按16位回绕了。要拿到真实的位置变化量必须用带符号的差值计算要靠软件把这个回绕处理好。还要注意一点SysConfig生成的代码不要手动改。比如你直接在生成的timer.c里改了某个寄存器的值下次重新生成代码会被覆盖掉。所有配置都回到.syscfg里去改然后重新生成。这是SDK开发的基本规矩新手经常在这里翻车。3.2 位置累计和方向判别的完整写法实际项目里我们一般不会直接用16位RAW值当位置数据太容易溢出了。正确做法是在中断里维护一个32位甚至64位的软件位置计数器每次读取到新的RAW值后和上一次的RAW值做带符号差值再累加到全局位置变量上。示例代码如下volatile int32_t g_position 0; volatile uint16_t g_last_raw 0; volatile int8_t g_direction 1; void TIMG8_IRQHandler(void) { uint32_t int_status DL_TimerG_getEnabledInterruptStatus(TIMER_8); DL_TimerG_clearInterruptStatus(TIMER_8, int_status); // 读取当前RAW值 uint16_t current_raw DL_TimerG_getTimerCount(TIMER_8); // 计算差值处理16位回绕 int16_t delta (int16_t)(current_raw - g_last_raw); // 累加到32位全局位置 g_position delta; g_last_raw current_raw; // 方向判断 if (DL_TimerG_getTimerDirection(TIMER_8) DL_TIMER_DIRECTION_UP) { g_direction 1; } else { g_direction -1; } }这里关键在于(int16_t)(current_raw - g_last_raw)这个处理。只要两次读取之间实际转过的计数不超过32767这个差值就是准确的不仅消除了16位回绕问题还保留了正负号。比如上一次Raw值是65000这一次变成了100实际是正向转了516个计数那么(int16_t)(100-65000)算出来的结果就是516完美避开了回绕误判。手动写32位扩展时要保证ISR执行时间足够短不能在中断里做串口打印、浮点运算这类耗时操作。中断频率如果很高比如编码器每分钟几千转再加上高线数和4x倍频每秒钟可能产生几十万次计数每次计数不一定触发中断要看Load Event的配置方式。通常做法是每累计固定脉冲数比如每次Load事件才进一次中断别让边沿直接驱动中断。3.3 从计数差值换算转速RPM有了位置计数速度计算就顺理成章了。最常用的方法就是固定时间窗口法每隔固定时间比如100ms读取一次位置值和上次做差然后用差值、编码器线数、倍频数算转速。公式很简单转速RPM 计数差值 / (编码器每转总计数 × 采样时间秒) × 60其中编码器每转总计数 编码器线数PPR × 倍频数。比如500线的编码器4x模式下每转总计数是2000。采样周期100ms两次采样之间计数差值2000那正好一秒转了20转不对100ms转了2000个计数相当于0.1秒转一圈所以一秒10转RPM600。写代码时可以直接用整数运算避免浮点开销#define ENCODER_PPR 500 #define QUAD_MULTIPLIER 4 #define COUNTS_PER_REV (ENCODER_PPR * QUAD_MULTIPLIER) #define SAMPLE_PERIOD_MS 100 volatile int32_t g_position 0; volatile int32_t g_last_pos 0; volatile int32_t g_speed_rpm 0; void Speed_Task_100ms(void) { int32_t current_pos g_position; int32_t delta current_pos - g_last_pos; g_last_pos current_pos; // 转成RPM g_speed_rpm (delta * 1000) / SAMPLE_PERIOD_MS * 60 / COUNTS_PER_REV; }实际用的时候建议先打印原始计数差值验证方向对不对再套公式。如果正转时速度显示为负说明接线或者方向配置反了在SysConfig里把两个输入引脚对调就行不用改代码。固定时间窗口法的缺点是在低速时分辨率不够。如果电机一分钟只转几转100ms窗口内计数差值只有几个脉冲算出来的速度要么是0要么跳变很大。低速高精度测速可以改用脉冲间隔法通过输入捕获测量两个边沿之间的时间再换算出速度但这个话题展开又是一篇后面再单独聊。4. 实测中踩过的坑和排查技巧4.1 上电丢脉冲和毛刺计数我最开始接编码器的时候遇到一个很典型的问题用手慢慢转编码器串口打印的位置值偶尔会突然跳几十上百个计数而且方向不固定。用万用表量A/B引脚电平转动时确实有变化但波形不稳定。排查了一圈发现问题是A/B引脚悬空时电平不确定。因为编码器是开漏输出或者机械触点驱动能力很弱如果MCU引脚内部没有使能上拉电阻引脚电平就会在阈值附近飘产生毛刺硬件QEI根本分不清哪些是有效边沿全都记下来了。解决方法是把A/B两个输入引脚都配置成内部上拉。在SysConfig里给引脚设置Pull Up或者在代码里手动配置DL_GPIO_setPulls(GPIOA, GPIO_PIN_0, DL_GPIO_PULL_UP);如果编码器线缆比较长或者现场干扰源多光靠MCU内部上拉还不够建议在靠近MCU引脚的地方外加一个10kΩ的上拉电阻到3.3V同时配合第2节讲的输入滤波器一起用。另外一个细节上电瞬间电机驱动器和编码器可能不是同时供电MCU会先跑起来而编码器还没稳定输出上电最初几十毫秒内的计数值是不可信的。工程上一般在上电后延时一下或者等系统启动完成后再清零位置值再开始累计。4.2 方向接反和引脚复用冲突方向接反是QEI配置里最普遍的问题特征是你手动正转打印的位置值却是负数或者位置值减小。很多人第一反应是改代码把差值取反但其实标准做法是回到SysConfig里把A相和B相的引脚对调或者把编码器插头反过来接。为什么推荐从硬件层面对调而不是软件取反因为只要软件里做一次取反以后别人看代码就得时刻记着这个坑而且如果需要用到方向改变事件取反逻辑很容易出遗漏。硬件把A/B对调之后信号相位关系和软件逻辑就完全一致了不容易出二次问题。引脚复用冲突是另一个高频事故。SysConfig里选引脚的时候如果编码器的A相引脚被其他外设占用配置界面会直接报错。但有些冲突是隐性的比如引脚本身支持的功能非常多你选了一个GPIO输出控制LED另一个外设也用了同一个引脚SysConfig能检测到但如果你用寄存器直接配置或者是旧版本SDK可能不会提示那么清楚。养成一个好习惯配置编码器输入引脚之前先看一遍原理图确认这个引脚没有被板上的跳线、按键、LED占用。4.3 16位计数溢出与多圈扩展第2节提过TIMG默认是16位计数器多圈位置累计要靠软件扩展。但在实际ISR里写扩展逻辑时有一个很容易被忽略的问题读取CPU死等和中断延迟。如果主循环里有长临界区比如关中断操作或者长时间屏蔽中断那么编码器中断可能不会及时响应导致两次读取之间实际计数差值超过32767这时(int16_t)差值计算就会溢出位置数据直接乱掉。所以用QEI的项目主循环里尽量不要长时间关中断尤其不能在高转速下用__disable_irq()包着一大段浮点运算。如果真的避免不了长时间关中断那就得提高采样频率或者改用硬件更强的方案。比如把Load Event配置成每256个计数触发一次中断频率就很低了主循环关中断几十微秒也不会丢位置因为QEI计数在硬件层面是实时累加的不受中断影响只是软件读取时可能错过一些中断而已。还有人问能不能把TIMG配置成32位模式。MSPM0G3507的部分TIMG支持32位计数模式但在Encoder模式下能否使用32位计数要参考芯片参考手册。即使支持32位计数器起始值、回绕处理方式也和16位完全不同代码逻辑不一样别指望直接替换。稳妥方案还是16位计数器软件32位扩展。4.4 滤波参数和信号完整性的取舍调滤波器参数的时候我试过把滤波窗口设到很大比如几百个系统时钟周期结果编码器转快了之后方向判断开始出错。原因很简单滤波器本质上是个低通环节窗口越大延迟越大A相和B相的边沿经过不同延迟处理后相位关系可能被破坏QEI硬件就分不清谁先谁后了方向自然乱跳。所以滤波器设置的原则是能滤掉干扰的正确窗口但不要追求越大越好。一般从8个时钟周期的窗口开始调如果还有毛刺再往上加同时配合外部RC滤波电路一起处理。外部在A/B引脚对地各加一个100pF到1nF的小电容也可以滤高频噪声效果也不错。如果编码器信号质量本身很差比如长线传输、接头松动那软件调参只是权宜之计最终要查接线和屏蔽。QEI遇到奇怪的方向抖动先拿示波器或者逻辑分析仪看A/B波形确认信号干净再回头怀疑软件。5. 后续扩展从测速到闭环控制这篇聊的编码器读取和测速方法其实是闭环控制的基础。位置值就是PID控制里的位置反馈速度值就是速度环的反馈有了这两个量电机控制就算真正打通了反馈回路。如果你后面要做速度闭环我建议速度环的采样周期和电流环尽量协调好比如速度环跑1kHz那编码器速度采样也按1ms周期来做别拖后腿。固定时间窗口法在低速时分辨率不够的问题闭环控制里会更敏感这时可以配合M/T法测速或者用输入捕获去测脉冲间隔低速效果会好很多。另外如果编码器带Z相回零逻辑一定要做进初始化流程里。设备上电先朝一个方向找Z相脉冲找到之后清零位置值这个位置的绝对性和可重复性比单纯靠限位开关更可靠。做这期内容的时候我又把小车的编码器电机驱动板翻出来重新测了一遍。给大家一个建议刚把代码跑通的时候先别急着写PID或者复杂算法把编码器正转、反转时打印的RAW值和方向值观察清楚确认接线、倍频数、方向都和预期一致之后再往上层加控制逻辑。基础的数据读准了后面怎么折腾都稳。这套配置流程放到其他型号的MSPM0上也基本通用理解了Encoder模式的本质换芯片也就是找引脚、配参数的事。
返回列表