In diesem Artikel gehe ich etwas genauer auf die Grundlagen der Nachrichtenübertragung über den CAN-Bus mit Hilfe eines MCP2515 ein. Für ein besseres Verständnis des Programmes wäre es hilfreich dieses griffbereit zu haben, da es sehr lang ist und ich es aus diesem Grund nicht komplett hier posten werde.
Der Loopback Modus:
Der MCP2515 bietet ein nettes Feature, wodurch es möglich ist die Übertragung ohne zweiten CAN-Controller o.ä. zu testen; den sogenannten Loopback Modus. Wenn der Controller in diesen Modus versetzt worden ist, kann er Nachrichten versenden und gleichzeitig empfängt er sie. Um den MCP2515 in den „Loopback“ Modus zu versetzen, verwende ich folgendem Befehl:
Bitmodify Canctrl , &HE0 , &H40
Dieser Befehl wird einfach an den Anfang eures Hauptprogramms gesetzt und er modifiziert die „REQOP“ Bits vom „CANCTRL“ Register.
Diese Bits legen den Betriebsmodus des Controllers fest:
Nachdem der CAN-Controller nun in den „Loopback“ Modus versetzt worden ist, können wir mit dem Versenden und Empfangen von Nachrichten beginnen.
Aktivieren der Interrupts:
Die erste Nachricht, die wir versenden wird ein Remoteframe sein, da dieser keine Daten enthält und somit etwas leichter zu handhaben ist. Bevor ich allerdings auf das Programm eingehe muss der Mega32 erstmal vorbereitet werden. Da ich die gesamte Kommunikation über CAN Interruptgesteuert realisiert habe, muss der INT Pin vom MCP2515 mit einem INT Pin vom Mega32 verbunden werden. Für meine Versuche habe ich den INT0 gewählt.
Aktiviert wird dieser wie folgt:
Enable Interrupts Config Int0 = Falling On Int0 Mcp2515_int Enable Int0
Als erstes werden die Interrupts global freigegeben und dann wird der INT0 Pin so eingestellt das er auf fallende Flanken reagiert. Die Zeile
On Int0 Mcp2515_int
legt die Sprungmarke für den Interrupt fest. Das Programm springt also bei einer fallenden Flanke in die ISR „MCP2515_Int“. Als letztes wird der INT0 noch aktiviert. Anschließend muss noch der Pin D.2 als Eingang geschaltet werden:
Config Pind.2 = Input
Falls keine externen Pull-Up Widerstände verwendet werden, muss der interne Pull-Up mit dem Befehl
Pind.2 = 1
eingeschaltet werden.
Auswerten eines Interrupts:
Die ISR für den INT0 sieht folgendermaßen aus:
Mcp2515_int: Statusflag = 1 Return
Da es nicht sonderlich effizient ist in einer ISR einen Sprung in ein Unterprogramm zu machen (da die ISR sonst zu lange aktiv ist), setze ich dort nur ein Flag. Dieses Flag frage ich dann im Hautprogramm ab:
If Statusflag = 1 Then Print "MCP Interrupt!" Statusflag = 0 Mcp2515_interrupt</code> End If
Sobald also ein Interrupt auftritt (was erstmal nur passiert wenn der CAN-Controller eine Nachricht empfängt und den entsprechend ein Rx-Buffer Full Interrupt auslöst), wird eine Meldung im Terminal ausgegeben, das Flag auf 0 gesetzt und in ein Unterprogramm mit dem Namen „MCP2515_Interrupt“ gesprungen. Dieses Unterprogramm sieht wie folgt aus:
Sub Mcp2515_interrupt Local Interrupt As Byte Local Rx0if As Byte Local Rx1if As Byte Local Tx0if As Byte Local Tx1if As Byte Local Tx2if As Byte Local Errif As Byte Local Wakif As Byte Local Merrf As Byte Interrupt = Read_register(canintf) Merrf = Interrupt Shift Merrf , Right , 7 Wakif = Interrupt Shift Wakif , Right , 6 Wakif = Wakif And &H01 Errif = Interrupt Shift Errif , Right , 5 Errif = Errif And &H01 Tx2if = Interrupt Shift Tx2if , Right , 4 Tx2if = Tx2if And &H01 Tx1if = Interrupt Shift Tx1if , Right , 3 Tx1if = Tx1if And &H01 Tx0if = Interrupt Shift Tx0if , Right , 2 Tx0if = Tx0if And &H01 Rx1if = Interrupt Shift Rx1if , Right , 1 Rx1if = Rx1if And &H01 Rx0if = Interrupt Shift Rx0if , Left , 7 Shift Rx0if , Right , 7 Print "---Statusregister---" Print "Interrupt Flag: " ; Read_register(canintf) Print "Message Error Interrupt Flag: " ; Merrf Print "Wakeup Interrupt Flag: " ; Wakif Print "Error Interrupt Flag: " ; Errif Print "Transmit Buffer 2 Empty Interrupt Flag: " ; Tx2if Print "Transmit Buffer 1 Empty Interrupt Flag: " ; Tx1if Print "Transmit Buffer 0 Empty Interrupt Flag: " ; Tx0if Print "Recievebuffer 1 Full Interrupt Flag: " ; Rx1if Print "Recievebuffer 0 Full Interrupt Flag: " ; Rx0if Print "------------------------------------------------------------------------" Recieve_can End Sub
In diesem Unterprogramm findet die eigentliche Analyse des Interrupts statt. Diese erfolgt in mehreren Schritten:
Als erstes wird mittels
Interrupt = Read_register(canintf)
das Bitmuster aus dem Interruptflag Register des CAN-Controllers geladen.
Anschließend werden die einzelnen Bits über eine Reihe von Bitmanipulationen herausgefiltert.
Diese Bits werden anschließend über ein Terminal ausgegeben.
Im letzten Schritt wird das Unterprogramm „Recieve_can“ aufgerufen.
Wofür dieses Unterprogramm da ist erkläre ich später genauer.
Für die ersten Versuche wird es nicht benötigt.
Das Grundgerüst für das Empfangen steht nun. Wir können jetzt erkennen ob der CAN-Controller eine neue Nachricht in seinen Buffern hat sobald dieser einen Interrupt auslöst.
Verschicken eines Remoteframes:
Jetzt geht es an das Versenden einer Nachricht. Wie oben bereits erwähnt habe ich für meine ersten Versuche einen Remoteframe verschickt. Dies geschieht mit folgendem Unterprogramm:
Sub Send_remote(byval Tx_identifier As Integer , Byval Datalenght As Byte) Local Tx_low As Integer Local Tx_high As Integer Local Id_high As Byte Local Id_low As Byte If Datalenght > 8 Then Print "Ungültige Länge!" Return End If If Tx_identifier > 2047 Then Print "Identifier beträgt " ; Tx_identifier ; " und ist damit zu groß. Maximaler Wert ist 2047!" Else Tx_low = Tx_identifier ' Tx_high = Tx_identifier Shift Tx_low , Left , 5 Shift Tx_high , Right , 3 Id_high = Tx_high Id_low = Tx_low</code> Write_register Txb0sidh , Id_high Write_register Txb0sidl , Id_low</code> Datalenght = Datalenght + &H40 Write_register Txb0dlc , Datalenght</code> Reset Cs pdr = Spi_rts0 Do Loop Until Spsr.spif = 1 Set Cs< End If End Sub
Es wird wie folgt aufgerufen:
Send_remote, Message_id, Datenlänge
Das Unterprogramm bekommt als Übergabeparameter die ID der zukünftigen Nachricht, sowie die Nachrichtenlänge übergeben. Im Unterprogramm wird als erstes geprüft ob die Nachrichtenlänge mehr als 8 Zeichen beträgt und ob der Identifier größer als 2047 (11 Bit) ist. Wenn dies der Fall ist wird das Unterprogramm verlassen und ein Fehler ausgegeben.
Wenn alles in Ordnung ist, wird der Identifier zerlegt, geshiftet und in die beiden Register für den Identifier geschrieben (einmal die höherwertigen 8 Bit und einmal die niederwertigen 3 Bit). Im nächsten Schritt wird die Datenlänge in das „Datalenght“-Register geschrieben. Zusätzlich wird dort noch das Remotebit gesetzt, damit der CAN-Controller weiß das die nächste Nachricht ein Remoteframe sein wird. Als letztes wird mit dem Befehl:
Spdr = Spi_rts0
die Übertragung ausgelöst. Wenn ihr das Unterprogramm nun z.B. wie folgt aufruft (z.B. 1x die Sekunde mittels Timer)
Remote,7,1
sollte nach dem Versenden der Nachricht eine Ausgabe im Terminal erscheinen. Wenn diese Meldung im Terminal erscheint, habt ihr alles richtig gemacht. Dann wurde der Remoteframe erfolgreich gesendet und der CAN-Controller hat ihn auch erfolgreich empfangen.
Auswerten der Nachricht:
Natürlich ist dies etwas wenig für eine Kommunikation. Die nächsten Schritte finden anschließend im Unterprogramm „Recieve_can“ statt. Dort wird als aller erstes mit diesem Befehl
Status = Read_rx_status()
das Statusregister der Recievebuffer ausgelesen. Anschließend werden wieder über eine Reihe von Bitmanipulationen alle notwendigen Informationen aus dem im Buffer gespeicherten Bitmuster gewonnen:
Buffer = Status Shift Buffer , Right , 6 Frame = Status Frame = Frame And &H18 Shift Frame , Right , 3 Filter = Status Filter = Filter And &H07
Danach wird geprüft in welchem Buffer die Nachricht liegt. Dies geschieht über eine einfache If-Else Abfrage und nachdem der Empfangsbuffer bestimmt worden ist, geschieht dasselbe nochmal für den Frame um zu erkennen ob es sich um einen Standard Datenframe oder um einen Standard Remote Frame handelt.
Da ich in dem Beispiel erstmal nur einen Remoteframe gesendet habe, werde ich auch erstmal nur auf diesen Teil der Abfrage eingehen.
Da nun bestimmt ist, dass die Nachricht in Buffer 0 liegt und es sich um einen Standard Remote Frame handelt, kann mit dem Befehl
Spi_read_rx0
das Lesen von Buffer 0 ausgelöst werden.
Sobald dieser Befehl abgeschickt wurde, gibt der CAN-Controller alle in dem Frame enthaltenen Informationen über das SPI Interface aus.
Diese werden nun eingelesen:
Spiin Id_high , 1 Spiin Id_low , 1 Spiin Dummy , 1 Spiin Dummy , 1 Spiin Remote_lenght , 1
Die ersten beiden Bytes sind die beiden Teile der ID. Diese werden in separaten Variablen gespeichert. Bei den Werten die unter „Dummy“ gespeichert werden handelt es sich um Bytes für den Extended Identifier, welche sofort verworfen werden, da ich sie nicht verwenden möchte.
Das letzte Byte ist dann die Länge.
Sobald der Einlesevorgang abgeschlossen ist müssen die empfangenen Daten noch wieder richtig zusammen gesetzt werden.
Dies geschieht so:
Shift Id_high , Left , 3 Shift Id_low , Right , 5 Can_message(1) = Id_high Can_message(1) = Can_message(1) Or Id_low Remote_lenght = Remote_lenght - 64 Can_message(2) = Remote_lenght
Als erstes wird die Aufteilung des Identifiers rückgängig gemacht. Hierfür werden die Bitmuster wieder in die richtige Position geschoben und anschließend verodert und an der ersten Stelle des Arrays für die Nachricht gespeichert.
Bei dem Bitmuster für die Länge muss noch das Remotebit entfernt werden.
Dies geschieht über eine einfache Subtraktion
Die komplette Nachricht wird anschließend ausgegeben:
Print "Status: " ; Status Print "ID Nachricht: " ; Can_message(1) Print "Länge: " ; Can_message(2)
Wenn auch hier alles funktioniert, erhaltet ihr eine Nachricht mit exakt den Werten die ihr abgeschickt habt.
Das verschicken, empfangen und auswerten eines Remoteframes klappt nun also bereits.
Im nächsten Artikel erkläre ich euch dann wie ihr einen Datenframe sendet.
Verschicken eines Datenframes:
Das Versenden eines Datenframes geschieht auf ähnliche Weise wie das Versenden eines Remoteframes.
Das Unterprogramm hierfür sieht so aus:
Sub Send_can(byval Tx_identifier As Integer , Byval Datalenght As Byte , Daten(8) As Byte) Local Tx_low As Integer Local Tx_high As Integer Local Id_high As Byte Local Id_low As Byte Local Transmitcounter As Byte Local Transmitadress As Byte If Datalenght > 8 Then Print "Ungültige Länge der Nachricht!" Return End If If Tx_identifier > 2047 Then Print "Identifier beträgt " ; Tx_identifier ; " und ist damit zu groß. Maximaler Wert ist 2047!" Return Else Tx_low = Tx_identifier Tx_high = Tx_identifier Shift Tx_low , Left , 5 Shift Tx_high , Right , 3 Id_high = Tx_high Id_low = Tx_low</code> Write_register Txb0sidh , Id_high Write_register Txb0sidl , Id_low Write_register Txb0dlc , Datalenght Transmitadress = Txb0d0 For Transmitcounter = 1 To Datalenght Write_register Transmitadress , Daten(transmitcounter) Incr Transmitadress Next Transmitcounter Reset Cs Spdr = Spi_rts0 Do Loop Until Spsr.spif = 1 Set Cs End If End Sub
Das Unterprogramm wird wie folgt aufgerufen:
Send_can Message_id, Datenlänge, Datenbytes
In das Unterprogramm werden die ID, die Datenlänge und die einzelnen Datenbytes der Nachricht übergeben.
Auch hier wird erst geprüft ob ein gültiger Parameter für die ID und für die Datenlänge übergeben wurde.
Anschließend wird, wie auch beim Remoteframe, das Bitmuster für das Identifierregister erzeugt und die Datenlänge in das „Datalenght“-Register geschrieben. Danach beginnt eine Zählschleife, die die Datenregister des CAN-Controllers mit den übergebenen Daten füttert. Diese Schleife sieht so aus:
Transmitadress = Txb0d0 For Transmitcounter = 1 To Datalenght Write_register Transmitadress , Daten(transmitcounter) Incr Transmitadress Next Transmitcounter
Als erstes wird die Variable „Transmitadress“ auf die Adresse vom Datenregister 0 gesetzt. Danach beginnt eine Schleife die von 1 bis zu der Anzahl an übergebenen Daten zählt (also maximal 8) und jedes mal die Variable „Transmitadress“ um eins erhöht. Anschließend wird das Register mit der, durch die Variable „Transmitadress“ angegebenen, Adresse mit einem durch den Zähler markierten Datenbyte gefüllt. Danach wird der Zähler um eins erhöht und alles beginnt von vorne. Sobald die Schleife durchlaufen wurde, wird die Übertragung ausgelöst.
Dies geschieht auf die selbe Weise wie beim Remoteframe.
Auswerten der Nachricht:
Das Auswerten der Nachricht geschieht ebenfalls in dem Unterprogramm „Recieve_can“ und ist bis zu der If-Else Abfrage identisch mit der Auswertung eines Remoteframes. Sobald das Programm aber feststellt das es sich um einen Datenframe handelt, springt es in den Teil der Abfrage der für die Auswertung eines Datenframes zuständig ist. Als erstes wird mit
Spi_read_rx0 Spiin Id_high , 1 Spiin Id_low , 1 Spiin Dummy , 1 Spiin Dummy , 1
das Lesen des Empfangsbuffers ausgelöst und die beiden Bytes für die ID, sowie die beiden bytes für den Extended Identifier eingelesen.
Anschließend wird die Länge der Daten eingelesen und aufbereitet:
Spiin Laenge , 1
Zaehler_laenge = Laenge + 2
Laenge = Laenge And &H0F
Diese Länge verwende ich nun für eine Schleife, die bei jedem Durchlauf ein Datenbyte einließt und es in einem Array speichert:
For Daten_laenge = 3 To Zaehler_laenge Spiin Can_message(daten_laenge) , 1 Next
Nachdem alle Daten eingelesen wurde, werden die beiden Bytes der Message ID wieder zusammen gesetzt um die ursprüngliche Nachrichten ID zu erhalten. Anschließend werden alle Informationen in einem Nachrichtenarray gespeichert:
Shift Id_high , Left , 3 Shift Id_low , Right , 5 Can_message(1) = Id_high Can_message(1) = Can_message(1) Or Id_low Can_message(2) = Laenge
Als letztes wird die komplette Nachricht über UART ausgegeben:
Print "Status: " ; Status Print "ID Nachricht: " ; Can_message(1) Print "Länge Nachricht: " ; Can_message(2) For Printcounter = 1 To Can_message(2) Print "Daten " ; Printcounter ; ": " ; Can_message(printcounter + 2) Next Printcounter
Das ganze kann nun getestet werden, indem mit folgendem Befehl eine Übertragung gestartet wird:
Data,7,4,2,5,7,8
Dadurch wird ein Datenframe mit der ID 7, einer Länge von 4 Byte und den Datenbytes 2, 5, 7 und 8 verschickt.
Schreibe einen Kommentar