STM32G0 SPI从机接收实战:从配置到中断与DMA的完整指南

发布时间:2026/9/10 0:59:28
STM32G0 SPI从机接收实战:从配置到中断与DMA的完整指南 简介STM32G0系列微控制器的SPI从机HAL库接收示例工程专门面向嵌入式开发初学者和需要快速实现SPI通信的工程师。压缩包基于STM32 HAL库提供完整的从机接收代码通过一个可运行的实验演示SPI在设备间通信中的实际运作方式帮助读者理解从机模式下数据接收的初始化、配置与处理流程也适合作为工业控制、传感器数据采集等场景的参考模板。包内共1138个文件以C语言源码662个、头文件260个和汇编语言文件103个为主同时包含IAR与Keil工程配置、启动文件、链接脚本及实用脚本等整体约9.21MB目录结构清晰便于按模块检索。目前已有235人学习下载可供初学者对照实践并快速上手。此外包内还附带了定时器、I2C等外设驱动源码能够帮助使用者一并了解STM32G0系列的外设编程方法提升整体开发效率。 直接上一个干活的例子。前阵子从同事手里接过来一个工程包名字就叫stmg0-spi-receive一看就是STM32G0系列做SPI接收的参考工程。我本来以为跟F1的老代码差不多结果仔细翻了配置和中断处理才发现G0的SPI外设跟老系列玩法差异不小尤其是接收方向配置不对是真的会掉数据。这篇就围绕这个工程把G0做SPI从机接收的完整思路、关键配置、状态机设计和排查经验一次讲清楚。适合正在用G0做从机通信、被SPI接收丢数据折磨的朋友参考。1. STM32G0的SPI接收架构跟老系列差别在哪1.1 G0 SPI外设的硬件升级点STM32G0虽然定位是入门级但SPI外设反而比F1时代强了不少。最直观的变化是数据寄存器支持8位、16位和32位帧格式而且收发路径上带了小FIFO不像F1那样只有一个可怜巴巴的缓冲寄存器。以前F1收高频小数据包时稍不注意RXNE没及时清数据就被覆盖了G0的小FIFO至少给了你一点缓冲时间不会一压就崩。另外G0的SPI外设把错误标志细分了像OVR溢出错误、MODF模式错误、UDR欠载错误都各自独立调试时能更快定位问题。老F1虽然也有OVR但标志位混在一起出了问题还要猜。还有一个容易被忽略的地方G0从机模式下NSS引脚的时序要求比F1更敏感。这个后面会细说它直接决定了你要不要用硬件片选。1.2 为什么接收方向更容易翻车发送方向出错主机那边回读比对马上就能发现。接收方向出错就不一样了数据可能卡在FIFO里没被读走也可能因为一个多余的时钟边沿多收了一个垃圾字节表现出来就是莫名其妙多一个0x00、少一个起始字节、或者整帧错位。我最开始在这个工程上踩的坑就是上电后FIFO里有残留数据——G0的SPI从机刚初始化完如果NSS被拉低过哪怕主机一个时钟都没发也有可能产生一个假接收事件。这个幽灵数据问题老F1基本碰不到G0上就必须在初始化后主动清一次FIFO和OVR标志不然第一次读取的数据都是脏的。2. CubeMX里SPI接收的关键配置2.1 参数配置里最容易踩的三个坑用CubeMX配置G0的SPI从机接收界面上看起来跟老系列差不多但实际有几个坑。第一个坑是时钟极性CPOL和时钟相位CPHA。必须跟主机端完全一致模式0是空闲低电平、第二个边沿采样模式3是空闲高电平、第二个边沿采样这个大家都知道。但G0有个细节从机模式下SPI时钟频率必须小于PCLK的1/2CubeMX不会帮你自动限制。如果你把从机SPI时钟设得很高短线实测可能没问题但稳定性会下降我习惯保守一点不超过PCLK/4。第二个坑是数据帧格式。主机如果是8位帧从机老老实实选8位。但有些主控支持7位或12位帧长这时候从机必须跟主机设置成一样的帧长不能用8位去兼容——不然收出来的数据是乱的。第三个坑关系到数据打包。G0的SPI数据寄存器支持8位、16位、32位三种模式如果你的主机每次发送一个固定长度的数据块建议从机把帧宽设成跟主机一致这样每次RXNE事件正好对应一个完整的数据单元处理逻辑清爽很多。比如主机每次发16位从机也设16位一个RXNE读一次就够了不用拼两个字节。2.2 接收方式选中断还是DMACubeMX里SPI接收你看到三种选项中断、DMA和轮询。轮询在从机接收场景里基本没用因为你不知道主机什么时候发数据来总不能一直空转刷标志位。中断方式适合数据量不大、帧长不固定的场景。每次RXNE触发中断在中断里读数据、写入自己的缓冲区。G0的SPI中断向量在NVIC里是独立的不像老F1还要复用配置起来很干净。DMA方式适合数据量大、帧长固定的场景比如从SPI Flash读一大块数据、接收音频流。G0的SPI DMA请求有独立的通道映射需要在CubeMX里把SPI的接收DMA请求打开并配置好数据宽度。DMA的好处是CPU完全不用管字节搬运坏处是帧边界不好判断——你不知道主机这一帧到底发了多少个字节除非配合片选信号。我的建议是如果做的是命令响应型通信主机时不时发一条指令长度不定用中断软件状态机。如果做的是数据流传输固定长度、高频率用DMA硬件片选。这个工程里的STMF4那套DMA配合包装的代码思路可以参考但注册表可以直接沿用。后面第三、第四章分别给这两套方案的实现细节。3. 中断接收不定长数据的实战写法3.1 从机接收状态机的设计主机发来的数据长度不固定从机怎么判断一帧数据结束业界最常见的方案是用片选信号作为帧边界CS拉低表示帧开始CS拉高表示帧结束。所以G0从机端要把NSS配置成硬件片选或者外部中断引脚。我建议直接用GPIO外部中断监控CS上升沿。具体做法是NSS引脚复用为SPI的NSS功能再把同一个引脚的外部中断使能配置为上升沿触发。这样既能正常参与SPI通信又能在主机拉高CS时立刻知道这一帧结束了。整个从机接收的状态机可以简化成三个状态IDLE等待CS拉低不接收数据RECVCS为低持续接收数据每收到一个字节存入缓冲区FRAME_DONECS上升沿触发表示一帧数据接收完成处理这一帧内容核心伪代码如下void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin NSS_Pin) { if (HAL_GPIO_ReadPin(NSS_Port, NSS_Pin) GPIO_PIN_RESET) { // CS拉低帧开始 rx_state STATE_RECV; rx_len 0; } else { // CS拉高帧结束 rx_state STATE_FRAME_DONE; process_frame(rx_buffer, rx_len); } } } void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { if (rx_state STATE_RECV rx_len RX_BUF_SIZE) { rx_buffer[rx_len] rx_byte; // 准备接收下一字节 HAL_SPI_Receive_IT(hspi1, rx_byte, 1); } } }注意HAL的HAL_SPI_Receive_IT每次只接收一字节接收完成后会调用回调然后中断会暂时关闭必须在回调里再次调用HAL_SPI_Receive_IT才能继续接收。这个机制在G0上要特别留意因为G0的SPI中断标志位比较灵敏如果回调里处理太慢或者忘了重新打开接收数据直接丢掉。3.2 溢出处理与环形缓冲区CS作为帧边界的方案有个隐患如果主机在CS拉高后又快速拉低开始下一帧而你已经进入了FRAME_DONE处理状态处理耗时较长就会漏掉第二帧的开头。所以FRAME_DONE状态里应该只做拷贝缓冲区、置标志位这种轻量操作真正的业务解析放到主循环里做。另一个需要重点关注的是溢出问题。G0的SPI从机如果接收端没有及时读走FIFO里的数据新来的字节就会触发OVR错误之后通信直接卡死。HAL库里HAL_SPI_Receive_IT在OVR后返回的状态不是HAL_OK很多人没查返回值导致后续数据全丢。我的做法是给接收缓冲区加个环形结构中断里只负责写指针移动主循环负责读指针移动当两者相等时认为缓冲区已读空。同时在SPI错误回调里加OVR恢复逻辑void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_OVR)) { __HAL_SPI_CLEAR_OVRFLAG(hspi); // 重新开启接收 HAL_SPI_Receive_IT(hspi1, rx_byte, 1); } } }注意G0的OVR标志清除方式跟F1不太一样直接读DR寄存器再读SR寄存器的老方法不好使了推荐用__HAL_SPI_CLEAR_OVRFLAG宏它会自动处理寄存器访问顺序。4. DMA接收配合硬件片选的使用细节4.1 硬件片选与DMA的组合要点如果主机每次发来的数据长度是固定的比如100字节一帧那可以用DMA让硬件把数据自动搬运到内存缓冲区CPU零参与。但DMA模式下帧边界的判断更麻烦这时候必须靠硬件片选。G0的SPI在从机模式下NSS可以配置为硬件片选NSS hardware mode。当NSS被拉低时从机被选中所有时钟信号都被接收NSS拉高后接收停止。硬件片选的好处是片选变化由外设内部处理不需要GPIO中断额外介入。但要注意CubeMX默认配的硬件片选模式可能是需要NSS输出使能的做从机时要选NSS hardware而不是NSS output。选错了从机会一直认为自己被选中数据乱收。DMA接收配置时需要把SPI的RX DMA请求打开并把DMA通道设为循环模式或正常模式。正常模式下一帧数据刚好填满缓冲区然后DMA传输完成中断触发在中断里处理数据。循环模式适合连续数据流。4.2 一帧接收完成后的数据装配DMA有个特点它是无感的主机发了多少字节它不知道只有缓冲区满了或者DMA传输完才知道。所以用DMA接收固定长度帧时必须保证DMA配置的传输长度跟主机发送长度完全一致。举个例子主机每次发64字节从机DMA传输长度就配置成64。主机开始发送前先拉低CS从机收到CS下降沿之后开始DMA搬运等64字节收齐DMA传输完成中断触发。如果主机发到一半突然不发了DMA就会一直等直到超时。我在这个工程里给DMA方案加了个CS超时保护在片选上升沿时如果DMA还没传输完成就读取当前DMA计数器的剩余值算出实际收到多少字节然后手动终止DMA传输按实际字节数去解析数据。这样就算主机发了一半CS就拉高了从机也能正确处理半包数据。void on_nss_rising_edge(void) { uint32_t remaining __HAL_DMA_GET_COUNTER(hdma_spi1_rx); uint32_t received RX_FIXED_LEN - remaining; HAL_DMA_Abort(hdma_spi1_rx); process_frame(rx_buffer, received); }提示DMA计数器返回的是剩余未传输的数据单元数单位跟DMA数据宽度一致。如果数据宽度是8位那数量就是字节数如果宽度是16位要乘以2才是字节数。别搞错了。5. 常见问题与排查技巧实录5.1 SPI接收不到数据的三个排查方向遇到从机收不到数据或者收到的数据全是0xFF这类问题我一般按下面顺序排查基本能覆盖八成情况。先查时钟配置。G0从机的SPI时钟必须来自主机但如果APB时钟或SPI预分频配错了哪怕主机发了时钟从机这边也可能因为采样窗口不对而收不到。用逻辑分析仪抓SCK和MOSI的波形确认时钟极性和相位是否真的匹配。再查NSS片选。最常见的问题是NSS引脚内部上拉没启用。主机端可能默认输出高电平但从机端如果不把NSS引脚上拉引脚悬空或状态不稳定从机会随机进入选中状态数据收发全部错乱。CubeMX里把NSS引脚设为输入模式并打开内部上拉是大多数人漏掉的一步。最后查FIFO残留。如果G0上电后直接进接收状态很有可能FIFO里有脏数据。我建议在初始化完成后做一次软复位把SPE位清零然后读DR寄存器把FIFO清空确保FIFO深度和RXNE标志都归零再重新置位SPE。5.2 时序误区与调试手段SPI接收的调试我第一次做的时候走了不少弯路还好这次G0工程里总算是摸清了路数。有一个特别容易踩的坑是从机完全不需要配置波特率。SPI波特率只对主机有意义从机是跟随外部时钟的。但很多人习惯性在CubeMX里把从机SPI波特率也调低结果反而影响了内部位时序判断尤其在40MHz主频下SPI的过采样逻辑可能出问题。调试手段方面最实用的是加一个GPIO翻转来测量中断处理耗时。比如在SPI接收中断入口把PB0拉高出口拉低用示波器看PB0的高电平时间就能算出中断处理占用了多久。如果这个时间超过了从机的字节间隔就会丢数据。这个办法我一直在用比看时序图猜效率高得多。另外如果手头没有逻辑分析仪也有个土办法主机端用固定数据模式循环发送0xAA、0x55这种交替位数据从机接收后把数据通过串口打印出来。0xAA和0x55交替出现说明位序对了如果收到的是0x55和0xAA反过来说明LSB/MSB设置不一致如果收到的是0xFF和0x00说明采样沿或者CPOL/CPHA有偏差。5.3 硬件片选和软件片选怎么选排查的时候经常会纠结一个问题到底用硬件片选还是软件片选。我的建议很明确从机端优先用硬件片选。软件片选虽然可以省一个专用的NSS引脚把它当普通GPIO用但需要自己控制引脚状态而且GPIO翻转的速度比不上硬件片选外设内部处理的稳定性。尤其是主机速度跑到10Mbps以上的时候软件片选完全扛不住帧边界容易漂移。但硬件片选在G0上有个小坑SPI外设的NSS引脚会直接参与接收逻辑如果主机没有把NSS拉高拉低的时序处理好从机可能在一帧中间失去选中状态导致数据截断。所以硬件片选模式下主机端的NSS控制一定要可靠不能通过简单的软件延时去模拟片选时序最好也用硬件NSS输出。对比项硬件片选软件片选引脚占用占用一个专用NSS引脚任意GPIO即可接收稳定性高外设内部处理低受GPIO翻转速度影响帧边界检测外设自动识别需要外部中断辅助适合场景高速、固定帧长、DMA传输低速、命令型通信、引脚受限我做这个工程的时候最终确定的是硬件片选中断DMA混合的架构命令帧用中断接收配合CS上升沿判断帧结束数据块用DMA搬运配合硬件片选保证边界。这套组合在G0上实测稳定还是那句老话通信这东西硬件能少操心的就让硬件去管人为干预越少出错的概率越低。最后再分享一个小技巧。如果手头的逻辑分析仪只能抓到主机波形抓不到从机波形可以在从机接收中断里加一个计数器每收到一字节就把一个GPIO翻转一次用示波器看这个GPIO的波形频率就能反推主机实际发送的字节速率。我在排查一个主机时钟不稳的问题时就是这么做的几分钟就定位到了原来是主机端PLL配置带来的抖动。调试嵌入式通信思路比工具重要先把变量的位置锁定准确再去看波形和数据才不会浪费时间。本文还有配套的精品资源点击获取