Programming CAN with Arduino, MCP2515 and MCP_CAN_LIB

Everything regarding communication-protocols like CAN, OBD, LIN, JTAG, BDM, I2C, SPI, ...
Locked
Go4IT
Pro
Posts: 967
Joined: 08 Feb 2019, 12:25

Programming CAN with Arduino, MCP2515 and MCP_CAN_LIB

Post by Go4IT »

I'd like to start a series of articles describing how to program CAN sketches.

Prepare hardware

For an easy jumpstart let's use this basic setup of Arduino Nano and MCP2515 CAN-Shield:
mb_canbus_schema.png
The MCP2515 is a CAN-Controller and uses an MCP2551 as an CAN-Driver. The controller has an SPI-Interface with a maximum clock-frequency of 10 MHz, which we connect directly to the SPI-Ports of the Arduino. SPI is a serial protocol with nearly no overhead (see "SPI-Protocol" unter "Communication protocols" for further details). The Quarz on the MCP-Shield is mostly an 8 MHz one. It is only used for generating/calculating clock frequencies on the CAN-Bus and has nothing to do with SPI-communication, so you can easly forgett about it.

Load libs

To communicate with the MCP-Controller we need at least two libraries:
1. An SPI-Library to use the hardware-SPI-Interface of the Arduino. I prefer the one from Paul Stoffregen, download here: https://github.com/PaulStoffregen/SPI/a ... master.zip
2. And a CAN-library, which contains basic functions of the CAN-Controller. Here i would suggest using the one from Cory J. Fowler called "MCP_CAN_LIB" which can be downloaded here: https://github.com/coryjfowler/MCP_CAN_ ... master.zip

Unzip them and rename the folder inside (the one where the files are inside) to "SPI" and "MCP_CAN_LIB" (remove the "-master" from the name) and put them into your Arduinos library directory found here: %USERPROFILE%\Documents\Arduino\libraries

First step: Include the libs
Now it's time to program! Start Arduino IDE, create new Sketch and use the libs:

Code: Select all

#include <mcp_can.h>
#include <SPI.h>

void setup() {
  // put your setup code here, to run once:
}

void loop() {
  // put your main code here, to run repeatedly:
}
Those two statements above instructs the linker to load the header-files (*.h) from the libraries folder. This is denoted by including the headerfilename into <> brackets. The linker finds the headerfiles automatically, so compile the sketch to check if everything is in place. If there is an errormessage, check your libraries directory to not have any othe CAN or SPI-libs that may clash with those. Remove all other testwise.

Setup the hardware

Next we need to tell the library how we connected the board. This is done by calling the universal constructor method of the class, giving the IO-Pin name where the CS-Signal (Chip-Select) is connected. In our example above it is connected to Pin 2, which can be referred to in Arduino by simply using "2":

Code: Select all

MCP_CAN CAN0(2);
For cleaner code and better reconfiguration you could put a define in front of your program to assign a meaningfull name:

Code: Select all

#define CAN0_CS 2
MCP_CAN CAN0(CAN0_CS);
The method-name "CAN0()" is not fix, you can use whatever you want to instantiate a global object of the lib, e.g.

Code: Select all

MCP_CAN MMCAN_IN(..);
or
MCP_CAN MMCAN_OUT(..);
Got it?

For the first examples we're not going to use the interrupt-signal, so ignore it for a while.

Initialize the controller
By calling the "begin()"-method of the object you created in the step above, you configure the controller using the most basic parameters:

Code: Select all

CAN0.begin(MCP_STD, CAN_125KBPS, MCP_8MHZ);
This will instruct the controller to send/receive standard CAN frames (11 Bit IDs, max. 8 Byte length) on a CAN-Bus with a speed of 125 kbaud and the MCP-Shield equiped with an 8 MHz quarz. Other CAN-Speed would be "CAN_500KBPS", for example. All this can be found in the "mcp_can_dfs.h" file in the canlib-directory.

Send a message

Now we are ready to send a message. Until now, the controller is in loopback-mode, which means it does not receive anything from the CAN, nor could it send anything to it. We first need to "activate" it with:

Code: Select all

CAN0.setMode(MCP_NORMAL);
Now it is an active member of the bus. By default it will receive any CAN-ID. We will see later how to use mask and filter to reduce this to the ones we need.

To send a message you need the following informations:
  • The CAN-ID of the message
  • The number of data bytes to send (payload)
  • The bytes to send itself
For the CAN-ID you have to use an variable to type "unsigned long", which is a two byte value on Arduino:

Code: Select all

unsigned long canid = 0x433;
The DLC (data length code) can have a value from 1 to 8 (sending 0 bytes is not allowed), so this is sufficient:

Code: Select all

unsigned char dlc = 8;
[code]

And least the data, which is an array of bytes, which can be assigned as a fixed value:
[code]
unsigned char data[8] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xAA, 0x55, 0xAA, 0x55 };
or as just an uninitialized variable, changed/calculated by software:

Code: Select all

unsigned char data[8];

data[0] = 0xDE;
data[1] = 0xAD;
...
data[7] = 0x55;
Now to actually send the data on the bus, use the "sendMsgBuf()" method:

Code: Select all

CAN0.sendMsgBuf(canid, dlc, data);
(You could also put static values instead of variables here, but that would be too unflexible and only usefull in special purposes)

Here the complete code:

Code: Select all

#include <mcp_can.h>
#include <SPI.h>

#define CAN0_CS 2
MCP_CAN CAN0(CAN0_CS);

void setup() {
	CAN0.begin(MCP_STD, CAN_125KBPS, MCP_8MHZ);
	CAN0.setMode(MCP_NORMAL);

	unsigned long canid = 0x433;
	unsigned char dlc = 8;
	unsigned char data[8] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xAA, 0x55, 0xAA, 0x55 };
	CAN0.sendMsgBuf(canid, dlc, data);
}

void loop() {
  // put your main code here, to run repeatedly:
}
Read message

The opposite way is just as simple. Use "readMsgBuf()" method to retrieve data from the last received CAN frame:

Code: Select all

CAN0.readMsgBuf(&canid, &dlc, &data);
Instead of assigning the values in the method-call, you simple assign pointers to the variables values by using the "&" in front of the variable names. The method then stores the values received into the variables and you can then process them, e.g.:

Code: Select all

if (canid == 0x1F0) {
	...
}
Here a full code example:

Code: Select all

#include <mcp_can.h>
#include <SPI.h>

#define CAN0_CS 2
MCP_CAN CAN0(CAN0_CS);

void setup() {
	CAN0.begin(MCP_STD, CAN_125KBPS, MCP_8MHZ);
	CAN0.setMode(MCP_NORMAL);

	unsigned long canid;
	unsigned char dlc;
	unsigned char data[8];
	CAN0.readMsgBuf(&canid, &dlc, &data);
	if (canid == 0x1F0) {
		// do something with the data...
	}
}

void loop() {
  // put your main code here, to run repeatedly:
}

That's all it needs for the basics!
Try it out, get familar and we can go on to more complicated, but more usefull code... :D
You do not have the required permissions to view the files attached to this post.
Locked