去年夏天工作室的3D打印机频繁出现热端温度波动问题,导致打印质量下降。为了解决这个问题,我决定自己动手设计一个基于单片机的PID温控风扇系统。这个项目不仅解决了我的实际问题,还让我深入理解了PID控制在温度调节中的应用。
温控风扇在电子设备散热、工业控制等领域应用广泛。传统开关式温控存在温度波动大、响应慢的问题,而PID控制能实现更精准的温度调节。这个设计使用常见的STM32单片机,配合DS18B20温度传感器和PWM调速风扇,构建了一个完整的闭环控制系统。
主控芯片选择了STM32F103C8T6,这款ARM Cortex-M3内核的单片机性价比极高,具有丰富的外设资源。实测在72MHz主频下运行PID算法完全够用,而且开发环境成熟,资料丰富。
温度传感器选用DS18B20数字温度传感器,它的优势在于:
风扇驱动部分采用常见的4线PWM风扇,通过MOSFET(我用的IRLZ44N)进行PWM调速控制。这种方案比三极管驱动效率更高,发热更小。
电源部分需要特别注意:
我在设计中使用了AMS1117-3.3稳压芯片为单片机供电,风扇直接接12V电源。DS18B20通过4.7kΩ上拉电阻连接到单片机IO口。
重要提示:PWM驱动电路一定要加续流二极管(我用的是1N4148),否则MOSFET关断时产生的反电动势可能损坏元件。
PID控制的核心是三个参数的调节:
在代码中,我采用了位置式PID算法:
c复制typedef struct {
float Kp, Ki, Kd;
float integral;
float prev_error;
} PID_Controller;
float PID_Update(PID_Controller* pid, float setpoint, float measurement) {
float error = setpoint - measurement;
pid->integral += error;
if(pid->integral > INTEGRAL_LIMIT) pid->integral = INTEGRAL_LIMIT;
if(pid->integral < -INTEGRAL_LIMIT) pid->integral = -INTEGRAL_LIMIT;
float derivative = error - pid->prev_error;
pid->prev_error = error;
return pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;
}
DS18B20的温度读取需要严格的时序控制。我使用了STM32的硬件定时器来确保时序精度:
c复制void DS18B20_StartConversion(void) {
DS18B20_Reset();
DS18B20_WriteByte(0xCC); // Skip ROM
DS18B20_WriteByte(0x44); // Convert T
}
float DS18B20_ReadTemp(void) {
uint8_t tempL, tempH;
int16_t temp;
DS18B20_Reset();
DS18B20_WriteByte(0xCC); // Skip ROM
DS18B20_WriteByte(0xBE); // Read Scratchpad
tempL = DS18B20_ReadByte();
tempH = DS18B20_ReadByte();
temp = (tempH << 8) | tempL;
return temp / 16.0f;
}
STM32的定时器可以方便地生成PWM信号。我配置TIM3_CH2输出PWM,频率25kHz(人耳听不到噪音):
c复制void PWM_Init(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
// 72MHz/72 = 1MHz, 1MHz/40 = 25kHz
TIM_TimeBaseStruct.TIM_Prescaler = 71;
TIM_TimeBaseStruct.TIM_Period = 39;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStruct);
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_Pulse = 0;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM3, &TIM_OCInitStruct);
TIM_Cmd(TIM3, ENABLE);
}
void PWM_SetDuty(uint8_t duty) {
if(duty > 100) duty = 100;
TIM3->CCR2 = (TIM3->ARR+1) * duty / 100;
}
我采用经典的Ziegler-Nichols方法进行参数整定:
在我的系统中,最终确定的参数为:
长时间的温度偏差会导致积分项过大,我采用了两种方法防止积分饱和:
c复制if(fabs(error) > ERROR_THRESHOLD) {
pid->integral = 0; // 重置积分项
}
DS18B20的测量值会有微小波动,我采用了一阶低通滤波:
c复制float filtered_temp = 0.8 * filtered_temp + 0.2 * new_temp;
滤波系数需要根据具体应用调整,系数越大滤波效果越强,但响应也会变慢。
在设定温度为50℃时,系统表现如下:
测试中发现,风扇的PWM频率对噪音影响很大。低于20kHz会有可闻噪音,最终选择25kHz既保证了静音,又不会对MOSFET开关造成太大压力。
模拟温度突变情况:
降温较慢是因为只能依靠自然散热和风扇气流,没有主动制冷装置。在实际应用中,可以根据需要增加散热片面积或提高最大风扇转速。
测试了三种负载情况:
这表明PID参数需要根据负载特性进行调整,或者实现参数自整定功能。
可能原因及解决方法:
排查步骤:
调整建议:
我的经验是,先从较小的Kp值开始,逐步增加直到出现轻微振荡,然后加入微分项抑制振荡,最后调整积分项消除稳态误差。
这个设计已经成功应用于:
在3D打印机应用中,我将设定温度存储在EEPROM中,通过旋转编码器调整温度值,配合OLED显示屏,形成了一个完整的温控解决方案。