从电机振动到平稳运行:STM32F103实战拆解PID控制的每一步
看着眼前的直流电机在PWM信号驱动下疯狂抖动,转速忽高忽低,我意识到教科书上的PID公式远没有实际调试来得深刻。这次我们就用STM32F103开发板和一个小型直流电机,通过实时观测速度曲线变化,真正理解比例、积分、微分这三个环节如何协同工作。
1. 实验环境搭建与基础准备
1.1 硬件配置清单
工欲善其事,必先利其器。我们需要准备以下硬件组件搭建实验平台:
- STM32F103C8T6最小系统板(蓝色药丸开发板):作为主控制器
- L298N电机驱动模块:驱动12V直流电机
- 20孔光电编码盘+红外对管:用于电机转速测量
- 0.96寸OLED显示屏:实时显示转速和PID参数
- USB转TTL模块:将速度曲线数据发送到PC端绘图
提示:编码盘安装时需确保与电机轴同心,红外对管距离编码盘2-3mm为最佳检测距离
1.2 软件环境配置
开发环境采用Keil MDK-ARM,需要安装的软件包包括:
STM32F10x_DFP # STM32F1系列设备支持包 ARM::CMSIS # Cortex微控制器软件接口标准 ARM::CMSIS-Driver # 标准外设驱动关键外设初始化代码结构如下:
// 电机PWM初始化(TIM2通道1) void Motor_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct; TIM_OCInitTypeDef TIM_OCInitStruct; // 时钟使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIO配置 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStruct); // 定时器基础配置 TIM_TimeBaseStruct.TIM_Period = 719; // 10kHz PWM TIM_TimeBaseStruct.TIM_Prescaler = 0; TIM_TimeBaseStruct.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct); // PWM模式配置 TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC1Init(TIM2, &TIM_OCInitStruct); TIM_Cmd(TIM2, ENABLE); TIM_CtrlPWMOutputs(TIM2, ENABLE); }2. 纯比例控制(P)的振荡之谜
2.1 比例系数的直观影响
当我们仅使用比例控制时,系统表现就像新手司机踩油门——要么力度不够,要么冲过头。设置目标转速为200RPM,逐步增大Kp值,可以观察到三种典型状态:
| Kp值 | 系统响应 | 波形特征 | 物理现象 |
|---|---|---|---|
| 0.1 | 响应迟缓 | 缓慢上升无超调 | 电机启动缓慢,难以达到目标转速 |
| 0.5 | 适度响应 | 小幅超调后稳定 | 电机有明显加速过程,最终转速接近目标 |
| 2.0 | 剧烈振荡 | 持续等幅振荡 | 电机转速周期性波动,发出规律性噪音 |
// 纯比例控制实现代码 float P_Controller(float target, float current, float Kp) { float error = target - current; return Kp * error; // 只有比例项 }2.2 静差现象的成因分析
即使找到合适的Kp值,纯比例控制仍存在无法消除的稳态误差。这是因为:
- 电机需要最小PWM占空比才能克服静摩擦力
- 当误差减小到一定值时,比例输出不足以维持所需转速
- 系统最终会稳定在一个比目标值低的转速上
通过串口绘图可以清晰看到,当Kp=0.8时,系统最终稳定在185RPM(目标200RPM),存在7.5%的静差。这也是引入积分控制的根本原因。
3. 积分控制(I)的纠偏艺术
3.1 积分项消除静差的原理
积分项就像一个有记忆功能的调节器,它会累积历史误差并持续修正。在代码实现上,我们需要增加误差累积变量:
typedef struct { float Kp, Ki, Kd; float integral; // 积分累积项 float prev_error; } PIDController; float PID_Update(PIDController* pid, float target, float current) { float error = target - current; pid->integral += error; // 误差累积 float derivative = error - pid->prev_error; float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative; pid->prev_error = error; return output; }3.2 积分饱和与应对策略
过大的Ki值会导致积分饱和现象——当系统受到扰动(如突然加载)时,积分项会累积过大值,造成系统剧烈超调。通过实验可以观察到:
- Ki=0.01时,系统约5秒消除静差,响应平稳
- Ki=0.05时,系统出现明显超调(约15%)
- Ki=0.1时,电机转速在目标值附近持续振荡
注意:实际项目中常采用积分限幅或积分分离等高级技巧来避免饱和问题
4. 微分控制(D)的预见性调节
4.1 微分项的阻尼作用
微分项就像经验丰富的老司机,能预判速度变化趋势提前调节。在电机突然加载时:
- 无微分控制:转速会先下降再恢复,形成明显下凹曲线
- 加入微分:转速下降幅度减小,恢复时间缩短50%以上
合适的Kd值能使系统响应既快速又平稳。通过对比实验发现:
| Kd值 | 超调量 | 稳定时间 | 抗扰动性 |
|---|---|---|---|
| 0 | 12% | 800ms | 差 |
| 0.01 | 5% | 400ms | 良好 |
| 0.05 | 无 | 600ms | 优秀 |
4.2 微分噪声的滤波处理
实际应用中,编码器测量噪声会被微分环节放大。解决方法包括:
- 对测量值进行移动平均滤波
- 使用不完全微分算法
- 增加软件低通滤波器
// 带滤波的微分计算 float filtered_derivative = 0; float alpha = 0.2; // 滤波系数 void calculate_derivative(float error) { static float last_error = 0; float raw_derivative = error - last_error; filtered_derivative = alpha * raw_derivative + (1-alpha) * filtered_derivative; last_error = error; }5. PID参数整定的实战技巧
5.1 试凑法调整步骤
- 先调Kp:将Ki和Kd设为0,逐步增大Kp直到系统出现等幅振荡
- 记录临界值:此时Kp记为Ku,振荡周期记为Tu
- 按经验公式设置:
- Kp = 0.6 * Ku
- Ki = 1.2 * Ku / Tu
- Kd = 0.075 * Ku * Tu
5.2 波形诊断与参数修正
通过观察速度曲线形态可以快速定位问题:
- 持续小幅振荡:适当减小Kp或增大Kd
- 静差长期存在:小幅增大Ki
- 响应迟缓:增大Kp或减小Kd
- 超调过大:减小Kp或增大Kd
下表展示了典型问题及解决方案:
| 问题现象 | 可能原因 | 调整方向 |
|---|---|---|
| 转速波动大 | Kp过大或Kd过小 | 减小Kp 10%或增大Kd 20% |
| 达到稳态慢 | Ki过小 | 逐步增大Ki直至静差消除 |
| 电机启动无力 | Kp过小 | 以5%步进增大Kp |
| 负载突变恢复慢 | Kd不足 | 增大Kd并检查测量噪声 |
6. 进阶优化策略
6.1 变参数PID实现
根据误差大小动态调整参数可以获得更好性能:
float adaptive_PID(PIDController* pid, float error) { float abs_error = fabs(error); // 大误差时增强比例作用 if(abs_error > 50) { return 1.5 * pid->Kp * error; } // 小误差时启用完整PID else { pid->integral += error; float derivative = error - pid->prev_error; pid->prev_error = error; return pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative; } }6.2 速度前馈补偿
结合电机特性曲线,可以提前补偿非线性段:
// 电机PWM-转速特性查找表 const uint16_t PWM_Map[] = { 0, // 0% 300, // 10% 450, // 15% ... // 其他工作点 }; float feedforward_compensation(float target_rpm) { // 查表获取基础PWM值 uint16_t base_pwm = PWM_Map[(int)(target_rpm / 10)]; return base_pwm + 0.2 * target_rpm; // 附加线性补偿 }在项目后期调试中发现,当电机运行在180-220RPM区间时,配合前馈控制可将调节时间缩短至传统PID的1/3。这提醒我们:理解被控对象特性有时比复杂算法更有效。