STM32H743 与 FPGA 通过 FMC(Flexible Static Memory Controller)接口通信是一种高效、常见的异构系统互联方案,尤其适用于需要高速、大容量数据交换的场景,如工业控制、高速数据采集和信号处理。

STM32H743 的 FMC 是一个高度灵活的静态存储器控制器,原本设计用于连接 NOR Flash、SRAM、NAND Flash 等存储器。通过将其配置为 SRAM / NOR Flash 模式,并连接 FPGA 端的双端口 RAM 或寄存器接口,可以实现两者间的并行数据交互。这种方案能让 STM32 像访问内部存储器一样,直接通过地址总线读写 FPGA 内的数据,延迟极低。

硬件连接与配置要点

  1. 引脚分配:通常使用 16 位数据总线、地址总线以及控制信号(如片选 NE1、读使能 NOE、写使能 NWE、地址有效 NADV)实现连接。
  2. 电平匹配:必须确保 STM32H743 的 FMC 接口电压(通常是 3.3V)与 FPGA 的 IO 电压兼容。不匹配时需添加电平转换芯片。
  3. 时序配置:FMC 的时序参数(如 DataSetupTimeAddressSetupTime)需根据 FPGA 的响应速度进行配置。时序过紧会导致数据不稳,过松则会降低传输速率。

    关键软件实现与优化

  4. 地址映射访问:STM32 端可将 FPGA 的寄存器映射到 FMC 的地址空间(如 0x60000000),通过宏定义实现便捷的读写操作。
    #define FPGA_REG(addr)  (*(volatile uint16_t *)(0x60000000 | ((addr) << 1)))
    // 写入 FPGA 寄存器
    FPGA_REG(0x10) = 0xABCD;
    // 读取 FPGA 寄存器
    uint16_t status = FPGA_REG(0x11);
  5. DMA 传输:对于批量数据,配合 DMA 使用 FMC 可以大幅降低 CPU 负载,实现接近内存到内存的传输效率。
  6. MPU 配置是必须的:STM32H743 的 Cortex-M7 内核带有缓存,直接访问 FMC 地址时可能会因缓存一致性导致数据错乱。必须通过 MPU(内存保护单元) 将 FMC 所映射的地址区域配置为 Strongly-orderedDevice 类型,以禁止缓存,保证每次读写都直接访问 FPGA 硬件,避免数据丢失或错位。
  7. FPGA 端逻辑:FPGA 端需要实现一个双端口 RAM 作为数据缓冲区,并通过地址译码逻辑响应 STM32 的读写请求。同时,需注意将 FMC 的异步控制信号同步到 FPGA 的时钟域,并对双向数据总线进行三态控制。

时序参数怎么算?

H743 的 HCLK3 最高 240MHz,FMC 时钟 = HCLK3 / (CLKDivision + 1)。通常设 CLKDivision=1(即 FMC_CLK = 120MHz),但异步 SRAM 模式实际上不用 FMC_CLK,时序完全由 ADDSET/DATAST/BUSTURN 以 HCLK3 周期计算:

参数 含义 典型值 时间 (HCLK3=240MHz)
ADDSET 地址建立到 NOE/NWE 拉低 2 8.3 ns
DATAST NOE/NWE 低电平持续时间 4-6 16.6-25 ns
BUSTURN 总线恢复(读转写或连续读间隔) 1-2 4.2-8.3 ns

FPGA 端逻辑通常跑 50-100MHz,所以 DATAST 至少给 4 个 HCLK3 周期(~33ns),确保 FPGA 有时间响应。如果 FPGA 逻辑慢,用 NWAIT 信号动态插入等待周期。

常见踩坑清单:

  1. 数据总线悬空 — FPGA 的 data 引脚必须实现三态,不读时释放为 z。上面的 Verilog 用 assign fmc_data = data_drive_en ? read_data_r : 16'bz; 处理了这个问题。

  2. 字节对齐 — 16 位总线模式下,偶地址访问 D[7:0],奇地址访问 D[15:8]。如果你用 uint16_t* 指针,地址必须偶数对齐,否则触发 HardFault。

  3. Cache 问题 — H743 有 D-Cache,FMC 地址 0x60000000 默认是 Cacheable 的。如果 FPGA 寄存器值随时变化(状态寄存器、FIFO),必须把 MPU 把 0x60000000 区域设为 Device 或 Non-Cacheable,否则读到的是缓存旧值。

  4. __DSB() 屏障 — 每次写完 FPGA 寄存器后加 __DSB(),确保写操作真正到达总线再继续执行后续代码。

  5. NWAIT 时序 — 如果用 NWAIT,FMC 需要开启 AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_ENABLE,FPGA 在需要延长时拉低 NWAIT,FMC 会等待。

  6. MDMA/DMA 带宽 — 大块数据传输用 MDMA 做 memory-to-memory 搬运,比 CPU 逐字写快很多。FMC 的突发写可以达到 ~30-40 MB/s(16位@120MHz)。

MPU 配置示例(关闭 FMC 区域 Cache):

void MPU_ConfigForFMC(void)
{
    MPU_Region_InitTypeDef MPU_InitStruct = {0};
    HAL_MPU_Disable();

    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_1MB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;  // Critical!
    MPU_InitStruct.IsShareable      = MPU_ACCESS_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_DISABLE;
    HAL_MPU_ConfigRegion(&MPU_InitStruct);
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

代码示例:

/**
 * STM32H743 FMC + FPGA communication example
 * FMC configured as SRAM mode, 16-bit data bus, Bank1 NE1
 *
 * Memory map:
 *   Bank1 NE1 base address: 0x6000_0000
 *   FPGA register space:   0x6000_0000 ~ 0x600F_FFFF (1MB)
 *
 * HCLK3 = 240 MHz, FMC clock = HCLK3 / (divider+1)
 * With divider=1 (FMC prescaler /2), FMC_CLK = 120 MHz
 */

#include "stm32h7xx_hal.h"
#include <string.h>

/* ============================================================
 * FPGA register address definitions
 * ============================================================ */

/* Bank1, NE1 base address (defined by STM32H743 FMC memory map) */
#define FPGA_BASE_ADDR      0x60000000U

/* FPGA internal register offsets (word offset, each = 2 bytes since 16-bit bus) */
#define FPGA_REG_ID         0x00    /* Read-only: FPGA firmware ID */
#define FPGA_REG_CTRL       0x02    /* Control register */
#define FPGA_REG_STATUS     0x04    /* Status register */
#define FPGA_REG_DATA_LEN   0x06    /* Data length */
#define FPGA_REG_DATA_PORT  0x08    /* Data FIFO port (read/write) */
#define FPGA_REG_IRQ_EN     0x0A    /* Interrupt enable */
#define FPGA_REG_VERSION    0x0C    /* Firmware version */

/* Register pointer macros */
#define FPGA_REG(off)       (*(volatile uint16_t *)(FPGA_BASE_ADDR + (off)))

/* ============================================================
 * FMC GPIO initialization
 * D0-D15, A0-A18, NE1, NOE, NWE, NBL0, NBL1, NWAIT
 * ============================================================ */
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();
    __HAL_RCC_GPIOH_CLK_ENABLE();

    /* Configure FMC pins: all alternate function, push-pull, speed very high */
    GPIO_InitStruct.Mode      = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull      = GPIO_PULLUP;
    GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_FMC;

    /* --- Data bus D0-D15 --- */
    /* PD0=D0, PD1=D1, PD8=D8, PD9=D9, PD10=D10, PD14=D14, PD15=D15 */
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_8 | GPIO_PIN_9 |
                          GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    /* PE7=D4, PE8=D5, PE9=D6, PE10=D7, PE11=D12, PE12=D13, PE13=D14, PE14=D15 */
    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;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    /* PE0=NBL0, PE1=NBL1 */
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    /* --- Address bus A0-A18 --- */
    /* PF0=A0, PF1=A1, PF2=A2, PF3=A3, PF4=A4, PF5=A5, PF12=A6, PF13=A7, PF14=A8, PF15=A9 */
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3 |
                          GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_12 | GPIO_PIN_13 |
                          GPIO_PIN_14 | GPIO_PIN_15;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);

    /* PG0=A10, PG1=A11, PG2=A12, PG3=A13, PG4=A14, PG5=A15 */
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 |
                          GPIO_PIN_3 | GPIO_PIN_4 | GPIO_PIN_5;
    HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    /* PG9=A16, PG10=A17, PG12=A18 */
    GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_12;
    HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    /* --- Control signals --- */
    /* PD4=NOE, PD5=NWE, PD7=NE1 */
    GPIO_InitStruct.Pin = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_7;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    /* NWAIT input on PD6 (optional) */
    GPIO_InitStruct.Pin   = GPIO_PIN_6;
    GPIO_InitStruct.Mode  = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
}

/* ============================================================
 * FMC SRAM mode initialization
 * Configured for FPGA communication
 * ============================================================ */
static void FMC_SRAM_Init(void)
{
    FMC_NORSRAM_TimingTypeDef Timing = {0};

    __HAL_RCC_FMC_CLK_ENABLE();

    /* --- Timing configuration ---
     * HCLK3 = 240 MHz, FMC prescaler = FMC_NORSRAM_FLASH_ACCESS_NO_DIVIDER
     *   => FMC_CLK = HCLK3 / 2 = 120 MHz (8.33 ns per cycle)
     *
     * For FPGA at ~50 MHz internal logic, typical safe timing:
     *   ADDSET   = 2  (16.6 ns address setup)
     *   DATAST   = 4  (33.3 ns data valid time for FPGA to respond)
     *   BUSTURN  = 2  (16.6 ns bus turnaround)
     *
     * Total read cycle  = ADDSET + DATAST + 1 = 7 cycles ≈ 58 ns
     * Total write cycle = ADDSET + DATAST + 1 = 7 cycles ≈ 58 ns
     */
    Timing.AddressSetupTime      = 2;
    Timing.AddressHoldTime       = 1;
    Timing.DataSetupTime         = 4;
    Timing.BusTurnAroundDuration = 2;
    Timing.CLKDivision           = 1;   /* FMC_CLK = HCLK3/2 */
    Timing.DataLatency           = 2;
    Timing.AccessMode            = FMC_ACCESS_MODE_A;  /* SRAM mode A */

    hsram.Instance               = FMC_NORSRAM_DEVICE;
    hsram.Extended               = FMC_NORSRAM_EXTENDED_DEVICE;
    hsram.Init.NSBank            = FMC_NORSRAM_BANK1;     /* Use NE1 */
    hsram.Init.DataAddressMux    = FMC_DATA_ADDRESS_MUX_DISABLE;
    hsram.Init.MemoryType        = FMC_MEMORY_TYPE_SRAM;
    hsram.Init.MemoryDataWidth   = FMC_NORSRAM_MEM_BUS_WIDTH_16;  /* 16-bit */
    hsram.Init.BurstAccessMode   = FMC_BURST_ACCESS_MODE_DISABLE;
    hsram.Init.WaitSignalPolarity = FMC_WAIT_SIGNAL_POLARITY_LOW;
    hsram.Init.WaitSignalActive  = FMC_WAIT_TIMING_BEFORE_WS;
    hsram.Init.WriteOperation    = FMC_WRITE_OPERATION_ENABLE;
    hsram.Init.WaitSignal        = FMC_WAIT_SIGNAL_DISABLE;  /* Set ENABLE if using NWAIT */
    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_ASYNC;

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

/* ============================================================
 * High-level FPGA access functions
 * ============================================================ */

/**
 * Write a 16-bit value to FPGA register at given offset
 * @param offset  Register offset (must be even for 16-bit bus)
 * @param value   16-bit data to write
 */
void FPGA_WriteReg(uint16_t offset, uint16_t value)
{
    FPGA_REG(offset) = value;
    __DSB();  /* Data Synchronization Barrier - ensure write completes */
}

/**
 * Read a 16-bit value from FPGA register at given offset
 * @param offset  Register offset (must be even for 16-bit bus)
 * @return        16-bit data read from FPGA
 */
uint16_t FPGA_ReadReg(uint16_t offset)
{
    uint16_t val = FPGA_REG(offset);
    __DSB();
    return val;
}

/**
 * Burst write data block to FPGA
 * Uses pointer increment for fast transfer
 * @param offset  Starting register offset (FIFO port recommended)
 * @param buf     Source data buffer
 * @param len     Number of 16-bit words to write
 */
void FPGA_BurstWrite(uint16_t offset, const uint16_t *buf, uint32_t len)
{
    volatile uint16_t *p = (volatile uint16_t *)(FPGA_BASE_ADDR + offset);
    for (uint32_t i = 0; i < len; i++) {
        *p++ = buf[i];
    }
    __DSB();
}

/**
 * Burst read data block from FPGA
 * @param offset  Starting register offset (FIFO port recommended)
 * @param buf     Destination buffer
 * @param len     Number of 16-bit words to read
 */
void FPGA_BurstRead(uint16_t offset, uint16_t *buf, uint32_t len)
{
    volatile uint16_t *p = (volatile uint16_t *)(FPGA_BASE_ADDR + offset);
    for (uint32_t i = 0; i < len; i++) {
        buf[i] = *p++;
    }
    __DSB();
}

/**
 * Use MDMA for high-speed DMA transfer to FPGA
 * FMC can be MDMA destination/source in memory-to-memory mode
 */
void FPGA_MDMA_Write(uint16_t offset, const uint16_t *src, uint32_t len)
{
    /* Configure MDMA channel for memory-to-memory transfer */
    /* Source: internal SRAM buffer */
    /* Destination: FMC bank1 address (FPGA) */
    /* This achieves near-maximum FMC bandwidth */

    extern MDMA_HandleTypeDef hmdma_fpga;

    HAL_MDMA_Start(&hmdma_fpga,
                   (uint32_t)src,
                   (uint32_t)(FPGA_BASE_ADDR + offset),
                   len * 2,        /* byte count */
                   128);            /* 128-byte block transfer */

    /* Wait for transfer complete */
    HAL_MDMA_PollForTransfer(&hmdma_fpga, HAL_MDMA_FULL_TRANSFER, 1000);
}

/* ============================================================
 * Example: Initialize FPGA and transfer data
 * ============================================================
 */
void FPGA_Example(void)
{
    uint16_t fpga_id;
    uint16_t tx_buf[256];
    uint16_t rx_buf[256];

    /* Initialize FMC GPIO and controller */
    FMC_GPIO_Init();
    FMC_SRAM_Init();

    /* Wait for FPGA to be ready */
    HAL_Delay(100);

    /* Read FPGA ID to verify connection */
    fpga_id = FPGA_ReadReg(FPGA_REG_ID);
    if (fpga_id != 0xFPGA) {  /* Replace with your expected ID */
        /* Handle error: FPGA not responding */
        return;
    }

    /* Prepare test data */
    for (int i = 0; i < 256; i++) {
        tx_buf[i] = (uint16_t)(i & 0xFFFF);
    }

    /* Write 256 words to FPGA data FIFO */
    FPGA_WriteReg(FPGA_REG_DATA_LEN, 256);
    FPGA_BurstWrite(FPGA_REG_DATA_PORT, tx_buf, 256);

    /* Trigger processing */
    FPGA_WriteReg(FPGA_REG_CTRL, 0x0001);  /* Start bit */
    __DSB();

    /* Wait for done (poll status register) */
    while ((FPGA_ReadReg(FPGA_REG_STATUS) & 0x01) == 0) {
        /* Timeout handling recommended */
    }

    /* Read back processed data */
    FPGA_BurstRead(FPGA_REG_DATA_PORT, rx_buf, 256);

    /* Verify data integrity */
    for (int i = 0; i < 256; i++) {
        if (rx_buf[i] != tx_buf[i]) {
            /* Data mismatch */
        }
    }
}
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐