Skip to content

Commit

Permalink
Add SPI example using embedded_hal_bus
Browse files Browse the repository at this point in the history
Remove stale comment

Always another typo
  • Loading branch information
9names committed Aug 19, 2024
1 parent 32a29e0 commit 9e6a8e4
Show file tree
Hide file tree
Showing 2 changed files with 201 additions and 0 deletions.
4 changes: 4 additions & 0 deletions rp2040-hal-examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
Expand All @@ -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"]}
197 changes: 197 additions & 0 deletions rp2040-hal-examples/src/bin/spi_eh_bus.rs
Original file line number Diff line number Diff line change
@@ -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: 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(SPI),
// Add other errors for your driver here.
}

/// Implementation of the business logic for the remote Spi IC
impl<SPI> MySpiDriver<SPI>
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<SPI::Error>> {
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<SPI::Error>> {
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::<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_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

0 comments on commit 9e6a8e4

Please sign in to comment.