TURAG-Feldbus
|
TURAG-Feldbus-Basisimplementierung für AVR-Controller ohne Betriebssystem.
TURAG-Feldbus-Basisimplementierung für AVR-Controller ohne Betriebssystem.
Die Code-Struktur ist auf Interrupt-Betrieb optimiert.
Die Basis-Implementierung sitzt zwischen Hardware und Firmware des Slave-Gerätes. Die Kommunikation mit der Hardware geschieht über ein Hardware-Interface, während für die Firmware des Gerätes mit dem Protokoll-Interface eine abstrakte Schnittstelle bereitsteht, um mit geringem Aufwand über den TURAG-Feldbus kommunizieren zu können.
Beide Interfaces sind jeweils teilweise implementiert - der andere Teil muss vom Entwickler des Slave-Gerätes bereitgestellt werden, so wie dies im Strukturschema dargestellt ist.
Anhand des Strukturschemas lässt sich die Arbeitsweise der Basis-Implementierung auch gut beschreiben. Der Entwickler des Gerätes ruft zu einem frühen Zeitpunkt turag_feldbus_device_init() auf. Das initialisiert interne Strukturen und ruft seinerseits turag_feldbus_hardware_init() auf, welches die UART-Peripherie des Controllers initialisieren sollte. Danach müssen (falls das der Controller verlangt) die Interrupts global aktiviert werden. Ab diesem Zeitpunkt werden eintreffende Daten im Hardware-Interfaceteil der Basis-Implementierung verarbeitet. Da die Paket-Erkennung im Kontext von Interrupts ausgeführt wird, wird an dieser Stelle nur ein Flag gesetzt, sobald ein gültiges Paket erkannt wurde. Die eigentliche Auswertung eines angekommenen Paketes wird erst gestartet, wenn turag_feldbus_do_processing() aufgerufen wird. Diese Funktion deaktiviert temporär den Empfang neuer Daten, kopiert das empfangene Paket in einen Zwischenpuffer und aktiviert die Kommunikation wieder. Dann wird, je nach Pakettyp, turag_feldbus_device_process_package() oder turag_feldbus_device_process_broadcast() aufgerufen und das Paket wird so verarbeitet, wie das die Firmware des Slave-Gerätes vorsieht.
Diese strikte Trennung der Kommunikation zwischen Empfang der Daten in Interrupts und Verarbeitung im Kontext von main() hat zwei Vorteile:
Wichtig: damit sich das Slave-Gerät korrekt verhält, muss turag_feldbus_do_processing() regelmäßig von der Hauptschleife aus aufgerufen werden.
Ein weiteres erwähnenswertes Feature ist der Uptime-Counter. Ein Timer des Slaves wird so konfiguriert, dass er mit einer wählbaren Frequenz turag_feldbus_device_increase_uptime_counter() aufruft. Der Master kann die Laufzeit des Slaves auslesen und so zum Beispiel Probleme in der Stromversorgung feststellen, was sich in Neustarts des Slaves und einem Zurücksetzen des Uptime-Counters äußern würde.
Wenn eine LED vorhanden ist, kann automatisch die Funktion turag_feldbus_device_toggle_led() aufgerufen werden. Das Blinkmuster ist dabei (soweit möglich) unabhängig von der Timer-Frequenz und fungiert außerdem als Indikator für die Stabilität der Kommunikation: das Blinken der LED wird ausgesetzt, solange im Gerät ein unverarbeitetes Paket vorliegt (zum Beispiel weil turag_feldbus_do_processing()) nicht oder nur zu selten aufgerufen wird. Zeigt die LED also ein unregelmäßiges Blinkmuster an oder blinkt sie sogar gar nicht, gibt es Kommunikationsprobleme oder der Gerät ist abgestürzt.
Die Implementierungen der Anwendungsprotokolle führen im Allgemeinen zwei Änderungen ein:
An der Notwendigkeit, turag_feldbus_do_processing() zyklisch aufzurufen, ändert sich normalerweise nichts.
Das Makefile kann üblicherweise aus alten Projekten übernommen und entsprechend angepasst werden.
Wichtig: zumindest die Datei, die die ISRs enthält (also feldbus_hardware_driver.c) muss mit -O3 compiliert werden! Nur dann beginnt der Compiler automatisch Aufrufe von kleinen Funktionen zu inlinen, was im Falle der ISRs wichtig ist. Ansonsten leidet die Performance stark.
Wichtig: Die von der Basis-Implementerung bereitgestellten Hardware-Funktionen (wie z.B. turag_feldbus_device_receive_timeout_occured()) gehen davon aus, dass ihre Ausführung nicht unterbrochen werden kann. Die Interrupts, die diese Funktionen aufrefen, sollten also nicht von anderen Interrupts unterbrochen werden können.
Im folgenden wird ein Beispiel betrachtet, wie die Implementierung des Hardware-Interfaces für einen ATmega88 aussehen kann.
Neben den AVR-Headern wird der entsprechende Protokoll-Header eingebunden.
Als nächstes folgen einige Definitionen. Insbesondere die letzten beiden werden üblicherweise über das Makefile global im Projekt bereitgestellt.
Danach ein paar hilfreiche Makros:
Einen großen Teil der Implementierung nimmt die Bereitstellung der notwendigen Hardware-Interface-Funktionen ein, darunter die Funktion turag_feldbus_hardware_init() zum Initialisieren der Peripherie des Controllers.
Zu guter letzt folgen die ISR-Handler, die die Ausführung lediglich an die entsprechenden Funktionen der Basis-Implementierung weiterreichen:
Die folgenden Graphen zeigen an, welche Header sinnvollerweise von welchen Dateien includiert werden sollten. Blau umrandete Dateien sind Teil von TinA, schwarz umrandete solche, die der Entwickler des Slave-Gerätes bereitstellt.
Hervorzuheben sind folgende Punkte:
Ein neues Anwendungsprotokoll ist normalerweise dann von Nöten, wenn eine neue Gerätegruppe entwickelt werden soll. Die Entwicklung eines neuen Anwendungsprotokolls umfasst mehrere Schritte:
Wenn möglich, sollte _Static_assert benutzt werden, um sicherzustellen dass mit TURAG_FELDBUS_DEVICE_CONFIG_BUFFER_SIZE ein ausreichend großer Transmit-Buffer konfiguriert ist, da es ansonsten zu Pufferüberläufen kommen kann.
Siehe avr_feldbus_config.h.
avr_feldbus_config.h ist eine Beispielkonfigurationsdatei, die nicht direkt includiert werden darf. Eine Kopie dieser sollte unter dem Namen "feldbus_config.h" im Projektverzeichnis abgelegt werden.