You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

STM32 NucleoF303K8 SPI从机通信异常问题排查与配置咨询

问题:NucleoF303K8 SPI双机通信DMA异常问题

背景与通信逻辑

  • 两块NucleoF303K8通过SPI通信,主机由UART控制,可发送3种SPI消息:
    1. {0x01 0x00 0x00 0x00}:读取数值
    2. {0x81 0x00 0x00 0x00}:设置数值为0
    3. {0x81 0x01 0x00 0x00}:设置数值为1
  • 从机工作逻辑:
    1. NSS_Pin变低时触发EXTI_Callback,开始接收1字节命令头
    2. 根据命令头判断读/写操作:读命令用SPI_DMA发送数据,写命令用SPI_DMA接收数据
    3. 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. 写入数据循环移位:设置数值为1的0x01依次出现在rxb[0]、rxb[1]、rxb[2],循环往复
  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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 17:09:54