Skip to content

Commit 0ae62dd

Browse files
committed
Prior to testing machine.schedule
1 parent 626d0a6 commit 0ae62dd

File tree

13 files changed

+1098
-28
lines changed

13 files changed

+1098
-28
lines changed

i2c/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ application and is covered in detail
2727

2828
## Changes
2929

30-
V0.17 Dec 2018 Initiator: add optional "go" and "fail" user coroutines.
30+
V0.17 Dec 2018 Initiator: add optional "go" and "fail" user coroutines.
3131
V0.16 Minor improvements and bugfixes. Eliminate `timeout` option which caused
32-
failures where `Responder` was a Pyboard.
33-
V0.15 RAM allocation reduced. Flow control implemented.
32+
failures where `Responder` was a Pyboard.
33+
V0.15 RAM allocation reduced. Flow control implemented.
3434
V0.1 Initial release.
3535

3636
###### [Main README](../README.md)

v3/README.md

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ Documented in the tutorial.
2222

2323
#### Asynchronous device drivers
2424

25-
The device drivers are in the process of being ported. These currently
26-
comprise:
25+
These device drivers are intended as examples of asynchronous code which are
26+
useful in their own right:
2727

2828
* [GPS driver](./docs/GPS.md) Includes various GPS utilities.
2929
* [HTU21D](./docs/HTU21D.md) Temperature and humidity sensor.
30+
* [I2C](./docs/I2C.md) Use Pyboard I2C slave mode to implement a UART-like
31+
asynchronous stream interface. Typical use: communication with ESP8266.
32+
* [NEC IR](./docs/NEC_IR.md) A receiver for signals from IR remote controls
33+
using the popular NEC protocol.
3034

3135
# 2 V3 Overview
3236

@@ -65,10 +69,12 @@ The `Future` class is not supported, nor are the `event_loop` methods
6569
# 3 Porting applications from V2
6670

6771
Many applications using the coding style advocated in the V2 tutorial will work
68-
unchanged. However there are changes, firstly to `uasyncio` syntax and secondly
69-
related to modules in this repository.
72+
unchanged. However there are changes, firstly to `uasyncio` itself and secondly
73+
to modules in this repository.
7074

71-
## 3.1 Syntax changes
75+
## 3.1 Changes to uasyncio
76+
77+
### 3.1.1 Syntax changes
7278

7379
* Task cancellation: `cancel` is now a method of a `Task` instance.
7480
* Event loop methods: `call_at`, `call_later`, `call_later_ms` and
@@ -84,10 +90,16 @@ It is possible to write an awaitable class with code portable between
8490
MicroPython and CPython 3.8. This is discussed
8591
[in the tutorial](./docs/TUTORIAL.md#412-portable-code).
8692

93+
### 3.1.2 Change to stream I/O
94+
95+
Classes based on `uio.IOBase` will need changes to the `write` method. See
96+
[tutorial](./docs/TUTORIAL.md##64-writing-streaming-device-drivers).
97+
8798
## 3.2 Modules from this repository
8899

89100
Modules `asyn.py` and `aswitch.py` are deprecated for V3 applications. See
90-
[the tutorial](./docs/TUTORIAL.md#3-synchronisation) for V3 replacements.
101+
[the tutorial](./docs/TUTORIAL.md#3-synchronisation) for V3 replacements which
102+
are more RAM-efficient.
91103

92104
### 3.2.1 Synchronisation primitives
93105

v3/as_drivers/i2c/__init__.py

Whitespace-only changes.

v3/as_drivers/i2c/asi2c.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# asi2c.py A communications link using I2C slave mode on Pyboard.
2+
# Channel and Responder classes. Adapted for uasyncio V3, WBUS DIP28.
3+
4+
# The MIT License (MIT)
5+
#
6+
# Copyright (c) 2018-2020 Peter Hinch
7+
#
8+
# Permission is hereby granted, free of charge, to any person obtaining a copy
9+
# of this software and associated documentation files (the "Software"), to deal
10+
# in the Software without restriction, including without limitation the rights
11+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12+
# copies of the Software, and to permit persons to whom the Software is
13+
# furnished to do so, subject to the following conditions:
14+
#
15+
# The above copyright notice and this permission notice shall be included in
16+
# all copies or substantial portions of the Software.
17+
#
18+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24+
# THE SOFTWARE.
25+
26+
import uasyncio as asyncio
27+
import machine
28+
import utime
29+
from micropython import const
30+
import io
31+
32+
_MP_STREAM_POLL_RD = const(1)
33+
_MP_STREAM_POLL_WR = const(4)
34+
_MP_STREAM_POLL = const(3)
35+
_MP_STREAM_ERROR = const(-1)
36+
# Delay compensates for short Responder interrupt latency. Must be >= max delay
37+
# between Initiator setting a pin and initiating an I2C transfer: ensure
38+
# Initiator sets up first.
39+
_DELAY = const(20) # μs
40+
41+
42+
# Base class provides user interface and send/receive object buffers
43+
class Channel(io.IOBase):
44+
def __init__(self, i2c, own, rem, verbose, rxbufsize):
45+
self.rxbufsize = rxbufsize
46+
self.verbose = verbose
47+
self.synchronised = False
48+
# Hardware
49+
self.i2c = i2c
50+
self.own = own
51+
self.rem = rem
52+
own.init(mode=machine.Pin.OUT, value=1)
53+
rem.init(mode=machine.Pin.IN, pull=machine.Pin.PULL_UP)
54+
# I/O
55+
self.txbyt = b'' # Data to send
56+
self.txsiz = bytearray(2) # Size of .txbyt encoded as 2 bytes
57+
self.rxbyt = b''
58+
self.rxbuf = bytearray(rxbufsize)
59+
self.rx_mv = memoryview(self.rxbuf)
60+
self.cantx = True # Remote can accept data
61+
62+
async def _sync(self):
63+
self.verbose and print('Synchronising')
64+
self.own(0)
65+
while self.rem():
66+
await asyncio.sleep_ms(100)
67+
# Both pins are now low
68+
await asyncio.sleep(0)
69+
self.verbose and print('Synchronised')
70+
self.synchronised = True
71+
72+
def waitfor(self, val): # Initiator overrides
73+
while not self.rem() == val:
74+
pass
75+
76+
# Get incoming bytes instance from memoryview.
77+
def _handle_rxd(self, msg):
78+
self.rxbyt = bytes(msg)
79+
80+
def _txdone(self):
81+
self.txbyt = b''
82+
self.txsiz[0] = 0
83+
self.txsiz[1] = 0
84+
85+
# Stream interface
86+
87+
def ioctl(self, req, arg):
88+
ret = _MP_STREAM_ERROR
89+
if req == _MP_STREAM_POLL:
90+
ret = 0
91+
if self.synchronised:
92+
if arg & _MP_STREAM_POLL_RD:
93+
if self.rxbyt:
94+
ret |= _MP_STREAM_POLL_RD
95+
if arg & _MP_STREAM_POLL_WR:
96+
if (not self.txbyt) and self.cantx:
97+
ret |= _MP_STREAM_POLL_WR
98+
return ret
99+
100+
def readline(self):
101+
n = self.rxbyt.find(b'\n')
102+
if n == -1:
103+
t = self.rxbyt[:]
104+
self.rxbyt = b''
105+
else:
106+
t = self.rxbyt[: n + 1]
107+
self.rxbyt = self.rxbyt[n + 1:]
108+
return t.decode()
109+
110+
def read(self, n):
111+
t = self.rxbyt[:n]
112+
self.rxbyt = self.rxbyt[n:]
113+
return t.decode()
114+
115+
# Set .txbyt to the required data. Return its size. So awrite returns
116+
# with transmission occurring in tha background.
117+
# uasyncio V3: Stream.drain() calls write with buf being a memoryview
118+
# and no off or sz args.
119+
def write(self, buf):
120+
if self.synchronised:
121+
if self.txbyt: # Initial call from awrite
122+
return 0 # Waiting for existing data to go out
123+
l = len(buf)
124+
self.txbyt = buf
125+
self.txsiz[0] = l & 0xff
126+
self.txsiz[1] = l >> 8
127+
return l
128+
return 0
129+
130+
# User interface
131+
132+
# Wait for sync
133+
async def ready(self):
134+
while not self.synchronised:
135+
await asyncio.sleep_ms(100)
136+
137+
# Leave pin high in case we run again
138+
def close(self):
139+
self.own(1)
140+
141+
142+
# Responder is I2C master. It is cross-platform and uses machine.
143+
# It does not handle errors: if I2C fails it dies and awaits reset by initiator.
144+
# send_recv is triggered by Interrupt from Initiator.
145+
146+
class Responder(Channel):
147+
addr = 0x12
148+
rxbufsize = 200
149+
150+
def __init__(self, i2c, pin, pinack, verbose=True):
151+
super().__init__(i2c, pinack, pin, verbose, self.rxbufsize)
152+
loop = asyncio.get_event_loop()
153+
loop.create_task(self._run())
154+
155+
async def _run(self):
156+
await self._sync() # own pin ->0, wait for remote pin == 0
157+
self.rem.irq(handler=self._handler, trigger=machine.Pin.IRQ_RISING)
158+
159+
# Request was received: immediately read payload size, then payload
160+
# On Pyboard blocks for 380μs to 1.2ms for small amounts of data
161+
def _handler(self, _, sn=bytearray(2), txnull=bytearray(2)):
162+
addr = Responder.addr
163+
self.rem.irq(handler=None, trigger=machine.Pin.IRQ_RISING)
164+
utime.sleep_us(_DELAY) # Ensure Initiator has set up to write.
165+
self.i2c.readfrom_into(addr, sn)
166+
self.own(1)
167+
self.waitfor(0)
168+
self.own(0)
169+
n = sn[0] + ((sn[1] & 0x7f) << 8) # no of bytes to receive
170+
if n > self.rxbufsize:
171+
raise ValueError('Receive data too large for buffer.')
172+
self.cantx = not bool(sn[1] & 0x80) # Can Initiator accept a payload?
173+
if n:
174+
self.waitfor(1)
175+
utime.sleep_us(_DELAY)
176+
mv = memoryview(self.rx_mv[0: n]) # allocates
177+
self.i2c.readfrom_into(addr, mv)
178+
self.own(1)
179+
self.waitfor(0)
180+
self.own(0)
181+
self._handle_rxd(mv)
182+
183+
self.own(1) # Request to send
184+
self.waitfor(1)
185+
utime.sleep_us(_DELAY)
186+
dtx = self.txbyt != b'' and self.cantx # Data to send
187+
siz = self.txsiz if dtx else txnull
188+
if self.rxbyt:
189+
siz[1] |= 0x80 # Hold off Initiator TX
190+
else:
191+
siz[1] &= 0x7f
192+
self.i2c.writeto(addr, siz) # Was getting ENODEV occasionally on Pyboard
193+
self.own(0)
194+
self.waitfor(0)
195+
if dtx:
196+
self.own(1)
197+
self.waitfor(1)
198+
utime.sleep_us(_DELAY)
199+
self.i2c.writeto(addr, self.txbyt)
200+
self.own(0)
201+
self.waitfor(0)
202+
self._txdone() # Invalidate source
203+
self.rem.irq(handler=self._handler, trigger=machine.Pin.IRQ_RISING)

0 commit comments

Comments
 (0)