From 2b63ab2a107e13be09ee79f08df91b4cc9918529 Mon Sep 17 00:00:00 2001 From: 9names <60134748+9names@users.noreply.github.com> Date: Mon, 19 Aug 2024 20:18:31 +1000 Subject: [PATCH 1/6] Add SPI example using embedded_hal_bus --- rp2040-hal-examples/Cargo.toml | 4 + rp2040-hal-examples/src/bin/spi_eh_bus.rs | 197 ++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 rp2040-hal-examples/src/bin/spi_eh_bus.rs diff --git a/rp2040-hal-examples/Cargo.toml b/rp2040-hal-examples/Cargo.toml index 7956ab7ac..f6e30c979 100644 --- a/rp2040-hal-examples/Cargo.toml +++ b/rp2040-hal-examples/Cargo.toml @@ -22,6 +22,7 @@ dht-sensor = "0.2.1" embedded-alloc = "0.5.1" embedded-hal = "1.0.0" embedded-hal-async = "1.0.0" +embedded-hal-bus = { version = "0.2.0", features = ["defmt-03"] } embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} fugit = "0.3.6" futures = {version = "0.3.30", default-features = false, features = ["async-await"]} @@ -32,5 +33,8 @@ panic-halt = "0.2.0" panic-probe = {version = "0.3.1", features = ["print-defmt"]} pio = "0.2.0" pio-proc = "0.2.0" +# We aren't using this, but embedded-hal-bus 0.2 unconditionally requires atomics. +# Should be fixed in e-h-b 0.3 via https://github.com/rust-embedded/embedded-hal/pull/607 +portable-atomic = { version = "1.7.0", features = ["critical-section"] } rp2040-boot2 = "0.3.0" rp2040-hal = {path = "../rp2040-hal", version = "0.10.0", features = ["binary-info", "critical-section-impl", "rt", "defmt"]} diff --git a/rp2040-hal-examples/src/bin/spi_eh_bus.rs b/rp2040-hal-examples/src/bin/spi_eh_bus.rs new file mode 100644 index 000000000..6c3e08de5 --- /dev/null +++ b/rp2040-hal-examples/src/bin/spi_eh_bus.rs @@ -0,0 +1,197 @@ +//! # SPI Bus example +//! +//! This application demonstrates how to construct a simple Spi Driver, +//! and configure rp2040-hal's Spi peripheral to access it by utilising +//! `ExclusiveDevice`` from `embedded_hal_bus` +//! +//! +//! It may need to be adapted to your particular board layout and/or pin +//! assignment. +//! +//! See the top-level `README.md` file for Copyright and license details. + +#![no_std] +#![no_main] + +use embedded_hal::spi::Operation; +use embedded_hal::spi::SpiDevice; +use embedded_hal_bus::spi::ExclusiveDevice; +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +use rp2040_hal::gpio::PinState; +use rp2040_hal::uart::{DataBits, StopBits, UartConfig}; +// Alias for our HAL crate +use rp2040_hal as hal; + +// Some traits we need +use core::fmt::Write; +use hal::clocks::Clock; +use hal::fugit::RateExtU32; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use hal::pac; + +/// The linker will place this boot block at the start of our program image. We +/// need this to help the ROM bootloader get our code up and running. +/// Note: This boot block is not necessary when using a rp-hal based BSP +/// as the BSPs already perform this step. +#[link_section = ".boot2"] +#[used] +pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; + +/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust +/// if your board has a different frequency +const XTAL_FREQ_HZ: u32 = 12_000_000u32; + +/// Our Spi device driver +/// We need to use a generic here (SPI), because we want our driver to work for any other +/// microcontroller that implements the embedded-hal Spi traits +struct MySpiDriver { + spi: SPI, +} + +/// When things go wrong, we could return Error(()) but that wouldn't help anyone troubleshoot +/// so we'll create an error type with additional info to return. +#[derive(Copy, Clone, Debug)] +enum MyError { + Spi(SPI), + // Add other errors for your driver here. +} + +/// Implementation of the business logic for the remote Spi IC +impl MySpiDriver +where + SPI: SpiDevice, +{ + /// Construct a new instance of Spi device driver + pub fn new(spi: SPI) -> Self { + Self { spi } + } + + /// Our hypothetical Spi device has a register at 0x20, that accepts a u8 value + pub fn set_value(&mut self, value: u8) -> Result<(), MyError> { + self.spi + .transaction(&mut [Operation::Write(&[0x20, value])]) + .map_err(MyError::Spi)?; + + Ok(()) + } + + /// Our hypothetical Spi device has a register at 0x90, that we can read a 2 byte value from + pub fn get_value(&mut self) -> Result<[u8; 2], MyError> { + let mut buf = [0; 2]; + self.spi + .transaction(&mut [Operation::Write(&[0x90]), Operation::Read(&mut buf)]) + .map_err(MyError::Spi)?; + + Ok(buf) + } +} + +/// Entry point to our bare-metal application. +/// +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables and the spinlock are initialised. +/// +/// The function configures the RP2040 peripherals, then performs some example +/// SPI transactions, then goes to sleep. +#[rp2040_hal::entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + let clocks = hal::clocks::init_clocks_and_plls( + XTAL_FREQ_HZ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog, + ) + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins to their default state + let pins = hal::gpio::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + let timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + + let uart_pins = ( + // UART TX (characters sent from RP2040) on pin 1 (GPIO0) + pins.gpio0.into_function(), + // UART RX (characters received by RP2040) on pin 2 (GPIO1) + pins.gpio1.into_function(), + ); + + // Set up a uart so we can print out the values from our Spi peripheral + let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) + .enable( + UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), + clocks.peripheral_clock.freq(), + ) + .unwrap(); + + // Set up our SPI pins so they can be used by the SPI driver + let spi_mosi = pins.gpio7.into_function::(); + let spi_miso = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); + let spi_cs = pins.gpio8.into_push_pull_output_in_state(PinState::High); + let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + + // Exchange the uninitialised SPI driver for an initialised one + let spi = spi.init( + &mut pac.RESETS, + clocks.peripheral_clock.freq(), + 16.MHz(), + embedded_hal::spi::MODE_0, + ); + + // We are the only task talking to this SPI peripheral, so we can use ExclusiveDevice here. + // If we had multiple tasks accessing this, you would need to use a different interface to + // ensure that we have exclusive access to this bus (via AtomicDevice or CriticalSectionDevice, for example) + // We can safely unwrap here, because the only possible failure is CS assertion failure, but our CS pin is infallible + let spi_bus = ExclusiveDevice::new(spi, spi_cs, timer).unwrap(); + + // Now that we've constructed a SpiDevice for our driver to use, we can finally construct our Spi driver + let mut driver = MySpiDriver::new(spi_bus); + + match driver.set_value(10) { + Ok(_) => { + // Do something on success + } + Err(_) => { + // Do something on failure + } + } + + match driver.get_value() { + Ok(value) => { + // Do something on success + writeln!(uart, "Read value was {} {}\n", value[0], value[1]).unwrap(); + } + Err(_) => { + // Do something on failure + } + } + + loop { + cortex_m::asm::wfi(); + } +} + +// End of file From bb8c6a096733bc6bf890dc81a49021a3d990358b Mon Sep 17 00:00:00 2001 From: 9names <60134748+9names@users.noreply.github.com> Date: Mon, 19 Aug 2024 20:30:51 +1000 Subject: [PATCH 2/6] Fix up the SPI example pins comment too --- rp2040-hal-examples/src/bin/spi.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rp2040-hal-examples/src/bin/spi.rs b/rp2040-hal-examples/src/bin/spi.rs index 85b0b7445..a7a76ff4b 100644 --- a/rp2040-hal-examples/src/bin/spi.rs +++ b/rp2040-hal-examples/src/bin/spi.rs @@ -78,7 +78,7 @@ fn main() -> ! { &mut pac.RESETS, ); - // These are implicitly used by the spi driver if they are in the correct mode + // Set up our SPI pins so they can be used by the SPI driver let spi_mosi = pins.gpio7.into_function::(); let spi_miso = pins.gpio4.into_function::(); let spi_sclk = pins.gpio6.into_function::(); From 3d89e339da8d3a3717a7367272d3ef59dc88d07d Mon Sep 17 00:00:00 2001 From: 9names <60134748+9names@users.noreply.github.com> Date: Mon, 19 Aug 2024 21:01:54 +1000 Subject: [PATCH 3/6] Impl some review fixes --- rp2040-hal-examples/Cargo.toml | 4 ++-- rp2040-hal-examples/src/bin/spi_eh_bus.rs | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/rp2040-hal-examples/Cargo.toml b/rp2040-hal-examples/Cargo.toml index f6e30c979..184f373e2 100644 --- a/rp2040-hal-examples/Cargo.toml +++ b/rp2040-hal-examples/Cargo.toml @@ -22,7 +22,7 @@ dht-sensor = "0.2.1" embedded-alloc = "0.5.1" embedded-hal = "1.0.0" embedded-hal-async = "1.0.0" -embedded-hal-bus = { version = "0.2.0", features = ["defmt-03"] } +embedded-hal-bus = {version = "0.2.0", features = ["defmt-03"]} embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]} fugit = "0.3.6" futures = {version = "0.3.30", default-features = false, features = ["async-await"]} @@ -35,6 +35,6 @@ pio = "0.2.0" pio-proc = "0.2.0" # We aren't using this, but embedded-hal-bus 0.2 unconditionally requires atomics. # Should be fixed in e-h-b 0.3 via https://github.com/rust-embedded/embedded-hal/pull/607 -portable-atomic = { version = "1.7.0", features = ["critical-section"] } +portable-atomic = {version = "1.7.0", features = ["critical-section"]} rp2040-boot2 = "0.3.0" rp2040-hal = {path = "../rp2040-hal", version = "0.10.0", features = ["binary-info", "critical-section-impl", "rt", "defmt"]} diff --git a/rp2040-hal-examples/src/bin/spi_eh_bus.rs b/rp2040-hal-examples/src/bin/spi_eh_bus.rs index 6c3e08de5..eac2f4eaf 100644 --- a/rp2040-hal-examples/src/bin/spi_eh_bus.rs +++ b/rp2040-hal-examples/src/bin/spi_eh_bus.rs @@ -61,17 +61,17 @@ enum MyError { // Add other errors for your driver here. } -/// Implementation of the business logic for the remote Spi IC +/// Implementation of the business logic for the remote SPI IC impl MySpiDriver where SPI: SpiDevice, { - /// Construct a new instance of Spi device driver + /// Construct a new instance of SPI device driver pub fn new(spi: SPI) -> Self { Self { spi } } - /// Our hypothetical Spi device has a register at 0x20, that accepts a u8 value + /// Our hypothetical SPI device has a register at 0x20, that accepts a u8 value pub fn set_value(&mut self, value: u8) -> Result<(), MyError> { self.spi .transaction(&mut [Operation::Write(&[0x20, value])]) @@ -138,10 +138,10 @@ fn main() -> ! { pins.gpio1.into_function(), ); - // Set up a uart so we can print out the values from our Spi peripheral + // Set up a uart so we can print out the values from our SPI peripheral let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) .enable( - UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One), + UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), clocks.peripheral_clock.freq(), ) .unwrap(); @@ -150,10 +150,14 @@ fn main() -> ! { let spi_mosi = pins.gpio7.into_function::(); let spi_miso = pins.gpio4.into_function::(); let spi_sclk = pins.gpio6.into_function::(); + + // Chip select is handled by SpiDevice, not SpiBus. It logically belongs with the specific SPI device we're talking to let spi_cs = pins.gpio8.into_push_pull_output_in_state(PinState::High); + + // Create new, uninitialized SPI bus with one of the 2 SPI peripherals from our PAC, and our SPI data/clock pins let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); - // Exchange the uninitialised SPI driver for an initialised one + // Exchange the uninitialised Spi bus for an initialised one, passing in the extra bus parameters required let spi = spi.init( &mut pac.RESETS, clocks.peripheral_clock.freq(), @@ -164,11 +168,12 @@ fn main() -> ! { // We are the only task talking to this SPI peripheral, so we can use ExclusiveDevice here. // If we had multiple tasks accessing this, you would need to use a different interface to // ensure that we have exclusive access to this bus (via AtomicDevice or CriticalSectionDevice, for example) + // // We can safely unwrap here, because the only possible failure is CS assertion failure, but our CS pin is infallible - let spi_bus = ExclusiveDevice::new(spi, spi_cs, timer).unwrap(); + let excl_dev = ExclusiveDevice::new(spi, spi_cs, timer).unwrap(); // Now that we've constructed a SpiDevice for our driver to use, we can finally construct our Spi driver - let mut driver = MySpiDriver::new(spi_bus); + let mut driver = MySpiDriver::new(excl_dev); match driver.set_value(10) { Ok(_) => { From 7ae4610bbdc119d71b76bc0000e64feea0ade77e Mon Sep 17 00:00:00 2001 From: Jonathan 'theJPster' Pallant Date: Mon, 19 Aug 2024 19:12:06 +0100 Subject: [PATCH 4/6] Added even more comments to spi_eh_bus. --- rp2040-hal-examples/src/bin/spi_eh_bus.rs | 250 ++++++++++++++++------ 1 file changed, 180 insertions(+), 70 deletions(-) diff --git a/rp2040-hal-examples/src/bin/spi_eh_bus.rs b/rp2040-hal-examples/src/bin/spi_eh_bus.rs index eac2f4eaf..243ed9c57 100644 --- a/rp2040-hal-examples/src/bin/spi_eh_bus.rs +++ b/rp2040-hal-examples/src/bin/spi_eh_bus.rs @@ -1,38 +1,57 @@ //! # SPI Bus example //! -//! This application demonstrates how to construct a simple Spi Driver, -//! and configure rp2040-hal's Spi peripheral to access it by utilising -//! `ExclusiveDevice`` from `embedded_hal_bus` +//! This application demonstrates how to construct a simple SPI Driver and +//! configure rp2040-hal's SPI peripheral to access it by utilising +//! [`ExclusiveDevice`] from [`embedded-hal-bus`]. //! +//! [`ExclusiveDevice`]: +//! https://docs.rs/embedded-hal-bus/latest/embedded_hal_bus/spi/struct.ExclusiveDevice.html +//! [`embedded-hal-bus`]: https://crates.io/crates/embedded-hal-bus //! //! It may need to be adapted to your particular board layout and/or pin //! assignment. //! //! See the top-level `README.md` file for Copyright and license details. +//! +//! ## Glossary +//! +//! * *SPI Bus* - a shared bus consisting of three shared wires (Clock, COPI and +//! CIPO) and a unique 'Chip Select' wire for each device on the bus. An *SPI +//! Bus* typically only has one *SPI Controller* which is 'driving' the bus +//! (specifically it is driving the clock signal and the *COPI* data line). +//! * *COPI* - One of the data lines on an *SPI Bus*. Stands for *Controller +//! Out, Peripheral In*, but we use the term *SPI Device* instead of *SPI +//! Peripheral*. +//! * *CIPO* - One of the data lines on an *SPI Bus*. Stands for *Controller In, +//! Peripheral Out*, but we use the term *SPI Device* instead of *SPI +//! Peripheral*. +//! * *SPI Controller* - a block of silicon within the RP2040 designed for +//! driving an *SPI Bus*. +//! * *SPI Device* - a device on the *SPI Bus*; has its own unique chip select +//! signal. +//! * *Device Driver* - a Rust type (and/or a value of that type) which +//! represents a particular *SPI Device*, such as an Bosch BMA400 +//! accelerometer chip, or an ST7789 LCD controller chip. #![no_std] #![no_main] -use embedded_hal::spi::Operation; -use embedded_hal::spi::SpiDevice; -use embedded_hal_bus::spi::ExclusiveDevice; // Ensure we halt the program on panic (if we don't mention this crate it won't // be linked) use panic_halt as _; -use rp2040_hal::gpio::PinState; -use rp2040_hal::uart::{DataBits, StopBits, UartConfig}; // Alias for our HAL crate use rp2040_hal as hal; -// Some traits we need +// Some types/traits we need use core::fmt::Write; +use embedded_hal::spi::Operation; +use embedded_hal::spi::SpiDevice; +use embedded_hal_bus::spi::ExclusiveDevice; use hal::clocks::Clock; use hal::fugit::RateExtU32; - -// A shorter alias for the Peripheral Access Crate, which provides low-level -// register access -use hal::pac; +use hal::gpio::PinState; +use hal::uart::{DataBits, StopBits, UartConfig}; /// The linker will place this boot block at the start of our program image. We /// need this to help the ROM bootloader get our code up and running. @@ -46,62 +65,89 @@ pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H; /// if your board has a different frequency const XTAL_FREQ_HZ: u32 = 12_000_000u32; -/// Our Spi device driver -/// We need to use a generic here (SPI), because we want our driver to work for any other -/// microcontroller that implements the embedded-hal Spi traits -struct MySpiDriver { - spi: SPI, -} - -/// When things go wrong, we could return Error(()) but that wouldn't help anyone troubleshoot -/// so we'll create an error type with additional info to return. +/// The ways our device driver might fail. +/// +/// When things go wrong, we could return `Err(())`` but that wouldn't help +/// anyone troubleshoot so we'll create an error type with additional info to +/// return. #[derive(Copy, Clone, Debug)] -enum MyError { - Spi(SPI), +pub enum MyError { + /// We got an error from the *SPI Device* + Spi(E), // Add other errors for your driver here. } -/// Implementation of the business logic for the remote SPI IC -impl MySpiDriver +/// Our *Device Driver* type. +/// +/// This is an example of a device driver that manages some kind of *SPI +/// Device*. +/// +/// It is not a driver for any real device - it's here as an example of how to +/// write such a driver. You would typically get real drivers from a library +/// crate but we've pasted it into the example so you can see it. +/// +/// We need to use a generic here (`SD`), because we want our example +/// driver to work for any other microcontroller that implements the +/// embedded-hal SPI traits - specifically the `embedded_hal::spi::SpiDevice` +/// trait that represents access to a unique device on the bus. +pub struct MySpiDeviceDriver { + spi_dev: SD, +} + +impl MySpiDeviceDriver where - SPI: SpiDevice, + SD: SpiDevice, { - /// Construct a new instance of SPI device driver - pub fn new(spi: SPI) -> Self { - Self { spi } + /// Construct a new instance of our *Device Driver*. + /// + /// Takes ownership of a value representing a unique device on the *SPI + /// Bus*. + pub fn new(spi_dev: SD) -> Self { + Self { spi_dev } } - /// Our hypothetical SPI device has a register at 0x20, that accepts a u8 value - pub fn set_value(&mut self, value: u8) -> Result<(), MyError> { - self.spi + /// Write to our hypothetical device. + /// + /// We imagine our device has a register at `0x20`, that accepts a `u8` + /// value. + pub fn set_value(&mut self, value: u8) -> Result<(), MyError> { + self.spi_dev .transaction(&mut [Operation::Write(&[0x20, value])]) .map_err(MyError::Spi)?; Ok(()) } - /// Our hypothetical Spi device has a register at 0x90, that we can read a 2 byte value from - pub fn get_value(&mut self) -> Result<[u8; 2], MyError> { - let mut buf = [0; 2]; - self.spi + /// Read from our hypothetical device. + /// + /// We imagine our device has a register at `0x90`, that we can read a 2 + /// byte integer value from. + pub fn get_value(&mut self) -> Result> { + let mut buf = [0u8; 2]; + self.spi_dev .transaction(&mut [Operation::Write(&[0x90]), Operation::Read(&mut buf)]) .map_err(MyError::Spi)?; - Ok(buf) + Ok(u16::from_le_bytes(buf)) } } /// Entry point to our bare-metal application. /// -/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function -/// as soon as all global variables and the spinlock are initialised. +/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls +/// this function as soon as all global variables and the spinlock are +/// initialised. /// /// The function configures the RP2040 peripherals, then performs some example /// SPI transactions, then goes to sleep. #[rp2040_hal::entry] fn main() -> ! { + // ======================================================================== + // Some things that pretty much every rp2040-hal program will do + // ======================================================================== + // Grab our singleton objects - let mut pac = pac::Peripherals::take().unwrap(); + let mut pac = hal::pac::Peripherals::take().unwrap(); // Set up the watchdog driver - needed by the clock setup code let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); @@ -129,8 +175,21 @@ fn main() -> ! { &mut pac.RESETS, ); + // ======================================================================== + // Construct a timer object, which we will need later. + // ======================================================================== + let timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + // ======================================================================== + // Set up a UART so we can print out the values we read using our *Device + // Driver*: + // ======================================================================== + + // How we want our UART to be configured + let uart_config = UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One); + + // The pins we want to use for our UART. let uart_pins = ( // UART TX (characters sent from RP2040) on pin 1 (GPIO0) pins.gpio0.into_function(), @@ -138,62 +197,113 @@ fn main() -> ! { pins.gpio1.into_function(), ); - // Set up a uart so we can print out the values from our SPI peripheral - let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS) - .enable( - UartConfig::new(115200.Hz(), DataBits::Eight, None, StopBits::One), - clocks.peripheral_clock.freq(), - ) - .unwrap(); + // Create new, uninitialized UART driver with one of the two UART objects + // from our PAC, and our UART pins. We need temporary access to the RESETS + // object to reset the UART. + let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS); - // Set up our SPI pins so they can be used by the SPI driver - let spi_mosi = pins.gpio7.into_function::(); - let spi_miso = pins.gpio4.into_function::(); - let spi_sclk = pins.gpio6.into_function::(); + // Swap the uninitialised UART driver for an initialised one, passing in the + // desired configuration. We need to know the internal UART clock frequency + // to set up the baud rate dividers correctly. + let mut uart = uart + .enable(uart_config, clocks.peripheral_clock.freq()) + .unwrap(); - // Chip select is handled by SpiDevice, not SpiBus. It logically belongs with the specific SPI device we're talking to - let spi_cs = pins.gpio8.into_push_pull_output_in_state(PinState::High); + // ======================================================================== + // Set up our *SPI Bus* and one *SPI Device* on the bus, and then build our + // *Device Driver*: + // ======================================================================== - // Create new, uninitialized SPI bus with one of the 2 SPI peripherals from our PAC, and our SPI data/clock pins - let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk)); + // Set up our SPI pins so they can be used by the *SPI Bus* driver. + let spi_copi = pins.gpio7.into_function::(); + let spi_cipo = pins.gpio4.into_function::(); + let spi_sclk = pins.gpio6.into_function::(); - // Exchange the uninitialised Spi bus for an initialised one, passing in the extra bus parameters required - let spi = spi.init( + // Create new, uninitialized *SPI Bus* driver with one of the two *SPI* + // objects from our PAC, plus our data/clock pins for the *SPI Bus*. + // + // We've stubbed out most of the type parameters because the compiler can + // work them out for us. The only one we need to specify is the size of a + // word sent over the *SPI Bus* to our device - and we picked 8 bits (a + // byte). + let spi_bus = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_copi, spi_cipo, spi_sclk)); + + // Exchange the uninitialised *SPI Bus* driver for an initialised one, by + // passing in the extra bus parameters required. + // + // We need temporary access to the RESETS object to reset the SPI + // peripheral, and we need to know the internal clock speed in order to set + // up the clock dividers correctly. + let spi_bus = spi_bus.init( &mut pac.RESETS, clocks.peripheral_clock.freq(), 16.MHz(), embedded_hal::spi::MODE_0, ); - // We are the only task talking to this SPI peripheral, so we can use ExclusiveDevice here. - // If we had multiple tasks accessing this, you would need to use a different interface to - // ensure that we have exclusive access to this bus (via AtomicDevice or CriticalSectionDevice, for example) - // - // We can safely unwrap here, because the only possible failure is CS assertion failure, but our CS pin is infallible - let excl_dev = ExclusiveDevice::new(spi, spi_cs, timer).unwrap(); - - // Now that we've constructed a SpiDevice for our driver to use, we can finally construct our Spi driver - let mut driver = MySpiDriver::new(excl_dev); + // The Chip Select pin. Every *SPI Device* has a unique chip select signal, + // and when this pin goes low, it uniquely selects our desired + // (hypothetical) *SPI Device* on the *SPI Bus*. + let spi_cs = pins.gpio8.into_push_pull_output_in_state(PinState::High); + // Create an object which represents our (hypothetical) *SPI Device* on the + // *SPI Bus*. + // + // We are the only task talking to this *SPI Bus*, so we can use + // `ExclusiveDevice` here. If we had multiple tasks accessing the same bus + // (e.g. you had both an accelerometer and a pressure sensor on the same + // bus), we would need to use a different interface to ensure that we have + // exclusive access to this bus (via `AtomicDevice` or + // `CriticalSectionDevice`, for example). + // + // See https://docs.rs/embedded-hal-bus/0.2.0/embedded_hal_bus/spi for a + // list of options. + // + // We can safely unwrap here, because the only possible failure is CS + // assertion failure and our CS pin is infallible. + // + // Note that it also takes ownership of our timer object so that it has a + // way of measuring elapsed time. This is required so it can handle `Delay` + // transactions that can put small pauses inbetween say, a `Write` + // transaction and a `Read` transaction (which some SPI devices require + // because they're a bit sluggish and take time to process things). + let excl_spi_dev = ExclusiveDevice::new(spi_bus, spi_cs, timer).unwrap(); + + // Now that we've constructed a value of type `ExclusiveDevice` (which + // implements the embedded-hal `SpiDevice` traits) we can finally construct + // our device driver. + let mut driver = MySpiDeviceDriver::new(excl_spi_dev); + + // ======================================================================== + // Now let's use our device driver. + // ======================================================================== + + // Let's write to the device match driver.set_value(10) { Ok(_) => { // Do something on success + _ = writeln!(uart, "Wrote to device OK!"); } - Err(_) => { + Err(e) => { // Do something on failure + _ = writeln!(uart, "Device write error: {:?}", e); } } + // Let's read from the device match driver.get_value() { Ok(value) => { // Do something on success - writeln!(uart, "Read value was {} {}\n", value[0], value[1]).unwrap(); + _ = writeln!(uart, "Read value: {}", value); } - Err(_) => { + Err(e) => { // Do something on failure + _ = writeln!(uart, "Device write error: {:?}", e); } } + // We're done, so just sleep in an infinite loop. Your program should + // probably do something more useful. loop { cortex_m::asm::wfi(); } From 5124f5d8d32b54e63a607e9a576f2b04a46ecd3a Mon Sep 17 00:00:00 2001 From: 9names <60134748+9names@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:48:08 +1000 Subject: [PATCH 5/6] Rename binding pac->p --- rp2040-hal-examples/src/bin/spi_eh_bus.rs | 31 ++++++++++------------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/rp2040-hal-examples/src/bin/spi_eh_bus.rs b/rp2040-hal-examples/src/bin/spi_eh_bus.rs index 243ed9c57..22c861727 100644 --- a/rp2040-hal-examples/src/bin/spi_eh_bus.rs +++ b/rp2040-hal-examples/src/bin/spi_eh_bus.rs @@ -147,39 +147,34 @@ fn main() -> ! { // ======================================================================== // Grab our singleton objects - let mut pac = hal::pac::Peripherals::take().unwrap(); + let mut p = hal::pac::Peripherals::take().unwrap(); // Set up the watchdog driver - needed by the clock setup code - let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + let mut watchdog = hal::Watchdog::new(p.WATCHDOG); // Configure the clocks let clocks = hal::clocks::init_clocks_and_plls( XTAL_FREQ_HZ, - pac.XOSC, - pac.CLOCKS, - pac.PLL_SYS, - pac.PLL_USB, - &mut pac.RESETS, + p.XOSC, + p.CLOCKS, + p.PLL_SYS, + p.PLL_USB, + &mut p.RESETS, &mut watchdog, ) .unwrap(); // The single-cycle I/O block controls our GPIO pins - let sio = hal::Sio::new(pac.SIO); + let sio = hal::Sio::new(p.SIO); // Set the pins to their default state - let pins = hal::gpio::Pins::new( - pac.IO_BANK0, - pac.PADS_BANK0, - sio.gpio_bank0, - &mut pac.RESETS, - ); + let pins = hal::gpio::Pins::new(p.IO_BANK0, p.PADS_BANK0, sio.gpio_bank0, &mut p.RESETS); // ======================================================================== // Construct a timer object, which we will need later. // ======================================================================== - let timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks); + let timer = rp2040_hal::Timer::new(p.TIMER, &mut p.RESETS, &clocks); // ======================================================================== // Set up a UART so we can print out the values we read using our *Device @@ -200,7 +195,7 @@ fn main() -> ! { // Create new, uninitialized UART driver with one of the two UART objects // from our PAC, and our UART pins. We need temporary access to the RESETS // object to reset the UART. - let uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS); + let uart = hal::uart::UartPeripheral::new(p.UART0, uart_pins, &mut p.RESETS); // Swap the uninitialised UART driver for an initialised one, passing in the // desired configuration. We need to know the internal UART clock frequency @@ -226,7 +221,7 @@ fn main() -> ! { // work them out for us. The only one we need to specify is the size of a // word sent over the *SPI Bus* to our device - and we picked 8 bits (a // byte). - let spi_bus = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_copi, spi_cipo, spi_sclk)); + let spi_bus = hal::spi::Spi::<_, _, _, 8>::new(p.SPI0, (spi_copi, spi_cipo, spi_sclk)); // Exchange the uninitialised *SPI Bus* driver for an initialised one, by // passing in the extra bus parameters required. @@ -235,7 +230,7 @@ fn main() -> ! { // peripheral, and we need to know the internal clock speed in order to set // up the clock dividers correctly. let spi_bus = spi_bus.init( - &mut pac.RESETS, + &mut p.RESETS, clocks.peripheral_clock.freq(), 16.MHz(), embedded_hal::spi::MODE_0, From 466be690bb8155bf6759983dad46fd414d7d2ccc Mon Sep 17 00:00:00 2001 From: 9names <60134748+9names@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:49:27 +1000 Subject: [PATCH 6/6] Use hal::Timer to be consistent --- rp2040-hal-examples/src/bin/spi_eh_bus.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rp2040-hal-examples/src/bin/spi_eh_bus.rs b/rp2040-hal-examples/src/bin/spi_eh_bus.rs index 22c861727..456de1764 100644 --- a/rp2040-hal-examples/src/bin/spi_eh_bus.rs +++ b/rp2040-hal-examples/src/bin/spi_eh_bus.rs @@ -174,7 +174,7 @@ fn main() -> ! { // Construct a timer object, which we will need later. // ======================================================================== - let timer = rp2040_hal::Timer::new(p.TIMER, &mut p.RESETS, &clocks); + let timer = hal::Timer::new(p.TIMER, &mut p.RESETS, &clocks); // ======================================================================== // Set up a UART so we can print out the values we read using our *Device