前章では PID を「使い」ました。この章では PID を分解します。 教科書に最初に出てくる式は3行で書けますが、そのまま実機に載せると たいてい痛い目に遭います——微分はノイズで暴れ、指令を変えるたびに出力が跳ね、 積分はいつの間にか育ちすぎて機体が言うことを聞かなくなる。 教科書の式と実機のコードの間にある「工夫の階段」を、 第4章で同定したモータを実験台にひとつずつ登っていきます。
6.1 教科書のPID — 3つの項、3つの時間
最もよく使われるのは並列形です。誤差 \(e = r - y\) (目標値 − 測定値)に対して:
3つの項は、見ている「時間」が違います。 P は現在——いまの誤差に比例して押す。 I は過去——これまでの誤差の蓄積を返済するまで押し続ける。 D は未来——誤差の増える速さから先回りしてブレーキを掛ける。 ゲインを時間の単位でくくり直した時定数形もよく使われます:
\(T_i\)(積分時間)は「I が P に追いつくまでの時間」、\(T_d\)(微分時間)は 「D が何秒先を予測するか」と読めます。実機 StampFly のファームウェアは 時定数形で、前章のゲイン表の \(T_i\)・\(T_d\) がまさにこれです。 本章では式の見通しがよい並列形で進め、必要なときに換算します。
6.2 モータで体感する — P の限界、I の御利益、I の欲張りすぎ
実験台は第4章で同定したモータです。ホバリング点まわりで線形化すると、 Duty からプロペラ回転数への伝達はほぼ一次遅れになります:
「回転数を 300 rad/s 上げろ」というステップ指令と、途中で入る負荷外乱 (プロペラが傷ついて負荷が増えた想定)に対して、6.1 の並列形 PID を そのまま実装しました。P・I・D すべて自由に触れます。 まず P と I で応答を整えて、それから \(K_d\) を 0 から少しずつ 上げてみてください——何が起きるでしょうか。
条件: 目標 +300 rad/s のステップ(t=0.05s)、負荷外乱(t=0.45s、Duty −0.05 相当)。 制御は 6.1 の並列形 PID をそのまま実装(微分は教科書どおりの理想微分・偏差入力)。 飽和なし・ノイズなし。
やってみると、教科書の1行ずつが体感になります:
- P だけ → 必ず誤差が残ります。一次遅れの DC ゲインは有限なので、 定常誤差は \(e_{ss} = r/(1 + K_p K_m)\)。\(K_p\) を上げれば減りますが、 ゼロにはなりません。外乱が入ると誤差はさらに増えます。
- I を足す → 定常誤差が消えます。I は「誤差が残っている限り 押し続ける」ので、DC では無限大のゲイン——ステップ目標にもステップ外乱にも 最終的に勝ちます。
- I を欲張る → 振動が始まります。過去の蓄積は急には 止まれないからです(\(K_i\) を上げて確かめてください)。
- D を足す → 少し上げると行き過ぎがわずかに減って、 「未来に先回りするブレーキ」の顔が見えます。ところが、 もう少し上げると——突然暴走します。 教科書の式のどこにも嘘はないのに、です。 この暴走の種明かしが、ここから始まる「実機の階段」です。
6.3 理想微分は使えない — 不完全微分と η の意味
6.2 の暴走の犯人は、理想微分 \(D(s) = K_d\,s\) のゲインが周波数に比例して 無限に増えることです。ゲインが高い周波数で頭打ちにならないため、 制御周期1回分のわずかな遅れさえ許容できず、ある \(K_d\) を超えると ループごと不安定になります。仮に安定でも、センサの測定値には必ず高周波ノイズが 乗っているので、理想微分は「ノイズ専用の増幅器」として暴れます。 そこで一次遅れフィルタを抱き合わせた不完全微分を使います:
低い周波数(\(\omega \ll 1/\tau_f\))ではほぼ理想微分 \(K_d s\) として働き、 高い周波数ではゲインが頭打ちになります。その上限は
つまり \(\eta\) は「微分項のゲインを \(K_p\) の何倍で打ち止めにするか」を 決める係数です。\(\eta = 0.125\)(実機の値)なら上限は \(K_p\) の 8 倍。 \(\eta\) を小さくするほど微分は理想に近づき鋭くなりますが、ノイズの増幅上限も \(1/\eta\) 倍に上がります。微分が「微分として」働く帯域は \(1/T_d\) から \(1/(\eta T_d)\) まで——幅にして \(1/\eta\) 倍。 \(\eta\) は 0.1〜0.2 が相場です。
6.2 と同じ実験を、微分だけ不完全微分に差し替えてやり直します。 今度は \(K_d\) をいくら上げても暴走しません——上限が \(K_p/\eta\) で 頭打ちだからです。ノイズを入れて \(\eta\) を動かすと、 「切れ味」と「うるささ」のトレードオフが制御入力に現れます。
条件: 6.2 と同じ実験(目標 +300 rad/s・負荷外乱つき)。 微分だけが不完全微分(偏差入力)に変わった。 上段=回転数、下段=制御入力(Duty)。
- 6.2 では暴走した大きさの \(K_d\) でも、平気で安定しています。
- ノイズを入れて \(\eta\) を小さくすると制御入力が毛羽立ち、 大きくすると鎮まります(上限 \(K_p/\eta\) の実演)。
- ただし、目標ステップの瞬間に制御入力へ鋭いトゲが 立っているのに気づきましたか? これが次節の主役です。
6.4 微分キックと微分先行 — D は測定値に掛ける
もうひとつ、微分には問題があります。誤差は \(e = r - y\) なので \(\dot{e} = \dot{r} - \dot{y}\)。目標値 \(r\) がステップで変わると、 \(\dot{r}\) は撃力(一瞬だけ無限大)——微分項が出力を思い切り蹴飛ばします。 これが微分キックです。マルチコプタでは目標値はスティックから来ます。 12bit の AD 変換値は常に階段状に変わるので、キックは「たまに」ではなく 常時発生します。
対策は簡単で、D を誤差ではなく測定値 \(y\) だけに掛ける (\(\dot e\) の代わりに \(-\dot y\) を使う)。これを微分先行(PI-D) と呼びます。閉ループの分母(極)は変わらないので安定性・外乱応答はそのまま、 目標値の微分だけが式から消えます。 さらに P も測定値側に移した比例微分先行(I-PD)という形もあります ——目標値応答をいっそう滑らかにしたいプロセス制御などで使われる、 「そういうものもある」と知っておけば十分です。 StampFly は P は偏差・D は測定値の PI-D 構成です (ソースコード pid.hpp で確認済み)。
6.3 の実験の続きです。同じ条件のまま、D の掛け先だけを変えた2本 (偏差 vs 測定値)と、D なしの基準を重ねて描きます。
条件: 6.3 と同じ(\(K_p=10^{-3}, K_i=0.2, K_d=6\times10^{-6}, \eta=0.125\)、 目標 +300 rad/s・負荷外乱つき・ノイズなし)。 上段=回転数(3本)、下段=制御入力(D あり2本)。
- 制御入力(下段)は別世界: 偏差に掛けた D は目標ステップの瞬間に \(K_p \Delta r/\eta = 2.4\) Duty のトゲを立てます(毎回!)。 測定値に掛けた D にはトゲがありません。
- 外乱(t=0.45s)への応答は、D あり2本で完全に一致—— 「閉ループの極は同じ、違いは目標値側の零点だけ」の実証です。
- 偏差D の目標応答が一番きれいに見えますが、それは 2.7 Duty のトゲで 買った「先払いのブレーキ」です。実機ではモータ全開の 5 倍—— 飽和で出せないので、この見かけの利得は実機では消えます。 実機で使える測定値D で比べると、行き過ぎは D なしの2倍 (13% → 26%)に増えています。
種明かしをすると、一次遅れのプラントでは、回転数の変化に逆らう D は 「重り」として働きます(実効時定数が \(\tau + K_m K_d\) に伸びる)。 重りは外乱にも逆らうので落ち込みはわずかに浅くなりますが、 戻りは遅くなり、遅くなったプラントを I が急かすので目標応答は振動的になる—— 教科書の期待に反して、このプラントに D を入れる理由はありません。
6.5 積分の実装 — 地味だが、いちばん事故る場所
積分のコードは1行です。前進オイラー(毎周期、誤差×周期を足し込む)なら:
// Forward Euler / 前進オイラー integral += e * dt; // Trapezoidal (Tustin) — the real firmware uses this for all terms // 台形則(Tustin)— 実機は全項この形 integral += 0.5f * (e + e_prev) * dt;
400 Hz もあれば両者の差はわずかです。恐いのは離散化ではなく、 ワインドアップ(巻き上がり)のほうです。 モータの Duty は 0〜1 までしか出せません。出力が飽和している間も 積分器は「まだ誤差がある」と足し込み続け、際限なく育ちます。 誤差が反転しても、育った積分が吐き出されるまで出力は戻らない—— 大オーバーシュートや、最悪「しばらく言うことを聞かない」機体になります。
対策には段階があります:
- クランプ(積分値の上下限) — 最低限の保険。ただし これだけでは不十分です。上限を出力幅いっぱいに取ると、 積分は依然として出力幅いっぱいまで巻き上がれます。
- 条件付き積分 — 「出力がすでに飽和していて、いま足そうとしている 誤差が飽和をさらに深くする向きなら、積分を止める」。実機 StampFly の方式です。
- 逆算法(back-calculation) — 飽和ではみ出した量を時定数 \(T_t\) で 積分器に戻す。旧ファームの方式(\(T_t = \sqrt{T_i T_d}\))。 どちらも飽和中しか働かないので、線形領域の挙動は同一です。
仕上げの実験です。飽和が長く続く大きなステップで、ワインドアップの被害と 条件付き積分の効果を確かめてください。
条件: PI 制御(\(K_p=0.001,\ K_i=0.05\)——飽和がなければオーバーシュートしない おとなしい設計)。目標 +1800 rad/s、Duty の飽和 ±0.5。 上段=回転数、下段=制御入力(Duty)。
- 条件付き積分なし → 出力が飽和に張り付いている間に積分が育ち、 目標を 15% も超えてから戻ります。 飽和がなければオーバーシュートしないはずの設計なのに、です。
- 条件付き積分あり → 飽和中は積分が止まり、 オーバーシュートが消えます。制御入力が飽和から離れる瞬間の違いに注目。
6.6 実装の形 — 時定数形、そして PI-D、むだ時間
仕上げに、実機 StampFly の PID を読みます。ゲインは 6.1 で出てきた 時定数形(\(K_p, T_i, T_d\))です。まず、時定数形+不完全微分を 「1本の制御器を誤差に掛ける」形で書くと:
この式のまま、モータで運転してみましょう。ツマミが \(K_i, K_d\) から \(T_i, T_d\) に変わると操作感がどう変わるか—— 6.2〜6.3 の並列形と比べて味わってください。
条件: 6.2 と同じ実験(目標 +300 rad/s・負荷外乱つき)。時定数形・誤差入力・ 不完全微分。\(T_i\) のスライダーは対数刻み(2 ms〜1 s)。 読み出しに並列形への換算を表示。
- \(T_i\) は分母にいます: 小さくするほど I が強い—— 並列形の \(K_i\) とは逆向きの感覚です。
- \(K_p\) を動かすと P・I・D が丸ごとスケールします。 「応答の形を保ったまま、強さだけを変える」ツマミになる—— これが時定数形が現場で好まれる理由のひとつです。
- そして \(T_d\)——どう動かしても良くなりません。それがこのプラント (むだ時間のない一次遅れ)の正しい結論です(6.4節の「重り」)。 では \(T_d\) はいつ意味を持つのか。すぐ下で種明かしします。
ただし——実機の実装は上の式ではありません。6.4 で学んだとおり、 D は誤差ではなく測定値に掛けるのが実機の流儀です。StampFly の実装を 式に忠実に書くと:
目標値と測定値で通り道が違うため、もう「1本の \(C(s)\) を誤差に掛ける」形には 書けません(2自由度制御と呼ばれる構造の一種です)。
では、\(T_d\) はいつ意味を持つのでしょうか。ここまでのモータには むだ時間がありませんでした。実機には必ずあります—— センサの読み出し、フィルタ、制御周期、PWM が反映されるまでの待ち。 そこで、同じモータにむだ時間 \(L\) を足して、 実装の形(PI-D)で運転してみます。
条件: 6.2 と同じモータ+むだ時間 \(L\)(初期値 8 ms)。制御は実装の形 (P・I=誤差、D=測定値、η=0.125)。目標 +300 rad/s・負荷外乱つき。
- \(L=0\) に戻すと上の実験と同じ世界です——\(T_d\) は良くしてくれません。
- \(L=8\) ms では話が変わります。\(T_d=0\) だと行き過ぎ 14%。 \(T_d\) を 2〜4 ms 入れるとゼロまで改善—— むだ時間で失われた位相を D が返してくれる、この章で初めての D の勝利です。
- ただし \(T_d=6\) ms で悪化に転じ、8 ms で発散します。 D はゲインも持ち上げるので、効かせすぎると自分で新しい振動を作る—— 薬にも毒にもなる、適量があるのです。
- 「失われた位相」「適量」を目分量でなく定量的に扱う道具が、 次章の位相余裕です。
6.7 実機を運転する — Web の中の StampFly
部品が揃いました。時定数形のゲイン、PI-D の構造、むだ時間—— 全部組み上げると、本物の StampFly のロール・レートループが このページの中に再現できます。プラントは第4・5章で実測した同定値そのまま (\(I_{xx}=9.16\times10^{-6}\ \mathrm{kg\,m^2}\)、モータ遅れ 20 ms、 むだ時間 12 ms)、制御器は実際に飛んでいるファームウェアと同じ構造 (PI-D・\(\eta=0.125\))、そして初期値は飛行実績のあるゲイン そのものです。スライダーを動かすことは、実機にパラメータコマンドで ゲインを書き込む行為に相当します——違いは、失敗してもブラウザを リロードすれば済むことだけ。実機の値の前後にスライドして、この設計が 崖からどのくらいの距離に立っているかを確かめてください。
条件: ロールレート指令 +3 rad/s のステップ(t=0.05s)+トルク外乱(t=0.45s、 \(10^{-4}\) N·m)。初期値は実機の飛行実績ゲイン \(K_p=9.8\times10^{-4},\ T_i=0.7,\ T_d=0.010\)(η=0.125)。 上段=ロールレート、下段=トルク指令。
- 実機値は攻めた設計: 生のステップには6割ほど行き過ぎます。 実機ではレート指令がスティックや外側ループでなまされて届くので これで良く、外乱への強さ(すぐ戻る)を優先した置き方です。
- \(T_d\) を 0 へ → 大振動。実機の D は飾りではなく、 むだ時間 12 ms で失われた位相を取り戻してループを支えています。 第5章で「カスケードの内側ループは構造の D」と言いましたが、 その内側ループ自身も D に支えられているのです。
- \(K_p\) を2倍 → 発振・発散。実機値は崖から 適度な距離に置かれています(この「距離」を定量的に測る道具が 次章の位相余裕です)。
最後に pid.hpp(1軸あたり約80行)の設計をまとめます:
- ゲインは時定数形(\(K_p, T_i, T_d\)、\(\eta=0.125\)=ゲイン上限 \(K_p\) の8倍)。 ただし1本の \(C(s)\) ではなく、上の式のとおり P・I は誤差、D は測定値の PI-D(2自由度)
- 離散化は全項 Tustin(400 Hz)。積分は台形則、微分は双一次変換の漸化式
- リセット直後の1周期は微分の前回値を仕込むだけ——いきなり微分すると 初期値からのキックが出るため
- アンチワインドアップは条件付き積分+保険のクランプ。 コード中のコメントに「クランプだけではアンチワインドアップにならない」と明記
- 出力制限は段ごとの物理量で: レートループはトルク上限、角度ループは レート指令上限(前章 5.5 節の表の構成)
教科書の3行と実機の80行の差は、この章で登った階段——不完全微分・微分先行・ 条件付き積分・初回の仕込み——そのものです。
まとめ
- PID は P=現在、I=過去、D=未来。並列形と時定数形は \(T_i = K_p/K_i\)、\(T_d = K_d/K_p\) で行き来できる。
- P だけでは一次遅れに定常誤差 \(r/(1+K_pK_m)\) が残る。I が消すが、 欲張ると振動する。一次遅れには PI で十分——D はむしろ「重り」になる。 D の本領は積分の重なる系で、前章のカスケード内側ループは D の仕事を構造で実現したものだった。
- 理想微分はノイズ増幅器。不完全微分の \(\eta\) は 「微分ゲインを \(K_p\) の \(1/\eta\) 倍で打ち止めにする」係数(実機 0.125 → 8倍)。
- 目標値の微分はキックを生む。D は測定値に掛ける(PI-D)。 閉ループ極は変わらない。I-PD という形もある。
- 積分はワインドアップする。クランプだけでは不十分で、 条件付き積分か逆算法を使う。リミットのかけ忘れは暴走のもと。
- 実機の PID は「教科書の式+この章の工夫」で全行読める。
- 仕上げに、実測プラント+飛行実績ゲインの実機ループを運転した。 実機値は崖から適度な距離にある——その距離の定量化が次章の位相余裕。
道具箱は揃いました。次章では視点を時間から周波数へ移します——実機 StampFly の ゲインを実際に決めているループ整形(位相余裕・交差周波数)と、 第5章で見た「崖」の種明かしです。
本章の対話記録: notes/qa_log.md の Q-PID-1〜Q-PID-4。実機実装の出典: stampfly_ecosystem firmware/vehicle/components/sf_controller_pid/include/pid.hpp (P=偏差・D=測定値・η=0.125・全項Tustin・条件付き積分を原文で確認)。 プラント数値は第4章の同定値(ホバ点線形化)。