終結調參痛苦!ESP32 自適應 Adaptive PID 控制全攻略
Adaptive PID(比例-積分-微分)自適應 PID,在傳統 PID 控制器在工業與嵌入式領域應用廣泛,但固定參數難以應對動態變化的系統,調參往往耗時又不穩。Adaptive PID 則透過「根據誤差自動調整增益」來彌補這個問題,提供更聰明、更穩定的控制解法。
這時候,自適應 Adaptive PID 就派上用場了。而更棒的是,我們可以用 ESP32 這顆價格親民、功能強大的微控制器來實現它!

內容
什麼是自適應 Adaptive PID?
傳統 PID 控制器的三個參數:Kp、Ki、Kd 通常是固定的。而「自適應 PID」的核心概念是——讓 PID 參數隨著系統狀態自動調整,例如:
- 輕負載時降低增益避免震盪
- 誤差大時增加 Kp 加快響應
- 穩定區自動減少 Ki 以避免超調
這種智慧式調整方式,特別適合控制對象動態變化劇烈的場景。
Adaptive PID 實作邏輯
- 建立 PID 任務(使用 FreeRTOS Task)
- 讀取實際輸入值(如:溫度、速度)
- 輸出控制訊號(PWM 控制馬達、風扇或加熱器)
- 控制回圈週期(可用 Timer 或 vTaskDelayUntil() 精準控制)
開發環境
在開始編程之前,請確保已完成以下準備工作:
- 安裝 ESP-IDF 開發環境 (至少版本 v5.x 或更高)。
- ESP32 開發板。
專案結構
建立一個乾淨的 ESP-IDF PID Control 專案如下:
adaptive_pid_voltage/
├── CMakeLists.txt # Top-level build script
├── sdkconfig # Build-generated config file
├── components/ # Reusable modules
│ └── pid_controller/
│ ├── CMakeLists.txt # PID component build script
│ ├── pid_controller.c # PID algorithm
│ └── pid_controller.h # PID API
├── main/
│ ├── CMakeLists.txt # Main app build script
│ ├── main.c # app_main and task setup
│ └── simulation.c # Controlled system simulation
└── README.md # Project overview
為了讓讀者更容易上手,本範例將 PID 演算法、ADC 讀取與 PWM 控制整合於同一個檔案(main.c)中。這樣的寫法更加直觀,方便快速理解與示範,適合初學者快速掌握核心概念。
Adaptive PID 控制器程式碼
#include <stdio.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
static const char *TAG = "VoltagePID";
// PID parameters
float Kp = 1.0f, Ki = 0.5f, Kd = 0.1f;
float setpoint = 100.0f; // Target motor speed (RPM)
float integral = 0.0f;
float last_error = 0.0f;
// Simulated control output voltage (0 ~ 3.3V)
float control_voltage = 0.0f;
// Simulated feedback value (motor speed)
float motor_speed = 0.0f;
void update_pid(float input) {
float error = setpoint - input;
float delta_error = error - last_error;
// Adaptive gain tuning based on error magnitude
if (fabsf(error) > 50) {
Kp = 2.0f; Ki = 0.7f; Kd = 0.2f;
} else if (fabsf(error) > 20) {
Kp = 1.5f; Ki = 0.6f; Kd = 0.15f;
} else {
Kp = 1.0f; Ki = 0.5f; Kd = 0.1f;
}
integral += error;
float derivative = delta_error;
float raw_output = Kp * error + Ki * integral + Kd * derivative;
// Simulate control voltage output: clamp between 0V and 3.3V
control_voltage = raw_output / 100.0f; // Scale the output (depends on application)
if (control_voltage > 3.3f) control_voltage = 3.3f;
if (control_voltage < 0.0f) control_voltage = 0.0f;
last_error = error;
// Log output to monitor behavior
ESP_LOGI(TAG, "[PID] Speed: %.2f RPM | Error: %.2f | Kp: %.2f | Ki: %.2f | Kd: %.2f | Output Voltage: %.2f V",
input, error, Kp, Ki, Kd, control_voltage);
}
// Simulate motor response with inertia based on control voltage
void pid_task(void *arg) {
while (1) {
update_pid(motor_speed);
// Simulate motor acceleration (e.g., proportional to voltage)
float target_speed = control_voltage * 40.0f; // e.g., 3.3V -> ~132 RPM
motor_speed += (target_speed - motor_speed) * 0.05f; // Simulate inertia/delay
// Log the simulated speed for visualization
ESP_LOGI(TAG, "[SIM] Target Speed: %.2f RPM | Current Speed: %.2f RPM",
target_speed, motor_speed);
vTaskDelay(pdMS_TO_TICKS(100)); // Control cycle: 100 ms
}
}
void app_main(void) {
ESP_LOGI(TAG, "Starting Voltage-Controlled PID Simulation...");
xTaskCreate(pid_task, "pid_task", 4096, NULL, 5, NULL);
}
編譯和燒錄
完成程式碼後,您可以使用 ESP-IDF 提供的命令進行編譯、燒錄和監控。
在 VS Code 的左下角 ESP-IDF 工具列:
- 點選 Build project
- 點選 Flash device
- 點選 Monitor device
輸出說明
以下是模擬控制過程中的部份 log 輸出:
I (0) VoltagePID: Starting Voltage-Controlled PID Simulation…
I (100) VoltagePID: [PID] Speed: 0.00 RPM | Error: 100.00 | Kp: 2.00 | Ki: 0.70 | Kd: 0.20 | Output Voltage: 3.30 V
I (100) VoltagePID: [SIM] Target Speed: 132.00 RPM | Current Speed: 6.60 RPM
I (200) VoltagePID: [PID] Speed: 6.60 RPM | Error: 93.40 | Kp: 2.00 | Ki: 0.70 | Kd: 0.20 | Output Voltage: 3.30 V
…
I (1000) VoltagePID: [PID] Speed: 85.60 RPM | Error: 14.40 | Kp: 1.00 | Ki: 0.50 | Kd: 0.10 | Output Voltage: 1.82 V
I (1000) VoltagePID: [SIM] Target Speed: 72.80 RPM | Current Speed: 74.00 RPM
…
I (2000) VoltagePID: [PID] Speed: 98.20 RPM | Error: 1.80 | Kp: 1.00 | Ki: 0.50 | Kd: 0.10 | Output Voltage: 1.04 V
I (2000) VoltagePID: [SIM] Target Speed: 41.60 RPM | Current Speed: 97.00 RPM
分析與說明:
- 大誤差時快速響應(起始階段)
一開始馬達尚未轉動,速度為 0 RPM,誤差達 100。此時 Adaptive PID 自動將增益調高(Kp=2.0),使輸出電壓直接飽和到 3.3V,系統加速快速,縮短反應時間。 - 中間誤差階段,控制穩定
當速度進入中間區段,例如誤差約 20~50 時,增益切換為中等(Kp=1.5),電壓輸出也開始降低,系統控制進入「快速收斂、但避免過衝」的平衡狀態。 - 小誤差時精細微調
當誤差小於 20(如 10 以下),控制器自動調整回較小增益(Kp=1.0),避免震盪與過調,讓馬達平穩收斂至設定值附近,控制電壓也相對更平滑。 - 自動增益調整避免手動調參
整個過程中,無需開發者手動調整 PID 參數。Adaptive PID 根據誤差範圍動態調整參數,讓系統既有響應速度,又能避免震盪或不穩。
結論
我們透過 ESP32 實作 Adaptive PID,不僅成功展示了自動調整控制參數的能力,也證明了其在快速響應與穩定收斂間取得良好平衡。整體系統能夠根據誤差大小動態調整增益,有效加速收斂並抑制震盪,讓控制器在面對實際環境變動時依然保持精準控制。以電壓控制模擬馬達轉速為例,我們看見系統能根據誤差大小動態改變增益,有效降低過衝與震盪,同時加快收斂速度。這種自適應調整方法,不僅提升了 PID 控制器的彈性,也使控制系統更適合面對實務中不確定的環境變化。未來可將此架構擴展至溫度、光強或其他物理量控制,並結合實體 ADC 回授與 PWM 輸出,進一步驗證並應用於真實硬體系統。









