温度控制在工业自动化、医疗设备、家用电器等领域有着广泛应用。作为一名嵌入式开发者,我经常需要实现高精度的温度控制方案。今天要分享的是基于STM32的PID温度控制系统,这是一个典型的闭环控制案例,通过DS18B20采集环境温度,经过PID算法处理后输出PWM信号控制加热片功率。
这个项目最吸引我的地方在于它完美结合了控制理论和嵌入式实践。PID算法作为控制领域的经典方法,在温度控制这种大惯性系统中表现尤为出色。我们将使用STM32F103C8T6这款性价比极高的Cortex-M3内核单片机作为主控,配合单总线数字温度传感器DS18B20和PWM驱动加热模块,构建完整的硬件闭环。
PID控制器由比例(P)、积分(I)、微分(D)三个环节组成,其连续时间域的数学表达式为:
u(t) = K_p·e(t) + K_i∫e(t)dt + K_d·de(t)/dt
其中:
在实际嵌入式系统中,我们需要使用离散化的形式。以位置式PID为例,其离散表达式为:
u(k) = K_p·e(k) + K_i·T·∑e(n) + K_d·[e(k)-e(k-1)]/T
这里T是采样周期,k表示第k次采样。这个公式直接对应了我们后续要实现的代码逻辑。
比例环节对当前偏差做出即时响应,偏差越大输出越强。在实际温度控制中:
调试技巧:先设置Ki=0,Kd=0,逐渐增大Kp直到系统开始振荡,然后回调至振荡临界点。
积分环节累积历史偏差,专门消除静态误差(静差)。在温度控制中:
经验法则:Ki值通常设为Kp的1/10~1/20,并根据实际效果微调。
微分环节预测偏差变化趋势,抑制超调。但在温度控制中:
提示:对于初学者,建议先掌握PI控制,待系统稳定后再尝试加入微分环节。
c复制void System_Init(void)
{
RCC_Configuration(); // 时钟配置
GPIO_Configuration(); // GPIO初始化
NVIC_Configuration(); // 中断配置
TIM3_PWM_Init(999, 71); // PWM初始化(1kHz)
DS18B20_Init(); // 温度传感器初始化
USART1_Init(115200); // 调试串口初始化
}
c复制typedef struct {
float SetTemp; // 目标温度
float ActualTemp; // 实际温度
float Err; // 当前偏差
float ErrLast; // 上次偏差
float Kp, Ki, Kd; // PID参数
float Integral; // 积分项
float Output; // 输出值
} PID_TypeDef;
void PID_Calculate(PID_TypeDef *pid)
{
// 读取当前温度(带滤波)
pid->ActualTemp = Temp_Filter();
// 计算偏差
pid->Err = pid->SetTemp - pid->ActualTemp;
// 比例项
float Pout = pid->Kp * pid->Err;
// 积分项(带抗饱和)
pid->Integral += pid->Err;
if(pid->Integral > 500) pid->Integral = 500;
if(pid->Integral < -500) pid->Integral = -500;
float Iout = pid->Ki * pid->Integral;
// 微分项(温度系统通常省略)
float Dout = pid->Kd * (pid->Err - pid->ErrLast);
// 计算总输出
pid->Output = Pout + Iout + Dout;
// 输出限幅
if(pid->Output > 100) pid->Output = 100;
if(pid->Output < 0) pid->Output = 0;
// 更新历史偏差
pid->ErrLast = pid->Err;
// 输出PWM
PWM_SetDuty(pid->Output);
}
c复制#define FILTER_LEN 5
float Temp_Filter(void)
{
static float temp_buf[FILTER_LEN];
static uint8_t index = 0;
float sum = 0;
// 采集新数据
temp_buf[index] = DS18B20_ReadTemp();
index = (index + 1) % FILTER_LEN;
// 滑动平均
for(uint8_t i=0; i<FILTER_LEN; i++) {
sum += temp_buf[i];
}
return sum / FILTER_LEN;
}
初始化参数:
调节比例系数Kp:
调节积分系数Ki:
微调阶段:
| 加热功率 | Kp范围 | Ki范围 | 采样周期 |
|---|---|---|---|
| 10W | 2.0-5.0 | 0.05-0.2 | 100ms |
| 30W | 1.5-3.0 | 0.03-0.1 | 200ms |
| 50W | 1.0-2.0 | 0.01-0.05 | 300ms |
根据温度偏差大小动态调整PID参数:
c复制if(fabs(pid->Err) > 10.0) {
// 大偏差区间
pid->Kp = 5.0;
pid->Ki = 0.1;
} else {
// 小偏差区间
pid->Kp = 2.0;
pid->Ki = 0.05;
}
添加参数实时调整功能:
c复制void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE)) {
char cmd = USART_ReceiveData(USART1);
switch(cmd) {
case 'p': pid.Kp += 0.1; break;
case 'P': pid.Kp -= 0.1; break;
// 其他参数调整...
}
}
}
通过串口输出温度数据,用PC端工具绘制曲线:
c复制void Send_TempData(float temp)
{
printf("TEMP:%.1f\n", temp);
}
我在实际项目中发现,温度控制系统的性能很大程度上取决于传感器安装位置。最好将DS18B20固定在加热元件附近,但又不能直接接触,通常保持2-3mm间距并用导热硅胶固定最为理想。另外,对于大功率加热系统,建议采用固态继电器替代三极管,可以提高系统可靠性。