Yakindu Statechart Tools Arduino Integration

Yakindu Statechart Tools Arduino Integration
  • Deutsch
  • English

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)
  • 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:
/arduino-1.0.5/hardware/arduino/variants/mega
/arduino-1.0.5/hardware/arduino/cores/arduino
  • Danach sollte man das ArduinoTrafficLight-Projekt direkt aus dem Git-Repository auszuchecken. Das Projekt ist unter https://github.com/Yakindu/statecharts/tree/master/ im Unterverzeichnis archive 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.

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.

2. Ein paar Worte zum Beispielcode

Es gibt insgesamt drei Dinge, die als gluecode implementiert werden müssen, damit der generierte Code mit dem Arduino arbeitet.

2.1 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;
}

2.2 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;igetTimeMs()/10) <= events[i]->getTimerOverflowCount()){
   trafficlight_raiseTimeEvent(sc_handle, events[i]->getEventId());
   events[i]->setTimerOverflowCount(0);
  }

  events[i]->setTimerOverflowCount(events[i]->getTimerOverflowCount() + 1);
 }
}

2.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:
Das Signal der LED kann nun mit Hilfe der Funktion aus dem Statemachine-Interface gesetzt werden: 
const uint8_t ledPinTrafficRed = DDB5; // the number of the red LED pin
DDRB |= (1 << ledPinTrafficRed);       // set out direction for this port
setLight(PORTB, ledPinTrafficRed,
trafficlightIfaceTrafficLight_get_red(&handle));

3. 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.