Skip to content

Commit 38bfbdd

Browse files
committed
Change SPI driver to use internal NSS signal instead of disabling the whole block.
1 parent c728714 commit 38bfbdd

File tree

2 files changed

+86
-83
lines changed

2 files changed

+86
-83
lines changed

neotron-bmc-pico/src/main.rs

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ mod app {
8080
Ps2Data1(u16),
8181
/// Message from SPI bus
8282
SpiRequest(neotron_bmc_protocol::Request),
83+
/// SPI CS went low (active)
84+
SpiEnable,
85+
/// SPI CS went high (inactive)
86+
SpiDisable,
8387
/// The power button was given a press
8488
PowerButtonShortPress,
8589
/// The power button was held down
@@ -281,7 +285,6 @@ mod app {
281285
let spi = neotron_bmc_pico::spi::SpiPeripheral::new(
282286
dp.SPI1,
283287
(pin_sck, pin_cipo, pin_copi),
284-
2_000_000,
285288
&mut rcc,
286289
);
287290

@@ -347,7 +350,7 @@ mod app {
347350
/// Our idle task.
348351
///
349352
/// This task is called when there is nothing else to do.
350-
#[idle(shared = [msg_q_out, msg_q_in, spi, state_dc_power_enabled, pin_dc_on, pin_sys_reset, pin_cs])]
353+
#[idle(shared = [msg_q_out, msg_q_in, spi, state_dc_power_enabled, pin_dc_on, pin_sys_reset])]
351354
fn idle(mut ctx: idle::Context) -> ! {
352355
// TODO: Get this from the VERSION static variable or from PKG_VERSION
353356
let mut register_state = RegisterState {
@@ -377,36 +380,46 @@ mod app {
377380
}
378381
Some(Message::PowerButtonLongPress) => {
379382
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::On {
380-
defmt::info!("Power button held whilst on.");
383+
defmt::info!("Power off requested!");
381384
ctx.shared
382385
.state_dc_power_enabled
383386
.lock(|r| *r = DcPowerState::Off);
384-
defmt::info!("Power off!");
387+
// Stop any SPI stuff that's currently going on (the host is about to be powered off)
388+
ctx.shared.spi.lock(|s| s.stop());
389+
// Put the host into reset
385390
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
391+
// Shut off the 5V power
386392
ctx.shared.pin_dc_on.set_low().unwrap();
387-
ctx.shared.spi.lock(|s| s.disable());
388393
// Start LED blinking again
389394
led_power_blink::spawn().unwrap();
390395
}
391396
}
392397
Some(Message::PowerButtonShortPress) => {
393398
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::Off {
394-
defmt::info!("Power button pressed whilst off. Powering up.");
395-
// Button pressed - power on system
399+
defmt::info!("Power up requested!");
400+
// Button pressed - power on system.
401+
// Step 1 - Note our new power state
396402
ctx.shared
397403
.state_dc_power_enabled
398404
.lock(|r| *r = DcPowerState::Starting);
405+
// Step 2 - Hold reset line (active) low
406+
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
407+
// Step 3 - Turn on PSU
399408
ctx.shared.pin_dc_on.set_high().unwrap();
409+
// Step 4 - Leave it in reset for a while.
400410
// TODO: Start monitoring 3.3V and 5.0V rails here
401411
// TODO: Take system out of reset when 3.3V and 5.0V are good
402-
ctx.shared.pin_sys_reset.lock(|pin| pin.set_high().unwrap());
403-
ctx.shared.spi.lock(|s| s.enable());
412+
// Returns an error if it's already scheduled (but we don't care)
413+
let _ = exit_reset::spawn_after(RESET_DURATION_MS.millis());
404414
}
405415
}
406416
Some(Message::PowerButtonRelease) => {
407417
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::Starting {
408418
defmt::info!("Power button released.");
409-
// Button released after power on
419+
// Button released after power on. Change the power
420+
// state machine t "On". We were in 'Starting' to ignore
421+
// any further button events until the button had been
422+
// released.
410423
ctx.shared
411424
.state_dc_power_enabled
412425
.lock(|r| *r = DcPowerState::On);
@@ -417,10 +430,23 @@ mod app {
417430
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::On {
418431
defmt::info!("Reset!");
419432
ctx.shared.pin_sys_reset.lock(|pin| pin.set_low().unwrap());
420-
// Returns an error if it's already scheduled
433+
// Returns an error if it's already scheduled (but we don't care)
421434
let _ = exit_reset::spawn_after(RESET_DURATION_MS.millis());
422435
}
423436
}
437+
Some(Message::SpiEnable) => {
438+
if ctx.shared.state_dc_power_enabled.lock(|r| *r) != DcPowerState::Off {
439+
// Turn on the SPI peripheral
440+
ctx.shared.spi.lock(|s| s.start());
441+
} else {
442+
// Ignore message - it'll be the CS line being pulled low when the host is powered off
443+
defmt::info!("Ignoring spurious CS low");
444+
}
445+
}
446+
Some(Message::SpiDisable) => {
447+
// Turn off the SPI peripheral. Don't need to check power state for this.
448+
ctx.shared.spi.lock(|s| s.stop());
449+
}
424450
Some(Message::SpiRequest(req)) => {
425451
process_command(req, &mut register_state, |rsp| {
426452
ctx.shared.spi.lock(|spi| {
@@ -487,7 +513,7 @@ mod app {
487513
#[task(
488514
binds = EXTI4_15,
489515
priority = 4,
490-
shared = [ps2_clk0, msg_q_in, ps2_dat0, exti, spi, pin_cs, kb_decoder],
516+
shared = [ps2_clk0, msg_q_in, ps2_dat0, exti, pin_cs, kb_decoder],
491517
)]
492518
fn exti4_15_interrupt(mut ctx: exti4_15_interrupt::Context) {
493519
let pr = ctx.shared.exti.pr.read();
@@ -511,12 +537,15 @@ mod app {
511537
}
512538

513539
if pr.pr4().bit_is_set() {
514-
if ctx.shared.pin_cs.lock(|pin| pin.is_low().unwrap()) {
515-
// If incoming Chip Select is low, turn on the SPI engine
516-
ctx.shared.spi.lock(|s| s.start());
540+
let msg = if ctx.shared.pin_cs.lock(|pin| pin.is_low().unwrap()) {
541+
// If incoming Chip Select is low, tell the main thread to turn on the SPI engine
542+
Message::SpiEnable
517543
} else {
518-
// If incoming Chip Select is high, turn off the SPI engine
519-
ctx.shared.spi.lock(|s| s.stop());
544+
// If incoming Chip Select is high, tell the main thread to turn off the SPI engine
545+
Message::SpiDisable
546+
};
547+
if ctx.shared.msg_q_in.lock(|q| q.enqueue(msg)).is_err() {
548+
panic!("queue full");
520549
}
521550
// Clear the pending flag for this pin
522551
ctx.shared.exti.pr.write(|w| w.pr4().set_bit());
@@ -663,7 +692,8 @@ mod app {
663692
#[task(shared = [pin_sys_reset, state_dc_power_enabled])]
664693
fn exit_reset(mut ctx: exit_reset::Context) {
665694
defmt::debug!("End reset");
666-
if ctx.shared.state_dc_power_enabled.lock(|r| *r) == DcPowerState::On {
695+
if ctx.shared.state_dc_power_enabled.lock(|r| *r) != DcPowerState::Off {
696+
// Raising the reset line takes the rest of the system out of reset
667697
ctx.shared.pin_sys_reset.lock(|pin| pin.set_high().unwrap());
668698
}
669699
}

neotron-bmc-pico/src/spi.rs

Lines changed: 38 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,29 +17,23 @@ pub struct SpiPeripheral<const RXC: usize, const TXC: usize> {
1717
tx_idx: usize,
1818
/// How many bytes are loaded into the TX buffer
1919
tx_ready: usize,
20-
/// Has the RX been processed?
20+
/// Has the RX been processed? If so, lie and say we don't have any data
21+
/// (otherwise we process incoming messages multiple times).
2122
is_done: bool,
22-
/// Are we enabled? If not, start and stop won't work.
23-
enabled: bool,
2423
}
2524

2625
impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
2726
pub fn new<SCKPIN, MISOPIN, MOSIPIN>(
2827
dev: pac::SPI1,
2928
pins: (SCKPIN, MISOPIN, MOSIPIN),
30-
speed_hz: u32,
3129
rcc: &mut Rcc,
3230
) -> SpiPeripheral<RXC, TXC>
3331
where
3432
SCKPIN: stm32f0xx_hal::spi::SckPin<pac::SPI1>,
3533
MISOPIN: stm32f0xx_hal::spi::MisoPin<pac::SPI1>,
3634
MOSIPIN: stm32f0xx_hal::spi::MosiPin<pac::SPI1>,
3735
{
38-
defmt::info!(
39-
"pclk = {}, incoming spi_clock = {}",
40-
rcc.clocks.pclk().0,
41-
speed_hz
42-
);
36+
defmt::info!("pclk = {}", rcc.clocks.pclk().0,);
4337

4438
let mode = embedded_hal::spi::MODE_0;
4539

@@ -80,29 +74,33 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
8074
w.lsbfirst().clear_bit();
8175
// 2e. Configure the CRCL and CRCEN bits if CRC is needed (it is not)
8276
w.crcen().disabled();
83-
// 2f. Turn off soft-slave-management (SSM) and slave-select-internal (SSI)
84-
w.ssm().disabled();
85-
w.ssi().slave_selected();
77+
// 2f. Turn on soft-slave-management (SSM) (we control the NSS signal with the SSI bit).
78+
w.ssm().enabled();
79+
w.ssi().slave_not_selected();
8680
// 2g. Set the Master bit low for slave mode
8781
w.mstr().slave();
8882
w
8983
});
9084

9185
// 3. Write to SPI_CR2 register
9286
dev.cr2.write(|w| {
93-
// 3a. Configure the DS[3:0] bits to select the data length for the transfer.
87+
// 3a. Configure the DS[3:0] bits to select the data length for the transfer (0b111 = 8-bit words).
9488
unsafe { w.ds().bits(0b111) };
95-
// 3b. Disable hard-output on the CS pin
89+
// 3b. Disable hard-output on the CS pin (ignored in Master mode)
9690
w.ssoe().disabled();
9791
// 3c. Frame Format
9892
w.frf().motorola();
9993
// 3d. Set NSSP bit if required (we don't want NSS Pulse mode)
10094
w.nssp().no_pulse();
101-
// 3e. Configure the FIFO RX Threshold to 1/4 FIFO
95+
// 3e. Configure the FIFO RX Threshold to 1/4 FIFO (8 bits)
10296
w.frxth().quarter();
103-
// 3f. LDMA_TX and LDMA_RX for DMA mode - not used
104-
// Extra: Turn on RX Not Empty Interrupt Enable
105-
w.rxneie().set_bit();
97+
// 3f. Disable DMA mode
98+
w.txdmaen().disabled();
99+
w.rxdmaen().disabled();
100+
// Extra: Turn on RX, TX and Error interrupts
101+
w.rxneie().not_masked();
102+
w.txeie().not_masked();
103+
w.errie().not_masked();
106104
w
107105
});
108106

@@ -118,7 +116,6 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
118116
tx_idx: 0,
119117
tx_ready: 0,
120118
is_done: false,
121-
enabled: false,
122119
};
123120

124121
// Empty the receive register
@@ -129,23 +126,8 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
129126
spi
130127
}
131128

132-
/// Allow the [`Self::start`] and [`Self::stop`] functions to actually work.
133-
pub fn enable(&mut self) {
134-
self.enabled = true;
135-
}
136-
137-
/// Prevent the [`Self::start`] and [`Self::stop`] functions from working.
138-
pub fn disable(&mut self) {
139-
self.stop();
140-
self.enabled = false;
141-
}
142-
143-
/// Enable the SPI peripheral (i.e. when CS is low)
129+
/// Enable the SPI peripheral (i.e. when CS goes low)
144130
pub fn start(&mut self) {
145-
if !self.enabled {
146-
return;
147-
}
148-
149131
self.rx_idx = 0;
150132
self.tx_idx = 0;
151133
self.tx_ready = 0;
@@ -155,37 +137,18 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
155137
let _ = self.raw_read();
156138
}
157139
self.dev.cr1.modify(|_r, w| {
158-
w.spe().enabled();
140+
w.ssi().slave_selected();
159141
w
160142
});
161143
// Load our dummy byte (our TX FIFO will send this then repeat it whilst
162144
// it underflows during the receive phase).
163145
self.raw_write(0xFF);
164-
// Get an IRQ when there's RX data available
165-
self.enable_rxne_irq();
166146
}
167147

168-
/// Disable the SPI peripheral (i.e. when CS is high)
148+
/// Disable the SPI peripheral (i.e. when CS goes high)
169149
pub fn stop(&mut self) {
170-
self.disable_rxne_irq();
171150
self.dev.cr1.modify(|_r, w| {
172-
w.spe().disabled();
173-
w
174-
});
175-
}
176-
177-
/// Enable RX Not Empty interrupt
178-
fn enable_rxne_irq(&mut self) {
179-
self.dev.cr2.modify(|_r, w| {
180-
w.rxneie().set_bit();
181-
w
182-
});
183-
}
184-
185-
/// Disable RX Not Empty interrupt
186-
fn disable_rxne_irq(&mut self) {
187-
self.dev.cr2.modify(|_r, w| {
188-
w.rxneie().clear_bit();
151+
w.ssi().slave_not_selected();
189152
w
190153
});
191154
}
@@ -197,13 +160,13 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
197160

198161
fn raw_read(&mut self) -> u8 {
199162
// PAC only supports 16-bit read, but that pops two bytes off the FIFO.
200-
// So force a 16-bit read.
163+
// So force an 8-bit read.
201164
unsafe { core::ptr::read_volatile(&self.dev.dr as *const _ as *const u8) }
202165
}
203166

204167
fn raw_write(&mut self, data: u8) {
205-
// PAC only supports 16-bit read, but that pops two bytes off the FIFO.
206-
// So force a 16-bit read.
168+
// PAC only supports 16-bit read, but that pushes two bytes onto the FIFO.
169+
// So force an 8-bit write.
207170
unsafe { core::ptr::write_volatile(&self.dev.dr as *const _ as *mut u8, data) }
208171
}
209172

@@ -224,6 +187,10 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
224187

225188
pub fn handle_isr(&mut self) {
226189
let irq_status = self.dev.sr.read();
190+
if irq_status.fre().is_error() || irq_status.ovr().is_overrun() {
191+
// Handle errors!?
192+
defmt::info!("SPI sr=0b{:08b}", irq_status.bits());
193+
}
227194
if irq_status.rxne().is_not_empty() {
228195
self.rx_isr();
229196
}
@@ -244,9 +211,10 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
244211
/// Call this in the TXEIE interrupt. It will load the SPI FIFO with some
245212
/// data, either from `tx_buffer` or a padding byte.
246213
fn tx_isr(&mut self) {
247-
if (self.tx_idx < self.tx_ready) && (self.tx_idx < self.tx_buffer.len()) {
248-
// We have some data yet to send
249-
let next_tx = self.tx_buffer[self.tx_idx];
214+
if self.tx_idx < self.tx_ready {
215+
// We have some data yet to send. This is safe as long as we do the
216+
// bounds check when we set `self.tx_ready`.
217+
let next_tx = unsafe { *self.tx_buffer.get_unchecked(self.tx_idx) };
250218
self.raw_write(next_tx);
251219
self.tx_idx += 1;
252220
} else {
@@ -262,12 +230,15 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
262230
self.tx_ready = 0;
263231
self.tx_idx = 0;
264232
if data.len() > TXC {
265-
// Too much data
233+
// Too much data. This check is important for safety in
234+
// [`Self::tx_isr`].
266235
return Err(TXC);
267236
}
268237
for (inc, space) in data.iter().zip(self.tx_buffer.iter_mut()) {
269238
*space = *inc;
270239
}
240+
// We must never set this to be longer than `TXC` as we do an unchecked
241+
// read from `self.tx_buffer` in [`Self::tx_isr`].
271242
self.tx_ready = data.len();
272243
Ok(())
273244
}
@@ -284,7 +255,9 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
284255

285256
match message.render_to_buffer(&mut self.tx_buffer) {
286257
Ok(n) => {
287-
self.tx_ready = n;
258+
// We must never set this to be longer than `TXC` as we do an
259+
// unchecked read from `self.tx_buffer` in [`Self::tx_isr`].
260+
self.tx_ready = n.min(TXC);
288261
Ok(())
289262
}
290263
Err(_) => Err(()),

0 commit comments

Comments
 (0)