Getting started with CAN bus - a practical guide with Arduino
Tags: tutorial, programming, learning, arduino
Published: 2024-02-20 22:42:48


# Intended audience
The purpose of this article is to demonstrate the setup of a CAN bus using Arduino microcontrollers and basic components. It would be of interest to anyone new to CAN bus and/or are interested in creating their own bus for educational purposes.

# Introduction
In the world of embedded systems, reliable communication between devices is vital for the overall operation of the system. One common technology used to achieve this in consumer, commercial, and agricultural equipment is the Controller Area Network or CAN bus. A wealth of detailed information about CAN bus design, implementation, and usage can be found online (see reference section below for some links). As such, this article will only give a high-level intro to the CAN bus and then focus on how to create a simple bus of your own.

# What is CAN bus?
At its core, CAN bus is a networking technology consisting of two wires known as CAN_Low and CAN_High, through which information is sent between Electronic Control Units (ECUs) or bus nodes. Its simple and robust design makes it a popular choice for enabling serial communication within challenging environments, predominantly in industrial and automotive applications.

## Simple communication
**Reducing wiring complexity**

Because the CAN bus is shared by all ECUs connected to it, it enables inter-ECU communication avoiding point-to-point communications, which would end up being a complicated web of wires.

**Broadcast by default**

The default communication behavior for bus nodes is to broadcast ‘what they know’ to the rest of the network. That information is then available to all other nodes on that network. Each ECU can read those messages and decide whether to process that data or simply ignore it.

**Multiplexing for efficiency**

CAN uses serial communication. As such, multiplexing can be used to allow messages to be combined and transmitted, reducing network traffic.

## Network architecture
A device which is connected to the bus is called an ECU or node. A bus can support a minimum of two nodes with the maximum number dependent on the nature of the specific bus (length of wire, chattiness of each node, etc) and the version of CAN used (CANopen supports 127 unique addresses. J1929 supports 253 unique addresses).

**Bus design**

- 2 terminating resistors (120 Ohm)
- Two wires – CAN high and CAN low (max 40 meters)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1ppk57elvbq36emfbdr.png)

**Node design**
- Microcontroller
  - process information such as sensor inputs or outputs to turn on lights or an actuator
  - can also process information to control a dashboard or another kind of communication
- CAN controller
  - converts digital information to messages on the bus
- Transceiver
- Connection to a sensor / actuator / display

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fdfz5ir1sklkggc4qcna.png)

## CAN message structure
CAN messages are made up of several elements with the most important being an identifier and the data.

### Identifier field
As mentioned, CAN supports broadcast-based communication (multi-master bus), where messages are sent to all nodes on the network. A message ID not only uniquely identifies the message but also helps with message prioritization when multiple messages are transmitted at the same time. The lower the ID, the higher the message priority.

There are two standards for recording an ID in a CAN message: standard (11-bit) and extended (29-bit). This article focuses on standard. See the references below for more info on CAN message structure and extended IDs.

### Standard CAN frame (V2.0A)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p80znx8qte774845o8qw.png)

- Start of frame (SOF): a dominant bit represents the start of a CAN message.
- 11-bit identifier: uniquely identifies and establishes the priority of the CAN message.
- Remote transmission request (RTR) bit: normally dominant, set to recessive if a node is requesting data from another.
- Identifier extension (IDE) bit: dominant when a standard CAN frame is being sent and not an extended one.
- r0 bit: reserved and not currently used.
- Data length code (DLC) nibble: number of bytes of data in this message.
- Data: size should match DLC value.
- Cyclic redundancy check (CRC): a 16-bit checksum for detecting errors in the transmitted data.
- ACK: if the message is properly received, the receiving node overwrites the recessive acknowledge bit with a dominant bit.
- End of frame (EOF): signifies the end of the CAN message.
- Interframe space (IFS): used as a time delay.

# Building your own bus

We’re going to be building a multi-node bus using a time-of-flight sensor (node one) to measure the distance of an object, and LCD (node 2) to display that distance, and a servo (node 3) to respond to the distance readings. You can imagine this setup in a self-driving system that detects an object and adjusts its course accordingly.

## Components

| **Part** | **Quantity** | **Notes** |
| --- | --- | --- |
| Basic servo (180-degree arc) | 1   |     |
| MCP2515 CAN bus shield | 3   |     |
| VL530X Time of Flight sensor | 1   | The wiring diagram shows an Adafruit sensor. I used a generic component from Amazon as it’s cheaper. |
| Arduino Nano (Rev3.0) | 3   |     |
| LCD with I2C adaptor | 1   | This is a combination of an HD44780 display and a PCF8574 I2C adapter, which simplifies the connection between Arduino and LCD screen. |
| Power adaptor (12v) | 1   |     |
| Mini breadboard | 4   |     |

## Setting up the hardware
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6008e2ew27zg5acmjse0.png)

### Wiring
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t8jdy90pyp5nlvyiy8bl.png)

### Node configuration
**Servo node**

1. Connect servo to Arduino

| **ToF sensor** | **Arduino** |
| --- | --- |
| Gound | Ground |
| VIN | V5  |
| Control | D9  |

1. Connect the CAN bus module to the Arduino

| **MCP2515** | **Arduino** |
| --- | --- |
| Gound | Ground |
| VCC | V5  |
| CS  | D10 |
| SO  | D12 |
| SI  | D11 |
| SCK | D13 |
| INT | D2  |

1. Connect power to Arduino

| **Power** | **Arduino** |
| --- | --- |
| \-  | Ground |
| +   | VIN |

1. Remove the resistor jumper. The CAN bus needs a terminating resistor at each end of the bus. Each CAN shield has a built-in resistor, which is activated by a jumper. As we have 3 nodes but only need two terminating resistors, this jumper should be removed.

**Time of flight node**
1. Connect the sensor to the Arduino

| **ToF sensor** | **Arduino** |
| --- | --- |
| Gound | Ground |
| VIN | V5  |
| SDA | A4  |
| SCL | A5  |

1. Connect the CAN bus module to Arduino. This is the same wiring as for the servo node.
2. Remove the jumper from the MCP2515 module.
3. Connect power to Arduino. This is the same wiring as for the servo node.

**Display node**
1. LCD to Arduino

| **PCF8574 I2C adaptor** | **Arduino** |
| --- | --- |
| Gound | Ground |
| VIN | V5  |
| SDA | A4  |
| SCL | A5  |

1. Connect the CAN bus module to Arduino. This is the same wiring as for the servo node.
2. Connect power to Arduino. This is the same wiring as for the servo node.

## The Arduino code

### Required libraries
[Arduino-CAN](https://github.com/sandeepmistry/arduino-CAN)
- Facilitates communication over a CAN bus, allowing the Arduino to send and receive packets within a network of devices.

[Wire](https://github.com/arduino/reference-en/blob/master/Language/Functions/Communication/Wire.adoc)
- Used for I2C communication between the Arduino and the ToF sensor, enabling the sensor's data to be read.

[vl53l0x-arduino](https://github.com/pololu/vl53l0x-arduino)
- Used to interface with the VL53L0X ToF sensor, making it easier to initialize the sensor and read distance measurements.

[LiquidCrystal_I2C](https://github.com/johnrickman/LiquidCrystal_I2C)
- Facilitates communication with an I2C LCD, making it possible to show text or numbers on the screen.

### Code walk-through
A high-level overview of the code. See the sketches for additional comments.

**TimeOfFlightNode.ino**
- [Github repo](https://github.com/BitsByO/CAN-bus/tree/main/TimeOfFlightNode)

Gets distance measurements from a Time-of-Flight sensor and sends these as CAN messages to other nodes on the network.

Message transmission
- beginPacket function: prepares the CAN controller to send a new message by setting up the necessary parameters for the message.
  - Message ID: a unique identifier for the CAN message, allowing receiving nodes to recognize the type or purpose of the message. In the sketches, g_canID represents this ID, distinguishing messages from the Time-of-Flight sensor.
  - DLC (Data Length Code): specifies the length of the data in bytes that the message will carry. For the Time-of-Flight measurements, the DLC is set based on the size of the data being sent (2 bytes for the distance measurement).
  - RTR (Remote Transmission Request): flag indicating whether the message is a data frame (actual data being sent) or a remote request frame (a request for data). In this use case, RTR is set to false because the node is sending actual data, not requesting it.
- After setting up the message with beginPacket, the data (distance measurement) is added using CAN.write, and the transmission is finalized with CAN.endPacket.

Setup
- The setup() function initializes serial communication for debugging, sets up I2C communication, initializes the ToF sensor, and attempts to start CAN communication.

Main Loop
- The loop() function contains the logic for continuously reading distance measurements from the ToF sensor and sending these measurements over the CAN bus if the bus is active.

Error Handling
- It incorporates error handling to manage timeouts or failures in reading from the sensor or communicating over the CAN bus.

**DisplayNode.ino**
- [Github repo](https://github.com/BitsByO/CAN-bus/tree/main/DisplayNode)

Its main role is to listen for messages on the CAN bus and display the contents of specific packets on an LCD. It demonstrates how an Arduino can be integrated into a CAN network as a receiver node, focusing on presenting received data in a human-readable format.

Receiving CAN Messages
- Listening for messages: These sketches continuously check for incoming CAN messages using CAN.parsePacket. This method checks if there is a new message available on the bus and prepares it for processing.
- Identifying relevant messages: Upon receiving a message, the sketches check the message ID to determine if it's relevant to their function. This is where the message ID set during transmission is important; it allows receivers to filter messages based on their intended processing logic.
- Processing message data: Once a relevant message is identified, its data (extracted using CAN.read) is processed and displayed on an LCD.

Setup
- The setup() function initializes serial communication, the LCD, and the CAN bus. It sets up the LCD with the specified dimensions and turns on its backlight for visibility. It also attempts to start CAN communication at the predefined speed, preparing the device to receive messages.

Main Loop
- The loop() function is where the device continuously listens for CAN messages. When a message is received, the code checks if the packet ID matches a predefined value, indicating it's a message of interest (for example, a distance measurement from a sensor). If the message is relevant, its contents are extracted and displayed on the LCD.

Error Handling
- The code includes basic error handling and feedback mechanisms. For instance, if the CAN bus is not active or fails to initialize, the LCD will display an error message. This immediate feedback helps in diagnosing connectivity issues on the network.

**ServoNode.ino**
- [Github repo](https://github.com/BitsByO/CAN-bus/tree/main/ServoNode)

Allows an Arduino to control a servo motor based on the data it received from the CAN bus. It demonstrates integrating CAN bus communications with physical control using Arduino.

Receiving CAN Messages
- Similar to how DisplayNode receives messages with the main difference being how the messages are dealt with. In this case, measurement readings are converted to movements of the servo.

Setup
- In the setup() function, the code initializes serial communication for debugging purposes, configures the servo motor by attaching it to the specified pin, and attempts to start CAN communication at the defined speed. This prepares the Arduino to receive data and control the servo accordingly.

Main Loop
- The loop() function periodically checks for incoming CAN messages. When a message is received, it verifies if the packet ID matches a predefined value, indicating that the message contains relevant data for this node.

Error Handling
- The sketch includes mechanisms for handling errors in CAN communication and potentially feedback mechanisms to ensure the servo can be controlled safely and within its operational limits.

# In action
A quick demo of the CAN bus in action.
<https://youtube.com/shorts/g0CQypjMR1Q?feature=share>

# Conclusion
In this article, we've navigated the essentials of setting up and operating a CAN bus system using Arduino, covering the theoretical underpinnings and practical applications, from wiring and hardware setup to coding for data transmission and reception. We've seen how CAN bus can facilitate communication in embedded systems, allowing devices to share data with minimal wiring complexity. I hope it provides you with a foundation of knowledge that allows you to explore further and embark on your own CAN bus projects.

# References
- [CAN BUS PROTOCOL - 10 MINUTE LESSON](https://store.chipkin.com/articles/can-bus-protocol-10-minute-lesson)
- [Introduction to CAN](https://www.allaboutcircuits.com/technical-articles/introduction-to-can-controller-area-network/)

**Examples of CAN bus in industry**
- Consumer automotives: [ODB2](https://www.csselectronics.com/pages/obd2-data-logger-sd-memory-convert)
- Commercial vehicles: [j1939](https://www.kvaser.com/about-can/higher-layer-protocols/j1939-introduction/)
- Agricultural equipment: [ISOBUS](https://www.csselectronics.com/pages/isobus-introduction-tutorial-iso-11783)