-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor LPSPI with word packing, futures
Instead of using LPSPI continuous transactions, we pack the user's data into u32 words. The primitives for translating the user's data to / from the data FIFOs should help us as we consider async LPSPI drivers. And, in order to implement embedded-hal 1.0 traits, we'll need something like the dummy transmit / receive helpers, since we need to handle differing transmit / receive buffer sizes. The included unit tests don't trigger an error in Miri, and they try to simulate how we'd use the primitives in firmware. (This is an "it's not obviously wrong" test, not an "it's correct" test; help me review here.) The commit introduces spinning futures into the LPSPI driver. By combining and spinning on these futures, we can realize in-place transfers, read-only transactions, write-only transactions, etc. These implementations flush the FIFOs, allowing users to synchronize external components with LPSPI I/O. We no longer return the Busy error; we'll wait for transmit FIFO space. We also never return the NoData error, instead returning success when there's no I/O to do. Since this commit is a non-breaking change, the two errors are still available in the error enum. I'll remove them later. I'm moving the blocking SPI example into RTIC and rewriting the driver test. The tests demonstrate overlapping writes, writes with flushes, and in-place transfers with a physical loopback. There's also tests that show how word sizes and bit orders interact. I'd appreciate if folks could test these changes in their system, since it affects how the embedded-hal implementations behave. I'm only testing this commit on a 1170EVK with the new example.
- Loading branch information
Showing
7 changed files
with
750 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
//! Demonstrates a SPI device with blocking I/O. | ||
//! | ||
//! Connect SDI to SDO. The example uses the LPSPI interrupt to | ||
//! schedule transfers, and to receive data. You can observe the | ||
//! I/O with a scope / logic analyzer. The SPI CLK runs at 1MHz. | ||
//! | ||
//! Keep an eye on the defmt log to see if tests fail. | ||
#![no_std] | ||
#![no_main] | ||
|
||
#[rtic::app(device = board, peripherals = false)] | ||
mod app { | ||
|
||
use imxrt_hal as hal; | ||
|
||
const PIT_DELAY_MS: u32 = board::PIT_FREQUENCY / 1_000 * 250; | ||
|
||
#[local] | ||
struct Local { | ||
spi: board::Spi, | ||
pit: hal::pit::Pit<2>, | ||
} | ||
|
||
#[shared] | ||
struct Shared {} | ||
|
||
#[init] | ||
fn init(_: init::Context) -> (Shared, Local, init::Monotonics) { | ||
let ( | ||
board::Common { | ||
pit: (_, _, pit, _), | ||
.. | ||
}, | ||
board::Specifics { spi, .. }, | ||
) = board::new(); | ||
(Shared {}, Local { spi, pit }, init::Monotonics()) | ||
} | ||
|
||
#[idle(local = [spi, pit])] | ||
fn idle(cx: idle::Context) -> ! { | ||
let idle::LocalResources { spi, pit, .. } = cx.local; | ||
pit.set_load_timer_value(PIT_DELAY_MS); | ||
|
||
let mut delay = move || { | ||
pit.enable(); | ||
while !pit.is_elapsed() {} | ||
pit.clear_elapsed(); | ||
pit.disable(); | ||
}; | ||
|
||
loop { | ||
for _ in 0..3 { | ||
delay(); | ||
} | ||
|
||
// For studying the effects of bit order and word size. | ||
// | ||
// If you have a logic analyzer that can change its word | ||
// size and bit order, use this sequence to evaluate how | ||
// the driver packs your transfer elements. | ||
{ | ||
use eh02::blocking::spi::Write; | ||
use hal::lpspi::BitOrder::{self, *}; | ||
|
||
const BIT_ORDERS: [BitOrder; 2] = [Msb, Lsb]; | ||
|
||
const U32_WORDS: [u32; 2] = [0xDEADBEEFu32, 0xAD1CAC1D]; | ||
for bit_order in BIT_ORDERS { | ||
spi.set_bit_order(bit_order); | ||
spi.write(&U32_WORDS).unwrap(); | ||
} | ||
|
||
const U8_WORDS: [u8; 7] = [0xDEu8, 0xAD, 0xBE, 0xEF, 0xA5, 0x00, 0x1D]; | ||
for bit_order in BIT_ORDERS { | ||
spi.set_bit_order(bit_order); | ||
spi.write(&U8_WORDS).unwrap(); | ||
} | ||
|
||
const U16_WORDS: [u16; 3] = [0xDEADu16, 0xBEEF, 0xA5A5]; | ||
for bit_order in BIT_ORDERS { | ||
spi.set_bit_order(bit_order); | ||
spi.write(&U16_WORDS).unwrap(); | ||
} | ||
|
||
delay(); | ||
} | ||
|
||
// Change me to explore bit order behavors in the | ||
// remaining write / loopback transfer tests. | ||
spi.set_bit_order(hal::lpspi::BitOrder::Msb); | ||
|
||
// Make sure concatenated elements look correct on the wire. | ||
{ | ||
use eh02::blocking::spi::Write; | ||
|
||
spi.write(&[1u8, 2, 3]).unwrap(); | ||
spi.write(&[1u8, 2, 3, 4]).unwrap(); | ||
spi.write(&[1u8, 2, 3, 4, 5]).unwrap(); | ||
spi.write(&[1u8, 2, 3, 4, 5, 6]).unwrap(); | ||
spi.write(&[1u8, 2, 3, 4, 5, 6, 7]).unwrap(); | ||
|
||
spi.write(&[0x0102u16, 0x0304, 0x0506]).unwrap(); | ||
spi.write(&[0x0102u16, 0x0304, 0x0506, 0x0708]).unwrap(); | ||
spi.write(&[0x0102u16, 0x0304, 0x0506, 0x0708, 0x090A]) | ||
.unwrap(); | ||
|
||
spi.write(&[0x01020304u32, 0x05060708, 0x090A0B0C]).unwrap(); | ||
|
||
delay(); | ||
} | ||
|
||
{ | ||
use eh02::blocking::spi::{Transfer, Write}; | ||
|
||
// Change me to test different Elem sizes, buffer sizes, | ||
// bit patterns. | ||
type Elem = u8; | ||
const SENTINEL: Elem = 0x0F; | ||
const BUFFER: [Elem; 13] = [SENTINEL; 13]; | ||
|
||
// Simple loopback transfer. Easy to find with your | ||
// scope. | ||
let mut buffer = BUFFER; | ||
spi.transfer(&mut buffer).unwrap(); | ||
if buffer != BUFFER { | ||
defmt::error!("Simple transfer buffer mismatch!"); | ||
} | ||
|
||
delay(); | ||
|
||
// Adjacent loopback transfer. Look for the big | ||
// burst of data on your scope. | ||
let mut buffer = BUFFER; | ||
let mut error = false; | ||
for idx in 0u32..16 { | ||
buffer.fill(SENTINEL.rotate_right(idx)); | ||
let expected = buffer; | ||
spi.transfer(&mut buffer).unwrap(); | ||
error |= buffer != expected; | ||
} | ||
if error { | ||
defmt::error!("At least one of the bursted transfers didn't match!"); | ||
} | ||
|
||
delay(); | ||
|
||
// Simple write. | ||
let buffer = BUFFER; | ||
spi.write(&buffer).unwrap(); | ||
|
||
delay(); | ||
|
||
// Pipelined writes. Look for the burst of data | ||
// on your scope. Internally, the writes will flush, | ||
// so the delay between transfers should be about | ||
// the same as they are for the transfers. | ||
let mut buffer = BUFFER; | ||
for idx in 0..16 { | ||
buffer.fill(SENTINEL.rotate_right(idx)); | ||
spi.write(&buffer).unwrap(); | ||
} | ||
|
||
delay(); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.