DIY CAN Communication Via Raspberry Pi Zero W and Raspberry Pi Pico
by Adam Kassim in Circuits > Raspberry Pi
2265 Views, 3 Favorites, 0 Comments
DIY CAN Communication Via Raspberry Pi Zero W and Raspberry Pi Pico
This Instructable is about sending data from a Raspberry Pi Zero W to a Raspberry Pi Pico over the CAN communication protocol. The Raspberry Pi's send and receive data through RS485 CAN HATs. The Raspberry Pi Zero W and one of the RS485 CAN HATs can be replaced with any H, L pins from another CAN-enabled device.
Supplies
Enable SPI on the Raspberry Pi Zero W
To start off, we need to enable SPI on the Raspberry Pi Zero W.
- Open up the terminal on the Raspberry Pi Zero W and enter the following command:
sudo raspi-config
- Select Interfacing options
- Select SPI
- Enable SPI
Next, we need to set the specifications of the CAN HAT on the Pi Zero. To do this:
- In the terminal enter the following command:
sudo nano /boot/config.txt
- At the bottom of the config.txt enter the following variables (if your CAN HAT has a 12MHz crystal the variables can be found on the product wiki here) :
dtoverlay= mcp2515-can0, oscillator= 8000000, interupt=25, spimaxfrequency= 1000000
- The SocketCAN module is installed by entering (the SocketCAN module github page can be found here):
sudo apt install can-utils
- Finally, reboot the Pi Zero which can be done in the terminal with:
sudo reboot
This allows the Raspberry Pi Zero W to communicate with the RS485 CAN HAT via the board's built-in SPI bus.
Setting Up the Raspberry Pi Pico
Next, the Raspberry Pi Pico needs to be set up. For this Instructable, I will be using the Thonny IDE if you do not already have it, which can be found here.
- Once the Pico is plugged into the PC, open Thonny and configure the interpreter on the bottom right-hand side of the editor, select Circuit Python (Generic).
- Select Tools tab at the top of the editor and then manage plug-ins.
- Search for the adafruit-circuitpython-mcp2515 plug-in and install it. The library documentation can be found here.
The adafruit library will automatically configure the Pico's required clock, chip select, and SPI pins.
Finally, the Pico must present itself to the host computer as a USB device. In order to do this, a new file called boot.py needs to be created on the Pico with the following code:
import usb_cdc
usb_cdc.enable(console=True, data=True)
Wiring Up the CAN HATs and the Rasperry Pis
Next, we need to connect the RS485 CAN HATs to the Raspberry Pis. We will also need to connect the resistor and LED to the Pico on the breadboard.
The links for the schematics for the RS485 CAN HAT and the Pico can be found below:
- The schematic for the RS485 CAN HAT can be found here.
- The pinout for the Raspberry Pi Pico can be found here.
The following are the pins used on the Pico:
- MISO - Pin 21 (GP16)
- CS - Pin 22 (GP17)
- SCK - Pin 24 (GP18)
- MOSI - Pin 25 (GP19)
- LED Out - Pin 34(GP 28)
- 3v3 Out - Pin 36
- GND - Pin 38
The pins from the Pico are then connected to the corresponding CAN HAT pins using the breadboard jumper wires. The LED is also connected to the Pico on the breadboard and serves to indicate when the Pico is receiving data.
The second CAN HAT is simply placed onto the GPIO pins of the Pi Zero.
Finally, the H and L terminals are connected to each other on the two CAN HATs.
Rasperry Pi Pico Code
To enable the Pico to receive the messages from the CAN HAT the code below is saved to the Pico as code.py, Circuitpython will automatically run the code saved in the code.py when the Pico is connected to a power supply.
# Importing required modules
from time import sleep
import board
import busio
import digitalio
from digitalio import DigitalInOut
from adafruit_mcp2515.canio import Message, RemoteTransmissionRequest
from adafruit_mcp2515 import MCP2515 as CAN
import usb_cdc
# Define the required pins for the CAN HAT
cs = DigitalInOut(board.GP17)
cs.switch_to_output()
spi = busio.SPI(board.GP18, board.GP19, board.GP16)
# Initialise the CAN transiever on the pico and set it to accept external data
can_bus = CAN(spi, cs, loopback=False, silent=True)
c = 0
x = True
# Set the LED GPIO pins
led = digitalio.DigitalInOut(board.GP28)
led.direction = digitalio.Direction.OUTPUT
# Initialize the USB device
usb_device = usb_cdc.data
# Set the receiver to listening and display the messages and sender ID
while True:
with can_bus.listen(timeout=1.0) as listener:
message_count = listener.in_waiting()
for _i in range(message_count):
msg = listener.receive()
print("Message from: ", hex(msg.id))
# Check if the CAN HAT is trying to write data over the GPIO pins
if isinstance(msg, Message):
print("Message data:", msg.data)
led.value = True
sleep(0.5)
led.value = False
sleep(1)
x = True
while x == True:
# Write message to serial port
usb_device.write(msg.data)
sleep(1)
x = False
Send CAN Message From Pi Zero
First, the CAN network is configured in the terminal on the Pi Zero. To do this the following is entered into the Zero's terminal:
- Configure the data transmission bitrate.
sudo ip link set can0 type can bitrate 125000
- Set up the CAN network.
sudo ip link set can0
Next, we can send a message over the can0 network.
- Send the message "Can Test" with ID 456.
cansend can0 456#43414e2054657374
With the Pico plugged in the LED will start flashing, indicating that the Pico is receiving data. The data being received will also print to the terminal with the sender ID and message data shown (shown in the image above).
Receiving and Displaying Message on Host Computer
In order to display the data coming from the Pico I used python, and tkinter.
The modules required can be installed by opening the command prompt on the host computer and entering:
pip install functools
pip install pyserial
Once the modules are installed the following CAN_UI.py script can be created and run with the Pico plugged in.
import functools
from tkinter import *
import serial.tools.list_ports
# List of available ports
portsList = serial.tools.list_ports.comports()
# Blank serial object
srl_Obj = serial.Serial()
# Creating instance of tkinter
root = Tk()
root.config(bg='grey')
root.title('CAN UI')
# Declare function that sends index of button
def initComport(index):
currentPort = str(portsList[index])
comPortVar = str(currentPort.split(' ')[0])
srl_Obj.port = comPortVar
srl_Obj.baudrate = 115200
srl_Obj.open()
# Loop through each entry in list and create button
for port in portsList:
button = Button(root, text=port, font=('Calibri', 13), height=1, width=45,
command=functools.partial(initComport, index=portsList.index(port)))
button.grid(row=portsList.index(port), column=0)
# Create data window
canvas = Canvas(root, width=600, height=400, bg='white')
canvas.grid(row=0, column=1, rowspan=100)
# Create a scroll bar
vsb = Scrollbar(root, orient='vertical', command=canvas.yview)
vsb.grid(row=0, column=2, rowspan=100, sticky='ns')
# Create data Canvas
canvas.config(yscrollcommand=vsb.set)
# Create data frame
frame = Frame(canvas, bg='white')
canvas.create_window((10, 0), window=frame, anchor='nw')
# Function that checks for incoming data
def checkSerialPort():
if srl_Obj.isOpen() and srl_Obj.inWaiting:
packet = str(srl_Obj.read(size=8))
recentPacketString = str(packet.split("'")[1])
Label(frame, text='Message data: ' + recentPacketString, font=('Calibri', 13), bg='white').pack()
# Main Loop
while True:
root.update()
checkSerialPort()
canvas.config(scrollregion=canvas.bbox('all'))
The code above uses tkinter to create an interactive GUI that displays the available comports detected with pyserial and displays the incoming serial data over the selected COM port (the code above is modified from YouTube creator Von and the link to the video can be found here).