程序代码篇---PID简介
P:比例
含义:输出量的大小与输入误差信号大小成比例关系的一种(类似一元一次函数)控制
结果:将输出量稳定在目标值附近
弊端:存在残差(仅仅使用比例控制无法将残差消除到零)
简单介绍:
P调节也可以叫做偏差调节,通过与目标之间的偏差控制大小
注意实际应用中需要乘以比例参数KP,原因:实际应用中输入和输出的单位有时差很多
I:积分
含义:输出量的大小与输入误差信号的积分值成比例关系的一种控制
结果:消除残差
简单介绍:
I调节也可以叫做累计偏差调节,一直达不到目标(由于部分原因导致系统存在影响目标结果的输出,及可能存在静态偏差),这个时候考虑利用偏差,及将所有偏差累积起来乘以比例系数KI,注意比例系数尽量取小(正因为小,所有偏差累加起来就会变大,但不至于过大),消除静态误差
D:微分
含义:输出量的大小与输入误差信号的微分值成比例关系的一种控制
结果:在系统稳定后,如果存在干扰信号,使系统在最短的时间内回到稳定状态。
简单介绍:
D调节也可以叫做近期偏差调节。
---
相比PID的闭环控制,还存在没有反馈(一股脑向着目标冲击)的开环控制。
PID的衍生:串级PID、前馈PID
---
无PID控制:
#include <OneWire.h>
#include <DallasTemperature.h>// 定义温度传感器和加热器控制引脚
#define ONE_WIRE_BUS 4 // DS18B20温度传感器连接的GPIO引脚
#define HEATER_PIN 5 // 加热器控制引脚(连接继电器模块)// 温度控制参数
#define TARGET_TEMP 33.0 // 目标温度: 33摄氏度
#define HYSTERESIS 0.5 // 滞后值,防止继电器频繁切换// 初始化OneWire和DallasTemperature库
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);float currentTemp = 0.0; // 当前温度
bool heaterState = false; // 加热器状态void setup() {// 初始化串口通信Serial.begin(115200);// 初始化温度传感器sensors.begin();// 初始化加热器控制引脚pinMode(HEATER_PIN, OUTPUT);digitalWrite(HEATER_PIN, LOW); // 初始关闭加热器Serial.println("温度控制系统启动中...");Serial.print("目标温度: ");Serial.print(TARGET_TEMP);Serial.println(" °C");
}void loop() {// 读取温度currentTemp = readTemperature();// 打印当前温度Serial.print("当前温度: ");Serial.print(currentTemp);Serial.println(" °C");// 温度控制逻辑temperatureControl();// 每2秒读取一次温度delay(2000);
}// 读取温度传感器数据
float readTemperature() {sensors.requestTemperatures(); // 发送温度转换命令return sensors.getTempCByIndex(0); // 获取第一个传感器的温度(摄氏度)
}// 温度控制逻辑
void temperatureControl() {// 如果当前温度低于目标温度减去滞后值,打开加热器if (currentTemp < (TARGET_TEMP - HYSTERESIS) && !heaterState) {digitalWrite(HEATER_PIN, HIGH);heaterState = true;Serial.println("加热器已开启");}// 如果当前温度高于目标温度,关闭加热器else if (currentTemp > TARGET_TEMP && heaterState) {digitalWrite(HEATER_PIN, LOW);heaterState = false;Serial.println("加热器已关闭");}
}
代码说明:
硬件需求:
- ESP32 开发板
- DS18B20 温度传感器
- 继电器模块(用于控制加热器)
- 加热器设备
工作原理:
- 使用 DS18B20 数字温度传感器采集温度数据
- 通过继电器模块控制加热器的开关状态
- 采用带滞后的开关控制策略,避免继电器频繁切换
控制逻辑:
- 当温度低于 32.5°C(33°C - 0.5°C)时,开启加热器
- 当温度达到或超过 33°C 时,关闭加热器
- 0.5°C 的滞后值用于防止继电器在目标温度附近频繁开关
引脚定义:
- DS18B20 连接到 GPIO4
- 继电器模块连接到 GPIO5
- 可根据实际接线情况修改引脚定义
使用时,确保已安装 OneWire 和 DallasTemperature 库,这两个库可以通过 Arduino 库管理器搜索安装。根据实际硬件连接情况,可能需要调整引脚定义。
有PID控制:
#include <OneWire.h>
#include <DallasTemperature.h>
#include <PID_v1.h>// 定义引脚
#define ONE_WIRE_BUS 4 // DS18B20温度传感器连接的GPIO引脚
#define HEATER_PIN 5 // 加热器控制引脚(连接继电器)// 温度控制参数
#define TARGET_TEMP 33.0 // 目标温度: 33摄氏度
#define CONTROL_PERIOD 5000 // 控制周期(毫秒),例如5秒// 初始化传感器
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);// PID参数
double Setpoint, Input, Output;
// 根据实际系统调整这些参数
double Kp = 50.0, Ki = 0.2, Kd = 10.0;// 创建PID对象
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);float currentTemp = 0.0;
unsigned long periodStartTime = 0;
bool heaterState = false;
unsigned long heaterOnTime = 0;void setup() {Serial.begin(115200);// 初始化传感器sensors.begin();// 初始化加热器引脚pinMode(HEATER_PIN, OUTPUT);digitalWrite(HEATER_PIN, LOW);// PID初始化Setpoint = TARGET_TEMP;myPID.SetOutputLimits(0, 100); // 输出限制在0-100%(占空比)myPID.SetMode(AUTOMATIC);periodStartTime = millis();Serial.println("开关型加热器PID温度控制系统启动");Serial.print("目标温度: ");Serial.print(TARGET_TEMP);Serial.println(" °C");Serial.print("控制周期: ");Serial.print(CONTROL_PERIOD/1000);Serial.println(" 秒");
}void loop() {unsigned long currentMillis = millis();// 读取温度(每1秒读取一次)static unsigned long lastReadTime = 0;if (currentMillis - lastReadTime >= 1000) {lastReadTime = currentMillis;currentTemp = readTemperature();Input = currentTemp;myPID.Compute(); // 计算PID输出}// 控制周期内的加热器控制unsigned long elapsedTime = currentMillis - periodStartTime;// 计算本周期内应该开启的时间(毫秒)heaterOnTime = (Output / 100.0) * CONTROL_PERIOD;// 控制逻辑if (elapsedTime < heaterOnTime && !heaterState) {// 需要开启加热器digitalWrite(HEATER_PIN, HIGH);heaterState = true;} else if (elapsedTime >= heaterOnTime && heaterState) {// 需要关闭加热器digitalWrite(HEATER_PIN, LOW);heaterState = false;}// 控制周期结束,重置计时if (elapsedTime >= CONTROL_PERIOD) {periodStartTime = currentMillis;printStatus(); // 打印本周期状态}
}// 读取温度传感器数据
float readTemperature() {sensors.requestTemperatures();return sensors.getTempCByIndex(0);
}// 打印状态信息
void printStatus() {Serial.print("当前温度: ");Serial.print(currentTemp);Serial.print(" °C, 目标: ");Serial.print(Setpoint);Serial.print(" °C, 输出占空比: ");Serial.print(Output);Serial.print("%, 加热时间: ");Serial.print(heaterOnTime/1000.0);Serial.println(" 秒");
}
代码说明:
工作原理:
- 采用固定控制周期(例如 5 秒),在每个周期内根据 PID 计算的输出值决定加热器开启的时间比例
- 例如,若 PID 输出为 60%,控制周期为 5 秒,则加热器在本周期内开启 3 秒,关闭 2 秒
- 通过这种方式,实现了对加热功率的近似比例控制
参数调整建议:
- CONTROL_PERIOD:控制周期,太短会导致继电器频繁动作,太长会导致控制精度下降,建议 5-10 秒
- Kp:比例系数,初始可设为 50,根据响应调整
- Ki:积分系数,初始可设为 0.1-0.5
- Kd:微分系数,初始可设为 5-20
调试方法:
- 先将系统加热到接近目标温度
- 观察温度曲线,若震荡严重则减小 Kp
- 若温度稳定后与目标有偏差则增大 Ki
- 若响应太慢可适当增大 Kp 或减小 Kd
这种控制方式兼顾了开关型加热器的特性和 PID 控制的优势,既能避免继电器过于频繁地切换,又能实现比简单开关控制更精确的温度调节。
核心算法解析
PID 控制公式:
代码中实现的核心公式是:输出 = Kp×误差 + Ki×∫误差dt - Kd×(输入变化率)
其中用输入变化率代替了误差变化率,这是为了避免设定值突变时产生的微分冲击。
关键技术点:
- 积分限幅:防止积分项累积过大导致的 "积分饱和" 现象
- 输出限幅:确保输出在执行器的有效范围内
- 时间处理:自动计算采样周期,适应不同调用频率
- 方向控制:支持正作用和反作用控制,适应不同类型系统
使用流程:
- 创建 PID 对象并指定输入、输出、设定值指针
- 设置 PID 参数 (Kp, Ki, Kd)
- 设置输出范围和控制方向
- 切换到自动模式
- 定期调用 Compute () 方法进行计算
这个实现是工业界广泛使用的 PID 控制算法的标准实现,包含了所有必要的功能和保护机制,适用于温度控制、电机速度控制等多种场景。