Zeitlicher Determinismus durch Timer, ISRs und Hardware im SoC – Teil 6

Teil 5 unserer Serie war gewissermaßen die mentale Brücke zwischen der physikalischen Betrachtung unseres Problems und der nun erfolgenden immer tieferen Betrachtung seiner numerischen und algorithmischen Umsetzung.

Ich empfehle daher dringend jedem Quereinsteiger, mindestens Teil 5 zu studieren, um hier folgen zu können.

Wir können unser in Teil 5 vorgestelltes Problem offensichtlich in drei Verarbeitungsebenen teilen:

  • Initialisierung
  • Wechsel der Zielposition
  • Laden / Berechnen der nächsten Zeit für den Timer (ISR)

Unser mathematisches Modell muss daher numerisch an die zugrundeliegende Hardware im Hinblick auf die Berechnungen (Rundungsfehler, Fehlerfortpflanzung, Fließkommazahlen oder Integers) wie auch algorithmisch (sehr kurz gehaltene ISR-Zeiten) angepasst werden.

Zunächst betrachten wir unsere Problemstellung „aus dem Stand“. Wir haben eine aktuelle Winkelposition φ0\varphi_0 und eine neue Zielposition φ1\varphi_1. Später erweitern wir auf neue Zielpositionen mitten in einer Fahrt.

Um den Zugang zu erleichtern, projizieren wir unser mentales Modell auf eine Wegbewegung. Der Grund liegt in einem einfacheren Zugang für Leser, welche nicht dieser Fachrichtung entspringen und so gewohnte Notationen vorfinden. Statt eines Winkels φ\varphi betrachten wir einen Weg ss, und damit statt einer Winkelgeschwindigkeit φt\frac{\varphi}{t} die klassische Geschwindigkeit v=stv=\frac{s}{t}.

Wie in Teil 5 beschrieben, setzen wir ein Δssettle=dsettle\Delta s_{\mathrm{settle}} = d_\mathrm{settle}, um vor der Zielposition wieder zu stoppen und von der Anfahrtsseite aus einzuregeln.

Damit folgt, wir müssen die Distanz dmove=max⁡(0,|s1−s0|−dsettle)d_{\mathrm{move}} = \max(0,\; |s_1-s_0|-d_\mathrm{settle}) fahren.

In unserem Fall fangen wir gleich zu Beginn |s1−s0|≤dsettle|s_1-s_0| \leq d_\mathrm{settle} ab, in diesem Fall wechseln wir sofort in den Einregel-Modus. Somit bleiben nur die Fälle |s1−s0|>dsettle|s_1-s_0| > d_\mathrm{settle} bestehen.

Nun schauen wir uns noch einmal unsere Rampe an.

Dabei haben wir Abschnitt 4, dessen Wegstrecke zwischen 00 und ∞\infty liegt, und die übrigen Abschnitte, welche jeweils zwischen 00 und einem jeweiligen Maximalwert liegen. Dieser Unterschied ist wichtig. Wir bezeichnen diese folgend als max⁡d1\max d_1, max⁡d2\max d_2, max⁡d3\max d_3, max⁡d5\max d_5, max⁡d6\max d_6 und max⁡d7\max d_7.

Durch Abschnitt 4 gibt es keine Begrenzung für dmoved_\mathrm{move} nach oben.

Anders sieht es aus, wenn eine Fahrt gerade kürzer ausfällt, als die Summe der anderen Abschnitte.

Dazu noch einmal die Reihenfolge der Kürzungen:

  1. Abschnitt 4, bis dieser ganz verschwindet
  2. Abschnitte 2 und 6 symmetrisch
  3. Abschnitte 1, 3, 5 und 7 symmetrisch

Aus didaktischen Gründen starten wir mit den mathematisch komplexesten Abschnitten 1, 3, 5 und 7. Diese Phasen dienen der Begrenzung des Rucks.

In unserem Beispiel gehen wir davon aus, dass der Ruck JJ beim Beschleunigen wie auch Abbremsen mit einem identischen Maximalwert festgelegt wird. Damit können die Funktionsverläufe der Abschnitte durch Transformationen auf einen gemeinsamen Funktionsverlauf als Basis gesetzt werden.

Wir betrachten zunächst Abschnitt 1. Hier werden Geschwindigkeit und Beschleunigung vom Stillstand v=0,a=0v=0, a=0 bis zur maximalen Beschleunigung aend=max⁡aa_\mathrm{end} = \max a angehoben. Dieser Abschnitt führt zu einem vendv_\mathrm{end} und tendt_\mathrm{end}.

Genauso wie wir Geschwindigkeit und Beschleunigung erhöht haben, müssen wir diese anschließend wieder erniedrigen. Im Abschnitt 3 wird die Beschleunigung bei weiter zunehmender Geschwindigkeit bis a=0a=0verringert, in Abschnitt 5 bei abnehmender Geschwindigkeit negative Beschleunigung aufgebaut und im Abschnitt 7 wieder abgebaut, wie auch die Restgeschwindigkeit. Am Ende herrschen wieder v=0,a=0v=0, a=0.

Nun schauen wir uns an, wie sich der Verlauf ändert, wenn die Abschnitte 1, 3, 5 und 7 nicht voll durchlaufen werden, da der Weg kürzer ausfällt. Zunächst gilt durch Integration von vv aus aa und ss aus vv:

a(t)=∫0tj(τ)dτ=∫0tJdτ=Jt,v(t)=∫0ta(τ)dτ=∫0tJτdτ=J2t2,s(t)=∫0tv(τ)dτ=∫0tJ2τ2dτ=J6t3.\begin{aligned} a(t) &=\int_0^t j(\tau)\,\mathrm{d}\tau =\int_0^t J\,\mathrm{d}\tau =Jt,\\[4pt] v(t) &=\int_0^t a(\tau)\,\mathrm{d}\tau =\int_0^t J\tau\,\mathrm{d}\tau =\frac{J}{2}t^2,\\[4pt] s(t) &=\int_0^t v(\tau)\,\mathrm{d}\tau =\int_0^t \frac{J}{2}\tau^2\,\mathrm{d}\tau =\frac{J}{6}t^3. \end{aligned}

Nun legen wir die verkürzten Abschnitte alle auf einen gemeinsamen Startpunkt:

Der dunkelblaue Kurvenverlauf ist in allen Fällen gemeinsam. Unterschiedlich ist nur, wann dieser verlassen wird. Zu trennen sind dabei der Graph für Weg-Zeit s(t)s(t) zu Geschwindigkeit-Zeit v(t)v(t) und Beschleunigung-Zeit a(t)a(t). Die Graphen von v(t)v(t) und a(t)a(t) lassen sich in allen Fällen durch einfache Spiegelungen und Translationen von Abschnitt 1, im Bedarfsfall gekürzt, modellieren.

Tatsächlich gibt es Implementationen, welche auf dieser Zeitbasis aufsetzen. Wir gehen aber einen Schritt weiter. Bei der Fahrt wird eine Funktion fortwährend gerufen, die ISR des Timers. Diese generiert den nächsten Teilschritt des Steppers und muss dann den Timer mit der nächsten Weckzeit laden.

Für unsere Ansteuerung des Steppers ist daher nicht die Zeit die Basis, sondern seine Schritte bzw. Teilschritte. Nicht die Zeit, sondern der Schritt ist sozusagen unser Takt, und daher betrachten wir nun Zeit, Geschwindigkeit und Beschleunigung aus Perspektive des Weges:

In jedem Abschnitt geht es daher darum, so schnell wie möglich bei jedem Teilschritt das Neuladen des Timers Δt(i)\Delta t(i) zu ermöglichen. Hier gilt es je nach Anforderung zwei grundsätzliche Ansätze zu unterscheiden:

  • Δt(i)\Delta t(i) wird direkt vorberechnet und muss von der ISR nur noch in den Timer geladen werden.
    Erfolgt die Timerprogrammierung relativ zum tatsächlichen Ausführungszeitpunkt der ISR, können sich ISR-Latenzen und Rundungsfehler über die Fahrt aufsummieren.
  • Δt(i)\Delta t(i) wird aus vorberechneten absoluten Zeitpunkten t(i)t(i) berechnet.
    Dadurch bleiben die Sollzeitpunkte auf einen gemeinsamen zeitlichen Ursprung bezogen. Eine Akkumulation von Rundungsfehlern bei der Berechnung einzelner Zeitintervalle wird vermieden. Auch ISR-Latenzen führen bei geeigneter Timerprogrammierung nicht zu einer fortschreitenden zeitlichen Verschiebung.

Die Wahl hängt von den Rahmenbedingungen der Fahrt ab, etwa, ob ein Messpunkt auch während der Fahrt z.B. Sensordaten erfassen soll.

Unsere LUT muss als Schlüssel also nicht die Zeit, sondern den Weg, als Teilschritte, besitzen. Jedem Teilschritt wird die Zeit zum Nachladen des Timers zugeordnet, wie oben erläutert bereits als Differenzzeit zwischen zwei Steps oder als Differenz zum Beginn des Abschnitts.

Daher müssen wir unsere Funktionen ff von Zeit tt auf Weg ss umstellen.

s=J6t36s=Jt36sJ=t36sJ3=t33=ttA(s)=6sJ3\begin{aligned} s&=\frac{J}{6}t^3\\ 6s&=Jt^3\\ \frac{6s}{J}&=t^3\\ \sqrt[3]{\frac{6s}{J}}&=\sqrt[3]{t^3}=t\\ t_A(s)&=\sqrt[3]{\frac{6s}{J}} \end{aligned}

Wird t(s)t(s) nun auch in v(t)v(t) und a(t)a(t) eingesetzt, so ergibt sich für v(s)v(s) und a(s)a(s) daraus:

v(s)=J2(6sJ)2/3,a(s)=J(6sJ)1/3\boxed{ v(s)=\frac{J}{2}\left(\frac{6s}{J}\right)^{2/3}, \qquad a(s)=J\left(\frac{6s}{J}\right)^{1/3} }

Darauf aufbauend betrachten wir nun unsere symmetrisch verkürzten Abschnitte 1, 3, 5 und 7 diesmal aus der Sicht des Weges:

Der Preis ist sofort sichtbar: nun können wir in den Abschnitten 3 und 5 nicht mehr nur mit Spiegelung und Verschiebung arbeiten. Der Grund liegt in t=dsdvt=\frac{ds}{dv}. Durch die unterschiedlichen Start- und Zielgeschwindigkeiten vstartv_\mathrm{start} und vendv_\mathrm{end} im Gegensatz zu den Abschnitten 1 und 7 ergeben sich die Skalierungseffekte für die Abschnitte 3 und 5.

Da die Abschnitte 1 und 7 dagegen immer v=0v=0 als Start- oder Endgeschwindigkeit besitzen, wie auch a=0a=0 als Start- oder Endbeschleunigung, ist auch die Skalierung hier immer gleich und muss nicht nachträglich einberechnet werden.

Die Grafiken zeigen ebenfalls, die Abschnitte 3 und 5 besitzen viel mehr Teilschritte als die Abschnitte 1 und 7, da hier die Geschwindigkeiten wesentlich höher liegen.

Im Abschnitt 1 gilt:

s(0)=0,v(0)=0,a(0)=0,j(t)=Js(0)=0,\qquad v(0)=0,\qquad a(0)=0,\qquad j(t)=J

Und daraus folgend:

a(t)=a(0)+∫0tj(τ)dτ=∫0tJdτ=Jt,v(t)=v(0)+∫0ta(τ)dτ=∫0tJτdτ=J2t2,s(t)=s(0)+∫0tv(τ)dτ=∫0tJ2τ2dτ=J6t3.\begin{aligned} a(t) &=a(0)+\int_0^t j(\tau)\,\mathrm{d}\tau =\int_0^t J\,\mathrm{d}\tau =Jt,\\[4pt] v(t) &=v(0)+\int_0^t a(\tau)\,\mathrm{d}\tau =\int_0^t J\tau\,\mathrm{d}\tau =\frac{J}{2}t^2,\\[4pt] s(t) &=s(0)+\int_0^t v(\tau)\,\mathrm{d}\tau =\int_0^t \frac{J}{2}\tau^2\,\mathrm{d}\tau =\frac{J}{6}t^3. \end{aligned}

Abschnitt 5 startet dagegen mit einer Geschwindigkeit VV:

s(0)=0,v(0)=V,a(0)=0,j(t)=−Js(0)=0,\qquad v(0)=V,\qquad a(0)=0,\qquad j(t)=-J

Daraus folgt:

a(t)=a(0)+∫0tj(τ)dτ=∫0t(−J)dτ=−Jt,v(t)=v(0)+∫0ta(τ)dτ=V+∫0t(−Jτ)dτ=V−J2t2,s(t)=s(0)+∫0tv(τ)dτ=∫0t(V−J2τ2)dτ=Vt−J6t3.\begin{aligned} a(t) &=a(0)+\int_0^t j(\tau)\,\mathrm{d}\tau =\int_0^t (-J)\,\mathrm{d}\tau =-Jt,\\[4pt] v(t) &=v(0)+\int_0^t a(\tau)\,\mathrm{d}\tau =V+\int_0^t (-J\tau)\,\mathrm{d}\tau =V-\frac{J}{2}t^2,\\[4pt] s(t) &=s(0)+\int_0^t v(\tau)\,\mathrm{d}\tau =\int_0^t \left(V-\frac{J}{2}\tau^2\right)\,\mathrm{d}\tau =Vt-\frac{J}{6}t^3. \end{aligned}

Genau hier sehen wir den Skalierungseffekt von a(s)a(s), v(s)v(s) und t(s)t(s) durch VV hervorgerufen.

Leider haben wir noch einen weiteren Effekt. Für uns ist t(s)t(s) durch die ISRs wesentlich interessanter als s(t)s(t). Für die Abschnitte 1 und 7 ist dies trivial, wie wir bereits gesehen haben: tA(s)=6sJ3t_A(s)=\sqrt[3]{\frac{6s}{J}}.

Bei den Abschnitten 3 und 5 mit der Umstellung von s(t)=Vt−J6t3s(t) =Vt-\frac{J}{6}t^3 auf t(s)t(s) wird das Problem erheblich größer, denn wir erhalten eine kubische Gleichung mit einem linearen Term:

t3−6vmax⁡Jt+6sJ=0t^3 – \frac{6v_{\max}}{J}t + \frac{6s}{J} = 0

Und die Werkzeuge zu deren Auflösung sind auf analytischem Weg Winkelfunktionen oder komplexe Zahlen (Cardano ohne komplexe Zahlen scheitert bei uns an der Wurzel negativer Zahlen):

t5(s)=22vstartJsin⁡[13arcsin⁡(3s2vstartJ2vstart)]t_5(s)= 2\sqrt{\frac{2v_{\mathrm{start}}}{J}}\, \sin\!\left[ \frac{1}{3}\arcsin\!\left( \frac{3s}{2v_{\mathrm{start}}} \sqrt{\frac{J}{2v_{\mathrm{start}}}} \right) \right]
t5(s)=Re⁡[(−1−i3)−3sJ+i8vstart3J3−9s2J23]t_5(s)= \operatorname{Re}\!\left[ (-1-\mathrm{i}\sqrt{3})\, \sqrt[3]{ -\frac{3s}{J} +\mathrm{i}\sqrt{ \frac{8v_{\mathrm{start}}^3}{J^3} -\frac{9s^2}{J^2} } } \right]

Und hier wird es interessant. Unsere Problemstellung lässt sich mit LUTs mathematisch so umstellen, dass es drei Berechnungsteile gibt. Möglich wird dies, da, wie im Vierabschnittsprofil über den Weg ersichtlich, die Verläufe immer wieder auf einen Verlauf mit Skalierung zurückführen lassen. Darauf aufbauend realisieren wir:

  • die Abbildung eines Grundverlaufes in der LUT, zur compile-Zeit oder beim Programmstart
  • die Berechnung der Skalierungswerte einmalig bei der Bahnberechnung
  • die Berechnung der nächsten Weckzeit auf Grundlage von LUT und Skalierungswerten in der ISR

Zur Verdeutlichung noch einmal die Verläufe:

Unser „Takt“ ist der Teilschritt. Berechnen wir dabei für den Fall der vollständigen Abschnitte 3 und 5, so erhalten wir die höchste Auflösung. Bei verkürzten Abschnitten skalieren wir immer nach unten. Heißt, es fehlen nie Stützstellen, wir müssen nur zwischen zwei Stützstellen interpolieren, wenn wir keine Stützstelle exakt treffen. Und gerade für die schnellste Fahrt mit vollständigen Abschnitten werden die Stützstellen direkt in der LUT gehalten, es ist dann keine Interpolation nötig.

Ausgangspunkt ist die physikalische Weggleichung

s=Vt−J6t3s=Vt-\frac J6t^3

Unsere LUT stellt die Beziehung zwischen einem Referenzweg SS und einer Referenzzeit TT her. Beide normieren wir hierzu:

T=2VJ,S=VTT=\sqrt{\frac{2V}{J}}, \qquad S=VT

Den späteren Bezug zu unserer realen Bahnberechnung stellen wir her durch den normierten Weg s^\hat{s} und die normierte Zeit t^\hat{t}:

s^=sS,t^=tT\hat{s}=\frac{s}{S}, \qquad \hat{t}=\frac{t}{T}

Nun stellen wir um

s^=sS=VtS−Jt36S=VTSt^−JT36St^3.\begin{aligned} \hat{s}=\frac{s}{S} &=\frac{Vt}{S}-\frac{Jt^3}{6S}\\ &=\frac{VT}{S}\hat{t}-\frac{JT^3}{6S}\hat{t}^3. \end{aligned}

Die Wahl S=VTS=VT setzt den Koeffizienten des linearen Terms auf eins. Damit folgt:

s^=t^−JT36VTt^3=t^−JT26Vt^3.\begin{aligned} \hat{s} &=\hat{t}-\frac{JT^3}{6VT}\hat{t}^3\\ &=\hat{t}-\frac{JT^2}{6V}\hat{t}^3. \end{aligned}

Und hier ist die Stelle, warum wir T=2VJT=\sqrt{\frac{2V}{J}} gesetzt haben. Denn dadurch gilt T2=2VJT^2=\frac{2V}{J}. Und damit reduziert sich unsere Gleichung zu:

s^=t^−t^33\hat{s}=\hat{t}-\frac{\hat{t}^3}{3}

Diese Gleichung liefert den normierten Weg s^\hat{s} aus der normierten Zeit t^\hat{t}.

Dies ist die Basis für unsere Umkehrfunktion. Wir nennen sie FtF_t. Sie liefert nun t^\hat{t} aus s^\hat{s}.

s^=t^−t^33⟺t^=Ft(s^)\hat{s}=\hat{t}-\frac{\hat{t}^3}{3} \quad\Longleftrightarrow\quad \hat{t}=F_t(\hat{s})

Im verwendeten Bereich 0≤t^≤1/20\leq \hat{t} \leq1/\sqrt2 ist die Wegfunktion streng monoton steigend und damit eindeutig umkehrbar.

Anmerkung: Die Funktion ist sogar auf 0≤t^≤10\leq \hat{t} \leq1 streng monoton steigend. Bei unserem symmetrischen Vierabschnittprofil gilt V=Jτ2V=J\tau^2. Mit T=2V/JT=\sqrt{2V/J} folgt damit unmittelbar:

τT=12\frac{\tau}{T}=\frac1{\sqrt2}

Nun setzen wir t^=t/T\hat{t}=t/T und s^=s/S\hat{s}=s/S wieder ein:

tT=Ft(sS)\frac{t}{T}=F_t\!\left(\frac{s}{S}\right)

Durch Multiplikation mit TT ergibt die Form:

t(s)=TFt(sS)t(s)=T\,F_t\!\left(\frac{s}{S}\right)

Dabei rechnet s/Ss/S den konkreten Bahnweg in einen normierten Weg s^\hat{s} um, FtF_t liefert die zugehörige normierte Zeit, und TT skaliert diese zurück in die reale Bahnzeit tt.

Durch diese Umformung haben wir unser Problem in drei Teile zerlegt:

  • Einmalig aufwendige Berechnung der LUT für FtF_t zur Compile-Zeit oder bei der Initialisierung der Firmware
  • Berechnung von TT und SS bei der Bahnberechnung
  • Berechnung von t(s)t(s) bei jeder ISR

Im Kern reduzieren wir damit die Auswertung der inversen Wegfunktion auf zwei Skalierungsmultiplikationen und einen LUT-Zugriff, gegebenenfalls mit Interpolation.

Im nächsten Teil wird die numerische Umsetzung dieses Ansatzes betrachtet.