手把手教你用STM32F103和Xilinx Spartan-6 FPGA实现SPI全双工通信(附完整代码)
2026/4/28 22:29:28 网站建设 项目流程

STM32与FPGA的SPI全双工通信实战指南

在嵌入式系统开发中,处理器与可编程逻辑器件的高效通信是一个常见需求。SPI(Serial Peripheral Interface)作为一种高速全双工同步串行通信协议,因其简单高效的特性,成为STM32等微控制器与FPGA之间数据交互的理想选择。本文将基于STM32F103和Xilinx Spartan-6 FPGA平台,深入讲解SPI通信的实现细节,提供可直接应用于项目的完整代码方案。

1. 硬件平台与通信基础

1.1 硬件选型与连接

我们选择的硬件组合是:

  • 主设备:STM32F103C8T6(72MHz主频,ARM Cortex-M3内核)
  • 从设备:Xilinx Spartan-6 XC6SLX9 FPGA

SPI物理连接需要四根信号线:

  1. SCK(Serial Clock):时钟信号,由主设备产生
  2. MOSI(Master Out Slave In):主设备输出,从设备输入
  3. MISO(Master In Slave Out):主设备输入,从设备输出
  4. CS(Chip Select):片选信号,低电平有效

注意:STM32的硬件SPI外设通常固定分配了MOSI/MISO/SCK引脚,但CS引脚可以自由选择任意GPIO,这为电路设计提供了灵活性。

1.2 SPI模式详解

SPI通信有四种工作模式,由时钟极性(CPOL)和时钟相位(CPHA)两个参数决定:

模式CPOLCPHA空闲时钟电平数据采样边沿
000低电平奇数边沿(上升沿)
101低电平偶数边沿(下降沿)
210高电平奇数边沿(下降沿)
311高电平偶数边沿(上升沿)

本实验采用模式3(CPOL=1,CPHA=1),这是许多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} <= 2'b11; 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 <= 8'h00; rxd_bit_cnt <= 3'd0; end else if(!CS_N && sck_rising) begin rxd_data <= {rxd_data[6:0], MOSI}; rxd_bit_cnt <= rxd_bit_cnt + 1'b1; end end // 发送逻辑(下降沿切换) reg [7:0] txd_data = 8'b00011000; // 固定返回0x18 reg [2:0] txd_bit_cnt; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin MISO <= 1'b0; txd_bit_cnt <= 3'd0; end else if(!CS_N && sck_falling) begin MISO <= txd_data[7 - txd_bit_cnt]; txd_bit_cnt <= txd_bit_cnt + 1'b1; end else if(CS_N) begin MISO <= 1'b0; txd_bit_cnt <= 3'd0; end end // 接收数据处理 always @(posedge clk or negedge rst_n) begin if(!rst_n) led <= 1'b0; else if(rxd_bit_cnt == 3'd7 && !CS_N && sck_rising) begin led <= (rxd_data == 8'd123); // 收到123时点亮LED end end endmodule

3.2 时序分析与优化

在FPGA实现中,需要特别注意:

  1. 时钟同步:使用双寄存器同步SCK信号,避免亚稳态
  2. 边沿检测:准确识别SCK的上升沿和下降沿
  3. 位计数器:确保8位数据的完整收发
  4. CS信号处理:片选无效时重置状态机

4. 系统联调与问题排查

4.1 调试流程

  1. 硬件检查

    • 确认所有连接线正确无误
    • 检查电源和地线连接
    • 测量SCK信号是否正常产生
  2. 分模块验证

    • 先单独测试STM32的SPI输出
    • 再验证FPGA的信号接收
    • 最后进行全双工通信测试
  3. 逻辑分析仪抓取: 典型的SPI模式3时序应显示:

    • CS拉低后,SCK从高电平开始
    • MOSI数据在SCK下降沿切换
    • MISO数据在SCK上升沿采样

4.2 常见问题与解决方案

问题现象可能原因解决方法
无任何通信硬件连接错误检查线路连通性,确认电源正常
只能发送不能接收CPOL/CPHA不匹配检查两端模式设置是否一致
数据错位相位错误调整采样边沿,或检查位序(MSB/LSB)
偶尔数据错误时序不满足降低SPI时钟频率,增加建立保持时间
CS信号异常软件控制不当确保CS在传输前后有足够延时

4.3 性能优化技巧

  1. 时钟分频选择

    // 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;
  2. DMA传输:对于大数据量传输,可以配置SPI DMA:

    void 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); }
  3. 双缓冲技术:在FPGA端实现双缓冲机制,避免数据覆盖。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询