CAN Bus Dashboard
ESP32 DevKit (WROOM-32)·Arduino C++·Updated Jul 27, 2026·by FluxBench TeamIncludes Lab + Schematic + PCB
Two-node CAN bus bench on a single ESP32: one MCP2515 module plays the engine ECU broadcasting RPM (0x100), coolant temp (0x200) and a chatty infotainment frame (0x300), while a second MCP2515 is the dashboard node whose acceptance mask/filters admit only the two engine IDs — in the Lab simulator the paired nodes exchange frames and the filtered 0x300 frame surfaces as a rejection warning. Bundled Lab bench, schematic and routed PCB included.
sketch.ino
// CAN Bus Dashboard — two MCP2515 nodes on one bench
// CAN_ECU (CS=GPIO5) plays the engine control unit: every 500 ms it
// broadcasts RPM (0x100), coolant temperature (0x200) and a chatty
// infotainment frame (0x300). CAN_DASH (CS=GPIO15) is the dashboard:
// its acceptance mask/filters admit ONLY 0x100 and 0x200, so the 0x300
// frame never reaches its receive buffer — watch the Lab warning.
// Both modules share VSPI: SO=GPIO19, SI=GPIO23, SCK=GPIO18, 3V3, GND.
// On a real bench, tie the two modules' CANH/CANL screw terminals together.
#include <SPI.h>
#include <mcp_can.h>
MCP_CAN CAN_ECU(5); // CS on GPIO5
MCP_CAN CAN_DASH(15); // CS on GPIO15
#define RPM_ID 0x100
#define TEMP_ID 0x200
#define RADIO_ID 0x300 // chatty frame the dashboard filters OUT
unsigned int rpm = 800;
int coolant = 62;
void setup() {
Serial.begin(115200);
if (CAN_ECU.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK) {
Serial.println("ECU node online (CS=5)");
}
if (CAN_DASH.begin(MCP_ANY, CAN_500KBPS, MCP_8MHZ) == CAN_OK) {
Serial.println("Dashboard node online (CS=15)");
}
// Dashboard only listens to 0x100 (RPM) and 0x200 (coolant).
CAN_DASH.init_Mask(0, 0, 0x7FF);
CAN_DASH.init_Filt(0, 0, RPM_ID);
CAN_DASH.init_Filt(1, 0, TEMP_ID);
CAN_DASH.init_Mask(1, 0, 0x7FF);
CAN_DASH.init_Filt(2, 0, RPM_ID);
CAN_DASH.init_Filt(3, 0, TEMP_ID);
CAN_DASH.init_Filt(4, 0, RPM_ID);
CAN_DASH.init_Filt(5, 0, TEMP_ID);
CAN_ECU.setMode(MCP_NORMAL);
CAN_DASH.setMode(MCP_NORMAL);
Serial.println("Filters set: dashboard accepts 0x100 + 0x200 only");
}
void loop() {
// --- ECU side: publish engine data ---
rpm = rpm + 350;
if (rpm > 4500) {
rpm = 800;
}
coolant = coolant + 3;
if (coolant > 95) {
coolant = 62;
}
byte rpmFrame[8];
rpmFrame[0] = rpm >> 8;
rpmFrame[1] = rpm & 0xFF;
CAN_ECU.sendMsgBuf(RPM_ID, 0, 2, rpmFrame);
byte tempFrame[8];
tempFrame[0] = coolant;
CAN_ECU.sendMsgBuf(TEMP_ID, 0, 1, tempFrame);
byte radioFrame[8];
radioFrame[0] = 42;
CAN_ECU.sendMsgBuf(RADIO_ID, 0, 1, radioFrame); // filtered out at the dash
// --- Dashboard side: drain the receive buffer ---
while (CAN_DASH.checkReceive() == CAN_MSGAVAIL) {
unsigned long rxId;
byte len = 0;
byte buf[8];
CAN_DASH.readMsgBuf(&rxId, &len, buf);
if (rxId == RPM_ID) {
unsigned int r = (buf[0] << 8) | buf[1];
Serial.print("DASH RPM gauge: ");
Serial.println(r);
} else if (rxId == TEMP_ID) {
Serial.print("DASH Coolant: ");
Serial.print(buf[0]);
Serial.println(" C");
} else {
Serial.print("DASH unexpected frame 0x");
Serial.println(rxId, HEX);
}
}
delay(500);
}Built with FluxBench.AI — the AI workbench for embedded projects.