Control exact polling rates using uasyncio
Control exact polling rates using uasyncio
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?
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?
Re: Control exact polling rates using uasyncio
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.
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.
-
- Posts: 969
- Joined: Sat Feb 03, 2018 7:02 pm
Re: Control exact polling rates using uasyncio
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.
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
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Control exact polling rates using uasyncio
@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
Index to my micropython libraries.
Index to my micropython libraries.
Re: Control exact polling rates using uasyncio
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...
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...
- pythoncoder
- Posts: 5956
- Joined: Fri Jul 18, 2014 8:01 am
- Location: UK
- Contact:
Re: Control exact polling rates using uasyncio
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.
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
Index to my micropython libraries.
Index to my micropython libraries.
Re: Control exact polling rates using uasyncio
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:
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)?
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()
-
- Posts: 969
- Joined: Sat Feb 03, 2018 7:02 pm
Re: Control exact polling rates using uasyncio
I see a few problems with your code so I'll try to break it down:
It should be "timer.init(period=5, mode=Timer.PERIODIC, callback=timed_poll)"
Yes the right place but the size may be questionable. How much data do you really need?
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.
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.
Code: Select all
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?
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?
Also you should not allocate any memory inside the interrupt, so using the pre-allocated buffer is the right way.
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.Shouldn't I use a queue for this or is this a bad idea using uasyncio code mixed with interrupts?
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.
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.Is the uasyncio read operation and the timed interrupt function on working on the same uart allowed (thread-safe or no issue at all)?
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode
Re: Control exact polling rates using uasyncio
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?
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?
-
- Posts: 969
- Joined: Sat Feb 03, 2018 7:02 pm
Re: Control exact polling rates using uasyncio
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:
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.
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
Kevin Köck
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode
Micropython Smarthome Firmware (with Home-Assistant integration): https://github.com/kevinkk525/pysmartnode