PID 温控设计(基于 STC51)

news/2025/1/31 15:27:02/文章来源:https://www.cnblogs.com/zbyisgudi/p/18695769

PID 温控设计(基于 STC51)

一、需求分析

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

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

二、技术设计

1、硬件选型

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

2、PID 控制算法

比例控制

如果能根据当前温度和设定温度的差异调节制冷的功率,比如选一个比例系数来控制制冷的功率,可解决半导体制冷量大的问题,接近温度设定值时,功率减小,降低温度的振荡,这种方法称为比例控制。

图1 比例控制示意图

上图列举了一个比例控制的过程,目标值为 1, 设置一个比例系数 Kp = 0.5, 每次的增量都不同,越接近目标值,增量越小,从图中可以看到大约 7 个周期后控制值就非常接近设定值,而且后面非常稳定。比例系数 Kp 的大小决定了达到设定值的快慢,从图中也可以看到,当设定值为 1时, Kp 的值不能设置的太大,所以 Kp 的选取要根据实际控制对象来定。若 Kp 选取的值较小,达到控制设定值的时间就会比较长,如下图所示:

图2 理想情况下不同比例系数的控制效果

比例控制在实际存在损耗时很难达到设定的目标值,如果通过提高目标设定值来达到实际控制值,在损耗值较小的情况下可能会超过实际的设定值,如下图所示,故该种方案在实际应用中是不太会采用的。

图3 有损耗情况下比例控制效果

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, KiKd 系数对制冷速度的影响不大,但对最终控制精度有影响,可以根据控制的结果,不断优化 KiKd 的值。

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

  1. 整定步骤
    • 先设Ki=0, Kd=0,逐步增大Kp至系统出现等幅振荡
    • 取振荡周期Tu,按Ziegler-Nichols法计算参数:
      Kp=0.6*Ku, Ki=2Kp/Tu, Kd=Kp*Tu/8
      
  2. 现场调试技巧
    • 出现超调时增大Kd或减小Kp
    • 稳态误差大时适当增大Ki
    • 环境扰动大时增强微分项

6. 优化方向

  • 增加积分分离机制:当误差较大时暂停积分项
  • 实现变参数PID:针对不同温度区间采用不同参数
  • 添加数字滤波:对ADC采样值进行滑动平均滤波
  • 引入前馈补偿:针对环境温度变化进行预调节

四、实践结果

将程序烧录进 STC51 单片机中,程序通过PID控制算法,将温度从室温加热至目标温度45℃,并以0.2℃的精度稳定在目标温度附近,并以每秒10次的采样频率将温度数据串口传输至个人PC电脑。温度时间关系如下图所示:

PID温控系统温度时间曲线图

PID温控系统温度时间曲线局部图

可以观察到,最后温度稳定在 45±0.2℃ 范围内。

实践效果如下图所示:

实践效果图

五、总结与思考

经过实际测试,这套基于STC51的PID温控系统在实验室环境下达到了±0.2℃的控制精度,满足设计目标,相比原始的开关控制已是质的飞跃。有趣的是,当将加热片更换为更大功率的型号时,原有PID参数突然失效——这暴露出控制算法对执行机构特性的强依赖性。

这套系统还有多个优化方向值得探索。比如改用移相PWM技术来降低高频干扰,或者引入模糊控制算法自动调节PID参数。最近尝试将温度数据通过串口导入MATLAB进行离线分析,发现加热过程存在明显的非线性特征——在60℃附近会出现热传导模式的突变。这可能需要设计分段式PID控制器,在不同温区采用差异化的控制策略。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/877285.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

gin: 接收参数时校验

一,安装第三方库: $ go get -u github.com/go-playground/validator/v10 go: downloading github.com/go-playground/validator/v10 v10.24.0 go: downloading github.com/gabriel-vasile/mimetype v1.4.8 go: downloading golang.org/x/crypto v0.32.0 go: downloading golan…

Java异常分类及处理

Throwable 是 Java 语言中所有错误或异常的超类,在 Java 中只有 Throwable 类型的实例才可以被抛出(throw)或者捕获(catch),它是异常处理机制的基本组成类型。实例分为 Error 和 Exception 两种。 其中,AWTError GUI图形界面化编程相关异常。Error(错误)是程序无法处理…

Apple Safari 18.3 - macOS 专属浏览器 (独立安装包下载)

Apple Safari 18.3 - macOS 专属浏览器 (独立安装包下载)Apple Safari 18.3 - macOS 专属浏览器 (独立安装包下载) 适用于 macOS Sonoma 和 macOS Ventura 的 Safari 浏览器 18 请访问原文链接:https://sysin.org/blog/apple-safari-18/ 查看最新版。原创作品,转载请保留出处…

AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试

AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试AppSpider Pro 7.5.015 for Windows - Web 应用程序安全测试 Rapid7 Dynamic Application Security Testing (DAST) released Jan 30, 2025 请访问原文链接:https://sysin.org/blog/appspider/ 查看最新版。原创作品…

Nexpose 7.4.0 for Linux Windows - 漏洞扫描

Nexpose 7.4.0 for Linux & Windows - 漏洞扫描Nexpose 7.4.0 for Linux & Windows - 漏洞扫描 Rapid7 on-prem Vulnerability Management, released Jan 30, 2025 请访问原文链接:https://sysin.org/blog/nexpose-7/ 查看最新版。原创作品,转载请保留出处。 作者主页…

kuboard作为k8s的dashboard使用

kuboard作为k8s的dashboard使用 1 资源信息主机名 OS IP CPU/MEMERY/DISK 备注k8s-master Rocky-8.10 172.16.1.30 2core/4GiB/100GiB k8s-v1.28.0k8s-node01 Rocky-8.10 172.16.1.31 4core/8GiB/100GiB k8s-v1.28.0k8s-node02 Rocky-8.10 172.16.1.32 4core/8GiB/100GiB k8s-v…

Python GIL(全局解释器锁)机制对多线程性能影响的深度分析

在Python开发领域,GIL(Global Interpreter Lock)一直是一个广受关注的技术话题。在3.13已经默认将GIL去除,在详细介绍3.13的更亲前,我们先要留了解GIL的技术本质、其对Python程序性能的影响。本文将主要基于CPython(用C语言实现的Python解释器,也是目前应用最广泛的Pyth…

java中的ArrayBlockingQueue

ArrayBlockingQueue ArrayBlockingQueue 是 Java 并发包 (java.util.concurrent) 中的一个线程安全的阻塞队列实现。 它基于数组实现,容量固定,支持先进先出的顺序。 Array Blocking Queue 数组阻塞队列 读音: [əˈreɪ] [ˈblɒkɪŋ] [kjuː] concurrent 同时发生的 /…

五. Redis 配置内容(详细配置说明)

五. Redis 配置内容(详细配置说明) @目录五. Redis 配置内容(详细配置说明)1. Units 单位配置2. INCLUDES (包含)配置3. NETWORK (网络)配置3.1 bind(配置访问内容)3.2 protected-mode (保护模式)3.3 port(端口)配置3.4 timeout(客户端超时时间)配置3.5 tcp-keepalive()配置4…