SPI Kommunikation zwischen dem MCP2515 und dem Mikrocontroller
Sämtliche Kommunikation mit dem MCP2515 läuft über das Auslesen bzw. Beschreiben von Registern. Der Zugriff auf die Register erfolgt nach folgendem Muster:
- CS auf LOW ziehen, um die SPI Schnittstelle des CAN Controllers zu aktivieren.
- Instruction Befehl senden (READ, WRITE, RESET, RTS gemäß Datenblatt)
- Je nach Befehl ggf. weitere Bytes senden (Registeradresse, Maske, neuer Registerinhalt)
- CS auf HIGH entlassen, um die SPI Kommunikation zu beenden
Wichtig für das Verständnis sind noch die Variablen vom Typ "Buffer". Eine CAN Botschaft besteht aus vielen
einzelnen Bytes die zusammengefasst als Buffer bezeichnet werden. Auf dem Arduino werden diese Buffer des MCP2515
gespiegelt. Sendebotschaften werden zunächst auf dem Arduino mit den "set-Methoden" zusammengestellt und dann
gemeinsam mit "loadTxBuffer()" auf den CAN Controller übertragen.
Empfangenen Botschaften dagegen werden
gemeinsam mit "readRxBuffer()" auf den Arduino geladen und mit get-Methoden anschließend zusammengefasst und
bereitgestellt. .
In der vorbereiteten Klasse werden nun die wichtigsten der im Datenblatt angegebenen Befehle zur SPI Kommunikation als PRIVATE Methoden programmiert.
Klasse "MCP25215" mit PRIVATE Methoden
#include "SPI.h"
/* Erstellen eines eigenen Datentyps 'Buffer' mit den gleichen Namen und der gleichen Reihenfolge wie die
* entsprechenden Buffer im MCP2515 (Kopie der 5 Buffer 3x Tx und 2xRx) */
struct Buffer{
byte SIDH;
byte SIDL;
byte EID8;
byte EID0;
byte DLC;
byte D0, D1, D2, D3, D4, D5, D6, D7;
};
class MCP2515 {
private:
byte CS; // Speichern des von Konstruktor übertragenen 'Chip Select' Pins
Buffer RXB0, RXB1, TXB0, TXB1, TXB2; // Byte-Container vom oben definierten Typ 'Buffer'
void reset(); // Reset des MCP2515 (= wie VCC Unterbrechung)
byte registerRead(byte); // Liest ein Registerinhalt (regAdr)
void registerWrite(byte, byte); // Beschreibt ein Register (regAdr, daten)
void modifyBits(byte, byte, byte); // Verändert einzelne Bits des Registers (regAdr, maske, daten)
void sendRTS(byte); // 'Request to Send' eines TX Buffers (bufferNr)
void setMode(byte); // 0=Normal Mode, 1=Sleep , 2=Loopback , 3= Listen , 4= Config
void readRXbuffer(byte); // Auslesen und quittieren eines RX/Empfangs Buffer (bufferNr)
void loadTXbuffer(byte); // Übertragen eines kompletten Buffers auf den MCP2515 (bufferNr)
public:
// Konstruktor (Initialisierungsfunktion)
MCP2515(byte cs = 10) {
SPI.begin();
SPI.setDataMode(SPI_MODE0); // oder SPI_MODE3
SPI.setClockDivider(SPI_CLOCK_DIV4); // max. 10 Mhz
SPI.setBitOrder(MSBFIRST);
CS = cs; // Ein andere CS Pin kann optional übergeben werden
pinMode(CS, OUTPUT); // Chip Select Pin für die SPI Schnittstelle konfigurieren
};
//// PRIVATE METHODS //////////////////////////////////////////////////////////////////////////////////
void MCP2515::reset() {
/* Funktion bildet den im Datenblatt angegebenen Befehl RESET als komplette SPI Sequenz ab.
* Alle Control-Register werden mit den Werten der "Werkseinstellung" beschrieben.
* Die Datenregisterinhalte werden nicht geändert.
*/
digitalWrite(CS, LOW); // CS auf LOW ziehen um den Slave auszuwählen
SPI.transfer(B11000000); // RESET Befehl zum Auslesen eines Registers gemäß Datenblatt
digitalWrite(CS, HIGH); // CS wieder freigeben
delay(10); // Warten bis Daten vollständig gespeichert sind
}
//----------------------------------------------------------------------------------------------------
byte MCP2515::registerRead(byte regAdr) {
/* Funktion bildet den im Datenblatt angegebenen Befehl READ als komplette SPI Sequenz ab.
* Der ausgelesene Registerinhalt wird der Funktion zurückgegeben.
*/
digitalWrite(CS, LOW); // CS auf LOW ziehen um den Slave auszuwählen
SPI.transfer(B00000011); // READ Befehl zum Auslesen eines Registers gemäß Datenblatt
SPI.transfer(regAdr); // Übernahme der Adresse des auszulesenden Registers
byte empfangen = SPI.transfer(0xff); // Einlesen von einem Byte über MISO
digitalWrite(CS, HIGH); // CS wieder freigeben
return empfangen;
}
//----------------------------------------------------------------------------------------------------
void MCP2515::registerWrite(byte regAdr, byte daten) {
/* Funktion bildet den im Datenblatt angegebenen Befehl WRITE als komplette SPI Sequenz ab.
*/
digitalWrite(CS, LOW); // CS auf LOW ziehen um den Slave auszuwählen
SPI.transfer(B00000010); // WRITE Befehl zum Auslesen eines Registers gemäß Datenblatt
SPI.transfer(regAdr); // Übernahme der Adresse des zu beschreibenden Registers
SPI.transfer(daten); // Daten die in das Register geschriebene werden sollen
digitalWrite(CS, HIGH); // CS wieder freigeben
delay(10); // Warten bis Daten vollständig gespeichert sind
}
//----------------------------------------------------------------------------------------------------
void MCP2515::modifyBits(byte regAdr, byte mask, byte daten) {
/* Funktion bildet den im Datenblatt angegebenen Befehl BIT MODIFY als komplette SPI Sequenz ab.
* Der Befehl dient zum ändern von einzelnen Bit innerhalb eines Registerbytes. Welche Bits
* überschrieben werden wird durch eine MASKE festgelegt. Durch die MASKE B11100000 werden vom
* anschließenden Byte nur die Stellen [7:5] übernommen. Bsp. Der Registerinhalt B10001100 wird durch
* die Maske B11100000 und das Datenbyte B10100001 zu dem neuen Registerinhalt B10101100.
* Es lassen sich nur die im Datenblatt grau hinterlegten Register modifizieren (meist Control-Register).
*/
digitalWrite(CS, LOW); // CS auf LOW ziehen um den Slave auszuwählen
SPI.transfer(B00000101); // BIT MODIFY Befehl senden gemäß Datenblatt
SPI.transfer(regAdr); // Register Adresse eines im Datenblatt grau hinterlegten Registers
SPI.transfer(mask); // MASKE welche Bits geändert werden sollen (=1)
SPI.transfer(daten); // Databyte
digitalWrite(CS, HIGH); // CS wieder freigeben
delay(10);
}
//----------------------------------------------------------------------------------------------------
void MCP2515::sendRTS(byte bufferNr) {
/* Funktion bildet den im Datenblatt angegebenen Befehl RTS als komplette SPI Sequenz ab.
* Der Variablen 'bufferNr' wird mitgegeben welcher Buffer gesendet werden soll (s. Datenblatt)
* 0=kein Buffer, 1=TXB0, 2=TXB1, 3=TXB1+TXB0, 4=TXB2, 5=TXB2+TXB0, 6=TXB2+TXB1, 7=TXB2+TXB1+TXB0
* Durch eine if-Abfrage am Anfang wird verhindert, dass Werte größer 7 bearbeitet werden.
*/
if (bufferNr > 7) bufferNr = 7; // nur die Bits [2:0] werden betrachte
digitalWrite(CS, LOW); // CS auf LOW ziehen um den Slave auszuwählen
SPI.transfer(B10000000|bufferNr); // RTS (Request To Send) Befehl senden gemäß Datenblatt
digitalWrite(CS, HIGH); // CS wieder freigeben
}
//----------------------------------------------------------------------------------------------------
void MCP2515::setMode(byte modus) {
/* Mit der Funktion kann der Betriebsmodus des MCP2515 eingestellt werden. Mit der 'modifyBits()'
* Funktion werden dazu die dafür vorgesehenen Bit im CANCTRL Register eingestellt.
* 0=Normal Mode (Senden und Empfangen), 1=Sleep Mode (Ruhezustand), 2=Loopback Mode (Testmodus zum
* Übertragen von TX auf RX Buffer ohne Bus), 3= Listen Mode, 4= Config Mode (zum Einstellen einiger
* Register notwendig).
*/
if (modus > 4) modus = 4;
byte neueBits = modus << 5;
modifyBits(B00001111, B11100000, neueBits); // Register Adresse 'CANCTRL', Maske [7:5], neue Datenbits)
}
//----------------------------------------------------------------------------------------------------
void MCP2515::loadTXbuffer(byte bufferNr) {
/* Funktion bildet den im Datenblatt angegebenen Befehl LOAD TX BUFFER als komplette SPI Sequenz ab.
* Im Datenblatt sind drei unterschiedliche Befehle angegeben mit denen man jeweils Daten in die Buffer
* TX0B, TX1B oder TX2B auf dem MCP2515 übertragen kann. Die zu Beginn initialisierten Variablen
* 'loadBuffer' und 'befehl' dienen zum temporären speichern des Befehls und des Buffer-Inhalts die im
* unteren Teil per SPI übertragen werden. Mit der if-Anweisung werden die Variablen mit den jeweils
* richtigen Daten gefüllt.
*/
Buffer loadBuffer; // Variable des selbst definierten Typs Buffer
byte befehl; // Variable die den Befehl abhängig vom der gewählten 'bufferNr' aufnimmt
// If Anweisung wählt den entsprechenden Befehl und den Inhalt des ausgewählten Buffers
if (bufferNr == 0) {
befehl = B01000000; // Start Auslesen bei Register TXB0SIDH
loadBuffer = TXB0;
} else if (bufferNr == 1) { // Start Auslesen bei Register TXB1SIDH
befehl = B01000010;
loadBuffer = TXB1;
} else { // Start Auslesen bei Register TXB2SIDH
befehl = B01000100;
loadBuffer = TXB2;
}
// Übertragen des Buffers
digitalWrite(CS, LOW); // CS auf LOW ziehen um den Slave auszuwählen
SPI.transfer(befehl); // LOAD TX BUFFER Befehl senden gemäß Datenblatt
SPI.transfer(loadBuffer.SIDH); // Aufgrund Autoincrement werden nachfolgende Register...
SPI.transfer(loadBuffer.SIDL); // ... hintereinander eingelesen.
SPI.transfer(loadBuffer.EID8);
SPI.transfer(loadBuffer.EID0);
SPI.transfer(loadBuffer.DLC);
SPI.transfer(loadBuffer.D0);
SPI.transfer(loadBuffer.D1);
SPI.transfer(loadBuffer.D2);
SPI.transfer(loadBuffer.D3);
SPI.transfer(loadBuffer.D4);
SPI.transfer(loadBuffer.D5);
SPI.transfer(loadBuffer.D6);
SPI.transfer(loadBuffer.D7);
digitalWrite(CS, HIGH); // CS wieder freigeben
}
//----------------------------------------------------------------------------------------------------
void MCP2515::readRXbuffer(byte bufferNr) {
/* Funktion bildet den im Datenblatt angegebenen Befehl READ RX BUFFER als komplette SPI Sequenz ab.
* Im Datenblatt sind zwei Befehle angegeben mit denen man jeweils Daten entweder aus RXb0 oder RXB1
* auslesen kann. Die beiden zu Beginn initialisierten Variablen dienen zur Zwischenspeicherung des über
* die if-Abfrage festgelegten Befehls und der per SPI eingelesenen Daten. Mit der if-Abfrage am Ende
* wird der Inhalt an den entsprechenden Buffer RXB0 oder RXB1 an den MCU übertragen.
*/
Buffer receiveBuffer; // Variable des selbst definierten Typs Buffer
byte befehl; // Variable die den Befehl abhängig vom der gewählten 'bufferNr' aufnimmt
// If Anweisung wählt den entsprechenden Befehl für den gewählten Buffer aus
if (bufferNr == 0) {
befehl = B10010000; // Start Auslesen bei Register RXB0SIDH
} else {
befehl = B10010100; // Start Auslesen bei Register RXB1SIDH
}
// Übertragen des Buffers
digitalWrite(CS, LOW); // CS auf LOW ziehen um den Slave auszuwählen
SPI.transfer(befehl); // READ RX BUFFER Befehl senden gemäß Datenblatt
receiveBuffer.SIDH = SPI.transfer(0xff); // Aufgrund Autoincrement werden nachfolgende...
receiveBuffer.SIDL = SPI.transfer(0xff); // ...Register hintereinander ausgelesen.
receiveBuffer.EID8 = SPI.transfer(0xff);
receiveBuffer.EID0 = SPI.transfer(0xff);
receiveBuffer.DLC = SPI.transfer(0xff);
receiveBuffer.D0 = SPI.transfer(0xff);
receiveBuffer.D1 = SPI.transfer(0xff);
receiveBuffer.D2 = SPI.transfer(0xff);
receiveBuffer.D3 = SPI.transfer(0xff);
receiveBuffer.D4 = SPI.transfer(0xff);
receiveBuffer.D5 = SPI.transfer(0xff);
receiveBuffer.D6 = SPI.transfer(0xff);
receiveBuffer.D7 = SPI.transfer(0xff);
digitalWrite(CS, HIGH); // CS wieder freigeben
// if-Abfrage zum Kopieren des receiveBuffers in den entsprechenden RX-Buffer
if (bufferNr == 0) {
RXB0 = receiveBuffer; // receiveBuffer kopieren in RXB0
} else {
RXB1 = receiveBuffer; // receiveBuffer kopieren in RXB1
}
}