Go Down

Topic: CAN Shield (Read 27 times) previous topic - next topic

DaveAK

Well, here's my first *complete* project for the Arduino.  (Never really complete, are they.)



Now I understand that there are a few CAN implementations out there, but the more the merrier, right? :)

This one uses the MCP2515/MCP2551 chips like most of them do. It has a switchable CS pin so that you can use other SPI shields with it that might have hardwired D10 as CS.  It has a switch to draw power from the CAN Bus if desired, if you wanted to add a WiFi or Bluetooth shield for example.  And for newbies like me it's an entirely through hole design making it an easy DIY project. :)

I've also written a library that's a complete implementation of the MCP2515 SPI command set.  It doesn't yet have any wrappers for filters or masks, but these are all accessible through the read and write commands.  I have a simple Init function that takes a bus speed and clock frequency and calculates out all the necessary bit timing parameters, which makes it pretty straightforward to setup and use.

Now all I need is something to connect it to ...... :D  (I actually have a need for this in another project that has been temporarily sidelined.)

Jonathan Oxer

Quote
Now all I need is something to connect it to


You didn't mention if you're wanting to use this for an automotive project, but if you are then something that might be useful for development is a mOByDic 1610:

http://www.ozenelektronik.com/?s=products2&group=eobd-obdii-ecu-simulators

Developing against an ECU is no fun if you have to sit in the garage with your laptop while you're coding! I've spent way too long out in the driveway with a lappie and a long extension cable. Much nicer to be able to sit at your bench and twiddle the knobs to simulate different behavior.
--
Jon
Vehicle Telemetry Platform: www.practicalarduino.com/projects/vehicle-telemetry-platform

DaveAK

#2
Nov 09, 2010, 05:59 am Last Edit: Nov 09, 2010, 05:59 am by DaveAK Reason: 1
It's actually going to be used to interface to an Electric Vehicle controller, so no OBD-II needed. ¬†Of course, it's a proprietry system that I've got to hack, so we'll see how that goes.  ::) ¬†Should be fun though. :D

DaveAK

Here's the library code:

MCP2515.h

Code: [Select]
/*
 MCP2515.h - Library for Microchip MCP2515 CAN Controller
 
 Author: David Harding
 
 Created: 11/08/2010
 
 For further information see:
 
 http://ww1.microchip.com/downloads/en/DeviceDoc/21801e.pdf
 http://en.wikipedia.org/wiki/CAN_bus
*/

#ifndef MCP2515_h
#define MCP2515_h

#include "WProgram.h"
#include "MCP2515_defs.h"

class MCP2515
{
 public:
     // Constructor defining which pins to use for CS and INT
   MCP2515(byte CS_Pin, byte INT_Pin);
     
     // Overloaded initialization function
     int Init(int baud, byte freq);
     int Init(int baud, byte freq, byte sjw);
     
     // Basic MCP2515 SPI Command Set
   void Reset();
   byte Read(byte address);
   void Read(byte address, byte data[], byte bytes);
     Frame ReadBuffer(byte buffer);
     void Write(byte address, byte data);
     void Write(byte address, byte data[], byte bytes);
     void LoadBuffer(byte buffer, Frame message);
     void SendBuffer(byte buffers);
     byte Status();
     byte RXStatus();
     void BitModify(byte address, byte mask, byte data);

     // Extra functions
     bool Interrupt(); // Expose state of INT pin
     bool Mode(byte mode); // Returns TRUE if mode change successful
     
 private:
     bool _init(int baud, byte freq, byte sjw, bool autoBaud);
   // Pin variables
     byte _CS;
     byte _INT;
};

#endif


MCP2515_defs.h

Code: [Select]
/*
 MCP2515_defs.h - Library for Microchip MCP2515 CAN Controller
 
 Author: David Harding
 
 Created: 11/08/2010
 
 For further information see:
 
 http://ww1.microchip.com/downloads/en/DeviceDoc/21801e.pdf
 http://en.wikipedia.org/wiki/CAN_bus
*/

#ifndef MCP2515_defs_h
#define MCP2515_defs_h

#include "WProgram.h"

typedef struct
{
     unsigned long id;      // EID if ide set, SID otherwise
     byte srr;                  // Standard Frame Remote Transmit Request
     byte rtr;                  // Remote Transmission Request
     byte ide;                  // Extended ID flag
     byte dlc;                  // Number of data bytes
     byte data[8];            // Data bytes
} Frame;
     

// MCP2515 SPI Commands
#define CAN_RESET       0xC0
#define CAN_READ        0x03
#define CAN_WRITE       0x02
#define CAN_RTS         0x80
#define CAN_STATUS      0xA0
#define CAN_BIT_MODIFY  0x05  
#define CAN_RX_STATUS   0xB0
#define CAN_READ_BUFFER 0x90
#define CAN_LOAD_BUFFER 0X40  

// Register Bit Masks
// CANSTAT
#define MODE_CONFIG            0x80
#define MODE_LISTEN            0x60
#define MODE_LOOPBACK      0x40
#define MODE_SLEEP            0x20
#define MODE_NORMAL            0x00
// CANINTF
#define RX0IF                  0x01
#define RX1IF                  0x02
#define TX0IF                  0x04
#define TX1IF                  0x08
#define TX2IF                  0x10
#define ERRIF                  0x20
#define WAKIF                  0x40
#define MERRF                  0x80

// Configuration Registers
#define CANSTAT         0x0E
#define CANCTRL         0x0F
#define BFPCTRL         0x0C
#define TEC             0x1C
#define REC             0x1D
#define CNF3            0x28
#define CNF2            0x29
#define CNF1            0x2A
#define CANINTE         0x2B
#define CANINTF         0x2C
#define EFLG            0x2D
#define TXRTSCTRL       0x0D

// TX Buffer 0
#define TXB0CTRL        0x30
#define TXB0SIDH        0x31
#define TXB0SIDL        0x32
#define TXB0EID8        0x33
#define TXB0EID0        0x34
#define TXB0DLC         0x35
#define TXB0D0          0x36
#define TXB0D1          0x37
#define TXB0D2          0x38
#define TXB0D3          0x39
#define TXB0D4          0x3A
#define TXB0D5          0x3B
#define TXB0D6          0x3C
#define TXB0D7          0x3D
                       
// TX Buffer 1
#define TXB1CTRL        0x40
#define TXB1SIDH        0x41
#define TXB1SIDL        0x42
#define TXB1EID8        0x43
#define TXB1EID0        0x44
#define TXB1DLC         0x45
#define TXB1D0          0x46
#define TXB1D1          0x47
#define TXB1D2          0x48
#define TXB1D3          0x49
#define TXB1D4          0x4A
#define TXB1D5          0x4B
#define TXB1D6          0x4C
#define TXB1D7          0x4D

// TX Buffer 2
#define TXB2CTRL        0x50
#define TXB2SIDH        0x51
#define TXB2SIDL        0x52
#define TXB2EID8        0x53
#define TXB2EID0        0x54
#define TXB2DLC         0x55
#define TXB2D0          0x56
#define TXB2D1          0x57
#define TXB2D2          0x58
#define TXB2D3          0x59
#define TXB2D4          0x5A
#define TXB2D5          0x5B
#define TXB2D6          0x5C
#define TXB2D7          0x5D
                       
// RX Buffer 0
#define RXB0CTRL        0x60
#define RXB0SIDH        0x61
#define RXB0SIDL        0x62
#define RXB0EID8        0x63
#define RXB0EID0        0x64
#define RXB0DLC         0x65
#define RXB0D0          0x66
#define RXB0D1          0x67
#define RXB0D2          0x68
#define RXB0D3          0x69
#define RXB0D4          0x6A
#define RXB0D5          0x6B
#define RXB0D6          0x6C
#define RXB0D7          0x6D
                       
// RX Buffer 1
#define RXB1CTRL        0x70
#define RXB1SIDH        0x71
#define RXB1SIDL        0x72
#define RXB1EID8        0x73
#define RXB1EID0        0x74
#define RXB1DLC         0x75
#define RXB1D0          0x76
#define RXB1D1          0x77
#define RXB1D2          0x78
#define RXB1D3          0x79
#define RXB1D4          0x7A
#define RXB1D5          0x7B
#define RXB1D6          0x7C
#define RXB1D7          0x7D

// Buffer Bit Masks
#define RXB0            0x00
#define RXB1            0x02
#define TXB0            0x01
#define TXB1            0x02
#define TXB2            0x04
#define TXB_ALL                  TXB0 | TXB1 | TXB2

#endif


Continued next post ...

DaveAK

MCP2515.cpp

Code: [Select]
/*
 MCP2515.cpp - Library for Microchip MCP2515 CAN Controller
 
 Author: David Harding
 
 Created: 11/08/2010
 
 For further information see:
 
 http://ww1.microchip.com/downloads/en/DeviceDoc/21801e.pdf
 http://en.wikipedia.org/wiki/CAN_bus
*/

#include "WProgram.h"
#include "SPI.h"
#include "MCP2515.h"
#include "MCP2515_defs.h"

MCP2515::MCP2515(byte CS_Pin, byte INT_Pin) {
 pinMode(CS_Pin, OUTPUT);
 digitalWrite(CS_Pin,HIGH);

 pinMode(INT_Pin,INPUT);
 digitalWrite(INT_Pin,HIGH);
 
 _CS = CS_Pin;
 _INT = INT_Pin;
}

/*
 Initialize MCP2515
 
 int CAN_Bus_Speed = transfer speed in kbps
 int Freq = MCP2515 oscillator frequency in MHz
 int SJW = Synchronization Jump Width Length bits - 1 to 4 (see data sheet)
 
 returns baud rate set
 
 Sending a bus speed of 0 kbps initiates AutoBaud and returns zero if no
 baud rate could be determined.  There must be two other active nodes on the bus!
*/
int MCP2515::Init(int CAN_Bus_Speed, byte Freq) {
 if(CAN_Bus_Speed>0) {
   if(_init(CAN_Bus_Speed, Freq, 1, false)) return CAN_Bus_Speed;
 } else {
     int i=0;
     byte interruptFlags = 0;
     for(i=5; i<1000; i=i+5) {
       if(_init(i, Freq, 1, true)) {
           // check for bus activity
           Write(CANINTF,0);
           delay(500); // need the bus to be communicating within this time frame
           if(Interrupt()) {
             // determine which interrupt flags have been set
             interruptFlags = Read(CANINTF);
             if(!(interruptFlags & MERRF)) {
               // to get here we must have received something without errors
               Mode(MODE_NORMAL);
                 return i;
             }
           }
       }
     }
 }
 return 0;
}

int MCP2515::Init(int CAN_Bus_Speed, byte Freq, byte SJW) {
 if(SJW < 1) SJW = 1;
 if(SJW > 4) SJW = 4;
 if(CAN_Bus_Speed>0) {
   if(_init(CAN_Bus_Speed, Freq, SJW, false)) return CAN_Bus_Speed;
 } else {
     int i=0;
     byte interruptFlags = 0;
     for(i=5; i<1000; i=i+5) {
       if(_init(i, Freq, SJW, true)) {
           // check for bus activity
           Write(CANINTF,0);
           delay(500); // need the bus to be communicating within this time frame
           if(Interrupt()) {
             // determine which interrupt flags have been set
             interruptFlags = Read(CANINTF);
             if(!(interruptFlags & MERRF)) {
               // to get here we must have received something without errors
               Mode(MODE_NORMAL);
                 return i;
             }
           }
       }
     }
 }
 return 0;
}

bool MCP2515::_init(int CAN_Bus_Speed, byte Freq, byte SJW, bool autoBaud) {
 
 // Reset MCP2515 which puts it in configuration mode
 Reset();
 
 // Calculate bit timing registers
 byte BRP;
 float TQ;
 byte BT;
 float tempBT;

 float NBT = 1.0 / (float)CAN_Bus_Speed * 1000.0; // Nominal Bit Time
 for(BRP=0;BRP<8;BRP++) {
   TQ = 2.0 * (float)(BRP + 1) / (float)Freq;
   tempBT = NBT / TQ;
     if(tempBT<=25) {
       BT = (int)tempBT;
       if(tempBT-BT==0) break;
     }
 }
 
 byte SPT = (0.7 * BT); // Sample point
 byte PRSEG = (SPT - 1) / 2;
 byte PHSEG1 = SPT - PRSEG - 1;
 byte PHSEG2 = BT - PHSEG1 - PRSEG - 1;

 // Programming requirements
 if(PRSEG + PHSEG1 < PHSEG2) return false;
 if(PHSEG2 <= SJW) return false;
 
 byte BTLMODE = 1;
 byte SAM = 0;
 
 // Set registers
 byte data = (((SJW-1) << 6) | BRP);
 Write(CNF1, data);
 Write(CNF2, ((BTLMODE << 7) | (SAM << 6) | ((PHSEG1-1) << 3) | (PRSEG-1)));
 Write(CNF3, (B10000000 | (PHSEG2-1)));
 Write(TXRTSCTRL,0);
 
 if(!autoBaud) {
   // Return to Normal mode
     if(!Mode(MODE_NORMAL)) return false;
 } else {
   // Set to Listen Only mode
     if(!Mode(MODE_LISTEN)) return false;
 }
 // Enable all interupts
 Write(CANINTE,255);
 
 // Test that we can read back from the MCP2515 what we wrote to it
 byte rtn = Read(CNF1);
 return (rtn==data);
}

void MCP2515::Reset() {
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_RESET);
 digitalWrite(_CS,HIGH);
}

byte MCP2515::Read(byte address) {
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_READ);
 SPI.transfer(address);
 byte data = SPI.transfer(0x00);
 digitalWrite(_CS,HIGH);
 return data;
}

void MCP2515::Read(byte address, byte data[], byte bytes) {
 // allows for sequential reading of registers starting at address - see data sheet
 byte i;
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_READ);
 SPI.transfer(address);
 for(i=0;i<bytes;i++) {
   data[i] = SPI.transfer(0x00);
 }
 digitalWrite(_CS,HIGH);
}

Frame MCP2515::ReadBuffer(byte buffer) {

 // Reads an entire RX buffer.
 // buffer should be either RXB0 or RXB1
 
 Frame message;
 
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_READ_BUFFER | (buffer<<1));
 byte byte1 = SPI.transfer(0x00); // RXBnSIDH
 byte byte2 = SPI.transfer(0x00); // RXBnSIDL
 byte byte3 = SPI.transfer(0x00); // RXBnEID8
 byte byte4 = SPI.transfer(0x00); // RXBnEID0
 byte byte5 = SPI.transfer(0x00); // RXBnDLC

 message.srr=(byte2 & B00010000);
 message.ide=(byte2 & B00001000);

 if(message.ide) {
   message.id = (byte1>>3);
   message.id = (message.id<<8) | ((byte1<<5) | ((byte2>>5)<<2) | (byte2 & B00000011));
   message.id = (message.id<<8) | byte3;
   message.id = (message.id<<8) | byte4;
 } else {
   message.id = ((byte1>>5)<<8) | ((byte1<<3) | (byte2>>5));
 }

 message.rtr=(byte5 & B01000000);
 message.dlc = (byte5 & B00001111);  // Number of data bytes
 for(int i=0;i<message.dlc;i++) {
   message.data[i] = SPI.transfer(0x00);
 }
 digitalWrite(_CS,HIGH);

 return message;
}

void MCP2515::Write(byte address, byte data) {
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_WRITE);
 SPI.transfer(address);
 SPI.transfer(data);
 digitalWrite(_CS,HIGH);
}

void MCP2515::Write(byte address, byte data[], byte bytes) {
 // allows for sequential writing of registers starting at address - see data sheet
 byte i;
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_WRITE);
 SPI.transfer(address);
 for(i=0;i<bytes;i++) {
   SPI.transfer(data[i]);
 }
 digitalWrite(_CS,HIGH);
}

void MCP2515::SendBuffer(byte buffers) {
 // buffers should be any combination of TXB0, TXB1, TXB2 ORed together, or TXB_ALL
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_RTS | buffers);
 digitalWrite(_CS,HIGH);
}

void MCP2515::LoadBuffer(byte buffer, Frame message) {

 // buffer should be one of TXB0, TXB1 or TXB2
 if(buffer==TXB0) buffer = 0;

 byte byte1=0; // TXBnSIDH
 byte byte2=0; // TXBnSIDL
 byte byte3=0; // TXBnEID8
 byte byte4=0; // TXBnEID0
 byte byte5=0; // TXBnDLC

 if(message.ide) {
   byte1 = byte((message.id<<3)>>24); // 8 MSBits of SID
     byte2 = byte((message.id<<11)>>24) & B11100000; // 3 LSBits of SID
     byte2 = byte2 | byte((message.id<<14)>>30); // 2 MSBits of EID
     byte2 = byte2 | B00001000; // EXIDE
   byte3 = byte((message.id<<16)>>24); // EID Bits 15-8
   byte4 = byte((message.id<<24)>>24); // EID Bits 7-0
 } else {
   byte1 = byte((message.id<<21)>>24); // 8 MSBits of SID
     byte2 = byte((message.id<<29)>>24) & B11100000; // 3 LSBits of SID
   byte3 = 0; // TXBnEID8
   byte4 = 0; // TXBnEID0
 }
 byte5 = message.dlc;
 if(message.rtr) {
   byte5 = byte5 | B01000000;
 }
 
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_LOAD_BUFFER | buffer);  
 SPI.transfer(byte1);
 SPI.transfer(byte2);
 SPI.transfer(byte3);
 SPI.transfer(byte4);
 SPI.transfer(byte5);

 for(int i=0;i<message.dlc;i++) {
   SPI.transfer(message.data[i]);
 }
 digitalWrite(_CS,HIGH);
}

byte MCP2515::Status() {
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_STATUS);
 byte data = SPI.transfer(0x00);
 digitalWrite(_CS,HIGH);
 return data;
 /*
 bit 7 - CANINTF.TX2IF
 bit 6 - TXB2CNTRL.TXREQ
 bit 5 - CANINTF.TX1IF
 bit 4 - TXB1CNTRL.TXREQ
 bit 3 - CANINTF.TX0IF
 bit 2 - TXB0CNTRL.TXREQ
 bit 1 - CANINTFL.RX1IF
 bit 0 - CANINTF.RX0IF
 */
}

byte MCP2515::RXStatus() {
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_RX_STATUS);
 byte data = SPI.transfer(0x00);
 digitalWrite(_CS,HIGH);
 return data;
 /*
 bit 7 - CANINTF.RX1IF
 bit 6 - CANINTF.RX0IF
 bit 5 -
 bit 4 - RXBnSIDL.EIDE
 bit 3 - RXBnDLC.RTR
 bit 2 | 1 | 0 | Filter Match
 ------|---|---|-------------
     0 | 0 | 0 | RXF0
       0 | 0 | 1 | RXF1
       0 | 1 | 0 | RXF2
       0 | 1 | 1 | RXF3
       1 | 0 | 0 | RXF4
       1 | 0 | 1 | RXF5
       1 | 1 | 0 | RXF0 (rollover to RXB1)
       1 | 1 | 1 | RXF1 (rollover to RXB1)
 */
}

void MCP2515::BitModify(byte address, byte mask, byte data) {
 // see data sheet for explanation
 digitalWrite(_CS,LOW);
 SPI.transfer(CAN_BIT_MODIFY);
 SPI.transfer(address);
 SPI.transfer(mask);
 SPI.transfer(data);
 digitalWrite(_CS,HIGH);
}

bool MCP2515::Interrupt() {
 return (digitalRead(_INT)==LOW);
}

bool MCP2515::Mode(byte mode) {
 /*
 mode can be one of the following:
 MODE_CONFIG
 MODE_LISTEN
 MODE_LOOPBACK
 MODE_SLEEP
 MODE_NORMAL
 */
 BitModify(CANCTRL, B11100000, mode);
 delay(10); // allow for any transmissions to complete
 byte data = Read(CANSTAT); // check mode has been set
 return ((data & mode)==mode);
}

Go Up