kopia lustrzana https://github.com/peterhinch/micropython-samples
SPI slave: add tx.py, rx.py, arx.py. Callback only runs in .read_into.
rodzic
d2a4fea5bf
commit
e54fed1a75
64
rp2/RP2.md
64
rp2/RP2.md
|
@ -116,9 +116,9 @@ Tests are run by issuing (e.g.):
|
|||
# 2.1 Introduction
|
||||
|
||||
This uses a PIO state machine (SM) with DMA to enable an RP2 to receive SPI
|
||||
transfers from a host. Reception is non-blocking, enabling code to run while a
|
||||
transfer is in progress. The principal application area is for fast transfer of
|
||||
large messages.
|
||||
transfers from a host. Non blocking reception is offered, enabling code to run
|
||||
while awaiting a message and while a transfer is in progress. The principal
|
||||
application area is for fast transfer of large messages.
|
||||
|
||||
# 2.2 SpiSlave class
|
||||
|
||||
|
@ -130,9 +130,11 @@ high, terminating the transfer.
|
|||
|
||||
This takes the following positional args:
|
||||
* `buf=None` Optional `bytearray` for incoming data. This is required if using
|
||||
the asynchronous iterator interface, otherwise it is unused.
|
||||
* `callback=None` Optional callback for use with the synchronous API. It takes
|
||||
a single arg, being the number of bytes received.
|
||||
the asynchronous iterator interface or the blocking `.read()` method, otherwise
|
||||
it is unused.
|
||||
* `callback=None` Callback for use with the nonblocking synchronous API. It takes
|
||||
a single arg, being the number of bytes received. It runs as a soft interrupt
|
||||
service routine (ISR). The callback will only run in response to `.read_into()`.
|
||||
* `sm_num=0` State machine number.
|
||||
|
||||
Keyword args: Pin instances, initialised as input. GPIO nos. must be consecutive
|
||||
|
@ -143,13 +145,51 @@ starting from `mosi`.
|
|||
|
||||
# 2.4 Synchronous Interface
|
||||
|
||||
This is via `SpiSlave.read_into(buffer)` where `buffer` is a user supplied
|
||||
`bytearray`. This must be large enough for the expected message. The method
|
||||
returns immediately. When a message arrives and reception is complete, the
|
||||
callback runs. Its integer arg is the number of bytes received.
|
||||
Methods:
|
||||
* `read()` Blocking read. Blocks until a message is received. Returns a
|
||||
`memoryview` into the buffer passed to the constructor. This slice contains the
|
||||
message. If a message is too long to fit the buffer excess bytes are lost.
|
||||
* `read_into(buffer)` Nonblocking read into the passed buffer. This must be
|
||||
large enough for the expected message. The method returns immediately. When a
|
||||
message arrives and reception is complete, the callback runs. Its integer arg is
|
||||
the number of bytes received. If a message is too long to fit the buffer, excess
|
||||
bytes are lost.
|
||||
|
||||
If a message is too long to fit the buffer, when the buffer fills, subsequent
|
||||
characters are lost.
|
||||
The nonblocking `.read_into()` method enables processing to be done while
|
||||
awaiting a complete message. The drawback (compared to `.read()`) is that
|
||||
synchronisation is required. This is to ensure that, when a message is received,
|
||||
the slave is set up to receive the next. The following illustrates this:
|
||||
```python
|
||||
from machine import Pin
|
||||
from .spi_slave import SpiSlave
|
||||
|
||||
nbytes = 0 # Synchronisation
|
||||
# Callback runs when transfer complete (soft ISR context)
|
||||
def receive(num_bytes):
|
||||
global nbytes
|
||||
nbytes = num_bytes
|
||||
|
||||
mosi = Pin(0, Pin.IN) # Consecutive GPIO nos.
|
||||
sck = Pin(1, Pin.IN)
|
||||
csn = Pin(2, Pin.IN)
|
||||
piospi = SpiSlave(callback=receive, sm_num=4, mosi=mosi, sck=sck, csn=csn)
|
||||
|
||||
|
||||
def test():
|
||||
global nbytes
|
||||
buf = bytearray(300) # Read buffer
|
||||
while True:
|
||||
nbytes = 0
|
||||
piospi.read_into(buf) # Initiate a read
|
||||
while nbytes == 0: # Wait for message arrival
|
||||
pass # Can do something useful while waiting
|
||||
print(bytes(buf[:nbytes])) # Message received. Process it.
|
||||
|
||||
test()
|
||||
```
|
||||
Note that if the master sends a message before the slave has finished processing
|
||||
its predecessor, data will be lost. This illustration is inefficient: allocation
|
||||
free slicing could be achieved via a `memoryview` of `buf`.
|
||||
|
||||
# 2.5 Asynchronous Interface
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
import asyncio
|
||||
from machine import Pin, reset
|
||||
from .spi_slave import SpiSlave
|
||||
from time import sleep_ms
|
||||
|
||||
mosi = Pin(0, Pin.IN)
|
||||
sck = Pin(1, Pin.IN)
|
||||
csn = Pin(2, Pin.IN)
|
||||
|
||||
async def main():
|
||||
piospi = SpiSlave(buf=bytearray(300), sm_num=0, mosi=mosi, sck=sck, csn=csn)
|
||||
async for msg in piospi:
|
||||
print(f"Received: {len(msg)} bytes:")
|
||||
print(bytes(msg))
|
||||
print()
|
||||
|
||||
try:
|
||||
asyncio.run(main())
|
||||
finally:
|
||||
reset()
|
|
@ -0,0 +1,35 @@
|
|||
from machine import Pin, reset
|
||||
from .spi_slave import SpiSlave
|
||||
from time import sleep_ms
|
||||
|
||||
mosi = Pin(0, Pin.IN)
|
||||
sck = Pin(1, Pin.IN)
|
||||
csn = Pin(2, Pin.IN)
|
||||
|
||||
nbytes = 0
|
||||
|
||||
buf = bytearray(300) # Read buffer
|
||||
|
||||
|
||||
def receive(num_bytes):
|
||||
global nbytes
|
||||
nbytes = num_bytes
|
||||
|
||||
|
||||
piospi = SpiSlave(callback=receive, sm_num=4, mosi=mosi, sck=sck, csn=csn)
|
||||
|
||||
|
||||
def test():
|
||||
global nbytes
|
||||
while True:
|
||||
nbytes = 0
|
||||
piospi.read_into(buf) # Initiate a read
|
||||
while nbytes == 0: # Wait for message arrival
|
||||
pass # Can do something useful while waiting
|
||||
print(bytes(buf[:nbytes])) # Message received. Process it.
|
||||
|
||||
|
||||
try:
|
||||
test()
|
||||
finally:
|
||||
reset()
|
|
@ -52,6 +52,7 @@ class SpiSlave:
|
|||
return 4 + d if rx else d
|
||||
|
||||
self._callback = callback
|
||||
self._docb = False # By default CB des not run
|
||||
# Set up DMA
|
||||
self._dma = rp2.DMA()
|
||||
# Transfer bytes, rather than words, don't increment the read address and pace the transfer.
|
||||
|
@ -62,6 +63,7 @@ class SpiSlave:
|
|||
if buf is not None:
|
||||
self._mvb = memoryview(buf)
|
||||
self._tsf = asyncio.ThreadSafeFlag()
|
||||
self._read_done = False # Synchronisation for .read()
|
||||
csn.irq(self._done, trigger=Pin.IRQ_RISING, hard=True) # IRQ occurs at end of transfer
|
||||
self._sm = rp2.StateMachine(
|
||||
sm_num,
|
||||
|
@ -76,15 +78,32 @@ class SpiSlave:
|
|||
return self
|
||||
|
||||
async def __anext__(self):
|
||||
if self._buf is None:
|
||||
raise OSError("No buffer provided to constructor.")
|
||||
|
||||
self.read_into(self._buf) # Initiate DMA and return.
|
||||
self._bufcheck() # Ensure there is a valid buffer
|
||||
self._rinto(self._buf) # Initiate DMA and return.
|
||||
await self._tsf.wait() # Wait for CS/ high (master signals transfer complete)
|
||||
return self._mvb[: self._nbytes]
|
||||
|
||||
# Initiate a read into a buffer. Immediate return.
|
||||
def _bufcheck(self):
|
||||
if self._buf is None:
|
||||
raise OSError("No buffer provided to constructor.")
|
||||
|
||||
def read(self): # Blocking read, own buffer
|
||||
self._bufcheck()
|
||||
self._read_done = False
|
||||
self._rinto(self._buf)
|
||||
while not self._read_done:
|
||||
pass
|
||||
return self._buf[: self._nbytes]
|
||||
|
||||
# Initiate a nonblocking read into a buffer. Immediate return.
|
||||
def read_into(self, buf):
|
||||
if self._callback is not None:
|
||||
self._docb = True
|
||||
self._rinto(buf)
|
||||
else:
|
||||
raise ValueError("Missing callback function.")
|
||||
|
||||
def _rinto(self, buf): # .read_into() without callback
|
||||
buflen = len(buf)
|
||||
self._buflen = buflen # Save for ISR
|
||||
self._dma.active(0) # De-activate befor re-configuring
|
||||
|
@ -100,18 +119,21 @@ class SpiSlave:
|
|||
def _done(self, _): # Get no. of bytes received.
|
||||
self._dma.active(0)
|
||||
self._sm.put(0) # Request no. of received bits
|
||||
if not self._sm.rx_fifo(): # Occurs if .read_into() never called while CSN is low:
|
||||
if not self._sm.rx_fifo(): # Occurs if ._rinto() never called while CSN is low:
|
||||
# print("GH")
|
||||
return # ISR runs on trailing edge but SM is not running. Nothing to do.
|
||||
sp = self._sm.get() >> 3 # Bits->bytes: space left in buffer or 7ffffff on overflow
|
||||
self._nbytes = self._buflen - sp if sp != 0x7FFFFFF else self._buflen
|
||||
self._dma.active(0)
|
||||
self._sm.active(0)
|
||||
self._tsf.set()
|
||||
if self._callback is not None:
|
||||
self._read_done = True
|
||||
if self._docb: # Only run CB if user has called .read_into()
|
||||
self._docb = False
|
||||
schedule(self._callback, self._nbytes) # Soft ISR
|
||||
|
||||
# Await a read into a user-supplied buffer. Return no. of bytes read.
|
||||
async def as_read_into(self, buf):
|
||||
self.read_into(buf) # Start the read
|
||||
self._rinto(buf) # Start the read
|
||||
await self._tsf.wait() # Wait for CS/ high (master signals transfer complete)
|
||||
return self._nbytes
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
from machine import Pin, SPI
|
||||
from time import sleep_ms
|
||||
|
||||
|
||||
cs = Pin(17, Pin.OUT, value=1) # Ensure CS/ is False before we try to receive.
|
||||
pin_miso = Pin(16, Pin.IN) # Not used: keep driver happy
|
||||
pin_sck = Pin(18, Pin.OUT, value=0)
|
||||
pin_mosi = Pin(19, Pin.OUT, value=0)
|
||||
|
||||
spi = SPI(0, baudrate=10_000_000, sck=pin_sck, mosi=pin_mosi, miso=pin_miso)
|
||||
|
||||
|
||||
def send(obuf):
|
||||
cs(0)
|
||||
spi.write(obuf)
|
||||
# sleep_ms(10)
|
||||
cs(1)
|
||||
# print("sent", obuf)
|
||||
sleep_ms(1000)
|
||||
|
||||
|
||||
while True:
|
||||
send("The quick brown fox")
|
||||
send("jumps over the lazy dog.")
|
Ładowanie…
Reference in New Issue