終結調參痛苦!ESP32 自適應 Adaptive PID 控制全攻略


Adaptive PID(比例-積分-微分)自適應 PID,在傳統 PID 控制器在工業與嵌入式領域應用廣泛,但固定參數難以應對動態變化的系統,調參往往耗時又不穩。Adaptive PID 則透過「根據誤差自動調整增益」來彌補這個問題,提供更聰明、更穩定的控制解法。

這時候,自適應 Adaptive PID 就派上用場了。而更棒的是,我們可以用 ESP32 這顆價格親民、功能強大的微控制器來實現它!

Adaptive PID

什麼是自適應 Adaptive PID?

傳統 PID 控制器的三個參數:Kp、Ki、Kd 通常是固定的。而「自適應 PID」的核心概念是——讓 PID 參數隨著系統狀態自動調整,例如:

  • 輕負載時降低增益避免震盪
  • 誤差大時增加 Kp 加快響應
  • 穩定區自動減少 Ki 以避免超調

這種智慧式調整方式,特別適合控制對象動態變化劇烈的場景。

Adaptive PID 實作邏輯

  • 建立 PID 任務(使用 FreeRTOS Task)
  • 讀取實際輸入值(如:溫度、速度)
  • 輸出控制訊號(PWM 控制馬達、風扇或加熱器)
  • 控制回圈週期(可用 Timer 或 vTaskDelayUntil() 精準控制)

開發環境

在開始編程之前,請確保已完成以下準備工作:

專案結構

建立一個乾淨的 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 輸出,進一步驗證並應用於真實硬體系統。