Skip to main content

TC 開發流程

後驅賽車 TC 開發完整指南

tags: Traction Control RWD Race Car IMU CAN Logger

📋 目錄


1. 系統概述

1.1 控制對象與目標

  • 控制對象:馬達扭矩(anti-spin,無煞車)
  • 傳感器:四輪輪速 + IMU(6 軸)+ 馬達轉速/扭矩
  • 控制目標
    • 讓後驅輪 slip ratio 收斂在 0.10~0.20(直線)/ 0.08~0.12(彎道)
    • 減少低 μ 路面打滑,提升 0–60 km/h 加速性能
    • 增加出彎穩定性(防過度 yaw)

1.2 核心優勢

  • 賽車應用:降低加速時間 20~30%(低 μ)
  • IMU 自適應:彎道自動降低 slip 目標,平衡速度與穩定
  • 即時反饋:10 Hz 控制頻率,<100 ms 響應延遲

2. 感測訊號與衍生量

2.1 原始訊號(CAN 直接讀取)

訊號 符號 來源 單位 採樣率
前左輪速 (\omega_{FL}) CAN/WSM rad/s 100 Hz
前右輪速 (\omega_{FR}) CAN/WSM rad/s 100 Hz
後左輪速 (\omega_{RL}) CAN/WSM rad/s 100 Hz
後右輪速 (\omega_{RR}) CAN/WSM rad/s 100 Hz
縱向加速度 (a_x) IMU m/s² 100 Hz
橫向加速度 (a_y) IMU m/s² 100 Hz
偏航角速度 (\dot{\psi}) IMU rad/s 100 Hz
馬達轉速 (\omega_m) CAN/inverter rpm 100 Hz
馬達命令扭矩 (T_{driver}) CAN/VCU N·m 333Hz
馬達實際扭矩 (T_{actual}) CAN/inverter N·m 100 Hz

2.2 衍生量計算(核心演算法)

2.2.1 車速估測 (v) (Low-pass + IMU Fusion)

前輪平均速度(原始):
    v_wheel = (ω_FL + ω_FR) / 2 × R_front
    
IMU 縱向積分(漂移補正):
    v_imu = ∫ a_x dt (with high-pass filter)
    
融合(Complementary Filter):
    v = α × v_wheel + (1 - α) × v_imu
    其中 α = 0.8(高速時多信任前輪,低速多信任 IMU)
    
防止低速除零:
    v_safe = max(v, 0.1 m/s)

Python 實作

def estimate_vehicle_speed(omega_fl, omega_fr, a_x, R_front=0.32, alpha=0.8):
    """
    輸入: omega_fl, omega_fr (rad/s), a_x (m/s²)
    輸出: v_safe (m/s)
    """
    v_wheel = (omega_fl + omega_fr) / 2 * R_front
    # IMU 積分部分(假設已有高通濾波)
    v_imu = alpha_imu * v_imu_prev + (1 - alpha_imu) * a_x * dt
    v = alpha * v_wheel + (1 - alpha) * v_imu
    return max(v, 0.1)

2.2.2 後輪 Slip Ratio (\lambda_L, \lambda_R)

定義(ISO 6954 標準):
    λ_L = (ω_RL × R_rear - v) / max(v, 0.1)
    λ_R = (ω_RR × R_rear - v) / max(v, 0.1)
    
平均 Slip:
    λ̄ = (λ_L + λ_R) / 2
    
物理意義:
    λ = 0      : 完全滾動,無打滑
    0 < λ < 0.3 : 加速打滑(TC 目標區間)
    λ > 0.5    : 嚴重打滑(失控)
    λ_L ≠ λ_R  : 單輪打滑(可診斷直線穩定性)

Python 實作

def compute_slip_ratio(omega_rl, omega_rr, v_safe, R_rear=0.32):
    """計算後輪 slip ratio"""
    slip_L = (omega_rl * R_rear - v_safe) / max(v_safe, 0.1)
    slip_R = (omega_rr * R_rear - v_safe) / max(v_safe, 0.1)
    slip_avg = (slip_L + slip_R) / 2
    return slip_L, slip_R, slip_avg

2.2.3 彎道檢測(IMU Yaw Rate)

直線 / 彎道判定閾值(可調,初始值推薦):
    θ_straight = 5°/s  (直線)
    θ_light    = 15°/s (輕彎)
    θ_heavy    = 25°/s (重彎)

實現:
    if |ψ̇| < θ_straight:
        state = "STRAIGHT"
    elif θ_straight ≤ |ψ̇| < θ_light:
        state = "LIGHT_TURN"
    elif θ_light ≤ |ψ̇| < θ_heavy:
        state = "HEAVY_TURN"
    else:
        state = "SPIN_RISK"

Python 實作

def detect_turn_state(psi_dot, thresholds=(5, 15, 25)):
    """
    輸入: psi_dot (rad/s, IMU yaw rate)
    輸出: state (str)
    """
    psi_dot_deg = abs(psi_dot) * 180 / 3.14159
    if psi_dot_deg < thresholds[0]:
        return "STRAIGHT"
    elif psi_dot_deg < thresholds[1]:
        return "LIGHT_TURN"
    elif psi_dot_deg < thresholds[2]:
        return "HEAVY_TURN"
    else:
        return "SPIN_RISK"

3. 控制邏輯詳細推導

3.1 TC 控制架構圖

[油門輸入 T_driver]
        ↓
[後輪 Slip 計算] → λ̄ = (λ_L + λ_R) / 2
        ↓
[IMU Yaw Rate] → |ψ̇| → 選定 Target λ*
        ↓
[Slip Error] → e = λ* - λ̄
        ↓
[PI Controller] → ΔT = K_p × e + K_i × ∫e dt
        ↓
[扭矩限縮] → T_cmd = T_driver × (1 + ΔT)
        ↓
[馬達執行 & 保護邏輯]
        ↓
[實際扭矩送出]

3.2 PI 控制器推導

3.2.1 Slip Error 定義

e(t) = λ*(t) - λ̄(t)

e > 0  : slip 低於目標 → 要放寬扭矩(加油)
e < 0  : slip 高於目標 → 要限縮扭矩(減油)

3.2.2 比例項 (Proportional)

ΔT_p = K_p × e

物理意義:
    - K_p 越大,反應越快(但容易超調)
    - K_p 越小,反應越慢(但更穩定)

推薦初值(賽車用):
    直線: K_p = 3.0 ~ 5.0  (aggressive,想要快速加速)
    彎道: K_p = 2.0 ~ 3.0  (保守,防 spin)
    低μ:  K_p = 2.0 ~ 2.5  (中等)

物理直覺:如果打滑很嚴重(e = -0.3),用 K_p=5,會一次砍 150% 的扭矩(實際限縮到 0.5 倍),很快就能回到目標。

3.2.3 積分項 (Integral)

ΔT_i = K_i × ∫e dt

物理意義:
    - 消除穩態誤差(e 小但持久時逐漸累積)
    - 低速起步用(此時 P 反應慢)
    
推薦初值:
    K_i = 0.3 ~ 1.0
    
積分限制(Anti-windup):
    ∫e dt 最大值 = 1.0 → 防止 I 項無限累積

3.2.4 完整 PI 控制律

ΔT(t) = K_p × e(t) + K_i × ∫₀ᵗ e(τ) dτ

T_cmd = T_driver × (1 + ΔT)  ; 原始扭矩限縮
T_cmd = max(0.3 × T_driver, min(T_driver, T_cmd))  ; 限縮範圍 [30%~100%]

Python 實作

class PIController:
    def __init__(self, Kp=3.0, Ki=0.5, dt=0.01, anti_windup_max=1.0):
        self.Kp = Kp
        self.Ki = Ki
        self.dt = dt
        self.integral = 0.0
        self.anti_windup_max = anti_windup_max
    
    def update(self, error):
        """計算 PI 輸出"""
        # P 項
        p_term = self.Kp * error
        
        # I 項(含 anti-windup)
        self.integral += error * self.dt
        self.integral = max(-self.anti_windup_max, 
                           min(self.anti_windup_max, self.integral))
        i_term = self.Ki * self.integral
        
        # 總輸出
        delta_T = p_term + i_term
        return delta_T
    
    def reset(self):
        """低速時重置積分"""
        self.integral = 0.0

3.3 扭矩命令生成與保護邏輯

Step 1: 計算 Slip Error
    e = λ* - λ̄

Step 2: PI 控制器
    ΔT = Kp × e + Ki × ∫e dt

Step 3: 扭矩限縮計算
    T_cmd_raw = T_driver × (1 + ΔT)

Step 4: 飽和限縮(Saturation)
    T_cmd_saturated = clamp(T_cmd_raw, 0.3 × T_driver, 1.0 × T_driver)

Step 5: 安全邏輯(Safety Logic)
    
    if v < 3 km/h:  // 低速起步
        T_cmd = T_driver  // TC OFF,用前輪參考加速
    
    elif λ_L > 0.4 or λ_R > 0.4:  // 單輪過度打滑
        T_cmd = 0.5 × T_driver  // 強制砍 50%
    
    elif state == "SPIN_RISK":  // IMU 偵測過度 yaw
        T_cmd = 0.3 × T_driver  // 進入失控防護
    
    else:
        T_cmd = T_cmd_saturated  // 正常 PI 控制

Python 實作

def compute_torque_command(T_driver, slip_avg, slip_L, slip_R, 
                           v, psi_dot, pi_controller, 
                           turn_state, thresholds=(5, 15, 25)):
    """完整扭矩命令生成"""
    
    # Safety: 低速關閉 TC
    if v < 3 / 3.6:  # 3 km/h
        return T_driver, "LOW_SPEED"
    
    # Safety: 單輪過度打滑
    if abs(slip_L) > 0.4 or abs(slip_R) > 0.4:
        return 0.5 * T_driver, "SINGLE_WHEEL_SLIP"
    
    # Safety: 過度 yaw(失控風險)
    psi_dot_deg = abs(psi_dot) * 180 / 3.14159
    if psi_dot_deg > thresholds[2]:
        return 0.3 * T_driver, "SPIN_RISK"
    
    # 正常 TC 控制
    delta_T = pi_controller.update(error=slip_avg)
    T_cmd_raw = T_driver * (1 + delta_T)
    T_cmd = max(0.3 * T_driver, min(T_driver, T_cmd_raw))
    
    return T_cmd, turn_state

4. 自適應 Target Slip 算法

4.1 Target Slip 自適應表

根據 IMU yaw rate 和檔位即時調整 slip 目標:

┌─────────────────────────────────────────────────────────┐
│          IMU Yaw Rate 與 Target Slip 自適應表             │
├──────────────────┬──────────────┬──────────┬─────────────┤
│   駕駛狀態       │ yaw rate範圍 │ λ*      │ 控制策略     │
├──────────────────┼──────────────┼──────────┼─────────────┤
│ 直線全油門加速   │ |ψ̇| < 5°/s  │ 0.18~0.20│ 最大縱向力   │
│                  │              │ (Gear 1) │ 0-60 最快    │
├──────────────────┼──────────────┼──────────┼─────────────┤
│ 輕微轉向加速     │ 5°/s ≤|ψ̇|  │ 0.14~0.16│ 平衡加速    │
│                  │      < 15°/s │ (Gear 2) │ 與側向穩定   │
├──────────────────┼──────────────┼──────────┼─────────────┤
│ 中等轉向 (彎道)  │15°/s ≤|ψ̇|  │ 0.10~0.12│ 優先穩定,   │
│                  │      < 25°/s │ (Gear 3) │ 防 oversteer │
├──────────────────┼──────────────┼──────────┼─────────────┤
│ 急彎 / 失控預防  │ |ψ̇| ≥ 25°/s │ 0.08~0.10│ 全力穩定,   │
│                  │              │ (Gear 4) │ 強制限縮     │
└──────────────────┴──────────────┴──────────┴─────────────┘

4.2 自適應演算法實作

def compute_target_slip(psi_dot, v):
    """
    根據 yaw rate 和速度自適應計算 target slip
    
    輸入:
        psi_dot: IMU 偏航角速度 (rad/s)
        v: 車速 (m/s)
    
    輸出:
        lambda_target: 目標 slip ratio
        gear: 檔位標籤 (用於調試)
    """
    psi_dot_deg = abs(psi_dot) * 180 / 3.14159
    
    # 檔位切換 & Target Slip
    if psi_dot_deg < 5:
        lambda_target = 0.18 + 0.02 * (v / 20)  # 速度越高,target 稍高
        gear = "GEAR_1_MAX_ACCEL"
    
    elif psi_dot_deg < 15:
        # 線性插值: 5°/s 時 0.15,15°/s 時 0.12
        lambda_target = 0.15 - 0.03 * (psi_dot_deg - 5) / 10
        gear = "GEAR_2_LIGHT_TURN"
    
    elif psi_dot_deg < 25:
        lambda_target = 0.10 + 0.02 * (25 - psi_dot_deg) / 10
        gear = "GEAR_3_HEAVY_TURN"
    
    else:  # Spin risk
        lambda_target = 0.08
        gear = "GEAR_4_SPIN_RISK"
    
    return lambda_target, gear

4.3 低 μ 路面自適應補正

可選擴展:如果有簡易的摩擦力估測(例如從加速度飽和判斷),進一步調整:

def estimate_mu_level(a_x_max_recent, slip_avg_recent):
    """
    簡化的摩擦力等級估測
    
    邏輯:
    - 高 μ (乾柏油): a_x_max > 8 m/s², slip < 0.3
    - 中 μ (濕地):   a_x_max ≈ 4~6 m/s², slip 0.3~0.4
    - 低 μ (冰/雪):  a_x_max < 3 m/s², slip > 0.4
    """
    if a_x_max_recent > 8:
        return "HIGH_MU"
    elif a_x_max_recent > 5:
        return "MEDIUM_MU"
    else:
        return "LOW_MU"

def adjust_target_slip_for_mu(lambda_target, mu_level):
    """在摩擦力低時,進一步降低 target slip(保守)"""
    if mu_level == "LOW_MU":
        return lambda_target * 0.85  # 降低 15%
    elif mu_level == "MEDIUM_MU":
        return lambda_target * 0.92  # 降低 8%
    else:
        return lambda_target

5. 實驗流程與工況規劃

5.1 實驗場地與地面條件

地面類型 路面特徵 μ 等級 場地要求
高 μ 乾燥柏油、新鋪 μ ≈ 0.85~1.0 平坦,無雨
中 μ 濕柏油、一般水泥 μ ≈ 0.6~0.75 濕潤但不積水
低 μ 碎石、濕草地、冰 μ ≈ 0.3~0.5 或人為灑水

5.2 單次測試 SOP(Standard Operating Procedure)

5.2.1 測試前準備(15 分鐘)

□ 輪胎檢查 □ 胎壓一致(左右前後差異 < 0.1 bar) □ 胎溫 20~30°C(冷胎,確保可重複性) □ 胎面無異物

□ CAN Logger 配置 □ 記錄頻率設定:100 Hz (wheel speed, IMU) □ CAN ID 表確認無誤 □ 存儲空間檢查(預期每次 5~10 MB)

□ 馬達 / 油門校準 □ 油門全開 -> T_driver max 確認 □ 油門全關 -> T_driver = 0 確認

□ 天氣記錄 □ 溫度、溼度、風速 □ 路面溫度(紅外溫度計)

5.2.2 Baseline 測試 - TC OFF(3 趟)

場景: 直線加速,全油門 0~60 km/h

每趟流程:
  1. 車停在起點,冷靜等待 (IMU 零點漂移穩定)
  2. 油門緩慢踩到 50% (預熱)
  3. 在 v = 0 瞬間,全油門 (t=0 記點)
  4. 保持全油門直到 60 km/h,然後收油
  5. 停車 5 分鐘,冷卻胎溫
  
數據記錄:
  ├─ 起點位置 (GPS)
  ├─ 環境溫度、胎溫
  ├─ CAN 原始數據 (導出為 CSV)
  └─ 駕駛備註 (路面狀況、車感描述)

觀察重點:
  ├─ 最大 slip ratio: λ_max ?
  ├─ 0~60 km/h 時間: t_60 ?
  ├─ 打滑持續時間 (λ > 0.3): t_slip ?
  └─ 前後輪 slip 差異: |λ_L - λ_R| ?

5.2.3 TC ON - 直線測試(3 趟)

場景: 同上,但啟動 TC(Target λ* = 0.18)

參數設定:
  ├─ Kp = 3.5 (aggressive)
  ├─ Ki = 0.5
  └─ 彎道檢測閾值: θ_straight = 5°/s

觀察重點 (對比 Baseline):
  ├─ 最大 slip ratio 是否 < 0.25?
  ├─ 0~60 km/h 時間是否縮短 (預期 -15~25%)?
  ├─ 縱向加速度是否更平順 (std 是否降低)?
  └─ 打滑時間是否 < 1 s?

數據分析:
  - 畫圖: slip(t) 對比 (OFF 紅、ON 藍)
  - 計算: 平均縱向 g (m/s²)

5.2.4 TC ON - 彎道測試(3 趟,如有彎道)

場景: 轉向加速 (方向盤 ≈ 30°) + 全油門,通過彎道

參數設定:
  ├─ Kp = 2.5 (moderate)
  ├─ Ki = 0.5
  └─ 自適應 target slip (根據 |ψ̇|)

觀察重點:
  ├─ 出彎 yaw rate 峰值: max(|ψ̇|)?
  ├─ Oversteer 徵兆 (yaw rate 尖峰)
  ├─ 加速度 (a_x, a_y) 是否均衡
  └─ slip 是否穩定在 0.10~0.12?

5.2.5 TC ON - 低 μ 起步測試(5 趟)

場景: 低 μ 地面 (濕碎石),全油門起步

關鍵觀察:
  ├─ 起步時打滑時間 (λ > 0.3 持續多久)?
  ├─ 穩定後的 slip 值是否在目標範圍?
  ├─ 連續 5 次起步中胎溫上升對 slip 的影響
  └─ 預期: 打滑時間逐次縮短 (胎溫升高 μ 增加)

5.3 工況組合表(建議先做順序)

優先級 地面 TC 狀態 彎度 趟數 預期時間 備註
1 高 μ OFF 直線 3 10 min Baseline
2 高 μ ON 直線 3 10 min 主要對比
3 中 μ OFF 直線 3 10 min
4 中 μ ON 直線 3 10 min
5 低 μ OFF 直線 3 10 min 最難
6 低 μ ON 直線 5 15 min 重點
7 高 μ ON 彎 30° 3 15 min 穩定性
8 低 μ ON 彎 30° 3 15 min 進階
- - - - 100 min ≈ 半天

6. 數據檢查表

6.1 實時檢查清單(場邊用)

在每趟實驗後,用此表快速檢查數據品質:

### 6.1.1 CAN 數據完整性

測試編號: ______  日期: ______  地面條件: ______

□ 訊號檢查
  □ 前左輪速:< 2 s 無跳動? Y / N
  □ 前右輪速:< 2 s 無跳動? Y / N
  □ 後左輪速:< 2 s 無跳動? Y / N
  □ 後右輪速:< 2 s 無跳動? Y / N
  □ IMU 加速度:在合理範圍 (< 2g, < 1g)? Y / N
  □ IMU 角速度:max(|ψ̇|) < 100°/s? Y / N
  □ 馬達扭矩:在 [0, T_max] 範圍? Y / N

□ 時間同步
  □ 所有訊號長度一致(同一時間範圍)? Y / N
  □ 無掉幀或時間戳異常? Y / N

□ 統計檢查
  □ 車速:0 ~ 60 km/h 正常遞增? Y / N
  □ Slip ratio:0 ~ 0.5 正常變化? Y / N
  □ 加速度:0~1 g,無 spike > 3g? Y / N

6.2 離線分析檢查單(Python 用)

6.2.1 基礎統計量

def verify_data_quality(df_log):
    """
    輸入: DataFrame with 'time', 'v', 'slip_L', 'slip_R', 'a_x', 'psi_dot', 'T_cmd'
    輸出: 品質報告
    """
    print("=" * 60)
    print("DATA QUALITY REPORT")
    print("=" * 60)
    
    # 訊號範圍檢查
    checks = {
        "v (m/s)": (df_log['v'].min(), df_log['v'].max(), "0 ~ 25"),
        "slip_L": (df_log['slip_L'].min(), df_log['slip_L'].max(), "-0.2 ~ 0.6"),
        "slip_R": (df_log['slip_R'].min(), df_log['slip_R'].max(), "-0.2 ~ 0.6"),
        "a_x (m/s²)": (df_log['a_x'].min(), df_log['a_x'].max(), "-1 ~ 2"),
        "psi_dot (°/s)": (df_log['psi_dot'].min() * 180/3.14, 
                         df_log['psi_dot'].max() * 180/3.14, "-50 ~ 50"),
        "T_cmd (N·m)": (df_log['T_cmd'].min(), df_log['T_cmd'].max(), "0 ~ T_max"),
    }
    
    for signal, (v_min, v_max, range_str) in checks.items():
        print(f"{signal:20} | Min: {v_min:8.3f} | Max: {v_max:8.3f} | Range: {range_str}")
    
    # 異常檢測
    print("\n" + "=" * 60)
    print("ANOMALY DETECTION")
    print("=" * 60)
    
    # 檢查加速度 spike
    a_x_spike = (df_log['a_x'].diff().abs() > 0.5).sum()  # 0.5 m/s² 變化
    print(f"Acceleration spikes (Δa > 0.5 m/s²): {a_x_spike} frames")
    
    # 檢查 slip 不連續
    slip_jump = ((df_log['slip_L'].diff().abs() > 0.1).sum() + 
                 (df_log['slip_R'].diff().abs() > 0.1).sum())
    print(f"Slip discontinuities (Δλ > 0.1): {slip_jump} frames")
    
    # 檢查時間戳
    dt = df_log['time'].diff().dropna()
    dt_expected = 0.01  # 100 Hz
    dt_anomaly = (dt[dt > 1.5 * dt_expected]).count()
    print(f"Timestamp gaps (Δt > 15ms): {dt_anomaly} frames")

6.2.2 性能指標計算

def compute_performance_metrics(df_log, tc_status="ON"):
    """計算實驗的性能指標"""
    print(f"\n{'='*60}")
    print(f"PERFORMANCE METRICS (TC {tc_status})")
    print(f"{'='*60}\n")
    
    # 1. 加速性能
    v_0_to_60_mask = (df_log['v'] >= 0) & (df_log['v'] <= 60/3.6)
    if v_0_to_60_mask.sum() > 0:
        t_0_60 = df_log[v_0_to_60_mask]['time'].max() - df_log[v_0_to_60_mask]['time'].min()
        print(f"[1] Acceleration 0~60 km/h: {t_0_60:.2f} s")
    
    # 2. 打滑統計
    slip_avg = (df_log['slip_L'] + df_log['slip_R']) / 2
    slip_high_mask = slip_avg > 0.25
    t_slip_high = (slip_high_mask.sum()) * 0.01  # 100 Hz
    print(f"[2] High slip time (λ > 0.25): {t_slip_high:.2f} s")
    print(f"    Max slip: {slip_avg.max():.3f}")
    print(f"    Mean slip (steady): {slip_avg[v_0_to_60_mask].mean():.3f}")
    
    # 3. 縱向加速度
    a_x_mean = df_log['a_x'].mean()
    a_x_std = df_log['a_x'].std()
    print(f"[3] Longitudinal acceleration: {a_x_mean:.2f} ± {a_x_std:.2f} m/s²")
    
    # 4. 側向穩定性(如有彎道)
    if df_log['psi_dot'].abs().max() > 5 * 3.14159 / 180:  # > 5°/s
        psi_dot_peak = df_log['psi_dot'].abs().max() * 180 / 3.14159
        print(f"[4] Peak yaw rate: {psi_dot_peak:.1f} °/s")
    
    # 5. 扭矩歷史
    print(f"[5] Torque limiting:")
    print(f"    Max commanded: {df_log['T_cmd'].max():.0f} N·m")
    print(f"    Avg limited: {(df_log['T_cmd'] < df_log['T_driver'] * 0.95).mean() * 100:.1f}%")
    
    return {
        't_0_60': t_0_60 if v_0_to_60_mask.sum() > 0 else None,
        'slip_max': slip_avg.max(),
        'slip_mean': slip_avg.mean(),
        'a_x_mean': a_x_mean,
        'a_x_std': a_x_std,
    }

6.2.3 對比分析表

def compare_tc_on_off(df_log_off, df_log_on):
    """對比 TC OFF vs ON 的性能"""
    print("\n" + "="*80)
    print("COMPARISON: TC OFF vs ON")
    print("="*80 + "\n")
    
    m_off = compute_performance_metrics(df_log_off, "OFF")
    m_on = compute_performance_metrics(df_log_on, "ON")
    
    # 製表
    import pandas as pd
    comparison = pd.DataFrame({
        'Metric': [
            '0~60 km/h Time (s)',
            'Max Slip Ratio',
            'Mean Slip Ratio',
            'Longitudinal g (mean)',
            'Acceleration Smoothness (std)',
        ],
        'TC OFF': [
            f"{m_off['t_0_60']:.2f}" if m_off['t_0_60'] else "—",
            f"{m_off['slip_max']:.3f}",
            f"{m_off['slip_mean']:.3f}",
            f"{m_off['a_x_mean']:.2f}",
            f"{m_off['a_x_std']:.2f}",
        ],
        'TC ON': [
            f"{m_on['t_0_60']:.2f}" if m_on['t_0_60'] else "—",
            f"{m_on['slip_max']:.3f}",
            f"{m_on['slip_mean']:.3f}",
            f"{m_on['a_x_mean']:.2f}",
            f"{m_on['a_x_std']:.2f}",
        ],
    })
    
    # 計算改善百分比
    if m_off['t_0_60'] and m_on['t_0_60']:
        t_improve = (m_off['t_0_60'] - m_on['t_0_60']) / m_off['t_0_60'] * 100
        comparison.loc[0, 'Improvement %'] = f"{t_improve:+.1f}%"
    
    slip_improve = (m_off['slip_max'] - m_on['slip_max']) / m_off['slip_max'] * 100
    comparison.loc[1, 'Improvement %'] = f"{slip_improve:+.1f}%"
    
    print(comparison.to_string(index=False))
    print("\n")

6.3 數據導出與可視化 Script

import pandas as pd
import matplotlib.pyplot as plt

def plot_tc_analysis(df_log, save_path="tc_analysis.png"):
    """生成四子圖分析視圖"""
    fig, axes = plt.subplots(2, 2, figsize=(14, 8))
    
    # Subplot 1: 車速 & Slip
    ax1 = axes[0, 0]
    ax1.plot(df_log['time'], df_log['v'] * 3.6, 'b-', label='Vehicle Speed', linewidth=2)
    ax1_slip = ax1.twinx()
    ax1_slip.plot(df_log['time'], (df_log['slip_L'] + df_log['slip_R'])/2, 'r--', 
                  label='Avg Slip Ratio', linewidth=2)
    ax1.set_xlabel('Time (s)')
    ax1.set_ylabel('Speed (km/h)', color='b')
    ax1_slip.set_ylabel('Slip Ratio', color='r')
    ax1.grid(True, alpha=0.3)
    ax1.set_title('Vehicle Speed & Slip Ratio vs Time')
    
    # Subplot 2: 左右輪 Slip 對比
    ax2 = axes[0, 1]
    ax2.plot(df_log['time'], df_log['slip_L'], 'g-', label='Left Wheel', linewidth=2)
    ax2.plot(df_log['time'], df_log['slip_R'], 'orange', label='Right Wheel', linewidth=2)
    ax2.axhline(y=0.1, color='r', linestyle='--', alpha=0.5, label='Target (0.1)')
    ax2.axhline(y=0.2, color='r', linestyle='--', alpha=0.5, label='Target (0.2)')
    ax2.set_xlabel('Time (s)')
    ax2.set_ylabel('Slip Ratio')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    ax2.set_title('Left vs Right Wheel Slip')
    
    # Subplot 3: 縱向加速度
    ax3 = axes[1, 0]
    ax3.plot(df_log['time'], df_log['a_x'], 'purple', linewidth=2)
    ax3.set_xlabel('Time (s)')
    ax3.set_ylabel('Acceleration (m/s²)')
    ax3.grid(True, alpha=0.3)
    ax3.set_title(f'Longitudinal Acceleration (Mean: {df_log["a_x"].mean():.2f}, Std: {df_log["a_x"].std():.2f})')
    
    # Subplot 4: 扭矩命令
    ax4 = axes[1, 1]
    ax4.plot(df_log['time'], df_log['T_driver'], 'b-', label='T_driver', linewidth=2)
    ax4.plot(df_log['time'], df_log['T_cmd'], 'r-', label='T_cmd (limited)', linewidth=2)
    ax4.set_xlabel('Time (s)')
    ax4.set_ylabel('Torque (N·m)')
    ax4.legend()
    ax4.grid(True, alpha=0.3)
    ax4.set_title('Motor Torque: Driver Input vs TC Command')
    
    plt.tight_layout()
    plt.savefig(save_path, dpi=150)
    print(f"Figure saved to {save_path}")
    return fig

7. 程式碼框架

7.1 上位機 Python 主程序框架

"""
TC 開發主程序框架 (Python 3.8+)
用途: 離線分析 CAN 數據,測試控制邏輯
"""

import pandas as pd
import numpy as np
from pathlib import Path
import matplotlib.pyplot as plt

class TCController:
    def __init__(self, Kp=3.0, Ki=0.5, R_rear=0.32, R_front=0.32):
        self.Kp = Kp
        self.Ki = Ki
        self.R_rear = R_rear
        self.R_front = R_front
        self.integral = 0.0
        self.dt = 0.01  # 100 Hz
    
    def estimate_vehicle_speed(self, omega_fl, omega_fr, a_x, alpha=0.8):
        """車速估測 (Complementary Filter)"""
        v_wheel = (omega_fl + omega_fr) / 2 * self.R_front
        return max(v_wheel, 0.1)
    
    def compute_slip_ratio(self, omega_rl, omega_rr, v_safe):
        """計算後輪 slip ratio"""
        slip_L = (omega_rl * self.R_rear - v_safe) / max(v_safe, 0.1)
        slip_R = (omega_rr * self.R_rear - v_safe) / max(v_safe, 0.1)
        return slip_L, slip_R, (slip_L + slip_R) / 2
    
    def compute_target_slip(self, psi_dot):
        """自適應 target slip"""
        psi_dot_deg = abs(psi_dot) * 180 / 3.14159
        if psi_dot_deg < 5:
            return 0.18
        elif psi_dot_deg < 15:
            return 0.15 - 0.03 * (psi_dot_deg - 5) / 10
        else:
            return 0.10
    
    def pi_control(self, slip_error):
        """PI 控制器"""
        p_term = self.Kp * slip_error
        self.integral = max(-1.0, min(1.0, self.integral + slip_error * self.dt))
        i_term = self.Ki * self.integral
        return p_term + i_term
    
    def compute_torque_command(self, T_driver, slip_avg, slip_L, slip_R, 
                               v, psi_dot):
        """完整扭矩命令生成"""
        # 低速關閉 TC
        if v < 3 / 3.6:
            return T_driver, "LOW_SPEED"
        
        # 單輪過度打滑
        if abs(slip_L) > 0.4 or abs(slip_R) > 0.4:
            return 0.5 * T_driver, "SINGLE_WHEEL_SLIP"
        
        # 過度 yaw
        psi_dot_deg = abs(psi_dot) * 180 / 3.14159
        if psi_dot_deg > 25:
            return 0.3 * T_driver, "SPIN_RISK"
        
        # 正常 TC
        lambda_target = self.compute_target_slip(psi_dot)
        error = lambda_target - slip_avg
        delta_T = self.pi_control(error)
        T_cmd = T_driver * (1 + delta_T)
        T_cmd = max(0.3 * T_driver, min(T_driver, T_cmd))
        
        return T_cmd, f"NORMAL (λ*={lambda_target:.2f})"

def main():
    # 1. 讀取 CAN 數據
    log_file = Path("tc_test_20260313.csv")  # 修改為你的文件
    df_raw = pd.read_csv(log_file)
    
    # 2. 初始化控制器
    tc = TCController(Kp=3.5, Ki=0.5)
    
    # 3. 處理數據
    df_processed = df_raw.copy()
    df_processed['v'] = df_raw.apply(
        lambda row: tc.estimate_vehicle_speed(row['omega_fl'], row['omega_fr'], row['a_x']),
        axis=1
    )
    
    df_processed[['slip_L', 'slip_R', 'slip_avg']] = df_raw.apply(
        lambda row: pd.Series(tc.compute_slip_ratio(row['omega_rl'], row['omega_rr'], 
                                                      df_processed.loc[row.name, 'v'])),
        axis=1
    )
    
    # 4. 計算 TC 命令
    df_processed[['T_cmd', 'tc_status']] = df_raw.apply(
        lambda row: pd.Series(tc.compute_torque_command(
            row['T_driver'], df_processed.loc[row.name, 'slip_avg'],
            df_processed.loc[row.name, 'slip_L'], df_processed.loc[row.name, 'slip_R'],
            df_processed.loc[row.name, 'v'], row['psi_dot']
        )),
        axis=1
    )
    
    # 5. 性能分析
    print("Performance Analysis Complete!")
    
    # 6. 生成報告
    plot_tc_analysis(df_processed, save_path="tc_analysis_result.png")
    df_processed.to_csv("tc_processed_data.csv", index=False)

if __name__ == "__main__":
    main()

7.2 STM32 CubeIDE C 程式碼框架

/*
 * TC_Controller.h
 * STM32 CAN 實時 TC 控制模組
 */

#ifndef TC_CONTROLLER_H
#define TC_CONTROLLER_H

#include "stdint.h"
#include "stdbool.h"

// 結構體定義
typedef struct {
    float omega_fl, omega_fr, omega_rl, omega_rr;  // rad/s
    float a_x, a_y, psi_dot;  // IMU
    float T_driver, T_cmd;     // N·m
} TCInput_t;

typedef struct {
    float v;            // m/s
    float slip_L, slip_R, slip_avg;
    float lambda_target;
    float T_cmd_final;
    uint8_t tc_status;  // 0=OFF, 1=NORMAL, 2=SAFETY
} TCOutput_t;

typedef struct {
    float Kp, Ki;
    float integral;
    float R_rear, R_front;
    float dt;
} TCController_t;

// 公開函數
void TC_Init(TCController_t *ctrl, float Kp, float Ki);
void TC_Update(TCController_t *ctrl, TCInput_t *input, TCOutput_t *output);

#endif

/**
 * TC_Controller.c - 實作
 */

void TC_Init(TCController_t *ctrl, float Kp, float Ki) {
    ctrl->Kp = Kp;
    ctrl->Ki = Ki;
    ctrl->integral = 0.0f;
    ctrl->R_rear = 0.32f;
    ctrl->R_front = 0.32f;
    ctrl->dt = 0.01f;  // 100 Hz
}

static float estimate_vehicle_speed(float omega_fl, float omega_fr, float R_front) {
    float v = ((omega_fl + omega_fr) / 2.0f) * R_front;
    return (v > 0.1f) ? v : 0.1f;
}

static void compute_slip_ratio(float omega_rl, float omega_rr, float v, float R_rear,
                                float *slip_L, float *slip_R, float *slip_avg) {
    float v_safe = (v > 0.1f) ? v : 0.1f;
    *slip_L = ((omega_rl * R_rear) - v_safe) / v_safe;
    *slip_R = ((omega_rr * R_rear) - v_safe) / v_safe;
    *slip_avg = (*slip_L + *slip_R) / 2.0f;
}

static float compute_target_slip(float psi_dot_deg) {
    if (psi_dot_deg < 5.0f) {
        return 0.18f;
    } else if (psi_dot_deg < 15.0f) {
        return 0.15f - 0.03f * (psi_dot_deg - 5.0f) / 10.0f;
    } else {
        return 0.10f;
    }
}

void TC_Update(TCController_t *ctrl, TCInput_t *input, TCOutput_t *output) {
    // 1. 車速估測
    output->v = estimate_vehicle_speed(input->omega_fl, input->omega_fr, ctrl->R_front);
    
    // 2. Slip ratio 計算
    compute_slip_ratio(input->omega_rl, input->omega_rr, output->v, ctrl->R_rear,
                       &output->slip_L, &output->slip_R, &output->slip_avg);
    
    // 3. Target slip 自適應
    float psi_dot_deg = fabsf(input->psi_dot) * 180.0f / 3.14159f;
    output->lambda_target = compute_target_slip(psi_dot_deg);
    
    // 4. 扭矩命令 (Safety First)
    output->tc_status = 1;  // NORMAL
    
    if (output->v < 0.83f) {  // < 3 km/h
        output->T_cmd_final = input->T_driver;
        output->tc_status = 0;  // OFF
    } 
    else if (fabsf(output->slip_L) > 0.4f || fabsf(output->slip_R) > 0.4f) {
        output->T_cmd_final = input->T_driver * 0.5f;
        output->tc_status = 2;  // SAFETY
    }
    else if (psi_dot_deg > 25.0f) {
        output->T_cmd_final = input->T_driver * 0.3f;
        output->tc_status = 2;  // SAFETY
    }
    else {
        // PI 控制
        float error = output->lambda_target - output->slip_avg;
        float p_term = ctrl->Kp * error;
        
        ctrl->integral += error * ctrl->dt;
        if (ctrl->integral > 1.0f) ctrl->integral = 1.0f;
        if (ctrl->integral < -1.0f) ctrl->integral = -1.0f;
        float i_term = ctrl->Ki * ctrl->integral;
        
        float delta_T = p_term + i_term;
        float T_raw = input->T_driver * (1.0f + delta_T);
        
        // 飽和限縮
        float T_min = input->T_driver * 0.3f;
        float T_max = input->T_driver * 1.0f;
        output->T_cmd_final = (T_raw > T_max) ? T_max : ((T_raw < T_min) ? T_min : T_raw);
    }
    
    output->T_cmd = output->T_cmd_final;
}

附錄 A: 常見問題與調試

Q1: Slip ratio 經常偏高(> 0.5),控制不穩定

原因可能

  • 輪胎半徑 R 估測錯誤
  • 前輪打滑(後驅時不應該發生,除非彎度很大)
  • 輪速感測器信號雜訊大

排查

  1. 用非驅動輪直線加速,手工驗證 R 值
  2. 比較 FL/FR 輪速,差異 > 5% 表示有問題
  3. 加低通濾波到輪速訊號

Q2: TC 限縮扭矩後加速反而變慢

原因可能

  • Target slip 設定太低
  • Kp/Ki 過大,過度限縮

解決

  1. 逐次提高 target slip (0.15 → 0.18 → 0.20),找最快點
  2. 降低 Kp (5.0 → 3.5 → 2.5),觀察 slip 波動

Q3: 彎道時車輛過度 yaw,無法控制

原因

  • Target slip 在彎道時未降低
  • IMU yaw rate 閾值設定不當

解決

  1. 確認自適應邏輯正確執行(加 log 輸出)
  2. 提高彎道 target slip 降幅(0.15 → 0.08)

附錄 B: 建議進階項目

  1. 路面摩擦力估測:從加速度飽和點推估 μ,動態調整 target slip
  2. 單輪扭矩分配:左右驅動輪獨立控制,改善彎道穩定性
  3. Neural Network 增益:結合你之前研究的 observer,用 NN 預測最佳 Kp/Ki
  4. 實時濾波優化:應用 Kalman filter 融合前輪和 IMU 速度估測

最後建議:先用 Python 在桌上測試演算法邏輯(週 1-2),確認無誤後移植到 STM32(週 3-4),場測時邊跑邊調參數,逐次優化。祝你實驗順利! 🚗💨