cancel
Showing results for 
Search instead for 
Did you mean: 

What's wrong with my SPI setup?!

CGalv
Associate III

Hi,

I'm really struggling with the simplest possible way to use SPI with STM32F745 as a master and a MPU6000 as a slave - no interrupts, no DMA, just plain while loops and busy waiting.

Problem: I can't get it to work, the SCK signal is plain dead!

Code:

Initialization:

==========

    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
    waitCycles();
 
    // Set Alternate Function for all SPI GPIO pins
    // SS ->  PA4
    // CLK -> PA5
    // MISO -> PA6
    // MOSI -> PA7
    constexpr uint32_t kAlternateFunction = 0b10;
    constexpr uint32_t kGeneralOutput = 0b01;
    GPIOA->MODER |=
        (kAlternateFunction << GPIO_MODER_MODER4_Pos | kAlternateFunction << GPIO_MODER_MODER5_Pos |
         kAlternateFunction << GPIO_MODER_MODER6_Pos | kAlternateFunction << GPIO_MODER_MODER7_Pos);
 
    GPIOA->OTYPER = 0x0000'0000; 
 
    constexpr uint32_t kSpeed = 0b11;  // Very high speed
    GPIOA->OSPEEDR |= (kSpeed << GPIO_OSPEEDER_OSPEEDR4_Pos | kSpeed << GPIO_OSPEEDER_OSPEEDR5_Pos |
                       kSpeed << GPIO_OSPEEDER_OSPEEDR6_Pos | kSpeed << GPIO_OSPEEDER_OSPEEDR7_Pos);
    constexpr uint32_t kPullUp = 0b01;
    GPIOA->PUPDR |= (kPullUp << GPIO_PUPDR_PUPDR4_Pos | kPullUp << GPIO_PUPDR_PUPDR5_Pos |
                     kPullUp << GPIO_PUPDR_PUPDR6_Pos | kPullUp << GPIO_PUPDR_PUPDR7_Pos);
 
    constexpr uint32_t kAlternateFncSpi = 5U;
    GPIOA->AFR[0U] |=
        (kAlternateFncSpi << GPIO_AFRL_AFRL4_Pos | kAlternateFncSpi << GPIO_AFRL_AFRL5_Pos |
         kAlternateFncSpi << GPIO_AFRL_AFRL6_Pos | kAlternateFncSpi << GPIO_AFRL_AFRL7_Pos);
 
    // 2) Configure SPI_CR1 register
    SPI1->CR1 |= ((7U << SPI_CR1_BR_Pos) |  // Prescaler
                  SPI_CR1_MSTR |                         // Configure as master
                  SPI_CR1_CPOL |                         // Polarity = 1
                  SPI_CR1_SSM  |
                  SPI_CR1_CPHA);                         // Phase = 1
 
    // 3) Configure SPI_CR2 register
    constexpr uint32_t kDataSizeOption = 0b0111;  // 8-bit data size
    SPI1->CR2 |=
        ((kDataSizeOption << SPI_CR2_DS_Pos) |
        SPI_CR2_FRXTH
        );
 
    // Enable SPI
    SPI1->CR1 |= SPI_CR1_SPE;

Transfer:

=======

uint8_t transfer(const uint8_t data)
{
    while ((SPI1->SR & SPI_SR_BSY) != 0U){}
 
    // Write data
    *(volatile uint8_t *)(&SPI1->DR) = data;
 
    // Wait for transmission
    // while ((SPI1->SR & SPI_SR_TXE) == 0U){}
 
    // Wait for reception
    // while ((SPI1->SR & SPI_SR_RXNE) == 0U){}
 
    // Wait until line is not busy
    while ((SPI1->SR & SPI_SR_BSY) != 0U){}
 
    // Return read data
    const uint8_t data_received = *(volatile uint8_t* )(&SPI1->DR);
    return data_received;
}

What am I missing?

Also, can someone please explain how to use the TXE and RXNE flags? I have read the reference manual a lot of times but I really don't understand what's written.

Thanks!

7 REPLIES 7
S.Ma
Principal

TXE means you can write in DR to queue data to be sent out (and generate SCK bit clock periods as master).

Some SPI have 32 bit FIFOs both RX and TX and definition of RXNE and TXE is changeable.

As soon as the first byte is on its way out, the TXE is set to receive next due byte to transmit on the HW REGISTER.

RNXE means a full data byte SCK train of pulses is done and incoming data is ready for reading. This is the flag that tells you the transfer is done.

CGalv
Associate III

Thanks for the clarification!

Now I activated the `SSOE` flag in CR2 and I can see SCK going alive now. However, why do I need to set it? I don't want HW NSS, I want to control it myself manually toggling the GPIO.

Yes you can and this is how I use the SPI. The NSS can be gpio outside, and SPI incoming NSS level being cut, is replaced by an internal register bit which should be zero. Run in debug mode, breakpoint after init and manually toggle register bit until you crack the right combination. Should take less time than read dpec, example or app notes.

turboscrew
Senior III

I don't (yet) know about F7 series, but in older models, HW NSS doesn't work. To use SW NSS you need to set the internal NSS active (bits SSM and SSI in SPI_CR1).

As turboscrew said above, you have to set *both* SSM and SSI in CR1, as in your case, SSM set and SSI cleared causes the SPI to go immediately to slave mode. Setting SSOE avoids that by blocking the internal SS input.

JW

CGalv
Associate III

Wow, I would never have guessed that I need to set SSI = 1. The documentation says:

Bit 8 SSI: Internal slave select This bit has an effect only when the SSM bit is set. The value of this bit is forced onto the NSS pin and the I/O value of the NSS pin is ignored.

I understood that as "The value (1) is forced into the physical NSS pin", which would mean that the output NSS would always be 1, therefore never activating the slave.

Sigh too much time lost on this. Thanks all for the quick replies! I don't understand why they couldn't write it more clearly...

turboscrew
Senior III

I vaguely recall that SSM is software slave select = incoming NSS. But yes, it fools the slave mode detection to always think that the slave mode is disabled.

The device always watches the NSS, and if it sees that the NSS is in different state than what the device drives it to, it thinks some master drives it and becomes a slave with mode error. With SSM set and SSI=1, the device feeds "1" to its own "slave NSS" instead of the actual NSS-pin.