Receiving uart data and polling sensor with uasyncio

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
picouser78
Posts: 3
Joined: Mon Mar 15, 2021 7:33 pm

Receiving uart data and polling sensor with uasyncio

Post by picouser78 » Mon Mar 15, 2021 7:45 pm

Hi,

I've been trying to use uasyncio for some "parallel" tasks (uart communication and sensor polling).
My problem is that "data = await sreader.readline()" behaves very buggy in my program:
When I send a bytestring like b'test12345\n' sometimes I receive nothing after the first try.
Then after a second attempt I receive something like b'test12345test12345\n'.

I think the problem lies in the delays (time.sleep_us(10)) of the sensor library (HC-SR094) which locks the readline() command.
Does anybody know a good practice to deal with modules that uses such delays?


Here is a minimal example:

Code: Select all

from machine import Pin, UART
import uasyncio as asyncio
uart = UART(1, baudrate=115200, tx=Pin(4), rx=Pin(5))

async def receiver():
    sreader = asyncio.StreamReader(uart)
    while True:
        data = await sreader.readline()
        print(data)

async def read_sensor():
    # read data from sensor
    await asyncio.sleep_ms(100)


async def main():
    asyncio.create_task(receiver())    
    asyncio.create_task(read_sensor())
    while True:    
        await asyncio.sleep_ms(1000)

if __name__ == '__main__':    
    try:
        asyncio.run(main())
    finally:
        asyncio.new_event_loop()
Without read_sensor(), uart works flawless...

Regards Marc

mbrizuarg
Posts: 1
Joined: Fri Dec 11, 2020 9:20 pm

Re: Receiving uart data and polling sensor with uasyncio

Post by mbrizuarg » Thu Mar 18, 2021 7:54 pm

Hi Marc

I'm also starting with asyncio. From what I understand you will have to modify the library so that it is also asyncio.

Greetings. Mariano.

kevinkk525
Posts: 969
Joined: Sat Feb 03, 2018 7:02 pm

Re: Receiving uart data and polling sensor with uasyncio

Post by kevinkk525 » Fri Mar 19, 2021 6:17 am

If you're using the uart, make sure to modify the uart driver like this PR: https://github.com/micropython/micropython/pull/7022
Otherwise it's extremely buggy and unusable. Hope it'll be in the daily build soon.
Last edited by kevinkk525 on Fri Mar 19, 2021 10:35 am, edited 1 time in total.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

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

Re: Receiving uart data and polling sensor with uasyncio

Post by pythoncoder » Fri Mar 19, 2021 9:09 am

As @Kevinkk525 says, the UART firmware is unstable.

A delay of 10μs (time.sleep_us(10)) is not going to affect any real uasyncio program. The latency involved in switching between concurrent tasks is typically three orders of magnitude greater than this. Consequently uasyncio can't provide μs level delays with any semblance of accuracy. If you need such delays, there is no choice other than to use time.sleep_us().

As for device drivers, it is perfectly OK to use conventional synchronous drivers with uasyncio so long as these don't block for long periods. Drivers that read sensors don't usually block for long. Examples of blocking drivers are communications devices where reception blocks while waiting for a transmission. This is where the uasyncio stream mechanism comes in. It would be bad practice to issue uart.read(10) because it would lock up all tasks for the duration: stream I/O solves this, enabling the reading task to pause while others continue.
Peter Hinch
Index to my micropython libraries.

picouser78
Posts: 3
Joined: Mon Mar 15, 2021 7:33 pm

Re: Receiving uart data and polling sensor with uasyncio

Post by picouser78 » Sat Mar 20, 2021 1:33 pm

Thank you all for your answers! Some days ago I've modified the HC-SR04 lib to work asynchronous.
I've used the very good example (https://github.com/peterhinch/micropyth ... /HTU21D.md) of the asynchronous HTU21D driver as blueprint.

When three coros (sensor, send, receive) are running, uart will still behave very buggy and unpredictable.
So I'm partly relieved that the uart driver itself is buggy. ;)

Is there a quick way to modify the files on the pico until they are part of the official build?

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

Re: Receiving uart data and polling sensor with uasyncio

Post by pythoncoder » Sat Mar 20, 2021 1:41 pm

Not without rebuilding, as the UART driver code is in C. If you are able to rebuild, get this PR, rebuild and flash the firmware.
Peter Hinch
Index to my micropython libraries.

picouser78
Posts: 3
Joined: Mon Mar 15, 2021 7:33 pm

Re: Receiving uart data and polling sensor with uasyncio

Post by picouser78 » Sun Mar 21, 2021 8:27 am

Ok, I've rebuilt/flashed the firmware but now there seems to be a new (?) issue with uasyncio.
Without it, everything works as intended. Here's a working minimal example for an uart test:

CPython

Code: Select all

import serial
import time
ser = serial.Serial(port='/dev/serial0', baudrate = 9600)

for x in range(100):
    message = str(x) + '\n'
    message = message.encode()
    ser.write(message)
    print(message)
    time.sleep(5)
RP2

Code: Select all

from machine import Pin, UART
uart = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))

while True:
    data = uart.readline()
    if data:
        print(data)
In my second example (with uasyncio), "data = await sreader.readline()" returns something only every other write.
So when the sender sends b'1\n' readline() returns nothing. After b'2\n' readline() returns both lines.
Is this a timout problem?

Code: Select all

from machine import Pin, UART
import uasyncio as asyncio
uart = UART(1, baudrate=9600, tx=Pin(4), rx=Pin(5))

async def receiver():
    sreader = asyncio.StreamReader(uart)
    while True:
        data = await sreader.readline()
        print(data)

async def main():
    asyncio.create_task(receiver())    
    while True:    
        await asyncio.sleep_ms(1000)

if __name__ == '__main__':    
    try:
        asyncio.run(main())
    finally:
        asyncio.new_event_loop()

With the current daily firmware (rp2-pico-20210321-unstable-v1.14-121-g4fc2866f4.uf2) the uasynio example is working. So it seems the changes in the PR affect StreamReader(uart).readline().

Post Reply