我在 Nucleo 板上使用 STM32F303RE 微控制器。
在尝试编写一个与 SPI 设备通信的程序后,我发现它不起作用,因此我将代码简化为最小的实现,除了接收单个字节之外什么也不做。这是我所拥有的:
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) {
}
}
结果是线路
while (!(SPI2->SR & SPI_SR_RXNE));
挂起,因为 RXNE 从未设置过。
请注意,我不是在问重复的this问题,因为我已经尝试实现给定的解决方案,即通过设置
SPI_CR2_FRXTH
和SPI_CR2_SSOE
位,确保启用正确的时钟,并确保SPI2 在所有其他寄存器设置后启用。
// 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
这肯定是错误的:每个后续分配都会覆盖所有先前的分配,因此仅设置 PB15 的值。
一般:
您从哪里获得这种编程风格,逐渐将位/位域添加到单个寄存器?一次性计算整个寄存器的值并写入,例如
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
;
每当您遇到问题时,请根据RM中这些寄存器的描述读出并检查相关外设的寄存器(此处:GPIO、SPI)
读取 SPI 和类似外设寄存器时请小心使用调试器,调试具有侵入性并且可能会清除 RXNE 等标志
GPIO 配置:
看起来您为
PB12-PB15
配置备用功能的代码行可能会单独覆盖每个引脚的 AFR[1]
寄存器。为了确保所有引脚都正确设置,您应该将这些设置合并到一行中,如下所示:
GPIOB->AFR[1] |= (0b0101 << GPIO_AFRH_AFRH4_Pos) |
(0b0101 << GPIO_AFRH_AFRH5_Pos) |
(0b0101 << GPIO_AFRH_AFRH6_Pos) |
(0b0101 << GPIO_AFRH_AFRH7_Pos);