EBI开发手册|FPGA与STM32 EBI高速并行通信技术详解
我的学记|刘航宇的博客

EBI开发手册|FPGA与STM32 EBI高速并行通信技术详解

刘航宇
1天前发布 /正在检测是否收录...
电子信息类专业“一站式”考研-简历-就业宝藏分享 电子信息类专业“一站式”考研-简历-就业宝藏分享
适用场景:复杂电子系统 / 高可靠FPGA-MCU协同设计
关键词:FPGA · STM32 · EBI · LocalBus · 异步SRAM

1. 为什么选EBI而非SPI?

在高可靠嵌入式系统中,STM32与FPGA通信的常见方式有SPI、I2C、UART等串行接口,以及EBI(External Bus Interface)等并行总线。虽然SPI实现简单,但在需要高带宽、低延迟、内存映射访问的场景下,EBI并行总线具有显著优势:

对比维度SPIEBI
数据宽度1~4 bit (QSPI)8/16 bit
理论带宽~50 MB/s (QSPI)~100+ MB/s
访问方式寄存器操作内存映射 (直接读写)
DMA支持有限原生支持
适用场景控制命令、低速数据图像缓冲、FFT数据流、航电总线

对于需要FPGA处理高速数据流(如传感器采集、信号处理)并将结果回传MCU的场景,EBI允许STM32像访问内部SRAM一样直接读写FPGA内部寄存器或FIFO,无需复杂的协议栈开销。这种"内存映射"方式使得CPU可以通过简单的指针操作完成数据交换,几乎零额外软件开销


2. EBI系统框图

pasted_1784792114679_uqlyxj.png
pasted_1784792133238_8k92xm.png

3. EBI开发手册下载

见文章末尾

4. EBI读写时序

EBI读时序

pasted_1784792256693_rqlsy7.png

EBI写时序

pasted_1784792336779_i0i8ah.png

5. EBI硬件连接与信号定义

以STM32F407 + FPGA(Xilinx/Altera)为例,采用16位数据宽度非复用地址模式,典型连接如下:

5.1 信号连接表

STM32 引脚FPGA 引脚信号说明
PD14~PD15, PE7~PE15IO_EBI_data[15:0]EBI数据总线 (16位双向)
PF0~PF5, PG0~PG13I_EBI_addr[23:0]EBI地址总线 (24位,可寻址16MB)
PG9I_EBI_CS_nEBI片选 (低有效)
PD4I_EBI_OE_nEBI读使能 (低有效)
PD5I_EBI_WE_nEBI写使能 (低有效)
I_sys_clkFPGA系统时钟 (≥100MHz)
I_rst_n系统复位 (低有效)

地址映射:Bank1_NE2 → 0x6400_0000 ~ 0x64FF_FFFF (16MB空间)

5.2 硬件设计注意事项

⚠️ 关键提示

  • FPGA的IO电平必须与STM32匹配(通常 3.3V LVTTL/LVCMOS33
  • 数据线建议串联 22~47Ω 电阻抑制信号反射,尤其是高速运行时
  • 地址线若不需要全部使用,可接地或接固定电平,减少FPGA引脚占用
  • 若使用等待信号(NWAIT),需在EBI配置中使能,并在FPGA中正确驱动
  • 航空电子场景需特别关注 EMC/EMI 设计,建议增加滤波和屏蔽

6. FPGA端Verilog接口设计

FPGA侧的核心任务是模拟一个"SRAM设备",响应STM32的读写时序。以下是基于异步SRAM模式的完整Verilog接口模块,直接对应你的实际工程代码:

6.1 模块接口定义

module EBI_LOCALBUS (
    /* --------------------- Input Signals --------------------- */
    input                                       I_sys_clk,          // 系统时钟
    input                                       I_rst_n,            // 系统复位
    input                                       I_EBI_CS_n,         // EBI片选
    input                                       I_EBI_WE_n,         // EBI写信号
    input                                       I_EBI_OE_n,         // EBI读信号
    input               [23 : 00]               I_EBI_addr,         // EBI地址 (24位)
    inout               [15 : 00]               IO_EBI_data,        // EBI数据总线 (16位双向)
    input               [15 : 00]               I_EBI_RD_data,      // CPU读数据 (来自内部模块)

    /* --------------------- Output Signals -------------------- */
    output              [15 : 00]               O_EBI_WR_data,      // CPU写数据 (输出到内部模块)
    output                                      O_EBI_CS_n,         // 片选转接
    output reg                                  O_EBI_WE_n,         // 写信号同步输出
    output                                      O_EBI_RD_n,         // 读信号同步输出
    output              [23 : 00]               O_EBI_addr          // 地址转接
);

6.2 核心设计要点

6.2.1 信号直通与数据三态控制

    // EBI片选直通
    assign O_EBI_CS_n = I_EBI_CS_n;

    // EBI地址直通
    assign O_EBI_addr = I_EBI_addr;

    // 写数据:EBI总线 → FPGA内部 (写操作时数据由STM32驱动)
    assign O_EBI_WR_data = IO_EBI_data;

    // 读数据:FPGA内部 → EBI总线 (仅在片选有效且读使能有效时驱动)
    assign IO_EBI_data = ( (I_EBI_CS_n == 1'b0) && (S_EBI_RD_N_d2 == 1'b0) ) 
                         ? I_EBI_RD_data : 16'bz;

关键设计

  • O_EBI_CS_nO_EBI_addr直通信号,不做时钟域转换,确保地址和片选的最小延迟
  • IO_EBI_data 采用三态控制:仅在 CS_n == 0RD_N_d2 == 0 时驱动读数据,否则置高阻态,避免总线冲突
  • O_EBI_WR_data 直接采样 IO_EBI_data,因为写操作时数据总线由STM32驱动,无需三态判断

3.2.2 写信号同步

    // EBI写信号打拍同步
    always @(posedge I_sys_clk or negedge I_rst_n) begin
        if (I_rst_n == 1'b0) begin
            O_EBI_WE_n <= 1'b1;     // 复位后写无效
        end
        else begin
            O_EBI_WE_n <= I_EBI_WE_n;
        end
    end

设计意图

  • 写信号经过1级同步,从异步EBI域转入FPGA系统时钟域
  • 同步后的 O_EBI_WE_n 可直接用于内部模块的写使能,配合 O_EBI_addrO_EBI_WR_data 完成一次完整的写操作

6.2.3 读信号同步

    reg S_EBI_RD_N_d0;
    reg S_EBI_RD_N_d1;
    reg S_EBI_RD_N_d2;

    // EBI读信号三级打拍
    always @(posedge I_sys_clk or negedge I_rst_n) begin
        if (I_rst_n == 1'b0) begin
            S_EBI_RD_N_d0 <= 1'b1;
            S_EBI_RD_N_d1 <= 1'b1;
            S_EBI_RD_N_d2 <= 1'b1;
        end
        else begin
            S_EBI_RD_N_d0 <= I_EBI_OE_n;
            S_EBI_RD_N_d1 <= S_EBI_RD_N_d0;
            S_EBI_RD_N_d2 <= S_EBI_RD_N_d1;
        end
    end

    assign O_EBI_RD_n = S_EBI_RD_N_d2;

为什么读信号需要3级同步?

读信号采用三级延迟(而非写信号的1级),原因如下:

同步级数作用
S_EBI_RD_N_d0第一级同步,消除亚稳态
S_EBI_RD_N_d1第二级同步,稳定信号
S_EBI_RD_N_d2第三级延迟,为FPGA内部模块预留数据准备时间

读操作需要FPGA内部模块在检测到读使能后,将数据放到 I_EBI_RD_data 上,再通过三态门驱动到 IO_EBI_data。三级延迟确保了:

  1. 内部模块有足够的时间响应读请求
  2. 数据在 S_EBI_RD_N_d2 == 0 时已经稳定
  3. 满足STM32 EBI的数据建立时间要求

7. 内部模块连接示例

EBI_LOCALBUS 模块本身是一个总线桥接器,需要配合内部功能模块使用。以下是一个典型的寄存器文件连接示例:

module internal_regfile (
    input               I_sys_clk,
    input               I_rst_n,
    input               I_cs_n,         // 来自 EBI_TO_LOCALBUS.O_EBI_CS_n
    input               I_we_n,         // 来自 EBI_TO_LOCALBUS.O_EBI_WE_n
    input               I_rd_n,         // 来自 EBI_TO_LOCALBUS.O_EBI_RD_n
    input       [23:0]  I_addr,         // 来自 EBI_TO_LOCALBUS.O_EBI_addr
    input       [15:0]  I_wr_data,      // 来自 EBI_TO_LOCALBUS.O_EBI_WR_data
    output reg  [15:0]  O_rd_data       // 送到 EBI_TO_LOCALBUS.I_EBI_RD_data
);

    reg [15:0] reg_file [0:255];  // 256个16位寄存器
    integer i;

    initial begin
        for (i = 0; i < 256; i = i + 1)
            reg_file[i] = 16'd0;
    end

    // 写操作:WE_n下降沿 (同步后的信号)
    always @(posedge I_sys_clk or negedge I_rst_n) begin
        if (!I_rst_n) begin
            // 复位逻辑
        end
        else if (!I_cs_n && !I_we_n) begin
            reg_file[I_addr[7:0]] <= I_wr_data;
        end
    end

    // 读操作:组合逻辑输出 (确保在RD_n有效时数据已稳定)
    always @(*) begin
        if (!I_cs_n && !I_rd_n)
            O_rd_data = reg_file[I_addr[7:0]];
        else
            O_rd_data = 16'h0000;
    end

endmodule

顶层连接

module top_fpga_stm32 (
    input               sys_clk,
    input               sys_rst_n,
    // EBI物理接口
    inout       [15:0]  fmc_data,
    input       [23:0]  fmc_addr,
    input               fmc_cs_n,
    input               fmc_oe_n,
    input               fmc_we_n
);

    wire [15:0] ebi_rd_data;
    wire [15:0] ebi_wr_data;
    wire        ebi_cs_n;
    wire        ebi_we_n;
    wire        ebi_rd_n;
    wire [23:0] ebi_addr;

    EBI_LOCALBUS u_ebi_bridge (
        .I_sys_clk      (sys_clk),
        .I_rst_n        (sys_rst_n),
        .I_EBI_CS_n     (fmc_cs_n),
        .I_EBI_WE_n     (fmc_we_n),
        .I_EBI_OE_n     (fmc_oe_n),
        .I_EBI_addr     (fmc_addr),
        .IO_EBI_data    (fmc_data),
        .I_EBI_RD_data  (ebi_rd_data),
        .O_EBI_WR_data  (ebi_wr_data),
        .O_EBI_CS_n     (ebi_cs_n),
        .O_EBI_WE_n     (ebi_we_n),
        .O_EBI_RD_n     (ebi_rd_n),
        .O_EBI_addr     (ebi_addr)
    );

    internal_regfile u_regfile (
        .I_sys_clk      (sys_clk),
        .I_rst_n        (sys_rst_n),
        .I_cs_n         (ebi_cs_n),
        .I_we_n         (ebi_we_n),
        .I_rd_n         (ebi_rd_n),
        .I_addr         (ebi_addr),
        .I_wr_data      (ebi_wr_data),
        .O_rd_data      (ebi_rd_data)
    );

endmodule

8. STM32端HAL配置与驱动

STM32侧使用HAL库配置FMC/EBI为异步SRAM模式。以下代码基于STM32F407,使用Bank1_NE2(对应地址 0x64000000):

8.1 GPIO初始化

#include "stm32f4xx_hal.h"

#define FPGA_BASE_ADDR    0x64000000

// 内存映射指针 (16位访问)
volatile uint16_t *fpga_regs = (volatile uint16_t *)FPGA_BASE_ADDR;

static void FMC_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_GPIOF_CLK_ENABLE();
    __HAL_RCC_GPIOG_CLK_ENABLE();

    // 数据总线 D0~D15: PD14/15, PE7~15
    GPIO_InitStruct.Pin = GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FMC;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 |
                          GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12 |
                          GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    // 地址总线 A0~A5: PF0~PF5 (可扩展至A23)
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 |
                          GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    // 控制信号: NOE(PD4), NWE(PD5), NE2(PG9)
    GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_9;
    HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
}

8.2 FMC异步SRAM模式初始化

void FMC_FPGA_Init(void)
{
    SRAM_HandleTypeDef hsram = {0};
    FMC_NORSRAM_TimingTypeDef Timing = {0};

    FMC_GPIO_Init();
    __HAL_RCC_FMC_CLK_ENABLE();

    hsram.Instance = FMC_NORSRAM_DEVICE;
    hsram.Extended = FMC_NORSRAM_EXTENDED_DEVICE;

    // Bank1_NE2, 16位数据宽度, SRAM类型
    hsram.Init.NSBank = FMC_NORSRAM_BANK2;
    hsram.Init.DataAddressMux = FMC_DATA_ADDRESS_MUX_DISABLE;  // 非复用
    hsram.Init.MemoryType = FMC_MEMORY_TYPE_SRAM;
    hsram.Init.MemoryDataWidth = FMC_NORSRAM_MEM_BUS_WIDTH_16;
    hsram.Init.BurstAccessMode = FMC_BURST_ACCESS_MODE_DISABLE;
    hsram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW;
    hsram.Init.WrapMode = FMC_WRAP_MODE_DISABLE;
    hsram.Init.WaitSignalActive = FMC_WAIT_TIMING_BEFORE_WS;
    hsram.Init.WriteOperation = FMC_WRITE_OPERATION_ENABLE;
    hsram.Init.WaitSignal = FMC_WAIT_SIGNAL_DISABLE;
    hsram.Init.ExtendedMode = FMC_EXTENDED_MODE_DISABLE;
    hsram.Init.AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_DISABLE;
    hsram.Init.WriteBurst = FMC_WRITE_BURST_DISABLE;
    hsram.Init.ContinuousClock = FMC_CONTINUOUS_CLOCK_SYNC_ONLY;

    // 时序配置 (HCLK = 168MHz, 1周期 ≈ 5.95ns)
    // 需根据FPGA时序报告调整,满足建立/保持时间
    Timing.AddressSetupTime = 2;      // 地址建立: ~12ns
    Timing.AddressHoldTime = 1;       // 地址保持
    Timing.DataSetupTime = 5;         // 数据建立: ~30ns
    Timing.BusTurnAroundDuration = 1; // 总线周转
    Timing.CLKDivision = 2;
    Timing.DataLatency = 2;
    Timing.AccessMode = FMC_ACCESS_MODE_A;

    HAL_SRAM_Init(&hsram, &Timing, &Timing);
}

8.3 读写驱动函数

/**
 * @brief 向FPGA寄存器写入16位数据
 */
static inline void FPGA_WriteReg(uint16_t reg, uint16_t value)
{
    fpga_regs[reg] = value;
    __DSB();  // 数据同步屏障,确保写入完成
}

/**
 * @brief 从FPGA寄存器读取16位数据
 */
static inline uint16_t FPGA_ReadReg(uint16_t reg)
{
    uint16_t val = fpga_regs[reg];
    __DSB();
    return val;
}

/**
 * @brief 批量数据写入
 */
void FPGA_WriteBuffer(uint16_t reg_base, uint16_t *data, uint32_t len)
{
    for (uint32_t i = 0; i < len; i++) {
        fpga_regs[reg_base + i] = data[i];
    }
    __DSB();
}

/**
 * @brief 批量数据读取
 */
void FPGA_ReadBuffer(uint16_t reg_base, uint16_t *data, uint32_t len)
{
    for (uint32_t i = 0; i < len; i++) {
        data[i] = fpga_regs[reg_base + i];
    }
    __DSB();
}

9. 读写时序分析

9.1 读时序 (FPGA → STM32)

读时序要点

  • I_EBI_OE_n 下降沿后,FPGA内部模块开始准备数据
  • 经过3个时钟周期(d0 → d1 → d2),S_EBI_RD_N_d2 变为低电平
  • 三态门在 S_EBI_RD_N_d2 == 0 时使能,将 I_EBI_RD_data 驱动到 IO_EBI_data
  • STM32在 NOE 上升沿采样数据,需确保数据在采样前已稳定(满足DataSetupTime)

9.2 写时序 (STM32 → FPGA)

写时序要点

  • I_EBI_WE_n 下降沿表示写周期开始,STM32地址和数据已稳定
  • O_EBI_WE_n 在1个时钟周期后同步到低电平,内部模块在时钟上升沿检测 !O_EBI_WE_n && !O_EBI_CS_n 执行写操作
  • O_EBI_WR_data 直接采样 IO_EBI_data,无需额外延迟,因为写操作时数据总线由STM32持续驱动

10. 关键设计决策说明

10.1 为什么写信号只用1级同步,读信号用3级?

信号同步级数原因
I_EBI_WE_nO_EBI_WE_n1级写操作对FPGA是"被动接收",数据总线由STM32驱动,FPGA只需在时钟沿采样稳定的数据即可
I_EBI_OE_nS_EBI_RD_N_d23级读操作对FPGA是"主动输出",需要给内部模块预留时间准备数据,再通过三态门驱动到总线

10.2 三态门控制逻辑

assign IO_EBI_data = ( (I_EBI_CS_n == 1'b0) && (S_EBI_RD_N_d2 == 1'b0) ) 
                     ? I_EBI_RD_data : 16'bz;

使用 S_EBI_RD_N_d2 而非 I_EBI_OE_n 的原因

  • 直接使用 I_EBI_OE_n 会导致FPGA在STM32还未准备好接收数据时就驱动总线,可能产生总线冲突
  • 使用延迟后的 S_EBI_RD_N_d2 确保:① 内部数据已准备好;② STM32的读使能确实有效;③ 满足数据建立时间

10.3 地址直通 vs 同步

assign O_EBI_addr = I_EBI_addr;  // 直通,无时钟延迟

地址采用组合逻辑直通而非同步,原因:

  • 地址在EBI访问期间保持稳定,无需跨时钟域处理
  • 减少地址到内部模块的延迟,提高访问速度
  • 地址稳定性由STM32硬件保证

EBI开发手册下载

支付宝支付
价格: 0.30 元
温馨提示:免登录付款后7天内可重复阅读隐藏内容,登录用户付款后可永久阅读隐藏的内容。付费后如无反应,刷新网页即可阅读。 付费可读
© 版权声明
THE END
喜欢就支持一下吧
点赞 2 分享 赞赏
评论 抢沙发
取消