资讯动态

手把手教你用STM32F103和Xilinx Spartan-6 FPGA实现SPI全双工通信(附完整代码)

发布时间:2026/10/2 5:39:16 来源:尧图企业网站定制
STM32与FPGA的SPI全双工通信实战指南在嵌入式系统开发中处理器与可编程逻辑器件的高效通信是一个常见需求。SPISerial Peripheral Interface作为一种高速全双工同步串行通信协议因其简单高效的特性成为STM32等微控制器与FPGA之间数据交互的理想选择。本文将基于STM32F103和Xilinx Spartan-6 FPGA平台深入讲解SPI通信的实现细节提供可直接应用于项目的完整代码方案。1. 硬件平台与通信基础1.1 硬件选型与连接我们选择的硬件组合是主设备STM32F103C8T672MHz主频ARM Cortex-M3内核从设备Xilinx Spartan-6 XC6SLX9 FPGASPI物理连接需要四根信号线SCKSerial Clock时钟信号由主设备产生MOSIMaster Out Slave In主设备输出从设备输入MISOMaster In Slave Out主设备输入从设备输出CSChip Select片选信号低电平有效注意STM32的硬件SPI外设通常固定分配了MOSI/MISO/SCK引脚但CS引脚可以自由选择任意GPIO这为电路设计提供了灵活性。1.2 SPI模式详解SPI通信有四种工作模式由时钟极性CPOL和时钟相位CPHA两个参数决定模式CPOLCPHA空闲时钟电平数据采样边沿000低电平奇数边沿上升沿101低电平偶数边沿下降沿210高电平奇数边沿下降沿311高电平偶数边沿上升沿本实验采用模式3CPOL1CPHA1这是许多FPGA SPI从机实现的首选模式。在这种模式下空闲时SCK保持高电平数据在SCK的上升沿采样数据在SCK的下降沿切换2. STM32主设备配置2.1 硬件初始化STM32标准库提供了完善的SPI外设支持。以下是关键初始化代码void SPI_FPGA_Init(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能SPI2时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); // 配置SPI引脚 GPIO_InitStructure.GPIO_Pin GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); // 配置CS引脚(PC3)为普通输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOC, GPIO_InitStructure); SPI_FPGA_CS_HIGH(); // 初始化为高电平 // SPI参数配置 SPI_InitStructure.SPI_Direction SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode SPI_Mode_Master; SPI_InitStructure.SPI_DataSize SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL SPI_CPOL_High; SPI_InitStructure.SPI_CPHA SPI_CPHA_2Edge; SPI_InitStructure.SPI_NSS SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler SPI_BaudRatePrescaler_4; // 18MHz SPI_InitStructure.SPI_FirstBit SPI_FirstBit_MSB; SPI_Init(SPI2, SPI_InitStructure); SPI_Cmd(SPI2, ENABLE); }2.2 数据收发实现SPI是全双工通信发送和接收同时进行。以下是字节收发函数uint8_t SPI_FPGA_SendByte(uint8_t byte) { // 等待发送缓冲区空 while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) RESET); // 发送数据 SPI_I2S_SendData(SPI2, byte); // 等待接收缓冲区非空 while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) RESET); // 返回接收到的数据 return SPI_I2S_ReceiveData(SPI2); }提示STM32的SPI数据寄存器是16位的但我们可以只使用低8位进行字节传输。高位数据在8位模式下会被忽略。3. FPGA从机Verilog实现3.1 SPI从机模块设计FPGA端的SPI从机需要精确同步主设备的时钟和数据。以下是Verilog核心代码module spi_slave ( input clk, // FPGA系统时钟 input rst_n, // 复位信号 input CS_N, // 片选信号 input SCK, // SPI时钟 input MOSI, // 主出从入 output reg MISO, // 主入从出 output reg led // 状态指示灯 ); // 时钟边沿检测 reg sck_r0, sck_r1; always (posedge clk or negedge rst_n) begin if(!rst_n) {sck_r0, sck_r1} 2b11; else {sck_r0, sck_r1} {SCK, sck_r0}; end wire sck_rising ~sck_r0 sck_r1; // 上升沿检测 wire sck_falling sck_r0 ~sck_r1; // 下降沿检测 // 接收逻辑上升沿采样 reg [7:0] rxd_data; reg [2:0] rxd_bit_cnt; always (posedge clk or negedge rst_n) begin if(!rst_n) begin rxd_data 8h00; rxd_bit_cnt 3d0; end else if(!CS_N sck_rising) begin rxd_data {rxd_data[6:0], MOSI}; rxd_bit_cnt rxd_bit_cnt 1b1; end end // 发送逻辑下降沿切换 reg [7:0] txd_data 8b00011000; // 固定返回0x18 reg [2:0] txd_bit_cnt; always (posedge clk or negedge rst_n) begin if(!rst_n) begin MISO 1b0; txd_bit_cnt 3d0; end else if(!CS_N sck_falling) begin MISO txd_data[7 - txd_bit_cnt]; txd_bit_cnt txd_bit_cnt 1b1; end else if(CS_N) begin MISO 1b0; txd_bit_cnt 3d0; end end // 接收数据处理 always (posedge clk or negedge rst_n) begin if(!rst_n) led 1b0; else if(rxd_bit_cnt 3d7 !CS_N sck_rising) begin led (rxd_data 8d123); // 收到123时点亮LED end end endmodule3.2 时序分析与优化在FPGA实现中需要特别注意时钟同步使用双寄存器同步SCK信号避免亚稳态边沿检测准确识别SCK的上升沿和下降沿位计数器确保8位数据的完整收发CS信号处理片选无效时重置状态机4. 系统联调与问题排查4.1 调试流程硬件检查确认所有连接线正确无误检查电源和地线连接测量SCK信号是否正常产生分模块验证先单独测试STM32的SPI输出再验证FPGA的信号接收最后进行全双工通信测试逻辑分析仪抓取 典型的SPI模式3时序应显示CS拉低后SCK从高电平开始MOSI数据在SCK下降沿切换MISO数据在SCK上升沿采样4.2 常见问题与解决方案问题现象可能原因解决方法无任何通信硬件连接错误检查线路连通性确认电源正常只能发送不能接收CPOL/CPHA不匹配检查两端模式设置是否一致数据错位相位错误调整采样边沿或检查位序(MSB/LSB)偶尔数据错误时序不满足降低SPI时钟频率增加建立保持时间CS信号异常软件控制不当确保CS在传输前后有足够延时4.3 性能优化技巧时钟分频选择// STM32 SPI时钟分频选项 typedef enum { SPI_BaudRatePrescaler_2 0x00, // 36MHz 72MHz PCLK SPI_BaudRatePrescaler_4 0x08, // 18MHz SPI_BaudRatePrescaler_8 0x10, // 9MHz SPI_BaudRatePrescaler_16 0x18, // 4.5MHz SPI_BaudRatePrescaler_32 0x20, // 2.25MHz SPI_BaudRatePrescaler_64 0x28, // 1.125MHz SPI_BaudRatePrescaler_128 0x30, // 562.5kHz SPI_BaudRatePrescaler_256 0x38 // 281.25kHz } SPIBaudRate;DMA传输对于大数据量传输可以配置SPI DMAvoid SPI_DMA_Config(void) { DMA_InitTypeDef DMA_InitStructure; // 使能DMA时钟 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 配置TX DMA DMA_DeInit(DMA1_Channel4); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)SPI2-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)tx_buffer; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_BufferSize BUFFER_SIZE; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode DMA_Mode_Normal; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel4, DMA_InitStructure); // 配置RX DMA类似... // 使能SPI DMA SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx | SPI_I2S_DMAReq_Rx, ENABLE); }双缓冲技术在FPGA端实现双缓冲机制避免数据覆盖。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑