[STM32L476RGT6] SPI data read mismatch
badrbouslikhin opened this issue · comments
Hi David,
I've been struggling for a couple of days with an SPI issue on STM32L476RGT6.
I am trying to communicate with a TI DRV8301 slave device (16 bit word). But, when reading one of its registers, what I get in the read buffer is not what I expect/measure with the Logic Analyzer.
Here's my app. code:
// Configure I/Os
defmt::println!("Configuring I/Os");
// SPI I/Os
let mut sck1 = Pin::new(Port::A, 5, PinMode::Alt(5));
let mut miso1 = Pin::new(Port::B, 4, PinMode::Alt(5)); // Shall be connected to SDO
let mut mosi1 = Pin::new(Port::B, 5, PinMode::Alt(5)); // Shall be conntected to SDI
let mut drv8301_cs = Pin::new(Port::A, 0, PinMode::Output);
let mut en_gate = Pin::new(Port::A, 1, PinMode::Output);
// We use SPI1 for the IMU
// The limit is the max SPI speed of the ICM-42605 IMU of 24 MHz. The Limit for the St Inemo ISM330 is 10Mhz.
let drv8301_baud_div = BaudRate::Div256; // Adjust this based on MCU speed, and IMU max speed.
let drv8301_spi_cfg = SpiConfig {
mode: SpiMode::mode1(),
data_size: DataSize::D16,
fifo_reception_thresh: ReceptionThresh::D16,
..Default::default()
};
let mut spi1 = Spi::new(cx.device.SPI1, drv8301_spi_cfg, drv8301_baud_div);
defmt::println!("Setting CS high");
drv8301_cs.set_high();
defmt::println!("Setting EN_GATE low");
en_gate.set_low();
defmt::println!("Resetting DRV8301");
en_gate.set_low();
cortex_m::asm::delay(899);
en_gate.set_high();
cortex_m::asm::delay(450000);
defmt::println!("Reset done!");
let mut read_buf = [0x0000];
defmt::println!("Writing read command");
drv8301_cs.set_low();
spi1.write(&[0x8800]).ok();
drv8301_cs.set_high();
// spi1.write(&[0xFFFF]).ok();
defmt::println!("Data: {:?}", read_buf);
defmt::println!("Reading register content");
drv8301_cs.set_low();
spi1.transfer(&mut read_buf).ok();
drv8301_cs.set_high();
defmt::println!("Data: {}", read_buf);
defmt::println!("Setting CS high");
(Note that I configured DataSize::D16 and ReceptionThresh::D16).
I've modified this HAL SPI implementation to support 16 bits transactions with the following (dirty) patches:
/// Write two bytes if available, or block until it's available.
/// See L44 RM, section 40.4.9: Data transmission and reception procedures.
pub fn write_word(&mut self, word: u16) -> nb::Result<(), Error> {
let sr = self.regs.sr.read();
cfg_if! {
if #[cfg(feature = "h7")] {
let crce = sr.crce().bit_is_set();
let rdy = sr.txp().bit_is_set();
} else {
let crce = sr.crcerr().bit_is_set();
let rdy = sr.txe().bit_is_set();
}
}
if sr.ovr().bit_is_set() {
Err(nb::Error::Other(Error::Overrun))
} else if sr.modf().bit_is_set() {
Err(nb::Error::Other(Error::ModeFault))
} else if crce {
Err(nb::Error::Other(Error::Crc))
} else if rdy {
cfg_if! {
if #[cfg(feature = "h7")] {
// todo: note: H7 can support words beyond u8. (Can others too?)
unsafe { ptr::write_volatile(&self.regs.txdr as *const _ as *mut u16, word) };
// write CSTART to start a transaction in master mode
self.regs.cr1.modify(|_, w| w.cstart().started());
}
else {
unsafe { ptr::write_volatile(&self.regs.dr as *const _ as *mut u16, word) };
}
}
Ok(())
} else {
Err(nb::Error::WouldBlock)
}
}
/// Write multiple bytes on the SPI line, blocking until complete.
/// See L44 RM, section 40.4.9: Data transmission and reception procedures.
pub fn write(&mut self, words: &[u16]) -> Result<(), Error> {
for word in words {
nb::block!(self.write_word(word.clone()))?;
nb::block!(self.read())?;
}
Ok(())
}
/// Read a single byte if available, or block until it's available.
/// See L44 RM, section 40.4.9: Data transmission and reception procedures.
pub fn read_sixteen(&mut self) -> nb::Result<u16, Error> {
let sr = self.regs.sr.read();
cfg_if! {
if #[cfg(feature = "h7")] {
let crce = sr.crce().bit_is_set();
let not_empty = sr.rxp().bit_is_set();
} else {
let crce = sr.crcerr().bit_is_set();
let not_empty = sr.rxne().bit_is_set();
}
}
if sr.ovr().bit_is_set() {
Err(nb::Error::Other(Error::Overrun))
} else if sr.modf().bit_is_set() {
Err(nb::Error::Other(Error::ModeFault))
} else if crce {
Err(nb::Error::Other(Error::Crc))
} else if not_empty {
#[cfg(feature = "h7")]
// todo: note: H7 can support words beyond u8. (Can others too?)
let result = unsafe { ptr::read_volatile(&self.regs.rxdr as *const _ as *const u16) };
#[cfg(not(feature = "h7"))]
let result = unsafe { ptr::read_volatile(&self.regs.dr as *const _ as *const u16) };
Ok(result)
} else {
Err(nb::Error::WouldBlock)
}
}
/// Read multiple bytes to a buffer, blocking until complete.
/// See L44 RM, section 40.4.9: Data transmission and reception procedures.
pub fn transfer<'w>(&mut self, words: &'w mut [u16]) -> Result<(), Error> {
for word in words.iter_mut() {
nb::block!(self.write_word(word.clone()))?;
*word = nb::block!(self.read_sixteen())?;
}
Ok(())
}
Basically, I just modified write
and transfer
functions and their underlying functions to read self.regs.dr
as a u16 instead of u8.
The read and write are OK from a transaction point of view:
So I expect to read 2049
in read_buf
after transfer
, instead I get 384
:
0 Systick = 80000000
└─ minimal::app::init @ src/bin/minimal.rs:64
1 Configuring I/Os
└─ minimal::app::init @ src/bin/minimal.rs:67
2 Setting CS high
└─ minimal::app::init @ src/bin/minimal.rs:103
3 Setting EN_GATE low
└─ minimal::app::init @ src/bin/minimal.rs:105
4 Resetting DRV8301
└─ minimal::app::init @ src/bin/minimal.rs:108
5 Reset done!
└─ minimal::app::init @ src/bin/minimal.rs:113
6 Writing read command
└─ minimal::app::init @ src/bin/minimal.rs:116
7 Data: [0]
└─ minimal::app::init @ src/bin/minimal.rs:121
8 Reading register content
└─ minimal::app::init @ src/bin/minimal.rs:122
9 Data: [384]
└─ minimal::app::init @ src/bin/minimal.rs:126
10 Setting CS high
└─ minimal::app::init @ src/bin/minimal.rs:127
11 Initializing TIM1
└─ minimal::app::init @ src/bin/minimal.rs:163
12 Configuring TIM1 output channels
└─ minimal::app::init @ src/bin/minimal.rs:168
I spent hours looking at the code and the RM without success. Any idea?
Thanks.
I noticed that 2049 = 0x0801, and 384 = 0x0108. So, you are probably interpreting the data in Little Endian (default on STM32), while the bytes come in as Big Endian. I think you need to swap the bytes of result: u16
.
I noticed that 2049 = 0x0801, and 384 = 0x0108. So, you are probably interpreting the data in Little Endian (default on STM32), while the bytes come in as Big Endian. I think you need to swap the bytes of
result: u16
.
384 = 0x0180 (not 0x0108). It's too close to be a coincidence though... I'll try to check with a different register.