CAN Bibiliothek für AT90CAN Prozessoren
Autor: Jürgen Harms 16:30, 21. Jul. 2008
Einleitung
Dieser Artikel beschreibt eine einfache C-Programmbibliothek für die Programmierung des Zugriffs auf einen CAN Bus mittels eines AVR AT90CAN Prozessors (AT90CAN128, AT90CAN64 oder AT90CAN32). Zusätzlich beschreibt er eine Demo-Anwendung zum Testen dieser Bibliothek; diese Demo-Anwendung kann aber auch als Anregung für die Gestaltung eines einfachen Echtzeitrahmens für kleine Anwendungen dienen.
Anwendungsgebiet, Eigenschaften
Die Bibliothek wurde als Teil eines in C geschriebenen Hausbus Systems entwickelt - daraus ergeben sich die Spezifikationen:
- Die Bibliothek macht die wichtigsten Fähigkeiten der CAN Kontrolleinheit auf der Ebene des Aufrufs von C-Unterprogrammen verfügbar.
- Die Anwendung muss interruptgetrieben sein.
- Die Bibliothek verwendet einen einfachen Prioritätsmechanismus bei der Behandlung von Nachrichten: falls mehrere Nachrichten gleichzeitig auf das Senden oder Empfangen warten, bestimmt dieser Mechanismus welche Nachricht zuerst von der Kontrolleinheit des AT90CAN bedient wird.
- Sie erlaubt sparsamen Umgang mit dynamischem Speicherplatz.
- Bei der Implementierung wurde auf Einfachheit und leichte Lesbarkeit geachtet.
- In der jetzigen Version sind die Taktraten fest kodiert und setzen einen 16 MHz Quarz voraus (Bus Takt = 100Kbps, interner Takt der CAN Kontrolleinheit = 10Kbps) - diese Parameter sind aber leicht änderbar.
- Mit einer Ausnahme bietet die Bibliothek derzeit keine Vorkehrungen zur Behandlung von Fehlern: die CAN Kontrolleinheit des AT90CAN besorgt weitgehend - und unter normalen Bedingungen ausreichend - die Behandlung von Übermittlungsfehlern. Die Ausnahme betrifft das Erkennen von gesendeten Nachrichten, die während längerer Zeit nicht vom Bus quittiert werden und damit das entsprechende MOb blockieren (der Begriff MOb ist im folgenden Abschnitt definiert).
Begriffe
Ein paar Begriffe werden im folgenden häufig verwendet und haben, im Kontext von CAN, eine sehr spezifische Bedeutung:
- Der Begriff MOb - (Message Object) - bezeichnet die Infrastruktur in der Kontrolleinheit, welche zur Darstellung und Behandlung einer Nachricht vorgesehen ist.
- Eine Adresse (Identifier Tag) auf einem CAN Bus bezeichnet eine - für normal Gruppe von - Adressaten. Knoten am Bus beobachten das Adressfeld von Nachrichten und vergleichen es mit Schablonen, die bei der Initialisierung des Knotens definiert wurden. CAN unterscheidet zwischen normalen und erweiterten Adressen (dargestellt in 11, bzw. 29 Bits).
- Ein Filter enthält solch eine Schablone; zusätzlich zu den Bits zur Adressierung von Knoten bestimmt das Filter auch Bits, welche bestimmte Eigenschaften beschreiben. Ein Filter wird getriggert - die entsprechende Nachricht wird vom Knoten empfangen - wenn alle in der Schablone festgelegten Bedingungen erfüllt sind.
Implementierung, Demo-Anwendung
Die Bibliothek wurde in einer Unix Programmierumgebung (AVR-Gcc, Make, AVR-Libc) entwickelt. Diese Umgebung gibt es auch in einer ausgezeichneten und gut gewarteten Variante für Windows. Die Bibliothek läuft bei mir ohne bekannte Probleme, allerdings wurde sie bisher (zur Zeit der Veröffentlichung dieses Artikels) nur im Rahmen der Anwendung eingesetzt für welche die Bibliothek geschrieben wurde.
Zur Illustration ist die Bibliothek durch eine Demo-Anwendung und eine entsprechenden Makefile Datei ergänzt.
Die Anzeige von Test- und Fehlermeldungen beruht auf der Ausgabe an stdout und verwendet die Programme in der Datei Uart.c (Teil der Demoanwendungen der AVR-Libc) - mit zwei kleinen Anpassungen: (1) es wird das Bestehen des zweiten seriellen Kanals im AT90CAN berücksichtigt, und (2) die Warteschleife im Unterprogramm UartPutchar ist durch den Aufruf der "Idle-Loop" der Demo-Anwendung ersetzt. Damit ist die Verwendung auch bei aktivem Interruptsystem möglich - allerdings muss man Sorge tragen, dass zu jeder Zeit höchstens eine Ausgabe an stdout erfolgt.
Elemente der CAN Bibliothek
Unterprogramme und globale Variable
Die Bibliothek verwendet eine globale Variable - can_status, welche in der Datei Can.c definiert ist. Sie dient vor allem der Synchronisation zwischen Interrupt Behandlung und Anwendungsprogramm.
Die wesentlichen Aufgaben werden von vier Unterprogrammen besorgt:
- CanInit initialisiert die Kontrolleinheit des AVR Prozessors und die Variablen der Programmbibliothek,
- CANIT_vect bedient CAN-relevante Interrupts,
- CanGetFrame behandelt den Empfang von Nachrichten,
- CanSendFrame besorgt das Senden von Nachrichten.
Weitere Unterprogramme sind:
- CanCheckWait hilft zu erkennen, wann das Senden einer Nachricht exzessiv lange dauert (z. B. weil kein Busteilnehmer antwortet),
- CanKillFrame erlaubt solche Nachrichten zu annullieren,
- CanNormalID und CanExtendedID dienen der Behandlung der Adressen und von Filtern; diese beiden Programme sind nur innerhalb von CanInit aufrufbar.
Alle diese Unterprogramme sind im Modul Can.c implementiert. Modul Can.h definiert die entsprechenden Datenstrukturen und Konstanten.
Datenstrukturen
Die folgenden Strukturen sind für die Programmierung der Anwendung von besonderer Bedeutung:
Die Struktur can_header dient zur Beschreibung einer zu sendenden oder gerade empfangenen Nachricht:
- mob_header = die Adresse der Nachricht - als 32-Bit binärer Wert dargestellt,
- mob_stamp = die Zeit (10 msec Einheiten) bei Eintreffen des die Nachrichten betreffenden Interrupts (Nachricht vollkommen empfangen, resp. Senden der Nachricht abgeschlossen),
- mob_length = die Zahl Bytes im Datenbereich der Nachricht,
- mob_flags = eine Liste von Bits zur Beschreibung der Eigenschaften des MOb:
- CAN_EXTD = wenn dieses Bit gesetzt ist, werden erweiterte, andernfalls normale Adressen verwendet.
- RTRTAG, RB0TAG, RB1TAG ... siehe AT90CAN Datenblatt,
- mob_page= der Index (0 bis 14) des die Nachricht enthaltenden MOb.
- Die Speicherung der Daten erfolgt unabhängig von dieser Struktur: die Speicherzone für die Daten wird beim Aufruf von CanSendFrame und CanGetFrame als eigenes Argument angegeben; dadurch können für die Beschreibung des MOb und der Daten Variablen mit unterschiedlicher Lebensdauer verwendet werden.
Die Struktur can_descr beschreibt den Zustand der CAN Kontrolleinheit:
- cs_stamp = eine Tabelle (15 Einträge - einer pro MOb) mit dem Zeitpunkt, bei dem das Senden eines MOb ausgelöst wurde (= Wert des oberen Bytes von CANTIMH),
- cs_rcvOK = eine 16-Bit Liste mit Bits zur Markierung von MObs, für welche die Anwendung den Empfang einer neuen Nachricht behandeln muss (0 wenn keine),
- cs_xmtOK = eine 16-Bit Liste mit Bits zur Markierung von MObs, für welche die Anwendung das Ende des Sendens behandeln muss (0 wenn keine),
- cs_mask = eine 16-Bit Liste mit Bits zur Markierung von MObs, die für das Senden reserviert sind,
- cs_xmit = der MOb Index des ersten MOb welches für das Senden reserviert ist (redundant zu cs_mask),
- cs_error = der Fehlerkode beim letzten Interrupt.
Die Struktur can_filter dient zur Definition der Filterdaten bei der Initialisierung eines MOb. Dies erlaubt bei der Initialisierung der Kontrolleinheit die für den Empfang bestimmten MObs auf einfache Weise zu definieren; die vier Elemente der Struktur sind:
- cf_idt = bestimmt die Adressen, die dem MOb zugeordnet sind, dargestellt als 32-Bit binärer Wert,
- cf_mask = die Liste der bei dieser Definition signifikanten Bits,
- cf_iflags = die Liste von Werten der Bits zur Beschreibung der Eigenschaften des MOb, die beim Filtern beachtet werden,
- cf_mflags = die Liste der bei dieser Definition signifikanten Bits.
- Die mit cf_iflags und cf_mflags zu definierenden Bits sind:
- - CAN_EXTD = erweiterte oder normale Adressen verwenden, je nachdem ob dieses Bit gesetzt ist oder nicht,
- - RTRTAG, RB0TAG, RB1TAG ... siehe AT90CAN Datenblatt,
- - RTRMSK, RB0MSK, RB1MSK ... siehe AT90CAN Datenblatt.
- Wenn das Unterprogramm CanInit ein Filter einrichtet, werden die binären Werte aus cf_idt und cf_mask in Felder zerlegt und in den Registern CANIDT1 ... CANDIT4, bzw. CANIDM1 ... CANDIM4 gespeichert. Die Bits aus cf_iflags und cf_mflags werden dabei in die dem Namen der Symbole entsprechenden Positionen von CANIDT4, bzw. CANIDM4 gesetzt.
- Das Bit CAN_EXTD spielt eine etwas eigene Rolle:
- - sein Wert wird zwar nur in cf_mflags angegeben, es steuert aber die Behandlung der meisten von CanInit behandelten Werte:
- - es bestimmt das Format, nachdem die Werte aus cf_idt und cf_idm in Felder zerlegt werden;
- - ausserdem bestimmt es, ob das IDE Bit im Register CANCDMOB gesetzt wird oder nicht.
Konstanten und Parameter
Die Bibliothek verwendet die Symbole für die Bezeichnung von Registern und Bits der Kontrolleinheit wie sie im ATMEL Datenblatt definiert sind - sie werden automatisch von AVR-Libc eingeführt; Can.h definiert zwei zusätzliche Symbole und deren Werte:
- CAN_EXTD bestimmt, ob ein Filter, resp. eine zu sendende Nachricht den normalen Adressbereich (V2.0 part A) oder den erweiterten Adressbereich (V2.0 part B) verwendet,
- MG_PRIORITY bestimmt ein Bit (binäre Maske) zur Kontrolle der Priorität beim Senden: Nachrichten, in deren Adresse dieses Bit gesetzt ist, haben geringere Priorität als solche, bei denen das Bit den Wert 0 hat (der Wert von MG_PRIORITY ist ein binärer Wert, nicht der Wert der dann in den Registern der Kontrolleinheit abgesetzt wird - siehe die Umwandlung in den Unterprogramme CanNormaID und CanExtendedID).
- MG_PRIORITY steuert sowohl die Priorität bei der Wahl des zu sendenden MOb - wenn mehr als eines in der Kontrolleinheit wartet (Auswahl durch das Programm CanSendFrame), als auch die Priorität beim Behandeln von Kollisionen am Bus (CAN arbitriert hierbei nach Massgabe der numerischen Werte der Adressfelder).
Die in die Register CANBT1, CANBT2, CANBT3 programmierten Werte müssen der Taktrate am Bus entsprechen (100Kbps im Original der Bibliothek); der Inhalt des Registers CANTCON bestimmt den internen Takt der Kontrolleinheit - er wird nur für den Mechanismus der Erkennung von hängengebliebenen Nachrichten beim Senden verwendet.
Falls der Wert von CANTCON oder die Echtzeit-Uhr anders definiert werden als im Original der Bibliothek, muss auch die Schwelle zum Erkennen von beim Senden zu lange wartenden MObs entsprechend geändert werden - siehe Kommentar im Unterprogramm CanCheckWait.
Unterprogramme der CAN Bibliothek
Die einzelnen Unterprogramme sind reichlich kommentiert, der folgende Text beschränkt sich daher auf ergänzende Kommentare.
CanInit
Der erste Abschnitt (Initialisierung des MObs) definiert die Filter für den Empfang von Nachrichten. Die Angaben hierfür werden als Argumente beim Aufruf von CanInit festgelegt: das erste Argument ist eine Tabelle mit Definitionen für die Filter (im Flash-Speicher programmiert!) und das zweite die Zahl der zu definierenden Filter (wenn diese Zahl 0 ist, definiert CanInit automatisch ein einziges Filter, das für den Empfang aller Nachrichten dient). Der Index in der Tabelle entspricht dem Index des zu definierenden MOb in der Kontrolleinheit. MObs mit Indizes jenseits der so festgelegten Werte werden für das Senden von Nachrichten verwendet.
Bei der Definition von Filtern ist zu beachten, dass immer ein letztes Filter zum Empfang aller bis dahin nicht erkannten Nachrichten vorgesehen werden sollte (siehe Datenblatt des AT90CAN, Abschnitt 33.2 - "Errata", Paragraph 6).
ISR CANIT_vect
Dies ist das Programm zum Abarbeiten der von der CAN Kontrolleinheit erzeugten Interrupts. Folgende Interrupts werden behandelt:
- Interrupt nach Vollendung des Sendens einer Nachricht:
- Das Interrupt wird gelöscht, das MOb wird für neue Verwendung freigegeben. Das Bit mit dem Index des betroffenen MOb wird in der Kontrollvariablen can_status.cs_xmtOK gesetzt - damit wird das Ereignis der "Idle-Loop" des Anwendungsprogrammes gemeldet.
- Interrupt wenn einer empfangene Nachricht verfügbar ist:
- Das Interrupt wird gelöscht, das MOb mit der empfangenen Nachricht wird bis zur Behandlung durch die Anwendung (Abholen der Empfangenen Daten) blockiert. Das Bit mit dem Index des betroffenen MOb wird in der Kontrollvariablen can_status.cs_rcvOK gesetzt - damit wird das Ereignis der "Idle-Loop" des Anwendungsprogrammes gemeldet.
- Interrupt zufolge eines Fehlers:
- Derzeit im Programm noch nicht unterstützt, nur das Interrupt wird gelöscht.
CanSendFrame
CanSendFrame löst das Senden einer Nachricht durch die Kontrolleinheit aus (das Ende des Sendevorganges wird aber nicht abgewartet). Falls - z. B. wenn Nachrichten hintereinander und in einer garantierten Reihenfolge gesendet werden sollen - das Ende abgewartet werden muss, ist dies Aufgabe der Anwendung (dazu dient z. B. das Unterprogramms XmitWait, siehe Demo-Anwendung).
Wichtige Abschnitte des Programms:
- Wahl eines verfügbaren MOb
- Die Kontrolleinheit des AT90CAN entscheidet bei Vorliegen mehrerer zu sendender Nachrichten welche Nachricht mit Priorität zu behandeln ist: die Nachricht in dem MOb mit dem jeweils niedrigsten Index wird als erste gesendet.
- Der Mechanismus zur Wahl des zu verwendenden MOb baut auf dieses Verhalten: das Abtasten zum Finden des ersten verfügbaren MObs beginnt bei dem niedrigsten MOb wenn der Wert des Bits in der Position MG_PRIORITY 0 ist, sonst bei dem MOb mit dem höchsten Index (Index 14).
- Setzen der IDT Register
- Der binäre Wert in p_header.mob_ID wird in die entsprechenden Werte für die IDT Register umgesetzt, wobei die unterschiedlichen Formate für die beiden Varianten der Adressierung berücksichtigt werden; der Vorgang hierbei ist analog zu dem, was in der Beschreibung der Datenstruktur can_filter erklärt ist.
- Auslösen des Sendevorgangs
- Die Daten werden vom Anwendungsbereich in die Register der Kontrolleinheit kopiert - danach sind die als Argumente für den Aufruf von CanSendFrame verwendeten Speicherzonen wieder verfügbar. Der Zeitpunkt des Sendens wird in can_status.cs_stamp (indiziert mit dem Index des MOb) gespeichert - dies ist der zu diesem Zeitpunk in CANTIMH befindliche Wert; er dient als Grundlage für das Programm CanCheckWait um MObs zu finden, die "hängen" geblieben sind (der häufigste Grund dafür ist das Fehlen eines Partners am Bus, der Nachrichten mit der entsprechenden Adresse quittiert).
CanGetFrame
CanGetFrame geht davon aus, dass die Ankunft einer Nachricht schon bei der Behandlung der Interrupts erkannt und behandelt wurde. CanGetFrame wird daraufhin aufgerufen um die Daten abzuholen (bei der Behandlung des Interrupts wurde das MOb gegen eine versehentliche neue Verwendung geschützt).
Das Programm kopiert danach (a) die wesentlichen Daten aus dem Nachrichten Kopf und (b) die Nachricht selber in die von der Anwendung bestimmten Speicherzonen. Danach wird das MOb wieder freigegeben.
CanCheckWait und CanKillFrame
CanCheckWait ist ein Unterprogramm, das von der Anwendung periodisch aufgerufen werden sollte, wenn nichts anderes zu tun ist. Bei jedem Aufruf wird zyklisch ein MOb untersucht und darauf geprüft, ob und wie lange es mit dem Senden einer Nachricht beschäftigt ist (Vergleich des Werts in can_status.cs_stamp und mit dem jeweiligen Wert von CANTIMH). Im Kode von CanCheckWait ist eine Schwelle definiert, deren Überschreiten als Fehler behandelt wird.
Die statische Variable next_mob in CanCheckWait bestimmt, welches MOb als nächstes zu Prüfen ist - sie wird nach jedem Aufruf neu definiert.
Das Programm CanKillFrame dient dazu, den Sendevorgang eines dermassen gefundenen MOb abzubrechen.
Die globale Variable killed_mobs (sie gibt es erst ab Version V1.0.1) informiert über MObs, deren Senden mithilfe von CanKillFrame abgebrochen wurde. Sie ist anfänglich auf 0 gesetzt; wenn CanKillFrame eine MOb re-aktiviert, wird das entsprechende Bit gesetzt. Eine Anwendung kann einzelne / alle Bits wieder löschen nachdem sie den Sachverhalt zur Kenntnis genommen hat.
Solch ein Sendeabbruch muss nicht unbedingt als Hinweis auf einen zu korrigierenden Fehler verstanden werden: beim Hochfahren einzelner / aller Netzknoten kann es vorkommen, dass Nachrichten versendet werden, bevor ein adressierter Partnerknoten aktiviert ist.
Demo-Anwendung
Die Demo-Anwendung erlaubt die Verwendung der CAN Bibliothek unter einfachen und erprobten Bedingungen.
Echtzeitrahmen
Das dabei eingesetzte Prinzip ist einfach:
- der Prozessor durchläuft eine Endlos-Warteschleife - die "Idle-Loop"; nach jedem Durchgang wird der Prozessor in den "Sleep" Zustand gesetzt;
- die Behandlung von Interrupts definiert "Ereignisse" - deren Eintreffen markiert eine zur Synchronisation vorgesehene Variable; als Folge des Interrupts wird die Warteschleife neu durchlaufen;
- jeder Durchgang durch die Endlos-Warteschleife prüft die zur Synchronisation bestimmten Variablen und erkennt, wenn eine Variable gesetzt ist: in der Folge wird alles unternommen, was für das Abarbeiten des Ereignisses nötig ist, und die Variable wird gelöscht.
Dieses Prinzip kann auch zur Gestaltung eines minimalen Echtzeitrahmens für einfache Anwendungen verwendet werden, was den Einsatz eines Echtzeit-Betriebsystems erübrigt. Ich verwende, zum Beispiel, dieses Konzept bei meiner Hausbus Anwendung.
Damit dieses Vorgehen klaglos funktioniert sind zwei wichtige Punkte zu beachten:
- In der gesamten Anwendung dürfen keine "busy-wait" Warteschleifen (= von Interrupts nicht unterbrechbare Schleifen) programmiert sein - wie sie z. B. in den in <util.delay.h> und <util/delay_basic.h> definierten Werkzeugen der AVR-Libc Bibliothek vorkommen; dies ist auch der Grund, warum im Uart.c Programm der AVR-Libc Bibliothek eine kleine Änderung notwendig ist.
- Iterative Aufrufe der Programme der "Idle-Loop" sind sorgfältig zu programmieren: solche Aufrufe erzeugen zusätzliche Niveaus der Warteschleife und reservieren dabei jeweils alle dafür nötigen lokalen Variablen; ein typisches Beispiel dafür ist, wenn beim Abarbeiten des Empfangs einer Nachricht eine neue Nachricht mit CanSendFrame gesendet wird und dann das Unterprogramm XmitWait aufgerufen wird.
- Die Variable idle_level der Demo-Anwendung gibt jederzeit die Zahl aktiver "Idle-Loops" an.
Diese "Verschachtelung der Aufrufe" von Warteschleifen ist das kritische Element, das bei der Entscheidung zwischen dem Einsatz der hier beschriebenen Methode und dem Einsatz eines Echtzeit Betriebsystems zu bewerten ist:
- Bei einer Demo-Anwendung zum Erproben der Bibliothek, oder bei einer einfachen Anwendung wie meinem Hausbus (ich habe nie mehr als 2 Niveaus von Warteschleifen) ist diese Methode hervorragend geeignet.
- Bei einer komplexen Anwendung, wo die Zahl der Niveaus hoch oder unvorhersehbar ist, kann der Einsatz dieses Konzepts problematisch werden.
Diese kritische Bemerkung betrifft natürlich nur die Demo-Anwendung, die CAN Programmbibliothek kann ohne Einschränkung sowohl mit einem Echtzeit Betriebssytem als auch mit der einfachen Warteschleifen Methode eingesetzt werden. Bei Einsatz eines Echtzeit Betriebssystems wird es aber wahrscheinlich nötig sein, im Unterprogramm CANIT_vect den Mechanismus für die Kommunikation mit dem Betriebssystem den Anforderungen des gewählten Systems anzupassen.
Elemente der Demo-Anwendung
Die wesentlichen Bestandteile der Demo-Anwendung sind:
- eine Warteschleife ("Idle-Loop") - nach Ende jeder Iteration geht der Prozessor in den "Sleep" Zustand,
- ein interruptgetriebener Taktgeber (Timer) mit 10 msec Intervallen, der die Warteschleife periodisch aktiviert und jeder Aktivierung das auslösende Ereignis beiordnet; vom Taktgeber generierte Ereignisse sind: (a) 10-msec Interrupts, (b) 1-sec Echtzeit Zähler-Schritte, und (c) der Ablauf einer vorgewählten Warte-Periode (Unterprogramm TimedWait),
- Erweiterungen der "Idle-Loop" zum Abarbeiten der bei der Interrupt Behandlung markierten Ereignisse (zusätzlich zu den vom Taktgeber erzeugten Ereignissen natürlich auch zum Abarbeiten der weiter oben beschriebenen Ereignisse der CAN Kontrolleinheit),
- zwei Hilfsprozeduren: XmitWait zum Warten auf die Vollendung des Sendens einer Nachricht (mit aktivierten Interrupts), und TimedWait zum Warten einer festgelegten Zeit (mit aktivierten Interrupts); TimedWait ist hier nur zur Illustration erwähnt, das Unterprogramm wird im Demobeispiel nicht verwendet.
Diese Unterprogramme sind im Modul Demo.c enthalten, in Demo.h werden alle global verwendbaren Definitionen vorgenommen.
Kommentare zum Hauptprogramm (main)
Das Programm erklärt sich im wesentlichen selbst: nach der Initialisierung startet das Hauptprogramm den "Watchdog Timer" (die "Idle-Loop" unternimmt alle Sekunden die nötigen periodischen Watchdog Aufrufe); danach wird eine Initialisierungszeile gedruckt und eine Nachricht auf den CAN Bus gelegt.
Der Aufruf von CanInit erfolgt im hier gezeigten Beispiel in einer vereinfachten Form: der Wert 0 des zweiten Arguments bedeutet, dass die Anwendung keine Filter definiert: das Programm CanInit erzeugt automatisch ein Default Filter, das die Kontrolleinheit alle Nachrichten in einem einzigen MOb empfangen lässt.
Schliesslich durchläuft das Programm die "Idle-Loop".
Kommentare zum Unterprogramm IdleActions
Die Kontrollvariablen werden der Reihe nach auf das Vorliegen der verschiedenen Ereignisse abgefragt: wenn eine Ereignis-Variable (hier ein Bit in den Variablen can_status.cs_xmtOK, can_status.cs_rcvOK oder interrupt_flags) bei der Behandlung eines Interrupts markiert worden ist, wird das entsprechende Ereignis abgearbeitet, die Markierung wird gelöscht.
Die spezifischen Aktionen bei den einzelnen Ereignissen sind im folgenden kurz beschrieben.
- Ablauf der vorgewählten Warte-Periode:
- Falls IdleActions vom TimedWait Unterprogramm aufgerufen wurde, Rückkehr zu diesem Unterprogramm - sonst keine Aktion.
- Echtzeit-Zähler Sekundenschritt:
- Sekundenzähler nachführen, "Watchdog Timer" aufrufen.
- Empfang einer Nachricht vom CAN Bus:
- Die Kontrollvariable can_status.cs_rcvOK enthält die Liste aller MObs (Liste von Bits), für die eine empfangene Nachricht wartet. Das Bit mit der höchsten Priorität in die Nummer (Nummern 0 ... 14) des entsprechenden MOb umwandeln, CanGetFrame für dieses MOb aufrufen, eine entsprechende Zeile drucken.
- Abschluss des Sendens einer vom CAN Bus gesendeten Nachricht:
- Falls IdleActions vom XmitWait Unterprogramm aufgerufen wurde, Rückkehr zu diesem Unterprogramm - sonst keine Aktion.
- Andere Ereignisse (10 msec Taktgeber Ereignis):
- Prüfen ob eine "hängengebliebene" Nachricht existiert und gegebenenfalls Löschen dieser Nachricht. Danach den Prozessor in den "Sleep" Zustand setzen.
Die Befehlsfolge am Ende des Unterprogramms bezweckt, dass der Prozessor sicher und ohne Auslösen eines Interrupt sofort nach dem "sei" Befehl in den "Sleep" Zustand gesetzt wird - wie in der Dokumentation zu avr/sleep.h (AVR-Libc) beschrieben:
cli () ;
sleep_enable () ;
sei () ;
sleep_cpu () ;
sleep_disable () ;
Dadurch wird sichergestellt, dass ein neuer Durchlauf durch die Warteschleife erfolgt, auch wenn ein Interrupt genau am Ende der Warteschleife erfolgt.
Kommentare zu den Unterprogrammen XmitWait und TimedWait
Mithilfe dieser Unterprogramme kann die Anwendung das Eintreten eines Ereignisses abwarten, ohne dass dafür eine "busy-wait" Schleife programmiert werden muss.
XmitWait startet eine neue Warteschleife und bleibt darin, bis das Senden der als Argument angegebenen MOb beendet ist. TimedWait startet eine neue Warteschleife und bleibt darin, bis die als Argument von TimedWait vorgegebene Zahl von 10-msec Intervallen abgelaufen ist.
Alle auftretenden Ereignisse werden von der jeweils ausgeführten Warteschleife abgearbeitet (das ist die Warteschleife, die als letzte geöffnet wurde).
Adressierung und Filter
Verwendung von CanInit zum Definieren von Filtern
Zum leichteren Verständnis verzichtet die oben beschriebene Demo-Anwendung auf den Einsatz von Filtern.
Die folgenden Bemerkungen illustrieren, wie Filter verwendet werden können und sollen Anregungen sein, wie das Broadcast Konzept von CAN für die Bestimmung der Partner beim Austausch von Nachrichten sinnvoll eingesetzt werden könnte.
Die beiden Argumente von CanInit erlauben, eine Tabelle mit Filtern zu definieren, welche dann von CanInit installiert werden. Wenn diese Tabelle in der Variablen filter_table definiert ist (sie muss im Flash Speicher liegen), sieht der Aufruf von CanInit so aus:
CanInit ( filter_table, sizeof (filter_table) / sizeof (can_filter) ) ;
Konzept für die Gestaltung des Adressraums, Beispiel
Bevor eine Filtertabelle erstellt wird, muss ein Konzept für die Gestaltung des Adressraums definiert werden. Im hier erläuterten Beispiel wurde das folgende Konzept gewählt:
Normale Adressen (11-Bit Werte) werden in zwei Zonen zerlegt:
- die oberen 3 Bits bestimmen eine Adress-Familie,
- die unteren 8 Bits unterscheiden spezifische Adressen innerhalb einer Familie,
- das oberste der 3 Bits in der ersten Zone teilt die Familien in 2 Gruppen - eine Gruppe mit hoher Priorität beim Senden, eine mit niedriger Priorität; die unteren 2 Bits definieren damit 4 Gruppen, die mit und ohne Priorität verwendet werden können (siehe auch die Beschreibung des MG_PRIORITY Bits im Abschnitt Datenstrukturen).
- im Modul Can.h werden Symbole mit Werten für die Familien definiert, z. B.:
#define MG_MASK 0x0700L // message group mask
#define MG_PRIORITY 0x0400L // priority bit (set if low priority)
#define MG_BROADCAST 0x0400L // low-priority broadcast
#define MG_ACTION 0x0500L // low-priority action trigger
#define MG_MODIFY 0x0600L // low-priority modify state request
- auch für die unteren 8 Bits ist es zweckmässig, Symbole zu definieren - hier sind ein paar Beispiele für Typen von Broadcast Nachrichten die ich verwende:
enum {
MB_TIME = 1, // sending time and date
MB_POLL, // CPU status poll
MB_ECHO, // CPU status echo
MB_CONFIG, // Solicit configuration messages
...
} ;
Auch erweiterte Adressen können in mehrere Zonen zerlegt werden - damit lässt sich Kontrollinformation im Nachrichtenkopf "verstecken" ohne die wertvollen 8 Datenbytes zu verwenden:
- Sequenznummer der Nachricht,
- Typ der Nachricht,
- Zieladresse.
Ich habe vorgesehen (es aber bisher nicht gebraucht und daher noch nicht implementiert) dies für verbindungs-orientierte Kommunikation zu verwenden. Zurzeit setze ich erweiterte Adressen ein, um eine Nachricht an eine spezifische CPU zu senden und dabei zwischen verschiedenen Arten von Nachrichten zu unterscheiden.
Die für eine derartige Verwendung des Adressraums nötige Programmierung ist Aufgabe der Anwendung: sie muss das mob_header Feld zum Senden entsprechend vorbereiten, bzw. beim Empfang entsprechend interpretieren - die CAN Bibliothek hat damit nichts zu tun, sie arbeitet auf einer tieferen "Schicht".
Definition eines Satzes von Filtern, Beispiel
Eine Definition der Filtertabelle kann dann so aussehen:
// Can receive filter definition table
// -----------------------------------
// Filter table item descriptors for channel 0 and on
// Channels beyond those defined in this table are used for
// transmission
static can_filter filter_table[] PROGMEM = {
// tag mask tag_flags mask_flags
// high-priority broadcasts
{ MG_BROADCAST & ~MG_PRIORITY, MG_MASK, 0, _BV(RTRMSK) | _BV(IDEMSK) },
// low- & high-priority broadcasts
{ MG_BROADCAST & ~MG_PRIORITY, MG_MASK & ~MG_PRIORITY, 0, _BV(RTRMSK) | _BV(IDEMSK) },
// high-priority modify state requests
{ MG_MODIFY & ~MG_PRIORITY, MG_MASK, 0, _BV(RTRMSK) | _BV(IDEMSK) },
// low- & high-priority modify state req.
{ MG_MODIFY & ~MG_PRIORITY, MG_MASK & ~MG_PRIORITY, 0, _BV(RTRMSK) | _BV(IDEMSK) },
// extended messages (sequence numbers)
{ CPU_ID, 0x000000FFL, 0, _BV(RTRMSK) | _BV(IDEMSK) | CAN_EXTD },
// all other messages
{ 0x00000000L, 0x00000000L, 0, _BV(RTRMSK) } } ;
Die ersten 4 Filter sind Paare von Filtern (je ein Filter mit hoher und mit niedriger Priorität) für die Gruppen mit Adressen der Familie MG_BROADCAST und MG_MODIFY, alle verwenden normale Adressen. Das fünfte Filter verwendet erweiterte Adressen und dient dem Empfang von Nachrichten nur für den betroffenen Prozessor (CPU_ID muss in Demo.h definiert sein). Das letzte Filter wird für den Empfang aller Nachrichten verwendet, die von den ersten 5 Filtern nicht angenommen worden sind.
Links
Referenzdokumente
Hier sind zwei Artikel: (a) spezifischen Fragen zum CAN Zugriff mittels AT90CAN Prozessoren, und (b) ein Tutorial zur praktischen Implementierung des Zugriffs auf einen CAN Bus von einem AVR Prozessor - auch wenn er den AT90CAN "umgeht" ist er eine hervorragende Einführung zu Fragen der praktischen Implementierung des Zugriffs von einem AVR Prozessor auf einen CAN Bus:
Daneben gibt es Tonnen von Artikeln zu CAN, die aufzuzählen hier fehl am Platze wäre.