Themen:

AVR, avr-gcc, CAN, CPLD, Elektronik, Mikrocontroller, MSP430, PIC, Roboter, Schaltungen, Sensoren, Software, Testboards

Ansteuerung des MCP2515 (Tutorial)

Tags: AVR, Software, CAN, avr-gcc
Stand: 6. Juli 2007, 16:36
40 Kommentar(e)

Ein kleines Tutorial zum Einstieg in die Programmierung des MCP2515 unter avr-gcc

Dies hier soll eine Schritt für Schritt Anleitung werden wie man mit Hilfe eines AVRs (in dem Fall ein ATMega8) und des MCP2515 Nachrichten über den CAN Bus verschickt. Entgegen weitläufiger Meinungen in den Roboterforen ist dies ganz einfach zu realisieren. Es kann natürlich keine vollständige Beschreibung werden, da der MCP2515 doch eine ganze Menge an Funktionalität bietet. Ich hoffe aber das diese Anleitung trotzdem einen Einstieg in das Thema liefert.

Alle die noch nicht so richtig wissen was ein CAN Bus eigentlich ist bzw. was man damit machen kann sollten sich erstmal zum Beispiel bei Wikipedia oder im CAN-Wiki umschauen.

Doch zurück zum MCP2515. Das Tutorial ist in mehrere Bereiche gegliedert:

  1. Lesen bzw. schreiben der Register
  2. Initialisieren des MCP2515
  3. Senden von CAN Nachrichten
  4. Empfangen von CAN Nachrichten

Die Programmteile sind auf das CAN-Testboard ausgelegt, sollten sich aber auch ohne große Änderungen für andere Boards anpassen lassen.

Der MCP2515 wird per SPI angesteuert. Da der AVR schon von Haus aus ein Hardware SPI Interface mitbringt erleichtert dies die Ansteuerung enorm. Um das SPI Interface nutzen zu können muss man es als erstes initialisieren:

C:
void spi_init(void)
{
    // Aktivieren der Pins für das SPI Interface
    DDR_SPI  |= (1<<P_SCK)|(1<<P_MOSI);
    PORT_SPI &= ~((1<<P_SCK)|(1<<P_MOSI)|(1<<P_MISO));
   
    DDR_CS   |= (1<<P_CS);
    PORT_CS  |= (1<<P_CS);
   
    // Aktivieren des SPI Master Interfaces, fosc = fclk / 2
    SPCR = (1<<SPE)|(1<<MSTR);
    SPSR = (1<<SPI2X);
}

Das SPI Interface wird dadurch in den Master Modus geschaltet. Man kann für das SPI Interface ruhig die maximale Geschwindigkeit (fosc / 2 = 8 MHz Clk-Takt bei 16 MHz Quarz takt) wählen, da der MCP2515 bis zu 10 MHz SPI Takt verarbeiten kann. Bei schlechter Verlegung der SPI Leitungen kann es dabei allerdings zu Problemen kommen, in diesem Fall muss die Geschwindigkeit heruntergesetzt werden. Auf meinem CAN Testboard läuft der SPI Bus mit ca. 3,7 MHz (aufgrund der 7,3728 MHz Taktfrequenz des AVRs) einwandfrei.

Bei der Initialisierung muss man beachten, dass man auch die entsprechenden Pins als Ausgang (SCK, MOSI, \CS) bzw. Eingang (MISO) schaltet. Im Gegensatz zu vielen anderen Schnittstellen der AVRs (UART, TWI usw.) wird dies hier nicht alles automatisch von Chip selbst erledigt. Um den Code auch für andere AVRs anpassen zu können habe ich die entsprechenden Pins über #defines festgelegt. Somit müssen sie nur an einer Stelle geändert werden wenn man zum Beispiel einmal den \CS Pin umdefinieren will (die anderen drei Pins sind für den jeweiligen AVR spezifisch und können nicht geändert werden, es sei denn man programmiert das SPI Interface selber).

C:
#define DDR_CS      DDRB
#define PORT_CS     PORTB
#define P_CS        2

#define DDR_SPI     DDRB
#define PORT_SPI    PORTB
#define P_MISO      4
#define P_MOSI      3
#define P_SCK       5

Um für die SPI Kommandos bzw. die MCP2515 Registeradressen nicht immer im Datenblatt nachschauen zu müssen habe ich zudem eine Datei mit den wichtigsten #defines dazu angelegt. So kann man dann einfach die Registernamen bzw. Bit namen in Quellcode verwenden.

Die entsprechenden Datei kann hier heruntergeladen werden.

Ab jetzt kann man ganz einfach Bytes über den SPI Bus schieben:

C:
uint8_t spi_putc( uint8_t data )
{
    // Sendet ein Byte
    SPDR = data;
   
    // Wartet bis Byte gesendet wurde
    while( !( SPSR & (1<<SPIF) ) )
        ;
   
    return SPDR;
}

Da beim SPI Bus das Senden um Empfangen auf zwei verschiedenen Leitungen läuft, kann gleichzeitig gesendet und empfangen werden. Um ein Byte zu empfangen muss man also ein Dummybyte beliebigen Inhalts auf den Bus legen und dabei die Daten auf der Empfangsleitung auswerten.

C:
// Daten per SPI senden
spi_putc(0xaa);

// Daten per SPI lesen
data = spi_putc(0xff);

Ab jetzt kann man auch einmal mit dem MCP2515 kommunizieren. Die Kommunikation läuft dabei immer nach dem gleichen Schema ab:

Die Funktionen zum Beschreiben bzw. Lesen von Registern sehen daher folgendermaßen aus:

Registerwerte schreiben

C:
void mcp2515_write_register( uint8_t adress, uint8_t data )
{
    // /CS des MCP2515 auf Low ziehen
    PORT_CS &= ~(1<<P_CS);
   
    spi_putc(SPI_WRITE);
    spi_putc(adress);
    spi_putc(data);
   
    // /CS Leitung wieder freigeben
    PORT_CS |= (1<<P_CS);
}

Registerwerte lesen

C:
uint8_t mcp2515_read_register(uint8_t adress)
{
    uint8_t data;
   
    // /CS des MCP2515 auf Low ziehen
    PORT_CS &= ~(1<<P_CS);
   
    spi_putc(SPI_READ);
    spi_putc(adress);
   
    data = spi_putc(0xff)
   
    // /CS Leitung wieder freigeben
    PORT_CS |= (1<<P_CS);
   
    return data;
}

Eine weiteres nützliche Feature des MCP2515 ist die Bit-Modify-Funktion. Dabei werden nur bestimmt Bits gesetzt bzw. gelöscht. Ein Bespiel dafür wird man unter anderem bei der Initialisierung sehen, wo damit die Operationsmodi umgeschaltet werden:

Einzelne Bits setzen/löschen

C:
void mcp2515_bit_modify(uint8_t adress, uint8_t mask, uint8_t data)
{
    // /CS des MCP2515 auf Low ziehen
    PORT_CS &= ~(1<<P_CS);
   
    spi_putc(SPI_BIT_MODIFY);
    spi_putc(adress);
    spi_putc(mask);
    spi_putc(data);
   
    // /CS Leitung wieder freigeben
    PORT_CS |= (1<<P_CS);
}

Allerdings muss man aufpassen da sich diese Funktion nur auf bestimmte Register anwenden lässt (siehe z.B. Datenblatt auf Seite 59).
Es wird dabei zuerst eine Bitmaske gesendet. Nur die Bits die dabei gesetzt sind werden später auch verändert. Danach werden die eigentlichen Daten gesendet. Ist ein Bit in der Maske gesetzt und bei den Daten gelöscht, so wird auch das entsprechende Bit im Register des MCP2515 gelöscht. Analog wenn das enstprechende Bit im Datenbyte gesetzt ist.

Jetzt kann man daran gehen den MCP2515 zum Senden bzw. Empfangen von CAN Nachrichten zu initialisieren:

Das einzig schwierige bei der Initialisierung ist das Einstellen des Bit-Timings bzw. der Bit Rate des CAN Buses.

Um die verschieden Konfigurationsregister des MCP2515 beschreiben zu können muss man ihn erstmal in den Configuration Mode versetzen. Nur in diesem Modus sind die Register CNF1, CNF2, CNF3, TXRTSCTRL und die Register für die Filter bzw. Masken beschreibbar.

Es gibt noch einige andere Modi auf die ich an dieser Stelle nicht weiter eingehen werden. Das Datenblatt erklärt es aber eigentlich ganz gut ;-)

Einstellen des Bit Timings

Da das Thema doch recht komplex ist gibt es an dieser Stelle erstmal nur ein paar Links zu selbst weiterlesen bzw. fertige Registerwerte für verschiedene Bitraten.

Die Bitrate wird über die drei Register CNF1 bis CNF3 eingestellt:

C:
// CAN Bitrate 125 kbps
#define R_CNF1  (1<<BRP2)|(1<<BRP1)|(1<<BRP0)
#define R_CNF2  (1<<BTLMODE)|(1<<PHSEG11)
#define R_CNF3  (1<<PHSEG21)

// CAN Bitrate 250 kbps
#define R_CNF1  (1<<BRP1)|(1<<BRP0)
#define R_CNF2  (1<<BTLMODE)|(1<<PHSEG11)
#define R_CNF3  (1<<PHSEG21)

// CAN Bitrate 500 kbps
#define R_CNF1  (1<<BRP0)
#define R_CNF2  (1<<BTLMODE)|(1<<PHSEG11)   
#define R_CNF3  (1<<PHSEG21)

// CAN Bitrate 1 Mbps
#define R_CNF1  0
#define R_CNF2  (1<<BTLMODE)|(1<<PHSEG11)
#define R_CNF3  (1<<PHSEG21)

Allen die wissen wollen wie man auf diese Werte kommt kann ich das Datenblatt (ab Seite 37) bzw. folgende Links empfehlen:

[http://www.intrepidsupport.com/mbtime.htm][Bit Timing Calculator]
[http://www.kvaser.com/index.htm][http://www.kvaser.com/index.htm]

Insbesondere der Microchip CAN Bit Timing Calculator (erster Link) ist sehr gut geeignet um die Berechungen einmal grafisch darzustellen und verschiedene Einstellungen auszuprobieren. Interrupts

Es gibt acht verschiedene Interruptquellen:

  1. Message Error Interrupt
  2. Wakeup Interrupt
  3. Error Interrupt Enable (multiple sources in EFLG register)
  4. Transmit Buffer 2 Empty Interrupt
  5. Transmit Buffer 1 Empty Interrupt
  6. Transmit Buffer 0 Empty Interrupt
  7. Receive Buffer 1 Full Interrupt
  8. Receive Buffer 0 Full Interrupt

Aktiviert man einen dieser Interrupts, so wird jedesmal die Interruptleitung auf Low gezogen wenn die Bedinung zutrifft. Die Leitunge bleibt dann so lange auf Low Pegel bis alle Interruptbedinungen aufgelöst wurden.

Bei meiner Software habe ich nur die beiden Puffer-Voll-Interrupts genutzt, um anzeigen zu lassen das eine neue Nachricht eingetroffen ist. Natürlich gibt es aber noch viel mehr Möglichkeiten die Interupts zu nutzen, im Datenblatt auf Seite 49 findt sich die genaue Beschreibung zum Weiterlesen.

Um die Interrupts zu aktivieren muss man nur das entsprechende Bit im CANINTE Register setzen:

C:
// Aktivieren der Rx Buffer Interrupts
mcp2515_write_register(CANINTE, (1<<RX1IE)|(1<<RX0IE));

Pin Funktionen

Der MCP2515 bietet auch noch fünf Pins die auf verschiedene Arten genutzt werden können. Die beiden Pins RX0BF und. RX1BF können entweder als Status Leitungen für den “Puffer voll” Interrupt fungieren oder aber von Anwender als Digitale Ausgänge genutzt werden. So könnte man zum Beispiel noch zwei zusätzliche Statusleds anschließen ohne dafür I/Os des AVRs zu benutzten.

Die Verwendung der Pins wird über das Register BFPCTRL gesteuert. Da auf diese Register die Bit-Modify-Funktion angewendet werden darf kann man darüber sehr leicht diese Pins steuern.

C:
// RX0BF als Digitalen Ausgang nutzen
mcp2515_bit_modify( BFPCTRL, (1<<B0BFE)|(1<<B0BFM), (1<<B0BFE));

// Pin setzen
mcp2515_bit_modify( BFPCTRL, (1<<B0BFE), (1<<B0BFE));

Die Ansteuerung erfolgt analog für RX1BF. Aber natürlich kann man die Pins auch als Interruptausgänge verwenden:

C:
// RX0BF und RX1BF als Interruptausgänge nutzen
mcp2515_write_register( BFPCTRL, (1<<B1BFE)|(1<<B0BFE)|(1<<B1BFM)|(1<<B0BFM));

Genauso kann man auch die TXnRTS Pins entweder als Digitale Eingänge verschalten oder damit das Senden der Nachricht in dem entsprechenden Puffers anstoßen. Dabei erfolgt das aktivieren der jeweiligen Funktion über das Register TXRTSCTRL (siehe Datenblatt Seite 19). Die Ansteuerung erfolgt im Prinzip genauso wie bei den RXnBF Pins.

CLKOUT Pin

Der MCP2515 bietet die Möglichkeit andere Bausteine mit einem Taktsignal zu versorgen. Über den CLKOUT Pin wird dann das Taktsignal des MCP2515 ausgegeben,um so zum Beispiel einen Mikrocontroller mit einem Takt zu versorgen. Es lässt sich außerdem noch ein interner Vorteiler aktivieren, der das Taktsignal nocheinmal durch zwei, vier oder acht teilt und so nochmal verschiedene Taktfrequenzen ermöglicht. Bei 16 MHz Takt stehen einem so 16 MHz, 8 MHz, 4 MHz und 2 MHz Takt zu Verfügung.

Der CLKOUT Pin wird über die letzten drei Bits im CANCTRL Register gesteuert. Da standardmäßig beim an legen einer Spannung an den MCP2515 der CLKOUT Pin aktiviert ist kann man damit durchaus den AVR, der den MCP2515 steuert, betreiben.

C:
// Vorteiler des CLKOUT Pin auf null setzen,
// => Taktfrequenz des MCP2515 am CLKOUT Pin ausgeben
mcp2515_bit_modify( CANCTRL, 0x07, (1<<CLKEN));

Ein bißchen muss man allerdings aufpassen, dass man wenn man dies nutzten will, nicht die beiden Resetleitungen von AVR und MCP2515 verbindet, da man ansonsten ein Problem bei dem Programmieren des AVRs bekommt. Beim Programmieren würde nämlich die Resetleitung des AVRs und damit auch des MCP2515s auf Low gezogen werden, damit würde auch der MCP2515 in den Resetmodus gezogen und dadurch den CLKOUT Pin deaktivieren. Allerdings ist es nicht möglich einen AVR zu programmieren wenn er kein Taktsignal hat. Das einzige was dann noch hilft ist die Resetleitung zu trennen und dem MCP2515 einen eigenen Pullup zur Versorgungsspannung zu spendieren. Allerdings sollte man dann immer in der Initialisierungsroutine den MCP2515 Reseten, damit er einen definierten Anfangszustand hat.

Akzeptanz Filter und Masken

Wie schon auf der Anfangsseite erwähnt ist eine der großen Stärken des CAN Buses (bzw. genauer gesagt der CAN Controller), dass man auf verschiedene Nachrichten Filtern kann. Der MCP2515 bietet zwei Filter für Buffer 0 und nochmal vier Stück für Buffer 1. So lassen sich Nachrichten die nicht für den Knoten bestimmt sind schon von vorne herein herausfiltern ohne das der AVR darauf reagieren muss.

Zu den Filtern findet man folgende Tabelle im Datenblatt:

Mask Bit Filter Bit Message Identifier bit  
0 x x Accept
1 0 0 Accept
1 0 1 Reject
1 1 0 Reject
1 1 1 Accept

Die einzelnen Bits des Identifiers werden also nur zur Filterung herangezogen, wenn man das entsprechende Bit in der Maske setzt. Will man also (z.B. zu Testzwecken) alle Nachrichten empfangen, so sollte man einfach alle Maskenbits auf Null setzten. Damit werden alle Nachrichten, unabhängig von ihrer ID in den Empfangspuffer weitergeleitet.

Man sollte allerdings beachten das die Filter bzw. Masken können nur im Configuration Mode gesetzt werden.

Und jetzt auch einmal ein vollständiger Code zur Initialisierung:

C:
void mcp2515_init(void)
{
    // SPI Interface initialisieren
    spi_init();
   
    // MCP2515 per Software Reset zuruecksetzten,
    // danach ist der MCP2515 im Configuration Mode
    PORTB &= ~(1<<SPI_CS);
    spi_putc( SPI_RESET );
    PORTB |= (1<<SPI_CS);
   
    /*
     *  Einstellen des Bit Timings
     * 
     *  Fosc       = 16MHz
     *  BRP        = 7                (teilen durch 8)
     *  TQ = 2 * (BRP + 1) / Fosc  (=> 1 uS)
     * 
     *  Sync Seg   = 1TQ
     *  Prop Seg   = (PRSEG + 1) * TQ  = 1 TQ
     *  Phase Seg1 = (PHSEG1 + 1) * TQ = 3 TQ
     *  Phase Seg2 = (PHSEG2 + 1) * TQ = 3 TQ
     * 
     *  Bus speed  = 1 / (Total # of TQ) * TQ
     *             = 1 / 8 * TQ = 125 kHz
     */

   
    // BRP = 7
    mcp2515_write_register( CNF1, (1<<BRP0)|(1<<BRP1)|(1<<BRP2) );
   
    // Prop Seg und Phase Seg1 einstellen
    mcp2515_write_register( CNF2, (1<<BTLMODE)|(1<<PHSEG11) );
   
    // Wake-up Filter deaktivieren, Phase Seg2 einstellen
    mcp2515_write_register( CNF3, (1<<PHSEG21) );
   
    // Aktivieren der Rx Buffer Interrupts
    mcp2515_write_register( CANINTE, (1<<RX1IE)|(1<<RX0IE) );
   
    /*
     *  Einstellen der Filter
     */

   
    // Buffer 0 : Empfangen aller Nachrichten
    mcp2515_write_register( RXB0CTRL, (1<<RXM1)|(1<<RXM0) );
   
    // Buffer 1 : Empfangen aller Nachrichten
    mcp2515_write_register( RXB1CTRL, (1<<RXM1)|(1<<RXM0) );
   
    // Alle Bits der Empfangsmaske loeschen,
    // damit werden alle Nachrichten empfangen
    mcp2515_write_register( RXM0SIDH, 0 );
    mcp2515_write_register( RXM0SIDL, 0 );
    mcp2515_write_register( RXM0EID8, 0 );
    mcp2515_write_register( RXM0EID0, 0 );
   
    mcp2515_write_register( RXM1SIDH, 0 );
    mcp2515_write_register( RXM1SIDL, 0 );
    mcp2515_write_register( RXM1EID8, 0 );
    mcp2515_write_register( RXM1EID0, 0 );
   
    /*
     *  Einstellen der Pin Funktionen
     */

   
    // Deaktivieren der Pins RXnBF Pins (High Impedance State)
    mcp2515_write_register( BFPCTRL, 0 );
   
    // TXnRTS Bits als Inputs schalten
    mcp2515_write_register( TXRTSCTRL, 0 );
   
    // Device zurueck in den normalen Modus versetzten
    mcp2515_bit_modify( CANCTRL, 0xE0, 0);
}

Man kann das Einstellen der Register auch noch wesentlich eleganter lösen wenn man die Registerwerte im Flash des AVRs ablegt und von dort aus lädt (siehe letztes Kapitel).

Nun fehlt nicht mehr viel bis wir die erste Nachricht über den CAN Bus senden können. Ich werde in diesem Kapitel allerdings nur auf das verschicken von Nachrichten mit Standart IDs (also 11-Bit Länge) eingehen. Wer das möchte kann den Code aber sicherlich leicht auf Extendet IDs mit 29-Bit erweitern.

Der MCP2515 hat drei unabhängige Sendepuffer mit einstellbarer Priorität. Um eine Nachricht zu verschicken muss man nur noch die entsprechenden Register mit der ID und den zu senden Daten füllen und die Nachricht abzuschicken.

Für das losschicken der Nachricht gibt es drei verschiedene Möglichkeiten:

Wenn man am Mikrocontroller nur noch wenig freie Pins hat lohnt es sich natürlich an das Ganze per SPI zu machen. Man spart so eine bis drei Leitungen (je nachdem wieviele Puffer man nutzt), ohne das das verschicken wesentlich länger dauert.

Das verschicken einer Nachricht könnte also folgendermaßen aussehen:

C:
/* Verschickt eine Nachricht ueber Puffer 0
 * 2 Datenbytes (0x04, 0xf3)
 * Standard ID: 0x0123
 */

uint16_t id = 0x0123;

/* Nachrichten Puffer auf Hoechste Prioritaet einstellen
   (braucht man nicht wenn man nur mit einem Puffer sendet, siehe Text) */

mcp2515_bit_modify( TXB0CTRL, (1<<TXP1)|(1<<TXP0), (1<<TXP1)|(1<<TXP0) );

// ID einstellen
mcp2515_write_register(TXB0SIDH, (uint8_t) (id>>3));
mcp2515_write_register(TXB0SIDL, (uint8_t) (id<<5));

// Nachrichten Laenge + RTR einstellen
mcp2515_write_register(TXB0DLC, 2);

// Daten
mcp2515_write_register(TXB0D0, 0x04);
mcp2515_write_register(TXB0D1, 0xf3);

// CAN Nachricht verschicken
PORT_CS &= ~(1<<P_CS);
spi_putc(SPI_RTS | 0x01);
PORT_CS |= (1<<P_CS);

Senden mit einem Puffer

Allerdings ist es so noch nicht so sehr komfortabel jedesmal extra die Register ausfüllen zu müssen. Wir brauchen also eine entsprechende Funktion dafür die genau das tut. Um die Nachrichten besser zwischen den Funktionen austauschen zu können bietet es sich an diese in ein struct zu verpacken.

Dieses struct kann man jetzt sehr einfach an die Sendefunktion übergeben:

C:
typedef struct
{
    uint16_t  id;
    uint8_t   rtr;
    uint8_t   length;
    uint8_t   data[8];
} CANMessage;

// Neue Nachricht erzeugen
CANMessage message;

// Daten eintragen
message.id = 0x0123;
message.rtr = 0;
message.length = 2;
message.data[0] = 0x04;
message.data[1] = 0xf3;

// Nachricht verschicken
can_send_message(&message);

Und die Sendefunktion dazu:

C:
void can_send_message(CANMessage *p_message)
{
    uint8_t length = p_message->length;
   
    // ID einstellen
    mcp2515_write_register(TXB0SIDH, (uint8_t) (p_message->id>>3));
    mcp2515_write_register(TXB0SIDL, (uint8_t) (p_message->id<<5));
   
    // Ist die Nachricht ein "Remote Transmit Request"
    if (p_message->rtr)
    {
        /* Eine RTR Nachricht hat zwar eine Laenge,
           aber keine Daten */

       
        // Nachrichten Laenge + RTR einstellen
        mcp2515_write_register(TXB0DLC, (1<<RTR) | length);
    }
    else
    {
        // Nachrichten Laenge einstellen
        mcp2515_write_register(TXB0DLC, length);
       
        // Daten
        for (uint8_t i=0;i<length;i++) {
            mcp2515_write_register(TXB0D0 + i, p_message->data[i]);
        }
    }
   
    // CAN Nachricht verschicken
    PORT_CS &= ~(1<<P_CS);
    spi_putc(SPI_RTS | 0x01);
    PORT_CS |= (1<<P_CS);
}

Natürlich kann man diese Funktionen jetzt noch so erweitern, dass man immer den ersten freien Puffer zum senden verwendet in dem man das TXREQ Bit in den TXBnCTRL Register auswertet und dem entsprechend einen Puffer wählt. Aber es geht auch einfacher, da es dafür ein extra SPI Kommando (Read Status Instruction) gibt:

Senden mit allen drei Puffern

Im Gegensatz zum ersten Beispiel wollen wir diesmal allerdings nicht die vorgefertigen Funktionen zum setzen der Register verwenden, sondern die Übertragung etwas beschleunigen in dem man die Daten direkt auf den SPI Bus legt und ausnutzt, dass man bei MCP2515 auch einfach mehrere Bytes hintereinander senden kann. Diese werden dann in den darauffolgenden Registern abgelegt.

C:
uint8_t can_send_message(CANMessage *p_message)
{
    uint8_t status, address;
   
    // Status des MCP2515 auslesen
    PORT_CS &= ~(1<<P_CS);
    spi_putc(SPI_READ_STATUS);
    status = spi_putc(0xff);
    spi_putc(0xff);
    PORT_CS |= (1<<P_CS);
   
    /* Statusbyte:
     *
     * Bit  Funktion
     *  2   TXB0CNTRL.TXREQ
     *  4   TXB1CNTRL.TXREQ
     *  6   TXB2CNTRL.TXREQ
     */

   
    if (bit_is_clear(status, 2)) {
        address = 0x00;
    }
    else if (bit_is_clear(status, 4)) {
        address = 0x02;
    }
    else if (bit_is_clear(status, 6)) {
        address = 0x04;
    }
    else {
        /* Alle Puffer sind belegt,
           Nachricht kann nicht verschickt werden */

        return 0;
    }
   
    PORT_CS &= ~(1<<P_CS);    // CS Low
    spi_putc(SPI_WRITE_TX | address);
   
    // Standard ID einstellen
    spi_putc((uint8_t) (p_message->id>>3));
    spi_putc((uint8_t) (p_message->id<<5));
   
    // Extended ID
    spi_putc(0x00);
    spi_putc(0x00);
   
    uint8_t length = p_message->length;
   
    if (length > 8) {
        length = 8;
    }
   
    // Ist die Nachricht ein "Remote Transmit Request" ?
    if (p_message->rtr)
    {
        /* Ein RTR hat zwar eine Laenge,
           aber enthaelt keine Daten */

       
        // Nachrichten Laenge + RTR einstellen
        spi_putc((1<<RTR) | length);
    }
    else
    {
        // Nachrichten Laenge einstellen
        spi_putc(length);
       
        // Daten
        for (uint8_t i=0;i<length;i++) {
            spi_putc(p_message->data[i]);
        }
    }
    PORT_CS |= (1<<P_CS);      // CS auf High
   
    asm volatile ("nop");
   
    /* CAN Nachricht verschicken
       die letzten drei Bit im RTS Kommando geben an welcher
       Puffer gesendet werden soll */

    PORT_CS &= ~(1<<P_CS);    // CS wieder Low
    if (address == 0x00) {
        spi_putc(SPI_RTS | 0x01);
    } else {
        spi_putc(SPI_RTS | address);
    }
    PORT_CS |= (1<<P_CS);      // CS auf High
   
    return 1;
}

Theoretisch könnte es damit allerdings vorkommen, dass wenn man viele Nachrichten hintereinander sendet eine Nachricht in einem der hinteren Puffer “liegen bleibt”, da der erste Puffer bei gleicher eingesteller Priorität immer zuerst gesendet wird. Um das zu verhindern müsste man immer den Puffer mit dem man die aktuelle Nachricht senden will auf die niedrigste Priörität setzen und die Priorität der anderen beiden Puffer um eins erhöhen. So würde dann die Nachrichten auf jeden Fall auch in der Reihenfolge gesendet in der sie auch eingetragen wurden.

Um überprüfen zu können ob auch was passiert muss man seine Nachrichten natürlich auch wieder empfangen können.

Nachrichten empfangen

Um festzustellen ob eine neue Nachricht empfangen wurde gibt es je nach Konfiguration des MCP2515 mehrere Möglichkeiten:

Natürlich lassen sich diese Möglichkeiten auch noch untereinander kombinieren. Ein ganz sinnvolle Möglichkeit ist zum Beispiel nur die Interruptleitung zum AVR zu führen und sich darüber anzeigen lassen ob eine neue Nachricht vorliegt und nur dann per SPI Kommando abzufragen in welchem Puffer die Nachricht liegt. Da es so auch auf dem CAN Testboard realisiert wurde will ich auf diese Möglichkeit noch einmal genauer eingehen:

Um den Interruptpin nutzten zu können um sich neue Nachrichten anzeigen zu lassen muss man ihn natürlich erstmal entsprechend einstellen. Da wir dies aber schon bei der Initialisierung getan haben (siehe Unterpunkt Interrupts) können wir jetzt einfach drauf warten bis der Interruptpin auf low-Pegel geht und uns anzeigt das wir eine Nachricht auslesen können.

Um festzustellen in welchem Puffer die Nachricht liegt bzw. was es für ein Typ ist (Standard ID, Extended ID, Remote Transfer Request Frame usw.) stellt der MCP2515 ein extra SPI Kommando bereit (Alternativ kann man auch das CANINTF Register auswerten):

C:
uint8_t mcp2515_read_rx_status(void)
{
    uint8_t data;
   
    // /CS des MCP2515 auf Low ziehen
    PORT_CS &= ~(1<<P_CS);
   
    spi_putc(SPI_RX_STATUS);
    data = spi_putc(0xff);
   
    // Die Daten werden noch einmal wiederholt gesendet,
    // man braucht also nur eins der beiden Bytes auswerten.
    spi_putc(0xff);
   
    // /CS Leitung wieder freigeben
    PORT_CS |= (1<<P_CS);
   
    return data;
}

Wir überprüfen also zuerst ob der Interruptpin auf Low Pegel liegt. Ist dies der Fall, so wurde eine neue Nachricht empfangen.

C:
uint8_t can_get_message(CANMessage *p_message)
{
    // Status auslesen
    uint8_t status = mcp2515_read_rx_status();

    if (bit_is_set(status,6))
    {
        // Nachricht in Puffer 0
       
        PORT_CS &= ~(1<<P_CS);    // CS Low
        spi_putc(SPI_READ_RX);
    }
    else if (bit_is_set(status,7))
    {
        // Nachricht in Puffer 1
       
        PORT_CS &= ~(1<<P_CS);    // CS Low
        spi_putc(SPI_READ_RX | 0x04);
    }
    else {
        /* Fehler: Keine neue Nachricht vorhanden */
        return 0xff;
    }
   
    // Standard ID auslesen
    p_message->id =  (uint16_t) spi_putc(0xff) << 3;
    p_message->id |= (uint16_t) spi_putc(0xff) >> 5;
   
    spi_putc(0xff);
    spi_putc(0xff);
   
    // Laenge auslesen
    uint8_t length = spi_putc(0xff) & 0x0f;
    p_message->length = length;
   
    // Daten auslesen
    for (uint8_t i=0;i<length;i++) {
        p_message->data[i] = spi_putc(0xff);
    }
   
    PORT_CS |= (1<<P_CS);
   
    if (bit_is_set(status,3)) {
        p_message->rtr = 1;
    } else {
        p_message->rtr = 0;
    }
   
    // Interrupt Flag loeschen
    if (bit_is_set(status,6)) {
        mcp2515_bit_modify(CANINTF, (1<<RX0IF), 0);
    } else {
        mcp2515_bit_modify(CANINTF, (1<<RX1IF), 0);
    }
   
    return (status & 0x07);
}

Die Empfangsroutine holt sich dann über das oben beschrieben SPI Kommando die Informationen über den Typ der Nachricht und den Puffer in den die Nachricht liegt. Danach müssen die Daten nur noch aus dem entsprechenden Registern gelesen werden. Auch hier bleiben Frames mit Extended ID erstmal außen vor.

Die Funktion gibt die Filternummer des Filters der die Nachricht durchgelassen hat zurück. Wenn keine neue Nachricht vorliegt gibt die Funktion 0xff als Fehlermeldung zurück.

Damit können wir jetzt Nachrichten senden und empfangen und können an für sich anfangen die Software für das eigentliche Problem zu schreiben :-)

UPDATE: Unter Downloads gibt es ein kleines Demoprogram für das CAN-Testboard. Wenn man einen anderen Quarz verwendet muss man dies im Makefile anpassen, andere Pins für die /CS und INT Leitung kann man in defaults.h einstellen. Die Werte für P_MOSI, P_MISO und P_SCK müssen allerdings mit den Pins für das Hardware SPI übereinstimmen.
Das Programm sollte sich ohne Problem auch für andere AVRs compilieren lassen!

Es initialisiert den MCP2515, schaltet ihn in den Loopback Modus, sendet eine Nachricht (nur intern), empfängt die gesendete Nachricht, schaltet zurück in den normalen Modus, versendet die Nachricht nochmal per CAN und wartet dann auf ankommende Nachrichten.

Bibliothek

Es gibt mittlerweile eine CAN-Bibliothek die auf den hier vorgestellten Code-Fragmenten aufbaut.

Downloads:

mcp2515_demo.zip [17.49 kB]

Zum Anfang

Kommentare

# Manfred Feitzinger meinte am 15. November 2006, 11:46 dazu:

Hallo Schön und gut. Ich kenn mich mit GCC nicht aus. Hast Du das eventuell auch in Bascom, oder kannst Du mir verraten wie Ich die Register beschreiben kann.

Danke Manfred

# Fabian Greif meinte am 15. November 2006, 20:52 dazu:

Im Prinzip sollte es nicht so schwer sein den Code für Bascom umzuschreiben. Man müsste spi_init() und spi_putc() durch die entsprechenden Libraryfunktion ersetzen, das setzen des CS Pins entsprechend abändern und könnte ansonsten den C-Code relativ direkt übernehmen.
Da ich Bascom nicht verwende kann ich das leider auch nicht ohne weiteres ausprobieren.

Hier hat wohl jemand schon ein bißchen Code dafür geschrieben.

# Frank meinte am 2. Dezember 2006, 13:42 dazu:

Hallo Gibt es zu dem Tutorial ein komplettes Beispiel Programm zu ansteuern des MCP2515? Oder habe ich den Download Link übersehen? Übrigens gute Arbeit! Gruss Frank

# Ralf Handrich meinte am 11. Dezember 2006, 15:37 dazu:

Hallo,

auch von mir erstmal großes Lob. Die Schaltung und die Software sind echt Spitze.

Ich hab 2 Module nachgebaut. Der CAN-Bus funktioniert einwandfrei. Allerdings hab ich ein kleines Problem beim Reset der Module. Jedesmal wenn ich einen Hardware-Reset ausführe (bzw. beim Einschalten der Spannungsversorgung der Module) werden 2 bzw. 3 Nachrichten auf den Bus gelegt, ohne das ich explizit etwas sende. Ich habe dabei festgestellt, das die Datenbytes identisch sind mit der letzten Nachricht, die vor dem Reset versendet wurde. Die ID?s scheinen willkürlich zu sein. Wo liegt der Fehler? Was kann ich dagegen machen?

Gruß Ralf

# Maxx meinte am 30. Dezember 2006, 00:21 dazu:

Hallo zusammen! Erstmal danke für das geniale Tutorial!

Bin beim Compilieren über ein paar Sachen drübergefallen. Wo ist z.B. SPI_RESET oder SPI_CS definiert? Möchte mein Projekt mit dem avr-gcc kompilieren. Hat zufällig jemand ein lauffähiges Beispielprogramm mit Makefile, welches er zur Verfügung stellt?

Danke!

Gruß Maxx

# Volker Bosch meinte am 4. Januar 2007, 12:06 dazu:

Hallo Fabian,

ich möchte dir herzlich für das Tutorial zur Ansteuerung des MCP2515 danken — es hat mir den Einstieg erheblich erleichtert.

Drei Anmerkungen habe ich zu Deinem Text:

  1. Du schreibst, der MCP2515 würde das Status-Byte grundsätzlich zwei Mal senden. So wie ich das Datenblatt interpretiere, sendet er das Statusbyte beliebig oft, d.h., Du könntest m.E. auf das zweite Lesen Statusbytes verzichten.

  2. Das letzte Code-Beispiel verstehe ich nicht, das ist doch die bereits vorgestellte Routine zum Senden einer Nachricht über den ersten freien der drei Sendepuffer?

  3. Lt. Datenblatt reagiert der AVR Mega 16 sehr sensibel auf Veränderungen der Taktfrequenz. Atmel empfiehlt während des Umschaltens den Controller im Reset zu halten. Ich konnte zwar keine Probleme beobachten, löse aber nun einen Watchdog-Reset beim Umschalten des Taktteilers aus.

Gruß, Volker.

# Björn meinte am 5. Januar 2007, 19:52 dazu:

Hi,

dein link zu den #defines funktioniert auf der neuen seite nicht mehr!

MFG

Björn

# Fabian Greif meinte am 8. Januar 2007, 23:08 dazu:

Ist korrigiert.

# Manfred Feitzinger meinte am 23. Februar 2007, 08:57 dazu:

Hallo Fabian Ich habe versucht Dein Programm in Bascom zu erstellen. Ich bin der Meinung, dass die Komunikatin zum MCP2515 steht. Aber wenn ich zum beispiel die Register nach der Initalisierung auslese bekomme ich andere Werte als ich reingeschrieben habe. zB ins Register DCF1 schreibe ich 02 (HEX) und bekomme 06 zurück. An was kann das liegen ?

# Fabian Greif meinte am 23. Februar 2007, 16:50 dazu:

Zum dem Problem das der Wert falsch ist würde mir spontan ein zu schneller SPI Bus einfallen, dann kann es schonmal vorkommen, dass Zeichen falsch übertragen werden.

Wie sieht den deine SPI Übertragung aus? Nicht das der Fehler dort irgendwo liegt.

Btw. welches Register ist eigentlich mit DCF1 gemeint?

# Manfred Feitzinger meinte am 26. Februar 2007, 07:53 dazu:

Hallo Fabian Ich habe mich verschrieben Ich meinte das Register CNF1 Ich habe dabei versucht eine Übertragungsrate von 50kByte/sek einzustellen. Der MCP2515 hat einen 16MHz Quarz, der ATMega16 Lauft mit dem Internen Oszilator 1MHz

# Stephan meinte am 6. März 2007, 13:05 dazu:

Hallo,

mit interesse habe ich mich durch deine Seite geklickt. Ich möchte mittels des AVR Butterfly den MCP2515 ansteuern. Da ich leider von der Mikrocontroller Programmierung noch nicht viel Ahnung habe meine Frage: Lässt sich dein Code auch für den Schmetterling verwenden?

Gruß Stephan

# Fabian Greif meinte am 6. März 2007, 13:24 dazu:

Ich kenne mich mit dem AVR Butterfly zwar nicht aus, aber solange er einen normalen AVR Kern (idealerweise mit Hardware SPI) und mindestens 1 bis 2 kByte Flashspeicher hat sollte es überhaupt kein Problem sein den Code dafür anzupassen.

# Frank Lorenzen meinte am 24. Mai 2007, 21:34 dazu:

Hallo, ich hätte da mal eine Frage zur Funktion mcp2515_init. Am Ende setzt du die Werte von CANCTRL zurück: mcp2515_bit_modify( CANCTRL, 0xE0, 0);

Die Werte wollen mir so gar keinen Sinn machen. Laut Datenblatt ist der Def.Wert von CANCTRL 11100000. Sollte man da nicht eher “0xE0, 0xE0” setzen oder meinetwegen auch “0xE0, 0xFF”, ich denke du weißt auf was ich hinauswill. Könntest du das bitte mal erläutern?

Gruß frank

# Fabian Greif meinte am 25. Mai 2007, 10:42 dazu:

Ich will den MCP2515 in den “Normal Operation Mode” zurücksetzen. Dazu muss man alle Bits REQOPn löschen. Und genau das erreiche ich mit dem Kommando ;-)

Es stimmt schon, dass REQOP2..0 = 111 der Startwert ist, allerdings sagt das Datenblatt dazu das dieser Zustand ungültig ist und nicht vom Anwender verwendet werden sollte.
(Siehe Datenblatt Seite 56)

# Frank Lorenzen meinte am 25. Mai 2007, 19:54 dazu:

Ah, jetzt ja. Dankeschön.

Ich hatte das Auge eher bei ?Register Map?.

Gruß frank

# Maximilian meinte am 2. Juli 2007, 12:09 dazu:

Hallo Fabian!

Erstmal schöne Beschreibung. Ich denke, dass ich das alles soweit verstanden habe. Da ich bisher nicht viel mehr als über LED ein/aus gekommen bin dachte ich mir ich suche mir mal ein schönes Projekt. Dies wollte ich jetzt mit Can untereinander vernetzen.

Kannst du vielleicht eine kleine Beschreibung geben, Wie man jetzt sagen wir mal 3 Knoten zusammen schließt? Und wie man diese dann Filtern kann? Und wie man dann testen kann, ob eine Nachricht ortnungsgemäß vom AVR verarbeitet worden ist. So LED an/aus ist bestimmt nicht so schwer. Ich brauch nur so eine grobe vorgehensweise! :) Wäre super COOOL! :)

# Nils meinte am 3. Juli 2007, 13:19 dazu:

Hallo,

die Frage nach einem kompletten Beispielprogramm gab es schon mal weiter oben, aber diese Frage hat sich mir auch gestellt.

Kann jemand diesen Beispielcode als komplettes Projekt für WinAVR bereitstellen?

Ich habe noch nie mit WinAVR gearbeitet und möchte gern mit dem Beispielcode den Aufbau des CAN-Testboards testen und einen Einstieg in WinAVR und die CAN Programmierung finden.

Das würde mir sehr weiter helfen! Danke!

# Maximilian meinte am 4. Juli 2007, 15:45 dazu:

Stellt denn das CanTestboard einen Knoten dar? Sprich man halt eine Leitung H und L und schließt dann soviele Knoten an, wie man will? (Ende jeweils mit einem 120 Ohm wiederstand beendet)

# Fabian Greif meinte am 6. Juli 2007, 16:39 dazu:

@Maximilian
Ja, jedes Board würde einen Knoten darstellen. CANH, CANL und GND werden dann einfach bei allen Knoten verbunden und an den beiden Enden mit 120Ohm abgeschloßen.

@Nils
Ein kleines Beispielprogramm findet sich jetzt hier.

# Maximilian meinte am 10. Juli 2007, 11:34 dazu:

AH COOL! :D Hast du ein Spenden Konto. So einen Service müsste man mal unterstützen…

Wie könnte man den folgendes lösen. Denn soweit ich weiß kann man ja nur entweder senden oder empfangen. Folgendes Problem:

Ich habe einen AVR wo ein LCD angeschloßen ist. Der soll über CAN eine Schnittstelle ansprechen, die zum Beispiel “schnelle” Spannungswechsel misst. (Spannungsschwankungen an einem Verstärker im Auto) Wie man vielleicht einen Wert ließt würde ich vielleicht noch hinbekommen, aber wie man es jetzt anstellt das die Schnittstelle so lange die Messwerte sendet, wie es der AVR vom LCD will, kA! :(

# Fabian Greif meinte am 10. Juli 2007, 14:41 dazu:

AH COOL! :D Hast du ein Spenden Konto. So einen Service müsste man mal unterstützen?

Nein bisher noch nicht, aber du kannst du ja mal per Mail mit mir in Verbindung setzen ;-)

Denn soweit ich weiß kann man ja nur entweder senden oder empfangen.

Nein, man kann natürlich sowohl senden als auch empfangen.

Wie man vielleicht einen Wert ließt würde ich vielleicht noch hinbekommen, aber wie man es jetzt anstellt das die Schnittstelle so lange die Messwerte sendet, wie es der AVR vom LCD will, kA! :(

Das ist dann aber eher die Frage des Programms das auf der Schnittstelle läuft. CAN stellt ja nur die Möglichkeiten zum Senden und Empfangen bereit. Was man mit den Pakten dann im Endeffekt macht bleibt einem selbst überlassen.
Dort setzen dann die Schicht-7-Protokolle wie DeviceNet, CANOpen und dergleichen an.

# Maximilian meinte am 10. Juli 2007, 15:43 dazu:

Also ich könnte sagen wir mal umgangssprachlich folgendes machen. Der LCD sendet eine Nachricht an die Schnittstelle (die Temp etc. liesst), wo dann eine spezielle Variable gesetzt wird. Dann fängt die Schnittstelle an so lange zu senden, bis der LCD eine Nachricht an die Schnittstelle sagt, die das ständige Senden unterbindet. Könnte man das so machen?

Diesen Datastream bereich kann ich doch so einteilen wie ich will oder? Sprich wenn ich folgende Daten von der Schnittstelle senden, wie könnte ich das optimal verpacken.

Alles Beispielswerte:

  • Temperatur: 24.5
  • Remoteanschluß: 1 (1 = an; 0= aus)
  • Spannung: 12.5 (springt im ms bereich herum. Spannungsschwankungen eben! :))

Boar das Thema ist voll spannend aber irgendwie kompliziert… :(

# Fabian Greif meinte am 10. Juli 2007, 15:55 dazu:

Ich hab dir eine E-Mail geschrieben.

# Hossam meinte am 31. Juli 2007, 11:34 dazu:

Hallo Fabian;

das Thema interessiert mich sehr, und die Antwort auf die gestellte Frage von Maximilian ist für mich sehr relevant, wäre sehr nett von dir wenn du mir auch die Antwort zu meiner Email schickst.

Danke

# Medalist meinte am 11. August 2007, 01:43 dazu:

Hi,

deine Seite hier ist echt cool, ich habe soweit 2 platinen gebaut, ein controller sendet der andere empfaengt, und das ganze funktioniert hervorragend (125kbit/s).

Nun zu meinem problem:

Ich moechte den Controller auf 100kbit/s bringen um den komfortbus vom VW/Audi/Skoda/Seat auszulesen. Allerdings bekomme ich irgendwie Probleme beim setzen der Timing Register, koenntest du mir vielleicht die richtigen Werte fuer die Register mal posten?

# Fabian Greif meinte am 11. August 2007, 16:49 dazu:

Der Online Calculator von www.kvaser.com meint für 100 kBit/s bei einem Samplepunkt bei 75% folgendes:

CNF1 = 0x09
CNF2 = 0x91
CNF3 = 0x01

Sag mal Bescheid ob das damit dann funktioniert.

# Medalist meinte am 11. August 2007, 19:31 dazu:

Hey super, hat auf Anhieb funktioniert, anscheinend hab ich das mit dem Time Calculator nicht richtig begriffen.

Jedenfalls bessten Dank! ;)

# J. Harms meinte am 11. November 2007, 18:32 dazu:

Der Artikel hat mir grossartig über die Anfangshürden mit CAN geholfen und mich mein Vorurteil: ?CAN verwenden ist zu aufwändig? vergessen lassen - grossen Dank dafür!

Beim Ausprobieren bin ich auf einen ernsten Fehler gestossen: in der Prozedur MCP2515SendMessage wird am Anfang ein Statusregister gelesen um zu sehen, welche ?Transmit Buffer? verfügbar sind:

x_status = MCP2515ReadStatus ( SPI_RX_STATUS ) ;

Das ist falsch, es sollte sein:

x_status = MCP2515ReadStatus ( SPI_READ_STATUS ) ;

(SPI_RX_STATUS, Adresse 0xB0, gibt den Stand der ?Receive Buffer?, nicht wie gewünscht den der ?Transmit Buffer? - dafür ist SPI_READ_STATUS, Adresse 0xA0, zu verwenden).

Bei dem einfachen einmaligen ?Ping-pong? der Demoanwendung stört dieser Fehler nicht, wenn man aber eine Schleife baut und eine Reihe von Nachrichten sendet (so, dass manchmal auch die Buffer #1 und #2 verwendet werden müssen), bleibt die Anwendung hängen solange dieser Fehler nicht behoben ist. Wirklich lästig wurde das im Moment wo ich anfing mit Interrupts in Echtzeit zu arbeiten.

Frage: hat jemand Erfahrung mit Fehlerbehandlung? CAN tut da ja schon selber eine Menge, z.B. Korrektur mit Prüfsummen. Ich habe daher bisher nichts weiters dazugetan - einzig dringend nötige Aktion war das Erkennen und Wegwerfen von Nachrichten, die kein Empfänger erkannt hat - sonst ?verstopfen? sich die Sende Buffer. Die Dokumentation zu diesem Thema ist leider sehr unbefriedigend.

# Martin meinte am 21. November 2007, 19:34 dazu:

Kleiner Fehler?

Mir ist aufgefallen, dass in der mcp2515_defs.h bei den Bitdefinitionen vom EFLG Register TXB0 steht meiner Meinung nach sollte da TXBO stehen.
Ansonsten ein großes Lob super Seite super Artikel!

# Marco meinte am 26. November 2007, 21:34 dazu:

Hallo.
Ich habe das Testbord mal nachgebaut und habe dein Demo zum MCP darauf gespielt. Hat super geklappt. Das war bis dahin mit dem 2006er Compiler. Nun habe ich das ganze mit einem 2007er Compiler (WINAVR-20070525) wiederholt und bekomme über die RS232 die Meldung ?Kann MCP2515 nicht ansprechen?. Wie gesagt, orginal hex-file läuft, und nur einmal mit 2007er drüber und nix geht mehr. Was ist da anders? Danke schonmal.

# Marco meinte am 27. November 2007, 10:09 dazu:

Ich habe einen Unterschied zwischen den Compilerausgaben des 2006er und des 2007er feststellen können

2006er: …

Creating load file for EEPROM: main.eep avr-objcopy -j .eeprom —set-section-flags=.eeprom=”alloc,load” \ —change-section-lma .eeprom=0 -O ihex main.elf main.eep

Process Exit Code: 0 …

Das war alles super und hat auf anhieb funktioniert.

Als ich das selbe Projekt… wirklich nichts verändert … mit dem Winavr-20070525 compiliert habe, stand da folgendes:

Creating load file for EEPROM: main.eep avr-objcopy -j .eeprom —set-section-flags=.eeprom=”alloc,load” \ —change-section-lma .eeprom=0 -O ihex main.elf main.eep c:\WinAVR-20070525\bin\avr-objcopy.exe: there are no sections to be copied! c:\WinAVR-20070525\bin\avr-objcopy.exe: —change-section-lma .eeprom=0x00000000 never used make.exe: [main.eep] Error 1 (ignored)

Process Exit Code: 0 …

Und nun will der MCP2515 nicht mer mit sich reden lassen. Zumindest die Init gibt den Fehler zurück, was ja darauf schließen lässt, dass die CNF-Register nicht korrekt gesezt wurden. Sagt euch diese Fehlermeldung etwas?

Bis dann.

# Marco meinte am 27. November 2007, 12:55 dazu:

Ich habs :-)

Das mit den Meldungen des Compilers war es nicht, wurde mir zumindest von Jörg Wunsch… jeder kennt ihn denke ich, gesagt. Es war die delay.h. Der Compiler hat die anscheinend wegotimiert und damit kahm der MCP2515 nicht anständig in den Reset bzw. Config. Mode. MfG Marco

# benjamin meinte am 7. Februar 2008, 11:19 dazu:

Hallo!
Ich hab eine Frage? und zwar verstehe ich die Registerconfiguration von CNF1-3 im Testprogram nicht ganz. Du beschreibst CNF3 und schiebst die restlichen Bits rüber. Kann man nicht einfach alle drei register wie CNF3 beschreiben?

Ich bräuchte eine Bustakt von 768 kBit/s und wenn ich die werte vom Online Rechner nehme funktioniert es nicht

# benjamin meinte am 7. Februar 2008, 17:48 dazu:

Ich mein 800kBit/s? war nur mit den gedanken woanderst

# Fabian Greif meinte am 8. Februar 2008, 15:53 dazu:

Hi,

natürlich kann man auch jeweils

mcp2515_write_register(CNF1, ...);
mcp2515_write_register(CNF2, ...);
...

schreiben, ich spare mir aber im Program das jeweilige neu setzen der angesprochenen Adresse und nutzte aus, dass nach dem schreiben eines Wertes der MCP2515 automatisch intern auf die nächste Adresse weiterschaltet. Sendet man also weitere Werte werden diese in den darauf folgenden Registern abgelegt. Und da CNF3..1 genau hintereinander liegen klappt das wunderbar.

Was für Werte hast du den versucht?
Wenn ich 800kbps bei 16 MHz eingebe komme ich auf folgendes:

CNF1 = 0x80
CNF2 = 0x92
CNF3 = 0x02

Grüße Fabian

# Benjamin meinte am 22. Februar 2008, 13:24 dazu:

Hallo!
Entschuldigung dass ich so lang gebraucht habe zum zurückschreiben? war auf Urlaub

Es geht jetzt zwer das er die Nachricht verschickt allerdings können die AVRs die NAchricht nciht empfangen (weder im loopback als auch normal verbunden)

mfg Benjamin

# Heiko meinte am 5. Juli 2008, 00:55 dazu:

Hallo Fabian,
auch von mir vielen Dank für diese Seite, ist wirklich sehr nett von Dir, den Code zu veröffentlichen.
Ich hab leider noch ein Problem damit, und zwar habe ich zwei CAN-Module gebaut und verkabelt und bekomme nun von beiden die folgende Nachricht über den UART zurück:


MCP2515 is aktiv!
Erzeuge Nachricht
  id:     0x123
  laenge: 2
  rtr:    0
  daten:  0xab 0xcd
Wechsle zum Loopback-Modus
Nachricht wurde in die Puffer geschrieben.
Fehler: keine Nachricht empfangen
Wechsle zurueck zum normalen Modus
Versuche die Nachricht per CAN zu verschicken
Nachricht wurde in die Puffer geschrieben
Warte auf den Empfang von Nachrichten?


Ich weiß leider gar nicht, wo ich mit der Fehlersuche beginnen soll, da die Kommunikation zwischen meinem ATmega8 und dem MCP2515 ja scheinbar funktioniert. Hast Du eine Idee?

Vielen Dank für Hilfe und ein schönes Wochenende, Heiko

# Fabian Greif meinte am 5. Juli 2008, 13:25 dazu:

Das einzige was mir spontan dazu einfallen würde wäre die INT-Leitung.
Wie du schon gesagt hast, die Kommunikation klappt, der AVR kann also die Register des MCP2515 beschreiben und wieder zurück lesen.

Ohne eine korrekt angeschlossene INT-Leitung wird er aber im Demo-Programm trotzdem keinerlei Nachrichten empfangen. Schau also am besten in ob der Wert in der ‘defaults.h’ mit dem real verwendeten Pin übereinstimmt und ob auch elektrisch eine Verbindung da ist.

Ohne die INT-Leitung funktioniert es prinzipiell auch, allerdings musst du dann die can-lib verwenden, die kann das.

# Heiko meinte am 5. Juli 2008, 17:04 dazu:

Hallo Fabian,
vielen Dank für die schnelle Hilfe! Der Fehler war aber ein anderer: Ich hatte die Reset-Leitung des MCP2515 zunächst nicht angeschlossen und sah dafür auch keine Notwendigkeit, da der Stein ja zu arbeiten schien. Wenn ich sie nun aber auf + lege, funktioniert alles, wie es soll.

Nochmals vielen vielen Dank!
Heiko

Deine Meinung:

  • Textformatierung ist mit Markdown möglich.
  • HTML wird entfernt.
  • Kommentare werden moderiert und sind daher eventuell nicht sofort sichtbar.
  • Irrelevante Kommentare werden gelöscht.