diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index bafc1f1a..f774181c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a03a54..48af351c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/Cargo.toml b/Cargo.toml index a841283f..78bb9a11 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -127,6 +127,7 @@ opt-level = "z" lto = "fat" panic = "abort" overflow-checks = true +debug = 2 [profile.dev.build-override] opt-level = 0 @@ -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"] diff --git a/board/src/imxrt1010evk.rs b/board/src/imxrt1010evk.rs index eb3b3af2..8de941d8 100644 --- a/board/src/imxrt1010evk.rs +++ b/board/src/imxrt1010evk.rs @@ -278,6 +278,7 @@ fn configure_pins( super::Pads { ref mut gpio, ref mut gpio_sd, + ref mut gpio_ad, .. }: &mut super::Pads, ) { @@ -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. diff --git a/board/src/imxrt1060evk.rs b/board/src/imxrt1060evk.rs index 042938dd..ded7fb59 100644 --- a/board/src/imxrt1060evk.rs +++ b/board/src/imxrt1060evk.rs @@ -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; @@ -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. diff --git a/board/src/imxrt1170evk-cm7.rs b/board/src/imxrt1170evk-cm7.rs index 9a8246a8..3e5a734b 100644 --- a/board/src/imxrt1170evk-cm7.rs +++ b/board/src/imxrt1170evk-cm7.rs @@ -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 { diff --git a/board/src/teensy4.rs b/board/src/teensy4.rs index 97e42293..862c6d44 100644 --- a/board/src/teensy4.rs +++ b/board/src/teensy4.rs @@ -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, ) { @@ -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")] diff --git a/examples/rtic_spi_controller.rs b/examples/rtic_spi_controller.rs new file mode 100644 index 00000000..971f65d6 --- /dev/null +++ b/examples/rtic_spi_controller.rs @@ -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) + ); + } +} diff --git a/examples/rtic_spi_device.rs b/examples/rtic_spi_device.rs new file mode 100644 index 00000000..4677cdb6 --- /dev/null +++ b/examples/rtic_spi_device.rs @@ -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::() * 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); + } +} diff --git a/src/common/lpspi.rs b/src/common/lpspi.rs index 739908ae..c81b50c6 100644 --- a/src/common/lpspi.rs +++ b/src/common/lpspi.rs @@ -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 @@ -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 Drop for Disabled<'_, N> {