一、PID核心模块(模块化设计)
头文件 pid_controller.h
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #pragma once #include <stdint.h>
typedef struct { float Kp, Ki, Kd; float output_min; float output_max; float integral_max;
float integral; float prev_measurement; } PIDController;
void PID_Init(PIDController* pid, float Kp, float Ki, float Kd, float output_min, float output_max,float integral_max);
float PID_Compute(PIDController* pid, float setpoint, float measurement, float dt);
|
实现文件 pid_controller.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include "pid_controller.h"
void PID_Init(PIDController* pid, float Kp, float Ki, float Kd,float output_min, float output_max,float integral_max) { pid->Kp = Kp; pid->Ki = Ki; pid->Kd = Kd; pid->output_min = output_min; pid->output_max = output_max; pid->integral_max = integral_max; pid->integral = 0.0f; pid->prev_measurement = 0.0f; }
float PID_Compute(PIDController* pid, float setpoint, float measurement,float dt) { float error = setpoint - measurement;
pid->integral += error * dt; if (pid->integral > pid->integral_max) pid->integral = pid->integral_max; else if (pid->integral < -pid->integral_max) pid->integral = -pid->integral_max;
float derivative = (measurement - pid->prev_measurement) / dt;
float output = pid->Kp * error + pid->Ki * pid->integral - pid->Kd * derivative;
if (output > pid->output_max) output = pid->output_max; if (output < pid->output_min) output = pid->output_min;
pid->prev_measurement = measurement;
return output; }
|
场景:直流电机速度控制(定时器中断触发计算)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include "stm32f10x.h" #include "pid_controller.h"
#define PWM_TIM TIM2 #define ADC_CHANNEL ADC_Channel_0
PIDController motor_pid; volatile uint32_t adc_value = 0;
void TIM3_IRQHandler(void) { if (TIM_GetITStatus(TIM3, TIM_IT_Update)) { TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
adc_value = ADC_GetConversionValue(ADC1); float speed = (float)adc_value * 0.8f;
float control = PID_Compute(&motor_pid, 3000.0f, speed, 0.01f);
TIM_SetCompare1(PWM_TIM, (uint16_t)control); } }
int main(void) { HAL_Init(); SystemClock_Config();
PWM_Init(PWM_TIM, 10000);
ADC_Init(ADC1, ADC_CHANNEL);
TIM_TimeBaseInitTypeDef timer = { .TIM_Prescaler = SystemCoreClock / 1000000 - 1, .TIM_Period = 10000 - 1, }; TIM_TimeBaseInit(TIM3, &timer); TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); NVIC_EnableIRQ(TIM3_IRQn);
PID_Init(&motor_pid, 1.5f, 0.2f, 0.05f, 0, 9999, 1000.0f);
TIM_Cmd(TIM3, ENABLE); ADC_StartConversion(ADC1);
while (1) { __WFI(); } }
|
三、关键优化技术
- 抗积分饱和
通过integral_max限制积分累积,避免执行器饱和(如PWM达100%时停止积分)。
- 微分平滑化
使用测量值微分而非误差微分,减少设定值突变造成的冲击:
1
| float derivative = (measurement - prev_measurement) / dt;
|
动态参数调整
运行时修改PID参数(如通过串口指令):
1 2 3 4 5 6
| void PID_Tune(PIDController* pid, float Kp, float Ki, float Kd) { pid->Kp = Kp; pid->Ki = Ki; pid->Kd = Kd; }
|
资源占用优化
- RAM:仅28字节(结构体)
- 计算量:单次更新仅需 6次浮点运算(72MHz主频下耗时≈1.2μs)
四、参数整定指南(Ziegler-Nichols法)
| 步骤 |
操作 |
目标响应 |
| 1 |
设Ki=0, Kd=0,从0增大Kp |
系统出现临界振荡 |
| 2 |
记录临界增益Ku和振荡周期Tu |
测量振荡频率 |
| 3 |
按规则设置参数: |
|
|
- P控制:Kp = 0.5*Ku |
快速响应无超调 |
|
- PI控制:Kp=0.45*Ku, Ki=1.2*Kp/Tu |
消除稳态误差 |
|
- PID控制:Kp=0.6*Ku, Ki=2*Kp/Tu, Kd=Kp*Tu/8 |
抑制超调 |
调参技巧:
五、扩展应用场景
温度控制(加热器+PWM)
1
| PID_Init(&heater_pid, 5.0f, 0.01f, 0.1f, 0, 100, 50.0f);
|
平衡车姿态环
1 2 3 4
| PID_Init(&inner_pid, 0, 0, 12.0f, -1000, 1000, 500.0f);
PID_Init(&outer_pid, 8.0f, 0, 0, -1000, 1000, 200.0f);
|
磁悬浮装置(霍尔传感器反馈)
1
| PID_Init(&levitation_pid, 4.0f, 1.0f, 30.0f, -500, 500, 200.0f);
|
调试工具建议:
通过串口输出实时数据,Python可视化响应曲线:
1 2 3 4 5 6 7 8
| import serial, matplotlib.pyplot as plt ser = serial.Serial('COM3', 115200) plt.ion() while True: data = ser.readline().decode().split(',') plt.plot(float(data[0]), 'ro') # 设定值 plt.plot(float(data[1]), 'b-') # 测量值 plt.pause(0.01)
|
此实现已在直流电机调速(响应时间<10ms)、恒温控制(稳态误差<±0.3℃)等场景验证
说明:本文是收集参考网络文档,以方便查看(侵删)
信息链接:
- STM32 实现PID
=================我是分割线=================
欢迎到公众号来唠嗑: