FSST - Hardwarenahe Programmierung

Events

Ereignisse in C++

Basiert auf Simplify your Code

Information

Make sure you have updated your firmware to the latest revision.

In mbed OS 5.2 wurden mbed-events eingeführt, ein Eventing-System, das in einem RTOS-Thread laufen kann. Die Verwendung einer Ereignisschleife ist sehr nützlich, um die Ausführung von Code in einen anderen Kontext zu verschieben. Ein Beispiel wäre, die Ausführung von einem Interrupt-Kontext (ISR) auf die Hauptschleife zu verschieben oder die Ausführung vom Thread mit hoher Priorität auf einen Thread mit niedriger Priorität zu verschieben. Nun, da mbed-events Teil von mbed OS ist soll nun gezeigt, wie man Anwendungen verbessern kann. (Siehe auch EventQueue Class Reference)

Printf in einem Interrupt-Kontext aufrufen

Das folgende Programm wurde wahrscheinlich von jemandem geschrieben, der sich mit der Programmierung von Mikrocontrollern vertraut macht. Es registriert einen Interrupt-Handler, wenn eine Taste gedrückt wird, und ruft dann printf von der ISR auf. Machen Sie ein neues mbed-OS-Projekt im Online-Compiler und kopieren Sie den jeweiligen Code in main.cpp.

Naive approach

#include "mbed.h"

DigitalOut led(LED1);
InterruptIn btn(p15);

void fall_handler() {
    led = !led;
    printf("fall_handler in context %p\r\n", Thread::gettid()); // CRASH! Blocking call in ISR...
}

int main() {
    btn.fall(&fall_handler);  // The 'fall' handler will execute in IRQ context
    printf("Starting in context %p\r\n", Thread::gettid());
    wait(osWaitForever);
}

Information

Anmerkung zum Online-Compiler: obiges Programm funktioniert, allerdings nicht in der Keil-Umgebung. Um obigen Fehler trotzdem zu simulieren kann die Funktion printf in einen Mutex gekapselt werden.

Wenn Sie diesen Code mit ARMCC kompilieren, stürzt das Programm unmittelbar nach dem Umschalten der LED ab. Dies liegt daran, dass Aufrufe von stdio (wie printf) durch Mutexe in der ARM C-Standardbibliothek geschützt werden und Mutex-Funktionen nicht von einer ISR aus aufgerufen werden können. Wir können dies umgehen, indem wir den Haupt-Thread von der ISR signalisieren und dort den printf-Aufruf durchführen. Dies ist besonders verwirrend, wenn Anfänger unterrichtet werden, da jetzt auch das Konzept von Semaphoren oder Mailboxen erläutert werden muss.

Semaphor Approach

#include "mbed.h"

DigitalOut led(LED1);
InterruptIn btn(p15);

Semaphore updates(0);

void fall_handler() {
    // release the semaphore
    updates.release();
}

int main() {
    btn.fall(&fall_handler);

    while (1) {
        int32_t v = updates.wait(); // wait for the semaphore to be released from the ISR

        if (v == 1) {     // now this runs on the main thread, and is safe
            led = !led;
            printf("Using a Semaphoer to toggle LED\r\n");
        }
    }
}

Während dies funktioniert, ist es viel unklarer, und wir müssen eine Zustandsmaschine erstellen, um zu bestimmen, warum das Semaphor freigegeben wurde, wenn weitere Interrupts hinzugefügt werden. Vorzugsweise würden wir dies auch in einem separaten Thread ausführen. Mit mbed-events können wir problemlos einen neuen Thread mit der darin ablaufenden Ereignisschleife starten und wir können in einer Codezeile von ISR zu diesem Thread wechseln.

Event Queue

#include "mbed.h"

DigitalOut led(LED1);
InterruptIn btn(p15);

EventQueue queue;   // create an event queue

void fall_handler() {
    // this now runs in the context of eventThread, instead of in the ISR
    led = !led;
    printf("Toggle LED in context %p\r\n", Thread::gettid());
}

int main() {
    // create a thread that'll run the event queue's dispatch function
    Thread eventThread;
    eventThread.start(callback(&queue, &EventQueue::dispatch_forever));
    printf("Using Event Queue starting in context %p\r\n", Thread::gettid());
    
// wrap calls in queue.event to automatically defer to the queue's thread
    btn.fall(queue.event(&fall_handler));
    wait(osWaitForever);
}

Wenn der Interrupt ausgelöst wird, wird der Aufruf der fall_handler-Funktion jetzt automatisch auf den anderen Thread verschoben, von wo aus der Befehl printf aufgerufen werden kann. Darüber hinaus müssen wir die Hauptschleife unseres Haupt-Threads nicht mit Programmlogik versehen.

Verschieben des Aufrufs in anderen Thread

Der Nachteil dieses Ansatzes ist, dass sowohl das Umschalten der LED als auch der printf-Aufruf jetzt außerhalb der ISR ausgeführt werden und nicht garantiert sind, dass sie sofort ausgeführt werden. Wir können dies umgehen, indem wir die LED von der ISR umschalten und dann das printf -Ereignis manuell auf den Thread verschieben.

Thread Transfer

#include "mbed.h"

DigitalOut led(LED1);
InterruptIn btn(p15);

EventQueue queue;

void fall_handler_outside_irq() {
    // this does not run in the ISR
    printf("Toggle LED in context %p\r\n", Thread::gettid()); 
}

void fall_handler_in_irq() {
    // this runs in the ISR
    led = !led;
    // comment next line because of ...
    printf("In ISR context %p\r\n", Thread::gettid());  // ... blocking call in ISR...

    // then defer the printf call to the other thread
    queue.call(&fall_handler_outside_irq);
}

int main() {
    Thread eventThread;
    eventThread.start(callback(&queue, &EventQueue::dispatch_forever));
    printf("Defering from ISR to thread in context %p\r\n", Thread::gettid());

    btn.fall(&fall_handler_in_irq);

    wait(osWaitForever);
}

Ereignisse mit hoher und niedriger Priorität mischen

Wir können die Wichtigkeit von Ereignissen unterscheiden, indem Sie mehrere Threads verwenden, die mit unterschiedlichen Prioritäten ausgeführt werden. Wir können dem Programm problemlos einen Ticker hinzufügen, durch den LED2 jede Sekunde umgeschaltet wird, der mit einer höheren Priorität als die printf-Aufrufe ausgeführt wird, indem eine zweite Ereigniswarteschlange erstellt wird.

Priorisierung

#include "mbed.h"

DigitalOut led1(LED1);
DigitalOut led2(LED2);
InterruptIn btn(p15);

EventQueue printfQueue;
EventQueue eventQueue;

void blink_led2() {
    // this runs in the normal priority thread
    led2 = !led2;
}

void print_toggle_led() {
    // this runs in the lower priority thread
    printf("Toggle LED!\r\n");
}

void btn_fall_irq() {
    led1 = !led1;

    // defer the printf call to the low priority thread
    printfQueue.call(&print_toggle_led);
}

int main() {
    // low priority thread for calling printf()
    Thread printfThread(osPriorityLow);
    printfThread.start(callback(&printfQueue, &EventQueue::dispatch_forever));

    // normal priority thread for other events
    Thread eventThread(osPriorityNormal);
    eventThread.start(callback(&eventQueue, &EventQueue::dispatch_forever));

    // call blink_led2 every second, automatically defering to the eventThread
    Ticker ledTicker;
    ledTicker.attach(eventQueue.event(&blink_led2), 1.0f);

    // button fall still runs in the ISR
    btn.fall(&btn_fall_irq);

    wait(osWaitForever);
}

mbed-events macht es viel einfacher, Aufrufe von einem Kontext in einen anderen zu verschieben, sei es von einer ISR zurück zu einem Benutzer-Thread oder von einem Thread zu einem anderen. Es macht es auch einfach, bestimmte Ereignisse gegenüber anderen Ereignissen zu priorisieren, und Sie müssen nicht Ihre eigene Zustandsmaschine schreiben oder Ihre Hauptschleife beeinflussen. Da es sich um einen Einzeiler handelt (wrappen (wickeln) Sie den Rückruf in queue.event () ein), um einen Anruf zu wrappen, der normalerweise in einer ISR ausgeführt würde, ist er auch für Anfänger sehr freundlich.

The mbed-events library

Die Bibliothek mbed-events bietet eine flexible Warteschlange für die Planung von Ereignissen; siehe auch auf GitHub.

Call Events

#include "mbed.h"

int main() {
    // creates a queue with the default size
    EventQueue queue;

    // events are simple callbacks
    queue.call(printf, "called immediately\n");
    queue.call_in(2000, printf, "called in 2 seconds\n");
    queue.call_every(1000, printf, "called every 1 seconds\n");

    // events are executed by the dispatch method
    queue.dispatch();
}

Sie können die mbed-events-Bibliothek als normale Ereignisschleife verwenden, oder Sie können sie mit einem einzelnen Hardware-Timer oder sogar einer anderen Ereignisschleife als Hintergrund verwenden. Es ist sowohl Thread- als auch IRQ-sicher und bietet Funktionen zum einfachen Erstellen unabhängiger Ereigniswarteschlangen. Die mbed-events-Bibliothek kann als Drop-In-Scheduler fungieren, Synchronisation zwischen mehreren Threads bereitstellen oder als Mechanismus zum Verschieben von Ereignissen aus Interrupt-Kontexten dienen. Verwendungszweck Der Kern der mbed-events-Bibliothek ist die EventQueue-Klasse, die eine einzelne Ereigniswarteschlange darstellt. Die EventQueue::dispatch-Funktion führt die Warteschlange aus und stellt den Kontext für die Ausführung von Ereignissen bereit.

Ad-on to the above program

// Creates an event queue enough buffer space for 32 Callbacks. This
// is the default if no argument was provided. Alternatively the size
// can just be specified in bytes.
EventQueue queue2(32*EVENTS_EVENT_SIZE);
printf("Size: %d\n", EVENTS_EVENT_SIZE);

// Events can be posted to the underlying event queue with dynamic
// context allocated from the specified buffer
queue.call(printf, "hello %d %d %d %d\n", 1, 2, 3, 4);

// The dispatch function provides the context for the running the queue
// and can take a millisecond timeout to run for a fixed time or to just
// dispatch any pending events
queue.dispatch();

Die EventQueue-Klasse bietet mehrere Aufruffunktionen zum Buchen von Ereignissen in die zugrundeliegende Ereigniswarteschlange. Die Aufruffunktionen sind Thread- und IRQ-sicher. Sie benötigen keine laufende Schleife und bieten einen Mechanismus zum Verschieben von Ereignissen aus Interrupt-Kontexten.

Timer and Events

// Simple call function registers events to be called as soon as possible
queue.call(doit);
queue.call(printf, "called immediately\n");

// The call_in function registers events to be called after a delay
// specified in milliseconds
queue.call_in(2000, printf, "do it in 2 seconds\n");
queue.call_in(300, printf, "called in 0.3 seconds\n");

// The call_every function registers events to be called repeatedly
// with a period specified in milliseconds
queue.call_every(2000, printf, "do it every 2 seconds\n");
queue.call_every(400, printf, "called every 0.4 seconds\n");

Die Aufruffunktionen geben eine ID zurück, die das Ereignis in der Ereigniswarteschlange eindeutig darstellt. Sie können diese ID an EventQueue::cancel übergeben, um ein Ereignis während des Fluges abzubrechen.

// The event id uniquely represents the event in the queue
int id = queue.call_in(100, printf, "will this work?\n");

// If there was not enough memory necessary to allocate the event,
// an id of 0 is returned from the call functions
if (id) {
    error("oh no!");
}

// Events can be cancelled as long as they have not been dispatched. If the
// event has already expired, cancel has no side-effects.
queue.cancel(id);

Für eine detailliertere Steuerung der Ereignisausgabe können Sie die Ereignisklasse manuell instanziieren und konfigurieren. Ein Ereignis stellt ein Ereignis als Funktionsobjekt im C++ - Stil dar und kann direkt an andere APIs übergeben werden, die einen Rückruf erwarten.

#include "mbed.h"

int main() {
// Creates an event bound to the specified event queue
    EventQueue queue;
// Events can also pass arguments to the underlying callback when both
// initially constructed and posted.
    Event<void(int, int)> event(&queue, printf, "received %d and %d\n");

// Events can be posted multiple times and enqueue gracefully until
// the dispatch function is called.
    event.post(1, 2);
    event.post(3, 4);
    event.post(5, 6);

    queue.dispatch();
}

Ereigniswarteschlangen können problemlos an den Modulgrenzen ausgerichtet werden, wobei der interne Status durch die Ereignisabgabe implizit synchronisiert werden kann. Mehrere Module können unabhängige Ereigniswarteschlangen verwenden, sie können jedoch über die EventQueue::chain-Funktion erstellt werden.

Chaining of Events

#include "mbed.h"

int main() {
    // Create some event queues with pending events
    EventQueue a;
    //a.call(printf, "hello from a!\n");

    EventQueue b;
    b.call(printf, "hello from b!\n");

    EventQueue c;
    c.call(printf, "hello from c!\n");

    // Chain c and b onto a's event queue. Both c and b will be dispatched
    // in the context of a's dispatch function.
    c.chain(&a);
    b.chain(&a);

    // Dispatching a will in turn dispatch b and c, printing hello from
    // all three queues
    a.dispatch();
}

Einfache STM mit mbed-Events

Siehe auch STM mit Klassen

STM class with mbed-Events

#include "mbed.h"

DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);

class StopLight {
private:
    EventQueue _queue;
    int _pending;

    enum state { RED, GREEN, YELLOW };

    // The _step function is a traditional FSM which enqueues transitions onto the event queue
    void _step(state transition) {
        switch (transition) {
            case RED:
                set_red();
                _pending = _queue.call_in(1000, this, &StopLight::_step, GREEN);
                break;

            case GREEN:
                set_green();
                _pending = _queue.call_in(1000, this, &StopLight::_step, YELLOW);
                break;

            case YELLOW:
                set_yellow();
                _pending = _queue.call_in(500, this, &StopLight::_step, RED);
                break;
        }
    }
    void set_red(){
        led1 = 1; led2 = 0; led3 = 0;
    }
    void set_green(){
        led1 = 0; led2 = 0; led3 = 1;
    }
    void set_yellow() {
        led1 = 0; led2 = 1; led3 = 0;
    }
public:
    // The fire function can immediately change states in the FSM
    void fire() {
        _queue.cancel(_pending);
        _queue.call(this, &StopLight::_step, GREEN);
        _queue.dispatch();
    }
};

StopLight sl;

int main (void) {
    sl.fire();
    wait(osWaitForever);
}

All wikipages