STM32 NucleoF303K8 SPI从机通信异常问题排查与配置咨询
问题:NucleoF303K8 SPI双机通信DMA异常问题
背景与通信逻辑
- 两块NucleoF303K8通过SPI通信,主机由UART控制,可发送3种SPI消息:
{0x01 0x00 0x00 0x00}:读取数值{0x81 0x00 0x00 0x00}:设置数值为0{0x81 0x01 0x00 0x00}:设置数值为1
- 从机工作逻辑:
NSS_Pin变低时触发EXTI_Callback,开始接收1字节命令头- 根据命令头判断读/写操作:读命令用SPI_DMA发送数据,写命令用SPI_DMA接收数据
NSS_Pin变高时,处理写命令的接收数据,读命令等待下一次请求
从机代码
extern DMA_HandleTypeDef hdma_spi1_tx; extern DMA_HandleTypeDef hdma_spi1_rx; extern SPI_HandleTypeDef hspi1; uint8_t txb[16] = {0x01,0xAA,0xBB}; uint8_t rxb[16] = {}; uint8_t code = 0; uint8_t op = 0; // 0: idle, 1: readout, 2: writein void readOut(){ op = 1; HAL_SPI_Transmit_DMA(&hspi1, txb,3); } void receiveWriteIn(){ op = 2; HAL_SPI_Receive_DMA(&hspi1, rxb, 3); } void processWriteIn(){ HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, (GPIO_PinState)rxb[0]); } void SystemLoad(){ HAL_GPIO_WritePin(LD3_GPIO_Port,LD3_Pin,GPIO_PIN_SET); while(1){ } } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ HAL_SPI_DMAStop(&hspi1); if(GPIO_Pin == NSS_Pin){ if(HAL_GPIO_ReadPin(NSS_GPIO_Port, NSS_Pin) == GPIO_PIN_RESET){ op = 0; HAL_SPI_Receive_DMA(&hspi1,&code,1); }else{ if(op == 2){ processWriteIn(); } op = 0; code = 0; } } } inline void mainCbk(SPI_HandleTypeDef *hspi){ HAL_SPI_DMAStop(hspi); if(hspi == &hspi1){ if(op == 0){ if(code & 0x80){ receiveWriteIn(); }else{ readOut(); } } } } void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi){ mainCbk(hspi); } void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi){ mainCbk(hspi); } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi){ mainCbk(hspi); }
CubeMX配置
- GPIO:1个输出(LED)+1个EXTI(NSS引脚)
- SPI1:配置DMARX(高优先级)、DMATX(极高优先级),启用DMA通道和SPI1全局中断NVIC
- NVIC:Debug、SysTick优先级为0(最高),EXTI和SPI1全局中断优先级为1
- 时钟:HCLK=64MHz,PCLK1=32MHz,PCLK2=64MHz
- 主机SPI速率:250KBit/s
当前核心问题
- 写入数据循环移位:设置数值为1的
0x01依次出现在rxb[0]、rxb[1]、rxb[2],循环往复 - 读出数据异常:前两条消息正常接收
{0x00 0x01 0xAA 0xBB},后续出现{0xBB 0x01 0xAA 0xBB}等异常值;读操作后执行写操作,接收缓冲区会全为0xBB
已尝试排查SPI1.DR寄存器(值为0)、调整NVIC优先级、改用HAL_SPI_Transmit_IT,均未解决问题。
解决方案
1. 禁用DMA循环模式
你的DMA配置大概率开启了循环模式(Circular Mode),这会导致DMA传输完成后自动重置地址指针,重复填充缓冲区,直接引发移位和残留数据问题:
- 在CubeMX中找到SPI1的DMA通道(TX和RX),将
Mode从Circular改为Normal - 代码层面手动修改:
hdma_spi1_rx.Mode = DMA_NORMAL; hdma_spi1_tx.Mode = DMA_NORMAL;
2. 修正NSS中断的SPI复位逻辑
当前HAL_GPIO_EXTI_Callback中仅调用HAL_SPI_DMAStop,未重置SPI和DMA的状态寄存器,残留的传输状态会干扰下一次通信:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin){ if(GPIO_Pin == NSS_Pin){ if(HAL_GPIO_ReadPin(NSS_GPIO_Port, NSS_Pin) == GPIO_PIN_RESET){ // 完全重置SPI和DMA状态 if(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY){ HAL_SPI_Abort(&hspi1); // 比DMAStop更彻底,清除所有挂起的传输请求 } op = 0; memset(rxb, 0, sizeof(rxb)); // 每次通信前清空接收缓冲区 HAL_SPI_Receive_DMA(&hspi1,&code,1); }else{ if(op == 2){ processWriteIn(); } op = 0; code = 0; } } }
3. 修正回调函数的DMA停止逻辑
mainCbk中每次回调都调用HAL_SPI_DMAStop是错误的——DMA传输完成后已经自动停止,强制停止会破坏后续的状态同步,应删除该调用:
inline void mainCbk(SPI_HandleTypeDef *hspi){ if(hspi == &hspi1){ if(op == 0){ if(code & 0x80){ receiveWriteIn(); }else{ readOut(); } } } }
4. 匹配主机与从机的传输长度
主机每次发送4字节数据,但从机的读/写操作仅处理3字节,导致SPI总线上有1字节未被处理,残留数据会干扰下一次通信:
- 读命令时,从机应发送4字节数据(对应主机的4字节接收):
void readOut(){ op = 1; // 调整为4字节,与主机发送长度匹配 HAL_SPI_Transmit_DMA(&hspi1, txb,4); } - 写命令时,从机应接收4字节数据(包含1字节命令头+3字节数据):
void receiveWriteIn(){ op = 2; // 调整为4字节,与主机发送长度匹配 HAL_SPI_Receive_DMA(&hspi1, rxb, 4); } // 处理时取第2字节作为设置值 void processWriteIn(){ HAL_GPIO_WritePin(LD3_GPIO_Port, LD3_Pin, (GPIO_PinState)rxb[1]); }
5. 优化NVIC优先级配置
EXTI(NSS中断)的优先级应高于SPI全局中断,确保NSS电平变化能及时抢占SPI传输,避免传输过程中NSS状态变化导致的异常:
- 在CubeMX中,将EXTI的NVIC优先级设为0(与Debug/SysTick同级别),SPI1全局中断设为1
内容的提问来源于stack exchange,提问作者lazba
相关产品推荐
相关产品推荐

