Skip to content

Commit 1d426f0

Browse files
committed
Force-reset the SPI block when you reset the host.
This is because the host might be mid-SPI-byte, and we don't want to leave any bits in the receive register. The SPI block itself won't let you clear them, so we just reset it. Sadly the Rcc driver in the HAL has no useful methods, so we ensure we have the object (for exclusive access) then do some raw pointer writes.
1 parent 0efe4a8 commit 1d426f0

File tree

2 files changed

+82
-34
lines changed

2 files changed

+82
-34
lines changed

neotron-bmc-pico/src/main.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use stm32f0xx_hal::{
2222
gpio::{Alternate, Floating, Input, Output, PullDown, PullUp, PushPull, AF1},
2323
pac,
2424
prelude::*,
25-
serial,
25+
rcc, serial,
2626
};
2727

2828
use neotron_bmc_commands::Command;
@@ -163,6 +163,8 @@ mod app {
163163
press_button_power_long: debouncr::Debouncer<u16, debouncr::Repeat16>,
164164
/// Tracks reset button state for short presses. 75ms x 2 = 150ms is a long press
165165
press_button_reset_short: debouncr::Debouncer<u8, debouncr::Repeat2>,
166+
/// Run-time Clock Control (required for resetting peripheral blocks)
167+
rcc: Option<rcc::Rcc>,
166168
}
167169

168170
#[monotonic(binds = SysTick, default = true)]
@@ -342,6 +344,7 @@ mod app {
342344
press_button_power_short: debouncr::debounce_2(false),
343345
press_button_power_long: debouncr::debounce_16(false),
344346
press_button_reset_short: debouncr::debounce_2(false),
347+
rcc: Some(rcc),
345348
};
346349
let init = init::Monotonics(mono);
347350
(shared_resources, local_resources, init)
@@ -350,14 +353,15 @@ mod app {
350353
/// Our idle task.
351354
///
352355
/// This task is called when there is nothing else to do.
353-
#[idle(shared = [msg_q_out, msg_q_in, spi, state_dc_power_enabled, pin_dc_on, pin_sys_reset])]
356+
#[idle(shared = [msg_q_out, msg_q_in, spi, state_dc_power_enabled, pin_dc_on, pin_sys_reset], local = [rcc])]
354357
fn idle(mut ctx: idle::Context) -> ! {
355358
// TODO: Get this from the VERSION static variable or from PKG_VERSION
356359
let mut register_state = RegisterState {
357360
firmware_version: *b"Neotron BMC v0.4.1-alpha\x00\x00\x00\x00\x00\x00\x00\x00",
358361
..Default::default()
359362
};
360-
363+
// Take this out of the `local` object to avoid sharing issues.
364+
let mut rcc = ctx.local.rcc.take().unwrap();
361365
defmt::info!("Idle is running...");
362366
loop {
363367
match ctx.shared.msg_q_out.dequeue() {
@@ -430,6 +434,7 @@ mod app {
430434
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::On {
431435
defmt::info!("Reset!");
432436
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
437+
ctx.shared.spi.lock(|s| s.reset(&mut rcc));
433438
// Returns an error if it's already scheduled (but we don't care)
434439
let _ = exit_reset::spawn_after(RESET_DURATION_MS.millis());
435440
}

neotron-bmc-pico/src/spi.rs

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use stm32f0xx_hal::{pac, prelude::*, rcc::Rcc};
77

88
pub struct SpiPeripheral<const RXC: usize, const TXC: usize> {
9+
/// Our PAC object for register access
910
dev: pac::SPI1,
1011
/// A space for bytes received from the host
1112
rx_buffer: [u8; RXC],
@@ -23,6 +24,9 @@ pub struct SpiPeripheral<const RXC: usize, const TXC: usize> {
2324
}
2425

2526
impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
27+
const MODE: embedded_hal::spi::Mode = embedded_hal::spi::MODE_0;
28+
29+
/// Construct a new driver
2630
pub fn new<SCKPIN, MISOPIN, MOSIPIN>(
2731
dev: pac::SPI1,
2832
pins: (SCKPIN, MISOPIN, MOSIPIN),
@@ -35,24 +39,53 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
3539
{
3640
defmt::info!("pclk = {}", rcc.clocks.pclk().0,);
3741

38-
let mode = embedded_hal::spi::MODE_0;
39-
4042
// Set SPI up in Controller mode. This will cause the HAL to enable the clocks and power to the IP block.
4143
// It also checks the pins are OK.
42-
let spi_controller = stm32f0xx_hal::spi::Spi::spi1(dev, pins, mode, 8_000_000u32.hz(), rcc);
44+
let spi_controller =
45+
stm32f0xx_hal::spi::Spi::spi1(dev, pins, Self::MODE, 8_000_000u32.hz(), rcc);
4346
// Now disassemble the driver so we can set it into Controller mode instead
4447
let (dev, _pins) = spi_controller.release();
4548

49+
let mut spi = SpiPeripheral {
50+
dev,
51+
rx_buffer: [0u8; RXC],
52+
rx_idx: 0,
53+
tx_buffer: [0u8; TXC],
54+
tx_idx: 0,
55+
tx_ready: 0,
56+
is_done: false,
57+
};
58+
59+
spi.config(Self::MODE);
60+
61+
// Empty the receive register
62+
while spi.has_rx_data() {
63+
let _ = spi.raw_read();
64+
}
65+
66+
// Enable the SPI device
67+
spi.stop();
68+
spi.dev.cr1.write(|w| {
69+
// Enable the peripheral
70+
w.spe().enabled();
71+
w
72+
});
73+
74+
spi
75+
}
76+
77+
/// Set up the registers
78+
fn config(&mut self, mode: embedded_hal::spi::Mode) {
4679
// We are following DM00043574, Section 30.5.1 Configuration of SPI
4780

4881
// 1. Disable SPI
49-
dev.cr1.modify(|_r, w| {
82+
self.dev.cr1.modify(|_r, w| {
5083
w.spe().disabled();
5184
w
5285
});
5386

5487
// 2. Write to the SPI_CR1 register. Apologies for the outdated terminology.
55-
dev.cr1.write(|w| {
88+
self.dev.cr1.write(|w| {
5689
// 2a. Configure the serial clock baud rate (ignored in peripheral mode)
5790
w.br().div2();
5891
// 2b. Configure the CPHA and CPOL bits.
@@ -83,7 +116,7 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
83116
});
84117

85118
// 3. Write to SPI_CR2 register
86-
dev.cr2.write(|w| {
119+
self.dev.cr2.write(|w| {
87120
// 3a. Configure the DS[3:0] bits to select the data length for the transfer (0b111 = 8-bit words).
88121
unsafe { w.ds().bits(0b111) };
89122
// 3b. Disable hard-output on the CS pin (ignored in Master mode)
@@ -109,31 +142,6 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
109142
// 4. SPI_CRCPR - not required
110143

111144
// 5. DMA registers - not required
112-
113-
let mut spi = SpiPeripheral {
114-
dev,
115-
rx_buffer: [0u8; RXC],
116-
rx_idx: 0,
117-
tx_buffer: [0u8; TXC],
118-
tx_idx: 0,
119-
tx_ready: 0,
120-
is_done: false,
121-
};
122-
123-
// Empty the receive register
124-
while spi.has_rx_data() {
125-
let _ = spi.raw_read();
126-
}
127-
128-
// Enable the SPI device
129-
spi.stop();
130-
spi.dev.cr1.write(|w| {
131-
// Enable the peripheral
132-
w.spe().enabled();
133-
w
134-
});
135-
136-
spi
137145
}
138146

139147
/// Enable the SPI peripheral (i.e. when CS goes low)
@@ -160,6 +168,41 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
160168
});
161169
}
162170

171+
/// Fully reset the SPI peripheral
172+
pub fn reset(&mut self, _rcc: &mut stm32f0xx_hal::rcc::Rcc) {
173+
self.dev.cr1.write(|w| {
174+
// Disable the peripheral
175+
w.spe().disabled();
176+
w
177+
});
178+
179+
// Reset the IP manually. This is OK as we have exclusive access to the
180+
// RCC peripheral. But sadly the RCC peripheral doesn't let us reset
181+
// anything (it assumes it can handle it all internally).
182+
let reset_reg = 0x4002_100C as *mut u32;
183+
let spi1_bit = 1 << 12;
184+
unsafe {
185+
*reset_reg |= spi1_bit;
186+
*reset_reg &= !(spi1_bit);
187+
}
188+
189+
// Reconfigure
190+
self.config(Self::MODE);
191+
192+
// Empty the receive register
193+
while self.has_rx_data() {
194+
let _ = self.raw_read();
195+
}
196+
197+
// Enable the SPI device and leave it idle
198+
self.stop();
199+
self.dev.cr1.write(|w| {
200+
// Enable the peripheral
201+
w.spe().enabled();
202+
w
203+
});
204+
}
205+
163206
/// Does the RX FIFO have any data in it?
164207
fn has_rx_data(&self) -> bool {
165208
self.dev.sr.read().rxne().is_not_empty()

0 commit comments

Comments
 (0)