1. PID控制在电机转速闭环系统中的工程定位

在嵌入式电机控制系统中,开环控制是最基础的实现方式:MCU直接输出固定占空比PWM信号驱动H桥,电机响应该指令并达到某一稳态转速。这种方案结构简单、代码量小,适用于对动态性能和稳态精度要求极低的场景。但在实际电赛小车或工业应用中,开环控制存在根本性缺陷——它完全忽略系统外部扰动与内部参数漂移的影响。

以STM32平台下的直流电机控制为例,当程序设定 TIMx->CCRy = 10000 (假设最大计数值为50000),理论占空比为20%。编码器反馈显示当前转速为130单位角度/10ms(即13000°/s)。这一数值看似稳定,但只要施加轻微机械负载——例如手指轻触电机轴、车轮接触粗糙地面、或小车开始爬坡——反馈转速立即跌落至90甚至更低。此时开环系统毫无感知能力,仍固执地维持10000的CCR值,导致实际转速持续偏离期望值。问题本质在于: 电机的电磁转矩与PWM占空比并非严格线性映射关系,且该映射受供电电压波动、绕组温升、机械摩擦系数、负载惯量等多重时变因素影响

PID控制器正是为解决这一类“模型不确定性+外部扰动”问题而生的工程工具。它不依赖被控对象(电机+机械结构)的精确数学模型,仅通过误差信号(目标值与实际值之差)的实时运算,生成修正后的控制量(新占空比)。在电赛小车这类资源受限、开发周期紧张的嵌入式项目中,PID不是理论玩具,而是连接“理想控制目标”与“真实物理世界”的关键桥梁。其价值不在于数学完美性,而在于工程鲁棒性——用极少的计算开销和存储空间,换取可接受的动态响应与抗扰能力。

2. 从物理问题到控制框图:四要素闭环构建

将电机转速控制问题抽象为标准闭环控制系统,需明确四个不可省略的工程要素。这不仅是理论推导,更是代码架构设计的起点。

2.1 目标设定(Setpoint):Speed_Target

这是整个闭环的指挥官,定义系统“应该达到什么状态”。在小车应用中,Speed_Target通常由上层逻辑生成:
- 匀速巡航模式下,设为恒定值(如130单位角度/10ms);
- 路径跟踪时,根据曲率动态调整(如弯道降速至80);
- 启动阶段,采用斜坡发生器(Ramp Generator)避免突加扭矩导致打滑。

关键工程约束:Speed_Target必须与编码器采样单位严格一致。若编码器10ms中断内计得脉冲数为130,则Target也必须是130。单位错位会导致比例增益Kp物理意义失效,整定过程完全失焦。

2.2 状态感知(Feedback):Speed_Current

这是系统的“眼睛”,通过硬件传感器获取真实运行状态。在本例中,采用增量式编码器+定时器输入捕获(TI)方案:
- STM32的TIMx编码器接口模式自动解析AB相正交脉冲,计数值存于 TIMx->CNT
- 每10ms触发一次更新事件(UEV),读取 CNT 并清零,所得差值即为该周期转过的角度脉冲数;
- 需注意: CNT 为有符号16位寄存器,若电机高速反转可能溢出,实际工程中应启用更新中断并做溢出补偿。

Speed_Current的采样频率决定系统带宽上限。10ms周期对应100Hz采样率,理论上可抑制10Hz以下扰动。若小车需应对高频路面激励(如鹅卵石路),则需提升至5ms甚至2ms,但会增加CPU负担与ADC(若用霍尔)噪声敏感度。

2.3 控制器(Controller):PID算法核

这是闭环的“大脑”,接收 Error = Speed_Target - Speed_Current ,输出 Duty_Output 。其核心公式为:

Duty_Output = Kp * Error + Ki * ∫Error·dt + Kd * d(Error)/dt

在离散化嵌入式实现中,需转化为差分形式:

Error[k] = Speed_Target - Speed_Current[k]  
Integral[k] = Integral[k-1] + Error[k] * Ts  
Derivative[k] = (Error[k] - Error[k-1]) / Ts  
Duty_Output[k] = Kp * Error[k] + Ki * Integral[k] + Kd * Derivative[k]

其中 Ts=0.01s 为采样周期。此离散化非数学近似,而是为匹配MCU定时器中断节拍的必然选择。所有系数Kp/Ki/Kd均为无量纲整数或定点小数,其量纲已隐含在 Ts 与单位换算中。

2.4 执行机构(Actuator):PWM占空比映射

这是闭环的“手”,将算法输出转化为物理动作。关键转换关系为:

Duty_Output → TIMx->CCRy  

需建立线性映射: CCRy = (Duty_Output * Max_CCR) / Max_Duty 。本例中 Max_Duty=50000 (软件定义的最大占空比基准), Max_CCR 取决于TIMx时基——若 TIMx->ARR=65535 ,则 Max_CCR=65535 。此映射必须满足:
- Duty_Output=0 时, CCRy=0 (电机停转);
- Duty_Output=Max_Duty 时, CCRy=Max_CCR (满占空比);
- 中间值严格线性,避免死区或饱和畸变。

若映射错误(如误将 Max_Duty 设为10000却仍用50000计算),Kp整定结果将完全失效——工程师调参时以为在调“比例增益”,实际在调“映射缩放系数”。

3. PID三参数的物理意义与工程整定逻辑

PID的三个参数并非黑箱魔法数字,每个都对应特定的物理作用机制。理解其本质是高效整定的前提,而非盲目试凑。

3.1 比例项(P):即时误差响应

Kp * Error 提供与当前误差成正比的纠正力。Kp增大,系统响应变快,稳态误差减小。但过大的Kp会引发振荡:当电机因惯性冲过Target时,反向大误差产生强反向扭矩,导致反复超调。在小车直行测试中,Kp过高表现为“蛇形摆动”——车轮左右高频抖动。典型初始值:Kp=0.5~2.0(以Speed_Target=130为基准,Kp=1.0表示误差130时输出满占空比)。

3.2 积分项(I):消除静差的累积器

Ki * ∫Error·dt 对历史误差进行累加,专门对抗恒定扰动(如轴承摩擦、电压缓慢跌落)。若仅有P项,系统在摩擦负载下会稳定在Target下方某点(静差),I项持续积分此偏差,逐步抬高输出直至静差归零。但I项有滞后性,过强会导致“积分饱和”(Integral Windup):当电机堵转时,Error持续为正,Integral疯狂累加,一旦负载消失,系统因巨大积分值严重超调。工程对策:
- 设定Integral上下限(如±2000);
- 采用抗饱和算法(Back-Calculation),当输出达限幅时,停止积分更新。

典型初始值:Ki=0.01~0.1(以Ts=0.01s计,Ki=0.05表示每秒累积误差的5%)。

3.3 微分项(D):超前阻尼器

Kd * d(Error)/dt 预测误差变化趋势,对快速上升的误差施加抑制力。它像机械系统的阻尼器,吸收动能,抑制振荡。在电机启动瞬间,Error从130骤降至0,d(Error)/dt为负大值,D项输出正向制动扭矩,平抑加速过冲。但D项对噪声极度敏感——编码器脉冲抖动会被微分放大,造成PWM毛刺。因此:
- 必须对Speed_Current做低通滤波(如一阶IIR: Speed_Filtered = 0.8*Speed_Current + 0.2*Speed_Filtered_prev );
- D项常与P项组合使用(PD控制),I项单独作用易激化噪声。

典型初始值:Kd=0.5~5.0(Kd=2.0表示误差变化率每100单位/10ms,输出调整2%占空比)。

4. STM32平台上的嵌入式PID实现细节

在STM32 HAL库环境下,PID需深度融入硬件中断框架。以下为经量产验证的实现要点,避开常见陷阱。

4.1 定时器配置:确保采样周期精准

采用TIM6或TIM7作为PID主定时器(无通道输出,纯更新中断):

// 初始化TIM7(72MHz APB1)
htim7.Instance = TIM7;
htim7.Init.Prescaler = 7199;    // 72MHz/7200 = 10kHz → 100us tick
htim7.Init.CounterMode = TIM_COUNTERMODE_UP;
htim7.Init.Period = 99;         // 100 * 100us = 10ms
HAL_TIM_Base_Init(&htim7);
HAL_TIM_Base_Start_IT(&htim7); // 启用更新中断

关键点:
- 禁用重复计数器(Repetition Counter) :否则中断周期不稳定;
- 关闭自动重装载预装载(ARPE) :避免 ARR 更新延迟一个周期;
- 中断优先级高于编码器捕获 :确保 Speed_Current 读取后立即进入PID计算,减少时序抖动。

4.2 编码器数据同步:避免读取竞争

编码器计数寄存器 TIMx->CNT 在更新事件(UEV)时锁存,但 CNT 本身仍在计数。正确读取流程:

// 在TIM7更新中断中执行
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
  if(htim->Instance == TIM7) {
    // 1. 先读取TIM2(编码器)的CNT(已锁存)
    int16_t cnt_now = (int16_t)__HAL_TIM_GET_COUNTER(&htim2);
    // 2. 清除TIM2的更新标志(非UEV中断,手动触发)
    __HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
    // 3. 计算本次速度(需处理符号与溢出)
    int16_t delta = cnt_now - cnt_last;
    cnt_last = cnt_now;
    Speed_Current = (delta > 32767) ? delta - 65536 : 
                   (delta < -32767) ? delta + 65536 : delta;
  }
}

若直接在 HAL_TIM_IC_CaptureCallback 中读取 CNT ,会因IC中断与UEV中断异步导致数据错位。

4.3 PID计算:定点化与防溢出

为节省浮点运算资源,采用Q15定点格式(16位有符号数,小数位15):

#define Q15(x) ((int16_t)((x)*32768))
int32_t pid_calc(int16_t target, int16_t current) {
  int32_t error = (int32_t)target - (int32_t)current;
  // P项:Kp_Q15 * error >> 15
  int32_t p_out = (Kp_Q15 * error) >> 15;
  // I项:Ki_Q15 * error * Ts(Ts=0.01→Q15=327)  
  integral += (Ki_Q15 * error * 327) >> 15;
  // 限幅
  if(integral > 2000) integral = 2000;
  if(integral < -2000) integral = -2000;
  int32_t i_out = integral;
  // D项:Kd_Q15 * (error - last_error) >> 15
  int32_t d_out = (Kd_Q15 * (error - last_error)) >> 15;
  last_error = error;
  return p_out + i_out + d_out;
}

输出值范围需映射至 [0, 50000] ,再写入 TIMx->CCRy 。此定点实现使STM32F103在72MHz下单次PID耗时<5μs。

5. 电赛小车场景下的典型问题与实战对策

理论参数在实验室环境可调出理想波形,但真实小车面临更复杂的物理约束。以下是多年电赛现场积累的硬核经验。

5.1 启动阶段的“零速振荡”

小车静止时,Speed_Current=0,Error=130,P项输出大值使电机猛转,但因静摩擦力未克服,电机实际未动,Error仍为130,形成“输出全功率→不动→继续全功率”死循环,伴随高频嗡鸣。
对策 :引入启动阈值(Start Threshold)

if(abs(Speed_Target) < 10) { // Target过小,直接停机
  Duty_Output = 0;
} else if(Speed_Current == 0 && abs(Error) > 50) { // 静止且大误差
  Duty_Output = 10000; // 强制给10000启动,突破静摩擦
} else {
  Duty_Output = pid_calc(...); // 正常PID
}

此策略模拟人工“点动启动”,10000对应约20%占空比,足够突破静摩擦而不致失控。

5.2 上坡动力不足的自适应增益

小车匀速上坡时,PID持续输出高占空比(如45000)仍无法维持Target,表明负载转矩已超出电机额定能力。此时强行增大Kp只会加剧振荡。
对策 :基于Duty_Output的增益调度

if(Duty_Output > 40000) { // 占空比超80%
  Kp_adj = Kp * 1.2; // 提高比例作用,增强响应
  Ki_adj = Ki * 0.8; // 降低积分,防饱和
} else {
  Kp_adj = Kp;
  Ki_adj = Ki;
}

此逻辑无需额外传感器,仅凭执行器输出即可判断负载状态,是资源受限系统的智慧妥协。

5.3 编码器信号干扰导致的“虚假超调”

粗糙路面引起车轮跳动,编码器A/B相出现毛刺,导致 CNT 误增或误减,PID误判为速度突变而剧烈调整PWM。
对策 :硬件+软件双重滤波
- 硬件 :在编码器信号线并联100pF陶瓷电容,抑制高频噪声;
- 软件 :速度值连续3次采样中,若某次值偏离中位数>30%,则丢弃该次数据,用前次值替代。

speed_buf[buf_idx++] = Speed_Current;
if(buf_idx >= 3) buf_idx = 0;
// 取speed_buf中三个值的中位数
Speed_Current = median3(speed_buf[0], speed_buf[1], speed_buf[2]);

实测此法可消除95%以上路面噪声引发的误动作。

6. 参数整定的工程实践路径

面对未知电机特性,切忌从Kp=100开始暴力试探。推荐按“P→PI→PID”三步渐进法,每步验证后再推进。

6.1 第一步:纯P控制找临界稳定点

  • 设Ki=0, Kd=0,Kp从0.1开始,每轮增加0.5;
  • 观察示波器上 Speed_Current 波形:
  • 若曲线缓慢爬升无超调→Kp过小;
  • 若出现1~2次衰减振荡→Kp接近最优;
  • 若持续等幅振荡→记录此时Kp_cr=2.8,周期T_cr=150ms;
  • 根据Ziegler-Nichols法则,初步Kp = 0.6*Kp_cr = 1.68。

此步骤可在3分钟内完成,快速建立系统基本响应认知。

6.2 第二步:加入I项消除静差

  • 固定Kp=1.68,Ki从0.01开始,每轮增加0.02;
  • 加载恒定负载(如手压电机轴),观察稳态误差:
  • 误差缓慢收敛但最终残留5单位→Ki仍偏小;
  • 误差归零但出现缓慢爬升超调→Ki过大;
  • 最佳Ki使误差在5秒内收敛至±1单位内,无持续超调。

注意:I项整定必须在有负载条件下进行,空载时静差本就为零,无法验证效果。

6.3 第三步:D项抑制高频振荡

  • 当P+I已使系统稳定但启动/变速时有小幅超调(如超调10%),引入Kd;
  • Kd从0.5开始,每轮增加1.0,观察超调量:
  • 超调量从10%降至5%→有效;
  • 波形出现锯齿状高频抖动→Kd过大,需同步加强速度滤波。

最终参数需在真实小车运动中反复验证:直道匀速、S弯跟踪、坡道启停。记住, 没有“最优参数”,只有“当前工况下最鲁棒的参数” 。一块电池从满电到欠电,Kp需下调10%以补偿电压下降带来的扭矩衰减——这才是嵌入式工程师的真实战场。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐