Quantcast
Channel: Raspberry Pi Forums
Viewing all articles
Browse latest Browse all 8082

General • Raspberry Pi Pico serial behaviour with CTS/RTS

$
0
0
RPI Pico serial behaviour with CTS/RTS
Hi,
I am converting the 'bottom' layer of a project from Arduino to use a Pico 2 instead.
The upper layer ('Host') is an Rpi3B+ for development and will then go back to a Jetson Nano - all that just for the 'big picture'
At the bottom layer of this the two halves communicate with MQTT messages and use a serial connection between these boards.

And just to add a challenge I decided to convert the Arduino C/C++ code to MicroPython - what could possibly go wrong?
The basic serial-2-serial connection was easy enough but when it was bolted back into the 'real program' it became apparent that the link/code was not good enough for so many messages and of increasing length so I reverted back to class modules and their test harnesses

Messages FROM the Pico to the Rpi3 seem to survive OK.
However messages from the Rpi3 TO the Pico were being chopped up - mostly partial messages received. I revised the interface to include known start and end characters so I could see things.
Several days later I decided that I would have to add hardware flow control (on the PICO its DIY if you want software flow control)
This was easy to implement on the Pico but quite the effort on the Rpi3 to get the settings to stick across a reboot.

Now cutting to the chase.. From the attached image I can see that at some point in the transmission the RTS line goes High and the flow of characters from the RPI stop.
That is how I understand flow control should work. Then a bit later RTS drops and the flow continues.
(I conclude from this behaviour that the flow control setup from each end is correct)

My problem seems to be that the Pico does not 'wait' rather it delivers each bit as whole message and of course they dont pass muster because one has no tail and one has no head.

This is the first time I have played with CTS/RTS flow control so I have obviously got something wrong in my understanding.
I am hoping some of you could explain how flow control should be treated from the program's perspective. ie what does 'wait for the receive buffer to be emptied' actually mean I should do?
The receive logs below show 2 messages fail and and one succeeds.
Note: 300 bytes has been selected for this test to try and find where the edge case is.

Code:

#############  Sending side (Rpi3)Host_ser  Serial port open baud= 230400 and timeout = 0.05 ***** Starting RPI Serial TestHarness2 / (RPI-T3)        mainThrottle = 0.001 (RPI-T3) sending      Host_ser sending len=303 mess3 = b'\x1eUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU\x1e\n' (RPI-T3) sending UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUHost_ser sending len=8 mess3 = b'\x1e     \x1e\n' (RPI-T3) sending      Host_ser sending len=303 mess3 = b'\x1eUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU\x1e\n' (RPI-T3) sending UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUHost_ser sending len=8 mess3 = b'\x1e     \x1e\n' (repeats)
############# Receiving side (Pico)

Code:

PICO_ser  Serial port open baud= 230400 and timeout = 0***** Starting Pico Serial TestHarness2 / (Pico-T3)        mainThrottle = 0.1PICO_ser messageBytes is len = 274PICO_ser messageBytes = b'\x1eUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'PICO_ser  rc=1 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>PICO_ser  messageBytes is len = 274PICO_ser  messagesBytes   = b'\x1eUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'(Pico-T3)  rc=1 = Full message did not arrive (corruption or timeout or buffer overflow?)(Pico-T3)  rc=1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<PICO_ser messageBytes is len = 303PICO_ser messageBytes = b'\x1eUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU\x1e\n'PICO_ser  returning messageList = ['UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'](Pico-T3) have 1 response(s)= ['UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU']PICO_ser messageBytes is len = 278PICO_ser messageBytes = b'\x1eUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'PICO_ser  rc=1 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>PICO_ser  messageBytes is len = 278PICO_ser  messagesBytes   = b'\x1eUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU'(Pico-T3)  rc=1 = Full message did not arrive (corruption or timeout or buffer overflow?)(Pico-T3)  rc=1 <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Pico_RTS_high_at_buffer_full_longer_msg_img36.png
The code has 4 modules - 2 on the pico end and 2 on the Rpi end. 1 the class and one the driver.
Pico Class

Code:

# serial-2-serial has 2 parts 'TestHarness' and (dumb) Serial_in_out# and Serial_in_out has several flavours, Pico, RPi, Jetson...from machine import UART, Pin # we need to import UART to use itimport timeclass SerialInOut:    ########## Class Attributes (shared by all instances of the class)    #  Record seperator (RS) = 0x1E  = 30 decimal    RS  = '\x1E'    #  chr(30) Type = <class 'str'>    RSb = b'\x1E'   #          Type = <class 'bytes'>    NL  = '\x0A'    # one end calls this NL and one LF but that is the same char(10) == \x0A    NLb = b'\x0A'     # MAXMsgLen = 4000    G_MyName = "Pico SerialInOut"    G_MyTag  = "PICO_ser "    ## Constructor(s) *********************************************************************    # WARNING for normal Python (RPI) timeout is 'maximum time to wait for the requested number of bytes to be received'    #         for microPython (Pico)  timeout is 'time (in milliseconds) to wait for the FIRST CHARACTER to arrive'    def __init__(self,     UartNo=0, TXPinNo=16, RXPinNo=17, baudrate=57600, timeout=0, CTSPinNo=18, RTSPinNo=19, port="tester"):        ########## instance Attributes        self.serNum     = UartNo        self.port       = port  #NA for Pico        self.baudrate   = baudrate        #self.timeoutSec = timeout       # Rpi3 wants sec        self.timeoutMs  = timeout       # Pico wants ms        self.txPin      = Pin(TXPinNo)        self.rxPin      = Pin(RXPinNo)        self.CTSPin     = Pin(CTSPinNo)        self.RTSPin     = Pin(RTSPinNo)                ########## Constructor actions        # Step 1. initialize the UART        try:            self.ser = UART(self.serNum,                baudrate = self.baudrate ,                 tx       = self.txPin,                 rx       = self.rxPin,                 timeout  = self.timeoutMs,                 cts      = self.CTSPin,                rts      = self.RTSPin                )            self.ser.init(rxbuf = 512,      # default was 256                # timeout_char = 1000,    no effect                flow     = UART.RTS | UART.CTS                )            print(f"{self.G_MyTag} Serial port open baud= {self.baudrate} and timeout = {self.timeoutMs} ")            print(f"{self.ser}")        except:            print(f"{self.G_MyTag} Error opening Serial / UART on pins {self.txPin}: {self.txPin}")            raise        # Give the serial port time to initialize        time.sleep(0.5)    ########## Class Methods    def send(self, message):        mess2 = self.RS + message + self.RS + self.NL        mess3 = mess2.encode('utf-8')                       # split out so we can print it 1st        print (f"{self.G_MyTag}sending mess3 = {mess3} ")            self.ser.write(mess3) # Encode the string to bytes        # self.ser.write(mess2.encode('utf-8')) # Encode the string to bytes and send in one step    # READ     # returns a 'rc' also    #   0 = OK    #   1 = Full message did not arrive (corruption or timeout or buffer overflow?)     #   2 = Fail - 1st byte is not a RS    #   3 = Fail - last byte is not a RS    #  and returns the message list or an empty list if there is nothing waiting or there is an error    def receive(self):        rc = 0        # if self.ser.in_waiting > 0:     # Rpi        if self.ser.any():                # Pico            #messageBytes = self.ser.read_until()          #  RPI    was (self.MAXMsgLen)            messageBytes = self.ser.readline()          #  Pico     was (self.MAXMsgLen)            # print (f"{self.G_MyTag} messageBytes is of type {type(messageBytes)} ")  # <class 'bytes'>            print (f"{self.G_MyTag} messageBytes is len = {len(messageBytes)} ")     # 1993 Next one was 4486!            print (f"{self.G_MyTag} messageBytes = {messageBytes} ")                        # stop compiler errors/warnings            if messageBytes is None:                return rc, []            # validation            # did the full message arrive including the NL?            if messageBytes[-1] != self.NLb[0]:     # RSb[0]:                rc = 1  #print(" Full message did not arrive (corruption or timeout or buffer overflow?) ")             elif messageBytes[0] != self.RSb[0]:                rc = 2  #print(" Fail: 1st byte is not a RS")    # This is important - we can receive just the tail of a message            elif messageBytes[-2] != self.RSb[0]:                rc = 3  #print(" Fail: last byte is not a RS")             if rc != 0:                print (f"{self.G_MyTag} rc={rc} >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>")                 print (f"{self.G_MyTag} messageBytes is len = {len(messageBytes)} ")     # 1993 Next one was 4486!                #print (f"{self.G_MyTag} messagesBytes!r = {messageBytes!r} ")  # !r : Applies repr() to the value, providing a developer-focused representation useful for debugging.                print (f"{self.G_MyTag} messagesBytes   = {messageBytes} ")                  self.ser.read()                    # Pico try and purge the buffer                 # self.ser.reset_input_buffer()    # RPI  try and purge the buffer                 return rc, []            messages = messageBytes[:-1].decode('utf-8') # , in pico there is no option > "errors='ignore' " # this .decode() removes the byte string format; strip() removes all common leading and trailing whitespace             # print (f"     messages = {messages} ")            tempList    = messages.split(self.RS)  # this will create empty items - especially at <end> <start> sequences            messageList = list(filter(None, tempList))  #delete all empty items            print(f"{self.G_MyTag} returning messageList = {messageList}")            return rc, messageList        else:            return rc, []
Driver / Test Harness

Code:

# 2026/02/04 V3 enabling hardware flow control (CTS/RTS)#               (just pass the pin numbers in)#from serial_in_out_RPi import SerialInOutfrom serial_in_out_pico import SerialInOutfrom machine import Timerimport time# ================================================================# ===  Call-back Functions for Threads# ================================================================    ##############################################################################    # handle_burst_mode_trigger    #   just the variable to trigger the output    ##############################################################################def handle_burst_mode_trigger(t):      # Pico has parm 't'#def handle_burst_mode_trigger():      # Rpi has no Parm    global G_doSendBurst    G_doSendBurst     = True # ================================================================# ===  Initialize# ================================================================G_MyName = "Pico Serial TestHarness3"G_MyTag  = "(Pico-T3) "# instantiate a SerialInOut object# for baudrate choose from 115200, 230400, 460800 (obviously both end have to be the same)test_uart = SerialInOut(UartNo=0, TXPinNo=16, RXPinNo=17, baudrate=230400, CTSPinNo=18, RTSPinNo=19)  # for Pico need to specify which Uart and pins# test_uart = SerialInOut(baudrate=230400, port='/dev/serial0')  # for RPi-3 "/dev/ttyUSB0 is the USB-2-serial breakout, /dev/serial0 is via pins 14/15"port='/dev/serial0'# test_uart = SerialInOut(baudrate=230400, port='/dev/ttyUSB0' ) # for PCmsg2Send = ['First',    #300 char        "300UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"             ]            # pause time - make it different for each end to prove, almost, that multiple messages will queue up#pauseTime = 5 #in secondsbatch_sent   = FalsemainThrottle = 0.01 #in seconds (at 0.01 the 'swamp' of messages was handled. At 0.1 and even 0.02 they were truncated)######## items concerned with sending the output messages in a 'burst'G_doSendBurst      = FalseG_timer_send_every =  10000      # millisec. How ofter we send our batch of messages out# PicoburstTimer      = Timer(-1)## disable following 'burstTimer.init()' line to suppress outgoing messagesburstTimer.init(mode=Timer.PERIODIC, period=G_timer_send_every, callback=handle_burst_mode_trigger)# RPI#   burstTimer         = threading.Timer(G_timer_send_every, handle_burst_mode_trigger)      # RPI / normal python#   burstTimer.daemon  = True#   # disable following 'burstTimer.start' line to suppress outgoing messages#   burstTimer.start()      # RPI / normal pythonNoResponsesReceivedCount = 0# receive return-code decodes RC_decode = {        0: "OK",        #  - and Message is in 2nd return var    1: "Full message did not arrive (corruption or timeout or buffer overflow?) ",    2: "Fail - 1st byte is not a RS ",    3: "Fail - last byte is not a RS ",    4: "Fail - ",    5: "Fail - ",    6: "Fail - ",}print(f"***** Starting {G_MyName} / {G_MyTag}       mainThrottle = {mainThrottle} ")# ================================================================# ===               Main Code Loop                             ===# ================================================================try:    while True:        if G_doSendBurst:            for m in msg2Send:                test_uart.send(m)  # Send message to the other device (Pico or RPi or Jetson)                print(f"{G_MyTag}sending {m}")            G_doSendBurst = False        # now just listen a lot - quickly        rc, respList = test_uart.receive()        if rc != 0:            print(f"{G_MyTag} rc={rc} = {RC_decode.get(rc)}")            print (f"{G_MyTag} rc={rc} <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<")         # The childSerial will return an empty list if there is an error         if respList:   # list is NOT empty            print(f"{G_MyTag}have {len(respList)} response(s)= {respList}")            print(f"{G_MyTag} interveening 'No responses' received = {NoResponsesReceivedCount} ")            NoResponsesReceivedCount = 0        else:            NoResponsesReceivedCount += 1        time.sleep(mainThrottle)      # Wait for 'n' secondsexcept KeyboardInterrupt:     print("Program stopped by user")

Statistics: Posted by jc508 — Thu Feb 05, 2026 7:26 am



Viewing all articles
Browse latest Browse all 8082

Trending Articles