一、电机速度闭环整体流程
电机通过编码器测速获得实际转速,与设定的目标转速进行比较,通过 PID 计算控制电压,再转换成 PWM 驱动电机。
目标转速 Ω* ↓ PID控制 ↓ 目标电压 Ua ↓ Ua / Vbat → PWM占空比 ↓ 电机 ↓ 编码器 ↓ 实际转速 Ω └────反馈二、App_Motor.c
1. PID初始化
左右电机分别使用一个 PID:
PID_Init(&pid_motor_l,0.5,7,0);PID_Init(&pid_motor_r,0.5,7,0);参数:
Kp = 0.5 Ki = 7 Kd = 0由于Kd = 0,所以实际是PI控制器。
同时限制输出:
PID_LimitConfig(&pid_motor_l,+8.4f,-8.4f);即控制电压范围:
−8.4V∼+8.4V-8.4V \sim +8.4V−8.4V∼+8.4V
2. 电机速度控制
App_Motor_Proc()每1ms执行一次:
① 编码器获取实际转速 ② PID计算控制电压 Ua ③ 根据电池电压计算PWM占空比 ④ 输出PWMPWM计算:
Duty=UaVbat×100%Duty=\frac{U_a}{V_{bat}}\times100\%Duty=VbatUa×100%
这样可以根据当前电池电压调整占空比。
3. 设置目标转速
App_Motor_SetOmega_L(Omega);App_Motor_SetOmega_R(Omega);本质上就是修改 PID 的:
SP = 目标转速4. 电机开关
App_Motor_Cmd(On);除了控制 PWM,还会:
PID_Reset()清除 PID 的历史误差、积分和时间状态,避免重新启动时继承之前的 PID 状态。
三、pid_simple.c
1. PID基本计算
首先计算误差:
e=SP−FBe=SP-FBe=SP−FB
其中:
SP:目标值FB:实际反馈值
然后计算 P、I、D 三部分:
P=KpeP=K_p eP=Kpe
I=Ki∫e dtI=K_i\int e\,dtI=Ki∫edt
D=KddedtD=K_d\frac{de}{dt}D=Kddtde
最后:
CO=P+I+DCO=P+I+DCO=P+I+D
在本项目中:
Kd=0K_d=0Kd=0
所以实际使用:
CO=Kpe+Ki∫e dtCO=K_pe+K_i\int e\,dtCO=Kpe+Ki∫edt
即PI控制。
2. 离散算法实现
程序运行在 STM32 上,需要每次采样时计算一次,所以使用离散形式。
时间间隔:
deltaT=(t_k-t_k_1)*1e-6f;积分采用梯形积分:
Ik=Ik−1+ek+ek−12ΔtI_k=I_{k-1}+\frac{e_k+e_{k-1}}{2}\Delta tIk=Ik−1+2ek+ek−1Δt
微分:
Dk=ek−ek−1ΔtD_k=\frac{e_k-e_{k-1}}{\Delta t}Dk=Δtek−ek−1
因此 PID 每次计算都需要保存:
上一次时间 上一次误差 上一次积分值3. 限幅
PID 输出进行限幅:
if(CO>UpperLimit)CO=UpperLimit;if(CO<LowerLimit)CO=LowerLimit;防止输出超过电机允许范围。
同时对积分值进行限制,避免积分不断累积造成积分饱和。
四、两个文件的关系
App_Motor.c │ ├── 编码器 → 获取实际转速 │ ├── PID → 计算 Ua │ ├── 电池 → 获取 Vbat │ └── PWM → 驱动电机其中:
App_Motor.c负责整个电机速度控制流程,pid_simple.c负责具体的 PID 算法。
最终记住这一条就够了:
目标转速 ↓ 速度误差 ↓ PI计算 ↓ Ua ↓ Ua / Vbat ↓ PWM ↓ 电机 ↓ 编码器测速 ↓ 反馈