ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MSPM0G3507+TB6612+MG513X直流减速电机闭环调速实战

MSPM0G3507+TB6612+MG513X直流减速电机闭环调速实战 在轮式机器人和智能小车的制作圈子里驱动一组高性价比直流减速电机几乎是所有项目的必经之路。我之前给一个两轮底盘换主控把以前用的STM32F103换成MSPM0G3507之后顺手把驱动方案也换成了TB6612电机则选了带编码器反馈的MG513X系列。折腾完这套“MSPM0G3507 TB6612 MG513X”的组合最大的感受是BOM成本不高但速度控制的稳定性完全能支撑起后续做循迹、避障、SLAM底盘这些功能。这篇文章会从选型原因讲起给出完整的硬件接线、可直接运行的驱动代码、M法测速和增量式PID调速实现最后再把我实测踩过的坑一并列出来。如果你手头也是这几个物料或者正打算用它们搭一个带闭环调速的小车/机器人底盘这篇可以直接当操作手册看。1. 选型之前先搞清楚这套组合为什么合拍很多人直接抄别人原理图觉得能转就行。但真把MSPM0G3507、TB6612、MG513X三个物料放在一起看你会发现它们在资源配置上是互相补位的不是随便凑出来的。1.1 MSPM0G3507并非只是便宜MSPM0G3507是TI新一代Cortex-M0内核MCU主频最高80MHz带128KB Flash和32KB SRAM。光看性能可能不如STM32F103的高配版本但对电机控制这个场景来说它反而很顺手定时器资源多能同时输出多路PWM也能做输入捕获自带正交编码器接口QEI只是我这次用GPIO中断方案也能跑3.3V逻辑电平和TB6612的VCC逻辑侧完全兼容价格低开发环境用CCS或者Keil都行SysConfig图形化配置外设非常方便真正打动我的是SysConfig这个图形化工具。PWM周期、引脚复用、中断优先级这些都可视化配置生成代码后我再往里填控制逻辑。比起纯寄存器操作省出来的时间足够把算法调好。很多初学者担心M0能力不够实际上单路速度环跑到1ms周期都绰绰有余根本不需要上F4系列。1.2 TB6612和其它H桥器件相比强在哪儿TB6612FNG是东芝的电机驱动芯片单颗芯片内集成了两个H桥每个H桥额定电流1.2A峰值电流3.2A。很多人一提到驱动器就想到L298N我早期也用过后来全部换掉了根本原因就几点L298N的饱和压降大电机转速损耗明显芯片发热严重模块体积大双路H桥板子塞不进小车底盘需要外部二极管续流外围元件多接线容易错TB6612这些方面克制得多。电机供电电压范围2.7V到13.5V逻辑电压2.7V到5.5V板载已经把续流保护集成好了。MG513X这种12V直流减速电机空载电流一般两三百毫安堵转时可能超过1ATB6612的1.2A持续能力刚好压得住设计上还有余量。如果你非要用它驱动大型直流电机那才需要考虑换大电流驱动芯片但不是本方案该考虑的事。1.3 MG513X的编码器版本决定了闭环能否成立MG513X本质上是一个带减速箱的直流永磁电机但市面上卖的变体非常多这是新手最容易踩坑的地方。不带编码器的普通版只能做开环调速——给多少PWM跑多少速度负载变化时转速根本稳不住。而带霍尔编码器的版本在电机尾部会多出一组A、B两路正交方波输出这套方案才能做PID闭环。我用的型号是带霍尔编码器的MG513X系列也就是常说的MG513P/XR类变体常见减速比有1:10、1:19、1:30等。减速比越大输出轴转速越低、扭矩越大。做小车底盘一般选1:19或1:30具体看你需要的工作速度和爬坡能力。注意一点编码器反馈的是电机轴位置经过减速箱后才表现为输出轴转速所以后面测速换算要把减速比算进去这个在4.1节我会给出完整公式。2. 硬件接线关于电源、逻辑电平与编码器上拉的细节接线是整个系统最枯燥也最容易毁板子的一步。我见过不少人因为忘记共地或者把电机正负极接反直接烧了驱动芯片和MCU引脚。下面按模块拆开说。2.1 TB6612的十八个引脚怎么接才不懵TB6612常用模块引脚一般引出两组控制端和两组输出端核心引脚对照关系如下引脚功能接到哪里VM电机电源正极12V电源正极VCC逻辑电源3.3VGND电源地系统地PWMAA路PWM输入MCU某PWM引脚AIN1 / AIN2A路方向控制任意GPIOAOUT1 / AOUT2A路电机输出电机两根线STBY待机控制必须接高电平GND_AD模拟地与GND相连最容易踩的第一个坑就是STBY。STBY一旦没有拉高芯片内部全部处于待机模式电机怎么给PWM都不动。很多人怀疑代码有问题查半天发现是STBY这根杜邦线没接。建议直接用3.3V跳线固定在板上后面永远不会忘。第二个坑是VM和VCC供电混淆。VM是给电机输出级的电源电流大VCC是芯片逻辑电源电流小。给VM供5V、给VCC供3.3V是完全正常的反过来才是灾难。MG513X使用12V电机供电时VM接12VVCC接3.3V然后把两个GND连到一起。2.2 编码器A相B相接到MCU时要先想清楚的事带霍尔编码器的MG513X尾部通常引出5根线电机电源线2根一般红色/黑色直接接电机电源或TB6612输出编码器线3到5根VCC、GND、A相、B相有的还带索引Z相但常见是4或5根。编码器信号常见有两种输出方式推挽输出和开漏输出。很多MG513X霍尔编码器板载了上拉电阻直接输出0-3.3V方波但如果你拿到的是只有霍尔传感器的裸板输出端必须外加4.7k到10k上拉到3.3V否则MCU采样不到稳定电平。接线原则很简单A相接一个MCU中断引脚B相接另一个中断引脚两个引脚都配置为上升沿中断。A、B两路信号的相位差是90度所以A相上升沿时去读B相电平就能判断电机的旋转方向。这个方案不需要芯片支持正交解码任何M0都行。2.3 一张接线总表外加接线后必查清单以我手头这套为例引脚分配是软硬件一起设计的直接列出来供参考MSPM0G3507引脚连接目标功能PB0TB6612 PWMAPWM调速PB1TB6612 AIN1方向控制PB2TB6612 AIN2方向控制PC0编码器A相测速/方向判断PC1编码器B相测速/方向判断3V3TB6612 VCC、编码器VCC逻辑电源12V_INTB6612 VM电机电源GNDTB6612 GND、编码器GND共地接线完成之后先别急着上电按这个顺序检查用万用表量VM和GND之间是否有正常电机电压量VCC和GND之间是否为3.3V确认STBY引脚已经接到3.3V确认电机输出两根线接到AOUT1/AOUT2而不是接到VM如果编码器是开漏输出确认上拉电阻已经补上这套检查做完硬件基本就不会出问题了。接下来可以烧代码看现象。3. 让电机先动起来PWM与方向控制的代码级拆解闭环调速的前提是开环驱动稳定。如果连“给占空比-电机转”这个基本链路都跑不通PID写得再漂亮也没用。所以这一节先把驱动部分写扎实。3.1 定时器PWM和GPIO方向控制的配置逻辑在MSPM0 SDK环境下我用SysConfig完成以下配置选择一个定时器配置为PWM模式输出通道连接到PB0PWM频率设置为20kHz初始占空比0%AIN1、AIN2引脚配置为GPIO输出初始一个高一个低STBY引脚配置为GPIO输出初始为高20kHz这个频率不是随手填的。直流电机电枢绕组的电感在高频PWM下能把电流续得比较平滑运转噪音小频率太低比如1kHz电机会发出明显啸叫而且转矩脉动大。频率超过20kHz以后人耳就听不到了开关损耗也不高对MSPM0G3507来说80MHz主频输出20kHz占空比毫无压力。方向控制的逻辑是TB6612的H桥真值表决定的AIN1AIN2行为10正转01反转00刹车惯性停止11短接刹车所以“正转“不是靠AOUT1/AOUT2的物理接线定义的而是由IN1和IN2逻辑组合决定的。你可以先用一组正转状态确认接线方向是否符合预期不对就对调AOUT1/AOUT2两根线或者反了软件方向逻辑两种办法都行。3.2 最小驱动代码一个文件让电机转起来以下代码基于TI的MSPM0 SDK假设SysConfig已经生成了ti_msp_dl_config.h和相应的初始化函数。PWM的宏名以你实际SysConfig生成的为准下面这些名字是常见生成结果。#include ti_msp_dl_config.h #define PWM_PERIOD_COUNT (4000 - 1) // 80MHz / 20kHz 4000 void motor_direction_set(bool forward) { if (forward) { DL_GPIO_setPins(GPIOB, AIN1_PIN); DL_GPIO_clearPins(GPIOB, AIN2_PIN); } else { DL_GPIO_clearPins(GPIOB, AIN1_PIN); DL_GPIO_setPins(GPIOB, AIN2_PIN); } } void motor_duty_set(uint16_t duty_pct) { uint16_t compare_value (uint16_t)((uint32_t)duty_pct * PWM_PERIOD_COUNT / 100); DL_TimerG_setCaptureCompareValue(PWM_TIMER, compare_value, DL_TIMERG_CC_0_INDEX_C0); } int main(void) { SYSCFG_DL_init(); // STBY 引脚拉高让TB6612退出待机模式 DL_GPIO_setPins(GPIOB, STBY_PIN); // 方向默认正转占空比30% motor_direction_set(true); motor_duty_set(30); while (1) { // 可以在这里改占空比测试 } } }这段代码里PWM_PERIOD_COUNT为什么写4000减1因为80MHz的定时器时钟每个PWM周期计数4000次正好是20kHz。占空比寄存器存的是比较值比如50%对应2000。写成函数后motor_duty_set(30)就是30%占空比逻辑很直观。注意DL_TIMERG_CC_0_INDEX_C0里的CC_0跟你SysConfig里选的通道索引要对上如果选CC1这里要改成DL_TIMERG_CC_1_INDEX_C0之类的对应写法。3.3 开环空载验证不要急着写PID先把编码器放一边跑通这段代码观察电机空载转动情况。我的建议是从10%占空比开始逐步5%往上加同时记下每个占空比下电机的表现占空比很低时电机不转是正常的直流电机有启动死区不转不代表接线错误占空比到某个临界点后电机突然启动这是静摩擦造成的负载启动时要考虑这一点正常转动后声音均匀无明显啸叫说明PWM频率合适用稍大劲捏一下电机轴注意安全转速会有明显变化这就是开环系统不抗负载的表现这个现象不是失败而是后面PID调速要解决的核心问题。如果连这一步都跑不通回过头检查硬件接线和STBY配置。4. 转速闭环M法测速与增量式PID的实现开环驱动跑通之后就可以进入重头戏了让电机的实际转速追踪目标速度。这里最核心的两块是“怎么测转速”和“怎么根据误差调整PWM”。4.1 M法测速单位时间数脉冲的转换计算M法测速的原理很简单固定一个时间窗口在这个窗口内统计编码器脉冲数然后换算成转速。它适合中高速测量转速越高脉冲越多分辨率越好。对于MG513X这类减速电机来说低速时脉冲也够用所以这个方案能cover大部分小车场景。换算公式要理解清楚别硬背。MG513X编码器在电机轴上每转一周会输出若干脉冲电机轴又经过减速箱才到输出轴。所以输出轴转一圈编码器实际收到的脉冲数是PPR_TOTAL 编码器线数 × 减速比假设编码器线数为13减速比为30那么输出轴每转一圈编码器脉冲数就是390。如果一个50ms的采样窗口内计数到N个脉冲那么输出轴的转速RPM算出来就是RPM N / PPR_TOTAL / (50 / 60000) N × 1200 / 390 N × 3.08约也就是说50ms内每多统计到1个脉冲转速就增加了约3.08RPM。反过来如果目标是300RPM50ms窗口大概需要统计到约98个脉冲。用这种具体的数字去核对代码比只看抽象公式踏实得多。MCU端我用GPIO中断计数代码是volatile uint32_t g_pulse_cnt 0; void GPIOA_IRQHandler(void) { // A相上升沿触发 if (DL_GPIO_getEnabledInterruptStatus(GPIOC, ENCA_PIN) ! 0) { if (DL_GPIO_readPins(GPIOC, ENCB_PIN) ! 0) { g_pulse_cnt; // 正转 } else { g_pulse_cnt--; // 反转 } DL_GPIO_clearInterruptStatus(GPIOC, ENCA_PIN); } }这里的关键是A相上升沿去读B相电平如果B相也是高说明电机正转计数加一B相是低说明反转计数减一。如果电机方向跟代码里预期的相反要么对调A、B两根信号线要么把正负判断反过来不影响效果。4.2 增量式PID控制量和参数的完整实现PID算法有很多变体这里我用增量式。增量式输出的不是绝对的PWM占空比而是占空比的增量也就是说每次控制周期只修改一点点这样天然不容易积分饱和调试也更安全。用一个结构体保存PID状态核心计算函数如下typedef struct { float kp; float ki; float kd; float out; float err_last; float err_prev2; float out_min; float out_max; } IncPID; float IncPID_Calc(IncPID *pid, float target, float actual) { float err target - actual; float inc pid-kp * (err - pid-err_last) pid-ki * err pid-kd * (err - 2.0f * pid-err_last pid-err_prev2); pid-err_prev2 pid-err_last; pid-err_last err; // 输出限幅 pid-out inc; if (pid-out pid-out_max) pid-out pid-out_max; else if (pid-out pid-out_min) pid-out pid-out_min; return pid-out; }增量式的公式看起来比位置式复杂但细看不难。误差err的变化量和变化加速度分别对应P项和D项积分项则直接累加。它输出的是inc加到上一拍的输出上占空比变化是平滑的。我在实际项目里基本都用这种结构尤其是电机调速这种执行机构本身有惯性的场景assert起来后调参非常顺手。参数整定有个通用思路先把Ki和Kd设为0只加Kp。从0.5开始每次增加0.5观察转速响应。当Kp增大到一定值后转速会开始来回振荡记下这个临界值然后把Kp调回临界值的60%到70%。下一步加Ki用来消除静差。比如给定300RPM只靠P时稳定在280RPM这个差值就是稳态误差把Ki从0.02开始缓慢往上加直到转速能收敛到目标值。最后加很少的Kd一般是Kp的0.1倍以下改善快速响应时的超调。4.3 合并进定时器中断的完整闭环代码测速、PID计算、占空比更新都在定时器中断里完成周期我选50ms。为什么不是1ms因为M测速需要统计一段时间内的脉冲周期太短脉冲个数就少测速分辨率下降低速时误差会非常大。50ms是一个折中值实际跑下来速度环响应速度足够。#include ti_msp_dl_config.h #define PWM_PERIOD_COUNT (4000 - 1) #define PPR_TOTAL (390) // 编码器线数13 × 减速比30 #define SAMPLE_MS (50) #define TARGET_RPM (300.0f) volatile uint32_t g_pulse_cnt 0; static uint32_t last_cnt 0; static float actual_rpm 0.0f; static float target_rpm TARGET_RPM; IncPID speed_pid { .kp 2.0f, .ki 0.15f, .kd 0.0f, .out 0.0f, .err_last 0.0f, .err_prev2 0.0f, .out_min 0.0f, .out_max 100.0f }; void TIMG1_IRQHandler(void) { if (DL_TimerG_getPendingInterrupt(TIMG1) DL_TIMERG_IIDX_CC0_DOWN) { uint32_t delta g_pulse_cnt - last_cnt; last_cnt g_pulse_cnt; actual_rpm (float)delta * 60000.0f / ((float)PPR_TOTAL * SAMPLE_MS); float duty IncPID_Calc(speed_pid, target_rpm, actual_rpm); uint16_t comp (uint16_t)(duty / 100.0f * PWM_PERIOD_COUNT); DL_TimerG_setCaptureCompareValue(PWM_TIMER, comp, DL_TIMERG_CC_0_INDEX_C0); DL_TimerG_clearInterruptStatus(TIMG1, DL_TIMERG_CLEAR_INTERRUPT_STATUS_CC0_DOWN); } } int main(void) { SYSCFG_DL_init(); DL_GPIO_setPins(GPIOB, STBY_PIN); motor_direction_set(true); DL_TimerG_startCounter(TIMG1); while (1) { // 在这里通过串口打印 actual_rpm方便观察实时转速 } }如果希望在运行中改目标转速直接把target_rpm设置为新的目标值就行。我在串口调试程序里做成了数字指令输入比如命令行输入300电机就会自动加速到300RPM附近并稳定下来。加一步延时打印// 假设你已经初始化了UART char buf[64]; snprintf(buf, sizeof(buf), target: %.1f rpm, actual: %.1f rpm\r\n, target_rpm, actual_rpm); DL_UART_Main_transmitData(UART_0, (uint8_t *)buf, strlen(buf));打印频率不用太高每250ms打印一次即可通过报文能直观看到转速从启动到稳定的完整过渡过程。5. 实测记录故障排查、调参经验与后续扩展闭环跑的整个过程不总是一帆风顺。我把这段时间踩过的坑和调参记录整理出来这些内容在官方手册里通常是不会写的。5.1 那些让电机不转、乱转、飞车的常见原因一次完整的调试中以下问题我基本都遇到过电机完全不动。首要怀疑对象是STBY没拉高其次是VCC没供3.3V。初学者容易把注意力放在PWM代码上实际上硬件问题占大多数。用万用表量一下几个关键点电压比反复看代码高效得多。电机只能正转不能反转。很多情况下不是TB6612坏了而是AIN1/AIN2两个引脚没有同时配置为GPIO输出或者其中一个被复用成外设引脚。检查SysConfig里的引脚配置确认GPIO模式是GPIO_OUTPUT_PUSH_PULL。编码器计数乱跳方向不规律。先检查编码器GND是否和MCU、TB6612共地。没有共地时编码器信号高电平会悬浮不定中断随便触发。然后是上拉电阻开漏输出的霍尔编码器没有上拉采集到的就是噪声。最后是接线长度杜邦线超过20cm容易引入干扰尽量缩短或者用双绞线。一上电电机就猛转飞车。这是PID参数没整定好的典型表现。Ki过大或者积分项在启动时快速饱和会让输出直接顶到100%。启动阶段可以把积分项清零或者把out_max限制在80%以内等转速进入误差范围后再放开。低速抖动。转速低于20RPM时M法测速的几个脉冲误差占比很大这是算法本身的固有问题。解决办法是拉长测量窗口比如50ms改成100ms甚至200ms代价是响应变慢但低速稳定度提升明显。5.2 一次真实的PID调参过程与参数记录以目标转速300RPM为例我记录了一组完整的调参过程阶段KpKiKd现象初始1.000转速稳定在270RPM左右有明显稳态误差加P2.000转速升到290RPM附近误差缩小加P3.000出现明显振荡转速在240-330RPM之间波动回退2.00.050稳态误差逐渐消除约3秒后稳定在300RPM微调2.00.10响应加快约1.5秒后达到300RPM轻微超调加Kd2.00.10.1超调基本消失约1.5秒内到达目标值这个过程中最有价值的经验是P项决定了系统刚硬与否调得不够会懒调得太过会抖Ki是用来补P留下的稳态差的别指望Kp一个人干完所有事。实际带载以后底盘负重几百克参数应该重新调一遍负载变化会直接改变系统的等效惯量和摩擦力矩。5.3 从速度环起步还能往哪些方向扩展速度环稳定后能做的上层功能就多了。我接下来的几个方向可以给你参考位置控制把编码器脉冲做累加外环再套一个位置PID就能实现小车精确走固定距离双电机差速TB6612还有另一路B桥MSPM0G3507定时器资源足够再补一组同样的编码器处理就能做差速转向更精确的低速测速当需要在非常低的速度下运行可以改用T法测速也就是测量单个脉冲的宽度来换算转速增加滤波实际采样数据有毛刺加一个一阶低通滤波器公式是filtered a * raw (1 - a) * filtereda取0.2到0.5效果不错最后再分享一个我自己的调试习惯PID参数一定要配合串口曲线来看肉眼盯着电机根本判断不了超调量和调节时间。我一般会把目标转速、实际转速、PID输出三个值一起打印出来记录到文件后用绘图工具画曲线。这样调参是有依据的而不是靠“感觉好像稳了”。这套系统的完整代码结构完全可以照搬根据不同减速比改一下PPR_TOTAL再根据电源电压把输出限幅调整一下基本就是一套通用的小车电机闭环驱动底板了。
返回列表