Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

RP2350 SPI Peripherals

The RP2350 includes two SPI controllers, named SPI0 and SPI1. Both are general purpose peripherals and are available for application use. These controllers provide flexible serial communication capabilities for connecting external devices like sensors, displays, SD cards, and other SPI peripherals.

Each SPI peripheral supports standard SPI operation, including master and slave modes, full-duplex transfers, and the usual SPI signals: SCLK, MOSI, MISO, and chip select.

You can refer to the 1046th page on the RP2350 datasheet for more detailed and accurate information: https://pip-assets.raspberrypi.com/categories/1214-rp2350/documents/RP-008373-DS-2-rp2350-datasheet.pdf?disposition=inline#page=1047

GPIO Pins

The Raspberry Pi Pico 2 exposes two SPI buses on its GPIO header: SPI0 and SPI1. Each SPI bus uses four signals: clock (SCK), transmit (MOSI), receive (MISO), and chip select (CS).

Raspberry Pi Pico 2 Pinout Diagram

In the above diagram, the SPI pins are highlighted in pink.

SPI0 pin options

MISO (RX)CSSCKMOSI (TX)
GPIO 0GPIO 1GPIO 2GPIO 3
GPIO 4GPIO 5GPIO 6GPIO 7
GPIO 16GPIO 17GPIO 18GPIO 19

SPI1 pin options

MISO (RX)CSSCKMOSI (TX)
GPIO 8GPIO 9GPIO 10GPIO 11
GPIO 12GPIO 13GPIO 14GPIO 15

Using SPI in embassy

Embassy provides two ways to use SPI. One is async, where transfers can be awaited without blocking the executor. The other is blocking, which behaves more like traditional HALs and is often simpler for small examples or one-off transfers.

Async mode

In async mode, SPI uses DMA under the hood. You must provide DMA channels, and all transfers are performed using .await. This is useful when SPI transfers are part of a larger async application and you do not want to block other tasks.

#![allow(unused)]
fn main() {
let miso = p.PIN_12;
let mosi = p.PIN_11;
let clk = p.PIN_10;

let mut spi_bus = Spi::new(p.SPI1, clk, mosi, miso, p.DMA_CH0, p.DMA_CH1, spi::Config::default());

let tx_buf = [1_u8, 2, 3, 4, 5, 6];
let mut rx_buf = [0_u8; 6];
spi_bus.transfer(&mut rx_buf, &tx_buf).await.unwrap();
info!("{:?}", rx_buf);
}

Here, data is written from tx_buf while data received from the peripheral is stored in rx_buf. The call to transfer only returns once the SPI transaction is complete.

Blocking mode

Blocking SPI is simpler, calls return only after the transfer is finished, and no DMA or async context is required.

#![allow(unused)]
fn main() {
let miso = p.PIN_12;
let mosi = p.PIN_11;
let clk = p.PIN_10;

let mut config = spi::Config::default();
let mut spi_bus = Spi::new_blocking(p.SPI1, clk, mosi, miso, config);

spi_bus.blocking_transfer_in_place(&mut buf).unwrap();
}

Using SPI in rp-hal

In the rp-hal SPI, you need to first configure the pins into SPI function mode, then the SPI peripheral is created and initialized with the desired clock speed and SPI mode.

#![allow(unused)]
fn main() {
 // These are implicitly used by the spi driver if they are in the correct mode
let spi_mosi = pins.gpio7.into_function::<hal::gpio::FunctionSpi>();
let spi_miso = pins.gpio4.into_function::<hal::gpio::FunctionSpi>();
let spi_sclk = pins.gpio6.into_function::<hal::gpio::FunctionSpi>();
let spi_bus = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk));

// Exchange the uninitialised SPI driver for an initialised one
let mut spi_bus = spi_bus.init(
    &mut pac.RESETS,
    clocks.peripheral_clock.freq(),
    16.MHz(),
    embedded_hal::spi::MODE_0,
);

// Write out 0, ignore return value
if spi_bus.write(&[0]).is_ok() {
    // SPI write was successful
};
}

Example Driver Usage

Usually, you will not talk to SPI directly. Instead, you pass an SPI device to a driver. This usually means creating an SPI bus first, then wrapping it using ExclusiveDevice provided by the embedded-hal-bus crate.

#![allow(unused)]
fn main() {
let clk = p.PIN_14;
let mosi = p.PIN_15;

let spi_bus = Spi::new_blocking_txonly(p.SPI1, clk, mosi, SpiConfig::default());
let spi_dev = ExclusiveDevice::new_no_delay(spi_bus, cs_pin).expect("Failed to get exclusive device");

// Create a display instance for a single 8x8 LED matrix (not daisy-chained)
let mut display = SingleMatrix::from_spi(spi_dev).expect("display count 1 should not panic");
}