Hi Everybody,
I tryied to get a RP2040 to work with CAN-BUS MCP2515-module but I did not manage it.
I tried multiple libraries but I got none of them to work.
So I switched over to an ESP32-S3 which has an inbuild CAN-BUS-engine.
And only needs a CAN-BUS-tranceiver that transform the bits to CAN-BUS-standard.
Adafruit has a very nice CAN-BUS-Board that can be driven with 3V and creates the 5V for the CAN-BUS itself.
I did the same and tried multiple libraries.
Same problems some did not compile, some did not work
finally user @horace pointed me to this library
ESP32-TWAI-CAN library
There is only one rather poor example-code for this library which is somehow specialised to request the coolant-temperature of a car if you connect the ESP32 to the ODB2-socket in the car.
Though I don't got a car laying around on my table I started to modify the demo-code to be more informative and to be more general to better explain the principle how to use this library
So I finally have a working demo-code that demonstrates the basic principle.
ATTENTION ! 8 MHz-CAN-Bus modules require changing the library-sourcecode
If you use a MCP2515-can-bus module with a 8 MHz chrystal the source-code of the library must be changed
Most of these 8 MHz modules look like this
The chrystal is this component

Inside the library-file MCP2515.h comment / uncomment that line that matches the chystal-frequency on your MCP2515-module
A smart improvement that would take away this beginner-trap would be to modify the library to have this chrystal-frequency as a parameter in the constructor
// Copyright (c) Sandeep Mistry. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
#ifndef ARDUINO_ARCH_ESP32
#ifndef MCP2515_H
#define MCP2515_H
#include <SPI.h>
#include "CANController.h"
//define MCP2515_DEFAULT_CLOCK_FREQUENCY 16e6 // defining a 16 MHz chrystal
#define MCP2515_DEFAULT_CLOCK_FREQUENCY 8e6 // defining a 8 MHz chrystal
I am using an arduino uno with the most common MCP2515-CAN-BUS-engine combined with a TJA 1050 CAN-BUS-tranceiver for receiving and on this Arduino Uno I have this code running for receiving
Code for receiver Arduino Uno with MCP2515-TJA1050
// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298
#define dbg(myFixedText, variableName) \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName);
#define dbgi(myFixedText, variableName,timeInterval) \
{ \
static unsigned long intervalStartTime; \
if ( millis() - intervalStartTime >= timeInterval ){ \
intervalStartTime = millis(); \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName); \
} \
}
#define dbgc(myFixedText, variableName) \
{ \
static long lastState; \
if ( lastState != variableName ){ \
Serial.print( F(#myFixedText " " #variableName" changed from ") ); \
Serial.print(lastState); \
Serial.print( F(" to ") ); \
Serial.println(variableName); \
lastState = variableName; \
} \
}
#define dbgcf(myFixedText, variableName) \
{ \
static float lastState; \
if ( lastState != variableName ){ \
Serial.print( F(#myFixedText " " #variableName" changed from ") ); \
Serial.print(lastState); \
Serial.print( F(" to ") ); \
Serial.println(variableName); \
lastState = variableName; \
} \
}
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *
#include <CAN.h>
void PrintFileNameDateTime() {
Serial.println( F("Code running comes from file ") );
Serial.println( F(__FILE__) );
Serial.print( F(" compiled ") );
Serial.print( F(__DATE__) );
Serial.print( F(" ") );
Serial.println( F(__TIME__) );
}
// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - startOfPeriod >= TimePeriod ) {
// more time than TimePeriod has elapsed since last time if-condition was true
startOfPeriod = currentMillis; // a new period starts right here so set new starttime
return true;
}
else return false; // actual TimePeriod is NOT yet over
}
unsigned long MyTestTimer = 0; // Timer-variables MUST be of type unsigned long
const byte OnBoard_LED = 4;
void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
static unsigned long MyBlinkTimer;
pinMode(IO_Pin, OUTPUT);
if ( TimePeriodIsOver(MyBlinkTimer,BlinkPeriod) ) {
digitalWrite(IO_Pin,!digitalRead(IO_Pin) );
}
}
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("Setup-Start");
PrintFileNameDateTime();
Serial.println("CAN Receiver Callback");
// start the CAN bus at 500 kbps
//if (!CAN.begin(500E3)) {
long kbpS = 500E3;
Serial.print("CAN.begin(");
Serial.print(kbpS);
Serial.println(") done");
if (!CAN.begin(kbpS)) {
Serial.println("Starting CAN failed!");
while (1);
}
Serial.println("Starting CAN successful!");
// register the receive callback
Serial.println("registering callback-function");
Serial.println("exiting setup start listening....");
CAN.onReceive(onReceive);
}
void loop() {
BlinkHeartBeatLED(OnBoard_LED,250);
yield();
if ( TimePeriodIsOver(MyTestTimer,1000) ) {
}
}
void onReceive(int packetSize) {
// received a packet
Serial.print("Received ");
if (CAN.packetExtended()) {
Serial.print("extended ");
}
if (CAN.packetRtr()) {
// Remote transmission request, packet contains no data
Serial.print("RTR ");
}
Serial.print("packet with id 0x");
Serial.print(CAN.packetId(), HEX);
if (CAN.packetRtr()) {
Serial.print(" and requested length ");
Serial.println(CAN.packetDlc());
}
else {
Serial.print(" and length ");
Serial.println(packetSize);
// only print packet data for non-RTR packets
while (CAN.available()) {
Serial.print((char)CAN.read());
}
Serial.println();
}
Serial.println();
}
This is then printed to the serial monitor
19:47:28.134 -> Setup-Start
19:47:28.134 -> Code running comes from file
19:47:28.134 -> C:\Arduino-Pure-Portable\arduino-1.8.19\portable\sketchbook\CAN-Bus-receiver-Demo-Code-002\CAN-Bus-receiver-Demo-Code-002.ino
19:47:28.134 -> compiled Feb 11 2024 19:47:21
19:47:28.134 -> CAN Receiver Callback
19:47:28.134 -> CAN.begin(500000) done
19:47:28.134 -> Starting CAN successful!
19:47:28.134 -> registering callback-function
19:47:28.134 -> exiting setup start listening....
19:47:28.464 -> Received packet with id 0x7FF and length 8
19:47:28.512 -> 794HELLO
19:47:28.512 ->
19:47:28.890 -> Received packet with id 0x7FF and length 8
19:47:28.890 -> 795HELLO
19:47:28.890 ->
19:47:29.267 -> Received packet with id 0x7FF and length 8
19:47:29.314 -> 796HELLO
19:47:29.314 ->
19:47:29.692 -> Received packet with id 0x7FF and length 8
19:47:29.692 -> 797HELLO
19:47:29.692 ->
19:47:30.069 -> Received packet with id 0x7FF and length 8
19:47:30.069 -> 798HELLO
19:47:30.069 ->
19:47:30.494 -> Received packet with id 0x7FF and length 8
19:47:30.494 -> 799HELLO
19:47:30.494 ->
19:47:30.872 -> Received packet with id 0x7FF and length 8
19:47:30.872 -> 800HELLO
19:47:30.872 ->
19:47:31.296 -> Received packet with id 0x7FF and length 8
19:47:31.296 -> 801HELLO
19:47:31.296 ->
19:47:31.673 -> Received packet with id 0x7FF and length 8
19:47:31.673 -> 802HELLO
19:47:31.673 ->
19:47:32.098 -> Received packet with id 0x7FF and length 8
19:47:32.098 -> 803HELLO
19:47:32.098 ->
19:47:32.475 -> Received packet with id 0x7FF and length 8
19:47:32.475 -> 804HELLO
19:47:32.475 ->
19:47:32.899 -> Received packet with id 0x7FF and length 8
19:47:32.899 -> 805HELLO
19:47:32.899 ->
19:47:33.276 -> Received packet with id 0x7FF and length 8
19:47:33.276 -> 806HELLO
19:47:33.276 ->
19:47:33.701 -> Received packet with id 0x7FF and length 8
19:47:33.701 -> 807HELLO
19:47:33.701 ->
19:47:34.078 -> Received packet with id 0x7FF and length 8
19:47:34.078 -> 808HELLO
19:47:34.078 ->
19:47:34.502 -> Received packet with id 0x7FF and length 8
19:47:34.502 -> 809HELLO
19:47:34.502 ->
19:47:34.879 -> Received packet with id 0x7FF and length 8
19:47:34.879 -> 810HELLO
19:47:34.879 ->
19:47:35.304 -> Received packet with id 0x7FF and length 8
19:47:35.304 -> 811HELLO
19:47:35.304 ->
19:47:35.681 -> Received packet with id 0x7FF and length 8
19:47:35.681 -> 812HELLO
19:47:35.681 ->
19:47:36.107 -> Received packet with id 0x7FF and length 8
19:47:36.107 -> 813HELLO
19:47:36.107 ->
19:47:36.485 -> Received packet with id 0x7FF and length 8
19:47:36.485 -> 814HELLO
19:47:36.485 ->
sending is done with this code modified from the OBD2-request-Demo
This code prints its parameters and the can-frame with ID length and 8 byte data
code for ESP32 tested on a ESP32-S3 connected to an Adafruit CAN Pal - CAN Bus Transceiver
// MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START * MACRO-START *
// a detailed explanation how these macros work is given in this tutorial
// https://forum.arduino.cc/t/comfortable-serial-debug-output-short-to-write-fixed-text-name-and-content-of-any-variable-code-example/888298
#define dbg(myFixedText, variableName) \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName);
#define dbgi(myFixedText, variableName,timeInterval) \
{ \
static unsigned long intervalStartTime; \
if ( millis() - intervalStartTime >= timeInterval ){ \
intervalStartTime = millis(); \
Serial.print( F(#myFixedText " " #variableName"=") ); \
Serial.println(variableName); \
} \
}
#define dbgc(myFixedText, variableName) \
{ \
static long lastState; \
if ( lastState != variableName ){ \
Serial.print( F(#myFixedText " " #variableName" changed from ") ); \
Serial.print(lastState); \
Serial.print( F(" to ") ); \
Serial.println(variableName); \
lastState = variableName; \
} \
}
#define dbgcf(myFixedText, variableName) \
{ \
static float lastState; \
if ( lastState != variableName ){ \
Serial.print( F(#myFixedText " " #variableName" changed from ") ); \
Serial.print(lastState); \
Serial.print( F(" to ") ); \
Serial.println(variableName); \
lastState = variableName; \
} \
}
// MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END * MACRO-END *
/*
Serial.begin(115200);
Serial.println("Setup-Start");
PrintFileNameDateTime();
BlinkHeartBeatLED(OnBoard_LED,250);
static unsigned long MyTestTimer;
if ( TimePeriodIsOver(MyTestTimer,1000) ) {
*/
#include <ESP32-TWAI-CAN.hpp>
// Showcasing simple use of ESP32-TWAI-CAN library driver.
// Default for ESP32
const byte CAN_TX = 5; // which means connect GPIO-Pin with this number with the Tx-output on the CAN-transeiver
const byte CAN_RX = 4; // which means connect GPIO-Pin with this number with the Rx-input on the CAN-transeiver
CanFrame rxFrame;
int myCounter;
void setup() {
Serial.begin(115200);
Serial.println("Setup-Start");
PrintFileNameDateTime();
Serial.println();
// You can set custom size for the queues - those are default
byte QueueSize = 5;
ESP32Can.setRxQueueSize(QueueSize);
ESP32Can.setTxQueueSize(QueueSize);
Serial.print("ESP32Can.setRxQueueSize(");
Serial.print(QueueSize);
Serial.println(")");
Serial.print("ESP32Can.setTxQueueSize(");
Serial.print(QueueSize);
Serial.println(")");
int CAN_Speed = 500;
Serial.print("ESP32Can.begin(ESP32Can.convertSpeed(");
Serial.print(CAN_Speed);
Serial.print("), CAN_TX=");
Serial.print(CAN_TX);
Serial.print(", CAN_RX=");
Serial.print(CAN_RX);
Serial.print(", 10, 10) )");
Serial.println();
if (ESP32Can.begin(ESP32Can.convertSpeed(CAN_Speed), CAN_TX, CAN_RX, 10, 10) ) {
Serial.println("CAN bus started successfully!");
}
else {
Serial.println("starting CAN bus failed!");
}
}
void loop() {
static unsigned long MyTestTimer;
if ( TimePeriodIsOver(MyTestTimer, 400) ) {
myCounter++;
if (myCounter > 999) {
myCounter = 0;
}
sendCanFrame('A', myCounter);
}
// You can set custom timeout, default is 1000 milliseconds
if (ESP32Can.readFrame(rxFrame, 100)) {
// Comment out if too many frames
Serial.printf("Received frame: %03X \r\n", rxFrame.identifier);
if (rxFrame.identifier == 0x7E8) { // Standard OBD2 frame responce ID
Serial.printf("Collant temp: %3d°C \r\n", rxFrame.data[3] - 40); // Convert to °C
}
}
}
void sendCanFrame(uint8_t obdId, int Number) {
char myDigitBuffer[10] = " ";
itoa(Number, myDigitBuffer, 10); // itoa convert integer to ASCII-coded char-array
CanFrame myDataFrame = { 0 };
//obdFrame.identifier = 0x7DF; // Default OBD2 address;
myDataFrame.identifier = 0x7FF;
myDataFrame.extd = 0;
myDataFrame.data_length_code = 8;
myDataFrame.data[0] = myDigitBuffer[0];
myDataFrame.data[1] = myDigitBuffer[1];
myDataFrame.data[2] = myDigitBuffer[2];
myDataFrame.data[3] = 'H';
myDataFrame.data[4] = 'e';
myDataFrame.data[5] = 'l';
myDataFrame.data[6] = 'l';
myDataFrame.data[7] = 'o';
// Accepts both pointers and references
printCanFrame(myDataFrame);
ESP32Can.writeFrame(myDataFrame); // timeout defaults to 1 ms
}
void printCanFrame(CanFrame p_CAN_Frame) {
Serial.println("sending CAN-frame");
Serial.print("identifier=");
Serial.print(p_CAN_Frame.identifier, HEX);
Serial.print(" frame length=");
Serial.print(p_CAN_Frame.data_length_code);
Serial.print(" data as ASCII-Code#");
for (byte IdxNr = 0; IdxNr < 8; IdxNr++) {
Serial.print(char(p_CAN_Frame.data[IdxNr]) );
}
Serial.print("#");
Serial.println();
}
// helper-functions
void PrintFileNameDateTime() {
Serial.println( F("Code running comes from file ") );
Serial.println( F(__FILE__) );
Serial.print( F(" compiled ") );
Serial.print( F(__DATE__) );
Serial.print( F(" ") );
Serial.println( F(__TIME__) );
}
// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
unsigned long currentMillis = millis();
if ( currentMillis - startOfPeriod >= TimePeriod ) {
// more time than TimePeriod has elapsed since last time if-condition was true
startOfPeriod = currentMillis; // a new period starts right here so set new starttime
return true;
}
else return false; // actual TimePeriod is NOT yet over
}
void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
static unsigned long MyBlinkTimer;
pinMode(IO_Pin, OUTPUT);
if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
}
}
The serial monitor shows this
20:11:04.336> ESP-ROM:esp32s3-20210327
20:11:04.336> Build:Mar 27 2021
20:11:04.336> rst:0x1 (POWERON),boot:0x8 (SPI_FAST_FLASH_BOOT)
20:11:04.336> SPIWP:0xee
20:11:04.336> mode:DIO, clock div:1
20:11:04.399> load:0x3fce3808,len:0x44c
20:11:04.399> load:0x403c9700,len:0xbd8
20:11:04.399> load:0x403cc700,len:0x2a80
20:11:04.399> entry 0x403c98d0
20:11:04.461> Setup-Start
20:11:04.461> Code running comes from file
20:11:04.461> C:\Arduino-Pure-Portable\arduino-1.8.19\portable\sketchbook\ESP32-S3-CAN-BUS-transmitter-Demo-002\ESP32-S3-CAN-BUS-transmitter-Demo-002.ino
20:11:04.461> compiled Feb 11 2024 20:02:31
20:11:04.461>
20:11:04.461> ESP32Can.setRxQueueSize(5)
20:11:04.461> ESP32Can.setTxQueueSize(5)
20:11:04.461> ESP32Can.begin(ESP32Can.convertSpeed(500), CAN_TX=5, CAN_RX=4, 10, 10) )
20:11:04.461> CAN bus started successfully!
20:11:04.776> sending CAN-frame
20:11:04.776> identifier=7FF frame length=8 data as ASCII-Code#1<0> Hello#
20:11:05.154> sending CAN-frame
20:11:05.154> identifier=7FF frame length=8 data as ASCII-Code#2<0> Hello#
20:11:05.595> sending CAN-frame
20:11:05.595> identifier=7FF frame length=8 data as ASCII-Code#3<0> Hello#
20:11:05.973> sending CAN-frame
20:11:05.973> identifier=7FF frame length=8 data as ASCII-Code#4<0> Hello#
20:11:06.351> sending CAN-frame
20:11:06.351> identifier=7FF frame length=8 data as ASCII-Code#5<0> Hello#
20:11:06.792> sending CAN-frame
20:11:06.792> identifier=7FF frame length=8 data as ASCII-Code#6<0> Hello#
20:11:07.171> sending CAN-frame
20:11:07.171> identifier=7FF frame length=8 data as ASCII-Code#7<0> Hello#
20:11:07.548> sending CAN-frame
20:11:07.548> identifier=7FF frame length=8 data as ASCII-Code#8<0> Hello#
20:11:07.988> sending CAN-frame
20:11:07.988> identifier=7FF frame length=8 data as ASCII-Code#9<0> Hello#
20:11:08.366> sending CAN-frame
20:11:08.366> identifier=7FF frame length=8 data as ASCII-Code#10<0>Hello#
best regards Stefan