Skip to content

Serial Connection between Raspberry Pi and Raspberry Pico

By Sebastian Günther

Posted in Microcontrollers, Raspberry_pi_sbc, Raspberry_pico, Micropython

At the beginning of 2021, I started my robot project. One of its subprojects was to get familiar with the Arduino and C-Programming. The Arduino is unchallenged in terms of sensor and actuator libraries, and unparallelled in the "connect and it works" experience. However, in the middle of the year, I made the decision to switch to using the Raspberry Pico and MicroPython to implement my Robot. This choice is motivated by using one and only one programming language for the Robotics middleware, the SBD and the Microcontroller, as well as to tap into the powerful Python libraries for image recognition that are offered by the RealSense D435 camera.

To get this started, I need to re-learn the essentials of Serial connections between the Raspberry Pico running MicroPython and the Raspberry Pi running Python. This article explains the basics for making a successful connection.

Hardware Connection Options

To connect from the Pi to the Pico, there are three options:

  • Direct USB-to-USB
  • Direct Tx/Rx Pins
  • USB-TTL to Rx/Rx

Since the Raspberry Pico is still relatively young, the technical maturity of the MicroPython stack influences which of these connections can be used.

When you use option A or C you can jump ahead to the examples. If you want to use option B, you need enable the Tx/Rx pins with the steps described in the next section.

How to Enable Tx/Rx Pins on Raspberry Pi

Following this great article, you need to do the following:

  • Initialize Serial Port via Raspi-Config

    $> sudo raspi-config
    
    => 3. Interface Option
    => P6. Serial Port
      =>  Would you like a login shell to be accessible over serial?
          Answer with 'No'
      =>  Would you like the serial port hardware to be enabled?
          Answer with 'Yes'
    
  • Disable the /dev/ttyS0 service

    $> sudo systemctl stop serial-getty@ttyS0.service
    $> sudo systemctl disable serial-getty@ttyS0.service
    
  • Remove the console from booting

    $> sudo nano /boot/cmdline.txt
      => Remove or comment out a line that says 'console=serial0,115200'
    

Interestingly, the Pi will still send system messages over the Tx/Rx pins - this might be a particular use case you have been looking for. Here is example output when I shutdown the Pi.

>> [  OK  ] Stopped LSB: automatic crash report generation.
>> [  OK  ] Stopped User Manager for UID 1000.
>> [  OK  ] Stopped Login Service.
>> RPI 4 Model B (0xc03111)

Sending Data from Pi to Pico

Sending data from the Raspberry Pi to the Pico is the same code in all examples, with a minor modification: The port to which the Pico is connected.

Use this boilerplate code:

# sender.py
import time
import serial

ser = serial.Serial(
  port='/dev/ttyS0', # Change this according to connection methods, e.g. /dev/ttyUSB0
  baudrate = 115200,
  parity=serial.PARITY_NONE,
  stopbits=serial.STOPBITS_ONE,
  bytesize=serial.EIGHTBITS,
  timeout=1
)

msg = ""
i = 0

while True:
    i+=1
    print("Counter {} - Hello from Raspberry Pi".format(i))
    ser.write('hello'.encode('utf-8'))
    time.sleep(2)

Explanation:

  • Line 2: Import the serial library that enables the serial connection
  • Line 4: Create a serial object that is configures exactly as the default Pico UART configuration (baudrate 115200, bytesize 8bits etc.). The port section needs to be customized according to the connection methods - see details below
  • Line 19: To send data, you first encode the string data, and then write it to the serial object

Now let’s examine the receiver side.

Receiving Data: USB to USB Connection

At the time of writing this article, the most recent version of MicroPython running on the Pico is MicroPython v1.16 on 2021-06-18. This version does not have a build-in library for serial communication over USB.

However, in this Pico Forum thread, a user provides a pure MicroPython implementation that spawns a thread on the 2nd Pico CPU Core which listens actively for incoming bytes over USB. I tried this code in my early Robot prototype and could use it to receive messages via USB on the Pico that commanded my robot to move.

Here is my working example:

# receiver.py / USB => USB
import sys
sys.path.append('/radu')

import os

from bot import Bot
from rusb import USB

from _thread import start_new_thread
from time import sleep_ms

radu = Bot('Radu MK1')
usb = USB()

input_msg = None
bufferSTDINthread = start_new_thread(usb.bufferSTDIN, ())

while True:
  input_msg = usb.getLineBuffer()
  if input_msg and 'ros_msg' in input_msg:
    obj = eval(input_msg)
    radu.notify(obj)

  sleep_ms(10)

Receiving Data: Tx/Rx Pin Connection

Direct connection between the Tx/Rx pins means:

  • Connect Ground: Pi PIN 6 to Pico Pin 3
  • Connect Tx to Rx: Pi PIN 8 (GPIO 14) to Pico Pin 1 (GPIO 0)
  • Connect Tx to Rx: Pi PIN 10 (GPIO 15) to Pico Pin 2 (GPIO 1)

As explained above, you also need to configure your Raspberry Pi to enable these pins. When completed, use the following code:

# receiver.py / Tx/Rx => Tx/Rx
import os
import machine
from time import sleep

uart = machine.UART(0, 115200)
print(uart)

b = None
msg = ""

while True:
    sleep(1)
    if uart.any():
        b = uart.readline()
        print(type(b))
        print(b)
        try:
            msg = b.decode('utf-8')
            print(type(msg))
            print(">> " + msg)
        except:
            pass

In this program:

  • Line 5: Create an uart instance by accessing the Pico’s internal machine.UART instance. This comes defined with the same values with which the sender code is configured. Note that the MicroPython documentation also allows you to create an differently configured UART instance, e.g. with a slower baudrate.
  • Line 12: The condition uart.any() returns a positive integer when there is at least one character available on the UART connection.
  • Line 13: To read data, either use read(i) where i is the number of characters, or readline() to read all characters until a newline is reached. Working with single characters is more efficient and faster, working with complete lines is more reliable if you need to exchange complex information.
  • Line 16: A try - except block starts in which ...
  • Line 17: ... the received message is decoded and then printed. If there is any error, the program continues without raising the error.

Receiving Data: USB-TTL to Tx/Rx Pin Connection

First, you need to get a USB-TTL adapter, which comes in at least two different chipsets: FT232RL and CH340g.

Source: Amazon.de, Amazon.de.

Insert the adapter on of your Picos USB ports, and then use dmesg to see how this device is configured.

[326612.390873] usb 1-2: new full-speed USB device number 54 using xhci_hcd
[326612.543888] usb 1-2: New USB device found, idVendor=1a86, idProduct=7523, bcdDevice= 2.64
[326612.543893] usb 1-2: New USB device strings: Mfr=0, Product=2, SerialNumber=0
[326612.543895] usb 1-2: Product: USB Serial
[326612.546221] ch341 1-2:1.0: ch341-uart converter detected
[326612.549000] usb 1-2: ch341-uart converter now attached to ttyUSB0

The last line says ttyUSB0, so the sender code needs to change as follows:

# sender.py / USB-TTL => Tx/Rx
# ...
ser = serial.Serial(
  port='/dev/USB0'
  # ...
)

The code for receiving data remains the same as in the direct Tx/Rx connection.

Example Output

Now lets see our code in action.

Executing the sender.py on the Raspberry Pi outputs this information:

>>>
Counter 1 - Hello from Pi4
Counter 2 - Hello from Pi4
Counter 3 - Hello from Pi4
Counter 4 - Hello from Pi4

And running receiver.py on the Raspberry Pico show this:

%Run -c $EDITOR_CONTENT
(sysname='rp2', nodename='rp2', release='1.16.0', version='v1.16 on 2021-06-18 (GNU 10.2.0 MinSizeRel)', machine='Raspberry Pi Pico with RP2040')
UART(0, baudrate=115200, bits=8, parity=None, stop=1, tx=0, rx=1, txbuf=256, rxbuf=256, timeout=0, timeout_char=1, invert=None)
<class 'bytes'>
b'hello\n'
<class 'str'>
>> hello

Conclusion

This article explained the three options for an connecting a Raspberry Pi with a Raspberry Pico using (Micro)Python. The first option is USB-to-USB and requires an external MicroPython library to receive data. The second option is to connect Rx/Tx directly from both machines. After some configuration of your Pi, this works stable. The third option is to use a USB-FTL to Tx/Rx. No configuration requires, and it works out of the box.

Considering the source code, on the Pi it’s the pyserial library: Open a serial connection object with the same configuration as the Pico (baud rate, stop bit) and send encoded data with serial.write(). On the Pico, its the building machine.UART object which access either the built-in UARTs or is configurable. Use uart.readline() to receives bytes, then decode() the data.