Pico stops writing to UART via uasyncio and Stream

RP2040 based microcontroller boards running MicroPython.
Target audience: MicroPython users with an RP2040 boards.
This does not include conventional Linux-based Raspberry Pi boards.
Post Reply
martgut
Posts: 2
Joined: Sun Oct 17, 2021 3:51 pm

Pico stops writing to UART via uasyncio and Stream

Post by martgut » Sun Oct 17, 2021 4:26 pm

Hi,
I have a Pico and the problem that UART reading & writing via uasyncio Streams stalls after some time.

Using following recent pre-built version:
MicroPython v1.17 on 2021-09-02; Raspberry Pi Pico with RP2040

The final goal is to attach multiple sensors to it and report status via UART to a pretty remote Beaglebone. Right now I'm focusing on the UART communication, which does not work reliable: For testing purpose I'm using the Pico to write to one UART and read the same signal from a different UART. That perfectly works fine for hours, but at some point the communication stalls. There is no error reported, the StreamWriter still loops, debug output is printed to my attached Linux box. But when I measure the electrical output on the pins, there is no change. The test is a little extreme, sending lots of data on fast interval, but this should simulate a Pico which runs reliable for months. Multiple tests have been performed, but it randomly fails after 10000, or more read and writes (typically after a few hours).

So the program is still working and looping, but there is no change on the electrical output PIN's and as result the reader doesn't report anything more.

I have tried lots of different things: Monitoring the memory consumption, which looks OK. Heap memory is consumed, but cleared by the GC on regular intervals. I also tried once and disabled all print statements to reduce the communication overhead, still the same problem shows up (checking the LED indicator)

Help and hints a really appreciated, since robust communication is key to make progress on this project.

The original code is more complex with sensors, timers etc., but I have down stripped it to a simple case:

Code: Select all


import machine
import uasyncio

class PicoUart:
    def __init__(self):

        self._led = machine.Pin(25, machine.Pin.OUT)
        self._led.value(1)

        self._uart0 = machine.UART(0, baudrate=9600, parity=0, bits=8, tx=machine.Pin(16), rx=machine.Pin(17))
        self._uart1 = machine.UART(1, baudrate=9600, parity=0, bits=8, tx=machine.Pin(8), rx=machine.Pin(9))
        self._button_stop = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)

        self._run = True
        self._swriter = uasyncio.StreamWriter(self._uart0)
        self._led_timer = None

    def close(self):
        if self._led_timer:
            self._led_timer.deinit()
            self._led_timer = None

    async def reader(self):

        sreader = uasyncio.StreamReader(self._uart1)
        while self._run:
            bdata = await sreader.readline()
            print("Read:  ", bdata.decode("utf-8"))
            self._led.value(1)
            await uasyncio.sleep_ms(10)
            self._led.value(0)

    async def writer(self):

        idx = 0
        while self._button_stop.value() == 1:
            idx += 1
            data = f"{{'idx': {idx} 'temp_ext': 21.5625, 'temp_int': 26.57626, 'action': 'send_data'}}\n"
            bdata = data.encode("utf-8")
            self._swriter.write(bdata)
            await self._swriter.drain()
            await uasyncio.sleep_ms(200)

        self._run = False
        await uasyncio.sleep(3)

    async def run(self):
        t1 = uasyncio.create_task(self.writer())
        t2 = uasyncio.create_task(self.reader())
        await t1
        t2.cancel()


print("running: uart.py...")
pico_uart = PicoUart()
uasyncio.run(pico_uart.run())
pico_uart.close()
print("bye...")

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Pico stops writing to UART via uasyncio and Stream

Post by pythoncoder » Mon Oct 18, 2021 11:59 am

You may be the first person to try this kind of test. I would seek to determine whether uasyncio is the cause or whether it is the Pico UART driver. My money is on the latter.

So I would write a similar test script not using uasyncio. You'll need to ensure that rxbuf and txbuf can hold an entire message. If that fails after a similar period then I'd raise an issue.

Another thought is that there may be a memory fragmentation problem. I would try running a task like:

Code: Select all

async def collect():
    while True:
        gc.collect()
        await asyncio.sleep(1)
My long-running uasyncio applications have such a task.
Peter Hinch
Index to my micropython libraries.

martgut
Posts: 2
Joined: Sun Oct 17, 2021 3:51 pm

Re: Pico stops writing to UART via uasyncio and Stream

Post by martgut » Fri Oct 22, 2021 3:40 am

So I further simplified my code using only one UART for reading & writing, but now in a synchronous manner as suggested. I stripped out all asyncio and Stream usage, please see code below. With the result, that the code is running flawlessly now for +36 hours.

In another test, I modified my original asyncio code to also only use one UART instance. But results are the same: After ~12 hours the Pico stops writing to the PIN's, which I validated with a Voltmeter.

With these finding, I could proceed using the sync implementation, but I would really prefer the asyncio one, since it's much more elegant.

Code: Select all

# This example works without problems
import machine
import time

class PicoUart:
    def __init__(self):

        self._led = machine.Pin(25, machine.Pin.OUT)
        self._uart1 = machine.UART(1, baudrate=9600, parity=0, bits=8, tx=machine.Pin(8), rx=machine.Pin(9), timeout=100)
        self._button_stop = machine.Pin(14, machine.Pin.IN, machine.Pin.PULL_DOWN)
        self._led_timer = None

    def close(self):
        self._led_timer.deinit()

    def uart_rw(self):

        idx = 0
        while self._button_stop.value() == 1:
            # Read line, but honor timeout in ctor
            bdata = self._uart1.readline()
            if bdata:
                print("Read:  ", bdata.decode("utf-8"))
                self._led.value(1)
                time.sleep_ms(10)
                self._led.value(0)
            else:
                idx += 1
                data = f"{{'idx': {idx} 'temp_ext': 21.5625, 'temp_int': 26.57626, 'action': 'send_data'}}\n"
                bdata = data.encode("utf-8")
                self._uart1.write(bdata)

print("running: uart.py...")
pico_uart = PicoUart()
pico_uart.uart_rw()
pico_uart.close()
print("bye...")

Post Reply