Skip to content

Commit 829ffd6

Browse files
techmccatusbalbin
authored andcommitted
fix (spi): fix spi, add hal-1 example
Fixed visibility of methods set_bidi and set_tx_only, which were not supposed to be public Fixed SPI reads never happening at all because of a broken pointer cast
1 parent ee7c010 commit 829ffd6

File tree

2 files changed

+113
-1
lines changed

2 files changed

+113
-1
lines changed

examples/spi-hal-one.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
// This example is to test the SPI without any external devices,
2+
// it writes to the MOSI line and logs whatever is received on the MISO line
3+
//
4+
// MISO and MOSI should be connected to make the assertions work
5+
6+
#![no_main]
7+
#![no_std]
8+
9+
use crate::hal::{
10+
prelude::*,
11+
pwr::PwrExt,
12+
rcc::Config,
13+
spi,
14+
stm32::Peripherals,
15+
time::RateExtU32,
16+
};
17+
18+
use cortex_m_rt::entry;
19+
use stm32g4xx_hal as hal;
20+
21+
#[macro_use]
22+
mod utils;
23+
use utils::logger::info;
24+
25+
#[entry]
26+
fn main() -> ! {
27+
utils::logger::init();
28+
info!("Logger init");
29+
30+
let dp = Peripherals::take().unwrap();
31+
let rcc = dp.RCC.constrain();
32+
let pwr = dp.PWR.constrain().freeze();
33+
let mut rcc = rcc.freeze(
34+
Config::hsi(),
35+
pwr
36+
);
37+
38+
// let gpioa = dp.GPIOA.split(&mut rcc);
39+
let gpioa = dp.GPIOA.split(&mut rcc);
40+
let sclk = gpioa.pa5.into_alternate();
41+
let miso = gpioa.pa6.into_alternate();
42+
let mosi = gpioa.pa7.into_alternate();
43+
44+
// 1/8 SPI/SysClk ratio seems to be the upper limit for continuous transmission
45+
// one byte at a time
46+
// 1/4 works well when writing two packed bytes at once
47+
// At 1/2 the clock stays on for ~80% of the time
48+
let mut spi = dp
49+
.SPI1
50+
.spi((sclk, miso, mosi), spi::MODE_0, 8.MHz(), &mut rcc);
51+
let mut cs = gpioa.pa8.into_push_pull_output();
52+
cs.set_high();
53+
54+
// Odd number of bits to test packing edge case
55+
const MESSAGE: &[u8] = "Hello world, but longer".as_bytes();
56+
cs.set_low();
57+
spi.write(MESSAGE).unwrap();
58+
SpiBus::<u8>::flush(&mut spi).unwrap();
59+
cs.set_high();
60+
61+
let received = &mut [0u8; MESSAGE.len()];
62+
spi.read(received).unwrap();
63+
64+
cortex_m::asm::delay(100);
65+
cs.set_low();
66+
spi.transfer(received, MESSAGE).unwrap();
67+
// downside of having 8 and 16 bit impls on the same struct is you have to specify which flush
68+
// impl to call, although internally they call the same function
69+
SpiBus::<u8>::flush(&mut spi).unwrap();
70+
cs.set_high();
71+
72+
info!("Received {:?}", core::str::from_utf8(received).ok());
73+
assert_eq!(MESSAGE, received);
74+
75+
cs.set_low();
76+
spi.transfer_in_place(received).unwrap();
77+
SpiBus::<u8>::flush(&mut spi).unwrap();
78+
cs.set_high();
79+
80+
info!("Received {:?}", core::str::from_utf8(received).ok());
81+
assert_eq!(MESSAGE, received);
82+
83+
// Switch between 8 and 16 bit frames on the fly
84+
const TX_16B: &[u16] = &[0xf00f, 0xfeef, 0xfaaf];
85+
let rx_16b = &mut [0u16; TX_16B.len()];
86+
87+
cs.set_low();
88+
spi.transfer(rx_16b, TX_16B).unwrap();
89+
// internally works the same as SpiBus::<u8>::flush()
90+
SpiBus::<u16>::flush(&mut spi).unwrap();
91+
cs.set_high();
92+
93+
info!("Received {:?}", rx_16b);
94+
assert_eq!(TX_16B, rx_16b);
95+
96+
cs.set_low();
97+
spi.transfer_in_place(rx_16b).unwrap();
98+
SpiBus::<u16>::flush(&mut spi).unwrap();
99+
cs.set_high();
100+
101+
info!("Received {:?}", rx_16b);
102+
assert_eq!(TX_16B, rx_16b);
103+
104+
loop {
105+
cortex_m::asm::nop();
106+
}
107+
}

src/spi.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,20 +186,24 @@ impl<SPI: Instance, PINS> Spi<SPI, PINS> {
186186
fn read_unchecked<W: FrameSize>(&mut self) -> W {
187187
// NOTE(read_volatile) read only 1 byte (the svd2rust API only allows
188188
// reading a half-word)
189-
unsafe { ptr::read_volatile(&self.spi.dr() as *const _ as *const W) }
189+
unsafe { ptr::read_volatile(self.spi.dr().as_ptr() as *const W) }
190190
}
191191
#[inline]
192192
fn write_unchecked<W: FrameSize>(&mut self, word: W) {
193193
// NOTE(write_volatile) see note above
194194
let dr = self.spi.dr().as_ptr() as *mut W;
195195
unsafe { ptr::write_volatile(dr, word) };
196196
}
197+
/// disables rx
197198
#[inline]
198199
pub fn set_tx_only(&mut self) {
200+
// very counter-intuitively, setting spi bidi mode on disables rx while transmitting
201+
// it's made for half-duplex spi, which they called bidirectional in the manual
199202
self.spi
200203
.cr1()
201204
.modify(|_, w| w.bidimode().bidirectional().bidioe().output_enabled());
202205
}
206+
/// re-enables rx if it was disabled
203207
#[inline]
204208
pub fn set_bidi(&mut self) {
205209
self.spi
@@ -314,6 +318,7 @@ impl<SPI: Instance, PINS: Pins<SPI>> embedded_hal::spi::SpiBus<u8> for Spi<SPI,
314318
self.set_bidi();
315319

316320
let half_len = len / 2;
321+
// leftover write/read operation because bytes are not a multiple of 2
317322
let pair_left = len % 2;
318323

319324
// prefill write fifo so that the clock doen't stop while fetch the read byte

0 commit comments

Comments
 (0)