The Controller Area Network (CAN) is a robust, multi-master communication protocol widely used in automotive, industrial, and energy systems due to its real-time capabilities and high noise immunity. The EQSP32 IIoT micro-PLC product family natively supports CAN-bus communication, making it ideal interfacing with:
- Other microcontrollers/PLCs
- Motor controllers
- Battery Management Systems (BMS)
- Sensors
- Human-Machine Interfaces (HMIs)
If you are familiar with CAN-Bus hardware setup and network configuration, you may want to skip directly to the code example.
CAN-Bus hardware setup
Before implementing CAN communication, it’s important to understand the physical connection requirements.
The EQSP32 features an onboard CAN transceiver, allowing direct connection to other CAN-compatible devices via its CANH (CAN High) and CANL (CAN Low) differential signal terminals.

To establish a working CAN network:
- Add 120Ω termination resistors at both ends of the bus to ensure signal integrity.
- Connect all devices in a linear bus topology (daisy-chain).
- Use twisted pair cabling to minimize electromagnetic interference.
Ensure all nodes on the CAN network share a common ground (GND) for reliable communication.
The following image demonstrates how to wire your EQSP32 PLC on the CAN network. As mentioned before, the common GND connection is mandatory for reliable communication.
EQSP32’s features an on-board 5V DC power supply which may be used to power additional nodes on the network.
CAN-Bus network configuration
Before sending or receiving messages, the EQSP32 PLC (and any connected node) needs to be properly configured for CAN-Bus communication. This setup ensures your device is synchronized with the rest of the CAN network and only processes the relevant messages.
When working with the EQSP32 wrapper functions— designed to simplify the esp32 CAN-Bus setup and communication—the parameters that must be configured for successful communication are the following:
- Bitrate selection (from 25k to 1M bits per second): All CAN devices on the network must operate at the same bitrate to communicate reliably. Typical bitrates include:
- 125 kbps for industrial networks
- 500 kbps for automotive and general-purpose applications
- Message Filtering: The EQSP32 wrapper functions offer the most useful filtering modes for incoming CAN frames:
- Accept All (Default): Receives all incoming CAN messages, regardless of CAN ID.
- Filter by CAN ID: Receives only messages matching the specified 11-bit CAN identifier.
- Loopback Mode: Receives all incoming messages, including its own transmitted messages. Ideal for local testing without a second device.
Note: When loopback is enabled, you can still set a specific CAN ID to filter only those messages, including self-transmitted ones.
Manual CAN-Bus network configuration is also possible by bypassing the EQSP32 wrapper functions, getting full control in setting up the CAN-Bus peripheral.
CAN-Bus frame structure
A CAN bus frame is a structured data packet used to transmit information over the Controller Area Network (CAN).
Each frame includes multiple fields, such as the start of frame (SOF), identifier, control bits, data payload, CRC (for error checking), acknowledgment (ACK), and end of frame (EOF).
The bus operates using dominant (logical 0) and recessive (logical 1) states, with dominant bits overriding recessive ones to manage arbitration and ensure reliable communication.
Preparing a CAN frame for EQSP32
When using the EQSP32 library (or the esp32 TWAI directly), you only need to configure a few essential fields to construct and send a valid standard CAN frame:
- Identifier (or recipient’s CAN ID): A unique 11-bit address for the message. All receivers use this field for arbitration and filtering. Choose a value that is unique and compatible with the message filtering configuration (e.g. 0x120).
- Data length code (DLC): This defines how many bytes of data are being sent (0–8 bytes). The actual data must match this length.
- Data payload: The actual information to be transmitted. Each byte is placed into a separate position in the data array. Only DLC number of bytes will be sent from the array.
All other fields in the CAN frame—such as CRC, RTR, and ACK—are handled automatically by the EQSP32 library and the hardware CAN controller.
CAN-Bus code example using EQSP32 wrapper functions
The following example demonstrates how to initialize and use the CAN interface on the EQSP32 PLC, by showing three different configurations:
- Demo configuration 1 – Accept All Messages: The EQSP32 receives all incoming traffic from the CAN bus, regardless of identifier (except of its own messages).
- Demo configuration 2 – Loopback + Accept All: Useful for testing with or without other CAN nodes. The device receives all incoming traffic and its own messages.
- Demo configuration 3 – Filter by CAN ID: Only processes messages matching a specific identifier (in this case
0x123).
This example also shows how to send a CAN message every 2 seconds using a timer and how to read incoming messages using the EQSP32 PLC’s receive function.
You may also find this example in the EQSP32 library examples and on GitHub: EQSP32 CAN-Bus Demo.
A separate example is available for demonstrating how to directly configure and control the CAN peripheral using TWAI functions for full customization and control: EQSP32_CAN-Bus_getPin_Demo.
It is recommended that the loopback configuration of this example is used first, to validate proper EQSP32 setup and wiring, before testing with a bigger network.
/**
* @file EQSP32_CAN-Bus_Demo.ino
* @brief Demonstrates CAN Bus communication using EQSP32 library functions.
*
* This example:
* - Initializes CAN interface at 500 kbps.
* - Supports three configuration modes:
* 1. Accept all messages
* 2. Accept all + loopback (self-testing)
* 3. Filter exact CAN ID
*
* - Sends a CAN message every 2 seconds.
* - Receives and prints CAN messages if available.
*
* Hardware:
* - EQSP32 connected to a CAN bus network via CAN_TX/CAN_RX pins.
* - Ensure 120-ohm termination at both ends of the CAN bus.
*
* @date 2025-04-04
* @author Erqos Technologies
*/
#include <EQSP32.h>
EQSP32 eqsp32;
// === Configuration Constants ===
#define CAN_CONFIG 1 // Set to 1, 2, or 3 to select mode
#define SEND_INTERVAL 2000 // Transmit a frame every 2 seconds
uint32_t exactCanId = 0x123; // Used in CAN_CONFIG 3 of this example
uint8_t messageCounter = 0;
EQTimer txFrameTimer(SEND_INTERVAL);
/**
* @brief Configure the CAN interface with the selected mode.
*
* Modes:
* 1. Accept All IDs: receives all CAN messages from other nodes.
* eqsp32.configCAN(CAN_500K);
*
* 2. Loopback + Accept All: used for self-testing — receive even your own transmitted frames.
* eqsp32.configCAN(CAN_500K, 0, true);
*
* 3. Filter by exact Standard CAN ID:
* eqsp32.configCAN(CAN_500K, 0x123);
* // Only receives messages with identifier == 0x123
*/
bool initCANBus() {
switch (CAN_CONFIG) {
case 1:
Serial.println("🔧 Mode 1: Accept All Messages");
return eqsp32.configCAN(CAN_500K); // Accept all incoming messages
case 2:
Serial.println("🔧 Mode 2: Accept All + Loopback");
return eqsp32.configCAN(CAN_500K, 0, true); // Accept all + loopback (also accepts own messages)
case 3:
Serial.println("🔧 Mode 3: Filter by Exact ID (0x123)");
return eqsp32.configCAN(CAN_500K, exactCanId); // Exact ID (only receives ID 0x123), not own messages
default:
Serial.println("❌ Invalid CAN_CONFIG. Set to 1, 2, or 3.");
return false;
}
}
void setup() {
Serial.begin(115200);
Serial.println("\n🚗 Starting EQSP32 CAN Bus Demo...");
eqsp32.begin(); // EQSP32 setup (non-verbose)
if (initCANBus()) {
Serial.println("✅ CAN interface initialized successfully.");
} else {
Serial.println("❌ Failed to initialize CAN interface.");
while (true); // Halt
}
txFrameTimer.start();
}
void loop() {
// Transmit every 2 seconds
if (txFrameTimer.isExpired()) {
txFrameTimer.reset();
transmitForCANMessage();
}
checkForCANMessage();
delay(10); // FreeRTOS-friendly loop delay
}
void transmitForCANMessage() {
CanMessage txMsg = {};
txMsg.identifier = 0x120; // Default outgoing ID; may not be received if filtered
txMsg.data_length_code = 4;
txMsg.data[0] = messageCounter++;
txMsg.data[1] = 0x11;
txMsg.data[2] = 0x22;
txMsg.data[3] = 0x33;
if (eqsp32.transmitCANFrame(txMsg)) {
Serial.printf("✉️🡆\tTX: ID=0x%03X\tData=[ %02X %02X %02X %02X ]\n",
txMsg.identifier, txMsg.data[0], txMsg.data[1],
txMsg.data[2], txMsg.data[3]);
} else {
Serial.println("⚠️ Failed to send CAN message.");
}
}
void checkForCANMessage() {
CanMessage rxMsg;
if (eqsp32.receiveCANFrame(rxMsg)) {
Serial.printf("🡄✉️\tRX: ID=0x%03X\tData=[ ", rxMsg.identifier);
for (int i = 0; i < rxMsg.data_length_code; ++i) {
Serial.printf("%02X ", rxMsg.data[i]);
}
Serial.printf("]\tDLC=%d\n", rxMsg.data_length_code);
}
}
The example begins by calling eqsp32.begin() to initialize the EQSP32 system. The initCANBus() function applies the selected of three CAN configuration demos, using the eqsp32.configCAN() function.
Once initialized, the main loop does the following:
- Sends a CAN frame every 2 seconds using
transmitForCANMessage(). - Constantly checks for new messages using
checkForCANMessage().
Each transmitted message includes an 11-bit identifier and a 4-byte data payload. Incoming messages are printed to the serial monitor along with their ID and data content.
Troubleshooting tips for CAN-Bus communication
Even with proper configuration, CAN-Bus issues can arise due to hardware, wiring, or bus state conditions. Here are some common problems and how to resolve them:
1. No messages received
- Check bitrate matching: All devices on the bus must operate at the same bitrate (e.g. 500 kbps).
- Inspect filtering configuration: Ensure that your
configCAN()parameters match the desired reception mode (e.g.canIDorloopBack). - Verify message ID: The receiving node will only process messages matching its filter, if set. The loopback flag must be set in order for the EQSP32 to allow its won messages to be received.
- Confirm termination resistors: The CAN bus network must be terminated with 120-ohm resistors at both ends.
2. Driver enters bus-off state (or stucks there)
- Check CAN lines for short: If the CAN lines are accidentally shorted, the EQSP32 CAN peripheral may enter bus-off state.
- Automatic bus-off recovery: EQSP32 automatically handles bus-off recovery when using
transmitCANFrame()andreceiveCANFrame(). - Manual bus-off recovery: If using the esp32 TWAI (Two-Wire Automotive Interface) driver directly (manual configuration), bus-off must be manually detected and the bus recovery process must be initialized.
3. Loopback mode doesn’t receive own messages
- Set loopback flag: Loopback requires the
loopBackflag to be set true,configCAN(..., 0, true)and a matchingcanIDto be set in the message, if a filter is used. UsecanID 0to accept all messages. When in loopback mode, EQSP32 disables message acknowledgment to allow self-reception. - Check no acknowledgment and self reception: If manual configuration is used, both the message acknowledgment and the transmitted message must be properly configured for self reception.
4. CAN transmission fails
- Add a receiving node: Check that another device is actively connected and acknowledges the frame.
- Check wiring: Ensure the TX/RX lines are not reversed.
- Self-test: Use loopback mode to verify that transmission works locally.
Conclusion
The EQSP32 makes CAN-Bus integration simple and efficient—whether you’re building distributed automation systems, interfacing with motor controllers, PLCs or BMS units, it provides the desired ease of use and flexibility.
With native support for message filtering, loopback testing and automatic recovery from bus faults, EQSP32 handles the complexities of CAN communication under the hood, so you can focus on your application logic.
When deeper control is needed, developers can bypass the high level EQSP32 functions and access the ESP32-S3’s TWAI peripheral directly—ideal for advanced setups or protocol-specific implementations.
Explore the examples on GitHub and start integrating real-time, industrial-grade CAN-Bus communication into your automation projects.