STM32H743 FMC与FPGA通信
STM32H743 与 FPGA 通过 FMC(Flexible Static Memory Controller)接口通信是一种高效、常见的异构系统互联方案,尤其适用于需要高速、大容量数据交换的场景,如工业控制、高速数据采集和信号处理。
STM32H743 的 FMC 是一个高度灵活的静态存储器控制器,原本设计用于连接 NOR Flash、SRAM、NAND Flash 等存储器。通过将其配置为 SRAM / NOR Flash 模式,并连接 FPGA 端的双端口 RAM 或寄存器接口,可以实现两者间的并行数据交互。这种方案能让 STM32 像访问内部存储器一样,直接通过地址总线读写 FPGA 内的数据,延迟极低。
硬件连接与配置要点
- 引脚分配:通常使用 16 位数据总线、地址总线以及控制信号(如片选 NE1、读使能 NOE、写使能 NWE、地址有效 NADV)实现连接。
- 电平匹配:必须确保 STM32H743 的 FMC 接口电压(通常是 3.3V)与 FPGA 的 IO 电压兼容。不匹配时需添加电平转换芯片。
- 时序配置:FMC 的时序参数(如
DataSetupTime、AddressSetupTime)需根据 FPGA 的响应速度进行配置。时序过紧会导致数据不稳,过松则会降低传输速率。关键软件实现与优化
- 地址映射访问: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); - DMA 传输:对于批量数据,配合 DMA 使用 FMC 可以大幅降低 CPU 负载,实现接近内存到内存的传输效率。
- MPU 配置是必须的:STM32H743 的 Cortex-M7 内核带有缓存,直接访问 FMC 地址时可能会因缓存一致性导致数据错乱。必须通过 MPU(内存保护单元) 将 FMC 所映射的地址区域配置为 Strongly-ordered 或 Device 类型,以禁止缓存,保证每次读写都直接访问 FPGA 硬件,避免数据丢失或错位。
- 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 信号动态插入等待周期。


常见踩坑清单:
-
数据总线悬空 — FPGA 的 data 引脚必须实现三态,不读时释放为 z。上面的 Verilog 用
assign fmc_data = data_drive_en ? read_data_r : 16'bz;处理了这个问题。 -
字节对齐 — 16 位总线模式下,偶地址访问 D[7:0],奇地址访问 D[15:8]。如果你用
uint16_t*指针,地址必须偶数对齐,否则触发 HardFault。 -
Cache 问题 — H743 有 D-Cache,FMC 地址 0x60000000 默认是 Cacheable 的。如果 FPGA 寄存器值随时变化(状态寄存器、FIFO),必须把 MPU 把 0x60000000 区域设为 Device 或 Non-Cacheable,否则读到的是缓存旧值。
-
__DSB() 屏障 — 每次写完 FPGA 寄存器后加
__DSB(),确保写操作真正到达总线再继续执行后续代码。 -
NWAIT 时序 — 如果用 NWAIT,FMC 需要开启
AsynchronousWait = FMC_ASYNCHRONOUS_WAIT_ENABLE,FPGA 在需要延长时拉低 NWAIT,FMC 会等待。 -
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 */
}
}
}
更多推荐


所有评论(0)