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

HAL_SPI_TransmitReceive主从通信应答滞后一个请求周期求助

SPI收发应答滞后问题排查

问题背景

我正在研究HAL_SPI_TransmitReceive的工作特性,编写了主设备程序,每12秒发送1字节数据。从设备侧RX缓冲区数据显示接收序列正常,但通过逻辑分析仪捕捉到异常时序:当从设备收到有效请求(0x5A)时,应答数据会滞后两次传输才被主设备接收到——具体来说,收到0x5A后,预设的应答(0x58)会在第三次请求(发送0x54)时才被主设备读取到。


主设备代码与配置

while循环核心代码

Request = 0x5a;
HAL_GPIO_WritePin(GPIOB,"GPIO_PIN_1", GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)&Request , (uint8_t *)&Responce, 1, 200);
HAL_GPIO_WritePin(GPIOB,"GPIO_PIN_1", GPIO_PIN_SET);
HAL_Delay(12000);

Request = 0x52;
HAL_GPIO_WritePin(GPIOB,"GPIO_PIN_1", GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)&Request , (uint8_t *)&Responce, 1, 200);
HAL_GPIO_WritePin(GPIOB,"GPIO_PIN_1", GPIO_PIN_SET);
HAL_Delay(12000);

Request = 0x54;
HAL_GPIO_WritePin(GPIOB,"GPIO_PIN_1", GPIO_PIN_RESET);
HAL_SPI_TransmitReceive(&hspi1, (uint8_t *)&Request , (uint8_t *)&Responce, 1, 200);
HAL_GPIO_WritePin(GPIOB,"GPIO_PIN_1", GPIO_PIN_SET);
HAL_Delay(12000);

SPI配置

hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
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_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
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();
}

从设备代码与配置

SPI中断启用代码

NVIC->ISER[1] |= ( 1u<<3 );     //SPI1 Enable
SPI1->CR2 |= SPI_CR2_RXNEIE;    //SPI_ENABLE
SPI1->CR1 |= SPI_CR1_SPE;       //SPI_ENABLE
//NVIC_EnableIRQ(SPI1_IRQn);        //u can this to enable spi interrupt

中断服务程序

SPI_RX_data[SPI_RX_Counter++] = SPI1 -> DR;
for(uint8_t i =0 ; i<10 ;i++)
{
    if(SPI_RX_data[i]==0x5a)
    {    
        SPI1->DR = 0x58;
    }
}

SPI配置

void MX_SPI1_Init(void)
{

  /* USER CODE BEGIN SPI1_Init 0 */

  /* USER CODE END SPI1_Init 0 */

  /* USER CODE BEGIN SPI1_Init 1 */

  /* USER CODE END SPI1_Init 1 */
  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_SOFT;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi1.Init.CRCPolynomial = 7;
  hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
  hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */

  /* USER CODE END SPI1_Init 2 */

}

问题原因分析

这个滞后现象的核心是SPI的同步传输机制,结合当前从设备中断逻辑的缺陷导致:

  1. SPI全双工同步传输的本质:主设备每一次HAL_SPI_TransmitReceive操作,都会在同一组时钟周期内完成双向数据交换——主设备发送1字节的同时,从设备必须已经把要发送的应答数据写入SPI->DR,才能让主设备在本次传输中接收到对应应答。如果在接收完主设备数据后才写入SPI->DR,这个数据只能等待下一次主设备发起传输时才会被发送。

  2. 中断处理的时序滞后:

    • 当主设备发送0x5A时,从设备通过RXNE中断触发并接收数据,此时本次传输已经接近完成,此时写入SPI->DR的0x58无法在本次传输中被主设备读取,只能留到下一次传输。
    • 当主设备发送0x52时,从设备发送的是上一次中断写入的0x58,但主设备此时认为这个数据是对应0x52的应答;而当主设备发送0x54时,从设备可能因为之前的循环逻辑再次写入0x58,最终导致主设备在第三次传输时才接收到对应0x5A的应答,形成“滞后两次传输”的现象。
  3. 中断逻辑的冗余干扰:中断服务程序中循环遍历前10个缓冲区元素,每次收到数据都重复检查是否存在0x5A,会导致0x5A被接收后,后续每一次中断都会重复写入0x58到SPI->DR,进一步打乱应答时序。


修复建议

  • 调整应答时机逻辑:如果需要实现“请求-即时应答”,可以让主设备发起两次连续传输:第一次发送请求,第二次专门接收应答;或者在从设备中提前预加载默认应答,收到请求后更新下一次要发送的应答数据。
  • 优化中断处理代码:去掉冗余的循环遍历,只针对当前接收的数据判断并写入应答,避免重复操作:
    uint8_t received_data = SPI1->DR;
    SPI_RX_data[SPI_RX_Counter++] = received_data;
    if(received_data == 0x5a)
    {    
        SPI1->DR = 0x58;
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 13:19:58