Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

W02 飛控架構

本週要知道

學完能回答:無人機為什麼能自己保持水平、定高、抗風?

  1. 資料流:感測器 → 估算(EKF)→ 控制(PID)→ 馬達。
  2. 四種感測器各測什麼:
    • IMU(陀螺儀 + 加速度計):旋轉速度與加速度
    • 氣壓計:高度
    • GPS:位置
    • 磁力計:朝向
  3. EKF 的用途:感測器都有雜訊,EKF 把它們融合成「我在哪、朝哪」的最佳估計。不用懂數學。
  4. PID 的用途:誤差越大修正越多(P)、長期偏差慢慢消除(I)、抑制過衝(D)。
  5. 控制是分層的:飛行模式決定目標 → 姿態控制算目標轉速 → 轉速控制(PID)算推力 → 馬達混控分給四個馬達。
  6. W1 現象對應的原因:
    • AltHold 定高:氣壓計加高度 PID
    • Loiter 抗風:GPS 加位置 PID
    • Stabilize 不定高:沒有高度控制這一層
  7. ArduPilot 與 PX4 同類:都是開源飛控,架構相近;本計畫用 ArduPilot。

兩條鏈:資料流與控制分層

兩條鏈講的是同一件事的不同縮放層級:第二條是第一條「控制」那一段的展開。

資料流(誰把什麼交給誰)
  感測器 ──▶ EKF ──▶ PID ──▶ 馬達
            估算      控制
         「我在哪、朝哪」  「該出多少力」
                       │
                       ▼ 放大「控制」這一段
控制分層(目標怎麼變成馬達指令)
  飛行模式 ──▶ 姿態控制 ──▶ 轉速控制(PID)──▶ 馬達混控 ──▶ PWM
  去哪裡      要傾斜多少    要轉多快           各馬達多少    送出訊號
  • EKF 屬於「估算」,只出現在第一條,不在控制分層裡。
  • 題目問「資料流」就從感測器答起;問「控制分層」就從飛行模式答起。

縮寫

縮寫全名
EKFExtended Kalman Filter(擴展卡爾曼濾波器)
PIDProportional–Integral–Derivative(比例-積分-微分)
IMUInertial Measurement Unit(慣性測量單元)
SITLSoftware In The Loop
PX4非縮寫,源自 PIXHAWK 計畫

可以跳過

  • 原始碼函式名稱(如 angle_ef_roll_pitch_rate_ef_yaw())
  • 單位換算(centi-degree、cm/s)
  • Kalman Filter 的數學推導
  • PID 調校參數細節(W10 才用)

閱讀清單

必讀(約 1.5 小時)

  1. EKF 概觀:https://ardupilot.org/copter/docs/common-apm-navigation-extended-kalman-filter-overview.html:看文字,知道它融合了哪些感測器就好
  2. PID 調校入門:https://ardupilot.org/copter/docs/ac_rollpitchtuning.html:只看 P、I、D 各自的作用,參數先略過

選讀

實作

  1. 在 SITL 靜止時,用 pymavlink 讀 RAW_IMU、SCALED_PRESSURE、GPS_RAW_INT,印出原始感測器值
  2. 再讀 ATTITUDE,比較原始值與 EKF 估算結果
  3. 觀察 IMU 靜止時的雜訊大小(結果見「學到什麼」)

學到什麼

IMU 不等於陀螺儀

IMU(慣性測量單元)= 陀螺儀 + 加速度計。ArduPilot 通常把磁力計獨立計算。

元件測什麼特性
陀螺儀旋轉速度(°/秒)短時間很準,長時間會漂移
加速度計加速度(含重力)長期可靠(重力永遠向下),短時間雜訊大
磁力計磁場方向(朝向)像指南針

兩者特性互補,EKF 把它們融合起來。

PID:以「升到 10 m 並停住」為例

  • P(比例):誤差越大,推力越大。
    • 問題:差 0.5 m 時,P 算出的推力剛好只夠撐住機身,誤差再縮小推力就不夠,所以停在差 0.5 m 的地方到不了目標(穩態誤差);而且接近目標時速度仍快,會衝過頭、來回震盪。
  • I(積分):累積誤差。
    • 還差 0.5 m 時,I 項每一瞬間都多加一點,越積越大,直到推力夠、誤差變 0 才停止增加。此時 I 項的值就是「撐住機身的基本推力」。
    • 比喻:定速巡航上坡時速度掉了一點,I 會一直補油門,直到回到設定速度。
  • D(微分):看誤差變化有多快。
    • 無人機正快速接近目標,D 就反向出力,像提前點煞車,接近目標時自然減速,避免衝過頭。
解決什麼副作用
P誤差大就用力修單獨用會殘留偏差、會震盪
I消除長期殘留偏差太大會累積過頭,來回晃
D抑制衝過頭對雜訊敏感,太大會抖

調 PID 要三個一起平衡(W10 會做)。

用模擬看 PID:升到 10 m 並停住

X 軸是時間(秒),Y 軸是高度(公尺),只畫 10 m 附近(前面的爬升段省略)。

控制器最終高度最高點現象
只有 P9.50 m9.86 m卡在 9.5 m,到不了 10 m
P + I10.00 m10.36 m能到 10 m,但先衝過頭 0.36 m、再來回震盪
P + I + D10.00 m10.00 m平順到 10 m,沒有衝過頭

為什麼是 9.5 m:模型中懸停需要 50% 推力,P 的規則是「推力 = KP × 誤差」,KP = 1。穩定時推力要等於重力,所以 1 × 誤差 = 0.5,誤差 = 0.5 m,停在 10 − 0.5 = 9.5 m。9.5 這個數字沒有特殊意義,是 0.5 ÷ KP 的結果(KP = 2 → 9.75 m,KP = 10 → 9.95 m);有意義的是「一定會有非零誤差」,這就是需要 I 項的原因。

假設與限制:這是自己寫的一維簡化模型(懸停推力 50%、有空氣阻力),不是 ArduPilot 的真實控制器;增益 P = 1、I = 0.5、D = 1.2 是為了讓三種行為都看得清楚而挑的。程式:scripts/pid_demo.py,用法 vendor/venv/bin/python -I scripts/pid_demo.py <輸出.png>,可自行改 KP、KI、KD 觀察。

用模擬看 P、I、D 的數字怎麼變:0 → 9.5 → 10 m

同一個一維模型與增益(P = 1、I = 0.5、D = 1.2),但這次三項一起開,並把三項各自的「數字」畫出來(1.0 = 100% 推力,不管單位)。灰色垂直線是高度剛過 9.5 m 的時刻(t = 4.9 秒)。

時間高度PID實際推力發生什麼事
0 秒0 m10.000.050.001.00差 10 m,P 算出 10,但推力最多 1,所以滿推
0.1 秒0.06 m9.950.50−1.031.00I 已頂到 0.5(積分上限);D 開始反向
2 秒5.5 m4.520.50−3.911.00速度最快,D 煞車最用力(負值)
3 秒7.9 m2.120.50−1.940.68加起來不到 1,開始收推力
4.9 秒9.5 m0.500.50−0.460.54只有 P 時會卡住的高度,這裡 P 剛好 0.5
6 秒9.78 m0.220.50−0.200.52P、D 持續縮小
8 秒9.95 m0.050.50−0.040.50接近目標
11 秒以後10.00 m0.000.500.000.50P、D 歸零,只剩 I 撐住機身

怎麼看這張圖:

  • P(藍):永遠等於「誤差 × 1」,從 10 一路平滑降到 0,跟高度曲線是鏡像。9.5 m 時誤差 0.5,P 就是 0.5。
  • I(橘):這裡幾乎是一條水平線,0.1 秒就升到 0.5 後不動。這就是前面「為什麼只有 P 會卡在 9.5 m」的答案:只有 P 時,要靠 P = 0.5 才撐得住機身,所以停在差 0.5 m;加了 I,由 I 先頂住 0.5,P 就可以降到 0,高度才能走完最後 0.5 m。
  • D(青):全程是負值(煞車),升得越快越負(約 −3.9),接近目標時回到 0。它不是在「幫忙升」,是在「防止衝過頭」。
  • 實際推力(虛線):P + I + D 的和,再夾在 0–1。前約 2 秒被夾在 1.0(飽和),此時三項加起來超過 1,多出來的部分被截掉;之後才由三項相加直接決定。

注意:

  • 這張圖的積分上限是 ±1,I 在起飛 0.1 秒就頂到 0.5 的上限,所以看不到「9.5 → 10 m 之間 I 慢慢爬升」;想看 I 慢慢累積,見下一張圖(上限放寬到 ±2、情境改成載重改變)。
  • 這是簡化模型,不是 ArduPilot 的真實數字。程式:scripts/pid_climb.py,用法 vendor/venv/bin/python -I scripts/pid_climb.py <輸出.png>。

用模擬看 I「累積」是什麼意思

情境:無人機本來穩穩懸停在 10 m(I 單獨撐住 50% 推力);t = 2 秒時突然多了載重,需要的推力變成 60%。三項各自貢獻多少「推力」,全部畫在同一個單位(1.0 = 100%)。

項在圖上的表現意思
P(藍)載重後掉了一點高度,誤差出現,P 出力;約 t = 4 秒達高峰(約 6%),之後回到 0只在「還有誤差」時出力,誤差消失就歸零
I(橘)從 50% 一路慢慢爬到 60%,約 t = 6~8 秒後停住(略過 60% 一點點再回落)只要誤差還在,每個瞬間就多加一點;累積的量就是橘線的高度,誤差歸零後保持不變
D(青)載重剛發生時有一個尖峰(約 8%),很快回到 0只在「變化」時出力,靜止後歸零
合計(虛線)載重後瞬間跳到約 59%,後來維持 60%立刻應付的是 D 與 P,最後長期撐住的是 I

重點:

  • I 的累積量不會消失。誤差回到 0 後,I 保持在 60%,由它長期撐住載重;P 和 D 都回到 0。
  • 這就是 W2「PID 一直都在運作、沒有介入時間點」的實際樣子:三條線從頭到尾都在算,只是各自的份量不同。
  • 反應速度排序:D 最快(立刻),P 次之,I 最慢(要累積)。

注意:前一張圖(升到 10 m)的模擬把積分上限設為 ±1,I 項在起飛後 0.1 秒就頂到上限 0.5,剛好等於所需的懸停推力,所以看不出「慢慢累積」;這張圖把上限放寬到 ±2,並用「載重改變」這個情境,才看得到 I 逐步累積。程式:scripts/pid_terms.py,用法 vendor/venv/bin/python -I scripts/pid_terms.py <輸出.png>。

實作:讀原始感測器值

腳本 scripts/w2_sensors.py(先執行 scripts/sitl.sh):SITL 靜止時各取 50 筆。

感測器讀到的值意思
陀螺儀三軸約 0.001 rad/s沒在轉
加速度計x、y = 0;z = −9.815 m/s²只剩重力,方向向下
氣壓計945.0 hPa、31.1 °C約 584 m 海拔的氣壓
GPS10 顆衛星,緯度 −35.363262、經度 149.165237SITL 預設位置
EKF 姿態roll 0.07°、pitch 0.06°、yaw −5.8°幾乎水平
  • 預設的 SITL 感測器幾乎沒有雜訊(加速度計 z 標準差 0.003 m/s²),看不出 EKF 的作用。
  • GPS fix_type 回報 6(RTK 固定解),是 SITL 的模擬值;真實 GPS 通常是 3(3D 定位)。

實作:開雜訊,比較原始值與 EKF

腳本 scripts/w2_noise.py:起飛到 10 m 懸停,開啟模擬的 IMU 振動,比較「只用加速度計算的 roll」與 EKF 估算的 roll。加 --hold 則帶著雜訊一直懸停,給 QGC 觀察,Ctrl-C 結束並降落。

只用加速度計算 rollEKF 估算 roll
標準差(較小雜訊)0.166°0.044°

QGC 的 Analyze Tools → MAVLink Inspector:

  • 上圖:RAW_IMU 的 xacc、yacc 約 ±150 mG(單位是毫 g,1000 mG = 1 g),zacc 在 −1000 mG(重力)附近抖。若單看加速度計,相當於機身傾斜約 ±8°。
  • 下圖:ATTITUDE 的 roll、pitch 約 ±0.003 rad(約 ±0.2°),是平滑的慢波。
  • 機身其實幾乎沒動,晃的只是馬達振動;EKF 靠陀螺儀(短時間準)把振動過濾掉,大約縮小到 1/40。這就是「融合」的實例。
  • 兩張圖單位不同(mG vs 弧度),要換算才能比;QGC 只以 3~10 Hz 顯示,而振動是 50~70 Hz,所以鋸齒的形狀是取樣造成的,只看抖動幅度。

踩到的坑:

  • 參數名稱是 SIM_ACC1_RND、SIM_GYR1_RND(含 IMU 編號),不是 SIM_ACC_RND;先查原始碼 libraries/SITL/SITL.cpp 確認。
  • 雜訊只在馬達轉動時才有,必須在空中懸停;陀螺儀雜訊還會乘上油門。
  • 加速度計的雜訊要 SIM_VIB_FREQ_X/Y/Z 非 0 才會加進去;只開 ACC1_RND 沒有效果。
  • SITL 的 TCP 連線如果不讀封包,緩衝區塞滿後會卡住整個模擬(SITL CPU 掉到 1%,QGC 所有訊息變 0 Hz)。懸停等待時也要持續 recv_match()。
  • 腳本用 MAV_CMD_DO_SET_MODE 切 Land;結束時一定要把 SIM_* 參數歸零。

姿態控制頁面的圖看不懂是正常的

那張圖畫的是原始碼的函式呼叫路徑,給開發者看,不是概念圖。只要記住分層流水線:飛行模式 → 姿態控制 → 轉速控制(PID)→ 馬達混控 → PWM。

卡在哪

  • 姿態控制頁面的圖表看不懂(已確認可跳過)
  • 陀螺儀與加速度計的可靠時段容易記反(見自我測驗第一輪第 5 題)

自我測驗

第一輪(3/5)

✅ 1 分、⚠️ 0.5 分、❌ 0 分。

#題目我的回答結果正確答案
1資料流順序?(感測器 → ? → ? → 馬達)EKF -> PID✅感測器 → EKF → PID → 馬達
2懸停中多了載重,最後長期撐住的是 P、I、D 哪一項?另外兩項為何回到 0?I;因為沒有變化了⚠️I 對。D 看「變化」,不變就歸零;P 看「現在的誤差」,誤差回到 0 就歸零(不是因為沒變化)
3只用 P 為什麼停在差一點點的地方?誤差太小⚠️方向對,漏了後果:誤差小 → P 算出的推力小 → 差 0.5 m 時剛好只夠撐住機身,無法再縮小誤差
4Loiter 能抗風、AltHold 不能,多了什麼?GPS; PID✅多了 GPS,以及用位置誤差算修正的位置 PID
5IMU 的兩個元件,各自短時間或長時間可靠?陀螺儀(長)、加速度計(短)❌剛好相反:陀螺儀短時間準、長時間漂移;加速度計長期可靠(重力永遠向下)、短時間雜訊大

第二輪(2.5/5)

#題目我的回答結果正確答案
1陀螺儀與加速度計,哪個短時間準、長時間漂移?哪個長期可靠、短時間雜訊大?陀螺儀;加速度計✅陀螺儀短時間準;加速度計長期可靠
2EKF 為什麼要融合多個感測器?每個感測器觀察的資料不一樣⚠️觀察對象不同只對一半;關鍵是每個感測器都有雜訊或漂移,特性互補(如陀螺儀+加速度計),融合才能得到最佳估計
3I 如何讓誤差變成 0?在尚未達到目標高度的時候加大推力⚠️這句連 P 也適用;I 的特點是把誤差一直累加,只要還有誤差就越加越多,直到推力夠撐住、誤差變 0 才停止增加
4D 在快速接近目標時做了什麼?為何能避免衝過頭?跟 I 對抗避免超過目標高度⚠️與 I 對抗是現象;D 的作用是看誤差縮小得多快,太快就反向出力(提前煞車),所以接近目標時自然減速
5飛行模式 → ? → ? → 馬達混控 → PWMEKF -> PID❌姿態控制 → 轉速控制(PID)。EKF 是估算、不在控制分層裡

需要加強:EKF 屬於「估算」,PID 屬於「控制」,兩者不同層;回答「為什麼」要說出機制,不只現象。

第三輪(4/5)

#題目我的回答結果正確答案
1資料流:感測器 → ? → ? → 馬達EKF -> PID✅EKF → PID
2控制分層:飛行模式 → ? → ? → 馬達混控 → PWM姿態控制 -> PID✅姿態控制 → 轉速控制(PID)
3I 為什麼能消除 P 殘留的 0.5 m 誤差?只要有誤差就不斷加大直到誤差消失✅誤差一直累加,直到推力夠撐住、誤差變 0 才停止增加
4D 看的是誤差的什麼?快速接近目標時怎麼出力?目前高度跟目標高度的誤差;隨著誤差減小反而加大❌D 看誤差的變化速度(不是誤差本身);接近越快反向出力越大(煞車),接近變慢 D 就跟著變小
5EKF 為何同時用陀螺儀和加速度計?一個看旋轉速度、一個看加速度;單一偵測器有誤差所以互補✅兩者各有缺點(陀螺儀長時間漂移、加速度計短時間雜訊大),互補後估計更準

需要加強:D 看的是「變化速度」,不是「誤差大小」;P 看大小、I 看累積、D 看變化。

第四輪(2.5/5)

#題目我的回答結果正確答案
1P、I、D 各自看誤差的什麼?大小、累積、變化速度✅P 看大小、I 看累積、D 看變化速度
2SITL 靜止時加速度計 z 軸讀到約 −9.8 m/s²,代表什麼?懸停;正在對抗地表加速度⚠️當時是靜止在地面,不是懸停。加速度計靜止時只量到重力(1 g),z 軸朝下所以是負值;它量的是支撐力,不是「對抗」
3開雜訊後 RAW_IMU 大幅抖動、ATTITUDE 平穩,EKF 靠哪個感測器的什麼特性過濾振動?(空白)❌陀螺儀短時間準:靠它積分得到姿態,壓住加速度計的短時間雜訊;加速度計只負責長期的重力方向,修正陀螺儀的漂移
4只開 SIM_ACC1_RND,加速度計為何沒有雜訊?(空白)❌加速度計雜訊要 SIM_VIB_FREQ_X/Y/Z 非 0 才會加進去,而且馬達要在轉(空中懸停)才生效
5飛行模式 → ? → ? → 馬達混控 → PWM姿態控制 -> PID✅姿態控制 → 轉速控制(PID)

需要加強:實作中的細節(第 3、4 題)沒有留下印象,回頭看「實作:開雜訊」那節的圖與「踩到的坑」。

下週問題

  • W4:我對 RAW_IMU 要求 20 Hz,QGC 的 MAVLink Inspector 卻顯示 4 Hz;誰決定實際速率?多個連線要的速率會互相覆蓋嗎?
  • W4:COMMAND_ACK 的 result 有哪些值?(實測:0 = 成功;對已在空中的無人機下 takeoff 得到 4)
  • W4:TCP 連線不讀封包就卡住整個 SITL,MAVLink 本身有沒有流量控制,還是只能自己持續讀?
  • W4:訊息欄位的單位為什麼這麼雜(毫 g、毫弧度/秒、經緯度 ×1e7、公分)?去哪查?