之前收到了一位粉絲朋友的問題,是需要了解温度相關的PID控制,主要是關於PID根據温度進行風扇轉速的調節。針對這個粉絲的問題,我覺得也是比較感興趣的,加上自己也是研究PID控制這塊,所以也花了一些時間去查閲了相關的資料,加上自己的思考寫了這一篇文章,有不正確的地方,希望大家可以多理解和進行指正。

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_#define

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_閉環控制_02

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_#define_03

同樣是控温,為什麼他的風扇又靜又能散熱?

在台式機/服務器的風扇調速系統中,PID閉環控制的核心是以硬件温度為反饋信號,通過算法動態調整風扇PWM佔空比,最終將硬件温度穩定在目標區間。而温度採樣值到風扇電機佔空比的映射,是連接 “温度檢測” 與 “轉速控制” 的核心鏈路,分為偏差計算→PID運算→控制量映射→PWM 驅動四個關鍵步驟。

一、 PID閉環控制的核心原理

閉環控制的本質是 “檢測偏差 - 消除偏差” 的循環反饋過程 ,相比於開環控制(如固定佔空比調速),它能根據硬件實時温度動態調整,兼顧散熱效果、靜音性和風扇壽命。

1. 閉環控制的基本組成

一個完整的PID閉環温控系統包含 5 個核心環節,形成閉環反饋迴路

目標温度設定 → 温度採樣 → 偏差計算 → PID 算法運算 → PWM 佔空比輸出 → 風扇轉速調整 → 硬件温度變化 → 温度採樣(循環)

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_段映射_04

各環節的具體作用:

目標温度設定:根據硬件安全手冊設定閾值(如 CPU 目標温度 70℃,GPU 目標温度 75℃),是控制的 “基準值”。

温度採樣:通過硬件內置傳感器(如 CPU 的 DTS 數字温度傳感器、主板的 NTC 熱敏電阻)採集實時温度,是反饋信號。

偏差計算:計算實時温度與目標温度的差值,即

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_#define_05

若 e(k)>0,説明温度低於目標,可降低風扇轉速;若 e(k)<0,説明温度超標,需提高轉速。

PID算法運算:根據偏差的大小、累積趨勢、變化速率,計算出對應的控制量,是閉環控制的 “大腦”。

PWM 佔空比輸出:將PID計算的控制量轉化為風扇電機的驅動信號,最終改變轉速。

2. PID 三環節的作用(比例 P + 積分 I + 微分 D)

PID 算法通過三個獨立環節的協同,實現 “快速響應、消除穩態誤差、抑制超調” 的目標,離散化後的實用公式為:

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_#define_06

環節

核心作用

對温控的影響

比例環節(P)

按偏差大小即時調整控制量,偏差越大,輸出控制量越大

決定系統的響應速度。Kp 越大,温度超標時風扇轉速提升越快,但過大易導致轉速頻繁波動(振盪)

積分環節(I)

對偏差累積求和,消除 “靜態誤差”(如温度長期略高於目標的情況)

解決 “比例控制無法完全達標的問題”。例如 CPU 長期 72℃(目標 70℃),積分累積偏差會緩慢提高佔空比,直至温度達標

微分環節(D)

計算偏差的變化速率,預判温度趨勢,提前調整

抑制超調。例如 CPU 温度從 60℃ 快速升至 75℃(偏差變化率大),微分環節會瞬間增大控制量,提前拉高轉速,避免温度持續飆升

3. 閉環控制的優勢

  • 抗干擾性強環境温度變化、硬件負載波動(如CPU從空閒到滿載)時,系統能自動調整轉速,無需人工干預。
  • 穩定性高温度不會出現 “忽高忽低” 的情況,始終穩定在目標區間。
  • 兼顧靜音與散熱低負載低温時風扇低速靜音,高負載高温時風扇高速散熱,避免 “一直滿轉” 的能源浪費。

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_閉環控制_07

二、 温度採樣到風扇佔空比的映射關係

映射關係的核心是將 PID 輸出的 “抽象控制量 u(k)”,轉化為風扇電機可識別的 “PWM 佔空比”,分為 3 個關鍵步驟,且每個步驟都需考慮硬件的實際約束。

步驟 1:PID 控制量的限幅處理

PID 計算出的控制量 u(k) 是一個無單位的抽象值,可能超出風扇的實際可控範圍,因此第一步要鉗位控制量的上下限

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_段映射_08

  • 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%]),可直接線性對應:

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_段映射_09

特點:簡單易實現,適用於温度在目標區間附近的穩態場景。

分段映射(適用於全温度區間,更實用)

風扇的轉速-佔空比特性是非線性的:低速時佔空比變化對轉速影響大(如 15%→20%轉速提升明顯),高速時佔空比變化對轉速影響小(如 80%→85% 轉速提升微弱)。因此實際系統會分三段映射:

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_#define_10

步驟 3:佔空比到風扇轉速的最終轉換

直流風扇電機的轉速與 PWM 佔空比的關係為:佔空比決定電機的平均輸入電壓,平均電壓越高,轉速越快,近似滿足線性關係:

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_#define_11

  • n:風扇實際轉速
  • nmax:風扇滿佔空比時的額定轉速(如 2000 RPM)
  • Dmin:風扇啓動的最小佔空比(如 15%)

舉例:某風扇nmax=2000RPM,Dmin=15%。當佔空比D=50%時,轉速n=2000⋅(50-15)/(100-15)≈823RPM;當D=100%時,轉速 n=2000 RPM。

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_#define_12

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小,減少積分飽和風險

風扇調速總振盪?90% 工程師沒搞懂這層 PID 邏輯_#define_13

參數調整原則

  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) - 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;
}

代碼關鍵説明:

  1. PID 核心邏輯
  • 包含積分限幅(防止積分飽和)、温度死區(±2℃),避免風扇頻繁抖動;
  • 偏差計算為目標温度-實時温度,温度越高偏差越負,PID輸出控制量越大。
  1. 分段映射實現
  • CPU/GPU分為低温(<30)、中温(30~80)、高温(>80)三段映射,適配不同温度場景的調速需求;

  • 內存採用全區間低增益映射,適配其發熱平緩的特性。

  1. 多硬件加權

  • CPU(0.5)+GPU(0.35)+內存(0.15)權重融合佔空比,可根據場景調整權重(如平台調高 GPU 權重)。

  1. 工程化保護

  • 硬件超温時直接輸出 95% 佔空比強制散熱;

  • 佔空比限幅在 15%~95%,避免風扇停轉或滿負載損壞。

移植適配要點:

  • 採樣接口需替換main函數中的模擬温度為實際傳感器讀取邏輯(如I2C讀取 DS18B20、讀取 CPU 內置 DTS 温度);
  • PWM 輸出根據MCU型號,將final_duty寫入PWM定時器的比較寄存器(如 STM32 的TIM_SetCompare、ESP32 的ledc_set_duty);
  • 參數微調若實際運行中温度超調/振盪,可按整定表的調整原則修改KP/KI/KD宏定義。