Skip to content

Commit 3cff4de

Browse files
committed
Tutorial: further update to stream I/O.
1 parent 68ff1f8 commit 3cff4de

File tree

1 file changed

+166
-131
lines changed

1 file changed

+166
-131
lines changed

TUTORIAL.md

Lines changed: 166 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -119,20 +119,22 @@ and rebuilding.
119119

120120
4.5 [Exceptions](./TUTORIAL.md#45-exceptions)
121121

122-
5. [Device driver examples](./TUTORIAL.md#5-device-driver-examples)
122+
5. [Interfacing hardware](./TUTORIAL.md#5-interfacing-hardware)
123123

124-
5.1 [Using the IORead mechnanism](./TUTORIAL.md#51-using-the-ioread-mechanism)
124+
5.1 [Timing issues](./TUTORIAL.md#51-timing-issues)
125125

126-
5.1.1 [A UART driver example](./TUTORIAL.md#511-a-uart-driver-example)
126+
5.2 [Polling hardware with a coroutine](./TUTORIAL.md#52-polling-hardware-with-a-coroutine)
127127

128-
5.2 [Writing IORead device drivers](./TUTORIAL.md#52-writing-ioread-device-drivers)
128+
5.3 [Using the IORead mechnanism](./TUTORIAL.md#53-using-the-ioread-mechanism)
129129

130-
5.3 [Polling hardware without IORead](./TUTORIAL.md#53-polling-hardware-without-ioread)
130+
5.3.1 [A UART driver example](./TUTORIAL.md#531-a-uart-driver-example)
131131

132-
5.4 [A complete example: aremote.py](./TUTORIAL.md#54-a-complete-example-aremotepy)
132+
5.4 [Writing IORead device drivers](./TUTORIAL.md#54-writing-ioread-device-drivers)
133+
134+
5.5 [A complete example: aremote.py](./TUTORIAL.md#55-a-complete-example-aremotepy)
133135
A driver for an IR remote control receiver.
134136

135-
5.5 [Driver for HTU21D](./TUTORIAL.md#55-htu21d-environment-sensor) A
137+
5.6 [Driver for HTU21D](./TUTORIAL.md#56-htu21d-environment-sensor) A
136138
temperature and humidity sensor.
137139

138140
6. [Hints and tips](./TUTORIAL.md#6-hints-and-tips)
@@ -380,7 +382,7 @@ This is generally highly desirable, but it does introduce uncertainty in the
380382
timing as the calling routine will only be rescheduled when the one running at
381383
the appropriate time has yielded. The amount of latency depends on the design
382384
of the application, but is likely to be on the order of tens or hundreds of ms;
383-
this is discussed further in [Section 5](./TUTORIAL.md#5-device-driver-examples).
385+
this is discussed further in [Section 5](./TUTORIAL.md#5-interfacing-hardware).
384386

385387
Very precise delays may be issued by using the `utime` functions `sleep_ms`
386388
and `sleep_us`. These are best suited for short delays as the scheduler will
@@ -1006,46 +1008,161 @@ a keyboard interrupt should trap the exception at the event loop level.
10061008

10071009
###### [Contents](./TUTORIAL.md#contents)
10081010

1009-
# 5 Device driver examples
1010-
1011-
Many devices such as sensors are read-only in nature and need to be polled to
1012-
acquire data. In the case of a driver written in Python this must be done by
1013-
having a coro which does this periodically. This may present problems if there
1014-
is a requirement for rapid polling owing to the round-robin nature of uasyncio
1015-
scheduling: the coro will compete for execution with others. There are two
1016-
solutions to this. The official solution is to delegate polling to the
1017-
scheduler using the IORead mechanism. This is currently subject to limitations.
1018-
1019-
An alternative is to use the experimental version of uasyncio presented
1020-
[here](./FASTPOLL.md).
1021-
1022-
Note that where a very repeatable polling interval is required, it should be
1023-
done using a hardware timer with a hard interrupt callback. For "very"
1024-
repeatable read microsecond level (depending on platform).
1025-
1026-
In many cases less precise timing is acceptable. The definition of "less" is
1027-
application dependent but the latency associated with scheduling the coro which
1028-
is performing the polling may be variable on the order of tens or hundreds of
1029-
milliseconds. Latency is determined as follows. When `await asyncio.sleep(0)`
1030-
is issued all other pending coros will be scheduled in "fair round-robin"
1031-
fashion before it is re-scheduled. Thus its worst-case latency may be
1011+
# 5 Interfacing hardware
1012+
1013+
At heart all interfaces between `uasyncio` and external asynchronous events
1014+
rely on polling. Hardware requiring a fast response may use an interrupt. But
1015+
the interface between the interrupt service routine (ISR) and a user coro will
1016+
be polled. For example the ISR might trigger an `Event` or set a global flag,
1017+
while a coroutine awaiting the outcome polls the object each time it is
1018+
scheduled.
1019+
1020+
Polling may be effected in two ways, explicitly or implicitly. The latter is
1021+
performed by using the `IORead` mechanism which is a system designed for stream
1022+
devices such as UARTs and sockets. At its simplest explicit polling may consist
1023+
of code like this:
1024+
1025+
```python
1026+
async def poll_my_device():
1027+
global my_flag # Set by device ISR
1028+
while True:
1029+
if my_flag:
1030+
my_flag = False
1031+
# service the device
1032+
await asyncio.sleep(0)
1033+
```
1034+
1035+
In place of a global, an instance variable, an `Event` object or an instance of
1036+
an awaitable class might be used. Explicit polling is discussed
1037+
further [below](./TUTORIAL.md#52-polling-hardware-with-a-coroutine).
1038+
1039+
Implicit polling consists of designing the driver to behave like a stream I/O
1040+
device such as a socket or UART, using `IORead`. This polls devices using
1041+
Python's `select.poll` system: because the polling is done in C it is faster
1042+
and more efficient than explicit polling. The use of `IORead` is discussed
1043+
[here](./TUTORIAL.md#53-using-the-ioread-mechanism).
1044+
1045+
###### [Contents](./TUTORIAL.md#contents)
1046+
1047+
## 5.1 Timing issues
1048+
1049+
Both explicit and implicit polling are currently based on round-robin
1050+
scheduling. Assume I/O is operating concurrently with N user coros each of
1051+
which yields with a zero delay. When I/O has been serviced it will next be
1052+
polled once all user coros have been scheduled. The implied latency needs to be
1053+
considered in the design. I/O channels may require buffering, with an ISR
1054+
servicing the hardware in real time from buffers and coroutines filling or
1055+
emptying the buffers in slower time.
1056+
1057+
The possibility of overrun also needs to be considered: this is the case where
1058+
something being polled by a coroutine occurs more than once before the coro is
1059+
actually scheduled.
1060+
1061+
Another timing issue is the accuracy of delays. If a coro issues
1062+
1063+
```python
1064+
await asyncio.sleep_ms(t)
1065+
# next line
1066+
```
1067+
1068+
the scheduler guarantees that execution will pause for at least `t`ms. The
1069+
actual delay may be greater depending on the system state when `t` expires.
1070+
If, at that time, all other coros are waiting on nonzero delays, the next line
1071+
will immediately be scheduled. But if other coros are pending execution (either
1072+
because they issued a zero delay or because their time has also elapsed) they
1073+
may be scheduled first. This introduces a timing uncertainty into the `sleep()`
1074+
and `sleep_ms()` functions. The worst-case value for this overrun may be
10321075
calculated by summing, for every other coro, the worst-case execution time
10331076
between yielding to the scheduler.
10341077

1035-
If `await asyncio.sleep_ms(t)` is issued where t > 0 the coro is guaranteed not
1036-
to be rescheduled until t has elapsed. If, at that time, all other coros are
1037-
waiting on nonzero delays, it will immediately be scheduled. But if other coros
1038-
are pending execution (either because they issued a zero delay or because their
1039-
time has elapsed) they may be scheduled first. This introduces a timing
1040-
uncertainty into the `sleep()` and `sleep_ms()` functions. The worst-case value
1041-
for this may be calculated as above.
1078+
There is an experimental version of uasyncio presented [here](./FASTPOLL.md).
1079+
This provides for callbacks which run on every iteration of the scheduler
1080+
enabling a coro to wait on an event with much reduced latency. It is hoped
1081+
that improvements to `uasyncio` will remove the need for this in future.
1082+
1083+
###### [Contents](./TUTORIAL.md#contents)
10421084

1043-
[This document](./FASTPOLL.md) describes an experimental version of uasyncio
1044-
which offers a means of reducing this latency for critical tasks.
1085+
## 5.2 Polling hardware with a coroutine
1086+
1087+
This is a simple approach, but is most appropriate to hardware which may be
1088+
polled at a relatively low rate. This is primarily because polling with a short
1089+
(or zero) polling interval may cause the coro to consume more processor time
1090+
than is desirable.
1091+
1092+
The example `apoll.py` demonstrates this approach by polling the Pyboard
1093+
accelerometer at 100ms intervals. It performs some simple filtering to ignore
1094+
noisy samples and prints a message every two seconds if the board is not moved.
1095+
1096+
Further examples may be found in `aswitch.py` which provides drivers for
1097+
switch and pushbutton devices.
1098+
1099+
An example of a driver for a device capable of reading and writing is shown
1100+
below. For ease of testing Pyboard UART 4 emulates the notional device. The
1101+
driver implements a `RecordOrientedUart` class, where data is supplied in
1102+
variable length records consisting of bytes instances. The object appends a
1103+
delimiter before sending and buffers incoming data until the delimiter is
1104+
received. This is a demo and is an inefficient way to use a UART compared to
1105+
IORead.
1106+
1107+
For the purpose of demonstrating asynchronous transmission we assume the
1108+
device being emulated has a means of checking that transmission is complete
1109+
and that the application requires that we wait on this. Neither assumption is
1110+
true in this example but the code fakes it with `await asyncio.sleep(0.1)`.
1111+
1112+
Link pins X1 and X2 to run.
1113+
1114+
```python
1115+
import uasyncio as asyncio
1116+
from pyb import UART
1117+
1118+
class RecordOrientedUart():
1119+
DELIMITER = b'\0'
1120+
def __init__(self):
1121+
self.uart = UART(4, 9600)
1122+
self.data = b''
1123+
1124+
def __await__(self):
1125+
data = b''
1126+
while not data.endswith(self.DELIMITER):
1127+
yield from asyncio.sleep(0) # Neccessary because:
1128+
while not self.uart.any():
1129+
yield from asyncio.sleep(0) # timing may mean this is never called
1130+
data = b''.join((data, self.uart.read(self.uart.any())))
1131+
self.data = data
1132+
1133+
__iter__ = __await__ # workround for issue #2678
1134+
1135+
async def send_record(self, data):
1136+
data = b''.join((data, self.DELIMITER))
1137+
self.uart.write(data)
1138+
await self._send_complete()
1139+
1140+
# In a real device driver we would poll the hardware
1141+
# for completion in a loop with await asyncio.sleep(0)
1142+
async def _send_complete(self):
1143+
await asyncio.sleep(0.1)
1144+
1145+
def read_record(self): # Synchronous: await the object before calling
1146+
return self.data[0:-1] # Discard delimiter
1147+
1148+
async def run():
1149+
foo = RecordOrientedUart()
1150+
rx_data = b''
1151+
await foo.send_record(b'A line of text.')
1152+
for _ in range(20):
1153+
await foo # Other coros are scheduled while we wait
1154+
rx_data = foo.read_record()
1155+
print('Got: {}'.format(rx_data))
1156+
await foo.send_record(rx_data)
1157+
rx_data = b''
1158+
1159+
loop = asyncio.get_event_loop()
1160+
loop.run_until_complete(run())
1161+
```
10451162

10461163
###### [Contents](./TUTORIAL.md#contents)
10471164

1048-
## 5.1 Using the IORead Mechanism
1165+
## 5.3 Using the IORead Mechanism
10491166

10501167
This can be illustrated using a Pyboard UART. The following code sample
10511168
demonstrates concurrent I/O on one UART. To run, link Pyboard pins X1 and X2
@@ -1077,8 +1194,8 @@ loop.run_forever()
10771194
The supporting code may be found in `__init__.py` in the `uasyncio` library.
10781195
The mechanism works because the device driver (written in C) implements the
10791196
following methods: `ioctl`, `read`, `readline` and `write`. See
1080-
[section 5.2](./TUTORIAL.md#52-writing-ioread-device-drivers) for details on
1081-
how such drivers may be written in Python.
1197+
[Writing IORead device drivers](./TUTORIAL.md#54-writing-ioread-device-drivers)
1198+
for details on how such drivers may be written in Python.
10821199

10831200
A UART can receive data at any time. The IORead mechanism checks for pending
10841201
incoming characters whenever the scheduler has control. When a coro is running
@@ -1089,7 +1206,7 @@ avoid buffer overflows and data loss. This can be ameliorated by using a larger
10891206
UART read buffer or a lower baudrate. Alternatively hardware flow control will
10901207
provide a solution if the data source supports it.
10911208

1092-
### 5.1.1 A UART driver example
1209+
### 5.3.1 A UART driver example
10931210

10941211
The program `auart_hd.py` illustrates a method of communicating with a half
10951212
duplex device such as one responding to the modem 'AT' command set. Half duplex
@@ -1113,7 +1230,7 @@ returned. See the code comments for more details.
11131230

11141231
###### [Contents](./TUTORIAL.md#contents)
11151232

1116-
## 5.2 Writing IORead device drivers
1233+
## 5.4 Writing IORead device drivers
11171234

11181235
The `IORead` mechanism is provided to support I/O to stream devices. Its
11191236
typical use is to support streaming I/O devices such as UARTs and sockets. The
@@ -1123,8 +1240,8 @@ handlers for any devices which are ready. This is more efficient than running
11231240
multiple coros each polling a device.
11241241

11251242
It should be noted that currently the task polling I/O devices effectively runs
1126-
in round-robin fashion along with other coroutines. This is arguably sub
1127-
optimal: [see this GitHub RFC](https://github.com/micropython/micropython/issues/2664).
1243+
in round-robin fashion along with other coroutines. Arguably this could be
1244+
improved: [see this GitHub RFC](https://github.com/micropython/micropython/issues/2664).
11281245

11291246
A device driver capable of employing the IORead mechanism may support
11301247
`StreamReader`, `StreamWriter` instances or both. A readable device must
@@ -1178,89 +1295,7 @@ write-only.
11781295

11791296
###### [Contents](./TUTORIAL.md#contents)
11801297

1181-
## 5.3 Polling hardware without IORead
1182-
1183-
This is a simple approach, but is only appropriate to hardware which is to be
1184-
polled at a relatively low rate. This is for two reasons. Firstly the variable
1185-
latency caused by the execution of other coros will result in variable polling
1186-
intervals - this may or may not matter depending on the device and application.
1187-
Secondly, attempting to poll with a short polling interval may cause the coro
1188-
to consume more processor time than is desirable.
1189-
1190-
The example `apoll.py` demonstrates this approach by polling the Pyboard
1191-
accelerometer at 100ms intervals. It performs some simple filtering to ignore
1192-
noisy samples and prints a message every two seconds if the board is not moved.
1193-
1194-
Further examples may be found in `aswitch.py` which provides drivers for
1195-
switch and pushbutton devices.
1196-
1197-
An example of a driver for a device capable of reading and writing is shown
1198-
below. For ease of testing Pyboard UART 4 emulates the notional device. The
1199-
driver implements a `RecordOrientedUart` class, where data is supplied in
1200-
variable length records consisting of bytes instances. The object appends a
1201-
delimiter before sending and buffers incoming data until the delimiter is
1202-
received. This is a demo and is an inefficient way to use a UART compared to
1203-
IORead.
1204-
1205-
For the purpose of demonstrating asynchronous transmission we assume the
1206-
device being emulated has a means of checking that transmission is complete
1207-
and that the application requires that we wait on this. Neither assumption is
1208-
true in this example but the code fakes it with `await asyncio.sleep(0.1)`.
1209-
1210-
Link pins X1 and X2 to run.
1211-
1212-
```python
1213-
import uasyncio as asyncio
1214-
from pyb import UART
1215-
1216-
class RecordOrientedUart():
1217-
DELIMITER = b'\0'
1218-
def __init__(self):
1219-
self.uart = UART(4, 9600)
1220-
self.data = b''
1221-
1222-
def __await__(self):
1223-
data = b''
1224-
while not data.endswith(self.DELIMITER):
1225-
yield from asyncio.sleep(0) # Neccessary because:
1226-
while not self.uart.any():
1227-
yield from asyncio.sleep(0) # timing may mean this is never called
1228-
data = b''.join((data, self.uart.read(self.uart.any())))
1229-
self.data = data
1230-
1231-
__iter__ = __await__ # workround for issue #2678
1232-
1233-
async def send_record(self, data):
1234-
data = b''.join((data, self.DELIMITER))
1235-
self.uart.write(data)
1236-
await self._send_complete()
1237-
1238-
# In a real device driver we would poll the hardware
1239-
# for completion in a loop with await asyncio.sleep(0)
1240-
async def _send_complete(self):
1241-
await asyncio.sleep(0.1)
1242-
1243-
def read_record(self): # Synchronous: await the object before calling
1244-
return self.data[0:-1] # Discard delimiter
1245-
1246-
async def run():
1247-
foo = RecordOrientedUart()
1248-
rx_data = b''
1249-
await foo.send_record(b'A line of text.')
1250-
for _ in range(20):
1251-
await foo # Other coros are scheduled while we wait
1252-
rx_data = foo.read_record()
1253-
print('Got: {}'.format(rx_data))
1254-
await foo.send_record(rx_data)
1255-
rx_data = b''
1256-
1257-
loop = asyncio.get_event_loop()
1258-
loop.run_until_complete(run())
1259-
```
1260-
1261-
###### [Contents](./TUTORIAL.md#contents)
1262-
1263-
## 5.4 A complete example: aremote.py
1298+
## 5.5 A complete example: aremote.py
12641299

12651300
This may be found in the `nec_ir` directory. Its use is documented
12661301
[here](./nec_ir/README.md). The demo provides a complete device driver example:
@@ -1277,7 +1312,7 @@ any asyncio latency when setting its delay period.
12771312

12781313
###### [Contents](./TUTORIAL.md#contents)
12791314

1280-
## 5.5 HTU21D environment sensor
1315+
## 5.6 HTU21D environment sensor
12811316

12821317
This chip provides accurate measurements of temperature and humidity. The
12831318
driver is documented [here](./htu21d/README.md). It has a continuously running

0 commit comments

Comments
 (0)