I am using the STM32F303RE microcontroller in a Nucleo board.
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.
// 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