风扇调速总振荡?90% 工程师没搞懂这层 PID 逻辑
2026/5/7 8:02:36 网站建设 项目流程

之前收到了一位粉丝朋友的问题,是需要了解温度相关的PID控制,主要是关于PID根据温度进行风扇转速的调节。针对这个粉丝的问题,我觉得也是比较感兴趣的,加上自己也是研究PID控制这块,所以也花了一些时间去查阅了相关的资料,加上自己的思考写了这一篇文章,有不正确的地方,希望大家可以多理解和进行指正。

同样是控温,为什么他的风扇又静又能散热?

在台式机/服务器的风扇调速系统中,PID闭环控制的核心是以硬件温度为反馈信号,通过算法动态调整风扇PWM占空比,最终将硬件温度稳定在目标区间。而温度采样值到风扇电机占空比的映射,是连接 “温度检测” 与 “转速控制” 的核心链路,分为偏差计算→PID运算→控制量映射→PWM 驱动四个关键步骤。

一、 PID闭环控制的核心原理

闭环控制的本质是 “检测偏差 - 消除偏差” 的循环反馈过程 ,相比于开环控制(如固定占空比调速),它能根据硬件实时温度动态调整,兼顾散热效果、静音性和风扇寿命。

1. 闭环控制的基本组成

一个完整的PID闭环温控系统包含 5 个核心环节,形成闭环反馈回路

目标温度设定 → 温度采样 → 偏差计算 → PID 算法运算 → PWM 占空比输出 → 风扇转速调整 → 硬件温度变化 → 温度采样(循环)

各环节的具体作用:

目标温度设定:根据硬件安全手册设定阈值(如 CPU 目标温度 70℃,GPU 目标温度 75℃),是控制的 “基准值”。

温度采样:通过硬件内置传感器(如 CPU 的 DTS 数字温度传感器、主板的 NTC 热敏电阻)采集实时温度,是反馈信号。

偏差计算:计算实时温度与目标温度的差值,即

若 e(k)>0,说明温度低于目标,可降低风扇转速;若 e(k)<0,说明温度超标,需提高转速。

PID算法运算:根据偏差的大小、累积趋势、变化速率,计算出对应的控制量,是闭环控制的 “大脑”。

PWM 占空比输出:将PID计算的控制量转化为风扇电机的驱动信号,最终改变转速。

2. PID 三环节的作用(比例 P + 积分 I + 微分 D)

PID 算法通过三个独立环节的协同,实现 “快速响应、消除稳态误差、抑制超调” 的目标,离散化后的实用公式为:

环节

核心作用

对温控的影响

比例环节(P)

按偏差大小即时调整控制量,偏差越大,输出控制量越大

决定系统的响应速度。Kp 越大,温度超标时风扇转速提升越快,但过大易导致转速频繁波动(振荡)

积分环节(I)

对偏差累积求和,消除 “静态误差”(如温度长期略高于目标的情况)

解决 “比例控制无法完全达标的问题”。例如 CPU 长期 72℃(目标 70℃),积分累积偏差会缓慢提高占空比,直至温度达标

微分环节(D)

计算偏差的变化速率,预判温度趋势,提前调整

抑制超调。例如 CPU 温度从 60℃ 快速升至 75℃(偏差变化率大),微分环节会瞬间增大控制量,提前拉高转速,避免温度持续飙升

3. 闭环控制的优势

  • 抗干扰性强

    环境温度变化、硬件负载波动(如CPU从空闲到满载)时,系统能自动调整转速,无需人工干预。

  • 稳定性高

    温度不会出现 “忽高忽低” 的情况,始终稳定在目标区间。

  • 兼顾静音与散热

    低负载低温时风扇低速静音,高负载高温时风扇高速散热,避免 “一直满转” 的能源浪费。

二、 温度采样到风扇占空比的映射关系

映射关系的核心是将 PID 输出的 “抽象控制量 u(k)”,转化为风扇电机可识别的 “PWM 占空比”,分为3 个关键步骤,且每个步骤都需考虑硬件的实际约束。

步骤 1:PID 控制量的限幅处理

PID 计算出的控制量 u(k) 是一个无单位的抽象值,可能超出风扇的实际可控范围,因此第一步要钳位控制量的上下限

  • Umax

    对应风扇满转速的最大控制量(如占空比 100%)。

  • Umin

    对应风扇最低启动转速的控制量(如占空比 15%,低于此值风扇可能停转或抖动)。

举例:若PID计算出u(k)=120(超出最大可控值100),则钳位为100;若 u(k)=5(低于最小可控值 15),则钳位为15。

步骤 2:控制量到 PWM 占空比的线性 / 分段映射

限幅后的控制量u限幅(k)需与PWM占空比(0%~100%)建立一一对应的关系,常用两种映射方式:

线性映射(适用于中速区间)

若控制量的有效范围与占空比范围一致(如u限幅(k)∈[15,100]对应占空比 D∈[15%,100%]),可直接线性对应:

特点:简单易实现,适用于温度在目标区间附近的稳态场景。

分段映射(适用于全温度区间,更实用)

风扇的转速-占空比特性是非线性的:低速时占空比变化对转速影响大(如 15%→20%转速提升明显),高速时占空比变化对转速影响小(如 80%→85% 转速提升微弱)。因此实际系统会分三段映射:

步骤 3:占空比到风扇转速的最终转换

直流风扇电机的转速与 PWM 占空比的关系为:占空比决定电机的平均输入电压,平均电压越高,转速越快,近似满足线性关系:

  • n:风扇实际转速

  • nmax:风扇满占空比时的额定转速(如 2000 RPM)
  • Dmin:风扇启动的最小占空比(如 15%)

举例:某风扇nmax=2000RPM,Dmin=15%。当占空比D=50%时,转速n=2000⋅(50-15)/(100-15)≈823RPM;当D=100%时,转速 n=2000 RPM。

PID 参数整定表

硬件类型

比例系数 Kp

积分系数 Ki

微分系数 Kd

参数特点说明

CPU

8~12

0.2~0.5

3~5

CPU 热惯性小、温度波动快,Kp 适中保证响应速度;Kd 略高抑制突发负载(如跑分、编译)的温度超调

GPU

10~15

0.3~0.6

4~6

GPU 高负载时功耗波动大(如游戏、渲染),Kp 和 Kd 略高于 CPU,应对瞬时高温冲击

内存

5~8

0.1~0.3

1~2

内存发热平缓,Kp偏低避免风扇频繁调速;Ki小,减少积分饱和风险

参数调整原则

  1. 若温度持续超调(如 CPU 目标 70℃,实际稳定在 75℃)→ 增大 Ki(每次 +0.1)。

  2. 若风扇转速频繁振荡(如占空比在 40%~60% 来回跳)→ 减小 Kp(每次 -1),或增大 Kd(每次 +1)。

  3. 若温度上升时响应慢(如 CPU 从 70℃ 升至 85℃ 才开始提速)→ 增大 Kp(每次 +1)。

  4. 参考代码

    #include <stdint.h>#include <math.h>// 硬件温度目标值与安全阈值(可根据硬件调整)#define TARGET_CPU 70.0f // CPU目标温度(℃)#define TARGET_GPU 75.0f // GPU目标温度(℃)#define TARGET_MEM 45.0f // 内存目标温度(℃)#define MAX_CPU_TEMP 90.0f // CPU安全上限#define MAX_GPU_TEMP 95.0f // GPU安全上限#define MAX_MEM_TEMP 80.0f // 内存安全上限// PID参数(对应整定表)#define KP_CPU 10.0f // CPU比例系数#define KI_CPU 0.3f // CPU积分系数#define KD_CPU 4.0f // CPU微分系数#define KP_GPU 12.0f // GPU比例系数#define KI_GPU 0.4f // GPU积分系数#define KD_GPU 5.0f // GPU微分系数#define KP_MEM 6.0f // 内存比例系数#define KI_MEM 0.2f // 内存积分系数#define KD_MEM 1.5f // 内存微分系数// 占空比约束#define MIN_DUTY 15.0f // 最小占空比(%)#define MAX_DUTY 95.0f // 最大占空比(%)#define DEAD_ZONE 2.0f // 温度死区(±2℃)// PID控制器结构体typedef struct {float kp, ki, kd; // PID系数float target; // 目标值float error; // 当前偏差float last_error; // 上一次偏差float integral; // 积分项float integral_limit; // 积分限幅(防止饱和)float output; // PID输出控制量} PID_Controller;// 全局PID控制器实例PID_Controller pid_cpu = {KP_CPU, KI_CPU, KD_CPU, TARGET_CPU, 0, 0, 0, 50.0f, 0};PID_Controller pid_gpu = {KP_GPU, KI_GPU, KD_GPU, TARGET_GPU, 0, 0, 0, 50.0f, 0};PID_Controller pid_mem = {KP_MEM, KI_MEM, KD_MEM, TARGET_MEM, 0, 0, 0, 50.0f, 0};// 函数声明float PID_Calculate(PID_Controller *pid, float current_value);float Map_ControlToDuty_CPU_GPU(float control);float Map_ControlToDuty_MEM(float control);float Get_WeightedDuty(float duty_cpu, float duty_gpu, float duty_mem);float Limit_Value(float value, float min, float max);/*** @brief PID计算核心函数* @param pid PID控制器实例* @param current_value 当前采样温度* @return PID输出控制量*/float PID_Calculate(PID_Controller *pid, float current_value) {// 计算偏差(目标-当前)pid->error = pid->target - current_value;// 温度死区:偏差在±2℃内,输出保持不变if (fabs(pid->error) < DEAD_ZONE) {return pid->output;}// 比例项float p_term = pid->kp * pid->error;// 积分项(限幅防止饱和)pid->integral += pid->ki * pid->error;pid->integral = Limit_Value(pid->integral, -pid->integral_limit, pid->integral_limit);float i_term = pid->integral;// 微分项float d_term = pid->kd * (pid->error - pid->last_error);pid->last_error = pid->error;// 总输出控制量(限幅)pid->output = Limit_Value(p_term + i_term + d_term, 0, 100);return pid->output;}/*** @brief CPU/GPU控制量→占空比分段映射* @param control PID输出控制量(0~100)* @return 最终占空比(%)*/float Map_ControlToDuty_CPU_GPU(float control) {float duty;if (control <= 30) {// 低温段:低增益映射 D = u(k) - 10duty = control - 10;} else if (control <= 80) {// 中温段:线性映射 D = u(k)duty = control;} else {// 高温段:高增益映射 D = 0.8*u(k) + 20duty = 0.8 * control + 20;}// 占空比最终限幅return Limit_Value(duty, MIN_DUTY, MAX_DUTY);}/*** @brief 内存控制量→占空比低增益映射* @param control PID输出控制量(0~100)* @return 最终占空比(%)*/float Map_ControlToDuty_MEM(float control) {float duty = 0.7 * control + 4.5;return Limit_Value(duty, MIN_DUTY, MAX_DUTY);}/*** @brief 多硬件占空比加权融合* @param duty_cpu CPU对应占空比* @param duty_gpu GPU对应占空比* @param duty_mem 内存对应占空比* @return 综合占空比(CPU:0.5, GPU:0.35, 内存:0.15)*/float Get_WeightedDuty(float duty_cpu, float duty_gpu, float duty_mem) {float weighted = 0.5 * duty_cpu + 0.35 * duty_gpu + 0.15 * duty_mem;return Limit_Value(weighted, MIN_DUTY, MAX_DUTY);}/*** @brief 数值限幅辅助函数* @param value 输入值* @param min 最小值* @param max 最大值* @return 限幅后的值*/float Limit_Value(float value, float min, float max) {if (value < min) return min;if (value > max) return max;return value;}/*** @brief 主控制流程(示例:100ms调用一次)* @param temp_cpu 当前CPU温度* @param temp_gpu 当前GPU温度* @param temp_mem 当前内存温度* @return 最终输出的PWM占空比(%)*/float Fan_Control_Main(float temp_cpu, float temp_gpu, float temp_mem) {// 故障保护:任一硬件超温,直接输出最大占空比if (temp_cpu > MAX_CPU_TEMP || temp_gpu > MAX_GPU_TEMP || temp_mem > MAX_MEM_TEMP) {return MAX_DUTY;}// 分别计算各硬件PID控制量float ctrl_cpu = PID_Calculate(&pid_cpu, temp_cpu);float ctrl_gpu = PID_Calculate(&pid_gpu, temp_gpu);float ctrl_mem = PID_Calculate(&pid_mem, temp_mem);// 控制量映射为占空比float duty_cpu = Map_ControlToDuty_CPU_GPU(ctrl_cpu);float duty_gpu = Map_ControlToDuty_CPU_GPU(ctrl_gpu);float duty_mem = Map_ControlToDuty_MEM(ctrl_mem);// 加权融合得到最终占空比float final_duty = Get_WeightedDuty(duty_cpu, duty_gpu, duty_mem);return final_duty;}// 示例调用(模拟传感器数据)int main(void) {// 模拟采样温度:CPU=78℃, GPU=80℃, 内存=48℃float temp_cpu = 78.0f;float temp_gpu = 80.0f;float temp_mem = 48.0f;// 计算最终PWM占空比float pwm_duty = Fan_Control_Main(temp_cpu, temp_gpu, temp_mem);// 输出结果(实际应用中需将占空比写入PWM外设寄存器)// 例如:STM32可通过TIM_SetCompare1(TIM1, pwm_duty/100*ARR); 配置占空比(void)pwm_duty; // 避免编译警告return 0;}

    代码关键说明:

    1. PID 核心逻辑

      • 包含积分限幅(防止积分饱和)、温度死区(±2℃),避免风扇频繁抖动;

      • 偏差计算为目标温度-实时温度,温度越高偏差越负,PID输出控制量越大。

    2. 分段映射实现

      • CPU/GPU分为低温(<30)、中温(30~80)、高温(>80)三段映射,适配不同温度场景的调速需求;

      • 内存采用全区间低增益映射,适配其发热平缓的特性。

    3. 多硬件加权

      • CPU(0.5)+GPU(0.35)+内存(0.15)权重融合占空比,可根据场景调整权重(如挖矿平台调高 GPU 权重)。

    4. 工程化保护

      • 硬件超温时直接输出 95% 占空比强制散热;

      • 占空比限幅在 15%~95%,避免风扇停转或满负载损坏。

    移植适配要点:

    • 采样接口

      需替换main函数中的模拟温度为实际传感器读取逻辑(如I2C读取 DS18B20、读取 CPU 内置 DTS 温度);

    • PWM 输出

      根据MCU型号,将final_duty写入PWM定时器的比较寄存器(如 STM32 的TIM_SetCompare、ESP32 的ledc_set_duty);

    • 参数微调

      若实际运行中温度超调/振荡,可按整定表的调整原则修改KP/KI/KD宏定义。

    需要专业的网站建设服务?

    联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

    立即咨询