STM32直流电机PID闭环调速实战:从原理到参数整定
发布时间:2026/9/16 7:50:00 锦皓数字建站

做了两轮PID控制实验这一篇专门记录第二轮。第一轮我做的是开环PWM调速让电机转起来很容易可一旦用手捏住轴或者增加负载转速立刻往下掉这才意识到闭环的必要性。这轮实验核心就是用STM32F103C8T6采集直流电机的霍尔测速反馈手写PID算法把转速稳稳锁在目标值上。最终做出来的效果是设定500转/分用外力扰动之后转速能在1秒左右重新回到设定值稳态误差控制在2%以内。这篇笔记适合两类人看。一类是正在学自动控制原理、从没在真实硬件上跑过PID的学生另一类是做过嵌入式开发但一直调用现成库想从零实现一遍PID的工程师。笔记里没有用厂家封装好的PID库全部自己写代码结构和调参思路基本可以迁移到Arduino、树莓派Pico、ESP32这些常见平台上差别只在底层定时器和中断配置。1. 这一次实验要解决的核心问题1.1 为什么PID实验会排到第二次实验标题写成实验02是因为整个系列有明确的前后顺序。第一次实验做开环调速让电机转起来、调个基础转速搞清楚怎么让电机按指令转第二次实验才是闭环控制解决转速被扰动之后怎么恢复的问题。这两个实验的差异其实就是控制领域里最经典的命题开环靠校准闭环靠反馈。我做第一次实验时感受特别深当时用固定占空比驱动电机空载转速看起来还可以但拿手轻轻一按转速立刻降了几十转。原因很简单PWM占空比只代表你给电机多少电压而实际转速取决于负载、电压波动、摩擦力这些外部因素。开环系统天生无法感知这些变化自然也没法纠正。第二次实验就是把感知-决策-执行这条链路补齐。感知用霍尔编码器测转速决策用PID算法算输出执行继续用PWM控制电机。整个闭环回路一旦跑通系统的行为模式就完全变了它不再是一个你给多少、它转多少的开环对象而是一个你要它转多少、它自己想办法达到的自治对象。这也是PID控制实验的真正价值。1.2 硬件平台怎么选STM32加霍尔电机平台选择上我用了STM32F103C8T6而不是Arduino。原因有几条第一STM32的定时器资源丰富TIM2可以输出PWMTIM4可以配置成编码器模式直接计数霍尔脉冲硬件分工非常清晰第二它有多个定时器可以精确产生中断PID控制周期能稳定在10ms级别这在Arduino上用软延时常会因为串口输出、OLED刷新而抖动第三串口调试方便我可以实时打出设定值、反馈值、输出值方便观察PID内部状态。电机选的是带霍尔测速的直流减速电机俗称霍尔电机或者测速电机。这种电机尾部带一个磁环和霍尔传感器电机转动时霍尔传感器输出方波脉冲脉冲频率和电机转速成正比。相比光电编码器霍尔方案的优点是便宜、抗干扰、结构简单缺点是分辨率低一些但对入门级的转速闭环实验完全够用。有一个选型细节值得多说一句直流电机驱动不能直接接单片机引脚因为电机工作电流大必须通过驱动模块。我用的驱动是TB6612FNG而不是常见的L298N原因是TB6612的MOSFET压降小发热低频率响应快适合PID这种需要频繁调节PWM占空比的场景。L298N其实是三极管驱动压降大PWM频率高了还会发烫做实验不是不行但效果会差一截。2. PID算法实现位置式与增量式的权衡2.1 位置式PID差分方程怎么写到单片机里PID算法的教科书公式长成这个样子u(t) Kp * e(t) Ki * ∫e(t)dt Kd * de(t)/dt公式本身不复杂三个环节各管一件事比例项负责当前误差有多大就纠多少积分项负责把长期积累的小误差慢慢磨掉微分项负责提前判断误差变化的趋势踩一脚刹车。但单片机是数字系统没法直接算连续积分和微分必须离散化处理。离散化的方式业内最常用的就是用差分方程。设采样周期为T第k个采样周期的误差为e(k)那么积分项用累加近似∫e(t)dt ≈ T * Σe(i)i从0到k 微分项用差分近似de(t)/dt ≈ (e(k) - e(k-1)) / T代入连续公式之后就得到位置式PID的离散表达式u(k) Kp * e(k) Ki * T * Σe(i) Kd * (e(k) - e(k-1)) / T代码写出来是这样typedef struct { float Kp; float Ki; float Kd; float integral; // 积分累加值 float last_error; // 上一次误差 float out_max; float out_min; } PID_Position_t; float PID_PositionUpdate(PID_Position_t* pid, float setpoint, float actual) { float error setpoint - actual; pid-integral error; float output pid-Kp * error pid-Ki * pid-integral pid-Kd * (error - pid-last_error); pid-last_error error; if (output pid-out_max) output pid-out_max; if (output pid-out_min) output pid-out_min; return output; }需要注意这里Ki和Kd已经是把采样周期T吸收进去之后的综合系数也就是说实际代码里的Ki等于公式中的Ki*TKd等于公式中的Kd/T。我调参时直接调综合系数不用每个周期再乘除T省事很多。但前提是采样周期固定如果周期变了参数等于也变了系统就会乱套这一点在后面章节还会详细说。位置式PID有一个天生的毛病就是积分项累计问题。启动瞬间误差大积分项每周期都在拼命累加输出会长时间顶到饱和上限。等转速冲过头误差反向积分项还得一点一点还债系统就会表现为大幅度超调、反复振荡这就是所谓的积分饱和。这个问题在电机启动时特别明显我在第四章会展开讲。2.2 增量式PID为什么在电机调速里更好用第二次实验我实际落地用的是增量式PID。增量式和位置式的本质区别在于它不直接算出当前应该给多少输出而是算出相对上一次输出的变化量Δu(k)。推导过程就是从位置式的表达式做前后两项相减u(k) Kp * e(k) Ki * Σe(i) Kd * (e(k) - e(k-1)) u(k-1) Kp * e(k-1) Ki * Σe(i-1) Kd * (e(k-1) - e(k-2))两式相减得到Δ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) Δu(k)写成代码是这样#define ERROR_BUF_NUM 3 typedef struct { float Kp; float Ki; float Kd; float error_buf[ERROR_BUF_NUM]; // [0]e(k)[1]e(k-1)[2]e(k-2) float last_output; float out_max; float out_min; } PID_Incremental_t; float PID_IncrementalUpdate(PID_Incremental_t* pid, float setpoint, float actual) { float error setpoint - actual; pid-error_buf[2] pid-error_buf[1]; pid-error_buf[1] pid-error_buf[0]; pid-error_buf[0] error; float delta pid-Kp * (pid-error_buf[0] - pid-error_buf[1]) pid-Ki * pid-error_buf[0] pid-Kd * (pid-error_buf[0] - 2.0f * pid-error_buf[1] pid-error_buf[2]); pid-last_output delta; if (pid-last_output pid-out_max) pid-last_output pid-out_max; if (pid-last_output pid-out_min) pid-last_output pid-out_min; return pid-last_output; }增量式的优势第一体现在抗积分饱和。它天然没有积分累加项计算的是增量执行机构饱和时输出不再增长误差变化也不再往里面积累不会出现欠债太多的问题。第二体现在手动自动切换平滑。实验时我经常先在手动模式给一个固定PWM占空比观察电机能不能转然后切换到闭环自动控制。增量式因为输出是叠加变化切换瞬间不会有跳变位置式就必须仔细处理积分初始化否则输出会突然冲到100%。位置式和增量式的选择我整理了一个对比表对比项位置式PID增量式PID输出形式直接输出控制量绝对值输出控制量增量是否包含误差累加包含不包含积分饱和风险高低手动/自动切换需要处理积分初始化切换平滑适用场景执行机构需要绝对控制量电机调速、带保持性执行机构代表应用伺服位置环、舵机角度控制直流电机调速、温度控制这里也提醒一句增量式虽然省了积分累加输出限幅还是必须做。因为Δu虽然小但累加次数多了u(k)完全可能冲出PWM占空比的范围。如果不做限幅会出现算法算出的输出是120%但实际PWM最多只能到100%的割裂状态系统行为和预期就会脱节。3. 实操记录接线、测速周期与PID参数整定3.1 硬件接线和编码器测速的一手经验这轮实验的硬件清单如下主控板STM32F103C8T6核心板电机12V直流减速电机带霍尔测速减速比1:30驱动TB6612FNG模块电源12V给电机驱动5V给单片机两者共地调试CH340串口模块串口助手显示0.96寸OLED可选接线逻辑其实很简单我把关键引脚整理成表信号单片机引脚说明PWM输出PA0TIM2_CH1控制电机驱动输入PWMA方向控制1PA1TB6612的AIN1方向控制2PA2TB6612的AIN2霍尔A相PB6TIM4_CH1编码器脉冲计数输入霍尔B相PB7TIM4_CH2编码器方向判断电机测速的底层原理不复杂。霍尔传感器输出的是方波脉冲而STM32的TIM4可以配置成编码器模式自动对A/B两相脉冲进行正交计数一个机械周期内可以实现4倍频细分。我用的是这个模式而不是单纯的外部中断计数因为硬件计数不占CPU时间精度也更高。有一个接线坑必须单独拿出来讲霍尔信号线如果和PWM线在杜邦线束里挨在一起PWM的大电流边沿会串扰到霍尔线上导致测速脉冲忽多忽少、毛刺严重。我第一版接线把所有线扎成一束PID一启动转速就剧烈抖动串口曲线惨不忍睹。后来把霍尔信号线单独走并且在单片机端加了RC低通滤波10k电阻串联再加0.1uF电容到地问题才消失。这种噪声不是靠调PID能解决的它属于输入信号质量劣化PID只会把噪声放大。测速换算也要提前算明白。我的电机参数是减速比1:30尾部霍尔检测环一圈7个脉冲双路霍尔正交4倍频所以电机输出轴每转一圈对应的脉冲数是每转脉冲数 减速比 * 霍尔齿数 * 4倍频 30 * 7 * 4 840也就是说编码器每累计840个脉冲输出轴转了完整的一圈。这个数值会直接影响转速反馈的计算必须实测确认不能想当然。我一开始按厂家标称参数直接算发现转速显示偏高一点后来发现是减速比标称值和实际有偏差用另一个高精度转速表标定之后修正了每转脉冲数。3.2 采样周期和控制周期怎么定才不踩坑PID控制周期是整个调参的前提很多教程直接给10ms或者20ms但这是拍脑袋拍出来的。正确思路是根据被控对象的时间常数来选择。对直流减速电机速度环来说系统时间常数一般在几十毫秒到几百毫秒之间。经验法则是控制周期要明显小于系统时间常数的1/5到1/10这样控制器才能在系统有明显响应之前及时调整输出。我用10ms控制周期换算过来对应的时间常数为100ms以上对普通减速电机来说绰绰有余。但如果把周期调得太短比如1ms甚至更短反而会有问题。PID的微分项对误差跳变极其敏感周期太短时相邻周期误差变化很小但测量噪声相对突出微分项会放大噪声电机还没反应过来输出已经抖起来了。所以说控制周期不是越快越好。我在实验里的具体分配是TIM3的更新中断做PID运算中断周期设为10ms这是采样周期也是控制周期TIM4编码器模式做脉冲计数每隔10ms读一次计数寄存器然后清零重新累计。转速计算公式是Speed(rpm) 脉冲数 / 每转脉冲数 * (60 / 采样时间秒数)套上我的参数就是Speed(rpm) n / 840 * (60 / 0.01) n / 840 * 6000打个比方10ms内如果数到84个脉冲对应转速就是84/840*6000 600转/分。这个换算逻辑很简单但要注意低速场景。如果目标转速低到30转/分10ms内只有42个脉冲量化误差会非常大。遇到这种情况要么把采样周期拉长到50ms甚至100ms要么换更高分辨率的编码器。这也是工业上低速高精度控制会用500线甚至上千线光电编码器的原因。周期确定之后唯一要做的就是保证它严格稳定。PID的Ki和Kd综合参数隐含依赖采样周期如果控制周期抖动相当于控制器参数在实时变化系统必然乱。所以PID运算一定放定时器中断里做主循环只负责串口输出、OLED刷新这些不紧急的活。3.3 试凑法调PID参数的完整过程参数整定方法很多工业上最常用的是试凑法也叫经验整定法。虽然听起来土但它能帮你建立对系统响应的直觉。我一开始也试过Ziegler-Nichols法按临界增益和临界周期算了一版参数结果超调偏大后面还是回到试凑法微调。试凑法的口诀只有一句话先P、后I、再加D。具体步骤我走了一遍第一步只留比例项Ki和Kd全部置0目标转速设到500转/分。我从小往大加Kp0.5起步转速始终到不了目标有稳态误差加到2.0时响应开始有明显振荡但还在收敛加到3.0时振荡不收敛出现了持续等幅振荡。我记下这个临界增益大概在2.5左右。第二步取临界增益的60%到70%作为实际KpKp取1.6。这时候系统稳定了但稳态误差依然存在这就轮到积分项上场。Ki从0.05开始加加到0.15时转速能回到目标值但超调比较明显大约冲到8%。超调还在可接受范围Ki回退到0.1效果满意。第三步尝试加微分项Kd从0.01开始到0.02时发现转速噪声被明显放大电机有轻微的嗡嗡声。原因刚才已经解释过了霍尔测速本身有量化误差微分项把噪声放大了。最后我把Kd调回0直接用PI控制效果反而干净利落。最终参数是Kp1.6Ki0.1Kd0。在500转/分的阶跃响应下上升时间约300ms超调约6%稳态误差在2%以内用手捏住电机轴模拟扰动恢复时间约800ms。对一个入门实验来说这个表现已经及格。调参过程中的现象和判断依据我整理成一张表调试现象可能原因处理方式响应慢、稳态误差大Kp太小增大Kp阶跃时持续振荡Kp过大减小Kp稳态误差一直消除不掉缺少积分作用增大Ki超调大、有低频摆动Ki过大减小Ki高频抖动、电机嗡嗡响Kd过大或测速噪声大减小Kd或对反馈滤波串口调试是这次实验效率提升最大的工具。我每个控制周期在中断里把数据暂存主循环定时通过串口输出一行CSV数据格式是时间戳,设定值,反馈值然后用串口助手的波形显示功能直接画曲线。如果只有数字没有曲线调参基本靠猜效率极低。这个方法强烈推荐它能让参数整定从玄学变成科学。顺带提一嘴很多工业PLC都带了PID自整定功能原理其实和试凑法差不多内部用临界比例法或者阶跃辨识自动估算参数。理解了试凑法的过程再看那些自整定功能就不会觉得是黑魔法了。4. 实验问题排查与避坑实录4.1 积分饱和导致的启动超调怎么处理这轮实验遇到的第一个顽固问题就是积分饱和。现象是电机从0转速启动到目标500转/分时先是疯狂加速冲到650转/分左右然后掉下来再冲上去来回几个回合才勉强稳定。从控制角度讲这是典型的积分饱和特征。原因分析起来很清晰启动瞬间设定值和反馈值之间的误差非常大大概有500转/分的差距积分项每个周期都在累加这个巨大的误差值输出很快顶到了PWM上限100%。电机在全速加速但反馈值不会瞬间跟上积分项继续一路累加。等到转速冲过头、误差变负积分项才开始慢慢往下走这个还债过程就表现为大幅超调和振荡。解决办法分层次进行。第一层是输出限幅把PWM输出限制在0到100%之间这是最基本的保护我的代码里已经包含了。第二层是积分限幅单独限制积分累加项的绝对值。我在实验里把积分项钳位在输出范围的50%也就是说输出上限是100时积分项最大只能累计到50。这个做法非常有效代价是牺牲了一点积分速度。如果还想更精细可以用积分分离策略误差大于某一个阈值时积分项完全不参与运算误差回到阈值以内才恢复积分功能。这样在大误差阶段靠比例项快速逼近小误差阶段由积分项磨掉稳差。我实验里先用积分限幅解决了问题积分分离作为一个改进点留给了后面实验。4.2 微分项噪声引发的电机抖动怎么排查调Kd的时候我遇到了高频抖动问题。当时把Kd调到0.02电机运行声音变得尖锐刺耳转速反馈在目标值附近快速跳动从串口数据看FB值在498到502之间波动幅度不大但OUT值变动非常剧烈一会50%一会65%这种输出抖动在电机上听起来就是嗡嗡的震动声。根因在微分环节的计算方式上。微分项本质是误差变化率Kd乘以最近两个周期的误差差。霍尔测速在低速时有量化误差比如真实转速恒定的情况下10ms内数到的脉冲数可能因为干扰或者量化边界波动几个数换算成转速就是几转/分的跳变。微分项把这些测量噪声再乘以Kd放大成了大幅度的输出波动。解决办法有三条路。第一直接把Kd调小或者置0用PI控制这是最简单有效的方案我在实验里就是这么做的。第二对反馈信号做低通滤波让测量值变化更平滑再送入PID。第三引入微分先行或者对微分项单独滤波避免噪声直接进微分路径。我建议优先考虑第一种因为很多工程场景下PI控制已经够用D项是锦上添花不是必须。4.3 采样周期不稳定让PID曲线变锯齿的教训早期版本我用主循环里的延时来做PID运算结果跑出来的阶跃曲线不是光滑上升而是一段一段的锯齿状波动看着像控制器神经质一样。我花了好一会儿才定位到原因主循环里有串口输出、OLED刷新这些耗时任务导致PID运算周期并不是精确的10ms而是8ms、12ms来回跳。这个问题的本质是PID的Ki、Kd参数隐含依赖采样周期T。我把参数当成固定的但实际每个周期的间隔在变等于参数也在实时变。系统的控制律和实现不一致曲线自然乱。排查方法其实很简单在PID中断里翻转一个GPIO用示波器或者逻辑分析仪看这个引脚的波形周期立刻就能看出抖动程度。解决方法是把PID运算搬进TIM3的定时器中断让采样和控制周期严格固定。中断优先级的设置也要注意编码器计数用的TIM4、PID运算用的TIM3我把TIM3的优先级调高保证每次中断都按时执行。串口发送和OLED刷新全部放在主循环绝不占用中断时间。这个改动之后锯齿状波动立刻消失响应曲线变得干净平滑。4.4 常见故障速查表这轮实验遇到和排查过的典型问题整理成一张速查表方便后面复现实验时快速定位故障现象可能的根因排查步骤与解决办法电机完全不动PWM占空比一直为0或者驱动未使能检查PWM输出波形确认TB6612的STBY引脚接高电平转速显示恒为0编码器接线错误或引脚冲突用示波器看霍尔信号确认引脚没有复用冲突转速反馈凭空跳变霍尔信号受PWM串扰信号线单独走加10k0.1uF的RC滤波启动剧烈超调积分饱和加积分限幅或改用积分分离目标值附近高频抖动微分噪声或Kp过大减小Kd/Kp或对反馈做低通滤波稳态精度差Ki太小或存在机械死区增大Ki必要时给出死区补偿响应慢但稳定Kp和Ki都偏小重新按试凑法流程找临界增益响应曲线呈锯齿状控制周期不稳定PID运算移入定时器中断主循环只做非实时任务排查顺序建议是硬件信号、采样数据、控制输出、参数调整。先确认速度反馈是准确的再看PID输出有没有异常最后才怀疑参数问题。很多时候问题根本不在PID算法而在前面几步。这一轮实验下来我最大的体会是PID代码五分钟就能写完真正花时间的是把反馈搞干净、把参数整明白。尤其是测速反馈的稳定性对控制效果的影响远超预期。如果你正在做类似实验我建议一定先花时间把转速曲线完整打出来看一遍确认反馈没有问题再动手调Kp。另外试凑法虽然看起来土但它能帮你建立对系统响应的直觉这是用自动整定工具箱换不来的。后面我打算把这个实验扩展成串级PID内环转速环在外环位置环的基础上快5到10倍配合模糊PID处理变负载场景到时候再写一篇继续记录。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。