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

STM32G030 SPI从模式接收正常、发送数据重复卡顿问题求助

问题根因分析

你遇到的SPI从机发送数据重复问题是典型的从机时序逻辑错误,核心原因有3个:

  1. SPI从机没有主动发送数据的权利,所有传输必须由主机提供时钟驱动,你当前写的spi_send_string函数主动往DR寄存器塞数据,但此时树莓派没有发起时钟,第一个写入的字节会一直存在发送移位寄存器中,后续主机发起读取时每打8个时钟就会把这个字节发一次,就出现了全是重复值的情况
  2. SPI配置的NSS模式和实际硬件不匹配,且SPI_Receive_Commands函数中存在语法bug:while(HAL_GPIO_ReadPin(SPI_SS_GPIO_Port, SPI_SS_Pin) == GPIO_PIN_RESET);后面多了个分号,逻辑变成等CS引脚拉高之后才接收数据,时序完全错位
  3. 混用HAL库API和直接寄存器操作,HAL_SPI_Receive会重置SPI的状态标志位,和你直接读写DR寄存器的操作冲突

可行解决方案

1. 修正SPI配置

如果你的CS引脚连接到了STM32的SPI1_NSS硬件引脚,把NSS模式从SPI_NSS_SOFT改成SPI_NSS_HARD_INPUT,让硬件自动处理CS选中逻辑;如果用的是普通GPIO模拟CS检测,保留软件NSS的同时,每次检测到CS拉低时手动设置SET_BIT(SPI1->CR1, SPI_CR1_SSI),CS拉高时清除该位,否则SPI外设会认为自身未被选中,不响应传输。

// 修正后的SPI配置片段
hspi1.Init.NSS = SPI_NSS_HARD_INPUT; // 硬件CS引脚选这个
// 如果用软件CS,保留SPI_NSS_SOFT,额外加下面的控制逻辑
// if(HAL_GPIO_ReadPin(SPI_SS_GPIO_Port, SPI_SS_Pin) == GPIO_PIN_RESET) SET_BIT(SPI1->CR1, SPI_CR1_SSI);
// else CLEAR_BIT(SPI1->CR1, SPI_CR1_SSI);

2. 重写符合从机规则的收发逻辑

SPI是全双工同步总线,每接收1个字节的同时必然会发送1个字节,采用中断方式处理收发最稳定,不要主动批量写发送缓冲区,提前把要发的数据装填好,等主机时钟驱动时逐字节发送:

// 定义全局发送缓冲区和指针
uint8_t tx_buf[32];
uint8_t tx_len = 0;
uint8_t tx_ptr = 0;

// 开启SPI1 RXNE中断,在MX_SPI1_Init末尾加
__HAL_SPI_ENABLE_IT(&hspi1, SPI_IT_RXNE);
HAL_NVIC_EnableIRQ(SPI1_IRQn);

// 中断服务函数
void SPI1_IRQHandler(void)
{
  if(__HAL_SPI_GET_FLAG(&hspi1, SPI_FLAG_RXNE)) {
    uint8_t rx_data = (uint8_t)SPI1->DR;
    // 收到读电压指令
    if(rx_data == 'v') {
      // 提前装填ADC数据到发送缓冲区
      tx_len = sprintf((char*)tx_buf, "%.2f", get_adc_voltage());
      tx_ptr = 0;
      // 把第一个要发的字节提前放到DR,等主机来取
      SPI1->DR = tx_buf[tx_ptr++];
    } else {
      // 后续主机发空字节驱动时钟,逐字节发送数据
      if(tx_ptr < tx_len) {
        SPI1->DR = tx_buf[tx_ptr++];
      } else {
        // 发完后返回结束标志,方便主机判断
        SPI1->DR = 0xFF;
      }
    }
    // 清除溢出标志,避免后续收发异常
    __HAL_SPI_CLEAR_OVRFLAG(&hspi1);
  }
}

3. 修正树莓派侧的读取逻辑

树莓派发完指令后,需要连续发送N个空字节(N等于你要接收的最大数据长度)来驱动时钟,才能拿到完整数据:

import spidev
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1000000 # 从机模式下不要超过4M,先设1M测试
spi.mode = 0 # 和STM32配置的CPOL=0、CPHA=1EDGE对应

# 发'v'指令,同时读10个字节
resp = spi.xfer2([ord('v')] + [0x00]*10)
# 第一个字节是发指令时返回的无效值,从第二个开始是有效数据
print("收到的电压数据:", resp[1:tx_len])

4. 其他优化点

  • 删掉SPI_read函数中多余的while(SPI1->SR & SPI_SR_BSY),RXNE置1时数据已经完成接收,不需要等BSY清零,反而会增加延迟丢包
  • 尽量不要混用HAL库API和直接寄存器操作,要么全用HAL的中断收发,要么全用寄存器操作,避免状态位冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 23:15:08