2025-07-24 16:09:18 +00:00
|
|
|
# Code samples for RP2040 and RP2350 (Pico and Pico 2)
|
|
|
|
|
|
|
|
These are intended to demonstrate the use of Pico-specific hardware.
|
|
|
|
|
2025-07-24 16:24:36 +00:00
|
|
|
1. [Nonblocking SPI master](./RP2.md#1-nonblocking-spi-master) High speed bulk data output.
|
2025-07-25 08:11:09 +00:00
|
|
|
1.1 [Class SpiMaster](./RP2.md#11-class-spimaster)
|
2025-07-24 16:21:39 +00:00
|
|
|
1.2 [Constructor](./RP2.md#12-constructor)
|
|
|
|
1.3 [Methods](./RP2.md#13-methods)
|
|
|
|
1.4 [CS](./RP2.md#14-cs) How to control the CS/ pin.
|
2025-08-30 13:27:35 +00:00
|
|
|
1.5 [Callback](./RP2.md#15-callback) Notification of transfer complete.
|
2025-07-24 16:21:39 +00:00
|
|
|
2. [Nonblocking SPI slave](./RP2.md#2-nonblocking-spi-slave) High speed bulk data input.
|
|
|
|
2.1 [Introduction](./RP2.md#21-introduction)
|
|
|
|
2.2 [SpiSlave class](./RP2.md#22-spislave-class)
|
|
|
|
2.3 [Constructor](./RP2.md#23-constructor)
|
|
|
|
2.4 [Synchronous Interface](./RP2.md#24-synchronous-interface)
|
|
|
|
2.5 [Asynchronous Interface](./RP2.md#25-asynchronous-interface)
|
2025-07-24 16:24:36 +00:00
|
|
|
2.6 [operation](./RP2.md#26-operation)
|
2025-07-24 16:21:39 +00:00
|
|
|
3. [Pulse Measurement](./RP2.md#3-pulse-Measurement) Measure incoming pulses.
|
|
|
|
4. [Pulse train output](./RP2.md#4-pulse-train-output) Output arbitrary pulse trains as per ESP32 RMT.
|
|
|
|
4.1 [The RP2_RMT class](./RP2.md#41-the-rp2_rmt-class)
|
|
|
|
4.2 [Constructor](./RP2.md#42-constructor)
|
|
|
|
4.3 [Methods](./RP2.md#43-methods)
|
|
|
|
4.3.1 [send](./RP2.md#431-send)
|
|
|
|
4.3.2 [busy](./RP2.md#432-busy)
|
|
|
|
4.4 [Design](./RP2.md#44-design)
|
|
|
|
4.5 [Limitations](./RP2.md#45-limitations)
|
2025-07-24 16:09:18 +00:00
|
|
|
|
2025-07-25 08:11:09 +00:00
|
|
|
To install the demos issue:
|
|
|
|
```bash
|
|
|
|
$ mpremote mip install "github:peterhinch/micropython-samples/rp2"
|
|
|
|
```
|
|
|
|
They will be installed in directories:
|
|
|
|
* `spi` SPI modules and demos.
|
|
|
|
* `measure_pulse` Pulse measurement.
|
|
|
|
* `rmt` Pulse train output.
|
|
|
|
|
2025-07-24 16:09:18 +00:00
|
|
|
# 1. Nonblocking SPI master
|
|
|
|
|
2025-07-25 08:11:09 +00:00
|
|
|
The module `spi_master` provides a class `SpiMaster` which uses DMA to perform
|
2025-08-30 13:27:35 +00:00
|
|
|
fast SPI I/O. A typical use case is to transfer the contents of a frame buffer
|
|
|
|
to a display as a background task. The following files are provided in the `spi`
|
|
|
|
directory:
|
2025-07-24 16:09:18 +00:00
|
|
|
* `spi_master.py` The main module.
|
2025-08-30 13:27:35 +00:00
|
|
|
* `master_test.py` Test script - uses a loopback (MOSI linked to MISO) to verify
|
|
|
|
the master.
|
2025-07-24 16:09:18 +00:00
|
|
|
* `master_slave_test.py` Full test of master linked to slave, with the latter
|
|
|
|
printing results.
|
|
|
|
|
|
|
|
This module supports the most common SPI case, being the `machine.SPI` default:
|
|
|
|
polarity=0, phase=0, bits=8, firstbit=SPI.MSB. Benefits over the official
|
|
|
|
`machine.SPI` are the nonblocking write and the fact that baudrates are precise;
|
|
|
|
those on the official class are highly quantised, with (for example) 20MHz
|
|
|
|
manifesting as 12MHz. The module has been tested to 30MHz, but higher rates
|
|
|
|
should be possible.
|
|
|
|
|
2025-08-30 13:27:35 +00:00
|
|
|
To run the tests, the following pins should be linked:
|
|
|
|
* 0-19 MOSI (master-slave links).
|
2025-07-24 16:09:18 +00:00
|
|
|
* 1-18 SCK
|
|
|
|
* 2-17 CSN
|
2025-08-30 13:27:35 +00:00
|
|
|
* 16-19 MOSI-MISO (only needed for master_test.py).
|
2025-07-24 16:09:18 +00:00
|
|
|
|
|
|
|
To run a test issue (e.g.):
|
|
|
|
```py
|
|
|
|
>>> import spi.master_slave_test
|
|
|
|
```
|
|
|
|
|
2025-07-25 08:11:09 +00:00
|
|
|
## 1.1 Class SpiMaster
|
2025-07-24 16:09:18 +00:00
|
|
|
|
|
|
|
## 1.2 Constructor
|
|
|
|
|
|
|
|
This takes the following positional args:
|
|
|
|
* `sm_num` State machine no. (0..7 on RP2040, 0..11 on RP2350)
|
|
|
|
* `freq` SPI clock frequency in Hz.
|
2025-08-30 13:27:35 +00:00
|
|
|
* `sck` An output `Pin` instance for `sck`. Pins are arbitrary.
|
|
|
|
* `mosi` An output `Pin` instance for `mosi`.
|
|
|
|
* `callback` A callback to run when DMA is complete. See [below](./RP2.md#15-callback).
|
|
|
|
|
|
|
|
Optional keyword args, for the case where data coming from the slave must be
|
|
|
|
acquired (MISO):
|
|
|
|
* `miso` A `Pin` instance for MISO (defined as input).
|
|
|
|
* `ibuf` A `bytearray` for MISO data. If the quantity of data exceeds the length
|
|
|
|
of the buffer it will be truncated.
|
2025-07-24 16:09:18 +00:00
|
|
|
|
|
|
|
## 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`.
|
|
|
|
* `deinit()` Disables the DMA and the SM. Returns `None`.
|
|
|
|
|
|
|
|
## 1.4 CS/
|
|
|
|
|
|
|
|
The application should assert CS/ (set to 0) prior to transmission and deassert
|
2025-08-26 13:51:42 +00:00
|
|
|
it after transmission is complete. An external pullup resistor to 3.3V should be
|
|
|
|
provided to ensure that the receiving device sees CS/ `False` in the interval
|
|
|
|
between power-up and application start-up. A value of a few KΩ is suggested.
|
2025-07-24 16:09:18 +00:00
|
|
|
|
2025-08-30 13:27:35 +00:00
|
|
|
## 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.
|
|
|
|
|
2025-07-24 16:09:18 +00:00
|
|
|
# 2. Nonblocking SPI slave
|
|
|
|
|
|
|
|
This module requires incoming data to conform to the most common SPI case, being
|
|
|
|
the `machine.SPI` default: polarity=0, phase=0, bits=8, firstbit=SPI.MSB.
|
|
|
|
|
|
|
|
It has been tested at a clock rate of 24MHz on an RP2040 running at 250MHz.
|
|
|
|
|
|
|
|
The following files may be found in the `spi` directory:
|
|
|
|
* `spi_slave.py` Main module.
|
2025-08-20 16:45:24 +00:00
|
|
|
* `tx.py` Transmitter script: provides SPI data for receiver demos below.
|
|
|
|
These demos use two linked boards, one supplying data, the other receiving.
|
|
|
|
* `rx.py` Demo of nonblocking receiver.
|
|
|
|
* `rxb.py` Blocking read demo.
|
|
|
|
* `arx.py` Asynchronous read demo.
|
|
|
|
Demos are run from the `rp2` directory by issuing (e.g.):
|
|
|
|
```python
|
|
|
|
>>> import spi.rx
|
|
|
|
```
|
|
|
|
Tests. These run on a single board and test special cases such as recovery from
|
|
|
|
overruns.
|
2025-07-24 16:09:18 +00:00
|
|
|
* `slave_sync_test` Test using synchronous code.
|
|
|
|
* `slave_async_test` Test using asynchronous code.
|
|
|
|
* `master_slave_test.py` Full test of master linked to slave, with the latter
|
|
|
|
printing results.
|
|
|
|
|
|
|
|
Tests operate by using the official SPI library to transmit with the module
|
|
|
|
receiving (`master_slave_test` uses the nonblocking master). To run the tests
|
|
|
|
the following pins should be linked:
|
|
|
|
* 0-19 MOSI
|
|
|
|
* 1-18 SCK
|
|
|
|
* 2-17 CSN
|
|
|
|
|
2025-08-20 16:45:24 +00:00
|
|
|
Tests are run from the `rp2` directory by issuing (e.g.):
|
2025-07-24 16:09:18 +00:00
|
|
|
```py
|
|
|
|
>>> import spi.master_slave_test
|
|
|
|
```
|
|
|
|
|
|
|
|
# 2.1 Introduction
|
|
|
|
|
|
|
|
This uses a PIO state machine (SM) with DMA to enable an RP2 to receive SPI
|
2025-08-19 14:27:16 +00:00
|
|
|
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.
|
2025-07-24 16:09:18 +00:00
|
|
|
|
|
|
|
# 2.2 SpiSlave class
|
|
|
|
|
|
|
|
The class presents synchronous and asynchronous APIs. In use the class is set
|
|
|
|
up to read data. The master sets `CS/` low, transmits data, then sets `CS/`
|
|
|
|
high, terminating the transfer.
|
|
|
|
|
|
|
|
## 2.3 Constructor
|
|
|
|
|
|
|
|
This takes the following positional args:
|
|
|
|
* `buf=None` Optional `bytearray` for incoming data. This is required if using
|
2025-08-19 14:27:16 +00:00
|
|
|
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()`.
|
2025-07-24 16:09:18 +00:00
|
|
|
* `sm_num=0` State machine number.
|
|
|
|
|
|
|
|
Keyword args: Pin instances, initialised as input. GPIO nos. must be consecutive
|
|
|
|
starting from `mosi`.
|
|
|
|
* `mosi` Pin for MOSI.
|
|
|
|
* `sck` SCK Pin.
|
|
|
|
* `csn` CSN Pin.
|
|
|
|
|
|
|
|
# 2.4 Synchronous Interface
|
|
|
|
|
2025-08-19 14:27:16 +00:00
|
|
|
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.
|
2025-08-20 16:45:24 +00:00
|
|
|
* `deinit()` Close the interface. This should be done on exit to avoid hanging
|
|
|
|
at the REPL.
|
2025-08-19 14:27:16 +00:00
|
|
|
|
|
|
|
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`.
|
2025-07-24 16:09:18 +00:00
|
|
|
|
|
|
|
# 2.5 Asynchronous Interface
|
|
|
|
|
|
|
|
There are two ways to use this. The simplest uses a single buffer passed to the
|
|
|
|
constructor. This should be sized to accommodate the longest expected message.
|
|
|
|
Reception is via an asynchronous iterator:
|
|
|
|
```py
|
|
|
|
async def receive(piospi): # Arg is an SpiSlave instantiated with a buffer
|
|
|
|
async for msg in piospi: # msg is a memoryview of the buffer
|
|
|
|
print(bytes(msg))
|
|
|
|
```
|
|
|
|
This prints incoming messages as they arrive.
|
|
|
|
|
|
|
|
An alternative approach enables the use of multiple buffers (for example two
|
|
|
|
phase "ping pong" buffering). Reception is via an asynchronous method
|
|
|
|
`SpiSlave.as_read_into(buffer)`:
|
|
|
|
```py
|
|
|
|
rbuf = bytearray(200)
|
|
|
|
nbytes = await piospi.as_read_into(rbuf) # Wait for a message, get no. of received bytes
|
|
|
|
print(bytes(rbuf[:nbytes]))
|
|
|
|
```
|
|
|
|
As with all asynchronous code, this task pauses while others continue to run.
|
|
|
|
|
2025-08-20 16:45:24 +00:00
|
|
|
On application exit `SpiSlave.deinit()` should be called to avoid hanging at the
|
|
|
|
REPL.
|
|
|
|
|
2025-07-24 16:09:18 +00:00
|
|
|
# 2.6 Operation
|
|
|
|
|
|
|
|
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/.
|
|
|
|
|
|
|
|
# 3. Pulse Measurement
|
|
|
|
|
|
|
|
The file `measure_pulse.py` is a simple demo of using the PIO to measure a pulse
|
|
|
|
waveform. As written it runs a PWM to provide a signal. To run the demo link
|
|
|
|
pins GPIO16 and GPIO17. It measures period/frequency and mark (hence space and
|
|
|
|
mark/space can readily be derived).
|
|
|
|
|
|
|
|
As written the native clock rate is used (125MHz on Pico).
|
|
|
|
|
|
|
|
# 4. Pulse train output
|
|
|
|
|
|
|
|
The `RP2_RMT` class provides functionality similar to that of the ESP32 `RMT`
|
|
|
|
class. It enables pulse trains to be output using a non-blocking driver. By
|
|
|
|
default the train occurs once. Alternatively it can repeat a defined number of
|
|
|
|
times, or can be repeated continuously.
|
|
|
|
|
|
|
|
The class was designed for my [IR blaster](https://github.com/peterhinch/micropython_ir)
|
|
|
|
and [433MHz remote](https://github.com/peterhinch/micropython_remote)
|
|
|
|
libraries. It supports an optional carrier frequency, where each high pulse can
|
|
|
|
appear as a burst of a defined carrier frequency. The class can support both
|
|
|
|
forms concurrently on a pair of pins: one pin produces pulses while a second
|
|
|
|
produces carrier bursts.
|
|
|
|
|
|
|
|
Pulse trains are specified as arrays with each element being a duration in μs.
|
|
|
|
Arrays may be of integers or half-words depending on the range of times to be
|
|
|
|
covered. The duration of a "tick" is 1μs by default, but this can be changed.
|
|
|
|
|
|
|
|
To run the test issue:
|
|
|
|
```py
|
|
|
|
>>> import rmt.rp2_rmt_test
|
|
|
|
```
|
|
|
|
Output is on pins 16 and/or 17: see below and test source.
|
|
|
|
|
|
|
|
# 4.1 The RP2_RMT class
|
|
|
|
|
|
|
|
## 4.2 Constructor
|
|
|
|
|
|
|
|
This takes the following args:
|
|
|
|
1. `pin_pulse=None` If an output `Pin` instance is provided, pulses will be
|
|
|
|
output on it.
|
|
|
|
2. `carrier=None` To output a carrier, a 3-tuple should be provided comprising
|
|
|
|
`(pin, freq, duty)` where `pin` is an output pin instance, `freq` is the
|
|
|
|
carrier frequency in Hz and `duty` is the duty ratio in %.
|
|
|
|
3. `sm_no=0` State machine no.
|
|
|
|
4. `sm_freq=1_000_000` Clock frequency for SM. Defines the unit for pulse
|
|
|
|
durations.
|
|
|
|
|
|
|
|
## 4.3 Methods
|
|
|
|
|
|
|
|
### 4.3.1 send
|
|
|
|
|
|
|
|
This returns "immediately" with a pulse train being emitted as a background
|
|
|
|
process. Args:
|
|
|
|
1. `ar` A zero terminated array of pulse durations in μs. See notes below.
|
|
|
|
2. `reps=1` No. of repetions. 0 indicates continuous output.
|
|
|
|
3. `check=True` By default ensures that the pulse train ends in the inactive
|
|
|
|
state.
|
|
|
|
|
|
|
|
In normal operation, between pulse trains, the pulse pin is low and the carrier
|
|
|
|
is off. A pulse train ends when a 0 pulse width is encountered: this allows
|
|
|
|
pulse trains to be shorter than the array length, for example where a
|
|
|
|
pre-allocated array stores pulse trains of varying lengths. In RF transmitter
|
|
|
|
applications ensuring the carrier is off between pulse trains may be a legal
|
|
|
|
requirement, so by default the `send` method enforces this.
|
|
|
|
|
|
|
|
The first element of the array defines the duration of the first high going
|
|
|
|
pulse, with the second being the duration of the first `off` period. If there
|
|
|
|
are an even number of elements prior to the terminating 0, the signal will end
|
|
|
|
in the `off` state. If the `check` arg is `True`, `.send()` will check for an
|
|
|
|
odd number of elements; in this case it will overwrite the last element with 0
|
|
|
|
to enforce a final `off` state.
|
|
|
|
|
|
|
|
This check may be skipped by setting `check=False`. This provides a means of
|
|
|
|
inverting the normal sense of the driver: if the first pulse train has an odd
|
|
|
|
number of elements and `check=False` the pin will be left high (and the carrier
|
|
|
|
on). Subsequent normal pulsetrains will start and end in the high state.
|
|
|
|
|
|
|
|
### 4.3.2 busy
|
|
|
|
|
|
|
|
No args. Returns `True` if a pulse train is being emitted.
|
|
|
|
|
|
|
|
### 4.3.3 cancel
|
|
|
|
|
|
|
|
No args. If a pulse train is being emitted it will continue to the end but no
|
|
|
|
further repetitions will take place.
|
|
|
|
|
|
|
|
# 4.4 Design
|
|
|
|
|
|
|
|
The class constructor installs one of two PIO scripts depending on whether a
|
|
|
|
`pin_pulse` is specified. If it is, the `pulsetrain` script is loaded which
|
|
|
|
drives the pin directly from the PIO. If no `pin_pulse` is required, the
|
|
|
|
`irqtrain` script is loaded. Both scripts cause an IRQ to be raised at times
|
|
|
|
when a pulse would start or end.
|
|
|
|
|
|
|
|
The `send` method loads the transmit FIFO with initial pulse durations and
|
|
|
|
starts the state machine. The `._cb` ISR keeps the FIFO loaded with data until
|
|
|
|
a 0 entry is encountered. It also turns the carrier on and off (using a PWM
|
|
|
|
instance). This means that there is some latency between the pulse and the
|
|
|
|
carrier. However latencies at start and end are effectively identical, so the
|
|
|
|
duration of a carrier burst is correct.
|
|
|
|
|
|
|
|
# 4.5 Limitations
|
|
|
|
|
|
|
|
While the tick interval can be reduced to provide timing precision better than
|
|
|
|
1μs, the design of this class will not support very high pulse repetition
|
|
|
|
frequencies. This is because each pulse causes an interrupt: MicroPython is
|
|
|
|
unable to support high IRQ rates.
|
|
|
|
[This library](https://github.com/robert-hh/RP2040-Examples/tree/master/pulses)
|
|
|
|
is more capable in this regard.
|