From d899710e43a1f7966261f41f1bd5baf00b20f623 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Sat, 20 Oct 2018 15:31:09 +0100 Subject: [PATCH 1/3] Fails on ESP8266 after ~1000 passes. --- i2c/asi2c.py | 36 ++++++++++++++++++++++++++++-------- i2c/asi2c_i.py | 6 ++++-- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/i2c/asi2c.py b/i2c/asi2c.py index ade4ea4..59e39ad 100644 --- a/i2c/asi2c.py +++ b/i2c/asi2c.py @@ -27,6 +27,10 @@ import utime from micropython import const, schedule import io +import gc + +import micropython +micropython.alloc_emergency_exception_buf(100) _MP_STREAM_POLL_RD = const(1) _MP_STREAM_POLL_WR = const(4) @@ -36,6 +40,8 @@ # between Initiator setting a pin and initiating an I2C transfer: ensure # Initiator sets up first. _DELAY = const(20) # μs +# bytes objects are transmitted in blocks of size 16N where N is an integer +upsize = lambda x : (x + 15) & ~15 # Base class provides user interface and send/receive object buffers class Channel(io.IOBase): @@ -52,8 +58,12 @@ def __init__(self, i2c, own, rem, verbose, rxbufsize): self.txbyt = b'' # Data to send self.txsiz = bytearray(2) # Size of .txbyt encoded as 2 bytes self.rxbyt = b'' - self.rxbuf = bytearray(rxbufsize) + self.rxbuf = bytearray(upsize(rxbufsize)) # Hold an integer no. of blocks self.rx_mv = memoryview(self.rxbuf) + self.lstmv = [] + for n in range(16, len(self.rxbuf) + 16, 16): # Preallocate memoryviews + self.lstmv.append(self.rx_mv[0 : n]) # for all data lengths + self.nrx = 0 # No. of bytes received (ignoring padding) self.cantx = True # Remote can accept data async def _sync(self): @@ -72,7 +82,7 @@ def waitfor(self, val): # Initiator overrides # Get incoming bytes instance from memoryview. def _handle_rxd(self, msg): - self.rxbyt = bytes(msg) + self.rxbyt = bytes(msg[:self.nrx]) def _txdone(self): self.txbyt = b'' @@ -122,7 +132,8 @@ def write(self, buf, off, sz): d = buf[off : off + sz] d = d.encode() l = len(d) - self.txbyt = d + # Pad to integer no. of blocks + self.txbyt = b''.join((d, bytes(upsize(l) - l))) self.txsiz[0] = l & 0xff self.txsiz[1] = l >> 8 return l @@ -148,19 +159,27 @@ class Responder(Channel): rxbufsize = 200 def __init__(self, i2c, pin, pinack, verbose=True): super().__init__(i2c, pinack, pin, verbose, self.rxbufsize) + self._handle_rxd_ref = self._handle_rxd # Alocate RAM here + self._re_enable_ref = self._re_enable loop = asyncio.get_event_loop() loop.create_task(self._run()) async def _run(self): await self._sync() # own pin ->0, wait for remote pin == 0 - self.rem.irq(handler = self._handler, trigger = machine.Pin.IRQ_RISING) + self.rem.irq(handler = self._handler, trigger = machine.Pin.IRQ_RISING, hard = True) + while True: + await asyncio.sleep(1) + gc.collect() + + def _re_enable(self, _): + self.rem.irq(handler = self._handler, trigger = machine.Pin.IRQ_RISING, hard = True) # Request was received: immediately read payload size, then payload # On Pyboard blocks for 380μs to 1.2ms for small amounts of data def _handler(self, _, sn=bytearray(2), txnull=bytearray(2)): # tstart = utime.ticks_us() # TEST addr = Responder.addr - self.rem.irq(handler = None, trigger = machine.Pin.IRQ_RISING) + self.rem.irq(handler = None, trigger = machine.Pin.IRQ_RISING, hard = True) utime.sleep_us(_DELAY) # Ensure Initiator has set up to write. self.i2c.readfrom_into(addr, sn) self.own(1) @@ -171,12 +190,13 @@ def _handler(self, _, sn=bytearray(2), txnull=bytearray(2)): if n: self.waitfor(1) utime.sleep_us(_DELAY) - mv = memoryview(self.rx_mv[0 : n]) # allocates + mv = self.lstmv[(n >> 4)] self.i2c.readfrom_into(addr, mv) self.own(1) self.waitfor(0) self.own(0) - schedule(self._handle_rxd, mv) # Postpone allocation + self.nrx = n + schedule(self._handle_rxd_ref, mv) # Postpone allocation self.own(1) # Request to send self.waitfor(1) @@ -198,5 +218,5 @@ def _handler(self, _, sn=bytearray(2), txnull=bytearray(2)): self.own(0) self.waitfor(0) self._txdone() # Invalidate source - self.rem.irq(handler = self._handler, trigger = machine.Pin.IRQ_RISING) + schedule(self._re_enable_ref, 0) # print('Time: ', utime.ticks_diff(utime.ticks_us(), tstart)) diff --git a/i2c/asi2c_i.py b/i2c/asi2c_i.py index 2a08735..4586515 100644 --- a/i2c/asi2c_i.py +++ b/i2c/asi2c_i.py @@ -28,7 +28,7 @@ import utime import gc from micropython import schedule -from asi2c import Channel +from asi2c import Channel, upsize # The initiator is an I2C slave. It runs on a Pyboard. I2C uses pyb for slave # mode, but pins are instantiated using machine. @@ -128,10 +128,12 @@ def _sendrx(self, rxbusy, sn=bytearray(2), txnull=bytearray(2)): if n: self.waitfor(1) # Wait for responder to request send self.own(1) # Acknowledge - mv = memoryview(self.rx_mv[0 : n]) + mv = self.lstmv[(n >> 4)] +# mv = memoryview(self.rx_mv[0 : upsize(n)]) self.i2c.recv(mv, timeout=to) self.waitfor(0) self.own(0) + self.nrx = n schedule(self._handle_rxd, mv) # Postpone allocation return True return False From 03e6f718cff55c8bab057c0c901c4a129898b061 Mon Sep 17 00:00:00 2001 From: peterhinch Date: Sat, 20 Oct 2018 18:09:36 +0100 Subject: [PATCH 2/3] Re-initialise IRQ at end of ISR (via reference) --- i2c/asi2c.py | 7 ++----- i2c/asi2c_i.py | 3 ++- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/i2c/asi2c.py b/i2c/asi2c.py index 59e39ad..00838ed 100644 --- a/i2c/asi2c.py +++ b/i2c/asi2c.py @@ -160,7 +160,7 @@ class Responder(Channel): def __init__(self, i2c, pin, pinack, verbose=True): super().__init__(i2c, pinack, pin, verbose, self.rxbufsize) self._handle_rxd_ref = self._handle_rxd # Alocate RAM here - self._re_enable_ref = self._re_enable + self._handler_ref = self._handler loop = asyncio.get_event_loop() loop.create_task(self._run()) @@ -171,9 +171,6 @@ async def _run(self): await asyncio.sleep(1) gc.collect() - def _re_enable(self, _): - self.rem.irq(handler = self._handler, trigger = machine.Pin.IRQ_RISING, hard = True) - # Request was received: immediately read payload size, then payload # On Pyboard blocks for 380μs to 1.2ms for small amounts of data def _handler(self, _, sn=bytearray(2), txnull=bytearray(2)): @@ -218,5 +215,5 @@ def _handler(self, _, sn=bytearray(2), txnull=bytearray(2)): self.own(0) self.waitfor(0) self._txdone() # Invalidate source - schedule(self._re_enable_ref, 0) + self.rem.irq(handler = self._handler_ref, trigger = machine.Pin.IRQ_RISING, hard = True) # print('Time: ', utime.ticks_diff(utime.ticks_us(), tstart)) diff --git a/i2c/asi2c_i.py b/i2c/asi2c_i.py index 4586515..c8f7bdb 100644 --- a/i2c/asi2c_i.py +++ b/i2c/asi2c_i.py @@ -85,8 +85,9 @@ async def _run(self): except OSError: break await asyncio.sleep_ms(Initiator.t_poll) - self.block_max = max(self.block_max, t) # self measurement self.block_cnt += 1 + if self.block_cnt > 2: # Avoid any start-up effects + self.block_max = max(self.block_max, t) # self measurement self.block_sum += t self.nboots += 1 if self.reset is None: # No means of recovery From 2e9d7ac4ffbd61092e331be4c7f6cd828f1de345 Mon Sep 17 00:00:00 2001 From: peterhinch Date: Sun, 21 Oct 2018 11:40:44 +0100 Subject: [PATCH 3/3] Revert to soft. Pyboard responder not working. --- i2c/asi2c.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/i2c/asi2c.py b/i2c/asi2c.py index 00838ed..995c3eb 100644 --- a/i2c/asi2c.py +++ b/i2c/asi2c.py @@ -166,7 +166,7 @@ def __init__(self, i2c, pin, pinack, verbose=True): async def _run(self): await self._sync() # own pin ->0, wait for remote pin == 0 - self.rem.irq(handler = self._handler, trigger = machine.Pin.IRQ_RISING, hard = True) + self.rem.irq(handler = self._handler, trigger = machine.Pin.IRQ_RISING) #, hard = True) while True: await asyncio.sleep(1) gc.collect() @@ -176,7 +176,7 @@ async def _run(self): def _handler(self, _, sn=bytearray(2), txnull=bytearray(2)): # tstart = utime.ticks_us() # TEST addr = Responder.addr - self.rem.irq(handler = None, trigger = machine.Pin.IRQ_RISING, hard = True) + self.rem.irq(handler = None, trigger = machine.Pin.IRQ_RISING) #, hard = True) utime.sleep_us(_DELAY) # Ensure Initiator has set up to write. self.i2c.readfrom_into(addr, sn) self.own(1) @@ -215,5 +215,5 @@ def _handler(self, _, sn=bytearray(2), txnull=bytearray(2)): self.own(0) self.waitfor(0) self._txdone() # Invalidate source - self.rem.irq(handler = self._handler_ref, trigger = machine.Pin.IRQ_RISING, hard = True) + #self.rem.irq(handler = self._handler_ref, trigger = machine.Pin.IRQ_RISING) #, hard = True) # print('Time: ', utime.ticks_diff(utime.ticks_us(), tstart))