Skip to content

Commit a2916a6

Browse files
SPI comms update.
) Calculate the CRC on the fly to reduce latency. ) Tell the SPI driver how many bytes to expect, so the main loop only has to lock the SPI object when that number of bytes has arrived.
1 parent c487a5b commit a2916a6

File tree

3 files changed

+169
-100
lines changed

3 files changed

+169
-100
lines changed

neotron-bmc-pico/src/main.rs

Lines changed: 42 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,8 @@ mod app {
7878
Ps2Data0(u16),
7979
/// Word from PS/2 port 1
8080
Ps2Data1(u16),
81-
/// Message from SPI bus
82-
SpiRequest(neotron_bmc_protocol::Request),
81+
/// SPI driver has a Request for us
82+
SpiRx,
8383
/// SPI CS went low (active)
8484
SpiEnable,
8585
/// SPI CS went high (inactive)
@@ -444,8 +444,9 @@ mod app {
444444
}
445445
Some(Message::SpiEnable) => {
446446
if ctx.shared.state_dc_power_enabled.lock(|r| *r) != DcPowerState::Off {
447-
// Turn on the SPI peripheral
448-
ctx.shared.spi.lock(|s| s.start());
447+
// Turn on the SPI peripheral and expect four bytes (the
448+
// length of a Request).
449+
ctx.shared.spi.lock(|s| s.start(4));
449450
} else {
450451
// Ignore message - it'll be the CS line being pulled low when the host is powered off
451452
defmt::info!("Ignoring spurious CS low");
@@ -456,12 +457,39 @@ mod app {
456457
ctx.shared.spi.lock(|s| s.stop());
457458
defmt::trace!("SPI Disable");
458459
}
459-
Some(Message::SpiRequest(req)) => {
460-
process_command(req, &mut register_state, |rsp| {
461-
ctx.shared.spi.lock(|spi| {
462-
spi.set_transmit_sendable(rsp).unwrap();
463-
});
460+
Some(Message::SpiRx) => {
461+
defmt::trace!("SpiRx");
462+
// Look for something in the SPI bytes received buffer:
463+
let mut req = None;
464+
ctx.shared.spi.lock(|spi| {
465+
if let Some((data, crc)) = spi.get_received() {
466+
use proto::Receivable;
467+
match proto::Request::from_bytes_with_crc(data, crc) {
468+
Ok(inner_req) => {
469+
defmt::debug!("Got packet");
470+
req = Some(inner_req);
471+
}
472+
Err(proto::Error::BadLength) => {
473+
// This is a programming bug. We said
474+
// start(4) earlier, so there should be four
475+
// bytes here.
476+
panic!("Wanted 4, got {}", data.len());
477+
}
478+
Err(e) => {
479+
defmt::warn!("Bad Req {:?} ({=[u8]:x}", e, data);
480+
}
481+
}
482+
}
464483
});
484+
485+
// If we got a valid message, queue it so we can look at it next time around
486+
if let Some(req) = req {
487+
process_command(req, &mut register_state, |rsp| {
488+
ctx.shared.spi.lock(|spi| {
489+
spi.set_transmit_sendable(rsp).unwrap();
490+
});
491+
});
492+
}
465493
}
466494
Some(Message::UartByte(rx_byte)) => {
467495
defmt::info!("UART RX {:?}", rx_byte);
@@ -472,44 +500,6 @@ mod app {
472500
// No messages
473501
}
474502
}
475-
476-
// Look for something in the SPI bytes received buffer:
477-
let mut req = None;
478-
ctx.shared.spi.lock(|spi| {
479-
let mut mark_done = false;
480-
if let Some(data) = spi.get_received() {
481-
use proto::Receivable;
482-
match proto::Request::from_bytes(data) {
483-
Ok(inner_req) => {
484-
mark_done = true;
485-
req = Some(inner_req);
486-
}
487-
Err(proto::Error::BadLength) => {
488-
// Need more data
489-
}
490-
Err(e) => {
491-
defmt::warn!("Bad Req {:?} ({=[u8]:x}", e, data);
492-
mark_done = true;
493-
}
494-
}
495-
}
496-
if mark_done {
497-
// Couldn't do this whilst holding the `data` ref.
498-
spi.mark_done();
499-
}
500-
});
501-
502-
// If we got a valid message, queue it so we can look at it next time around
503-
if let Some(req) = req {
504-
if ctx
505-
.shared
506-
.msg_q_in
507-
.lock(|q| q.enqueue(Message::SpiRequest(req)))
508-
.is_err()
509-
{
510-
panic!("Q full!");
511-
}
512-
}
513503
// TODO: Read ADC for 3.3V and 5.0V rails and check good
514504
}
515505
}
@@ -580,11 +570,12 @@ mod app {
580570
///
581571
/// It fires whenever there is new data received on SPI1. We should flag to the host
582572
/// that data is available.
583-
#[task(binds = SPI1, shared = [spi])]
573+
#[task(binds = SPI1, shared = [spi, msg_q_in])]
584574
fn spi1_interrupt(mut ctx: spi1_interrupt::Context) {
585-
ctx.shared.spi.lock(|spi| {
586-
spi.handle_isr();
587-
});
575+
let has_message = ctx.shared.spi.lock(|spi| spi.handle_isr());
576+
if has_message {
577+
let _ = ctx.shared.msg_q_in.lock(|q| q.enqueue(Message::SpiRx));
578+
}
588579
}
589580

590581
/// This is the LED blink task.

neotron-bmc-pico/src/spi.rs

Lines changed: 65 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@ pub struct SpiPeripheral<const RXC: usize, const TXC: usize> {
1212
rx_buffer: [u8; RXC],
1313
/// How many bytes have been received?
1414
rx_idx: usize,
15+
/// How many bytes do we want?
16+
rx_want: usize,
1517
/// A space for data we're about to send
1618
tx_buffer: [u8; TXC],
1719
/// How many bytes have been played from the TX buffer
1820
tx_idx: usize,
1921
/// How many bytes are loaded into the TX buffer
2022
tx_ready: usize,
21-
/// Has the RX been processed? If so, lie and say we don't have any data
22-
/// (otherwise we process incoming messages multiple times).
23-
is_done: bool,
23+
/// The in-progress RX CRC
24+
rx_crc: neotron_bmc_protocol::CrcCalc,
2425
}
2526

2627
impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
@@ -50,10 +51,11 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
5051
dev,
5152
rx_buffer: [0u8; RXC],
5253
rx_idx: 0,
54+
rx_want: 0,
5355
tx_buffer: [0u8; TXC],
5456
tx_idx: 0,
5557
tx_ready: 0,
56-
is_done: false,
58+
rx_crc: neotron_bmc_protocol::CrcCalc::new(),
5759
};
5860

5961
spi.config(Self::MODE);
@@ -130,12 +132,11 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
130132
// 3f. Disable DMA mode
131133
w.txdmaen().disabled();
132134
w.rxdmaen().disabled();
133-
// Extra: Turn on RX and Error interrupts, but not TX. The TX
134-
// interrupt is turned off because we deliberately underflow the
135-
// FIFO during the receive phase of the transaction.
136-
w.rxneie().not_masked();
135+
// Extra: Turn on RX and Error interrupts, but not TX. We swap
136+
// interrupts once the read phase is complete.
137+
w.rxneie().masked();
137138
w.txeie().masked();
138-
w.errie().not_masked();
139+
w.errie().masked();
139140
w
140141
});
141142

@@ -144,16 +145,30 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
144145
// 5. DMA registers - not required
145146
}
146147

147-
/// Enable the SPI peripheral (i.e. when CS goes low)
148-
pub fn start(&mut self) {
148+
/// Enable the SPI peripheral (i.e. when CS goes low).
149+
///
150+
/// We tell it how many bytes we are expecting, so it knows when to update
151+
/// the main thread.
152+
pub fn start(&mut self, num_bytes: usize) {
153+
if num_bytes > RXC {
154+
panic!("Read too large");
155+
}
149156
self.rx_idx = 0;
157+
self.rx_want = num_bytes as usize;
150158
self.tx_idx = 0;
151159
self.tx_ready = 0;
152-
self.is_done = false;
160+
self.rx_crc.reset();
153161
// Empty the receive register
154162
while self.has_rx_data() {
155163
let _ = self.raw_read();
156164
}
165+
// Turn on RX interrupt; turn off TX interrupt
166+
self.dev.cr2.write(|w| {
167+
w.rxneie().not_masked();
168+
w.txeie().masked();
169+
w
170+
});
171+
// Tell the SPI engine it has a chip-select
157172
self.dev.cr1.modify(|_r, w| {
158173
w.ssi().slave_selected();
159174
w
@@ -221,37 +236,46 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
221236
}
222237

223238
/// Get a slice of data received so far.
224-
pub fn get_received(&self) -> Option<&[u8]> {
225-
if !self.is_done {
226-
Some(&self.rx_buffer[0..self.rx_idx])
227-
} else {
228-
None
229-
}
230-
}
231-
232-
/// Mark the RX as processed, so we don't do it again (until the SPI is
233-
/// disabled and re-enabled by the next chip select).
234-
pub fn mark_done(&mut self) {
235-
self.is_done = true;
239+
pub fn get_received(&self) -> Option<(&[u8], u8)> {
240+
Some(((&self.rx_buffer[0..self.rx_idx]), self.rx_crc.get()))
236241
}
237242

238-
pub fn handle_isr(&mut self) {
243+
/// Call this when the SPI peripheral interrupt fires.
244+
///
245+
/// It will handle incoming bytes and/or outgoing bytes, depending on what
246+
/// phase we are in.
247+
pub fn handle_isr(&mut self) -> bool {
248+
let mut have_packet = false;
239249
let irq_status = self.dev.sr.read();
240250
if irq_status.rxne().is_not_empty() {
241-
self.rx_isr();
251+
if self.rx_isr() {
252+
// We've got enough, turn the RX interrupt off. Everything else
253+
// we receive is going to be garbage.
254+
self.dev.cr2.write(|w| {
255+
w.rxneie().masked();
256+
w
257+
});
258+
have_packet = true;
259+
}
242260
}
243261
if irq_status.txe().is_empty() {
244262
self.tx_isr();
245263
}
264+
have_packet
246265
}
247266

248267
/// Try and read from the SPI FIFO
249-
fn rx_isr(&mut self) {
250-
let cmd = self.raw_read();
268+
fn rx_isr(&mut self) -> bool {
269+
let byte = self.raw_read();
270+
if self.rx_want == 0 {
271+
panic!("unwanted data 0x{:02x}", byte);
272+
}
251273
if self.rx_idx < self.rx_buffer.len() {
252-
self.rx_buffer[self.rx_idx] = cmd;
274+
self.rx_buffer[self.rx_idx] = byte;
275+
self.rx_crc.add(byte);
253276
self.rx_idx += 1;
254277
}
278+
self.rx_idx == self.rx_want
255279
}
256280

257281
/// Call this in the TXEIE interrupt. It will load the SPI FIFO with some
@@ -264,7 +288,8 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
264288
self.raw_write(next_tx);
265289
self.tx_idx += 1;
266290
} else {
267-
// No data - send nothing
291+
// No data - send 0x00
292+
self.raw_write(0x00);
268293
}
269294
}
270295

@@ -285,6 +310,11 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
285310
// We must never set this to be longer than `TXC` as we do an unchecked
286311
// read from `self.tx_buffer` in [`Self::tx_isr`].
287312
self.tx_ready = data.len();
313+
// Turn on the TX interrupt
314+
self.dev.cr2.write(|w| {
315+
w.txeie().not_masked();
316+
w
317+
});
288318
Ok(())
289319
}
290320

@@ -303,6 +333,11 @@ impl<const RXC: usize, const TXC: usize> SpiPeripheral<RXC, TXC> {
303333
// We must never set this to be longer than `TXC` as we do an
304334
// unchecked read from `self.tx_buffer` in [`Self::tx_isr`].
305335
self.tx_ready = n.min(TXC);
336+
// Turn on the TX interrupt
337+
self.dev.cr2.write(|w| {
338+
w.txeie().not_masked();
339+
w
340+
});
306341
Ok(())
307342
}
308343
Err(_) => Err(()),

0 commit comments

Comments
 (0)