Search code examples
stm32spi

STM32 SPI RXNE Flag Will Not Change


I am using the STM32F303RE microcontroller in a Nucleo board.

datasheet

reference manual

After trying to write a program to talk to an SPI device I found that it wasn't working, so I stripped the code down to a minimum implementation that should do nothing but receive a single byte. Here is what I have:

volatile char data = 'A';

int main(void) {
  HAL_Init();
  SystemClock_Config();

  RCC->AHBENR |= RCC_AHBENR_GPIOBEN;    // Enable GPIOB clock
  RCC->APB1ENR |= RCC_APB1ENR_SPI2EN;   // Enable SPI2 clock

  // PB12 Alternate Function mode [10]
  GPIOB->MODER &= ~GPIO_MODER_MODER12_0;
  GPIOB->MODER |= GPIO_MODER_MODER12_1;

  // PB13 Alternate Function mode [10]
  GPIOB->MODER &= ~GPIO_MODER_MODER13_0;
  GPIOB->MODER |= GPIO_MODER_MODER13_1;

  // PB14 Alternate Function mode [10]
  GPIOB->MODER &= ~GPIO_MODER_MODER14_0;
  GPIOB->MODER |= GPIO_MODER_MODER14_1;

  // PB15 Alternate Function mode [10]
  GPIOB->MODER &= ~GPIO_MODER_MODER15_0;
  GPIOB->MODER |= GPIO_MODER_MODER15_1;

  // PB12 High Speed [11]
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR12_0;
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR12_1;

  // PB13 High Speed [11]
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR13_0;
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR13_1;

  // PB14 High Speed [11]
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR14_0;
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR14_1;

  // PB15 High Speed [11]
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR15_0;
  GPIOB->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR15_1;

  // Alternate function 5 [0101]
  GPIOB->AFR[1] = 0;                                // Reset register
  GPIOB->AFR[1] = 0b0101 << GPIO_AFRH_AFRH4_Pos;    // PB12
  GPIOB->AFR[1] = 0b0101 << GPIO_AFRH_AFRH5_Pos;    // PB13
  GPIOB->AFR[1] = 0b0101 << GPIO_AFRH_AFRH6_Pos;    // PB14
  GPIOB->AFR[1] = 0b0101 << GPIO_AFRH_AFRH7_Pos;    // PB15

  SPI2->CR1 |= SPI_CR1_MSTR;    // Master Mode
  SPI2->CR2 |= SPI_CR2_FRXTH;   // 8-bit reception threshold

  // 8-bit data length [0111]
  SPI2->CR2 &= ~SPI_CR2_DS_Msk;
  SPI2->CR2 |= 0b0111 << SPI_CR2_DS_Pos;

  SPI2->CR2 |= SPI_CR2_SSOE;  // SS output enable
  SPI2->CR1 |= SPI_CR1_SPE;   // SPI enable

  while (!(SPI2->SR & SPI_SR_TXE));   // Wait for TX

  SPI2->DR = 'T';                     // Send some byte

  while (!(SPI2->SR & SPI_SR_RXNE));  // Wait for RX (Hangs)

  data = SPI2->DR;                    // Receive byte

  while (1) {

  }
}

The result is that the line while (!(SPI2->SR & SPI_SR_RXNE)); hangs because RXNE is never set.

Note that I am not asking a duplicate of this question, because I have already tried implementing the given solutions, namely by setting the SPI_CR2_FRXTH and SPI_CR2_SSOE bits, ensuring that the proper clocks are enabled, as well as ensuring that SPI2 is enabled AFTER all the other registers have been set.


Solution

  •   // Alternate function 5 [0101]
      GPIOB->AFR[1] = 0;                                // Reset register
      GPIOB->AFR[1] = 0b0101 << GPIO_AFRH_AFRH4_Pos;    // PB12
      GPIOB->AFR[1] = 0b0101 << GPIO_AFRH_AFRH5_Pos;    // PB13
      GPIOB->AFR[1] = 0b0101 << GPIO_AFRH_AFRH6_Pos;    // PB14
      GPIOB->AFR[1] = 0b0101 << GPIO_AFRH_AFRH7_Pos;    // PB15
    

    This is certainly wrong: each subsequent assignment overwrites all previous ones, so only the value for PB15 is set.

    Generally:

    • where do you have this programming style from, gradually adding bits/bitfields to a single register? Calculate the value of the whole register at once and write, e.g.

       GPIOB->AFR[1] = 0
         | (0b0101 << GPIO_AFRH_AFRH4_Pos) // PB12
         | (0b0101 << GPIO_AFRH_AFRH5_Pos) // PB13      
         | (0b0101 << GPIO_AFRH_AFRH6_Pos) // PB14
         | (0b0101 << GPIO_AFRH_AFRH7_Pos) // PB15
      ;
      
    • whenever you encounter problems, read out and check relevant peripherals' registers (here: GPIO, SPI) against these registers' description in RM

    • use debugger carefully when reading SPI and similar peripherals' registers, debugging is intrusive and may clear flags like RXNE