Skip to content

Commit 68ff1f8

Browse files
committed
Tutorial: update stream I/O information.
1 parent 48d1d52 commit 68ff1f8

File tree

6 files changed

+93
-44
lines changed

6 files changed

+93
-44
lines changed

FASTPOLL.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ This document describes a "priority" version of uasyncio. Its purpose is to
44
provide a simple priority mechanism to facilitate the design of applications
55
with improved millisecond-level timing accuracy and reduced scheduling latency.
66

7+
I remain hopeful that uasyncio will mature natively to support fast I/O
8+
polling: if this occurs I plan to deprecate this solution. See
9+
[this thread](https://github.com/micropython/micropython/pull/3836#issuecomment-397317408)
10+
and [this one](https://github.com/micropython/micropython/issues/2664).
11+
712
V0.3 Feb 2018. A single module designed to work with the official `uasyncio`
813
library. This requires `uasyncio` V2.0 which requires firmware dated
914
22nd Feb 2018 or later.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,8 @@ Classes `Task` and `Future` are not supported.
7575
## 3.1 Asynchronous I/O
7676

7777
Asynchronous I/O (`StreamReader` and `StreamWriter` classes) support devices
78-
with streaming drivers, such as UARTs and sockets.
78+
with streaming drivers, such as UARTs and sockets. It is now possible to write
79+
streaming device drivers in Python.
7980

8081
## 3.2 Time values
8182

TUTORIAL.md

Lines changed: 83 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,13 @@ and rebuilding.
121121

122122
5. [Device driver examples](./TUTORIAL.md#5-device-driver-examples)
123123

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

126126
5.1.1 [A UART driver example](./TUTORIAL.md#511-a-uart-driver-example)
127127

128-
5.2 [Using a coro to poll hardware](./TUTORIAL.md#52-using-a-coro-to-poll-hardware)
128+
5.2 [Writing IORead device drivers](./TUTORIAL.md#52-writing-ioread-device-drivers)
129129

130-
5.3 [Using IORead to poll hardware](./TUTORIAL.md#53-using-ioread-to-poll-hardware)
130+
5.3 [Polling hardware without IORead](./TUTORIAL.md#53-polling-hardware-without-ioread)
131131

132132
5.4 [A complete example: aremote.py](./TUTORIAL.md#54-a-complete-example-aremotepy)
133133
A driver for an IR remote control receiver.
@@ -216,6 +216,7 @@ results by accessing Pyboard hardware.
216216
11. `auart_hd.py` Use of the Pyboard UART to communicate with a device using a
217217
half-duplex protocol. Suits devices such as those using the 'AT' modem command
218218
set.
219+
12. `iorw.py` Demo of a read/write device driver using the IORead mechanism.
219220

220221
**Test Programs**
221222

@@ -1012,11 +1013,11 @@ acquire data. In the case of a driver written in Python this must be done by
10121013
having a coro which does this periodically. This may present problems if there
10131014
is a requirement for rapid polling owing to the round-robin nature of uasyncio
10141015
scheduling: the coro will compete for execution with others. There are two
1015-
solutions to this. One is to use the experimental version of uasyncio presented
1016-
[here](./FASTPOLL.md).
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.
10171018

1018-
The other potential solution is to delegate the polling to the scheduler using
1019-
the IORead mechanism. This is unsupported for Python drivers: see section 5.3.
1019+
An alternative is to use the experimental version of uasyncio presented
1020+
[here](./FASTPOLL.md).
10201021

10211022
Note that where a very repeatable polling interval is required, it should be
10221023
done using a hardware timer with a hard interrupt callback. For "very"
@@ -1044,7 +1045,7 @@ which offers a means of reducing this latency for critical tasks.
10441045

10451046
###### [Contents](./TUTORIAL.md#contents)
10461047

1047-
## 5.1 The IORead Mechanism
1048+
## 5.1 Using the IORead Mechanism
10481049

10491050
This can be illustrated using a Pyboard UART. The following code sample
10501051
demonstrates concurrent I/O on one UART. To run, link Pyboard pins X1 and X2
@@ -1073,19 +1074,20 @@ loop.create_task(receiver())
10731074
loop.run_forever()
10741075
```
10751076

1076-
The supporting code may be found in `__init__.py` in the uasyncio library.
1077+
The supporting code may be found in `__init__.py` in the `uasyncio` library.
10771078
The mechanism works because the device driver (written in C) implements the
1078-
following methods: `ioctl`, `read`, `write`, `readline` and `close`. See
1079-
section 5.3 for further discussion.
1079+
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.
10801082

10811083
A UART can receive data at any time. The IORead mechanism checks for pending
10821084
incoming characters whenever the scheduler has control. When a coro is running
10831085
an interrupt service routine buffers incoming characters; these will be removed
10841086
when the coro yields to the scheduler. Consequently UART applications should be
1085-
designed such that all coros minimise blocking periods to avoid buffer
1086-
overflows and data loss. This can be ameliorated by using a larger UART read
1087-
buffer or a lower baudrate. Alternatively hardware flow control will provide a
1088-
solution if the data source supports it.
1087+
designed such that coros minimise the time between yielding to the scheduler to
1088+
avoid buffer overflows and data loss. This can be ameliorated by using a larger
1089+
UART read buffer or a lower baudrate. Alternatively hardware flow control will
1090+
provide a solution if the data source supports it.
10891091

10901092
### 5.1.1 A UART driver example
10911093

@@ -1111,7 +1113,72 @@ returned. See the code comments for more details.
11111113

11121114
###### [Contents](./TUTORIAL.md#contents)
11131115

1114-
## 5.2 Using a coro to poll hardware
1116+
## 5.2 Writing IORead device drivers
1117+
1118+
The `IORead` mechanism is provided to support I/O to stream devices. Its
1119+
typical use is to support streaming I/O devices such as UARTs and sockets. The
1120+
mechanism may be employed by drivers of any device which needs to be polled:
1121+
the polling is delegated to the scheduler which uses `select` to schedule the
1122+
handlers for any devices which are ready. This is more efficient than running
1123+
multiple coros each polling a device.
1124+
1125+
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).
1128+
1129+
A device driver capable of employing the IORead mechanism may support
1130+
`StreamReader`, `StreamWriter` instances or both. A readable device must
1131+
provide at least one of the following methods. Note that these are synchronous
1132+
methods. The `ioctl` method (see below) ensures that they are only called if
1133+
data is available. The methods should return as fast as possible with as much
1134+
data as is available.
1135+
1136+
`readline()` Return as many characters as are available up to and including any
1137+
newline character. Required if you intend to use `StreamReader.readline()`
1138+
`read(n)` Return as many characters as are available but no more than `n`.
1139+
Required if you plan to use `StreamReader.read()` or
1140+
`StreamReader.readexactly()`
1141+
1142+
A writeable driver must provide this synchronous method:
1143+
`write` Args `buf`, `off`, `sz`. Arguments:
1144+
`buf` is the buffer to write.
1145+
`off` is the offset into the buffer of the first character to write.
1146+
`sz` is the requested number of characters to write.
1147+
It should return immediately. The return value is the number of characters
1148+
actually written (may well be 1 if the device is slow). The `ioctl` method
1149+
ensures that this is only called if the device is ready to accept data.
1150+
1151+
All devices must provide an `ioctl` method which polls the hardware to
1152+
determine its ready status. A typical example for a read/write driver is:
1153+
1154+
```python
1155+
MP_STREAM_POLL_RD = const(1)
1156+
MP_STREAM_POLL_WR = const(4)
1157+
MP_STREAM_POLL = const(3)
1158+
MP_STREAM_ERROR = const(-1)
1159+
1160+
def ioctl(self, req, arg): # see ports/stm32/uart.c
1161+
ret = MP_STREAM_ERROR
1162+
if req == MP_STREAM_POLL:
1163+
ret = 0
1164+
if arg & MP_STREAM_POLL_RD:
1165+
if hardware_has_at_least_one_char_to_read:
1166+
ret |= MP_STREAM_POLL_RD
1167+
if arg & MP_STREAM_POLL_WR:
1168+
if hardware_can_accept_at_least_one_write_character:
1169+
ret |= MP_STREAM_POLL_WR
1170+
return ret
1171+
```
1172+
1173+
The demo program `iorw.py` illustrates a complete example. Note that, at the
1174+
time of writing there is a bug in `uasyncio` which prevents this from woking.
1175+
See [this GitHub thread](https://github.com/micropython/micropython/pull/3836#issuecomment-397317408).
1176+
The workround is to write two separate drivers, one read-only and the other
1177+
write-only.
1178+
1179+
###### [Contents](./TUTORIAL.md#contents)
1180+
1181+
## 5.3 Polling hardware without IORead
11151182

11161183
This is a simple approach, but is only appropriate to hardware which is to be
11171184
polled at a relatively low rate. This is for two reasons. Firstly the variable
@@ -1193,21 +1260,6 @@ loop.run_until_complete(run())
11931260

11941261
###### [Contents](./TUTORIAL.md#contents)
11951262

1196-
## 5.3 Using IORead to poll hardware
1197-
1198-
The uasyncio `IORead` class is provided to support IO to stream devices. It
1199-
may be employed by drivers of devices which need to be polled: the polling will
1200-
be delegated to the scheduler which uses `select` to schedule the first
1201-
stream or device driver to be ready. This is more efficient, and offers lower
1202-
latency, than running multiple coros each polling a device.
1203-
1204-
At the time of writing firmware support for using this mechanism in device
1205-
drivers written in Python has not been implemented, and the final comment to
1206-
[this](https://github.com/micropython/micropython/issues/2664) issue suggests
1207-
that it may never be done. So streaming device drivers must be written in C.
1208-
1209-
###### [Contents](./TUTORIAL.md#contents)
1210-
12111263
## 5.4 A complete example: aremote.py
12121264

12131265
This may be found in the `nec_ir` directory. Its use is documented

aledflash.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
# Run on MicroPython board bare hardware
66

77
import pyb
8-
try:
9-
import asyncio_priority as asyncio
10-
except ImportError:
11-
import uasyncio as asyncio
8+
import uasyncio as asyncio
129

1310
async def killer(duration):
1411
await asyncio.sleep(duration)

apoll.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
# Author: Peter Hinch
66
# Copyright Peter Hinch 2017 Released under the MIT license
77

8-
try:
9-
import asyncio_priority as asyncio
10-
except ImportError:
11-
import uasyncio as asyncio
8+
import uasyncio as asyncio
129
import pyb
1310
import utime as time
1411

aqtest.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22
# Author: Peter Hinch
33
# Copyright Peter Hinch 2017 Released under the MIT license
44

5-
try:
6-
import asyncio_priority as asyncio
7-
except ImportError:
8-
import uasyncio as asyncio
5+
import uasyncio as asyncio
96

107
from uasyncio.queues import Queue
118

0 commit comments

Comments
 (0)