# 空氣力學

# 卡爾曼濾波器 (Kalman Filter) 白話文與實戰

卡爾曼濾波器不是硬體，而是一套**「結合數學預測與感測器測量，找出最優解」**的演算法。

## 1. 白話情境：開車過隧道 (GPS 雜訊 vs. 時速表)

想像你正在開車，你想知道自己「精確的位置」。你手上有兩個資訊來源：
1. **你的油門與時速表 (數學預測 / 物理模型)：** 你知道自己前一秒在哪，也知道現在時速 60 km/h，所以你可以「猜」出自己現在大概在哪。但如果剛好遇到逆風或上坡，這個預測就不太準。
2. **車上的 GPS (感測器測量)：** GPS 會告訴你座標，但它有雜訊，可能會有 $\pm 5$ 公尺的誤差。

**卡爾曼濾波器在做什麼？**
當你開進隧道（GPS 訊號變弱，雜訊超大），它會選擇**相信你的時速表預測多一點**；當你開在空曠的高速公路（GPS 超準），它會選擇**相信 GPS 多一點**。
它透過一個叫做**「卡爾曼增益 (Kalman Gain)」**的比例，不斷在這兩者之間抓平衡，算出一個比單看 GPS 或單看時速表都還要準確的「真實位置」。

---

## 2. 核心運作邏輯：預測 (Predict) 與 更新 (Update)

卡爾曼濾波器永遠在做這兩件事的無限迴圈：

1. **預測 (Predict)「看未來」：** 根據上一秒的狀態和物理公式，猜測現在的狀態。同時也會估算這次「猜測的誤差有多大」。
2. **更新 (Update)「面對現實」：** 拿到感測器熱騰騰的數據後，把「猜測值」跟「測量值」做加權平均（權重就是卡爾曼增益）。

---

## 3. 卡爾曼濾波器的 5 條神聖方程式

雖然看起來很可怕，但只要知道它們對應的意義，寫程式時照抄即可。這裡使用的是矩陣形式（因為通常會同時計算位置、速度等多個變數）。

### ➡️ 步驟一：預測 (Predict)
* **狀態預測 (猜測現在位置)：** $$\hat{x}_{k}^{-} = A \hat{x}_{k-1} + B u_k$$
* **誤差協方差預測 (猜測的不確定性有多大)：**
  $$P_{k}^{-} = A P_{k-1} A^T + Q$$
  *(註：$Q$ 代表系統模型本身的雜訊，也就是你對物理公式的不信任程度)*

### ➡️ 步驟二：更新 (Update)
* **計算卡爾曼增益 (決定要相信模型還是感測器)：**
  $$K_k = P_{k}^{-} H^T (H P_{k}^{-} H^T + R)^{-1}$$
  *(註：$R$ 代表感測器的測量雜訊，也就是你對儀器的不信任程度)*
* **狀態更新 (算出最終的最優估計值)：**
  $$\hat{x}_k = \hat{x}_{k}^{-} + K_k (z_k - H \hat{x}_{k}^{-})$$
* **誤差協方差更新 (更新當前的不確定性，留給下一回合用)：**
  $$P_k = (I - K_k H) P_{k}^{-}$$

---

## 4. MATLAB 1D 實戰：過濾超晃的感測器雜訊

我們來寫一個最簡單的 1D 範例（純量，不用矩陣）。假設我們正在測量一個恆定電壓（真實值是 1.2V），但電壓表的雜訊非常大，看看卡爾曼濾波器怎麼把它壓平！

```matlab
% 1. 基本設定
N = 100;                % 模擬 100 個時間點
true_voltage = 1.2;     % 真實電壓 (我們假裝不知道)
measured_v = true_voltage + 0.1 * randn(1, N); % 加入常態分佈雜訊的測量值

% 2. 初始化卡爾曼濾波器變數
x_est = 0;              % 初始猜測的狀態 (隨便猜一個 0)
P = 1;                  % 初始的不確定性 (設 1 代表很不確定)

% 兩個魔法調音旋鈕：Q 和 R
Q = 1e-5;               % 過程雜訊 (我們相信電壓是恆定的，所以設很小)
R = 0.01;               % 測量雜訊 (我們知道電壓表很爛，雜訊大概是 0.1^2)

% 準備陣列來存畫圖用的資料
kalman_result = zeros(1, N);

% 3. 開始卡爾曼濾波器迴圈 (Predict -> Update)
for k = 1:N
    
    % --- [預測階段 Predict] ---
    % 因為是測量恆定電壓，沒有控制力輸入，也沒有物理移動，所以 A=1, B=0
    x_pred = x_est;             % 預測下一個狀態跟現在一樣
    P_pred = P + Q;             % 預測不確定性增加一點點
    
    % --- [更新階段 Update] ---
    % 拿到最新的測量值 z_k
    z_k = measured_v(k);
    
    % 計算卡爾曼增益 (H=1)
    K = P_pred / (P_pred + R);
    
    % 更新最優估計狀態
    x_est = x_pred + K * (z_k - x_pred);
    
    % 更新不確定性
    P = (1 - K) * P_pred;
    
    % 存起來準備畫圖
    kalman_result(k) = x_est;
end

% 4. 畫圖比較
figure;
hold on;
plot(1:N, true_voltage * ones(1,N), 'g-', 'LineWidth', 2); % 真實值 (綠線)
plot(1:N, measured_v, 'r.', 'MarkerSize', 10);             % 測量值 (紅點)
plot(1:N, kalman_result, 'b-', 'LineWidth', 2);            % 卡爾曼濾波結果 (藍線)
hold off;

title('卡爾曼濾波器 1D 電壓測量實戰');
xlabel('時間步長');
ylabel('電壓 (V)');
legend('真實值', '感測器測量值 (含雜訊)', '卡爾曼濾波後的最優估計');
grid on;
```

# 自動化參數掃描 (Parameter Sweeping 迴圈)

## 自動化參數掃描 (Parameter Sweeping 迴圈)

當你需要測試同一組模型，但想觀察某個參數（例如 `Kp`）在不同數值下的變化時，使用 `for` 迴圈是最高效的作法。

```matlab
% 1. 準備工作
Kp_values = [0.5, 1.0, 1.5, 2.0, 2.5];  % 設定你想測試的 5 組 Kp 數值
num_tests = length(Kp_values);          % 計算總共要跑幾次 (這裡會是 5)

% 2. 開啟畫布並鎖定
figure;
hold on;  % 鎖定畫布，讓接下來迴圈裡的每一條線都畫在同一張圖上

% 3. 準備一個空的陣列來裝圖例名稱 (進階美化技巧)
legend_labels = cell(1, num_tests); 

% 4. 開始自動化迴圈
for i = 1:num_tests
    
    % (A) 更新參數
    Kp = Kp_values(i);  % 把現在要測的數值丟給 Kp
    
    % (B) 執行模擬 (限制步長確保畫圖平滑)
    out = sim('my_model', 'MaxStep', '0.01'); 
    
    % (C) 抓取資料
    t = out.tout;
    y = out.sim_result;
    
    % (D) 畫上這一回合的線
    plot(t, y, 'LineWidth', 1.5);
    
    % (E) 記錄這條線的名字，等一下給圖例 (Legend) 使用
    legend_labels{i} = ['Kp = ', num2str(Kp)]; 
    
end

% 5. 迴圈結束後，進行圖表美化
hold off; % 解除鎖定
title('不同 Kp 參數之系統響應掃描');
xlabel('時間 (s)');
ylabel('系統輸出');
legend(legend_labels); % 自動貼上我們剛剛在迴圈裡準備好的標籤名稱！
grid on;

% 6. 自動存檔 (可選)
exportgraphics(gcf, 'Kp_Sweep_Result.png', 'Resolution', 300);
```

💡 程式碼亮點解析：
hold on 放外面：這非常重要！如果你把 hold on 寫在迴圈裡面，有時候 MATLAB 會傻掉。放在迴圈外面，就像是先在桌上釘好一張圖畫紙，然後迴圈裡的每次 plot 就是拿不同顏色的筆在上面畫畫。

num2str(Kp)：這是一個超實用的函數。因為 Kp 是數字，但圖例 (legend) 只能吃「文字字串」。所以我們用 num2str 把數字變成文字，這樣圖標就會自動顯示 Kp = 0.5, Kp = 1.0 等等，非常聰明！

## 將模擬數據匯出至 Excel (Export to Excel)

跑完 Simulink 後，如果想把時間軸（Time）和數據（Data）存成 `.xlsx` 檔，最專業的做法是先將資料轉成「表格 (Table)」，再加上欄位名稱，最後匯出。

```matlab
% 1. 確保你已經從 out 物件中抓出數據
t = out.tout;               % 時間資料 (假設是 1000x1 的直行陣列)
y = out.sim_result;         % 模擬數據 (假設是 1000x1 的直行陣列)

% 2. 將時間與數據合併成一個大的矩陣 (用中括號 [ ] 並排)
% 注意：t 和 y 必須都是「直行 (Column vector)」，長度要一樣！
data_matrix = [t, y];

% 3. 將矩陣轉換成 Table 格式，並設定 Excel 的欄位名稱 (標題)
% 假設第一欄叫 'Time_sec'，第二欄叫 'Motor_Speed_RPM'
T = array2table(data_matrix, 'VariableNames', {'Time_sec', 'Motor_Speed_RPM'});

% 4. 寫入 Excel 檔案
% 第一個參數是剛建立的表格 T，第二個參數是你想要的檔名
writetable(T, 'My_Simulation_Data.xlsx');

disp('✅ 資料已成功存入 Excel！');
```

---

### 💡 進階技巧：把迴圈掃描的結果存到「不同的工作表 (Sheet)」裡

如果你結合了前面的 `for` 迴圈（參數掃描），你可以利用 `Sheet` 屬性，把不同參數的結果存在同一個 Excel 檔案的不同分頁裡！

```matlab
% 假設這段寫在迴圈裡，i 是迴圈的計數器，Kp 是當前的參數
sheet_name = ['Kp_', num2str(Kp)];  % 自動生成分頁名稱，例如 'Kp_1.5'

% 將數據寫入指定的 Sheet
writetable(T, 'Kp_Sweep_Data.xlsx', 'Sheet', sheet_name);
```

# 牽引力控制核心：Pacejka 魔術公式 (Magic Formula) 解析

# 牽引力控制核心：Pacejka 魔術公式 (Magic Formula) 解析

這是一個非常核心且內行的問題！在 FSAE 或是任何高階車輛動態控制（ABS、TC、ESC）領域，Pacejka 輪胎模型（俗稱魔術公式 Magic Formula, MF）絕對是繞不開的「聖經級」模型。

既然你正在做牽引力控制（TC），理解輪胎如何把馬達的扭矩轉換成推動車身的力，是最基礎也最重要的一步。以下幫你把這個模型拆解成容易咀嚼的重點：

## 1. 什麼是 Pacejka 魔術公式？

由荷蘭學者 Hans B. Pacejka 提出，它是一個**半經驗模型（Semi-empirical Model）**。

意思是：它並不是從嚴格的物理摩擦學推導出來的，而是拿著大量在測試機台（例如 FSAE 常用的 TTC 輪胎測試數據）上跑出來的數據，用一組巧妙的三角函數去「硬配適（Curve Fitting）」出來的曲線。

因為這組公式不僅能極度精準地擬合出輪胎的真實反應，而且曲線十分平滑（有利於電腦計算與微分），所以業界驚呼這簡直是「魔術」，故得名 Magic Formula。

## 2. 魔術公式的核心數學式

無論是算縱向力（加速/煞車）、橫向力（轉向）還是回正力矩，魔術公式的基本原型都是同一條長相奇特的方程式：

$$
y(x) = D \sin(C \arctan(Bx - E(Bx - \arctan(Bx))))
$$

* **輸入值 $x$：** 通常是縱向滑移率 $\lambda$（或稱 $\kappa$）或是側偏角 $\alpha$。
* **輸出值 $y$：** 通常是縱向力 $F_x$ 或是橫向力 $F_y$。

## 3. 解碼神奇的四大參數（B, C, D, E）

這條公式之所以厲害，是因為它把複雜的曲線拆成四個具有「幾何意義」的常數。理解這四個常數，你就看懂了輪胎的靈魂：

* **D (Peak Factor - 峰值因子)：**
  * **意義：** 代表曲線的最高點，也就是輪胎能提供的最大抓地力（對應 $\mu_{peak}$）。
  * **物理影響：** $D$ 越大，車子能承受的極限加速度或過彎 G 值就越高。
* **C (Shape Factor - 形狀因子)：**
  * **意義：** 控制曲線兩端漸近線的高度，決定了過了極限打滑點之後，抓地力下降的幅度。
  * **物理影響：** 對於縱向力 $F_x$，$C$ 通常落在 1.65 左右；對於橫向力 $F_y$，$C$ 約為 1.3。
* **B (Stiffness Factor - 剛性因子)：**
  * **意義：** 控制曲線在原點（$x=0$）附近的斜率（Slope）。
  * **物理影響：** 代表輪胎的縱向剛性或側向剛性。$B$ 越大，曲線爬升越陡，車手會感覺這條輪胎反應非常直接、神經質；$B$ 越小，輪胎反應越遲鈍、漸進。
  *(註：原點斜率其實是 $B \times C \times D$ 三個乘起來的結果。)*
* **E (Curvature Factor - 曲率因子)：**
  * **意義：** 控制曲線在達到峰值（$D$）附近的彎曲程度。
  * **物理影響：** 決定了輪胎在瀕臨極限時，是「瞬間失去抓地力」（Snap）還是「慢慢滑出去」（Progressive）。

## 4. 與 Simulink 模型的關聯

在我們上一篇筆記中，你用的 Simulink QuarterCar 模型裡面的 `roadCoeffs`，其實是 Pacejka 的極度簡化版：

$$
\mu_x = c \sin(b \arctan(a\lambda))
$$

如果你把這個簡化版跟完整的魔術公式對照：
* **你的 $c$** 就是 **$D$**（峰值 $\mu$）。
* **你的 $b$** 就是 **$C$**（形狀因子）。
* **你的 $a$** 就是 **$B$**（剛性因子）。

**差異：** 你的簡化版省略了 $E$（令 $E=0$）。這在一般控制演算法的初步驗證上已經很夠用了，但在精確的車輛動態模擬（如 CarSim 或圈速模擬器）中，通常會帶入包含 $E$ 以及考慮垂直荷重 $F_z$ 動態變化的完整版公式。

## 5. Pacejka 模型的優缺點

| 痛點 / 特性 | 說明 | 對應到你的 FSAE TC 專案 |
| :--- | :--- | :--- |
| **精準度極高** | 穩態（Steady-state）下，對實車輪胎行為的描述幾乎無懈可擊。 | 用來建構 Simulink 的 Plant（受控體）非常完美。 |
| **需要龐大測試數據** | 模型本身沒有物理意義，你無法用「胎寬、橡膠配方」來算出 $B, C, D, E$，只能上機台硬測。 | 必須依賴 FSAE TTC 提供的原始數據來擬合參數。 |
| **運算量大、難以解析推導** | 對微控制器來說，算 $\sin$ 和 $\arctan$ 很耗資源；且在數學上很難直接反推或做線性化。 | 這就是為什麼 Savaresi 的書在設計控制器時，喜歡改用數學更簡單的 Burckhardt 模型或直接局部線性化。 |

---

## 透過 MATLAB 擬合 TTC 數據（Curve Fitting）做出車隊這套輪胎的 Pacejka

在實務上，FSAE 車隊通常會花很多時間在處理 TTC（輪胎測試聯盟）給的龐大數據檔。
把 FSAE TTC 給的實測數據（CSV/MAT檔），透過 MATLAB 擬合（Curve Fitting）出專屬於你們車隊這套輪胎的 Pacejka $B,C,D,E$ 參數。

這支程式使用了 MATLAB 內建的非線性最小平方法（`lsqcurvefit`），並內建了防呆機制：如果你還沒準備好實際的 TTC CSV 檔案，它會自動生成一組帶有雜訊的模擬數據，讓你立刻就能執行並看到擬合效果與圖表。


``` matlab
% =========================================================================
% FSAE TTC 輪胎數據 Pacejka 魔術公式 (Magic Formula) 曲線擬合腳本
% 適用於提取縱向力 (Fx) 或 縱向摩擦係數 (Mu_x) 的 B, C, D, E 參數
% =========================================================================

clear; clc; close all;

%% 1. 讀取輪胎數據 (載入 TTC CSV 或產生模擬數據)
filename = 'TTC_tire_data.csv';

if isfile(filename)
    % 如果你有真實的 TTC 數據，讀取 CSV
    fprintf('讀取真實數據: %s\n', filename);
    data = readtable(filename);
    % 假設你的 CSV 欄位名稱為 SlipRatio 和 Mu_x (請依實際情況修改)
    kappa = data.SlipRatio; 
    mu_x = data.Mu_x;       
else
    % 若無檔案，產生一組帶有雜訊的模擬實測數據供測試
    fprintf('找不到 %s，產生模擬測試數據...\n', filename);
    kappa = linspace(-0.3, 0.3, 200)'; % 滑移率從 -30% 到 +30%
    
    % 設定一組虛擬的真實參數 [B=10, C=1.65, D=1.2, E=0.05]
    true_B = 10; true_C = 1.65; true_D = 1.2; true_E = 0.05;
    
    % 產生理想曲線並加入隨機雜訊 (模擬機台測試數據)
    ideal_mu = true_D * sin(true_C * atan(true_B.*kappa - true_E.*(true_B.*kappa - atan(true_B.*kappa))));
    noise = 0.05 * randn(size(kappa)); 
    mu_x = ideal_mu + noise;
end

%% 2. 定義 Pacejka 魔術公式模型
% x(1) = B (Stiffness)
% x(2) = C (Shape)
% x(3) = D (Peak)
% x(4) = E (Curvature)
% xdata = kappa (滑移率)
magic_formula = @(x, xdata) x(3) .* sin(x(2) .* atan(x(1).*xdata - x(4).*(x(1).*xdata - atan(x(1).*xdata))));

%% 3. 設定初始猜測值與上下界
% 合理的初始猜測值能幫助演算法更快、更準確收斂
x0 = [12.0, 1.5, 1.0, 0.0]; % 初始猜測: [B, C, D, E]

% 設定參數的上下界 (Lower Bound & Upper Bound)，避免擬合出沒有物理意義的負值或極端值
lb = [1.0,  1.0,  0.5, -1.0]; % 下界
ub = [20.0, 2.0,  2.0,  1.0]; % 上界

%% 4. 執行曲線擬合 (Curve Fitting)
% 使用 lsqcurvefit (需要 Optimization Toolbox)
options = optimoptions('lsqcurvefit', 'Display', 'iter', 'StepTolerance', 1e-6);

fprintf('\n開始進行 Pacejka 參數擬合...\n');
[x_opt, resnorm] = lsqcurvefit(magic_formula, x0, kappa, mu_x, lb, ub, options);

% 提取擬合結果
B_fit = x_opt(1);
C_fit = x_opt(2);
D_fit = x_opt(3);
E_fit = x_opt(4);

fprintf('\n=== 擬合結果 ===\n');
fprintf('B (剛性因子) = %.4f\n', B_fit);
fprintf('C (形狀因子) = %.4f\n', C_fit);
fprintf('D (峰值因子) = %.4f\n', D_fit);
fprintf('E (曲率因子) = %.4f\n', E_fit);
fprintf('殘差平方和   = %.4f\n', resnorm);

%% 5. 繪圖驗證擬合效果
kappa_dense = linspace(min(kappa), max(kappa), 500)'; % 產生高密度 X 軸以繪製平滑曲線
mu_x_fit = magic_formula(x_opt, kappa_dense);         % 計算擬合出的曲線

figure('Name', 'Pacejka Magic Formula Fitting', 'Color', 'w');
plot(kappa, mu_x, 'k.', 'MarkerSize', 8, 'DisplayName', 'TTC 原始測試數據'); % 畫出散點圖
hold on;
plot(kappa_dense, mu_x_fit, 'r-', 'LineWidth', 2, 'DisplayName', 'Pacejka 擬合曲線'); % 畫出擬合線
hold off;

grid on;
title('FSAE TTC 輪胎數據: 縱向滑移率 vs 摩擦係數');
xlabel('縱向滑移率 \lambda (Slip Ratio)');
ylabel('縱向摩擦係數 \mu_x (F_x / F_z)');
legend('Location', 'southeast');
xlim([min(kappa) max(kappa)]);

% 在圖表上標示出計算出的參數
annotation_str = sprintf('B = %.2f\nC = %.2f\nD = %.2f\nE = %.2f', B_fit, C_fit, D_fit, E_fit);
text(min(kappa)*0.8, max(mu_x)*0.8, annotation_str, 'BackgroundColor', 'w', 'EdgeColor', 'k', 'FontSize', 10);

```

#### 執行前的小提醒：
1.  Toolbox 需求： 這支腳本使用了`lsqcurvefit`，你的 MATLAB 需要安裝 `Optimization Toolbox` 才能執行。
2.   導入真實數據： 當你要換成真實 TTC 數據時，只需把你的檔名填入 `filename = '你的檔名.csv';`，並確保 `kappa` 和 `mu_x` 抓取的是正確的欄位（TTC 的原始檔通常欄位名是 `SL` 代表**Slip Ratio**，`FX` 代表**縱向力**，記得要先把 $F_x$ 除以垂直荷重 $F_z$ 換算成 $\mu_x$ 再丟進來擬合會比較直觀）。

# 傅立葉轉換 (FFT) 白話文與頻譜分析實戰

# 傅立葉轉換 (FFT) 白話文與頻譜分析實戰

## 1. 白話解析：果汁機理論 🍓🍌
想像你面前有一杯打好的「綜合果汁」（這就是**時域訊號**，所有東西都混在一起了）。
你喝了一口，覺得很好喝，但你不知道裡面到底加了什麼。

**傅立葉轉換 (FFT)** 就像是一台「逆向果汁機」。你把果汁倒進去，它會瞬間幫你分離出：
* 3 顆草莓 🍓
* 1 根香蕉 🍌
* 0.5 顆蘋果 🍎

這份「食譜」就是**頻譜圖 (Spectrum)**！
在頻譜圖上，**X 軸是水果種類 (頻率 Hz)**，**Y 軸是水果的數量 (振幅 Amplitude)**。透過 FFT，不管雜訊藏得再深，都會在頻譜圖上變成一根特別突出的長條，讓你一眼就看穿它！

---

## 2. MATLAB 頻譜分析標準 S.O.P. (超級重要 ⚠️)

在 MATLAB 中使用 `fft()` 函數非常簡單，**但初學者 100% 會卡在「如何把 FFT 的結果轉成人類看得懂的頻譜圖」**。

因為 `fft()` 算出來的結果是包含虛數的「雙邊對稱頻譜」，我們必須經過**取絕對值、正規化、切一半**這三個神聖步驟，才能畫出正確的圖。請直接把下面這段當成你的萬用模板！

```matlab
% 1. 建立測試訊號 (我們延續前面濾波器的例子)
fs = 1000;             % 取樣頻率 1000 Hz (一秒鐘抓 1000 個點)
t = 0 : 1/fs : 1-1/fs; % 建立 1 秒的時間軸
L = length(t);         % 訊號總長度 (總共有幾個點)

% 混合訊號：振幅為 2 的 5Hz 正弦波 + 振幅為 0.5 的 100Hz 雜訊
sig_raw = 2 * sin(2 * pi * 5 * t) + 0.5 * sin(2 * pi * 100 * t);

% --- 開始進入 FFT 照妖鏡流程 ---

% 2. 執行快速傅立葉轉換
Y = fft(sig_raw);

% 3. 正規化與單邊頻譜轉換 (魔法步驟 🪄)
% (A) 取絕對值，並除以總長度 L 來還原真實振幅
P2 = abs(Y / L);       

% (B) 因為頻譜是左右對稱的，我們只需要看正頻率 (前半段)
P1 = P2(1 : L/2+1);    

% (C) 把被切掉的後半段能量補回前半段 (除了直流成分也就是第 1 個點以外，其餘乘 2)
P1(2:end-1) = 2 * P1(2:end-1); 

% 4. 建立對應的頻率 X 軸 (從 0 Hz 到 奈奎斯特頻率 fs/2)
f = fs * (0:(L/2)) / L;

% 5. 畫出頻譜圖
figure;

% 上半部：人類看不懂的時域圖
subplot(2,1,1);
plot(t, sig_raw);
title('時域圖 (Time Domain) - 完全看不出裡面有什麼');
xlabel('時間 (秒)');
ylabel('振幅');
grid on;

% 下半部：一目瞭然的頻域圖
subplot(2,1,2);
plot(f, P1, 'LineWidth', 2, 'Color', 'r');
title('頻域圖 (Frequency Domain) - 照妖鏡現形！');
xlabel('頻率 (Hz)');
ylabel('振幅 (Amplitude)');
xlim([0 150]); % 為了看清楚，我們只顯示 0 到 150 Hz 的區間
grid on;
```

---

## 3. 圖表解讀與實戰意義

當你執行上面這段程式碼後，看著下半部的紅線圖，你會發現兩件非常神奇的事情：
1. **精準定位：** 在 X 軸的 **5 Hz** 和 **100 Hz** 的地方，直直地插著兩根像電線桿一樣的線。這就代表 FFT 成功抓出了這兩個隱藏的頻率！
2. **還原振幅：** 你去看這兩根電線桿的高度 (Y 軸)，5 Hz 的高度剛好是 **2**，100 Hz 的高度剛好是 **0.5**。這完美對應了我們當初建立訊號時設定的倍數！

**工程應用：** 實務上，如果你測量一台馬達的震動，然後把訊號丟進這個 FFT 模板。如果你發現在 60 Hz 的地方有一根超高的長條，你立刻就能跟老闆報告：「老闆，我們的系統被台灣的市電 (60Hz) 干擾了！」；然後你就可以回去翻前面的筆記，用一個 **60Hz 的帶阻濾波器 (Band-stop Filter)** 把它精準幹掉！

# 無線電系統概述

本文件整理賽道通訊系統的硬體配置，包含 **IMSA 專業參考方案** 與 **DIY 自行組裝平替版** 的硬體組成。

---

## 🔝 參考方案：IMSA 專業套裝
針對專業賽事設計，具備高可靠性與標準 IMSA 介面，支援車手與技師長雙向對講。

- **官方連結**：[IMSA Driver and Crew Chief Racing Kit](https://www.ruggedradios.com/products/imsa-driver-and-crew-chief-racing-kit-with-r1-radios?srsltid=AfmBOoqZGEKhh50HJJUjsLVVHud_ozfrwgvSu8pzVzk63k6DiDH3SEGM)
- **核心設備**：R1 數位/類比雙模無線電、IMSA 標準線組、降噪耳機。

![IMSA Kit](https://hackmd.io/_uploads/BJnOHXtjZx.png)

---

## 🛠️ 平替版方案：DIY 配件組合
透過通用型配件達成通訊功能，適合入門或練習使用。

### 1. 核心轉接與控制
所有設備的中樞，用於連接無線電主機與音訊配件。
- **品名**：[K 頭手台通用手咪線 DIY](https://item.taobao.com/item.htm?abbucket=16&id=603278787070)
- **特點**：相容寶鋒等 K 頭無線電，可擴展 PTT 按鈕。
![K-Plug](https://hackmd.io/_uploads/H1mKUmtsWg.png)
![image](https://hackmd.io/_uploads/r1qdWNFibx.png)

### 2. 車手耳機
用勞保耳機替代，入耳式設計，提供物理隔音，價格便宜。
- **品名**：[線雙入耳式 工廠耳塞](https://item.taobao.com/item.htm?from=cart&id=624293413942)

![Earbuds](https://hackmd.io/_uploads/ryutvmFobl.png)

### 3. 頭盔內收音麥克風
體積小巧，可固定於頭盔下巴處。
- **品名**：[領夾式教師教學專用有線麥克風](https://detail.tmall.com/item.htm?from=cart&id=750651428197)

![Mic](https://hackmd.io/_uploads/H14Jk4YjWg.png)

---

## 📊 方案快速對比

| 比較維度 | IMSA 專業方案 | DIY 平替方案 |
| -------- | -------- | -------- |
| **可靠性** | ⭐️⭐️⭐️⭐️⭐️ (賽事專用) | ⭐️⭐️⭐️ (接頭需自行補強) |
| **安裝便利性** | 插拔即用，標準接口 | 需確認接頭規格相容性 |
| **成本預算** | 高 (美金計價) | 低 (人民幣/台幣計價) |

:::success
### 💡 實戰小建議
在使用 **DIY 平替方案** 時，建議在 3.5mm 或 K 頭連接處使用**電工膠布**或**熱縮套管**進行二次固定，防止賽車行進間的高頻震動導致接頭鬆脫。
:::

# 經典頻率濾波器 (低通、高通、帶通) 實戰速查表

在處理感測器訊號、音訊或心電圖 (ECG) 時，我們的訊號通常會混雜著各種不同頻率的波。透過濾波器，我們可以精準地「切出」我們想要的頻段。

---

## 1. 三大濾波器白話解析

### 🐢 低通濾波器 (Low-pass Filter, LPF)
* **保鑣規則：** 「低頻率 (變化慢的) 通過，高頻率 (變化快的) 擋下來！」
* **白話情境：** 就像你隔著牆壁聽隔壁開趴的音樂，你只會聽到「咚、咚、咚」的低音貝斯，因為高音（人聲、鈸聲）被牆壁（低通濾波器）擋住了。
* **工程用途：** **消除雜訊、平滑訊號。** 感測器的雜訊通常是高頻的毛刺（上下劇烈跳動），用 LPF 切掉高頻，訊號就會變得非常平滑。

### 🐇 高通濾波器 (High-pass Filter, HPF)
* **保鑣規則：** 「高頻率 (變化快的) 通過，低頻率 (變化慢的) 擋下來！」
* **白話情境：** 就像照相機的「邊緣檢測」。一整面平滑的白牆（低頻）會變成黑色的，但牆壁邊緣的銳利輪廓（高頻/劇烈變化）會被保留下來凸顯。
* **工程用途：** **消除基準線飄移 (Baseline Wander)、捕捉突發事件。** 比如移除重力造成的長期固定偏差，只保留瞬間的撞擊震動。

### 🎯 帶通濾波器 (Band-pass Filter, BPF)
* **保鑣規則：** 「只有某個『特定頻率範圍』的可以通過，太低或太高的通通擋掉！」
* **白話情境：** 收音機調頻！空氣中充滿了幾千個頻道的電磁波，當你轉到 FM 91.7，你的收音機就是啟動了一個 BPF，只讓 91.7 MHz 附近的頻率進來。
* **工程用途：** **提取特定特徵。** 例如人聲大約落在 300 Hz 到 3400 Hz 之間，用 BPF 把這個區間切出來，就能消除超低頻的風聲和超高頻的電流嘶嘶聲。

---

## 2. MATLAB 濾波器實戰：巴特沃斯濾波器 (Butterworth)

在 MATLAB 裡，最經典、最平滑、最常用的就是巴特沃斯濾波器。
⚠️ **核心觀念：奈奎斯特頻率 (Nyquist Frequency)**
在設計數位濾波器時，MATLAB 需要的頻率參數通常是「正規化」的。你必須先算出你的**取樣頻率 (Sampling Rate, $f_s$) 的一半**，這就是奈奎斯特頻率。

```matlab
% 1. 創造一個混合訊號 (低頻主訊號 + 高頻雜訊)
fs = 1000;                     % 取樣頻率 1000 Hz (每秒抓 1000 個點)
t = 0 : 1/fs : 1 - 1/fs;       % 建立 1 秒的時間軸

f_low = 5;                     % 5 Hz 的緩慢波 (我們想要的訊號)
f_high = 100;                  % 100 Hz 的快速波 (我們討厭的雜訊)

% 混合在一起的原始訊號
sig_raw = sin(2 * pi * f_low * t) + 0.5 * sin(2 * pi * f_high * t);

% --- 準備濾波器參數 ---
nyquist = fs / 2;              % 奈奎斯特頻率 (1000/2 = 500 Hz)
order = 4;                     % 濾波器階數 (數字越大，切得越乾淨，但運算越慢且可能變形)

% 2. 設計並應用【低通濾波器 LPF】(砍掉 50Hz 以上的)
cutoff_LPF = 50 / nyquist;     % 設定截止頻率 $f_c$ 為 50 Hz，並正規化
[b_low, a_low] = butter(order, cutoff_LPF, 'low');
sig_lpf = filtfilt(b_low, a_low, sig_raw);  % 使用 filtfilt 可以達成「零相位延遲」(訊號不會往後平移)

% 3. 設計並應用【高通濾波器 HPF】(砍掉 20Hz 以下的)
cutoff_HPF = 20 / nyquist;     % 設定截止頻率為 20 Hz
[b_high, a_high] = butter(order, cutoff_HPF, 'high');
sig_hpf = filtfilt(b_high, a_high, sig_raw);

% 4. 設計並應用【帶通濾波器 BPF】(只保留 80Hz 到 120Hz 之間的)
cutoff_BPF = [80 120] / nyquist; % 帶通需要輸入兩個數字的陣列
[b_band, a_band] = butter(order, cutoff_BPF, 'bandpass');
sig_bpf = filtfilt(b_band, a_band, sig_raw);

% 5. 畫圖大車拚
figure;
subplot(4,1,1); plot(t, sig_raw); title('1. 原始混合訊號 (5Hz + 100Hz)'); grid on;
subplot(4,1,2); plot(t, sig_lpf, 'r'); title('2. 低通濾波結果 (只剩 5Hz)'); grid on;
subplot(4,1,3); plot(t, sig_hpf, 'g'); title('3. 高通濾波結果 (只剩 100Hz)'); grid on;
subplot(4,1,4); plot(t, sig_bpf, 'm'); title('4. 帶通濾波結果 (精準抓出 100Hz)'); grid on;
```

### 💡 實戰隱藏秘訣：`filter` vs `filtfilt`
如果你去查網路，很多人會教你用 `filter(b, a, data)`。但強烈建議在「非即時（已經錄好整段數據）」的情況下，一律改用 `filtfilt(b, a, data)`！
因為一般濾波器會造成訊號「時間延遲（相位偏移）」，導致濾完波的波峰跟原始波峰對不起來。`filtfilt` 會正向濾一次、反向再濾一次，完美消除延遲，讓你的波形前後完全對齊！

# Simulink 檔案格式轉換 (.mdl 轉 .slx)

在處理比較舊的 Simulink 模型時，我們經常需要將舊版的 `.mdl` 升級成現代的 `.slx` 格式。這裡提供兩種最常用的轉換方式：

## 1. 滑鼠點擊法 (適合只有一兩個檔案時)

這是最直覺的方法，就像用 Word 把 `.doc` 存成 `.docx` 一樣：

1. 在 MATLAB 裡直接對著 `.mdl` 檔案點兩下，把它打開。
2. 在 Simulink 視窗的左上角選單，點擊 **File (檔案)** -> **Save As... (另存新檔)**。
   *(如果是較新版的 MATLAB 介面，請點擊頂部工具列的 **Simulation** 標籤 -> **Save** 下拉選單 -> **Save As**)*
3. 在存檔視窗的「存檔類型 (Save as type)」下拉選單中，選擇 **Simulink Model (*.slx)**。
4. 按下存檔，大功告成！

---

## 2. 程式碼一鍵轉換法 (適合用腳本自動化處理)

身為程式化控制的玩家，當然也可以直接在 Command Window (指令視窗) 下指令，連 Simulink 畫面都不用打開就能瞬間轉好！

這裡會用到我們前面在「程式化模擬」學過的 `load_system` 和 `save_system` 函數：

```matlab
% 假設你的舊檔案叫做 old_robot.mdl

% 1. 將舊模型載入記憶體 (畫面不會跳出來)
load_system('old_robot.mdl');

% 2. 使用 save_system 另存新檔，只要副檔名打 .slx，MATLAB 就會自動幫你轉換格式！
save_system('old_robot.mdl', 'new_robot.slx');

% 3. 關閉記憶體中的舊模型
close_system('old_robot.mdl');

disp('格式轉換成功！現在你有一個乾淨的 .slx 檔案了。');
```

---

## 💡 為什麼強烈建議轉成 .slx？
* **檔案超小：** `.slx` 底層其實是一個 ZIP 壓縮檔，檔案大小通常只有 `.mdl` 的 1/4 甚至更小。
* **讀寫更快：** 載入大型模型時，`.slx` 的開啟速度明顯快很多。
* **支援現代功能：** 很多 Simulink 的新特徵（例如國際字元編碼 UTF-8 支援、更好的版本控制整合）都只有在 `.slx` 格式下才能完美運作。