问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

基于STC51单片机的PID温控系统设计详解

创作时间:
作者:
@小白创作中心

基于STC51单片机的PID温控系统设计详解

引用
1
来源
1.
https://www.cnblogs.com/zbyisgudi/p/18695769

本文介绍了一种基于STC51单片机的PID温控系统设计。通过详细讲解PID控制算法的原理、硬件选型和软件编程实现,展示了如何实现高精度的温度控制。

一、需求分析

传统的开关型控制存在一些问题:加热过程是全功率加热,导致三极管发热量大,温度控制振荡幅度大,控制精度较低。而采用PID方法能够更加精确地控制加热片处于目标温度,并在一个较小范围内浮动。

  • 精度要求:±0.2℃ 温控范围
  • 目标温度:45℃
  • 温度工作区间:20℃ - 70℃
  • 温度显示

二、技术设计

1、硬件选型

  • STC51 单片机
  • 最小外围温控系统

2、PID 控制算法

比例控制

比例控制通过根据当前温度和设定温度的差异调节制冷的功率,可以解决半导体制冷量大的问题。但是,这种方法在实际存在损耗时很难达到设定的目标值。

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}] ]

PID 控制中的三项,有时也要根据实际的控制对象进行修改。比例控制,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 三个系数要根据实际控制的变化情况不断修正。虽然 PID 控制技术是个广泛应用的技术,但具体到某个控制对象,其 Kp, Ki, Kd 系数都是要在不断测试中完善,这个过程是一个相对花费时间的过程。

三、编程实现

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 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]);
        }
    }	
}

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. 参数整定建议

  1. 整定步骤
  • 先设Ki=0, Kd=0,逐步增大Kp至系统出现等幅振荡
  • 取振荡周期Tu,按Ziegler-Nichols法计算参数:
Kp=0.6*Ku, Ki=2Kp/Tu, Kd=Kp*Tu/8
  1. 现场调试技巧
  • 出现超调时增大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控制器,在不同温区采用差异化的控制策略。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号