之前收到了一位粉絲朋友的問題,是需要了解温度相關的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小,減少積分飽和風險 |
參數調整原則
- 若温度持續超調(如 CPU 目標 70℃,實際穩定在 75℃)→ 增大 Ki(每次 +0.1)。
- 若風扇轉速頻繁振盪(如佔空比在 40%~60% 來回跳)→ 減小 Kp(每次 -1),或增大 Kd(每次 +1)。
- 若温度上升時響應慢(如 CPU 從 70℃ 升至 85℃ 才開始提速)→ 增大 Kp(每次 +1)。
- 參考代碼
#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) - 10
duty = control - 10;
} else if (control <= 80) {
// 中温段:線性映射 D = u(k)
duty = control;
} else {
// 高温段:高增益映射 D = 0.8*u(k) + 20
duty = 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;
}
代碼關鍵説明:
- PID 核心邏輯:
- 包含積分限幅(防止積分飽和)、温度死區(±2℃),避免風扇頻繁抖動;
- 偏差計算為
目標温度-實時温度,温度越高偏差越負,PID輸出控制量越大。
- 分段映射實現:
-
CPU/GPU分為低温(<30)、中温(30~80)、高温(>80)三段映射,適配不同温度場景的調速需求;
-
內存採用全區間低增益映射,適配其發熱平緩的特性。
-
多硬件加權:
-
按
CPU(0.5)+GPU(0.35)+內存(0.15)權重融合佔空比,可根據場景調整權重(如平台調高 GPU 權重)。
-
工程化保護:
-
硬件超温時直接輸出 95% 佔空比強制散熱;
-
佔空比限幅在 15%~95%,避免風扇停轉或滿負載損壞。
移植適配要點:
- 採樣接口需替換
main函數中的模擬温度為實際傳感器讀取邏輯(如I2C讀取 DS18B20、讀取 CPU 內置 DTS 温度); - PWM 輸出根據MCU型號,將
final_duty寫入PWM定時器的比較寄存器(如 STM32 的TIM_SetCompare、ESP32 的ledc_set_duty); - 參數微調若實際運行中温度超調/振盪,可按整定表的調整原則修改
KP/KI/KD宏定義。