尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

基于MSPM0G3507的增量式PID直流电机闭环控制实战

基于MSPM0G3507的增量式PID直流电机闭环控制实战 1. 项目整体设计与需求拆解1.1 为什么选MSPM0G3507这颗芯片来做PID电机控制先聊一个很多人会问的问题做电机控制入门为什么不用烂大街的STM32非要选立创天猛星MSPM0G3507我的答案很直接因为它便宜、够用、而且调试链路极其顺手。MSPM0G3507是TI推出的Cortex-M0内核MCU主频最高80MHz片上集成了一堆对电机控制非常友好的外设——高级定时器可以输出带死区互补的PWM、内置运放OPA、12位ADC支持多通道同步采样再加上DMA可以自动搬运编码器计数值这些资源放在一个入门级芯片上属实厚道。更关键的是立创天猛星这块板子把MSPM0G3507的引脚几乎全部引出板上自带了按键、LED、USB转串口还预留了电机驱动接口的扩展排针。你不需要自己画板子也不需要外接一堆乱七八糟的调试工具一块板加一个直流减速电机再加一个带霍尔编码器的电机轮就能把PID闭环跑起来。实测下来这套组合的核心优势是从点灯到电机转起来只要半天从开环调速到闭环稳速也就是一个下午的事。对于大二大三做课程设计、社团培训或者毕业设计预研的同学来说这个学习曲线非常舒服。1.2 这个项目到底要解决什么问题如果你只是想让电机转起来直接给PWM占空比就行根本不用PID。但真正常见的痛点是这几个第一电机带载后转速会掉。你给30%的占空比空载时每分钟转2000转用手捏住轮子转速可能掉到1200转而你希望它稳定在2000转。第二不同的PWM占空比和转速之间不是线性关系。电池电压在变化、电机温度在变化、摩擦力在变化开环控制永远追不平。第三你需要的不是一个固定占空比而是一个稳定转速。这就必须引入闭环测出当前转速和期望转速做比较差值经过PID控制器计算调整PWM占空比让电机转速收敛到目标值。所以这个项目的本质是用MSPM0G3507采集霍尔编码器的脉冲信号换算成实际转速再通过增量式PID算法计算出应该输出的PWM占空比最终实现电机转速在负载扰动下的闭环稳定。做完了这一步你后面再去搞循迹小车、平衡车、四轴云台、甚至无刷电机FOC都是在这个基础上加外设、加算法维度而已。PID是底层地基地基打不牢上层全是空中楼阁。2. MSPM0G3507电机控制硬件方案与接线2.1 核心器件清单与选型理由在动手接线之前先把器件备齐。我的配置如下全部来自立创商城成本控制在100块以内器件型号/规格用途备注主控板立创天猛星MSPM0G3507运行PID算法、输出PWM、采集编码器板载DAP-Link调试器USB供电直流减速电机JGA25-370带霍尔编码器被控对象减速比1:30左右带AB相编码器电机驱动TB6612FNG模块将MCU的PWM信号放大为电机驱动电流比L298N体积小、压降低电源7.4V 18650两串锂电池组给电机供电逻辑电路由USB的5V供电共地杜邦线若干公对公、公对母接线调试建议多备几种颜色区分信号USB转串口模块CH340上位机调试数据回传板载串口也可直接用选TB6612FNG而不是L298N是因为L298N的饱和压降太高实测接近2V电机在低压时扭矩输出很肉而且模块体积大、发热严重。TB6612的内部MOSFET导通电阻只有零点几欧姆PWM频率高了也不发烫更适合这种学习场景。JGA25-370的减速电机则是因为它便宜、皮实、编码器集成的方案成熟淘宝一搜一大把替换件也好买。2.2 供电、电机驱动与编码器接线全图解接线前先强调一个非常重要的原则电机驱动电源和MCU逻辑电源必须共地。否则PWM信号没有参考地驱动板无法识别电平电机要么不动要么乱抖。我的接线方式如下电源模块正极接TB6612的VM引脚负极接GND同时从负极引一根线接到天猛星板的GND上完成共地。TB6612的PWMA接MSPM0G3507的PA10TIMG8通道0AIN1接PA11AIN2接PA12STBY接3.3V。这里PWMA控制转速AIN1和AIN2控制方向——一个高一个低时正转反向则反转。电机M、M-分别接TB6612的AO1、AO2。编码器的VCC接3.3VGND接GNDA相输出接PB6TIMG12通道0B相输出接PB7TIMG12通道1。为什么编码器要接在定时器的两个通道上因为MSPM0G3507的QEI正交编码器接口外设需要同时对A、B两相计数才能判断旋转方向和精确位置。单通道只能计数不能辨向。接线完成后先别急着上电。用万用表测一下TB6612的VM和GND之间电压是否正常再测STBY引脚是否为高电平——这个引脚如果接地了驱动芯片会直接进入待机模式电机怎么调都不转我见过太多人栽在这个细节上。2.3 为什么PWM频率选10kHz而不是1kHz或100kHzPWM频率的选择直接影响电机运行的噪音和力矩平滑度。频率太低比如1kHz电机电流纹波大转速在低速时会一顿一顿的听得到明显的高频啸叫。频率太高100kHz虽然电流平滑但MOS管的开关损耗急剧上升TB6612会发热厉害而且驱动板布线寄生参数的影响也开始显现。10kHz是一个折中值超出了人耳听觉敏感区的大部分范围实际上还是能听到轻微嘶嘶声开关损耗可控对霍尔编码器采集也没有干扰。这个频率在绝大多数直流电机驱动场景下都是安全选择。配置时注意MSPM0G3507的PWM用TIMG8计数模式选UpCount周期值存到LOAD寄存器占空比存到CMP寄存器。我以80MHz的时钟源为例预分频设为1那么定时器计数频率是40MHz要得到10kHz的PWMLOAD值就是40_000_000 / 10_000 4000。占空比50%时CMP2000占空比100%时CMP4000一般不要满载给死区留余量。提示占空比的更新一定要在PWM周期中断里做或者使用定时器的影子寄存器功能否则在计数中途改CMP值输出波形可能出现毛刺电机就会突然抖一下。3. PID控制算法原理与离散化落地3.1 大白话理解P、I、D三个参数各管什么事PID这三个字母拆开看都不复杂但很多人栽在不知道每个参数调大了会怎样、调小了会怎样。我用送外卖来打比方P比例相当于你离目的地还有1公里你决定开多快的车。误差大就开快一点误差小就开慢一点。比例系数Kp越大反应越激进但容易冲过头在目标附近来回震荡。I积分相当于你意识到一直有股逆风光靠速度控制到不了目的地于是你开始累计这段路程的“欠账”把欠的里程折算成额外的速度补上去。积分系数Ki解决的是稳态误差——比如电机带着负载比例控制只能让转速逼近目标但总是差一截积分项能把这截补平。D微分相当于你看到前面100米是红灯提前开始刹车减速而不是冲到跟前才猛踩。微分系数Kd抑制超调但也不能调太大否则对噪声特别敏感一个测量毛刺就能让输出剧烈抖动。三句话总结P管响应快不快I管准不准D管稳不稳。3.2 增量式PID与位置式PID怎么选实际工程里两种形式都有应用但直流电机调速场景更推荐增量式。位置式PID的输出是直接给执行器的绝对量公式为u(k) Kp*e(k) Ki*sum(e(k)) Kd*(e(k) - e(k-1))这个公式的问题在于积分项累计了所有历史误差。一旦某一阶段误差持续很大sum项会不断膨胀等误差变为0时积分输出还需要时间回落导致系统的超调量被放大。另外如果你需要切换控制模式或者换向位置式的输出会有一个很大的跳变对电机和驱动器都不友好。增量式PID则计算的是控制量的增量delta_u(k) Kp*(e(k)-e(k-1)) Ki*e(k) Kd*(e(k)-2*e(k-1)e(k-2)) u(k) u(k-1) delta_u(k)每次只改一点点输出平滑安全性高而且天然自带抗积分饱和的属性——就算误差一直很大增量也不会无限累积。代码上只需要保存e(k-1)和e(k-2)两个历史值内存开销极小非常适合M0内核这种小资源MCU。所以在这个项目里我采用增量式PID控制周期固定为10ms。3.3 从连续PID到差分方程的推导这一步是很多人看到数学公式就绕道的地方其实根本不复杂。连续PID的频域表达式是U(s) (Kp Ki/s Kd*s) * E(s)要做数字控制得把它离散化。这里用最直观的差分近似误差的积分近似为误差的累加所以积分项从连续域的Ki * integral(e(t))dt变成离散域的Ki * T * sum(e(k))其中T是采样周期。误差的微分近似为误差的变化量除以周期所以微分项从Kd * de(t)/dt变成Kd * (e(k) - e(k-1)) / T。把这两个替换代入位置式PID得到的就是前面写的那串公式。增量式的推导也不复杂把位置式u(k)减掉u(k-1)中间一堆项约掉最后只留下e(k)、e(k-1)、e(k-2)三个误差的组合。我在调试时踩过一个坑如果采样周期T不固定微分项的分母时大时小会导致Kd在不同时刻的实际增益飘忽不定系统会出现莫名其妙的抖动。所以务必用定时器中断固定控制周期不要在while循环里随缘采样。代码里我用的控制周期是10ms对应的控制频率是100Hz。这个频率对直流减速电机的转速环来说完全够用给得太高反而容易引入测量噪声。4. 编码器测速与MSPM0G3507底层驱动实现4.1 霍尔编码器的工作原理与倍频技巧JGA25-370电机尾部那个霍尔编码器内部其实是AB两相互差90度的方波信号。电机每转一圈编码器输出特定数量的脉冲。我的这个电机减速比1:30编码器是13线经过减速后输出轴每转一圈对应13 * 30 390个脉冲。AB两相的相位差还带了方向信息A相超前B相90度为正转反之是反转。用定时器的正交编码器模式来计数硬件会自动根据相位关系做加计数或减计数CPU完全不用干预。这里有个关键技巧你可以将A、B两相都用来计数得到4倍频的效果也就是实际脉冲计数是390 * 4 1560。用MSPM0G3507的QEI模式配置计数器的核心知识是每次A或B信号变化计数器都会更新一次而不是只在一个沿更新。这样低速时测速分辨率更高不容易出现“转速显示为0但电机明明在转”的尴尬。4.2 定时器配置与编码器计数值读取MSPM0G3507的QEI外设配置步骤大致如下初始化PB6、PB7为TIMG12的通道0和通道1输入设置为正交编码器模式。配置TIMG12的计数模式为Encode Mode这样计数方向由A/B相相位自动决定。配置溢出中断当计数器值超过设定范围或者跨过零时触发中断方便做多圈累计。在10ms的控制周期里我读取一次计数器值然后立刻清零这样拿到的是“10ms内的脉冲增量”。这个增量除以编码器分辨率再除以时间就得到转速。举个具体例子10ms内计数值读到780电机轴每转一圈计数1560那么转速就是每10ms圈数 780 / 1560 0.5圈/10ms 每秒圈数 0.5 * 100 50圈/秒 每分钟转速 50 * 60 3000转/分这个换算逻辑务必自己推一遍。脱裤子放屁地说转速计算的本质就一句话测出单位时间内的脉冲数再除以每圈的脉冲数就得出单位时间的圈数。4.3 DMA在编码器采集里的作用搜索热词里有人提到mspm0g3507 dma这里展开说一下。在更复杂的应用里比如你要同时采集编码器、电流传感器、电压传感器如果每个控制周期都靠CPU轮询去读计数器CPU会被打断得很频繁导致控制时序抖动。DMA可以做到定时器触发一个事件DMA自动把计数器值搬运到内存变量里CPU全程不用管。我在这个入门项目里没有用DMA因为只读一个编码器计数器没那么大压力。但如果你做了循迹小车板子上又接了多个传感器我强烈建议把DMA用起来。MSPM0G3507的DMA通道可以配置为定时器事件触发每次控制周期中断产生的同时DMA把编码器计数值搬运到全局变量中断服务函数里直接读取即可。配置DMA时有个坑源地址必须配置为编码器计数器的寄存器地址而且是增量模式关闭每次搬运的都是同一个地址。目标地址可以是内存里的一个数组开启循环模式这样搬运一次自动重新武装不用反复手动启动。4.4 电机转向判断与堵转保护有了AB相正交编码器判断转向非常简单读定时器的方向标志位或者看计数器的值是正增长还是负增长。正增长为正转负增长为反转。堵转保护是一个容易被忽略但很重要的功能。我在代码里加了一个逻辑如果设定转速大于0但实际转速小于某个阈值比如50转/分持续超过1秒就判定为堵转自动把PWM输出降到0防止电机持续大电流发热烧毁。这个保护逻辑在课程设计演示时很加分万一你的轮子被卡住了板子不会冒烟。5. 增量式PID的代码实现与参数调试实战5.1 主程序架构与控制周期怎么规划控制周期定时器我用的是TIMG0配置为10ms中断一次。中断优先级设置合理即可主循环里完全不用做和PID相关的事空出来做显示刷新、按键扫描、串口打印。伪代码如下volatile int32_t encoder_pulses 0; volatile int32_t encoder_pulses_prev 0; int main(void) { // 初始化系统时钟、GPIO、PWM、编码器接口、控制定时器 sys_init(); pwm_init(10000); // 10kHz PWM qei_init(); control_timer_init(10); // 10ms控制周期 int16_t target_speed 2000; // 目标转速 2000 RPM int16_t current_speed 0; int16_t pwm_output 0; while(1) { // 非实时任务按键、OLED显示、串口打印 if (status_changed) { target_speed read_target_from_key(); print_speed(target_speed, current_speed, pwm_output); } } } void TIMG0_IRQHandler(void) { // 读编码器增量 int32_t delta encoder_pulses - encoder_pulses_prev; encoder_pulses_prev encoder_pulses; // 换算成转速 RPM current_speed (int16_t)(delta * 60000 / (ENCODER_RESOLUTION * CONTROL_PERIOD_MS)); // 调用增量式PID pwm_output pid_update(target_speed, current_speed); if (pwm_output 0) { set_direction_reverse(); pwm_output -pwm_output; } else { set_direction_forward(); } pwm_set_duty(pwm_output); }这段代码是骨架突出的是“实时任务全放中断”的思想。很多初学者习惯在while循环里delay一会儿再采一次样一旦主循环有别的耗时操作控制周期就乱了PID就调不好。记住PID控制周期不允许被其他业务逻辑阻塞。5.2 PID结构体与核心计算函数我习惯把PID封装成结构体这样后面做串级PID比如循迹小车的位置环速度环可以直接复用。typedef struct { float kp; float ki; float kd; float target; // 目标值 float error_prev; // e(k-1) float error_prev2; // e(k-2) float output_prev; // u(k-1) } PidObject_t; float pid_update(PidObject_t *pid, float target, float actual) { float error target - actual; float delta_u pid-kp * (error - pid-error_prev) pid-ki * error pid-kd * (error - 2 * pid-error_prev pid-error_prev2); float output pid-output_prev delta_u; // 输出限幅防止PWM饱和 if (output 4000) output 4000; if (output 0) output 0; // 更新状态 pid-error_prev2 pid-error_prev; pid-error_prev error; pid-output_prev output; return output; }注意几点一是输出限幅要放在更新output_prev之前否则饱和时积分项继续累积恢复响应变慢这个在位置式里叫积分饱和增量式虽然好一些但也要限幅二是误差和输出变量的类型在M0上float运算比int慢不少但增量式PID计算量不大用float完全没问题代码可读性更高。5.3 参数整定先KP再KD最后KI参数整定是这个项目最磨人的环节也是最有干货的地方。我的经验是遵循一套固定流程不要瞎试先令Ki0Kd0只留Kp。从小到大慢慢加Kp同时用串口上位机观察转速曲线。当Kp比较小时电机响应慢转速达不到目标稳定在目标值附近存在明显静差继续加大Kp转速开始逼近目标但如果Kp过大转速会在目标值附近来回震荡示波器上看就是正弦波一样的波动。此时记下开始震荡的Kp值然后把Kp设为这个震荡临界值的一半到三分之二。接着引入Kd。Kd的作用是抑制超调但这个时候如果转速曲线还是震荡的说明Kd太小调大Kd震荡幅值会变小直到临界点变成平滑收敛。但Kd不能太大因为编码器测速本身有量化噪声Kd过大会把这种测量噪声放大成PWM抖动电机发出不规则的嗡嗡声。最后引入Ki。Ki用来消除静差。把Ki从0慢慢加观察稳定后的转速是否和目标吻合。如果Ki太大转速会出现低频的“喘振”比如目标2000转实际在1800和2200之间缓慢摇摆这就是积分过强导致的周期振荡减小Ki即可。一个比较实用的初始参数组合Kp0.5Ki0.01Kd0.02单位是按PWM满量程4000、转速单位RPM来的。你可以自己换算。5.4 使用Vofa上位机观察PID最优曲线搜热词的时候看到有人在找PID模拟器和调试工具这里我推荐一个特别好用的组合串口输出格式化数据 Vofa JustFloat协议。Vofa是一款上位机软件支持波形显示底下的实现逻辑很简单按照JustFloat协议发送float型数据末尾加帧尾0x00 0x00 0x80 0x7f上位机就能把多个数据实时画成曲线。我实际发的数据结构是这样目标转速float 实际转速float PWM占空比float 帧尾这样能在电脑上直观看到转速曲线怎么逼近目标、超调多少、PWM怎么变化。什么叫“PID最优曲线”不是一条直线而是启动时转速快速上升接近目标时有轻微超调但不超过5%随后迅速收敛到目标值稳定后曲线平直没有持续的周期性波动。我调试时的判断标准就是以目标2000RPM为例最好能在300ms内第一次到达目标超调控制在100RPM以内之后不再震荡。6. 常见问题与排查技巧实录6.1 电机不转但板子灯已经在闪了这种问题90%出在硬件接线上排查顺序非常明确第一步量TB6612的VM和GND电压确认电机供电正常。第二步量STBY引脚确认是高电平。第三步用示波器或者万用表频率档量PWMA引脚确认有10kHz方波且占空比在变化——如果没有看代码里PWM初始化是否正确通道映射对不对。第四步检查AIN1和AIN2的组合是否满足正转条件——相同电平是刹车一高一低才是转。第五步也是很多人会犯的编码器方向接反了PID输出反向导致电机在0占空比和反向死区之间徘徊看起来像“没转”。6.2 电机抖动、啸叫转速不稳定抖动分为好几种情况挨个说高频抖动声音尖锐大概率是Kd过大放大了编码器量化噪声。把Kd调到原来的一半试试。低频喘振转速不规则摇摆大概率是Ki过大尝试减小Ki。电机有规律地一顿一顿PWM频率太低或者控制周期太长。PWM频率低于5kHz时低速抖动感很明显。我用的10kHz基本没这个问题。启动瞬时剧烈震荡然后收敛Kp太大启动瞬间误差大输出瞬间饱和导致电机猛然一冲。解决方法是给输出变化率限幅或者减小Kp。6.3 转速值测量不准串口看到的数字乱跳编码器接线的信号线建议使用双绞线或者尽量短一点不要和电机电源线绑在一起。电机转动时碳刷会产生电磁干扰编码器信号线如果贴太近AB相信号会被毛刺干扰造成计数错误。这个现象我实测过电机线束和编码器线束平行走线时串口看到的转速比实际高了20%左右而且曲线毛糙把它们分离开之后毛刺立刻消失。如果你的板子已经把编码器接口排针挡住了不好理线可以尝试在编码器供电脚加一个100nF去耦电容也有明显改善。6.4 电机发热严重但转速上不去先检查是不是控制方向反了导致PID永远在满占空比输出如果你期望正转但实际上电机的反电动势让编码器计数为负误差就一直很大PID输出一直在上限电机全速运行发热很正常。这种情况的典型特征是断电后用手拧轮子编码器方向标志位和你设定目标方向不一致。解决办法是在代码里检测到方向和目标不一致时交换AIN1和AIN2的逻辑或者直接交换编码器A、B相接线到MCU的两个引脚。另外通电时间长了发热也正常但正常跑10分钟表面温度应该在50度左右如果超过60度摸上去烫手检查一下PWM频率是不是设定太低了。6.5 串口打印的数据为什么全是0xFFMSPM0G3507的串口配置方面有个容易踩的坑默认UART时钟来源不是你要的那个波特率偏差超过3%就会乱码。检查SysConfig里的UART时钟源选择务必勾选正确的低频时钟还是高频时钟。另外TX和RX的复用功能表也要对清楚不是随便一个引脚都能映射串口功能。还有一个细节板载USB转串口用的是板子上的调试串口如果需要额外接CH340记得共地否则电平参考点不一致打印出来的数据全是乱码。6.6 常见问题速查表现象可能原因排查/解决电机完全不转STBY引脚为低将STBY接3.3V电机只往一个方向转换向控制脚未配置确认AIN1/AIN2逻辑PID调半天不收敛控制周期不固定用定时器中断别在while里delay转速目标达到但频繁跳变编码器信号受干扰信号线远离电机线缩短长度转速稳定但和目标差一截积分项太小或为0增大Ki启动就疯狂震荡Kp过大减小Kp或者加输出限幅PWM占空比100%但电机无力电机供电电压不够检查电池电压TB6612压降串口乱码波特率偏差、未共地核对UART时钟源和波特率配置7. 实操心得与后续扩展方向7.1 这个项目做完你能带走什么做完这个项目你的收获绝不只是“电机转起来了”这么简单。你对MCU的定时器、PWM、正交编码器、中断这些基础外设会有一次完整的实战理解这些外设在STM32、华大、沁恒甚至更高端的DSP28379上都是通用的概念只是寄存器名字和配置方式不同而已。更重要的是你建立了一个“闭环”的思维框架先测量再计算误差再输出控制量。这套框架可以无缝迁移到温度控制、舵机角度控制、二维码循迹小车上。你再看网上那些dsp28379电机控制代码、无刷电机FOC工程会发现它们再复杂核心也是在这个基础上叠加了电流环、速度环、位置环三层PID而已。7.2 我建议你下一步做的三件事第一把增量式PID改成位置式PID对比两者在启动和换向时的表现差异体会为什么工程上更喜欢增量式。这个对比实验用时一小时但你对PID的理解会深一个层次。第二加一个OLED屏幕显示目标转速、实际转速和PWM占空比把调参过程从“串口看曲线”变成“随时看板子”方便现场演示。OLED的I2C初始化在MSPM0G3507上很顺半天能搞定。第三试着把控制周期从10ms改到5ms和20ms观察同一组PID参数下的转速曲线差异。你会发现采样周期对控制效果的影响比想象中更大这个经验在你以后做更复杂的控制系统时非常宝贵。我再分享一个个人经验PID调参不要追求一次到位先把Kp调出稳定的临界震荡记下参数再退出Kd压超调最后加Ki补静差。过程中把Vofa曲线截图保存方便对比每一轮参数调整带来的变化。这些调试记录写进你的实验报告或者博客会显得非常扎实。真的从零到一跑通一次闭环控制比看一百篇理论文章都管用。拿上板子接好电机开始吧。
返回列表