David-OConnor / stm32-hal

This library provides access to STM32 peripherals in Rust.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[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:
Screenshot 2022-08-21 at 15 00 28
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.