4

I got 2 boards with an ATMEGA328 microcontroller and MCP2515 CAN Controller and I'm trying to get them communicate with each other.

To shortcut the programming a little bit I'm using the Sparkfun CAN BUS Library, but I am not using ther CanbusClass(), I'm directly using the included mcp2515.c/h

The MCP2515 drives a MCP2551. CAN_H and CAN_L of both boards are connected with each other, the line has a 120Ohm termination resistor.

enter image description here enter image description here Both Microcontrollers share the same code:

#include <mcp2515.h>
#include <global.h>
#include <mcp2515_defs.h>
#include <defaults.h>
#define CANSPEED_125    7
const uint8_t PIN_LED_RED = 6;
const uint8_t PIN_LED_GREEN = 7;

void setup()
{   
  Serial.begin(9600);
  pinMode(PIN_LED_GREEN, OUTPUT);   
  pinMode(PIN_LED_RED, OUTPUT);
  unsigned long now=micros();
  unsigned long init = 0;
  if(mcp2515_init(CANSPEED_125)) {
    init=micros();
    Serial.println("CAN Init ok");  
    digitalWrite(PIN_LED_GREEN, HIGH);
    delay(500);
    digitalWrite(PIN_LED_GREEN, LOW);
  } else {
    init = micros();
    Serial.println("Can't init CAN");
    digitalWrite(PIN_LED_RED, HIGH);
    delay(1000);
    digitalWrite(PIN_LED_RED, LOW);
  }
  Serial.print("Time for init: ");
  Serial.println (init-now);
  delay(1000);
  mcp2515_bit_modify(CANCTRL, (1<<REQOP2)|(1<<REQOP1)|(1<<REQOP0), 0); 
  Serial.println("-------------------------");
  Serial.println("1. CAN - MSG1");
  Serial.println("-------------------------");
}

void loop()
{   
  tCAN CANmessage;
  unsigned long tnow;
  unsigned long tsend;
  if (Serial.available() > 0) {
    char incomingByte = Serial.read();
    switch (incomingByte) {
        case '1':
            Serial.println("1. CAN - MSG1");
            tsend=micros();
            CANmessage.id = 0x01;
            CANmessage.header.rtr = 0;
            CANmessage.header.length = 8;
            CANmessage.data[0] = B01010101;
            CANmessage.data[1] = B11001100;
            CANmessage.data[2] = B11100011;
            mcp2515_bit_modify(CANCTRL, (1<<REQOP2)|(1<<REQOP1)|(1<<REQOP0), 0);
            mcp2515_send_message(&CANmessage);
            tnow=micros();
            Serial.print("Message send in time: ");
            Serial.println(tnow-tsend);
            break;
    }
  }
  checkCANBus();
}

void checkCANBus() {
  if (mcp2515_check_message()) {
    tCAN CANmessage;
    PORTD ^= _BV(7);
    Serial.println("Has message");
    if (mcp2515_get_message(&CANmessage)) 
    {
        Serial.print("Message receivced: ");
        Serial.print(CANmessage.id);
        Serial.print(", ");
        Serial.print(CANmessage.data[0], BIN);
        Serial.print(", ");
        Serial.print(CANmessage.data[1], BIN);
        Serial.print(", ");
        Serial.print(CANmessage.data[2], BIN);
        Serial.print("end ");
        if (CANmessage.id==0x01) {
            Serial.println("MSG ID1 received");
            PORTD ^= _BV(6);
        }               
    }
  }
}

On Serial port of both boards I get on startup:

CAN Init ok
Time for init: 250
1. CAN - MSG1

When I send a byte on the Serial port of one baord ('1') I get on the Serial:

Message send in time: 212

On the receiver side I only get

Has message

and the green LED on PIN7 is turned on (means mcp2515_check_message() returns that the MCP2515 has set the receive interrupt signal).

After that the program crashes in the routine mcp2515_get_message().

By toggling the LED's I figured out that the program crashes directly at the beginning of mcp2515_get_message() in the line mcp2515_read_status(SPI_RX_STATUS); So the problem lies in mcp2515_read_status().

int8_t mcp2515_get_message(tCAN *message)
{
  // read status
  uint8_t status = mcp2515_read_status(SPI_RX_STATUS);
  ...
  ...
}

uint8_t mcp2515_read_status(uint8_t type)
{
    uint8_t data;
    RESET(MCP2515_CS);
    spi_putc(type);
    data = spi_putc(0xff);
    SET(MCP2515_CS);
    return data;
}

uint8_t spi_putc( uint8_t data )
{
  SPDR = data;
  while( !( SPSR & (1<<SPIF) ) )
    ;
  return SPDR;
}

By further toggling I figured that the last line correctly executed is spi_putc(type);

The line data = spi_putc(0xff); freezes the microcontoller! Which means the while loop in spi_putc() is endless running as the SPIF bit is not cleared.

Here is what I think I figured out so far:

  • The SPI bus connection is clearly working as the initialization is working!
  • As the mcp2515_check_message() returns on the receiver board that MCP2515_INT is set so I assume the CAN communication is working (otherwise, why would controller set the MCP2515_INT).

SPI_RX_STATUS is defined as 0xB0(original as in library). The Datasheet of the MCP2515 controller states on page 66

The RX STATUS instruction (Figure 12-9) is used to quickly determine which filter matched the message and message type (standard, extended, remote). After the command byte is sent, the controller will return 8 bits of data that contain the status data. If more clocks are sent after the eight bits are transmitted, the controller will continue to output the same status bits as long as the CS pin stays low and clocks are provided.

Which it clearly doesn't! But what I cant figure out is why :)

migrated from electronics.stackexchange.com Jul 10 '16 at 23:01

This question came from our site for electronics and electrical engineering professionals, students, and enthusiasts.

  • 1
    I would be tempted to find out why you only get 'Has message' which implies that if (omcp2515_get_message(&CANmessage)) is returning zero. – Peter Smith Jul 10 '16 at 15:47
  • The code shows omcp2515... and mcp2515... functions. What's that about? – JimmyB Jul 10 '16 at 22:05
  • @PeterSmith thats not the case. The program freezes in that call of mcp2515_get_message(&CANmessage). – KarlKarlsom Jul 10 '16 at 23:46
  • @JimmyB, thats just a copy of the function I used to toggle the LED. I didnt want to mess with the original code in the library. I edited the question! – KarlKarlsom Jul 10 '16 at 23:49
  • Just try another library, like this one: github.com/Seeed-Studio/CAN_BUS_Shield just be careful as this library does not implement atomic SPI transaction. – Talk2 Jul 13 '16 at 10:35
2
+50

There is one huge pitfall in Master Mode in this ATMega - Select Slave pin (/SS - active low). If it's configured as input, SPI is switched to Slave Mode by low level on this pin. And /SS is on pin PB2 (used as CAN_INT).

In the library defaults I can see: #define MCP2515_INT D,2. PD2 is also INT0 external interrupt pin, so it does make sense to be here and it can be used with attachInterrupt. PB2 is used as an output for selecting slave device, so it's ok too.
Schematics corresponds with that defaults: shield interface

In OPs schematics there are slight changes. Interrupt request CAN_INT is located on /SS pin (PB2, arduino pin 10). CAN_CS was moved from PB2 to PB1 (arduino pin 10 -> pin 9)

And in Mega328 datasheet is section: 19.2 SS Pin Functionality:

19.3.2 Master Mode

When the SPI is configured as a Master (MSTR in SPCR is set), the user can determine the direction of the /SS pin.

If /SS is configured as an output, the pin is a general output pin which does not affect the SPI system. Typically, the pin will be driving the SS pin of the SPI Slave.

If /SS is configured as an input, it must be held high to ensure Master SPI operation. If the /SS pin is driven low by peripheral circuitry when the SPI is configured as a Master with the /SS pin defined as an input, the SPI system interprets this as another master selecting the SPI as a slave and starting to send data to it.
To avoid bus contention, the SPI system takes the following actions:
1. The MSTR bit in SPCR is cleared and the SPI system becomes a Slave. As a result of the SPI becoming a Slave, the MOSI and SCK pins become inputs.
2. The SPIF Flag in SPSR is set, and if the SPI interrupt is enabled, and the I-bit in SREG is set, the interrupt routine will be executed.
Thus, when interrupt-driven SPI transmission is used in Master mode, and there exists a possibility that SS is driven low, the interrupt should always check that the MSTR bit is still set. If the MSTR bit has been cleared by a slave select, it must be set by the user to re-enable SPI Master mode.

Conclusion: If you are using HW SPI in master mode, you can't just use pin 10 (PB2) as general input.

  • 1
    Could you possibly clarify this answer more, it is really vague and short. Also the connections of the OP's circuit is basically the same as the schematic from Sparkfuns shield – RSM Jul 13 '16 at 8:36
  • I think you are on the right track. Dont know, why I missed that in the datasheet... Let me quickly cut the track of PB2 and connect a botch wire to PB0 to test it. Btw. if I understand it correct, actually I would only need to change CAN_CS (which is an output) with CAN_INT (which is an input) in my final layout and everything should be ok, right? – KarlKarlsom Jul 13 '16 at 13:39
  • Yes, you can switch these signals. But if you dont use D10, it must be set eiter as output (and used for another signal) or input pullup (and left unconnected). However i'd recommend to use PD2 (INT0) or PD3 (INT1). – KIIV Jul 13 '16 at 14:02
  • Just tested it and it works! Thank you very much for helping me out! Finding this cost me days :) – KarlKarlsom Jul 13 '16 at 14:33
  • So what is the conclusion? What should be done? Can you make it clear (by editing your answer)? – Peter Mortensen Jul 26 '16 at 12:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.