Create pt2.py
This commit is contained in:
112
common/pt2.py
Normal file
112
common/pt2.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import math
|
||||
|
||||
|
||||
class PT2Filter:
|
||||
"""
|
||||
Diskreter PT2-Filter mittels Tustin (bilinear transform)
|
||||
Übertragungsfunktion (kontinuierlich):
|
||||
H(s) = (w0^2) / (s^2 + 2*zeta*w0*s + w0^2)
|
||||
Abtastzeit dt.
|
||||
"""
|
||||
def __init__(self, w0: float, zeta: float, dt: float):
|
||||
"""
|
||||
w0: Eigenkreisfrequenz [rad/s], bestimmt u.a. die Anstiegszeit
|
||||
zeta: Dämpfungsgrad (zeta=1 => kritisch gedämpft)
|
||||
dt: Abtastzeit [s]
|
||||
"""
|
||||
self.w0 = w0
|
||||
self.zeta = zeta
|
||||
self.dt = dt
|
||||
self.a1, self.a2, self.b0, self.b1, self.b2 = self._design_pt2(self.w0, self.zeta, self.dt)
|
||||
self.y1 = 0.0
|
||||
self.y2 = 0.0
|
||||
self.u1 = 0.0
|
||||
self.u2 = 0.0
|
||||
|
||||
def _design_pt2(self, w0, zeta, dt):
|
||||
"""
|
||||
Erzeuge (a1, a2, b0, b1, b2) aus:
|
||||
H(s) = w0^2 / [s^2 + 2*zeta*w0 s + w0^2]
|
||||
via Tustin (s = (2/dt)*(1-z^-1)/(1+z^-1)).
|
||||
|
||||
Wir bringen G(z) in die Normalform:
|
||||
Y(z)/U(z) = (b0 + b1 z^-1 + b2 z^-2) / (1 + a1 z^-1 + a2 z^-2).
|
||||
|
||||
=> Zeitbereich: y[k] = -a1*y[k-1] - a2*y[k-2] + b0*u[k] + b1*u[k-1] + b2*u[k-2].
|
||||
"""
|
||||
|
||||
Ts = dt
|
||||
wd = w0
|
||||
alpha = 2.0 / Ts
|
||||
b2_ = w0**2
|
||||
b1_ = 2.0 * w0**2
|
||||
b0_ = w0**2
|
||||
A2_f1 = alpha**2
|
||||
A1_f1 = -2.0 * alpha**2
|
||||
A0_f1 = alpha**2
|
||||
factor2 = 2.0*zeta*wd*alpha
|
||||
A2_f2 = factor2
|
||||
A1_f2 = 0.0
|
||||
A0_f2 = -factor2
|
||||
A2_f3 = wd**2
|
||||
A1_f3 = 2.0*(wd**2)
|
||||
A0_f3 = wd**2
|
||||
A2 = A2_f1 + A2_f2 + A2_f3
|
||||
A1 = A1_f1 + A1_f2 + A1_f3
|
||||
A0 = A0_f1 + A0_f2 + A0_f3
|
||||
B2 = b2_
|
||||
B1 = b1_
|
||||
B0 = b0_
|
||||
b2d = B2 / A2
|
||||
b1d = B1 / A2
|
||||
b0d = B0 / A2
|
||||
a2d = A0 / A2
|
||||
a1d = A1 / A2
|
||||
|
||||
return (a1d, a2d, b0d, b1d, b2d)
|
||||
|
||||
def sync(self, target: float):
|
||||
steps = compute_saturation_steps(self.w0, self.zeta, self.dt)
|
||||
for i in range(1, steps + 1):
|
||||
update(target)
|
||||
|
||||
def compute_saturation_steps(self, w0: float, zeta: float, dt: float) -> int:
|
||||
"""
|
||||
Berechnet eine Abschätzung der Schritte, bis der Filter (95% des Endwerts) erreicht ist.
|
||||
|
||||
Wir nutzen hier die Abschätzung:
|
||||
T_s = 4 / (zeta * w0)
|
||||
und setzen N = T_s / dt.
|
||||
"""
|
||||
Ts = 4.0 / (zeta * w0)
|
||||
N = Ts / dt
|
||||
return math.ceil(N)
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Setzt interne Zustände zurück
|
||||
"""
|
||||
self.y1 = 0.0
|
||||
self.y2 = 0.0
|
||||
self.u1 = 0.0
|
||||
self.u2 = 0.0
|
||||
|
||||
def update(self, u: float) -> float:
|
||||
"""
|
||||
Ein Schritt des Filters mit aktuellem Eingang u.
|
||||
Gibt Ausgang y zurück.
|
||||
"""
|
||||
y = (
|
||||
- self.a1 * self.y1
|
||||
- self.a2 * self.y2
|
||||
+ self.b0 * u
|
||||
+ self.b1 * self.u1
|
||||
+ self.b2 * self.u2
|
||||
)
|
||||
|
||||
self.y2 = self.y1
|
||||
self.y1 = y
|
||||
self.u2 = self.u1
|
||||
self.u1 = u
|
||||
|
||||
return y
|
||||
Reference in New Issue
Block a user