Skip to content

Commit 2b63ab2

Browse files
committed
Add SPI example using embedded_hal_bus
1 parent 32a29e0 commit 2b63ab2

File tree

2 files changed

+201
-0
lines changed

2 files changed

+201
-0
lines changed

rp2040-hal-examples/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ dht-sensor = "0.2.1"
2222
embedded-alloc = "0.5.1"
2323
embedded-hal = "1.0.0"
2424
embedded-hal-async = "1.0.0"
25+
embedded-hal-bus = { version = "0.2.0", features = ["defmt-03"] }
2526
embedded_hal_0_2 = {package = "embedded-hal", version = "0.2.5", features = ["unproven"]}
2627
fugit = "0.3.6"
2728
futures = {version = "0.3.30", default-features = false, features = ["async-await"]}
@@ -32,5 +33,8 @@ panic-halt = "0.2.0"
3233
panic-probe = {version = "0.3.1", features = ["print-defmt"]}
3334
pio = "0.2.0"
3435
pio-proc = "0.2.0"
36+
# We aren't using this, but embedded-hal-bus 0.2 unconditionally requires atomics.
37+
# Should be fixed in e-h-b 0.3 via https://github.com/rust-embedded/embedded-hal/pull/607
38+
portable-atomic = { version = "1.7.0", features = ["critical-section"] }
3539
rp2040-boot2 = "0.3.0"
3640
rp2040-hal = {path = "../rp2040-hal", version = "0.10.0", features = ["binary-info", "critical-section-impl", "rt", "defmt"]}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//! # SPI Bus example
2+
//!
3+
//! This application demonstrates how to construct a simple Spi Driver,
4+
//! and configure rp2040-hal's Spi peripheral to access it by utilising
5+
//! `ExclusiveDevice`` from `embedded_hal_bus`
6+
//!
7+
//!
8+
//! It may need to be adapted to your particular board layout and/or pin
9+
//! assignment.
10+
//!
11+
//! See the top-level `README.md` file for Copyright and license details.
12+
13+
#![no_std]
14+
#![no_main]
15+
16+
use embedded_hal::spi::Operation;
17+
use embedded_hal::spi::SpiDevice;
18+
use embedded_hal_bus::spi::ExclusiveDevice;
19+
// Ensure we halt the program on panic (if we don't mention this crate it won't
20+
// be linked)
21+
use panic_halt as _;
22+
23+
use rp2040_hal::gpio::PinState;
24+
use rp2040_hal::uart::{DataBits, StopBits, UartConfig};
25+
// Alias for our HAL crate
26+
use rp2040_hal as hal;
27+
28+
// Some traits we need
29+
use core::fmt::Write;
30+
use hal::clocks::Clock;
31+
use hal::fugit::RateExtU32;
32+
33+
// A shorter alias for the Peripheral Access Crate, which provides low-level
34+
// register access
35+
use hal::pac;
36+
37+
/// The linker will place this boot block at the start of our program image. We
38+
/// need this to help the ROM bootloader get our code up and running.
39+
/// Note: This boot block is not necessary when using a rp-hal based BSP
40+
/// as the BSPs already perform this step.
41+
#[link_section = ".boot2"]
42+
#[used]
43+
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H;
44+
45+
/// External high-speed crystal on the Raspberry Pi Pico board is 12 MHz. Adjust
46+
/// if your board has a different frequency
47+
const XTAL_FREQ_HZ: u32 = 12_000_000u32;
48+
49+
/// Our Spi device driver
50+
/// We need to use a generic here (SPI), because we want our driver to work for any other
51+
/// microcontroller that implements the embedded-hal Spi traits
52+
struct MySpiDriver<SPI> {
53+
spi: SPI,
54+
}
55+
56+
/// When things go wrong, we could return Error(()) but that wouldn't help anyone troubleshoot
57+
/// so we'll create an error type with additional info to return.
58+
#[derive(Copy, Clone, Debug)]
59+
enum MyError<SPI> {
60+
Spi(SPI),
61+
// Add other errors for your driver here.
62+
}
63+
64+
/// Implementation of the business logic for the remote Spi IC
65+
impl<SPI> MySpiDriver<SPI>
66+
where
67+
SPI: SpiDevice,
68+
{
69+
/// Construct a new instance of Spi device driver
70+
pub fn new(spi: SPI) -> Self {
71+
Self { spi }
72+
}
73+
74+
/// Our hypothetical Spi device has a register at 0x20, that accepts a u8 value
75+
pub fn set_value(&mut self, value: u8) -> Result<(), MyError<SPI::Error>> {
76+
self.spi
77+
.transaction(&mut [Operation::Write(&[0x20, value])])
78+
.map_err(MyError::Spi)?;
79+
80+
Ok(())
81+
}
82+
83+
/// Our hypothetical Spi device has a register at 0x90, that we can read a 2 byte value from
84+
pub fn get_value(&mut self) -> Result<[u8; 2], MyError<SPI::Error>> {
85+
let mut buf = [0; 2];
86+
self.spi
87+
.transaction(&mut [Operation::Write(&[0x90]), Operation::Read(&mut buf)])
88+
.map_err(MyError::Spi)?;
89+
90+
Ok(buf)
91+
}
92+
}
93+
94+
/// Entry point to our bare-metal application.
95+
///
96+
/// The `#[rp2040_hal::entry]` macro ensures the Cortex-M start-up code calls this function
97+
/// as soon as all global variables and the spinlock are initialised.
98+
///
99+
/// The function configures the RP2040 peripherals, then performs some example
100+
/// SPI transactions, then goes to sleep.
101+
#[rp2040_hal::entry]
102+
fn main() -> ! {
103+
// Grab our singleton objects
104+
let mut pac = pac::Peripherals::take().unwrap();
105+
106+
// Set up the watchdog driver - needed by the clock setup code
107+
let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);
108+
109+
// Configure the clocks
110+
let clocks = hal::clocks::init_clocks_and_plls(
111+
XTAL_FREQ_HZ,
112+
pac.XOSC,
113+
pac.CLOCKS,
114+
pac.PLL_SYS,
115+
pac.PLL_USB,
116+
&mut pac.RESETS,
117+
&mut watchdog,
118+
)
119+
.unwrap();
120+
121+
// The single-cycle I/O block controls our GPIO pins
122+
let sio = hal::Sio::new(pac.SIO);
123+
124+
// Set the pins to their default state
125+
let pins = hal::gpio::Pins::new(
126+
pac.IO_BANK0,
127+
pac.PADS_BANK0,
128+
sio.gpio_bank0,
129+
&mut pac.RESETS,
130+
);
131+
132+
let timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);
133+
134+
let uart_pins = (
135+
// UART TX (characters sent from RP2040) on pin 1 (GPIO0)
136+
pins.gpio0.into_function(),
137+
// UART RX (characters received by RP2040) on pin 2 (GPIO1)
138+
pins.gpio1.into_function(),
139+
);
140+
141+
// Set up a uart so we can print out the values from our Spi peripheral
142+
let mut uart = hal::uart::UartPeripheral::new(pac.UART0, uart_pins, &mut pac.RESETS)
143+
.enable(
144+
UartConfig::new(9600.Hz(), DataBits::Eight, None, StopBits::One),
145+
clocks.peripheral_clock.freq(),
146+
)
147+
.unwrap();
148+
149+
// Set up our SPI pins so they can be used by the SPI driver
150+
let spi_mosi = pins.gpio7.into_function::<hal::gpio::FunctionSpi>();
151+
let spi_miso = pins.gpio4.into_function::<hal::gpio::FunctionSpi>();
152+
let spi_sclk = pins.gpio6.into_function::<hal::gpio::FunctionSpi>();
153+
let spi_cs = pins.gpio8.into_push_pull_output_in_state(PinState::High);
154+
let spi = hal::spi::Spi::<_, _, _, 8>::new(pac.SPI0, (spi_mosi, spi_miso, spi_sclk));
155+
156+
// Exchange the uninitialised SPI driver for an initialised one
157+
let spi = spi.init(
158+
&mut pac.RESETS,
159+
clocks.peripheral_clock.freq(),
160+
16.MHz(),
161+
embedded_hal::spi::MODE_0,
162+
);
163+
164+
// We are the only task talking to this SPI peripheral, so we can use ExclusiveDevice here.
165+
// If we had multiple tasks accessing this, you would need to use a different interface to
166+
// ensure that we have exclusive access to this bus (via AtomicDevice or CriticalSectionDevice, for example)
167+
// We can safely unwrap here, because the only possible failure is CS assertion failure, but our CS pin is infallible
168+
let spi_bus = ExclusiveDevice::new(spi, spi_cs, timer).unwrap();
169+
170+
// Now that we've constructed a SpiDevice for our driver to use, we can finally construct our Spi driver
171+
let mut driver = MySpiDriver::new(spi_bus);
172+
173+
match driver.set_value(10) {
174+
Ok(_) => {
175+
// Do something on success
176+
}
177+
Err(_) => {
178+
// Do something on failure
179+
}
180+
}
181+
182+
match driver.get_value() {
183+
Ok(value) => {
184+
// Do something on success
185+
writeln!(uart, "Read value was {} {}\n", value[0], value[1]).unwrap();
186+
}
187+
Err(_) => {
188+
// Do something on failure
189+
}
190+
}
191+
192+
loop {
193+
cortex_m::asm::wfi();
194+
}
195+
}
196+
197+
// End of file

0 commit comments

Comments
 (0)