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

STM32F411 SPI从机收发数据异常问题排查求助

STM32F411 SPI主从通信数据错乱问题排查

问题背景

使用STM32F411搭建主从机SPI通信系统:主机发送数据,从机负责将数据写入挂载的SD卡以分担主机负载。从机采用轮询方式处理SPI,未使用DMA或中断,两个项目均通过CubeIDE搭建。

当前问题:从机接收的数据存在错乱、移位或丢失字符的情况,且向主机回传的数据为无效dummy值。逻辑分析仪显示主机发送数据正常,单字节收发的sanity check可正常工作,但多字节传输异常。

主机SPI配置

static void MX_SPI3_Init(void) {
   hspi3.Instance = SPI3;
   hspi3.Init.Mode = SPI_MODE_MASTER;
   hspi3.Init.Direction = SPI_DIRECTION_2LINES;
   hspi3.Init.DataSize = SPI_DATASIZE_8BIT;
   hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
   hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
   hspi3.Init.NSS = SPI_NSS_SOFT;
   hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
   hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
   hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
   hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
   hspi3.Init.CRCPolynomial = 10;
   if (HAL_SPI_Init(&hspi3) != HAL_OK)   {
     Error_Handler();
   }
 }

从机SPI配置

static void MX_SPI1_Init(void)
{
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_SLAVE;
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi1.Init.NSS = SPI_NSS_HARD_INPUT;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
}

主机通信逻辑

示例调用:

sd_open_file("Quadcopter.txt", FA_WRITE | FA_READ | FA_OPEN_ALWAYS);

核心函数实现:

uint8_t sd_open_file(const char *file_name, uint8_t instruction){
        uint8_t response[1];
        uint8_t command = LOGGER_SD_OPEN_FILE;
        uint16_t file_length = strlen(file_name)+1;
        uint16_t total_length = file_length+1;
        uint8_t transmit_length[] = {(total_length >> 8) & 0xFF, total_length & 0xFF};
        uint8_t transmit_buffer[total_length]; // string length, \0 and command
        transmit_buffer[0] = instruction;
        memcpy(&transmit_buffer[1], file_name, file_length); // Copy the string including null terminator.


        HAL_StatusTypeDef status;
        slave_select();
        status = HAL_SPI_Transmit(m_device_handle, &command, 1, 5000); // send command
        status = HAL_SPI_Transmit(m_device_handle, transmit_length, 2, 5000); // send how many bytes the dat will be
        status = HAL_SPI_Transmit(m_device_handle, transmit_buffer, total_length, 5000); // send data
        status = HAL_SPI_Receive(m_device_handle, response, 1, 5000);
        uint8_t data[200];
        status = HAL_SPI_Receive(m_device_handle, data, total_length, 1000);
        slave_deselect();
...
}

从机通信逻辑

HAL_StatusTypeDef spi_slave_result_receive = HAL_SPI_Receive(&hspi1, slave_buffer, 1, 1000);

...

HAL_StatusTypeDef status_receive;

// We ask how much data should we receive. 
if((status_receive = HAL_SPI_Receive(&hspi1, slave_buffer, 2, 1000)) != HAL_OK){
    break;
} 
uint16_t amount_of_data_to_receive = (slave_buffer[0] << 8) | slave_buffer[1];

// Receive the instruction and file name
if( (status_receive = HAL_SPI_Receive(&hspi1, slave_buffer, amount_of_data_to_receive, 1000)) != HAL_OK){
    break;
} 
// instruction is byte 0
char* extracted_string = extract_string_from_spi_data_at_index(slave_buffer, SLAVE_BUFFER_SIZE, 1);

volatile uint8_t result = sd_open_file(extracted_string, slave_buffer[0]);
HAL_SPI_Transmit(&hspi1, &result, 1, 100);
HAL_SPI_Transmit(&hspi1, slave_buffer, amount_of_data_to_receive, 100);
free(extracted_string);

已尝试的调试手段

  • 降低SPI总线频率
  • 调整时钟极性(CPOL)和相位(CPHA)
  • 改用单一缓冲区接收所有数据
  • 单字节收发sanity check正常,但多字节传输仍异常

解决思路与设计缺陷分析

1. NSS信号同步问题

主机使用软件NSS,从机使用硬件NSS,需确保:

  • 主机slave_select()必须在所有传输操作前拉低NSS,slave_deselect()在所有传输(包括接收)完成后再拉高,禁止在HAL_SPI_Transmit/Receive调用之间切换NSS电平,否则从机会误判传输结束,导致同步丢失
  • 检查slave_select()/slave_deselect()的GPIO操作逻辑,确保无延迟或误操作

2. SPI全双工特性误用

SPI是全双工总线,每个时钟周期同时完成收发:

  • 主机单独调用HAL_SPI_Transmit时,从机实际会发送dummy数据;主机单独调用HAL_SPI_Receive时,必须发送dummy字节来产生时钟,从机才能发送有效数据
  • 当前逻辑中,从机在接收完数据后调用HAL_SPI_Transmit,但此时主机如果没有主动发送时钟(即调用HAL_SPI_Receive),从机无法完成发送,导致主机接收到无效dummy值
  • 建议改为连续全双工传输:主机将所有要发送的数据(命令+长度+dummy字节用于接收响应)打包,在单次HAL_SPI_TransmitReceive调用中完成收发,确保时钟连续

3. 传输时序断裂问题

主机将传输拆分为多个独立的HAL_SPI_Transmit/Receive调用,会导致SPI时钟中断:

  • 从机在时钟中断期间可能进入空闲状态,丢失同步,导致后续接收数据错乱
  • 优化方案:将主机的发送数据(命令+长度+传输数据)和需要接收的响应(结果+回传数据)合并为一个连续的传输流程,用单次全双工操作完成

4. 从机轮询响应延迟

从机在调用sd_open_file时会处理SD卡操作,占用大量时间:

  • 此时主机如果开始接收数据,从机无法及时准备有效回传数据,只能发送dummy值
  • 调整逻辑:从机先接收完所有主机数据,立即发送响应字节,再处理SD卡操作;或者在处理SD卡操作前,确保主机已经完成所有发送操作,等待从机处理完成后再触发回传

5. 缓冲区与数据解析问题

  • 检查从机slave_buffer的大小,确保amount_of_data_to_receive不会超过缓冲区容量,避免溢出导致数据错乱
  • 确认长度字段的端序:主机发送大端模式(高字节在前),从机解析逻辑匹配,但需验证实际接收的字节顺序是否正确

6. 硬件层面排查

  • 检查SPI线路接线:SCK、MOSI、MISO、NSS引脚是否连接牢固,有无电磁干扰
  • 确认从机NSS引脚配置为硬件输入,未被其他GPIO配置覆盖
  • 确保主从机供电稳定,避免电压波动影响SPI通信

内容的提问来源于stack exchange,提问作者Rokas1369

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:34:53