# Eventgesteuerte Programmierung auf Arduino: Enterprise-Muster für Embedded-Systeme
Arduino wird traditionell mit linearen Sketches und der zyklischen loop() in Verbindung gebracht. Doch wenn Projekte komplexer werden, führt dieser Ansatz zu Spaghetti-Code. Wir stellen eine Methode vor, um ereignisgesteuerte Architektur aus Enterprise-Anwendungen in die Embedded-Entwicklung zu übertragen – mit der EVA Core-Bibliothek.
Das Problem der Struktur in Mikrocontrollern
In Desktop- und Server-Anwendungen ist der Code um Event-Handler organisiert: Klicks, Timer, Netzwerkpakete. Bei Arduino hingegen wird alles in eine einzige loop() gezwängt, wo Zustände manuell mit millis() und Flags verfolgt werden müssen. Das erzeugt zwei kritische Einschränkungen:
- Keine Isolation der Komponentenlogik – Timing-Informationen werden durch die gesamte Objekt-Hierarchie propagiert
- Fehlende klare Verträge zwischen Modulen bezüglich Timing-Anforderungen
Standard-Arduino-Bibliotheken beheben das nur stückweise: Es gibt Button-Handler, separate Timer, aber kein einheitliches Event-System. Versuche, eine „All-in-One“-Lösung zu bauen, stoßen auf enge Kopplung an eine spezifische loop()-Implementierung.
EVA Core: Drei Komponenten in einem einheitlichen System
Die EVA Core-Bibliothek (Event-Driven Architecture) löst das Problem durch drei interagierende Schichten:
- Callback-Mechanismus für Klassenmethoden — ermöglicht das Abonnieren von Events ohne globale Funktionen
- Tickable-Interface — gibt Objekten Zugriff auf die Zeit-Schleife über die tick()-Methode
- Survival Kit — eine Sammlung vorgefertigter Komponenten (Buttons, Timer), die auf den ersten beiden Prinzipien basieren
Schlüsselinnovation – Trennung von Timing-Logik und Geschäftsregeln. Der Sensor verwaltet seine eigenen Timings, während die Anwendung nur auf Events reagiert. Das ermöglicht:
- Isolation von Filter- und Debouncing-Algorithmen in Komponenten
- Garantie für das Update-Rhythmus bei verschachtelten Objekten
- Wegfall der Notwendigkeit, Aufrufe durch die Hierarchie zu propagieren
Schauen wir uns die Implementierung anhand eines Temperatursensors an:
#include <evaTickable.h>
#include <evaHandler.h>
using namespace eva;
class TempSensor : public Tickable {
private:
IHandler* listener = nullptr;
unsigned long lastRead = 0;
public:
void subscribe(IHandler* handler) {
listener = handler;
}
private:
void tick() override {
if (millis() - lastRead > 1000) { // Interval 1 sek
int value = readTemperature();
if (listener && value != lastValue) {
CallbackInfo info;
info.eventType = TEMP_UPDATE;
info.eventArg = value;
listener->invoke(this, info);
}
lastRead = millis();
}
}
};
class ClimateControl : public IHandler {
private:
TempSensor sensor;
void onTempUpdate(int value) {
if (value > 25) activateCooling();
}
public:
ClimateControl() {
sensor.subscribe(this);
}
void invoke(void* sender, CallbackInfo info) override {
if (info.eventType == TEMP_UPDATE) {
onTempUpdate(info.eventArg);
}
}
};
void setup() {
static ClimateControl system;
}
void loop() {
eva::tac(); // Edinaya point update
}
Vorteile des Ansatzes:
- TempSensor kapselt Polling- und Filterlogik
- ClimateControl kennt nur Events, keine Timing-Details
- Hinzufügen neuer Sensoren erfordert keine Änderungen an loop()
- Testen der Geschäftslogik isoliert vom Timing
LEGO-Architektur für Peripheriegeräte
Survival Kit implementiert das Kompositionsprinzip durch Compile-Time-Templates. Zum Beispiel wird Multi-Button-Handling an einem analogen Eingang als Kette von Transformern aufgebaut:
template <int PIN, int PIN_MODE, signed short... LEVELS>
using PinMultiButton = Button<QuantizeDecor<DebounceDecor<AnalogPinReader<PIN, PIN_MODE>>, LEVELS...>>;
Signalverarbeitungsarchitektur:
- Analogsignal →
- Debounce →
- Quantisierung nach Stufen →
- Button-Logik →
- Events
Dieses Pattern erlaubt es, benutzerdefinierte Handler aus vorgefertigten Komponenten zusammenzubauen. Beispiel – ein Keypad aus diskreten Buttons:
#include <evaSwitch.h>
class MyKeypad {
public:
MyKeypad() {
pinMode(2, INPUT_PULLUP);
// ... initialization ostalnykh pinov
}
signed short getValue() {
if (digitalRead(2) == LOW) return 'u';
// ... processing drugikh knopok
return 0;
}
};
// Withborka chains processing
Switch<DebounceDecor<MyKeypad>> keypad;
// Podpiska on events
keypad.setListener(new Handler<App>(this, &App::onKeyPress), ON_PRESS);
Jede Schicht übernimmt ihre eigene Verantwortung. Bei Bedarf können Sie Debounce durch einen ausgeklügelteren Algorithmus ersetzen oder Press-Duration-Filtering hinzufügen – ohne die Kernanwendungslogik zu verändern.
Einschränkungen und Anwendungsfälle
EVA Core ist als methodisches Tool positioniert, nicht als universelle Lösung. Verwenden Sie es, wenn:
- Projekte mit komplexen Objekthierarchien gebaut werden
- Klare Trennung von Timing und Geschäftslogik benötigt wird
- Prinzipien der ereignisgesteuerten Architektur auf einfachen Geräten vermittelt werden
Kritische Einschränkungen:
- Erhöhter RAM-Verbrauch durch virtuelle Tabellen
- Komplexität für Einsteiger (erfordert Kenntnisse von Templates und Method-Pointern)
- Overkill für einfache Projekte (z. B. Steuerung einer einzelnen LED)
Wichtig zu verstehen: Das Ziel der Bibliothek ist die Demonstration von Architekturpatterns, nicht die Ressourcenoptimierung. Für Produktionsprojekte mit engen Restriktionen müssen Sie den Overhead analysieren.
Wichtige Erkenntnisse
- Event-Modell isoliert Timing-Abhängigkeiten in Komponenten
- Tickable-Interface gewährleistet regelmäßige Updates für verschachtelte Objekte
- Template-Komposition bietet Flexibilität ohne Laufzeit-Overhead
- Keine Propagierung von Aufrufen vereinfacht Refactoring und Testing
- Arduino wird zum Spielplatz für Enterprise-Muster
Dieser Ansatz beweist, dass selbst auf ressourcenbeschränkten Mikrocontrollern professionelle Designmethoden angewendet werden können. Der größte Vorteil – der Code wird vorhersehbar und skalierbar, statt nur „irgendwie zu funktionieren“.
— Editorial Team
Noch keine Kommentare.