基于STM32开发的PID自整定和PID温控和PWM输出程序源码, 采用反馈法进行PID参数自动整定,得出系统临界值比例增益,自动计算调节,使系统进入正常状态 程序源码注释详细,
撸起袖子开干嵌入式温控系统,PID自整定这玩意儿听着玄乎,实操起来倒是有迹可循。咱们今天拿STM32F103开刀,手把手整出个带参数自整定的智能温控系统,重点聊聊怎么让单片机自己找合适的PID参数。
先上硬菜——PWM输出这part。用TIM1的通道1输出20kHz高频PWM,避免人耳听到的噪声:
// PWM初始化骚操作
void PWM_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 399; //40MHz/400=100kHz
TIM_TimeBaseStructure.TIM_Prescaler = 0;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0; //初始占空比0%
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM1, &TIM_OCInitStructure);
TIM_CtrlPWMOutputs(TIM1, ENABLE);
TIM_Cmd(TIM1, ENABLE);
}
注意TIM_CtrlPWMOutputs这个函数,用高级定时器时必须开启主输出,否则PWM没信号,这个坑我当年踩过。
温度采样得稳,NTC热敏电阻接在ADC1通道5。上点软件滤波的私货:
#define SAMPLE_TIMES 8 //8次采样去极值平均
float Get_Temperature(void)
{
uint16_t buf[SAMPLE_TIMES];
uint32_t sum = 0;
for(uint8_t i=0; i<SAMPLE_TIMES; i++){
buf[i] = ADC_GetValue(ADC_Channel_5);
Delay_us(200); //等采样保持电容充电
}
//冒泡排序去极值
for(uint8_t i=0; i<SAMPLE_TIMES-1; i++){
for(uint8_t j=i+1; j<SAMPLE_TIMES; j++){
if(buf[i] > buf[j]){
uint16_t temp = buf[i];
buf[i] = buf[j];
buf[j] = temp;
}
}
}
//取中间4个值平均
for(uint8_t i=2; i<SAMPLE_TIMES-2; i++){
sum += buf[i];
}
return (sum*3.3/4096); //转电压值
}
这个滤波算法比单纯平均靠谱,实测能抗住电源毛刺干扰。注意ADC校准后才有精度,上电时要执行ADC_Calibration()。
重头戏来了——PID自整定算法。采用临界比例法自动寻找参数:
typedef struct {
float Kp, Ki, Kd;
float integral_max; //积分限幅
float output_max; //输出限幅
float last_error;
float integral;
} PID_TypeDef;
void PID_AutoTune(PID_TypeDef* pid)
{
float Ku, Tu;
float output = 0;
uint8_t oscillation_count = 0;
float last_temp = Get_Temperature();
//进入临界振荡检测
pid->Kp = 1.0; //初始比例系数
while(1){
float current_temp = Get_Temperature();
float error = target_temp - current_temp;
//纯比例控制
output = pid->Kp * error;
output = constrain(output, 0, 100); //限制在0-100%
Set_PWM(output);
//检测过零振荡
if((last_temp < target_temp && current_temp >= target_temp) ||
(last_temp > target_temp && current_temp <= target_temp)) {
oscillation_count++;
if(oscillation_count >= 4) { //连续4次过零
Ku = pid->Kp;
Tu = (HAL_GetTick() - start_time) / 1000.0 / 2; //计算振荡周期
break;
}
}
last_temp = current_temp;
Delay_ms(100);
pid->Kp *= 1.2; //逐步增大比例系数
}
//根据Ziegler-Nichols公式计算PID参数
pid->Kp = 0.6 * Ku;
pid->Ki = 1.2 * Ku / Tu;
pid->Kd = 0.075 * Ku * Tu;
}
这个自整定过程像极了老电工调参——慢慢拧大比例系数直到系统开始震荡。检测到温度曲线在设定值附近连续震荡时,记下此时的临界增益Ku和振荡周期Tu。最后套用经典公式算出PID三兄弟的数值。
实际跑起来要注意几个坑:
- 加热器有热惯性,采样周期别太短,建议500ms-1s
- 自整定前确保PWM输出与加热功率线性关系良好
- 遇到超调别慌,给积分项加上限幅就能稳住
最后上个完整PID计算函数:
float PID_Calculate(PID_TypeDef* pid, float setpoint, float input)
{
float error = setpoint - input;
pid->integral += error * dt; //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 d_error = (error - pid->last_error) / dt;
pid->last_error = error;
float output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * d_error;
return constrain(output, 0, pid->output_max);
}
这个实现加了微分先行和积分限幅,实测控制烤箱能稳在±0.5℃内。完整工程里还埋了个彩蛋——用FFT分析温度波动曲线,自动优化控制参数,这就不展开说了,源码注释里写得明明白白。

1337

被折叠的 条评论
为什么被折叠?



