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.
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 :)