Kampis Elektroecke

Versenden und Empfangen einer Nachricht

canknotenkomplett_klein.jpg

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 &amp;H01

   Errif = Interrupt
   Shift Errif , Right , 5
   Errif = Errif And &amp;H01

   Tx2if = Interrupt
   Shift Tx2if , Right , 4
   Tx2if = Tx2if And &amp;H01

   Tx1if = Interrupt
   Shift Tx1if , Right , 3
   Tx1if = Tx1if And &amp;H01

   Tx0if = Interrupt
   Shift Tx0if , Right , 2
   Tx0if = Tx0if And &amp;H01

   Rx1if = Interrupt
   Shift Rx1if , Right , 1
   Rx1if = Rx1if And &amp;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 &gt; 8 Then

      Print "Ungültige Länge!"
      Return

   End If


   If Tx_identifier &gt; 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 + &amp;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 &amp;H18
Shift Frame , Right , 3

Filter = Status
Filter = Filter And &amp;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 &gt; 8 Then

      Print "Ungültige Länge der Nachricht!"
      Return

   End If

   If Tx_identifier &gt; 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.

→ Zurück zum CAN-Bus

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert