PID 温控设计(基于 STC51)
一、需求分析
开关型控制存在的问题:加热的过程是全功率加热,三极管发热量大,温度控制振荡幅度大,控制精度较低。而通过采用PID方法能够更加精确地控制加热片处于目标温度,并在一个较小范围内浮动。
- 精度要求:±0.2℃ 温控范围
- 目标温度:45℃
- 温度工作区间:20℃ - 70℃
- 温度显示
二、技术设计
1、硬件选型
2、PID 控制算法
比例控制
如果能根据当前温度和设定温度的差异调节制冷的功率,比如选一个比例系数来控制制冷的功率,可解决半导体制冷量大的问题,接近温度设定值时,功率减小,降低温度的振荡,这种方法称为比例控制。
上图列举了一个比例控制的过程,目标值为 1, 设置一个比例系数 Kp = 0.5, 每次的增量都不同,越接近目标值,增量越小,从图中可以看到大约 7 个周期后控制值就非常接近设定值,而且后面非常稳定。比例系数 Kp 的大小决定了达到设定值的快慢,从图中也可以看到,当设定值为 1时, Kp 的值不能设置的太大,所以 Kp 的选取要根据实际控制对象来定。若 Kp 选取的值较小,达到控制设定值的时间就会比较长,如下图所示:
比例控制在实际存在损耗时很难达到设定的目标值,如果通过提高目标设定值来达到实际控制值,在损耗值较小的情况下可能会超过实际的设定值,如下图所示,故该种方案在实际应用中是不太会采用的。
PID 控制技术
PID 指的是 Proportion-Integral-Differential,即比例-积分-微分。用公式可表示为任一时刻的控制量要综合考虑比例因子,累计误差的积分,以及该时刻的微分变化量。
\[u(t) = K_p [e(t) + \frac{1}{T_I} \int_0^{t} {e(t)}dt + T_D \frac{de(t)}{dt}]\]
从公式可以看出,控制值包含三项,第一项为 比例项 \(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
- #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 45
- sbit 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]);
- }
- }
- }
复制代码 PWM生成策略:采用11ms周期(约90Hz),通过延时实现占空比调节- pid.Voltage = Kp * Error + Ki * integral + Kd * (Error - Error_last)
复制代码 </ul>5. 参数整定建议
- 整定步骤:
- 先设Ki=0, Kd=0,逐步增大Kp至系统出现等幅振荡
- 取振荡周期Tu,按Ziegler-Nichols法计算参数:
- Rt = res0 / (Vcc - res0) * Rp; //计算热敏电阻阻值
- T1 = 1 / (log(Rt / Rp) / B + 1 / T2); //Steinhart-Hart方程
- Temp_aver = T1 - 268.15 + 0.5; //转换为摄氏度并校准
复制代码
- 现场调试技巧:
- 出现超调时增大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控制器,在不同温区采用差异化的控制策略。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |