1. SPI通信基础与高速传输挑战
SPI通信在嵌入式系统中非常常见,它是一种高速、全双工的同步通信协议。在实际项目中,当STM32作为主机与FPGA作为从机进行通信时,往往会遇到一些时序上的问题,尤其是在高频率下(比如42MHz)。SPI通信本身依赖四根线:SCK(时钟)、MOSI(主发从收)、MISO(主收从发)和CS(片选)。虽然协议简单,但在高速场景下,信号完整性、时钟同步以及数据稳定性都会成为瓶颈。
我曾经在一个图像传感器数据采集的项目中用到STM32和FPGA之间的SPI通信,初始版本没有做任何优化,直接用了标准库的阻塞式传输方式。当SPI时钟设置到10MHz以上时,就发现数据时不时出现错位,或者FPGA端收不到完整数据包。通过示波器抓波形,才发现SCK和CS信号都有轻微的振铃和边沿畸变。这时候才意识到,硬件设计和软件配置同样重要,高速通信不是简单提高时钟频率就能解决的。
对于初学者来说,可能更关注“如何让数据发出去”,但实际项目中,我们更需要考虑“如何让数据稳定、快速地传输”。SPI的时序优化是一个系统级工程,涉及GPIO配置、时钟树设置、信号滤波、中断处理,以及DMA的应用。下面我会结合源码和实测波形,一步步分析如何实现可靠的高速SPI通信。
2. 硬件设计与时序基础
2.1 硬件连接与信号完整性
在STM32和FPGA之间进行高速SPI通信,硬件设计是第一步也是最重要的一步。如果硬件设计有缺陷,软件再怎么优化也无力回天。我的经验是,尽量缩短走线长度,避免信号线平行走线,必要时串联匹配电阻(通常22-33欧姆)来抑制振铃。对于CS和SCK信号,最好使用GPIO速度较高的模式(比如STM32的100MHz驱动模式),但也要注意过高的速度会导致边沿过冲。
在实际布线中,我习惯将SPI信号线走在PCB的内层,两侧用地线屏蔽,减少外部干扰。如果FPGA和STM32之间的距离超过5cm,建议使用差分信号(但SPI通常不是差分协议,所以只能尽量优化单端信号)。另外,电源去耦也很关键,每个芯片的VCC和GND之间至少放置一个100nF的陶瓷电容,靠近引脚放置。
2.2 SPI时序模式选择
SPI有四种时序模式,取决于时钟极性(CPOL)和时钟相位(CPHA)的组合。我常用的模式是CPOL=1、CPHA=2,即空闲时SCK为高电平,在第二个边沿(下降沿)采样数据。这种模式在高速情况下更稳定,因为数据在时钟边沿中间变化,采样点远离变化点。
在STM32端配置SPI时,需要确保和FPGA端的时序模式完全一致。我曾经踩过一个坑,STM32设置了CPHA=1,但FPGA端写的是CPHA=2,结果数据永远对不上。后来用示波器同时抓SCK和MOSI信号,才发现采样点错位了半个时钟周期。
3. STM32端SPI配置与优化
3.1 GPIO与SPI外设初始化
STM32的SPI初始化涉及GPIO复用和SPI参数配置。以下是我常用的初始化代码,基于标准外设库(HAL库类似,但寄存器名称不同)。注意,GPIO速度一定要设为GPIO_Speed_100MHz,否则IO口翻转速度跟不上SPI时钟。
// 开启GPIOB和SPI1时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
// 配置CS引脚(PB6)为推挽输出
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// 配置PB3、4、5为复用功能(SCK、MISO、MOSI)
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// 引脚复用映射
GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource4, GPIO_AF_SPI1);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1);
// SPI参数配置
SPI_InitTyp

&spm=1001.2101.3001.5002&articleId=155805949&d=1&t=3&u=d4a7fee1599b43949b9380306d1a47be)
8393

被折叠的 条评论
为什么被折叠?



