
【StormWorks:A004】#40 高度一定制御を修正する
前回取得できたデータ

高度の収束に対してI項の寄与が少なく、D項は全く仕事してないように見える
解釈の正確性を期すために、GPT5.6 Terra 高の回答
データを入れたエクセルファイルを渡して検証を依頼。
合ってる。Sheet 3 で、前回の推定はほぼ実測で確定した。
項実測範囲判断P項-0.252 ~ +0.0177制御出力の主成分I項-0.0200 ~ +0.00119下限 -0.02 に飽和D項-0.00341 ~ +0.00228符号は正しいが、絶対量が小さい
特にI項は、2,385 tick中622 tickで -0.0200 に張り付いている。手動上昇を離した直後の tick 4269 では、
P = -0.22162 I = -0.02000 ← 飽和 D = -0.00122
となっている。integral の下限 -100 と ki = 0.0002 による、理論上のI項下限 -0.02 と完全に一致する。UM-A004C_Record_2026-08-28.xlsx (3!A1:M2386)
D項については「ゼロ」ではない。離した直後の tick 4233 は、
P = -0.01704 I = -0.00010 D = -0.00341
で、Pに対しては約20%ある。ただしこの時点の上昇速度は約 9.7 m/s なのに、Dの制動は -0.0034 しかない。その後はP項が大きくなる一方でD項は -0.003 → -0.001 へ減るので、機体を止めるほどの仕事はできていない。
理由はその通りで、今のDは m/tick を使っているから。
derivative = error - last_error
固定ターゲットならこれは -高度変化量/tick。60 tick/s換算では、今の kd = 0.02 は実質 0.00033 × 上昇速度(m/s) くらいの弱さになる。
さらに跳ねる直接の流れも、ログとLuaが一致している。
手動中 → integral = 0 を毎tick実行 手動解除 → 必要だったホバー補正が消えた状態で自動へ戻る → I項はゼロから再学習 → しかし最大でも -0.02 まで → P項主体で大きく行き過ぎる
なので、最低限ここは変えたい。
-- 手動中に消さない -- integral = 0 ← 削除
そしてI項は「積分値」ではなく「I項そのもの」を保持すると扱いやすい。
local i_term = 0 -- 自動モード i_term = clamp(i_term + error * ki, -0.15, 0.15) gyro_updown = error * kp + i_term + derivative * kd
この場合、最後に i_term * ki はしない。±0.15 なら、ログ上必要だった -0.10 前後の定常補正も作れる。
次はDを上下速度として明示化するのがいい。
vertical_speed = (alt - last_alt) * 60 d_term = -kv * vertical_speed
これなら kv が「1 m/s上昇時にどれだけ下降側へ補正するか」になる。今の挙動なら、Iを残す・I上限を広げる・速度Dに替える、この3点でかなり素直になるはず。
修正案に従ってPIDを修正する
◯if文内のintegral = 0を削除(今回はコメントアウト)
◯積分値ではなくI項そのものを保持する(error * ki自体を積み上げていく)
◯微分項ではなく上下位置情報の微分=上下速度として取り扱う
おそらくした2つはコードの見やすさに起因した修正か?
最終的なコード
function clamp(x, min, max)
return math.max(min, math.min(max, x))
end
local target_alt = nil
local integral = 0
local last_error = 0
local last_alt = nil
local i_term = 0
function onTick()
local updown = input.getNumber(1) -- -11
local alt = input.getNumber(2) --
local deadzone = 0.01
-- PID
local kp = 0.05
local ki = 0.0002
local kv = 0.005
--
if target_alt == nil then
target_alt = alt
end
if last_alt == nil then
last_alt = alt
end
local vertical_speed = (alt - last_alt) * 60
last_alt = alt
local manual = math.abs(updown) > deadzone
local gyro_updown
--Variable declaration for data monitoring
local error = 0
local p_term = 0
local d_term = 0
if manual then
--
--
target_alt = alt
-- PID
-- integral = 0
last_error = 0
gyro_updown = updown
else
--
error = target_alt - alt
i_term = clamp(i_term + error * ki, -0.15, 0.15)
p_term = error * kp
d_term = -kv * vertical_speed
gyro_updown = p_term + i_term + d_term
end
--
gyro_updown = clamp(gyro_updown, -1, 1)
output.setNumber(1, gyro_updown)
output.setNumber(6, target_alt) --
output.setNumber(9, p_term)
output.setNumber(10, i_term)
output.setNumber(11, d_term)
endデータ採取
安定飛行後、大きく上昇、その後大きく下降、その後微小操作の順

上昇時は大きくズレてその後追従、下降時はズレが少なく安定してるように見えた。概ねそのような軌道をしてるが、上昇後に外から見て高度が安定してるように見えたのは、ターゲットよりも下の高度で安定してたのか
じゃあパラメータを修正。ではなく
上昇後のオーバーシュート原因
データを見ると

AXIS4のインプットが1じゃなくなる→上昇操作終了→その後GYRO UP/DOWN OUTPUTがしばらく0.01に張り付く
つまり制御量最大=フルブレーキ状態
PID側をいくら操作しても意味がない。
つまり今回の修正は見送り。
ターゲット高度より下で収束:I項の数字が溜まりすぎ
I項のパラメータ制御でなんとかするより、大きい値を積み足さない制御が必要
if manual then
--
--
target_alt = alt
-- PID
-- integral = 0
last_error = 0
gyro_updown = updown
else
error = target_alt - alt
local i_zone = 0.5
if math.abs(error) < i_zone then
i_term = clamp(i_term + error * ki, -0.15, 0.15)
end
p_term = error * kp
d_term = -kv * vertical_speed
gyro_updown = p_term + i_term + d_term
endというわけで制御の中核であるif文を修正して、自動制御中に目標高度との差が小さくなって初めてI項の積み足しを始める構成に変更
再度データ採取

完全ビタ付けとは行かないが、さっきと違ってターゲット高度に収束している。微小操作時の初期応答での跳ねる動作が気になるが、ここまで来ると後はパラメータを調整すれば良い。
というわけで一旦完成。おまけで飛行動画
上下移動テスト。いい感じ
高度維持制御無しで上下にフラフラしてる初期型、高度維持制御を付けただけで跳ねるからドンピシャに合わせづらい改良型を経て、格段に位置合わせしやすくなった。
あっ


回転数過大状態で上昇させたらひっくり返った
今後は強風状態でも維持できるように位置固定制御かなー