RP2: Bidirectional SPI release.

master
Peter Hinch 2025-09-14 12:36:19 +01:00
rodzic c5dc32b9a2
commit f22acb644b
3 zmienionych plików z 38 dodań i 23 usunięć

Wyświetl plik

@ -81,10 +81,19 @@ acquired (MISO):
* `ibuf` A `bytearray` for MISO data. If the quantity of data exceeds the length
of the buffer it will be truncated.
Using MISO affects the maximum achievable baudrate. This is because the incoming
signal is delayed by two system clock periods by the input synchronisers (RP2040
manual 3.5.6.3), plus up to one additional system clock period because the slave
and master are mutually asynchronous. The latency becomes even worse when using
the asynchronous slave because the slave's `sck` is further delayed relative to
the master's by the slave's input synchronisers. In testing even 10MHz was too
high a rate for reliable reception.
## 1.3 Methods
* `write(data : bytes)` arg a `bytes` or `bytearray` of data to transmit. Return
is rapid with transmission running in the background. Returns `None`.
is rapid, returns `None`. The data is queued for transmission. The oldest object
is transmitted when the master next initiates a transfer.
* `deinit()` Disables the DMA and the SM. Returns `None`.
## 1.4 CS/
@ -97,12 +106,14 @@ between power-up and application start-up. A value of a few KΩ is suggested.
## 1.5 Callback
This runs when the DMA is complete. It takes no args and runs in a hard IRQ
context. Typical use is to set a `ThreadSafeFlag`, allowing a pending task to
resume. Typically this will deassert `CS/` and initiate processing of received
data. Note that the DMA completes before transmission ends due to bytes stored
in the SM FIFO. This is unlikely to have practical consequences because of
MicroPython latency: the master executes several MP instructions before the
callback runs, and the response to a `ThreadSafeFlag` typically takes >200μs.
context. It should deassert `CS/`. Typical use is to set a `ThreadSafeFlag`,
allowing a pending task to resume, to initiate processing of received data.
Users of low baudrates (<1MHz) should note that the DMA completes before
transmission ends due to three bytes stored in the SM FIFO. This is unlikely to
have practical consequences because of MicroPython latency: where the callback
deasserts `CS/` the time between the last edge of `clk` and the trailing edge
of `CS/` was 93μs (RP2040 @125MHz).
# 2. Nonblocking SPI slave
@ -252,6 +263,11 @@ The slave will ignore all interface activity until CS/ is driven low. It then
receives data with the end of message identified by a low to high transition on
CS/.
The input pins are very sensitive to electrical noise: wiring to the master must
be kept very short. This is because of the very high speed of the RP2 internal
logic (clock rates >=125MHz). Pulses of a few ns duration can cause the state
machine to respond, or can cause an IRQ to be raised.
# 3. Pulse Measurement
The file `measure_pulse.py` is a simple demo of using the PIO to measure a pulse

Wyświetl plik

@ -3,15 +3,12 @@
# Released under the MIT License (MIT). See LICENSE.
# Copyright (c) 2025 Peter Hinch
# Performs SPI output: check on scope or LA.
# TODO:
# Fails if baudrate >= 9MHz
# Performs SPI I/O.
# Bidirectional test fails if baudrate >= 9MHz: see RP2.md for reason.
# Problem with pin nos?
# 11 Sept slave works bidirectionally with tx.py, but with this code slave receives
# nothing, but responds with correct data. Waveforms look identical. MOSI data looks correct.
# Slave gets correct data with unidirectional setup (no ibuf)
# 45us after last clock edge, clk emits a 250ns pulse. CS/ goes high 724us after last clock
# I think spurious clk is screwing RX SM.
from machine import Pin
import asyncio
from .spi_master import SpiMaster
@ -25,12 +22,15 @@ tsf = asyncio.ThreadSafeFlag()
def callback(): # Hard ISR
pin_cs(1) # Decrease deassert time from 724us to 93us but still fails
pin_cs(1) # Decrease deassert time from 724us to 93us
tsf.set() # Flag user code that transfer is complete
buf = bytearray(100)
# Bidirectional test:
spi = SpiMaster(6, 1_000_000, pin_sck, pin_mosi, callback, miso=pin_miso, ibuf=buf)
# For unidirectional test issue:
# spi = SpiMaster(6, 1_000_000, pin_sck, pin_mosi, callback))
async def send(data):
@ -42,7 +42,7 @@ async def send(data):
async def main():
src_data = bytearray(b"\xFF\x55\xAA\x00the quick brown fox jumps over the lazy dog")
src_data = bytearray(b"\x00 The quick brown fox jumps over the lazy dog")
n = 0
while True:
await send(src_data)

Wyświetl plik

@ -16,7 +16,7 @@ alloc_emergency_exception_buf(100)
# 2 CS\
@rp2.asm_pio(autopull=True)
@rp2.asm_pio(autopush=True, autopull=True)
def spi_in():
label("escape") # Just started, transfer ended or overrun attempt.
out(y, 32) # Get maximum byte count (blocking wait)
@ -37,14 +37,13 @@ def spi_in():
jmp("overrun")
label("continue")
jmp(x_dec, "bit") # Post decrement
push()
# push()
wrap() # Next byte
label("done") # ISR has sent data
out(x, 32) # Discard it
in_(y, 30) # Return amount of unfilled buffer truncated to 30 bits
push()
# TODO Is this valid given that push_thresh==8?
# Truncation ensures that overrun returns a short int
# push()
jmp("escape")
@ -95,7 +94,7 @@ class SpiSlave:
sm_num,
spi_in,
in_shiftdir=rp2.PIO.SHIFT_LEFT,
# push_thresh=8,
push_thresh=8,
in_base=mosi,
jmp_pin=sck,
)
@ -187,7 +186,7 @@ class SpiSlave:
return # ISR runs on trailing edge but SM is not running. Nothing to do.
# See above comment re memfails on next line
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._nbytes = self._buflen - sp if sp != 0x07FF_FFFF else self._buflen
self._dma.active(0)
self._sm.active(0)
self._tsf.set()