5. ünite / 10
PID I: irtifa tutma
Bu ünitenin sonunda
- P, I ve D terimlerinin her birinin yanıta etkisini simülasyonda göstermek
- Aşım, yükselme ve oturma süresini okumak
- İleri beslemenin ve I teriminin kalıcı hatayı nasıl kapattığını açıklamak
- Anti-windup ve ölçüm üzerinden türevi uygulamak
Ders notları
İrtifa denetiminde kontrolcü itkiyi belirler. İyi bir başlangıç noktası ileri beslemedir: askıda kalmak için gereken itkiyi (m g) baştan ver, PID yalnız farkı düzeltsin.
- P yay gibidir: hata büyüdükçe daha çok iter. Tek başına salınım üretir ve kontrolcünün bilmediği bir yük varsa Δm g / Kp kadar kalıcı hata bırakır.
- D sönümleyicidir: hıza karşı koyar, aşımı azaltır. Türev hedeften değil ölçümden alınır; yoksa hedef sıçradığında itki de sıçrar ("türev tekmesi").
- I belleğe sahiptir: süren hatayı toplar ve kalıcı hatayı sıfırlar. Ama tırmanışta biriken integral aşım yaptırır; itki doymuşken integrali durdurmak anti-winduptur.
Yanıtı okumak
Aşım (%), yükselme süresi (%10 → %90) ve oturma süresi (±%2 bandına son giriş) birbiriyle çekişir. Kp'yi artırmak yanıtı hızlandırır ama aşımı büyütür; Kd bunu dengeler; Ki kalıcı hatayı kapatır. Sıra hep aynıdır: önce P, sonra D, en son I.
PX4 gerçekte irtifayı kaskad yapar: konum hatası → dikey hız hedefi (MPC_Z_P) → hız PID'i (MPC_Z_VEL_P/I/D) → itki. Simülasyondaki tek döngü bu yapının sadeleştirilmiş halidir.
Simülasyon
İrtifa tutma PID ayarı
1,2 kg'lık drone t = 0,5 s'de 0'dan 2 m'ye çıkmak ister. Kontrolcü m·g ileri beslemesini bilir; bilmediği ek yükü kalıcı hata olarak görürsün. Önce yalnız P ve D ile oyna, sonra yük ekle ve hatayı I ile kapat. Aşım, yükselme ve oturma süresi (±%2) altta.
3B sahne yükleniyor…
Simülasyon görevi
İrtifa simülasyonunda: (1) Ki = 0, Kd = 0 yap, Kp'yi 6 bırak: yanıt ne yapıyor? (2) Kd'yi 3'e çıkar: aşım kaç oldu? (3) 0,3 kg ek yük ver: kalıcı hata Δm g / Kp = 0,49 m mi? (4) Ki'yi artırarak hatayı kapat. Hem aşımı %10'un altında hem oturmayı 3 s'nin altında tutan bir ayar bul (ipucu: Kp 8, Ki 2, Kd 5 civarı).
Kod
#include <algorithm>
#include <cmath>
class Pid {
public:
Pid(float kp, float ki, float kd, float outMin, float outMax, float dCutoffHz)
: kp_(kp), ki_(ki), kd_(kd), min_(outMin), max_(outMax), fc_(dCutoffHz) {}
// sp: hedef, meas: ölçüm, dt: s, ff: ileri besleme (ör. askıda itki m·g)
float update(float sp, float meas, float dt, float ff = 0.f) {
const float e = sp - meas;
// türev ölçümden: hedef sıçrayınca "türev tekmesi" olmaz
const float raw = first_ ? 0.f : -(meas - prev_) / dt;
prev_ = meas; first_ = false;
const float a = dt / (dt + 1.f / (2.f * float(M_PI) * fc_)); // birinci derece alçak geçiren
d_ += a * (raw - d_);
const float iNew = i_ + ki_ * e * dt;
const float u = ff + kp_ * e + iNew + kd_ * d_;
const float us = std::clamp(u, min_, max_);
// koşullu integrasyon: doymuşken integrali yalnız doymayı azaltan yönde güncelle
if (u == us || (u > us && e < 0) || (u < us && e > 0)) i_ = iNew;
return us;
}
void reset() { i_ = 0; d_ = 0; first_ = true; }
private:
float kp_, ki_, kd_, min_, max_, fc_;
float i_ = 0, d_ = 0, prev_ = 0;
bool first_ = true;
};
// İrtifa tutma, 500 Hz: thrust = pid.update(z_sp, z_est, 0.002f, m * 9.81f);import math
class PID:
def __init__(self, kp, ki, kd, out_min, out_max, d_cutoff_hz=20.0):
self.kp, self.ki, self.kd = kp, ki, kd
self.lo, self.hi, self.fc = out_min, out_max, d_cutoff_hz
self.i = self.d = 0.0
self.prev = None
def update(self, sp, meas, dt, ff=0.0):
e = sp - meas
raw = 0.0 if self.prev is None else -(meas - self.prev) / dt
self.prev = meas
a = dt / (dt + 1 / (2 * math.pi * self.fc))
self.d += a * (raw - self.d)
i_new = self.i + self.ki * e * dt
u = ff + self.kp * e + i_new + self.kd * self.d
us = min(self.hi, max(self.lo, u))
if u == us or (u > us and e < 0) or (u < us and e > 0):
self.i = i_new
return us
# 1,2 kg drone, ek yük 0,3 kg, 500 Hz
m_ff, m, g, dt = 1.2, 1.5, 9.81, 0.002
pid = PID(8, 2, 5, -m_ff * g, 2 * m_ff * g - m_ff * g)
z = v = 0.0
thrust = m_ff * g
log = []
for k in range(5000):
sp = 2.0 if k * dt >= 0.5 else 0.0
cmd = m_ff * g + pid.update(sp, z, dt)
thrust += (cmd - thrust) * dt / 0.05 # motor gecikmesi τ = 50 ms
v += (thrust - m * g - 0.3 * v) / m * dt
z = max(0.0, z + v * dt)
log.append(z)
print(f"aşım {max(0, (max(log) - 2) / 2 * 100):.1f}%, son hata {2 - log[-1]:.3f} m")Sık yapılan hatalar
- Önce Ki'yi artırmak: kalıcı hatayı I kapatır ama salınımı P ve D yönetir.
- Türevi hata üzerinden almak: hedef değişince motorlara sıçrama gider.
- Anti-windup koymamak: itki doyumdayken integral büyür, hedefe gelince uzun bir aşım yapar.
- Barometre gürültüsünü süzmeden D terimine vermek.
Yazılım mühendisliği bağlantısı
Ayar (tuning) bir deneydir; sonuçlarını tekrarlanabilir kıl.
- Kazançları kodda değil parametre dosyasında tut ve sürümle.
- Basamak yanıtı ölçütlerini (aşım, oturma) otomatik hesaplayan bir test yaz; kazanç değişikliği bir eşiği bozarsa CI kırmızı olsun. Bu dersteki
stepMetricstam olarak budur.
Kendini sına
Yalnız P (Kp = 6 N/m) ile kontrolcünün bilmediği 0,3 kg yük kalıcı hata olarak yaklaşık ne bırakır?
Türev terimi neden hedeften değil ölçümden alınır?
Anti-windup ne işe yarar?
Diğer kazançlar sabitken Kd artırılırsa aşım genellikle ne olur?
Bu ünitenin parçaları
3B modelli gerçek Hyvex parçaları: