Control exact polling rates using uasyncio

Discussion about programs, libraries and tools that work with MicroPython. Mostly these are provided by a third party.
Target audience: All users and developers of MicroPython.
ltmerlin
Posts: 10
Joined: Fri Jun 28, 2019 12:34 pm

Control exact polling rates using uasyncio

Post by ltmerlin » Mon Jan 27, 2020 8:23 pm

How to implement the usage of sensors that need to be polled at regular (read: quasi constant) interval or rate?
I’m using now the asyncio streamreader for polling the sensor data connected through UART, but I do not have any control or certainty over the reading rate. Let say I need an (quasi) exact reading rate at x ms. Even if all other asyncio task are handled fast (i.e. in a round-robin fashion with no extra asyncio.sleep_ms(0) )
Any tips, ideas?

User avatar
tve
Posts: 58
Joined: Wed Jan 01, 2020 10:12 pm
Location: Santa Barbara, CA
Contact:

Re: Control exact polling rates using uasyncio

Post by tve » Mon Jan 27, 2020 8:37 pm

I think there are answers in multiple layers...
If you're talking UART, how much precision do you need for what purpose? The uart is interrupt driven (I hope in all ports) so unless your sensor spews hundreds of bytes you can let the driver buffer until you get around to it.
The scheduling accuracy of coroutines is always going to be limited by the one that hogs the cpu the longest. You should obviously do your periodic scheduling carefully, e.g. calculate your next wake-up deadline from the previous deadline and then sleep for the delta (and do something sensible if you've overrun already) as opposed to always just doing a sleep(N).
Beyond this you either run a separate thread (if the RTOS is pre-emptive) or use interrupts.

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

Re: Control exact polling rates using uasyncio

Post by kevinkk525 » Mon Jan 27, 2020 9:55 pm

Or if timing is that important, use an interrupt approach with timers to read the uart every e.g. 50ms into a preallocated buffer.
Question is of course, why you would need to do that as the uart has a buffer.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

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

Re: Control exact polling rates using uasyncio

Post by pythoncoder » Tue Jan 28, 2020 10:02 am

@Itmerlin it would help if you could quantify what you mean by "exact". What is the maximum tolerable error? It would also help if you described your sensor and application.
Peter Hinch

ltmerlin
Posts: 10
Joined: Fri Jun 28, 2019 12:34 pm

Re: Control exact polling rates using uasyncio

Post by ltmerlin » Tue Jan 28, 2020 11:05 am

Thank you for the replies.

To be more specific: I have a radar module (FSK type) to measure a moving object's distance. This module has a UART interface and needs to be polled. The goal is to visualize the object's location in time by means of a grid where the objects will be visualized using blips/points. I want to poll the module at constant time intervals because the velocity of the object is not constant and I want to use equidistant measurement points in time...

I intend to use the latest version of uasyncio (using upip Pypi), is it safe to use this library for queueing, Events, UART streamreader/writer or should I use the asyn.py and extras from @pythoncoder (btw fantastic work there Peter :-) )?

Aiming towards a maximum tolerable error of 5ms...

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

Re: Control exact polling rates using uasyncio

Post by pythoncoder » Tue Jan 28, 2020 6:59 pm

5ms is pretty tight for a uasyncio application because coroutines run in round-robin fashion. If you have five coroutines and each uses (say) 3ms of CPU time between await calls, then you have 15ms of latency.

As others have suggested you may need to use uasyncio code for general processing but perform the polling in response to a timer interrupt. In that way the polling occurs at precise intervals. If the response is delayed by buffering and uasyncio latency, no information is lost: the measurement was correct at the moment of polling. Your code can deduce time and position - Einstein notwithstanding ;)

So you'd write to the UART directly, but read it at relative leisure using a StreamReader.
Peter Hinch

ltmerlin
Posts: 10
Joined: Fri Jun 28, 2019 12:34 pm

Re: Control exact polling rates using uasyncio

Post by ltmerlin » Tue Jan 28, 2020 8:48 pm

Thanks again for the tips. I'm new to micropython and its uart, Timers and buffers so please forgive me for the following code suggestion:

Code: Select all

from machine import Timer, UART
import uasyncio as asyncio

uart = machine.UART(1, baudrate=115200)
timer = Timer(-1)
timer.init(period=5, mode=Timer.PERIODIC, callback=timed_poll())

buf = bytearray(100) # Is an explicit global buffer needed and this the right place to create this buffer?

def timed_poll():
    global buf
    uart.write(b"GETDATA")
    uart.readinto(buf)
    # or should I just use uart.readline() without the use of explicit buffer object?

async def poll():
    sreader = asyncio.StreamReader(uart))
    while True:
        result = await sreader.readline()
        # process result...

loop = asyncio.get_event_loop()
loop.create_task(poll())
loop.run_forever()
Is this the right way to do this? Shouldn't I use a queue for this or is this a bad idea using uasyncio code mixed with interrupts? Is the uasyncio read operation and the timed interrupt function on working on the same uart allowed (thread-safe or no issue at all)?

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

Re: Control exact polling rates using uasyncio

Post by kevinkk525 » Tue Jan 28, 2020 9:48 pm

I see a few problems with your code so I'll try to break it down:

Code: Select all

timer.init(period=5, mode=Timer.PERIODIC, callback=timed_poll())
It should be "timer.init(period=5, mode=Timer.PERIODIC, callback=timed_poll)"

Code: Select all

buf = bytearray(100) # Is an explicit global buffer needed and this the right place to create this buffer?
Yes the right place but the size may be questionable. How much data do you really need?

Code: Select all

def timed_poll():
    global buf
    uart.write(b"GETDATA")
    uart.readinto(buf)
    # or should I just use uart.readline() without the use of explicit buffer object?
You start by sending data to the device. This causes a delay until the response can be read from the device. How long does it take? An interrupt callback shouldn't last longer than a few ms. It's best to not do any waiting in the interrupt.
Also you should not allocate any memory inside the interrupt, so using the pre-allocated buffer is the right way.
Shouldn't I use a queue for this or is this a bad idea using uasyncio code mixed with interrupts?
As your uasyncio loop and the interrupt could be accessing your buffer at the same time, I'd recommend using a ring-buffer/pre-allocated queue.
Also you need to make sure that the uasnycio loop can process all incoming data fast enough before the last entry of the buffer gets overwritten.
Is the uasyncio read operation and the timed interrupt function on working on the same uart allowed (thread-safe or no issue at all)?
Why do you have a read in the interrupt and in the loop? That makes no sense to me. And it probably isn't safe either.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

ltmerlin
Posts: 10
Joined: Fri Jun 28, 2019 12:34 pm

Re: Control exact polling rates using uasyncio

Post by ltmerlin » Wed Jan 29, 2020 8:30 pm

Thank you for your answers. I need to send a command to get the data out of the sensor so that’s why the GETDATA command is needed.
A stupid question about UART: do we need to use a global buffer object or is the UART Rx a buffer itself? (The docs state it is a Stream object) if so how can we know or set the size of this Rx buffer?
I added the ‘readinto’ statement to get the new data in our global buffer. If the UART object itself has a buffer, I don’t have to do this then?

Do you have an example of using the thread-safe ringbuffer or pre-allocated queue like you suggested?

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

Re: Control exact polling rates using uasyncio

Post by kevinkk525 » Thu Jan 30, 2020 7:36 am

If sending the command is needed then you could measure how long it takes to run the interrupt, just to be sure that it doesn't take too long. Your 5ms interval however could make it difficult.

The UART has a buffer, so it should be possible to read multiple values back from it. I can't tell you how big that buffer is and you can only change the size in the firmware build. I would test how many results the uart can hold.

You need the "readinto" statement with preallocated buffer because you can't allocate heap space in the interrupt. If you read the uart in the main loop, you could read it directly as a=uart.read().

Sorry I don't have an example ready for thread-safe ringbuffers or similar.
It basically works like using a buffer of e.g. 100Bytes and having an integer variable as pointer. So e.g. if you always read 10 characters at a time:

Code: Select all

pointer=0
buf=bytearray(100)

def read():
    uart.readinto(memoryview(buf)[pointer:pointer+10]) # i think uart can read into a memoryview object. otherwise a temporary 10 character buffer is needed
    pointer+=10
    if pointer==100:
        pointer=0
That is however only a simple adding part. To read the buffer you would have to know where the last value was inserted that you did not yet process and read from there.
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode

Post Reply