Dieser Blog ist umgezogen: http://scholtyssek.org/blog/2013/10/21/yakindu-statechart-tools-arduino-integration/
Die Yakindu Statechart Tools sind dafür prädestiniert, Systemverhalten als Statemachine zu beschreiben, um anschließend Quellcode aus dieser Beschreibung zu generieren. Der Code kann in Java, C oder C++ generiert werden. Wir wollten den Codegenerator dazu einsetzen, Quellcode für eingebettete Systeme zu generieren, deshalb haben wir und für den Arduino Uno als Plattform entschieden. Dafür haben wir das TrafficLight Beispiel modifiziert und etwas simplen glue code (also Verbindungscode zwischen der Hardware und dem Generat) geschrieben, um die timer und die I/Os des Controllers zu mappen. Außerdem haben wir eine Ampel mit LEDs auf einer kleinen Platine entworfen, welche die Signale des Arduinos repräsentiert. Im Anhang befindet sich ein eagle-Schaltplan, sodass diese Platine leicht nachgebaut werden kann.
Das Beispiel wurde direkt für den AVR ATMEGA328P-PU Prozessor entwickelt, da dieser sich auf dem Arduino Uno befinden. Es ist ebenfalls möglich, die Programmierbibliothek für den Arduino zu verwenden, um die Ampelsteuerung zu integrieren, allerdings haben wir uns bewusst dagegen entschieden, da wir zeigen wollen, dass wir eine ganze Prozessorfamilie unterstützen können, weil die eigentliche Implementierung losgelöst von dem Generat ist. Aus diesem Grund benutzt die Implementierung die AVR-Bibliothek direkt (avr-libc).
Um das Beispiel zu ergründen, sind ein paar Schritte notwendig, die im Folgenden beschrieben werden:
Die Yakindu Statechart Tools sind dafür prädestiniert, Systemverhalten als Statemachine zu beschreiben, um anschließend Quellcode aus dieser Beschreibung zu generieren. Der Code kann in Java, C oder C++ generiert werden. Wir wollten den Codegenerator dazu einsetzen, Quellcode für eingebettete Systeme zu generieren, deshalb haben wir und für den Arduino Uno als Plattform entschieden. Dafür haben wir das TrafficLight Beispiel modifiziert und etwas simplen glue code (also Verbindungscode zwischen der Hardware und dem Generat) geschrieben, um die timer und die I/Os des Controllers zu mappen. Außerdem haben wir eine Ampel mit LEDs auf einer kleinen Platine entworfen, welche die Signale des Arduinos repräsentiert. Im Anhang befindet sich ein eagle-Schaltplan, sodass diese Platine leicht nachgebaut werden kann.
Das Beispiel wurde direkt für den AVR ATMEGA328P-PU Prozessor entwickelt, da dieser sich auf dem Arduino Uno befinden. Es ist ebenfalls möglich, die Programmierbibliothek für den Arduino zu verwenden, um die Ampelsteuerung zu integrieren, allerdings haben wir uns bewusst dagegen entschieden, da wir zeigen wollen, dass wir eine ganze Prozessorfamilie unterstützen können, weil die eigentliche Implementierung losgelöst von dem Generat ist. Aus diesem Grund benutzt die Implementierung die AVR-Bibliothek direkt (avr-libc).
Um das Beispiel zu ergründen, sind ein paar Schritte notwendig, die im Folgenden beschrieben werden:
1. Entwicklungsumgebung
- Zu Beginn installiert man die Eclipse IDE mit dem integrierten Yakindu SCT. Alternativ können die Statechart Tools auch über den Eclipse Update Manager installiert werden. Beides findet man auf der Seite der SCT (http://statecharts.org/download.html)
- Anschließend installiert man das AVR Plugin für Eclipse von der folgenden Seite mit Hilfe des Update Managers: http://avr-eclipse.sourceforge.net/wiki/index.php/The_AVR_Eclipse_Plugin
- Unter (debian basierten) Linuxdistributionen installiert man nun die AVR Umgebung mit dem folgenden Befehl:sudo apt-get install avrdude binutils-avr gcc-avr avr-libc gdb-avr.
- Als nächstes lädt man die Arduino Bibliothek von der Seite http://arduino.cc/en/Main/Software und um die libArduinoCore.a Bibliothek zu erstellen. Unter http://playground.arduino.cc/Code/Eclipse#Arduino_core_library ist die Vorgehensweise genau beschrieben, sodass an dieser Stelle auf diese Beschreibung verwiesen wird. Sobald man die libArduinoCore.a erstellt hat, kopiert man diese in einen "lib" Ordner (ggf. muss dieser erstellt werden) im Projektverzeichnis und setzt den include path für das Projekt im Eclipse wie folgt:
<install_dir>/arduino-1.0.5/hardware/arduino/variants/mega<install_dir>/arduino-1.0.5/hardware/arduino/cores/arduino
- Danach sollte man das SVN Plugin mit dem SVN Kit via Eclipse Marketplace installieren, um das ArduinoTrafficLight-Projekt direkt aus dem Repository auszuchecken. Das Projekt ist unter http://code.google.com/a/eclipselabs.org/p/yakindu/source/checkout im Unterverzeichnis examples zu finden. Nach dem Import sollten noch einmal die include paths überprüft werden und ggf. an die eigene Umgebung angepasst werden.
- Damit das Beispiel ausprobiert werden kann, sollte die Platine gelötet und an den Arduino angeschlossen werden. Der Schaltplan befindet sich im Anhang.
- Nun kann der C++ Code mit der TrafficLight.sgen generiert werden. Dazu klickt man einfach mit der rechten Maustaste auf diese Datei und wählt den Menüpunkt "generate artifacts" aus. Ein kleiner Hinweis dazu:Aktuell generieren die SCT Datentypen, die nicht für die Hardware angepasst sind, sodass hier ein manueller Eingriff erfolgen muss. In der Datei sind die Datentypen definiert. Der Datentyp sc_integer muss in dieser Datei src-gen/sc_types.h von int32_t auf uint32_t verändert werden. Das war's schon. Jetzt kann das Projekt kompiliert werden, dazu klickt man einfach auf das Hammersymbol im Eclipse.
- Nachdem der Code nun kompiliert wurde, kann dieser durch einen Klick auf das AVR-Symbol, auf den Prozessor geflasht werden.
Jetzt führt der AVR-Prozessor den generierten Code aus. Wenn die Hardware korrekt gelötet und an den Arduino angeschlossen ist, kann man das beschriebene Verhalten aus dem Statechart direkt testen. Das Modell kann nach Belieben angepasst werden, um das Verhalten direkt auf der Hardware auszuprobieren.
Ein paar erklärende Worte zum Beispielcode
Es gibt insgesamt drei Dinge, die als gluecode implementiert werden müssen, damit der generierte Code mit dem Arduino arbeitet:
2. CycleRunner
Der CycleRunnter prüft periodisch, ob sich der Zustand des Modells verändert hat. Für den periodischen Aufruf wird ein (Hardware-) Timer verwendet (timer0), welcher diese Überprüfung durch einen Interrupt antriggert. Die Prüfung auf Zustandsänderungen im Statechart werden durch Aufruf der Funktion trafficlight_runCycle(handle) ausgeführt. Die Implementierung in dem Beispiel sieht wie folgt aus:
/** * The cycleRunner uses timer0 for his cycle-time-calculation (every second) */ void CycleRunner::start(void){ TCCR0B |= (1<<CS02) | (1<<CS00); // prescaler 1024 TCNT0 = 0; // Startvalue ->1s to overflow TIMSK0 |= (1<<TOIE0); } void CycleRunner::runCycle(void){ trafficlight_runCycle(handle); }
/*
* the cycleRunner is triggered by this timerinterrupt
*/
ISR(TIMER0_OVF_vect) {
runCycleFlag = 1;
}
3. Timer und TimeEvent
Die Klasse Timer repräsentiert die Timer des Prozessors und ist dafür zuständig, diese zu initialisieren und TimeEvents auszulösen. In dem TrafficLight Beispiel verwenden wir timer1 um diesen Trigger zu implementieren. Ein TimeEvent wird aufgelöst, wenn beispielsweise dann ausgelöst, wenn die Zeit für einen Zustandsübergang erreicht ist. In manchen Situationen laufen mehrere Timer parallel, deshalb ist es in diesen Fällen notwendig, die TimeEvents in einem Array zu speichern (vgl. das Event-Array in der Datei Main.cpp). Der Timer prüft periodisch (alle 10ms), ob eine Zeitgrenze erreicht wurde und löst bei Bedarf ein Event aus. Das folgende Beispiel verdeutlicht das beschriebene Verhalten:
// Timer ISR that triggers checking for expired time events ISR(TIMER1_OVF_vect) { TCNT1 = 64911; // startvalue for 10ms if ((counter++) >= timer->getTimerOverflowCount()) { counter = 0; timerRaiseEventFlag = 1; } } void Timer::raiseTimeEvent() { for(int i=0;i<TIMER_COUNT;i++){ if(events[i] == NULL) continue; if((events[i]->getTimeMs()/10) <= events[i]->getTimerOverflowCount()){ trafficlight_raiseTimeEvent(sc_handle, events[i]->getEventId()); events[i]->setTimerOverflowCount(0); } events[i]->setTimerOverflowCount(events[i]->getTimerOverflowCount() + 1); } }
3. IO port mapping
An dieser Stelle müssen die IOs der Hardware durch den Programmierer mit dem Statechart verbunden werden. In dem Beispiel werden die Pins des Arduinos als Ausgänge konfiguriert, sowie eine Funktion für das Setzen eines entsprechenden Signals angeboten. Ein beispielhaftes Mapping für die rote LED sieht wie folgt aus:
const uint8_t ledPinTrafficRed = DDB5; // the number of the red LED pin DDRB |= (1 << ledPinTrafficRed); // set out direction for this port
Das Signal der LED kann nun mit Hilfe der Funktion aus dem Statemachine-Interface gesetzt werden:
setLight(PORTB, ledPinTrafficRed,
trafficlightIfaceTrafficLight_get_red(&handle));
4. Anhang
Das Video zeigt die Ampelsteuerung mit dem definierten Verhalten aus dem Modell. Das Modell, welches das hier verwendeten Verhalten beschreibt, wird in der anschließenden Grafik dargestellt.
Schaltplan für die Ampelsteuerung |
Keine Kommentare:
Kommentar veröffentlichen