FluxBench.AIShared project · read-only

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.