Skip to content

Commit

Permalink
Demonstrate basic LPSPI device support
Browse files Browse the repository at this point in the history
Use `set_device_enable` to transition the LPSPI peripheral from a
controller into a device. You can then use the lower-level LPSPI
interface to coordinate I/O.

The commit includes two examples that you can run on
physically-connected boards. See the example documentation for more
info. I tested the LPSPI device behavior by running the `_device`
example on a 1010EVK and the `_controller` example on a 1170EVK. I'm
eagerly updating the pin configurations for other boards.
  • Loading branch information
mciantyre committed May 27, 2024
1 parent d696af8 commit d443eb1
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 6 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ jobs:
- --examples --features=board/imxrt1010evk,board/lcd1602
- --examples --features=board/imxrt1060evk,board/lcd1602
# SPI examples (might break other examples)
- --example=rtic_spi_blocking --example=rtic_spi --example=async_dma_spi --features=board/teensy4,board/spi
- --example=rtic_spi_blocking --example=rtic_spi --example=async_dma_spi --features=board/imxrt1010evk,board/spi
- --example=rtic_spi_blocking --example=rtic_spi --example=async_dma_spi --features=board/imxrt1060evk,board/spi
- --example=rtic_spi_blocking --example=rtic_spi --example=async_dma_spi --features=board/imxrt1170evk-cm7,board/spi
- --example=rtic_spi_blocking --example=rtic_spi_controller --example=rtic_spi_device --example=rtic_spi --example=async_dma_spi --features=board/teensy4,board/spi
- --example=rtic_spi_blocking --example=rtic_spi_controller --example=rtic_spi_device --example=rtic_spi --example=async_dma_spi --features=board/imxrt1010evk,board/spi
- --example=rtic_spi_blocking --example=rtic_spi_controller --example=rtic_spi_device --example=rtic_spi --example=async_dma_spi --features=board/imxrt1060evk,board/spi
- --example=rtic_spi_blocking --example=rtic_spi_controller --example=rtic_spi_device --example=rtic_spi --example=async_dma_spi --features=board/imxrt1170evk-cm7,board/spi
# The i.MX RT 1170 EVK (CM7) target is WIP. The list below describes the working examples.
- --features=board/imxrt1170evk-cm7,board/lcd1602 --example=hal_led
--example=hal_gpio_input --example=rtic_gpio_input
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Introduce LPSPI improvements:
- Allow users to change the watermark while enabled. Deprecate the corresponding
method on the `Disabled` helper.
- Add low-level clock configurations.
- Add `set_device_enable` to configure the peripheral as a SPI device.

Change how the LPSPI driver manages the FIFOs. As a result of this change, the
driver never returns the `Busy` or `NoData` errors through the embedded-hal
Expand Down
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ opt-level = "z"
lto = "fat"
panic = "abort"
overflow-checks = true
debug = 2

[profile.dev.build-override]
opt-level = 0
Expand Down Expand Up @@ -163,6 +164,14 @@ required-features = ["board/spi"]
name = "rtic_spi_blocking"
required-features = ["board/spi"]

[[example]]
name = "rtic_spi_controller"
required-features = ["board/spi"]

[[example]]
name = "rtic_spi_device"
required-features = ["board/spi"]

[[example]]
name = "rtic_spi"
required-features = ["board/spi"]
Expand Down
12 changes: 12 additions & 0 deletions board/src/imxrt1010evk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ fn configure_pins(
super::Pads {
ref mut gpio,
ref mut gpio_sd,
ref mut gpio_ad,
..
}: &mut super::Pads,
) {
Expand All @@ -302,6 +303,17 @@ fn configure_pins(
// Set the pin muxing for the two test points.
crate::iomuxc::ccm::prepare(&mut gpio_sd.p01);
crate::iomuxc::ccm::prepare(&mut gpio_sd.p02);

const SPI_PIN_CONFIG: iomuxc::Config = iomuxc::Config::zero()
.set_drive_strength(iomuxc::DriveStrength::R0_4)
.set_open_drain(iomuxc::OpenDrain::Disabled)
.set_hysteresis(iomuxc::Hysteresis::Disabled)
.set_pull_keeper(None);

iomuxc::configure(&mut gpio_ad.p04, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_ad.p03, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_ad.p06, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_ad.p05, SPI_PIN_CONFIG);
}

/// Helpers for the clock_out example.
Expand Down
15 changes: 14 additions & 1 deletion board/src/imxrt1060evk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,9 @@ pub(crate) const CLOCK_GATES: &[clock_gate::Locator] = &[
/// set alternates here.
fn configure_pins(
super::Pads {
ref mut gpio_ad_b1, ..
ref mut gpio_ad_b1,
ref mut gpio_sd_b0,
..
}: &mut super::Pads,
) {
use crate::iomuxc;
Expand All @@ -270,6 +272,17 @@ fn configure_pins(
let i2c_sda: &mut I2cSda = &mut gpio_ad_b1.p01;
iomuxc::configure(i2c_scl, I2C_PIN_CONFIG);
iomuxc::configure(i2c_sda, I2C_PIN_CONFIG);

const SPI_PIN_CONFIG: iomuxc::Config = iomuxc::Config::zero()
.set_drive_strength(iomuxc::DriveStrength::R0_4)
.set_open_drain(iomuxc::OpenDrain::Disabled)
.set_hysteresis(iomuxc::Hysteresis::Disabled)
.set_pull_keeper(None);

iomuxc::configure(&mut gpio_sd_b0.p02, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_sd_b0.p03, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_sd_b0.p00, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_sd_b0.p01, SPI_PIN_CONFIG);
}

/// Helpers for the clock_out example.
Expand Down
13 changes: 13 additions & 0 deletions board/src/imxrt1170evk-cm7.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,19 @@ fn configure_pins(iomuxc: &mut super::Pads) {
let clko2: &mut Tp1003 = &mut iomuxc.gpio_emc_b1.p41;
crate::iomuxc::ccm::prepare(clko1);
crate::iomuxc::ccm::prepare(clko2);

// Can't use imxrt-iomuxc configuration APIs for this chip.
// See the -iomuxc issue tracker for more information.
//
// Safety: We have exclusive ownership of the (higher-level)
// IOMUXC instance.
let iomuxc = unsafe { ral::iomuxc::IOMUXC::instance() };

// SPI: High drive strength, slow slew, no pulls, not open drain.
ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_AD_30, DSE: DSE_1_HIGH_DRIVER);
ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_AD_31, DSE: DSE_1_HIGH_DRIVER);
ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_AD_28, DSE: DSE_1_HIGH_DRIVER);
ral::write_reg!(ral::iomuxc, iomuxc, SW_PAD_CTL_PAD_GPIO_AD_29, DSE: DSE_1_HIGH_DRIVER);
}

pub mod interrupt {
Expand Down
12 changes: 12 additions & 0 deletions board/src/teensy4.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ fn configure_pins(
super::Pads {
ref mut gpio_ad_b1,
ref mut gpio_b1,
ref mut gpio_b0,
..
}: &mut super::Pads,
) {
Expand All @@ -251,6 +252,17 @@ fn configure_pins(

let button: &mut ButtonPad = &mut gpio_b1.p01;
iomuxc::configure(button, BUTTON_CONFIG);

const SPI_PIN_CONFIG: iomuxc::Config = iomuxc::Config::zero()
.set_drive_strength(iomuxc::DriveStrength::R0_4)
.set_open_drain(iomuxc::OpenDrain::Disabled)
.set_hysteresis(iomuxc::Hysteresis::Disabled)
.set_pull_keeper(None);

iomuxc::configure(&mut gpio_b0.p02, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_b0.p01, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_b0.p03, SPI_PIN_CONFIG);
iomuxc::configure(&mut gpio_b0.p00, SPI_PIN_CONFIG);
}

#[cfg(target_arch = "arm")]
Expand Down
102 changes: 102 additions & 0 deletions examples/rtic_spi_controller.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! A SPI controller for testing SPI devices.
//!
//! Run this on one of your two development boards. Connect each board's
//! SPI peripherals, and establish a common ground. Run rtic_spi_device.rs
//! on the other development board.
//!
//! The controller sends two operands to the device. The device is expected
//! to add those two operands (with wrapping). The controller expects this
//! response from the device. The controllers transacts I/O as fast as
//! possible. It periodically logs errors.
//!
//! You can monitor this device's defmt output to track the number of
//! protocol errors.

#![no_std]
#![no_main]

#[rtic::app(device = board, peripherals = false)]
mod app {

use core::sync::atomic::{AtomicU32, Ordering};

use hal::lpspi::BitOrder;
use imxrt_hal as hal;

const BIT_ORDER: BitOrder = BitOrder::Msb;

#[local]
struct Local {
spi: board::Spi,
pit: hal::pit::Pit<2>,
}

#[shared]
struct Shared {
errors: AtomicU32,
}

#[init]
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
let (
board::Common {
pit: (_, _, mut pit, _),
..
},
board::Specifics { mut spi, .. },
) = board::new();

pit.set_interrupt_enable(true);
pit.set_load_timer_value(board::PIT_FREQUENCY);
pit.enable();

spi.set_bit_order(BIT_ORDER);

(
Shared {
errors: AtomicU32::new(0),
},
Local { spi, pit },
init::Monotonics(),
)
}

#[idle(shared = [&errors], local = [spi])]
fn idle(cx: idle::Context) -> ! {
use eh02::blocking::spi::*;
let idle::SharedResources { errors, .. } = cx.shared;
let idle::LocalResources { spi, .. } = cx.local;

for fst in (0u8..!0).cycle() {
let snd = fst.wrapping_mul(7).wrapping_sub(13);
spi.write(&[fst]).unwrap();
spi.write(&[snd]).unwrap();

let mut sum = [0xFFu8; 1];
spi.transfer(&mut sum).unwrap();

errors.fetch_add(
u32::from(sum[0] != fst.wrapping_add(snd)),
Ordering::Relaxed,
);
}

unreachable!();
}

#[task(binds = BOARD_PIT, shared = [&errors], local = [pit, count: usize = 0])]
fn report_errors(cx: report_errors::Context) {
let report_errors::LocalResources { pit, count, .. } = cx.local;

while pit.is_elapsed() {
pit.clear_elapsed();
}

*count = count.wrapping_add(1);
defmt::warn!(
"({=usize}) Total errors: {=u32}",
*count,
cx.shared.errors.load(Ordering::Relaxed)
);
}
}
82 changes: 82 additions & 0 deletions examples/rtic_spi_device.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! A SPI device demonstration.
//!
//! Run this on one of your two development boards. Connect each board's
//! SPI peripherals, and establish a common ground. Run
//! rtic_spi_controller.rs on the other development board.
//!
//! To understand the protocol, see the rtic_spi_controller.rs documentation.

#![no_std]
#![no_main]

#[rtic::app(device = board, peripherals = false)]
mod app {

use hal::lpspi::{BitOrder, Direction, Interrupts, Transaction};
use imxrt_hal as hal;

#[local]
struct Local {
spi: board::Spi,
}

#[shared]
struct Shared {}

type Elem = u8;
const FRAME_SIZE: u16 = (core::mem::size_of::<Elem>() * 8) as u16;

const BIT_ORDER: BitOrder = BitOrder::Msb;

#[init]
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) {
let (_, board::Specifics { mut spi, .. }) = board::new();

spi.disabled(|spi| spi.set_device_enable(true));

// Expect Elem bits per transaction.
//
// Don't transmit anything during this transaction!
// Our protocol uses two separate transactions to
// convey data.
let mut transaction = Transaction::new(FRAME_SIZE).unwrap();
transaction.transmit_data_mask = true;
transaction.bit_order = BIT_ORDER;
spi.enqueue_transaction(&transaction);

// React once we have both operands in the FIFO.
spi.set_watermark(Direction::Rx, 1);
spi.set_interrupts(Interrupts::RECEIVE_DATA);

(Shared {}, Local { spi }, init::Monotonics())
}

#[task(binds=BOARD_SPI, local = [spi])]
fn spi_interrupt(cx: spi_interrupt::Context) {
let spi_interrupt::LocalResources { spi, .. } = cx.local;

let status = spi.status();
spi.clear_status(status);

// There must be something, or we wouldn't have activated.
let fst: Elem = spi.read_data().unwrap().try_into().unwrap();
let snd: Elem = spi.read_data().unwrap().try_into().unwrap();

// Prepare a new transaction that only sends data (ignores any received
// data).
let mut transaction = Transaction::new(FRAME_SIZE).unwrap();
transaction.receive_data_mask = true;
transaction.bit_order = BIT_ORDER;
spi.enqueue_transaction(&transaction);

// Send the result.
let sum: u8 = fst.wrapping_add(snd);
spi.enqueue_data(sum.into());

// Prepare to receive the next elements from the controller.
let mut transaction = Transaction::new(FRAME_SIZE).unwrap();
transaction.transmit_data_mask = true;
transaction.bit_order = BIT_ORDER;
spi.enqueue_transaction(&transaction);
}
}
20 changes: 19 additions & 1 deletion src/common/lpspi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,16 @@
//! you instead want to manage chip select in software, you should be able to multiplex your own
//! pins, then construct the driver [`without_pins`](Lpspi::without_pins).
//!
//! # Device support
//!
//! By default, the peripheral behaves as a SPI controller, coordinating I/O for other SPI devices.
//! To behave like a device, use [`set_device_enable`](Disabled::set_device_enable).
//!
//! As of this writing, you're expected to use the lower-level interface to perform device I/O.
//!
//! # Example
//!
//! Initialize an LPSPI with a 1MHz SCK. To understand how to configure the LPSPI
//! Initialize an LPSPI controller with a 1MHz SCK. To understand how to configure the LPSPI
//! peripheral clock, see the [`ccm::lpspi_clk`](crate::ccm::lpspi_clk) documentation.
//!
//! ```no_run
Expand Down Expand Up @@ -1205,6 +1212,17 @@ impl<'a, const N: u8> Disabled<'a, N> {
}
}
}

/// Become an LPSPI device, instead of a controller.
///
/// By default, the LPSPI driver acts as a controller, driving I/O.
/// By enabling the device functions (`true`), you can accept
/// and react to another controller's I/O. When you're acting as a
/// device, you don't control the clock and chip select lines.
#[inline]
pub fn set_device_enable(&mut self, enable: bool) {
ral::modify_reg!(ral::lpspi, self.lpspi, CFGR1, MASTER: !enable as u32);
}
}

impl<const N: u8> Drop for Disabled<'_, N> {
Expand Down

0 comments on commit d443eb1

Please sign in to comment.