Binzky
Binzky

Reputation: 33

Serial communication between Raspberry Pi Pico and PC

I'm trying to achieve 2-way communication over USB (COM port) between Raspberry Pi Pico and Windows PC (Python). I'm unable to send from my PC to Raspberry Pi Pico nor the way back. Doesn't affect the LEDs on breadboard, nor messages get printed in terminal.

Code for PC:

import serial
import time

# open a serial connection
s = serial.Serial("COM7", 115200)

print(s)
# blink the led
while True:
    s.write(b"on\n")
    time.sleep(1)
    s.readline().strip()
    s.write(b"off\n")
    time.sleep(1)
    s.readline().strip()

Code on Raspberry Pi Pico:

import time
from machine import Pin
import sys

led = Pin(0, machine.Pin.OUT)
led2 = Pin(2, machine.Pin.OUT)
led2.value(0)
led.value(0)

def led_on():
    led.value(1)

def led_off():
    led.value(0)


while True:
    # read a command from the host
    v = sys.stdin.readline().strip()

    # perform the requested action
    if v.lower() == "on":
        led_on()
        print("Turned on!")
    elif v.lower() == "off":
        led_off()
        print("Turned off!")

What's the smartest way to debug code on the Raspberry Pi Pico? After acquiring the serial connection, standard print debug? Is there any way to use the sequence debugger in Thonny IDE?

I've tried methods from both serial and stdlib libraries without result. For PC I'm using PyCharm, for Raspberry Pi Pico Thonny. After flashing Raspberry Pi Pico I'm disconnecting the serial and run the script in PyCharm with different interpreter.

Upvotes: 2

Views: 18258

Answers (2)

Dacoolinus
Dacoolinus

Reputation: 403

Easiest Solution

Serial communication is purely bi-directional. There cannot be more than 2 devices on a given serial port. In Thonny (and micropython) this is dedicated to loading code. You cannot directly write to the serial port from your computer when a program is running. When a program isn't running on the pico, however, you have access to REPL. You can read a little more about it here. This allows you to call functions directly on the pico. So you could write a program such as this:

from machine import pin

led = Pin(0, machine.Pin.OUT)
led2 = Pin(2, machine.Pin.OUT)
led2.value(0)
led.value(0)

def led_on():
    led.value(1)

def led_off():
    led.value(0)

and call it from REPL by sending >>> led_on()

This is far and away the easiest solution and also gives you access to any function within microPython which may be useful for debugging as you can just print whatever value you want.

More Robust Solution

Code above isn't wrong. It just will not work with Thonny since Thonny has taken over the serial port. If you upload your code as a .u2f file or use a separate debugger (such as a pi or a second pico as a picoprobe) using one of the UART peripherals then you could use the USB port as a serial connection. There is a thread over on the raspberry pi forms where functional 2-way serial code is shown. Similarly, this tutorial walks you through doing it in a background-thread.

Upvotes: 0

Ye Fire
Ye Fire

Reputation: 1

you can use Pico's dual core , create a thread to listen the COM. Look this page to give you a solution( https://forums.raspberrypi.com//viewtopic.php?t=302889 ). Upload a main.py to Pico and release the COM serial (usually COM3 , you can also use PC python to query). Connect Pico MicroUSB , main.py in Pico will run . Then you can use PC python to operate COM to send data.

''' main.py in Pico
#coming from https://forums.raspberrypi.com//viewtopic.php?t=302889
from sys import stdin, exit
from _thread import start_new_thread
from utime import sleep
# 
# global variables to share between both threads/processors
# 
bufferSize = 1024                 # size of circular buffer to allocate
buffer = [' '] * bufferSize       # circular incoming USB serial data buffer (pre fill)
bufferEcho = True                 # USB serial port echo incoming characters (True/False) 
bufferNextIn, bufferNextOut = 0,0 # pointers to next in/out character in circualr buffer
terminateThread = False           # tell 'bufferSTDIN' function to terminate (True/False)
#
# bufferSTDIN() function to execute in parallel on second Pico RD2040 thread/processor
#
def bufferSTDIN():
    global buffer, bufferSize, bufferEcho, bufferNextIn, terminateThread
    
    while True:                                 # endless loop
        if terminateThread:                     # if requested by main thread ...
            break                               #    ... exit loop
        buffer[bufferNextIn] = stdin.read(1)    # wait for/store next byte from USB serial
        if bufferEcho:                          # if echo is True ...
            print(buffer[bufferNextIn], end='') #    ... output byte to USB serial
        bufferNextIn += 1                       # bump pointer
        if bufferNextIn == bufferSize:          # ... and wrap, if necessary
            bufferNextIn = 0
#
# instantiate second 'background' thread on RD2040 dual processor to monitor and buffer
# incoming data from 'stdin' over USB serial port using ‘bufferSTDIN‘ function (above)
#
bufferSTDINthread = start_new_thread(bufferSTDIN, ())

#
# function to check if a byte is available in the buffer and if so, return it
#
def getByteBuffer():
    global buffer, bufferSize, bufferNextOut, bufferNextIn
    
    if bufferNextOut == bufferNextIn:           # if no unclaimed byte in buffer ...
        return ''                               #    ... return a null string
    n = bufferNextOut                           # save current pointer
    bufferNextOut += 1                          # bump pointer
    if bufferNextOut == bufferSize:             #    ... wrap, if necessary
        bufferNextOut = 0
    return (buffer[n])                          # return byte from buffer

#
# function to check if a line is available in the buffer and if so return it
# otherwise return a null string
#
# NOTE 1: a line is one or more bytes with the last byte being LF (\x0a)
#      2: a line containing only a single LF byte will also return a null string
#
def getLineBuffer():
    global buffer, bufferSize, bufferNextOut, bufferNextIn

    if bufferNextOut == bufferNextIn:           # if no unclaimed byte in buffer ...
        return ''                               #    ... RETURN a null string

    n = bufferNextOut                           # search for a LF in unclaimed bytes
    while n != bufferNextIn:
        if buffer[n] == '\x0a':                 # if a LF found ... 
            break                               #    ... exit loop ('n' pointing to LF)
        n += 1                                  # bump pointer
        if n == bufferSize:                     #    ... wrap, if necessary
            n = 0
    if (n == bufferNextIn):                     # if no LF found ...
            return ''                           #    ... RETURN a null string

    line = ''                                   # LF found in unclaimed bytes at pointer 'n'
    n += 1                                      # bump pointer past LF
    if n == bufferSize:                         #    ... wrap, if necessary
        n = 0

    while bufferNextOut != n:                   # BUILD line to RETURN until LF pointer 'n' hit
        
        if buffer[bufferNextOut] == '\x0d':     # if byte is CR
            bufferNextOut += 1                  #    bump pointer
            if bufferNextOut == bufferSize:     #    ... wrap, if necessary
                bufferNextOut = 0
            continue                            #    ignore (strip) any CR (\x0d) bytes
        
        if buffer[bufferNextOut] == '\x0a':     # if current byte is LF ...
            bufferNextOut += 1                  #    bump pointer
            if bufferNextOut == bufferSize:     #    ... wrap, if necessary
                bufferNextOut = 0
            break                               #    and exit loop, ignoring (i.e. strip) LF byte
        line = line + buffer[bufferNextOut]     # add byte to line
        bufferNextOut += 1                      # bump pointer
        if bufferNextOut == bufferSize:         #    wrap, if necessary
            bufferNextOut = 0
    return line                                 # RETURN unclaimed line of input

#
# main program begins here ...
#
# set 'inputOption' to either  one byte ‘BYTE’  OR one line ‘LINE’ at a time. Remember, ‘bufferEcho’
# determines if the background buffering function ‘bufferSTDIN’ should automatically echo each
# byte it receives from the USB serial port or not (useful when operating in line mode when the
# host computer is running a serial terminal program)
#
# start this MicroPython code running (exit Thonny with code still running) and then start a
# serial terminal program (e.g. putty, minicom or screen) on the host computer and connect
# to the Raspberry Pi Pico ...
#
#    ... start typing text and hit return.
#
#    NOTE: use Ctrl-C, Ctrl-C, Ctrl-D then Ctrl-B on in the host computer terminal program 
#           to terminate the MicroPython code running on the Pico 
#
try:
    inputOption = 'LINE'                    # get input from buffer one BYTE or LINE at a time
    while True:

        if inputOption == 'BYTE':           # NON-BLOCKING input one byte at a time
            buffCh = getByteBuffer()        # get a byte if it is available?
            if buffCh:                      # if there is...
                print (buffCh, end='')      # ...print it out to the USB serial port

        elif inputOption == 'LINE':         # NON-BLOCKING input one line at a time (ending LF)
            buffLine = getLineBuffer()      # get a line if it is available?
            if buffLine:                    # if there is...
                print (buffLine)            # ...print it out to the USB serial port

        sleep(0.1)

except KeyboardInterrupt:                   # trap Ctrl-C input
    terminateThread = True                  # signal second 'background' thread to terminate 
    exit()
'''

Upvotes: -1

Related Questions