Skip to content

Commit 690d0fc

Browse files
committed
Add updated syncom.
1 parent db07eb2 commit 690d0fc

File tree

5 files changed

+612
-0
lines changed

5 files changed

+612
-0
lines changed

v3/as_drivers/syncom/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import webrepl
2+
webrepl.start()
3+
import sr_passive
4+
sr_passive.test()

v3/as_drivers/syncom/sr_init.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# sr_init.py Test of synchronous comms library. Initiator end.
2+
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) 2016 Peter Hinch
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in
15+
# all copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
# THE SOFTWARE.
24+
25+
# Run on Pyboard
26+
from machine import Pin, Signal
27+
from pyb import LED
28+
import uasyncio as asyncio
29+
from utime import ticks_ms, ticks_diff
30+
from syncom import SynCom, SynComError
31+
32+
33+
async def initiator_task(channel):
34+
while True:
35+
so = ['test', 0, 0]
36+
for x in range(4): # Test full duplex by sending 4 in succession
37+
so[1] = x
38+
channel.send(so)
39+
await asyncio.sleep_ms(0)
40+
while True: # Receive the four responses
41+
si = await channel.await_obj() # Deal with queue
42+
if si is None:
43+
print('Timeout: restarting.')
44+
return
45+
print('initiator received', si)
46+
if si[1] == 3: # received last one
47+
break
48+
while True: # At 2 sec intervals send an object and get response
49+
await asyncio.sleep(2)
50+
print('sending', so)
51+
channel.send(so)
52+
tim = ticks_ms()
53+
so = await channel.await_obj() # wait for response
54+
duration = ticks_diff(ticks_ms(), tim)
55+
if so is None:
56+
print('Timeout: restarting.')
57+
return
58+
print('initiator received', so, 'timing', duration)
59+
60+
async def heartbeat():
61+
led = LED(1)
62+
while True:
63+
await asyncio.sleep_ms(500)
64+
led.toggle()
65+
66+
def test():
67+
dout = Pin(Pin.board.Y5, Pin.OUT_PP, value = 0) # Define pins
68+
ckout = Pin(Pin.board.Y6, Pin.OUT_PP, value = 0) # Don't assert clock until data is set
69+
din = Pin(Pin.board.Y7, Pin.IN)
70+
ckin = Pin(Pin.board.Y8, Pin.IN)
71+
reset = Pin(Pin.board.Y4, Pin.OPEN_DRAIN)
72+
sig_reset = Signal(reset, invert = True)
73+
74+
channel = SynCom(False, ckin, ckout, din, dout, sig_reset, 10000)
75+
76+
loop = asyncio.get_event_loop()
77+
loop.create_task(heartbeat())
78+
loop.create_task(channel.start(initiator_task))
79+
try:
80+
loop.run_forever()
81+
except KeyboardInterrupt:
82+
pass
83+
finally:
84+
ckout.value(0)
85+
86+
test()

v3/as_drivers/syncom/sr_passive.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# sr_passive.py Test of synchronous comms library. Passive end.
2+
3+
# The MIT License (MIT)
4+
#
5+
# Copyright (c) 2016 Peter Hinch
6+
#
7+
# Permission is hereby granted, free of charge, to any person obtaining a copy
8+
# of this software and associated documentation files (the "Software"), to deal
9+
# in the Software without restriction, including without limitation the rights
10+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
# copies of the Software, and to permit persons to whom the Software is
12+
# furnished to do so, subject to the following conditions:
13+
#
14+
# The above copyright notice and this permission notice shall be included in
15+
# all copies or substantial portions of the Software.
16+
#
17+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
# THE SOFTWARE.
24+
25+
# Run on ESP8266
26+
import uasyncio as asyncio
27+
from syncom import SynCom
28+
from machine import Pin, freq
29+
import gc
30+
31+
async def passive_task(chan):
32+
while True:
33+
obj = await chan.await_obj()
34+
if obj is not None: # Ignore timeouts
35+
# print('passive received: ', obj)
36+
obj[2] += 1 # modify object and send it back
37+
chan.send(obj)
38+
39+
async def heartbeat():
40+
led = Pin(2, Pin.OUT)
41+
while True:
42+
await asyncio.sleep_ms(500)
43+
led(not led())
44+
gc.collect()
45+
46+
def test():
47+
freq(160000000)
48+
dout = Pin(14, Pin.OUT, value = 0) # Define pins
49+
ckout = Pin(15, Pin.OUT, value = 0) # clocks must be initialised to zero.
50+
din = Pin(13, Pin.IN)
51+
ckin = Pin(12, Pin.IN)
52+
53+
channel = SynCom(True, ckin, ckout, din, dout)
54+
loop = asyncio.get_event_loop()
55+
loop.create_task(heartbeat())
56+
loop.create_task(channel.start(passive_task))
57+
try:
58+
loop.run_forever()
59+
except KeyboardInterrupt:
60+
pass
61+
finally:
62+
ckout(0)
63+
64+
test()

v3/as_drivers/syncom/syncom.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# syncom.py Synchronous communication channel between two MicroPython
2+
# platforms. 4 June 2017
3+
# Uses uasyncio.
4+
5+
# The MIT License (MIT)
6+
#
7+
# Copyright (c) 2017-2021 Peter Hinch
8+
#
9+
# Permission is hereby granted, free of charge, to any person obtaining a copy
10+
# of this software and associated documentation files (the "Software"), to deal
11+
# in the Software without restriction, including without limitation the rights
12+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13+
# copies of the Software, and to permit persons to whom the Software is
14+
# furnished to do so, subject to the following conditions:
15+
#
16+
# The above copyright notice and this permission notice shall be included in
17+
# all copies or substantial portions of the Software.
18+
#
19+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25+
# THE SOFTWARE.
26+
27+
# Timing: was 4.5mS per char between Pyboard and ESP8266 i.e. ~1.55Kbps. But
28+
# this version didn't yield on every bit, invalidating t/o detection.
29+
# New asyncio version yields on every bit.
30+
# Instantaneous bit rate running ESP8266 at 160MHz: 1.6Kbps
31+
# Mean throughput running test programs 8.8ms per char (800bps).
32+
33+
from utime import ticks_diff, ticks_ms
34+
import uasyncio as asyncio
35+
from micropython import const
36+
import ujson
37+
38+
_BITS_PER_CH = const(7)
39+
_BITS_SYN = const(8)
40+
_SYN = const(0x9d)
41+
_RX_BUFLEN = const(100)
42+
43+
class SynComError(Exception):
44+
pass
45+
46+
class SynCom:
47+
def __init__(self, passive, ckin, ckout, din, dout, pin_reset=None,
48+
timeout=0, string_mode=False, verbose=True): # Signal unsupported on rp2
49+
self.passive = passive
50+
self.string_mode = string_mode
51+
self._running = False # _run coro is down
52+
self._synchronised = False
53+
self.verbose = verbose
54+
self.idstr = 'passive' if self.passive else 'initiator'
55+
56+
self.ckin = ckin # Interface pins
57+
self.ckout = ckout
58+
self.din = din
59+
self.dout = dout
60+
self.pin_reset = pin_reset
61+
62+
self._timeout = timeout # In ms. 0 == No timeout.
63+
self.lsttx = [] # Queue of strings to send
64+
self.lstrx = [] # Queue of received strings
65+
66+
# Start interface and initiate an optional user task. If a timeout and reset
67+
# signal are specified and the target times out, the target is reset and the
68+
# interface restarted. If a user task is provided, this must return if a
69+
# timeout occurs (i.e. not running() or await_obj returns None).
70+
# If it returns for other (error) reasons, a timeout event is forced.
71+
async def start(self, user_task=None, awaitable=None):
72+
while True:
73+
if not self._running: # Restarting
74+
self.lstrx = [] # Clear down queues
75+
self.lsttx = []
76+
self._synchronised = False
77+
asyncio.create_task(self._run()) # Reset target (if possible)
78+
while not self._synchronised: # Wait for sync
79+
await asyncio.sleep_ms(100)
80+
if user_task is None:
81+
while self._running:
82+
await asyncio.sleep_ms(100)
83+
else:
84+
await user_task(self) # User task must quit on timeout
85+
# If it quit for other reasons force a t/o exception
86+
self.stop()
87+
await asyncio.sleep_ms(0)
88+
if awaitable is not None:
89+
await awaitable() # Optional user coro
90+
91+
# Can be used to force a failure
92+
def stop(self):
93+
self._running = False
94+
self.dout(0)
95+
self.ckout(0)
96+
97+
# Queue an object for tx. Convert to string NOW: snapshot of current
98+
# object state
99+
def send(self, obj):
100+
if self.string_mode:
101+
self.lsttx.append(obj) # strings are immutable
102+
else:
103+
self.lsttx.append(ujson.dumps(obj))
104+
105+
# Number of queued objects (None on timeout)
106+
def any(self):
107+
if self._running:
108+
return len(self.lstrx)
109+
110+
# Wait for an object. Return None on timeout.
111+
# If in string mode returns a string (or None on t/o)
112+
async def await_obj(self, t_ms=10):
113+
while self._running:
114+
await asyncio.sleep_ms(t_ms)
115+
if len(self.lstrx):
116+
return self.lstrx.pop(0)
117+
118+
# running() is False if the target has timed out.
119+
def running(self):
120+
return self._running
121+
122+
# Private methods
123+
async def _run(self):
124+
self.indata = 0 # Current data bits
125+
self.inbits = 0
126+
self.odata = _SYN
127+
self.phase = 0 # Interface initial conditions
128+
if self.passive:
129+
self.dout(0)
130+
self.ckout(0)
131+
else:
132+
self.dout(self.odata & 1)
133+
self.ckout(1)
134+
self.odata >>= 1 # we've sent that bit
135+
self.phase = 1
136+
if self.pin_reset is not None:
137+
self.verbose and print(self.idstr, ' resetting target...')
138+
self.pin_reset(0)
139+
await asyncio.sleep_ms(100)
140+
self.pin_reset(1)
141+
await asyncio.sleep(1) # let target settle down
142+
143+
self.verbose and print(self.idstr, ' awaiting sync...')
144+
try:
145+
self._running = True # False on failure: can be cleared by other tasks
146+
while self.indata != _SYN: # Don't hog CPU while waiting for start
147+
await self._synchronise()
148+
self._synchronised = True
149+
self.verbose and print(self.idstr, ' synchronised.')
150+
151+
sendstr = '' # string for transmission
152+
send_idx = None # character index. None: no current string
153+
getstr = '' # receive string
154+
rxbuf = bytearray(_RX_BUFLEN)
155+
rxidx = 0
156+
while True:
157+
if send_idx is None:
158+
if len(self.lsttx):
159+
sendstr = self.lsttx.pop(0) # oldest first
160+
send_idx = 0
161+
if send_idx is not None:
162+
if send_idx < len(sendstr):
163+
self.odata = ord(sendstr[send_idx])
164+
send_idx += 1
165+
else:
166+
send_idx = None
167+
if send_idx is None: # send zeros when nothing to send
168+
self.odata = 0
169+
if self.passive:
170+
await self._get_byte_passive()
171+
else:
172+
await self._get_byte_active()
173+
if self.indata: # Optimisation: buffer reduces allocations.
174+
if rxidx >= _RX_BUFLEN: # Buffer full: append to string.
175+
getstr = ''.join((getstr, bytes(rxbuf).decode()))
176+
rxidx = 0
177+
rxbuf[rxidx] = self.indata
178+
rxidx += 1
179+
elif rxidx or len(getstr): # Got 0 but have data so string is complete.
180+
# Append buffer.
181+
getstr = ''.join((getstr, bytes(rxbuf[:rxidx]).decode()))
182+
if self.string_mode:
183+
self.lstrx.append(getstr)
184+
else:
185+
try:
186+
self.lstrx.append(ujson.loads(getstr))
187+
except: # ujson fail means target has crashed
188+
raise SynComError
189+
getstr = '' # Reset for next string
190+
rxidx = 0
191+
192+
except SynComError:
193+
if self._running:
194+
self.verbose and print('SynCom Timeout.')
195+
else:
196+
self.verbose and print('SynCom was stopped.')
197+
finally:
198+
self.stop()
199+
200+
async def _get_byte_active(self):
201+
inbits = 0
202+
for _ in range(_BITS_PER_CH):
203+
inbits = await self._get_bit(inbits) # LSB first
204+
self.indata = inbits
205+
206+
async def _get_byte_passive(self):
207+
self.indata = await self._get_bit(self.inbits) # MSB is outstanding
208+
inbits = 0
209+
for _ in range(_BITS_PER_CH - 1):
210+
inbits = await self._get_bit(inbits)
211+
self.inbits = inbits
212+
213+
async def _synchronise(self): # wait for clock
214+
t = ticks_ms()
215+
while self.ckin() == self.phase ^ self.passive ^ 1:
216+
# Other tasks can clear self._running by calling stop()
217+
if (self._timeout and ticks_diff(ticks_ms(), t) > self._timeout) or not self._running:
218+
raise SynComError
219+
await asyncio.sleep_ms(0)
220+
self.indata = (self.indata | (self.din() << _BITS_SYN)) >> 1
221+
odata = self.odata
222+
self.dout(odata & 1)
223+
self.odata = odata >> 1
224+
self.phase ^= 1
225+
self.ckout(self.phase) # set clock
226+
227+
async def _get_bit(self, dest):
228+
t = ticks_ms()
229+
while self.ckin() == self.phase ^ self.passive ^ 1:
230+
if (self._timeout and ticks_diff(ticks_ms(), t) > self._timeout) or not self._running:
231+
raise SynComError
232+
await asyncio.sleep_ms(0)
233+
dest = (dest | (self.din() << _BITS_PER_CH)) >> 1
234+
obyte = self.odata
235+
self.dout(obyte & 1)
236+
self.odata = obyte >> 1
237+
self.phase ^= 1
238+
self.ckout(self.phase)
239+
return dest

0 commit comments

Comments
 (0)