PID 温控设计(基于 STC51)
一、需求分析
开关型控制存在的问题:加热的过程是全功率加热,三极管发热量大,温度控制振荡幅度大,控制精度较低。而通过采用PID方法能够更加精确地控制加热片处于目标温度,并在一个较小范围内浮动。
- 精度要求:±0.2℃ 温控范围
- 目标温度:45℃
- 温度工作区间:20℃ - 70℃
- 温度显示
二、技术设计
1、硬件选型
- STC51 单片机
- 最小外围温控系统
2、PID 控制算法
比例控制
如果能根据当前温度和设定温度的差异调节制冷的功率,比如选一个比例系数来控制制冷的功率,可解决半导体制冷量大的问题,接近温度设定值时,功率减小,降低温度的振荡,这种方法称为比例控制。
上图列举了一个比例控制的过程,目标值为 1
, 设置一个比例系数 Kp = 0.5
, 每次的增量都不同,越接近目标值,增量越小,从图中可以看到大约 7 个周期后控制值就非常接近设定值,而且后面非常稳定。比例系数 Kp
的大小决定了达到设定值的快慢,从图中也可以看到,当设定值为 1时, Kp
的值不能设置的太大,所以 Kp
的选取要根据实际控制对象来定。若 Kp
选取的值较小,达到控制设定值的时间就会比较长,如下图所示:
比例控制在实际存在损耗时很难达到设定的目标值,如果通过提高目标设定值来达到实际控制值,在损耗值较小的情况下可能会超过实际的设定值,如下图所示,故该种方案在实际应用中是不太会采用的。
PID 控制技术
PID 指的是 Proportion-Integral-Differential,即比例-积分-微分。用公式可表示为任一时刻的控制量要综合考虑比例因子,累计误差的积分,以及该时刻的微分变化量。
从公式可以看出,控制值包含三项,第一项为 比例项 \(K_p\), 前面分析过比例控制存在的不足是有外界影响时控制量会小于或大于设定值,在控制中存在不确定性。通过计算第二项 累计误差 \(\frac{1}{T_I} \int_0^{t} {e(t)}dt\) 和第三项 当前变化率 \(T_D \frac{de(t)}{dt}\) 来进行修正,每时每刻都在不断修正控制值,这样就可以避免外界干扰的影响,从而达到控制值和设定值尽可能的接近。
PID 控制中的三项,有时也要根据实际的控制对象进行修改,如累计误差时刻存在的系统,控制时就不能计算累计误差,否则系统很难达到稳定,此时控制就变成了 PD 控制。比例控制,PD 控制,PID 控制是控制中常用的几种方法。
PID 实现方法
实际编程时,需将积分、微分转换成累加和差分计算,如在温度控制中,用 PWM 技术控制时要根据每个时刻的设定值和实际值的误差值 (Error
)、所有误差值的求和值 (Integral
) 以及每个时刻误差的变化梯度 (ErrorError_last
),利用 Kp
, Ki
, Kd
三个系数计算出此时刻 PWM 的占空比的系数 PWM_t
,从而控制 PWM 的占空比,达到调节等效电流的目的,也即控制制冷的速度。
PWM_t = Kp * Error + Ki * Integral + Kd * (Error - Error_last);
Kp
, Ki
, Kd
三个系数要根据实际控制的变化情况不断修正,比如制冷速度太慢,可以增加 Kp
, Ki
和 Kd
系数对制冷速度的影响不大,但对最终控制精度有影响,可以根据控制的结果,不断优化 Ki
和 Kd
的值。
虽然 PID 控制技术是个广泛应用的技术,但具体到某个控制对象,其 Kp
, Ki
, Kd
系数都是要在不断测试中完善,这个过程是一个相对花费时间的过程,一旦三个系数优化好了,则控制就能得心应手。当然这些系数的优化也不是非要靠人工去逐步调节,当前已经有很多 PID 自整定技术,可以让系统自动搜索优化的系数,当然这个过程也是要花费很多时间的。比如一个温控系统,如果温度范围大,则自整定的时间就比较长,可能要几十分钟才能完成。
三、编程实现
1、具体代码
编写 PID 头文件 PID.h:
//定义两个变量,float Temp_point,(温度设置值)和 温度测量值 float Temp_aver
//在main函数 中调用 PID_Init();
//占空比的变化数值 t = PID_realize(Temp_point, Temp_aver);
//可先观察 t 大概是多少,然后设置周期 T
//温控精度可以通过修改 Kp, Ki, Kd 参数来优化,需不断输出数据,画图或模型分析来观察调节的结果
struct PID {float Set_point; //目标值float Actual_point; //实际值float Error; //当前误差float Error_last; //上次误差float Kp, Ki, Kd;float integral; //误差积分值float Differential; //误差微分值float Voltage;
};
struct PID pid;
void PID_Init(float Temp_point, float Temp_aver)
{pid.Set_point = Temp_point;pid.Actual_point = Temp_aver;pid.Error = 0;pid.Error_last = 0;pid.Kp = 60;pid.Ki = 0.01;pid.Kd = 100;pid.integral = 0;pid.Differential = 0;pid.Voltage = 0;
}char PID_realize(float Temp_point, float Temp_aver)
{char pidresult;pid.Set_point = Temp_point; pid.Actual_point = Temp_aver; pid.Error = pid.Set_point-pid.Actual_point; pid.integral = pid.integral+pid.Error; pid.Differential = pid.Error-pid.Error_last; pid.Voltage = pid.Kp*pid.Error + pid.Ki * pid.integral + pid.Kd * (pid.Error - pid.Error_last);//+pid.Kout; pid.Error_last = pid.Error;if(pid.Voltage > 100)pid.Voltage = 100;else if(pid.Voltage < 0)pid.Voltage = 0; pidresult = pid.Voltage + 10;return pidresult;
}
编写主函数 main.c:
#include <STC12.h>
#include <LCD1602.h>
#include <ADC.h>
#include <math.h>
#include <PID.h>
#include <stdio.h>
#define uchar unsigned char
#define uint unsigned int
#define Rp 10000
#define B 3435
#define Vcc 5
#define Temp_point 45sbit heat = P1^0;
uint heat_t = 0;void USART_Init()
{SCON = 0x50;TMOD = 0X20;TL1 = 0XFD;TH1 = 0XFD;TR1 = 1;ES = 1;TI = 1;EA = 1;
}void UART_SendByte(uchar Byte){SBUF = Byte;while(TI == 0);TI = 0;
}void Delay_ms(uint time)
{uint i,j;for(i = 0;i < time;i ++)for(j = 0;j < 930;j ++);
}void PWM(char a) {heat = 0;Delay_ms(a);heat = 1;Delay_ms(111 - a);
}void main()
{float Temp_aver = 20;float res0 = 0;float T1 = 0;float Rt = 0;float T2 = 273.15 + 29.7377;uint i = 0;uchar str[6] = "";heat = 1;USART_Init();LCD_1602_Init();ADC_Init(ADC_PORT1);PID_Init(Temp_point,Temp_aver);Write_1602_String("TEMP CTRL ZBY",0xc0 + 0x02);while(1){res0 = GetADCResult(ADC_CH1);Rt = res0 / (Vcc - res0) * Rp;T1 = 1 / (log(Rt / Rp) / B + 1 / T2);Temp_aver = T1 - 268.15 + 0.5;heat_t = PID_realize(Temp_point, Temp_aver);PWM(heat_t);Write_1602_String("T=",0x80);Write_1602_Data(0x30 + (uint)(Temp_aver/10)%10);Write_1602_Data(0x30 + (uint)Temp_aver%10);Write_1602_Data('.');Write_1602_Data(0x30 + (uint)(Temp_aver*10)%10);Write_1602_Data('C');str[0] = 0x30 + (uint)(Temp_aver/10)%10;str[1] = 0x30 + (uint)Temp_aver%10;str[2] = '.';str[3] = 0x30 + (uint)(Temp_aver*10)%10;str[4] = '\t';str[5] = '\n';for (i = 0; i <= 5; i++) {UART_SendByte(str[i]);}}
}
2、代码设计解析
在代码实现层面,整个系统的核心在于温度采集、PID运算与PWM输出的协同工作。让我们先看温度采集部分——通过ADC模块读取热敏电阻的电压值后,采用经典的 Steinhart-Hart 方程进行温度换算。这里有个细节需要注意:公式中的 T2 参数对应的是传感器在特定温度下的基准阻值,实际调试时需要根据热敏电阻的规格书精确校准。在代码中可以看到,最终对温度值做了 0.5℃ 的偏移补偿,这是为了消除传感器安装位置带来的测量误差。
PID控制模块的实现展现了典型的离散化处理思路。结构体PID
中保存着关键的状态变量:Error
记录当前温度偏差,integral
累积历史误差,Error_last
则用于计算微分项。算法核心在于这个公式:
pid.Voltage = Kp * Error + Ki * integral + Kd * (Error - Error_last)
这里的系数设置非常考验调试经验——初始参数Kp=60, Ki=0.01, Kd=100
看似夸张的比例系数,实际上与PWM周期设计密切相关。当PWM周期设定为11ms时,占空比调节步长需要足够明显才能产生有效的控制作用。
PWM生成函数的设计体现了实用主义风格:通过简单的延时循环产生占空比信号。代码中heat_t
参数经过+10的偏移处理,为补偿三极管导通电压带来的死区效应。
在参数整定过程中,一个有趣的矛盾现象值得注意:当增大比例系数Kp时,系统响应速度加快,但在接近目标温度时会产生明显的振荡;而增加微分系数Kd能有效抑制超调,却会延长升温时间。调试时需要在这两者间寻找平衡点。实践中发现,在温度上升阶段暂时关闭积分项(即采用PD控制),待进入±1℃范围后再启用完整PID控制,可以显著改善动态性能。
3. 系统架构解析
系统采用模块化设计,包含以下核心模块:
- 温度采集模块:通过ADC读取热敏电阻电压值,基于Steinhart-Hart方程进行温度转换:
Rt = res0 / (Vcc - res0) * Rp; //计算热敏电阻阻值 T1 = 1 / (log(Rt / Rp) / B + 1 / T2); //Steinhart-Hart方程 Temp_aver = T1 - 268.15 + 0.5; //转换为摄氏度并校准
- PID控制模块:通过
PID_realize
函数实时计算PWM占空比:pid.Voltage = pid.Kp*pid.Error + pid.Ki * pid.integral + pid.Kd * pid.Differential;
- 执行机构模块:使用PWM信号驱动加热片,
PWM(heat_t)
函数通过占空比调节加热功率 - 人机交互模块:LCD1602显示实时温度,串口输出调试数据
4. 关键代码说明
- PID初始化:设置初始参数并清零历史误差
void PID_Init(float Temp_point, float Temp_aver) {pid.Kp = 60; //比例系数初始值pid.Ki = 0.01; //积分系数初始值 pid.Kd = 100; //微分系数初始值//...其他初始化... }
- 抗积分饱和处理:通过输出限幅避免控制量溢出
if(pid.Voltage > 100) pid.Voltage = 100; else if(pid.Voltage < 0) pid.Voltage = 0;
- PWM生成策略:采用11ms周期(约90Hz),通过延时实现占空比调节
void PWM(char a) {heat = 0; Delay_ms(a); //加热时间heat = 1; Delay_ms(111 - a); //关闭时间 }
5. 参数整定建议
- 整定步骤:
- 先设Ki=0, Kd=0,逐步增大Kp至系统出现等幅振荡
- 取振荡周期Tu,按Ziegler-Nichols法计算参数:
Kp=0.6*Ku, Ki=2Kp/Tu, Kd=Kp*Tu/8
- 现场调试技巧:
- 出现超调时增大Kd或减小Kp
- 稳态误差大时适当增大Ki
- 环境扰动大时增强微分项
6. 优化方向
- 增加积分分离机制:当误差较大时暂停积分项
- 实现变参数PID:针对不同温度区间采用不同参数
- 添加数字滤波:对ADC采样值进行滑动平均滤波
- 引入前馈补偿:针对环境温度变化进行预调节
四、实践结果
将程序烧录进 STC51 单片机中,程序通过PID控制算法,将温度从室温加热至目标温度45℃,并以0.2℃的精度稳定在目标温度附近,并以每秒10次的采样频率将温度数据串口传输至个人PC电脑。温度时间关系如下图所示:
可以观察到,最后温度稳定在 45±0.2℃ 范围内。
实践效果如下图所示:
五、总结与思考
经过实际测试,这套基于STC51的PID温控系统在实验室环境下达到了±0.2℃的控制精度,满足设计目标,相比原始的开关控制已是质的飞跃。有趣的是,当将加热片更换为更大功率的型号时,原有PID参数突然失效——这暴露出控制算法对执行机构特性的强依赖性。
这套系统还有多个优化方向值得探索。比如改用移相PWM技术来降低高频干扰,或者引入模糊控制算法自动调节PID参数。最近尝试将温度数据通过串口导入MATLAB进行离线分析,发现加热过程存在明显的非线性特征——在60℃附近会出现热传导模式的突变。这可能需要设计分段式PID控制器,在不同温区采用差异化的控制策略。