Ich habe hauptsächlich mit 8-Bit-MCUs gearbeitet, wo die meisten RTOSs zu viel Overhead haben.
Die meisten Anwendungen, an denen ich gearbeitet habe, waren nur ein periodischer Interrupt mit if/else-Ketten für die gesamte Verarbeitungslogik, und dann geht die MCU wieder in den Ruhezustand.
Dies hat für viele Dinge gut funktioniert und hat einen wirklich minimalen Overhead. Aber für ein System komme ich an den Punkt, an dem es so viele Steuerflags gibt, dass ich bereit bin, mein eigenes System "Spaghetti" zu nennen. Es wäre entsetzlich für jemand neuen, dieses System aufzugreifen und einige neue Funktionen zu implementieren.
(Ich habe eine zweifarbige LED, die ungefähr 8 verschiedene Zustände und zeitabhängige Blinkmuster haben muss, je nachdem, in welchem Zustand sich der Rest des Systems befindet. Es ist eine schreckliche Übung, denn was so einfach sein sollte ...)
Ich habe mir überlegt, vielleicht eine endliche Zustandsmaschine zu machen und zu versuchen, so viele Kontrollflags auszusortieren.
Ein konzeptionelles Problem, das ich sehe, ist die Verwendung von Timern in einer Zustandsmaschine. Derzeit habe ich einen Hardware-Timer und dann eine Reihe von variabel definierten Timer-Zählern, die inkrementieren / dekrementieren, eine Steuerflag-Variable geht auf 0/1, und so gehen wir die if/else-Kette durch.
Würden Sie in meiner Planungsphase für eine strengere Zustandsmaschine einfach mehr Hardware-Timer verwenden und die externen Interrupts als Ereignisse auslösen, um zur Zustandsmaschine zurückzukehren?
Meine Bauchreaktion (ob richtig oder nicht) besteht darin, so viele externe Interrupts wie möglich für die Zustandsmaschine zu verwenden Die Steuerlogik ist einfach verwirrend und 2) Sie verwenden mehr Strom, um eine Reihe von Timern auszuführen, anstatt nur die Timer-Logik als Variablen zu behandeln.
Ich sehe, wie Sie variable Timer in Ihrer Zustandsmaschine immer noch inkrementieren / deinkrementieren könnten, aber ist das nicht unethisch gegenüber dem Zustandsmaschinenmuster?
Ich bin ziemlich zufrieden mit der Debatte zwischen Funktionszeigern und Schalteranweisungen, wie Sie die Zustandsmaschine codieren oder ob Sie eine Übergangstabelle usw. verwenden möchten.
Ich frage mich speziell, wie die Leute den Zeitverwaltungsaspekt ihrer Zustandsmaschinen auf elegante Weise gehandhabt haben.
Eine übliche Methode wäre, eine maximale Ausführungszeit für jeden Zustand festzulegen und dann jeden von ihnen zu bewerten (mit 100 % Codeabdeckung) und sicherzustellen, dass sie die maximale Ausführungszeit nie überschreiten. Mit etwas Glück kann man dafür sogar den On-Chip-Watchdog verwenden, wenn dieser mit ausreichend niedrigen Timeouts laufen kann.
Was Sie jetzt wahrscheinlich suchen, ist nicht eine, sondern mehrere Zustandsmaschinen. Das heißt, Sie können eine universelle Zustandsmaschine wie z
STATE_MACHINE[state++]();
if(state == STATES_N)
{ /* reset state machine */
}
was nichts anderes tut, als die verschiedenen Softwaremodule zu durchlaufen und ihnen jeweils eine "Zeitscheibe" zu geben. Entweder können Sie alle verschiedenen Hardwaretreiber auf einmal durchlaufen und wieder schlafen gehen, oder Sie können nur einen einzigen davon ausführen. Dies hängt natürlich von den Echtzeitanforderungen ab.
Ein solcher Zustand könnte sein led_execute()
, was die LED-Routine wäre, die verfolgt, was gerade auf den LEDs passiert. Diese Routine befindet sich im LED-Treiber und kann wiederum jeden LED-Zustand verfolgen, sodass es in etwa so aussieht:
typedef enum
{
LED_OFF,
LED_RED_LIT, // whatever names make sense
LED_RED_BLUE_LIT,
...
LED_DONE,
LED_N
} led_state_t;
...
static led_state_t led_state = LED_OFF;
...
void led_execute (void)
{
led_state = LED_STATE_MACHINE[led_state]();
}
Wenn die Zustände von externen Eingaben abhängen, überspringen Sie möglicherweise den Rückgabezustandsteil und lassen Sie den Zustand nur durch Setter/Getter aktualisieren.
Dies sollte die Notwendigkeit von Flags vollständig eliminieren – insbesondere nicht verwandte Flags, die sich im selben Bereich befinden, was ein Alptraum sein kann. Der wichtigste Teil hier ist, die LED-Komplexität nicht mit der Komplexität anderer Hardware zu verwechseln.
Nehmen wir an, Sie entprellen gleichzeitig eine Taste. Angenommen, Sie müssen das Entprellen beenden, bevor die LEDs leuchten können - das bedeutet nicht, dass die Tasten über LEDs Bescheid wissen müssen oder dass LEDs über Tasten Bescheid wissen müssen. Der Anrufercode sollte diese Dinge verfolgen. Das heißt, Sie benötigen möglicherweise eine Abstraktionsschicht zwischen der äußersten Zustandsmaschine und den Treibern selbst. Wenn der LED-Treiber nur einen Eingang bekommt, "mach das!" vom Anrufer, dann sind ihm die Gründe dafür völlig egal.
Wenn Sie Super-Low-Tech-Multitasking wollen, kann Ihr Timer-Interrupt so aussehen:
timer_isr()
{
process1();
process2();
process3();
}
Wenn Ihr Timer also beispielsweise 100x pro Sekunde ausgelöst wird, dann jedes Mal, wenn jede process() -Funktion aufgerufen wird. Diese Funktionen sind FSMs, die Ihre verschiedenen "Multitasking"-Aufgaben implementieren. Wenn sie alle unabhängig sind, dann ist es einfach.
Wenn Ihre Aufgaben nun abhängig sind, können Sie Folgendes tun:
timer_isr()
{
check_buttons();
blink_red();
blink_blue();
}
In diesem Fall kommunizieren die Tasks über hässliche globale Variablen (wir werden keine Messageboxen und FIFOs auf einem 8-Bit machen, oder). Zum Beispiel würde check_buttons() entprellen usw. und einige Flags setzen und/oder direkt den Zustand der anderen zwei FSMs beeinflussen, die die LEDs blinken lassen.
Wir können sogar diese Spitzentechnologie namens C++ verwenden:
timer_isr()
{
check_buttons();
red.blink();
blue.blink();
}
In diesem Fall würde check_buttons() beispielsweise "red.setBlinkMode(some value)" aufrufen, wenn der entsprechende Button gedrückt wird. "rot" und "blau" sind globale Objekte. In diesem Fall können Sie mit diesem winzigen Stück OO denselben Algorithmus für beide implementieren, ohne sich mit Tonnen von Globals oder Zeigern auf Strukturen usw. herumschlagen zu müssen.
Es ist eine nette Sache, Ihre Schaltflächen nur an einer Stelle in Ihrem Code zu behandeln. Vor allem, wenn die Tasten mehrere Dinge steuern, zum Beispiel eine Taste zum Auswählen der LED und eine andere Taste zum Ändern des Blinkmusters der ausgewählten LED.
Die .blink()-Methode würde zum Beispiel einen LED-spezifischen Zähler erhöhen, bis er die Blinkperiode erreicht, oder eine PWM zeitabhängig anpassen, damit sie ausgefallen blinkt, solche Sachen.
Wenn Ihre Zeiten beispielsweise alle Millisekunden aufgerufen werden, könnte Ihre Methode blink() so aussehen:
LED::blink()
{
if( led_on) {
if( counter++ > period ) {
counter=0;
led_pin = !led_pin;
}
} else { led_pin = 0; counter=0; }
}
...so ähnlich. Sie werden alle zur gleichen Zeit aufgerufen, also kennen all diese kleinen Zustandsmaschinen die Zeit, indem sie zählen, wie oft sie aufgerufen werden. In diesem Fall ist der Zustand des FSM led_on und counter.
Im Allgemeinen habe ich festgestellt, dass es am besten ist, ein kleines Zeitinkrement auszuwählen (vielleicht ein paar Millisekunden bis vielleicht 250 us für ein 8-Bit-Mikro) und dieses für den größten Teil oder das gesamte Timing zu verwenden. Dies ist vergleichbar mit der Granularität in einem RTOS.
Es ist einfacher, wenn Sie ein Mikro mit einer anständigen Architektur haben, die verschachtelte Interrupts zulässt.
Andi aka
Leroy105
mkeith
Leroy105
mkeith
AlphaGoku