跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C

单片机与 FPGA 通信方式及 STM32 高速并行接口实现

单片机与 FPGA 的多种通信方式,包括 GPIO、UART、SPI、I2C 及并行总线。重点讲解了 STM32 系列单片机的 EXMC 和 FSMC 接口,将其作为外部存储器映射以实现百 Mbps 级高速并行通信。内容涵盖硬件连接、寄存器配置、C 语言驱动代码及 FPGA Verilog 逻辑实现,并提供了时序调整、电平匹配等实操注意事项,适用于大数据量低延迟传输场景。

技术博主发布于 2026/4/6更新于 2026/7/538 浏览

了解单片机与 FPGA 之间的通信方式,这是嵌入式系统中非常常见的硬件交互场景,核心是要根据传输速率、硬件资源、开发复杂度选择合适的通信协议。

一、主流通信方式及实现方案

单片机和 FPGA 通信主要分为并行通信和串行通信两大类,下面按从易到难、从低速到高速的顺序介绍:

1. 通用 IO 口(GPIO)自定义协议(最简单)

适合低速、短距离、数据量小的场景(如按键、状态交互),完全自定义通信规则,开发灵活。

  • 硬件连接:
    • 单片机:1 个输出引脚(发送) + 1 个输入引脚(接收)
    • FPGA:1 个输入引脚(接收) + 1 个输出引脚(发送)
    • 需共地,建议加 10K 上拉电阻提高稳定性。

软件实现(以 51 单片机 + Verilog 为例)

#include <reg51.h>
#define uchar unsigned char
sbit TX = P1^0; // 发送引脚
sbit RX = P1^1; // 接收引脚
// 自定义协议:起始位 (0)+8 位数据 + 停止位 (1),波特率约 9600(延时约 104us)
void send_byte(uchar dat) {
    uchar i;
    TX = 0; // 起始位
    delay_us(104); // 需自行实现微秒级延时函数
    for(i=0; i<8; i++) { // 发送 8 位数据(低位先行)
        TX = dat & 0x01;
        dat >>= 1;
        delay_us(104);
    }
    TX = 1; // 停止位
    delay_us(104);
}
module uart_rx(
    input clk,      // FPGA 时钟(如 50MHz)
    input rx,       // 接收引脚(接单片机 TX)
    output reg [7:0] data_out, // 接收的数据
    output reg rx_done          // 接收完成标志
);
    // 时钟分频:50MHz -> 9600 波特率(计数到 5208)
    reg [12:0] cnt;
    reg [3:0] bit_cnt;
    reg rx_r; // 打拍消抖
    always @(posedge clk) begin
        rx_r <= rx;
    end
    always @(posedge clk) begin
        if(rx_done) begin
            cnt <= 0;
            bit_cnt <= 0;
            rx_done <= 0;
        end else if(bit_cnt == 0 && rx_r == 0) begin // 检测起始位
            if(cnt == 2604) begin // 中间采样
                cnt <= 0;
                bit_cnt <= 1;
            end else begin
                cnt <= cnt + 1;
            end
        end else if(bit_cnt > 0 && bit_cnt < 9) begin // 接收 8 位数据 + 停止位
            if(cnt == 5208) begin
                cnt <= 0;
                if(bit_cnt < 8) begin
                    data_out <= {rx_r, data_out[7:1]}; // 低位先行
                end
                bit_cnt <= bit_cnt + 1;
            end else begin
                cnt <= cnt + 1;
            end
        end else if(bit_cnt == 9) begin // 接收完成
            rx_done <= 1;
        end
    end
endmodule
2. UART 串口通信(最常用)

标准化异步串行协议,开发难度低、硬件资源占用少,速率一般≤1Mbps,适合大多数场景。

  • 硬件连接:
    • 单片机 TX → FPGA RX
    • 单片机 RX ← FPGA TX
    • 共地(关键!否则通信乱码)
    • 注意电平匹配:5V 单片机需加电平转换芯片(如 MAX3232)适配 FPGA 的 3.3V 电平。
  • 核心优势:
    • 单片机端可直接调用串口库函数(如 STM32 的 HAL_UART_Transmit),无需自定义时序;
    • FPGA 端可直接使用成熟的 UART IP 核,无需手动编写时序逻辑。
3. SPI 通信(高速串行)

同步串行协议,速率可达几十 Mbps,支持全双工,适合高速数据传输(如 AD/DA 数据、配置参数)。

  • 硬件连接(主从模式,通常单片机为主,FPGA 为从):
    • SCK:单片机输出 → FPGA 输入(时钟线)
    • MOSI:单片机输出 → FPGA 输入(主机发、从机收)
    • MISO:单片机输入 ← FPGA 输出(从机发、主机收)
    • CS:单片机输出 → FPGA 输入(片选线,低有效)
  • 实现要点:
    • 单片机端:配置 SPI 为主机模式,设置时钟极性(CPOL)和相位(CPHA);
    • FPGA 端:根据 SPI 时序,在 SCK 时钟沿采样 MOSI 数据,在对应时钟沿输出 MISO 数据。
4. I2C 通信(多设备组网)

双线制同步串行协议,支持多主多从,地址寻址,适合多设备组网场景(如 FPGA 扩展传感器,单片机统一控制)。

  • 硬件连接:
    • SDA:双向数据线(单片机↔FPGA)
    • SCL:时钟线(通常单片机为主,输出时钟)
    • 需在 SDA/SCL 线上拉 4.7KΩ 电阻到电源。
  • 注意事项:
    • I2C 是开漏输出,需严格遵循起始位、停止位、应答位时序;
    • FPGA 端实现 I2C 从机比 UART/SPI 稍复杂,需处理应答逻辑。
5. 并行总线(超高速)

直接通过多根数据线(如 8/16/32 位)+ 地址线 + 控制信号线通信,速率可达百 Mbps 级,适合大数据量、低延迟传输(如图像数据、高速采集)。

  • 硬件连接:
    • 数据线:D0~D7(8 位)→ 单片机 IO 口 ↔ FPGA IO 口
    • 控制信号:WR(写)、RD(读)、CS(片选)→ 单片机输出 → FPGA 输入
  • 缺点:占用大量 IO 口,布线复杂,抗干扰能力弱,仅适合板内短距离通信。
二、选型建议
通信方式速率硬件复杂度开发难度适用场景
GPIO 自定义低速(<100Kbps)最低低简单状态交互(如启停、复位)
UART中速(≤1Mbps)低极低常规数据传输(指令、状态)
SPI高速(几十 Mbps)中中高速数据传输(采集、配置)
I2C中速(≤400Kbps)中中多设备组网控制
并行总线超高速(百 Mbps)高中大数据量低延迟传输(图像)
总结
  1. 优先选 UART:开发最简单、通用性最强,满足 80% 以上的单片机 - FPGA 通信需求;
  2. 高速选 SPI:速率比 UART 高一个量级,硬件和开发复杂度适中;
  3. 简单交互选 GPIO:无需依赖外设,仅用通用 IO 口即可实现;
  4. 无论哪种方式,共地和电平匹配是通信稳定的关键,务必优先保证硬件连接正确。

在单片机与 FPGA 的高速通信中,EXMC(外部存储器控制器) 是 STM32 系列单片机的核心硬核接口,本质是并行总线控制器,专为外接 SRAM/NOR Flash/PSRAM 等外部存储器设计,也能直接适配 FPGA 实现百 Mbps 级超高速并行通信,是比普通 GPIO 自定义并行总线更稳定、更高效的方案,完美解决大数据量(如图像、高速采集数据)的低延迟传输需求。

下面从核心原理、硬件连接、通信配置、FPGA 适配、实操要点全维度讲解 STM32 的 EXMC 与 FPGA 的通信实现,以最常用的STM32F4/F7/H7 系列(EXMC 功能完善)和8 位并行通信为例(16/32 位可直接扩展)。

一、EXMC 核心原理:把 FPGA 当作'外部存储器'

EXMC 的核心逻辑是将 FPGA 映射为 STM32 的一片外部存储器,STM32 通过地址线、数据线、控制信号线对 FPGA 进行'读 / 写操作',FPGA 端只需模拟外部存储器的时序逻辑,即可实现与 STM32 的双向数据交互。

1. EXMC 的关键特性(适配 FPGA 通信)
  • 支持8/16/32 位数据宽度,速率由 STM32 时钟和时序配置决定,常规 8 位通信速率可达100~200Mbps(远高于 SPI/UART);
  • 提供独立的片选线(NCS)、读写控制线(NOE/NWE),时序可通过寄存器灵活配置(建立时间、保持时间),适配不同 FPGA 的响应速度;
  • 属于硬核外设,无需 CPU 频繁干预,数据传输由硬件完成,占用 CPU 资源极少;
  • 支持同步 / 异步模式,与 FPGA 通信优先用异步模式(无需时钟同步,硬件连接和时序实现更简单)。
2. EXMC 的总线分类(FPGA 通信常用NOR Flash 模式)

EXMC 分为多个总线区域,其中Bank1(对应 NOR Flash/PSRAM)是与 FPGA 通信的核心区域,Bank1 又分为 4 个子区(CE1CE4),每个子区对应独立的片选线(NCS1NCS4),可分别映射不同的 FPGA 逻辑模块,支持多设备扩展。

EXMC Bank1 子区片选引脚核心控制线地址 / 数据线
子区 1(CE1)NCS1NOE(读)、NWE(写)A0A25、D0D31
子区 2(CE2)NCS2NOE、NWEA0A25、D0D31
二、硬件连接:STM32 EXMC(Bank1)↔ FPGA

以STM32F407(8 位异步通信、Bank1 子区 1)和FPGA(如 XC7A35/EP4CE6) 为例,核心是数据线、地址线、控制信号线的直连,无需额外电平转换(STM32F4 为 3.3V,FPGA 主流也是 3.3V,电平匹配),必须共地。

核心硬件连接(8 位数据宽度)
STM32 EXMC 引脚信号功能FPGA 引脚备注
D0~D78 位双向数据线IO 口(双向)双向传输,FPGA 需配置为 IO 双向口
A0(可省)地址线IO 口(输入)若仅做'单地址数据传输',可省地址线,仅用片选 + 读写控制
NCS1(PB7)片选线(低有效)IO 口(输入)FPGA 的通信使能信号
NOE(PD4)读控制线(低有效)IO 口(输入)STM32 读 FPGA 时拉低
NWE(PD5)写控制线(低有效)IO 口(输入)STM32 写 FPGA 时拉低
GND地GND强推:多点共地,降低干扰
简化版连接(无地址线,最常用)

如果仅实现STM32 与 FPGA 的双向数据块传输,无需地址寻址,可直接省略所有地址线(A0~A25),仅通过NCS1(片选)+ NOE(读)+ NWE(写) 控制数据传输,大幅减少 FPGA 引脚占用,硬件布线更简单。

三、STM32 端配置:EXMC 异步模式初始化

STM32 端的核心是配置 EXMC 为 Bank1 子区 1 的 8 位异步模式,并将 FPGA 映射为片外存储地址,后续通过直接访问地址的方式完成读写(无需调用外设函数,操作像访问内部数组一样简单)。

1. 关键步骤
  1. 引脚初始化:将 EXMC 的 D0~D7、NCS1、NOE、NWE 配置为复用功能(AF12)、推挽输出、上拉、高速;
  2. EXMC 初始化:配置异步模式、8 位数据宽度、时序参数(建立时间 / 保持时间,默认值即可适配 FPGA);
  3. 地址映射:将 FPGA 映射到 STM32 的片外存储地址空间(Bank1 子区 1 的起始地址为0x60000000)。
2. 代码实现(STM32F4,HAL 库)
#include "stm32f4xx_hal.h"

// 1. 将 FPGA 映射为片外存储,起始地址 0x60000000,8 位宽度
#define FPGA_ADDR ((uint8_t *)0x60000000)

// 2. EXMC 引脚初始化
void EXMC_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_EXMC_CLK_ENABLE(); // 使能 EXMC 时钟

    // 数据线 D0~D7:PE0~PE7
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF12_EXMC;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    // 片选 NCS1:PB7
    GPIO_InitStruct.Pin = GPIO_PIN_7;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    // 读 NOE:PD4;写 NWE:PD5
    GPIO_InitStruct.Pin = GPIO_PIN_4|GPIO_PIN_5;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
}

// 3. EXMC 异步模式初始化(Bank1 子区 1,8 位)
void EXMC_FPGA_Init(void) {
    EXMC_InitTypeDef EXMC_InitStruct = {0};
    EXMC_NORSRAM_TimingTypeDef EXMC_TimingStruct = {0};

    // 时序配置:异步模式,建立时间/保持时间默认(可根据 FPGA 调整)
    EXMC_TimingStruct.AccessMode = EXMC_ACCESS_MODE_A;
    EXMC_TimingStruct.AsyncSetupTime = 0x02; // 建立时间 2 个 HCLK 周期
    EXMC_TimingStruct.AsyncHoldTime = 0x01; // 保持时间 1 个 HCLK 周期
    EXMC_TimingStruct.SyncSetupTime = 0x00;
    EXMC_TimingStruct.SyncHoldTime = 0x00;
    EXMC_TimingStruct.CLKDivision = 0x00;

    // EXMC 主配置:Bank1 子区 1,NOR Flash 模式,8 位宽度,异步
    EXMC_InitStruct.NORSRAMBank = EXMC_NORSRAM_BANK1;
    EXMC_InitStruct.DataAddressMux = EXMC_DATA_ADDRESS_MUX_DISABLE; // 地址/数据分离(无地址线可省)
    EXMC_InitStruct.MemoryType = EXMC_MEMORY_TYPE_NOR;
    EXMC_InitStruct.MemoryDataWidth = EXMC_NORSRAM_MEM_BUS_WIDTH_8; // 8 位宽度
    EXMC_InitStruct.BurstAccessMode = EXMC_BURST_ACCESS_MODE_DISABLE; // 异步关闭突发
    EXMC_InitStruct.WaitSignalPolarity = EXMC_WAIT_SIGNAL_POLARITY_LOW;
    EXMC_InitStruct.WrapMode = EXMC_WRAP_MODE_DISABLE;
    EXMC_InitStruct.WaitSignalActive = EXMC_WAIT_SIGNAL_ACTIVE_BEFORE_WS;
    EXMC_InitStruct.WriteOperation = EXMC_WRITE_OPERATION_ENABLE; // 使能写
    EXMC_InitStruct.WaitSignal = EXMC_WAIT_SIGNAL_DISABLE;
    EXMC_InitStruct.ExtendedMode = EXMC_EXTENDED_MODE_DISABLE;
    EXMC_InitStruct.AsynchronousWait = EXMC_ASYNCHRONOUS_WAIT_DISABLE;
    EXMC_InitStruct.NWAITPolarity = EXMC_NWAIT_POLARITY_LOW;
    EXMC_InitStruct.MemoryPreset = EXMC_MEMORY_PRESET_ENABLE;
    EXMC_InitStruct.NORSRAMTimingStruct = &EXMC_TimingStruct;
    EXMC_InitStruct.NORSRAMTimingStruct2 = NULL;

    HAL_EXMC_NORSRAM_Init(&EXMC_InitStruct, &hexmc_norsram1);
}

// 4. 读写 FPGA 函数(直接访问地址,极简)
uint8_t FPGA_Read_Byte(void) {
    return *FPGA_ADDR; // 读 1 字节:直接解引用映射地址
}

void FPGA_Write_Byte(uint8_t dat) {
    *FPGA_ADDR = dat; // 写 1 字节:直接赋值给映射地址
}

// 主函数中初始化
int main(void) {
    HAL_Init();
    SystemClock_Config(); // 配置系统时钟(如 168MHz)
    EXMC_GPIO_Init();
    EXMC_FPGA_Init();

    // 实操:写 1 字节到 FPGA,再读回来
    FPGA_Write_Byte(0x5A);
    uint8_t recv_dat = FPGA_Read_Byte();

    while(1) { }
}
四、FPGA 端适配:模拟 EXMC 异步时序

FPGA 端的核心是根据 STM32 EXMC 的异步读写时序,编写 Verilog/VHDL 逻辑,实现对 STM32 数据线的采样(读)和驱动(写),无需复杂 IP 核,纯组合逻辑 + 时序逻辑即可实现,核心是识别NCS1(片选低有效)、NOE(读低有效)、NWE(写低有效) 三个控制信号的电平变化。

1. EXMC 异步读写核心时序(8 位,低有效)
  • 写操作:STM32 拉低NCS1和NWE,同时将数据放到D0~D7,FPGA 在NWE下降沿 / 低电平期间采样D0~D7的数据,完成写接收;
  • 读操作:STM32 拉低NCS1和NOE,FPGA 检测到NOE低电平后,将待发送数据放到D0~D7,STM32 在NOE低电平期间采样数据线,完成读传输。
2. Verilog 代码实现(极简版,8 位双向通信)
module STM32_EXMC_FPGA(
    // STM32 EXMC 输入信号
    input EXMC_NCS1,   // 片选,低有效
    input EXMC_NOE,    // 读控制,低有效
    input EXMC_NWE,    // 写控制,低有效
    inout [7:0] EXMC_D,// 8 位双向数据线

    // FPGA 内部接口(供后续逻辑使用)
    output reg [7:0] fpga_recv_dat, // 从 STM32 接收的数据
    output reg fpga_recv_flag,      // 接收完成标志
    input [7:0] fpga_send_dat,      // FPGA 要发送给 STM32 的数据
    input fpga_send_en              // FPGA 发送使能
);

    // 双向数据线处理:高阻态(读)/ 驱动(写)
    assign EXMC_D = (EXMC_NCS1 == 0 && EXMC_NOE == 0 && fpga_send_en) ? fpga_send_dat : 8'hzz;

    // 写操作:STM32 写 FPGA,NCS1+NWE 低电平时采样数据
    always @(negedge EXMC_NWE or posedge EXMC_NCS1) begin
        if(EXMC_NCS1) // 片选拉高,复位
        begin
            fpga_recv_dat <= 8'h00;
            fpga_recv_flag <= 1'b0;
        end
        else // 片选拉低,NWE 下降沿采样
        begin
            fpga_recv_dat <= EXMC_D;
            fpga_recv_flag <= 1'b1; // 置位接收标志,供后续逻辑处理
        end
    end

    // 接收标志消抖(可选,防止多次触发)
    always @(posedge fpga_recv_flag) begin
        #10 fpga_recv_flag <= 1'b0;
    end
endmodule
代码说明
  1. 双向数据线EXMC_D通过三态门实现:仅当 STM32 读操作(NCS1+NOE低)且 FPGA 发送使能时,FPGA 驱动数据线,否则为高阻态(由 STM32 驱动);
  2. 写操作采用NWE 下降沿采样,是最稳定的方式,避免数据线抖动;
  3. 输出fpga_recv_flag为接收完成标志,FPGA 内部的其他逻辑(如 FIFO、数据处理模块)可通过该标志获取 STM32 发送的数据。
五、进阶扩展:提升传输效率
  1. 16/32 位数据宽度:STM32 端只需将EXMC_NORSRAM_MEM_BUS_WIDTH_8改为16/32,FPGA 端将数据线扩展为 16/32 位,传输速率直接翻倍 / 四倍;
  2. 地址线扩展:加入 A0~An 地址线,可实现多地址寄存器 / 数据块的读写,适合 FPGA 内部多个模块与 STM32 通信(如 A0 对应采集模块,A1 对应控制模块);
  3. FIFO 缓存:在 FPGA 端加入异步 FIFO,STM32 连续读写时,FIFO 可缓存数据,解决数据传输的速率匹配问题,支持大数据块连续传输(如图像数据);
  4. 中断交互:增加 1 根 GPIO 中断线,FPGA 端在数据准备好后拉低中断线,STM32 检测到中断后触发 EXMC 读操作,实现异步触发传输,无需轮询。
六、实操关键注意事项
  1. 电平匹配:STM32F4/F7 为 3.3V,FPGA 需配置为 3.3V_IO 标准,禁止 5V 直接连接;
  2. 布线规则:EXMC 的数据线、地址线、控制线等长布线(板内通信,误差控制在 500mil 内),减少信号延迟和串扰;
  3. 时序调整:若通信出现乱码,可调整 STM32 EXMC 的AsyncSetupTime和AsyncHoldTime(增大数值,增加建立 / 保持时间);
  4. 引脚驱动能力:STM32 EXMC 引脚驱动能力为 20mA,FPGA 端无需上拉 / 下拉,直接直连即可;
  5. 时钟频率:STM32 的 EXMC 时序由 HCLK 时钟驱动(F4 最大 168MHz),时钟越高,传输速率越快,无需额外时钟线同步。
七、EXMC 与其他通信方式的对比(FPGA 通信场景)
通信方式速率硬件复杂度开发难度CPU 占用适用场景
UART≤1Mbps极低极低中小数据量指令 / 状态交互
SPI几十 Mbps低中中中速数据传输
普通 GPIO 并行几十 Mbps中高高无 EXMC 的单片机
EXMC 并行100~200Mbps中中极低大数据量低延迟传输
总结

STM32 的 EXMC 是与 FPGA 实现高速并行通信的最优方案之一,核心思路是将 FPGA 模拟为 STM32 的外部存储器,通过地址映射实现极简的读写操作,FPGA 端仅需简单的时序逻辑即可适配。

相比普通 GPIO 自定义并行总线,EXMC 作为硬核外设,时序更稳定、CPU 占用更低、开发更规范;相比 SPI/UART,速率提升 10~20 倍,完美解决大数据量传输的需求。

实操中,8 位无地址线的简化版连接即可满足 90% 的场景,代码和硬件都极简,新手也能快速上手。


STM32 FSMC 接口(与 FPGA 高速通信)

FSMC(Flexible Static Memory Controller,灵活静态存储器控制器)是STM32F1/F4 等中低端系列的并行总线硬核外设,是EXMC 的前身(STM32F7/H7 将 FSMC 升级为 EXMC,功能更完善),核心定位和原理与 EXMC 一致 ——将 FPGA 模拟为片外静态存储器,通过并行总线实现高速双向通信,速率可达百 Mbps 级,是 STM32 无 EXMC 型号与 FPGA 大数据量、低延迟通信的最优方案。

简单来说:FSMC = 基础版 EXMC,二者核心逻辑、与 FPGA 的通信思路完全相同,仅在寄存器配置、部分时序参数、引脚复用定义上有细微差异,适配 FPGA 的方法可直接沿用 EXMC 的设计,新手无需刻意区分,重点掌握FSMC 的 NOR Flash 模式即可(FPGA 通信唯一常用模式)。

一、FSMC 核心特性(适配 FPGA 通信)

FSMC 专为外接 SRAM/NOR Flash/PSRAM 等静态存储器设计,天生适合与 FPGA 做并行通信,核心特性完美匹配嵌入式高速交互需求:

  1. 支持8/16 位数据宽度(STM32F1 无 32 位),常规 8 位通信速率50~150Mbps(由 STM32 时钟和时序配置决定);
  2. 提供独立片选线(NE)、读写控制线(NOE/NWE),时序参数(建立 / 保持时间)可通过寄存器灵活配置,适配不同 FPGA 的响应速度;
  3. 硬核外设,CPU 占用极低,数据传输由硬件完成,无需 CPU 频繁干预,读写操作像访问内部数组一样简单;
  4. 仅支持异步模式(无同步时钟),与 FPGA 通信无需时钟线同步,硬件连接和时序实现比 EXMC 更简单;
  5. 分为Bank1~Bank4,其中Bank1(对应 NOR Flash/SRAM)是与 FPGA 通信的核心区域,分为 4 个子区(NE1~NE4),每个子区对应独立片选线,支持多 FPGA / 多模块扩展。
FSMC 与 EXMC 的核心差异(适配 FPGA 时重点关注)
特性FSMC(STM32F1/F4 基础款)EXMC(STM32F7/H7)
数据宽度8/16 位8/16/32 位
时钟支持仅异步异步 + 同步
片选线命名NE1~NE4(Bank1)NCS1~NCS4(Bank1)
引脚复用 AF多为 AF0/FSMC统一 AF12/EXMC
寄存器结构基础版,参数较少增强版,参数更细
最高速率~150Mbps(8 位)~200Mbps(8 位)
适用型号STM32F103/F107/F405/F407STM32F7/H7 系列

核心结论:与 FPGA 通信时,FSMC 和 EXMC 的设计思路、硬件连接、FPGA 端时序逻辑完全一致,仅需修改 STM32 端的引脚初始化和寄存器配置,FPGA 端代码可直接复用(无需任何修改)。

二、FSMC 与 FPGA 的硬件连接(最常用:STM32F103+8 位无地址线)

以STM32F103ZET6(FSMC 最典型型号)、8 位异步通信、Bank1 子区 1(NE1) 为例,这是最简、最常用的连接方式(省略地址线,大幅减少引脚占用),硬件布线简单,新手易上手。

核心原则
  1. 电平匹配:STM32F103 为 3.3V,FPGA(如 EP4CE6/XC6SLX9)需配置为 3.3V IO 标准,直连无需电平转换;
  2. 必须共地:多点共地降低地弹干扰,是通信稳定的关键;
  3. 双向数据线:FPGA 端需配置为三态双向 IO 口,由 FSMC 控制线决定数据方向;
  4. 无地址线简化:仅做双向数据传输时,可省略所有地址线(A0~A23),仅用片选 + 读写控制实现,满足 90% 的 FPGA 通信场景。
详细硬件连接表(8 位宽度,Bank1 子区 1)
STM32F103 FSMC 引脚信号功能引脚编号(ZET6)FPGA 引脚备注
D0~D78 位双向数据线PD14PD15、PE0PE7双向 IO 口FPGA 核心数据传输通道
NE1片选线(低有效)PD7输入 IO 口FPGA 通信使能,低电平有效
NOE读控制线(低有效)PD4输入 IO 口STM32 读 FPGA 时拉低
NWE写控制线(低有效)PD5输入 IO 口STM32 写 FPGA 时拉低
GND地任意 GND 引脚任意 GND 引脚多点共地,强推 2 处以上
引脚说明
  1. STM32F103 的 FSMC 数据线D0~D7为PD14、PD15、PE0、PE1、PE2、PE3、PE4、PE5、PE6、PE7(前 2 位 PD,后 6 位 PE),是固定引脚,不可随意更换;
  2. 片选 NE1、读 NOE、写 NWE 也为固定引脚,无需配置复用,直接映射为 FSMC 功能;
  3. FPGA 端无特殊引脚要求,任意空闲 IO 口均可,只需区分输入(控制线)和双向(数据线)。

三、STM32 端 FSMC 配置(STM32F103,标准库 / HAL 库双版本)

与 FPGA 通信的核心是将 FSMC 配置为 Bank1 子区 1 的 8 位异步 NOR Flash 模式,并将 FPGA映射为片外存储地址,后续通过直接地址访问完成读写(无需调用外设函数,操作极简)。

STM32F103 的 FSMC 配置推荐标准库(更简洁,适配性更好),同时提供 HAL 库版本,满足不同开发习惯。

版本 1:标准库实现(STM32F103,最常用)
1. 头文件依赖
#include "stm32f10x.h"
#include "stm32f10x_fsmc.h"
#include "stm32f10x_gpio.h"
2. 地址映射定义(核心)

将 FPGA 映射到 FSMC Bank1 子区 1 的起始地址 0x60000000,8 位宽度,直接通过指针访问:

// FPGA 映射地址:Bank1 子区 1,8 位,0x60000000
#define FPGA_ADDR ((u8 *)0x60000000)
3. FSMC 引脚初始化(固定配置,直接复用)
// FSMC 引脚初始化:D0~D7、NE1、NOE、NWE
void FSMC_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct;
    // 使能时钟:GPIOD/GPIOE + FSMC
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE);

    // 配置 GPIOD:PD4(NOE)、PD5(NWE)、PD7(NE1)、PD14(D0)、PD15(D1)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 高速
    GPIO_Init(GPIOD, &GPIO_InitStruct);

    // 配置 GPIOE:PE0~PE7(D2~D7)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOE, &GPIO_InitStruct);
}
4. FSMC 核心初始化(8 位异步 NOR 模式,适配 FPGA)

时序参数为通用默认值,可直接适配所有 FPGA,若通信乱码,只需增大FSMC_AddressSetupTime/FSMC_DataSetupTime即可:

// FSMC 初始化:Bank1 子区 1,8 位异步 NOR Flash 模式
void FSMC_FPGA_Init(void) {
    FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStruct;
    FSMC_NORSRAMTimingInitTypeDef FSMC_TimingInitStruct;

    // ********** 时序配置:异步模式,核心参数 **********
    FSMC_TimingInitStruct.FSMC_AddressSetupTime = 0x02; // 地址建立时间:2 个 HCLK 周期
    FSMC_TimingInitStruct.FSMC_AddressHoldTime = 0x00; // 地址保持时间:0(无地址线可省)
    FSMC_TimingInitStruct.FSMC_DataSetupTime = 0x03; // 数据建立时间:3 个 HCLK 周期
    FSMC_TimingInitStruct.FSMC_BusTurnAroundDuration = 0x00;
    FSMC_TimingInitStruct.FSMC_CLKDivision = 0x00;
    FSMC_TimingInitStruct.FSMC_DataLatency = 0x00;
    FSMC_TimingInitStruct.FSMC_AccessMode = FSMC_AccessMode_A; // 访问模式 A

    // ********** FSMC 主配置:映射 FPGA 为 NOR Flash **********
    FSMC_NORSRAMInitStruct.FSMC_Bank = FSMC_Bank1_NORSRAM1; // Bank1 子区 1
    FSMC_NORSRAMInitStruct.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 地址/数据分离
    FSMC_NORSRAMInitStruct.FSMC_MemoryType = FSMC_MemoryType_NOR; // NOR Flash 模式
    FSMC_NORSRAMInitStruct.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_8b; // 8 位宽度
    FSMC_NORSRAMInitStruct.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable; // 关闭突发(异步)
    FSMC_NORSRAMInitStruct.FSMC_WaitSignalPolarity = FSMC_WaitSignalPolarity_Low;
    FSMC_NORSRAMInitStruct.FSMC_WrapMode = FSMC_WrapMode_Disable;
    FSMC_NORSRAMInitStruct.FSMC_WaitSignalActive = FSMC_WaitSignalActive_BeforeWS;
    FSMC_NORSRAMInitStruct.FSMC_WriteOperation = FSMC_WriteOperation_Enable; // 使能写操作
    FSMC_NORSRAMInitStruct.FSMC_WaitSignal = FSMC_WaitSignal_Disable;
    FSMC_NORSRAMInitStruct.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable;
    FSMC_NORSRAMInitStruct.FSMC_AsynchronousWait = FSMC_AsynchronousWait_Disable;
    FSMC_NORSRAMInitStruct.FSMC_WriteBurst = FSMC_WriteBurst_Disable;
    FSMC_NORSRAMInitStruct.FSMC_NORSRAMTimingStruct = &FSMC_TimingInitStruct;
    FSMC_NORSRAMInitStruct.FSMC_NORSRAMTimingStruct2 = NULL;

    FSMC_NORSRAMInit(&FSMC_NORSRAMInitStruct);
    FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE);
}
5. 极简读写函数(直接地址访问,核心亮点)

无需复杂的外设操作,像访问内部变量一样读写 FPGA,新手秒会:

// 读 1 字节:从 FPGA 读取数据
u8 FPGA_Read_Byte(void) {
    return *FPGA_ADDR; // 直接解引用映射地址
}

// 写 1 字节:向 FPGA 写入数据
void FPGA_Write_Byte(u8 dat) {
    *FPGA_ADDR = dat; // 直接赋值给映射地址
}

// 扩展:连续读 n 字节
void FPGA_Read_nByte(u8 *pbuf, u16 n) {
    u16 i;
    for(i=0; i<n; i++) {
        pbuf[i] = *FPGA_ADDR;
    }
}

// 扩展:连续写 n 字节
void FPGA_Write_nByte(u8 *pbuf, u16 n) {
    u16 i;
    for(i=0; i<n; i++) {
        *FPGA_ADDR = pbuf[i];
    }
}
6. 主函数初始化与测试
int main(void) {
    u8 send_dat = 0x5A;
    u8 recv_dat = 0x00;

    // 系统初始化:时钟 + 串口(可选)
    SystemInit(); // STM32F103 默认 72MHz

    // FSMC 初始化
    FSMC_GPIO_Init();
    FSMC_FPGA_Init();

    // 测试:写 1 字节到 FPGA,再读回来
    FPGA_Write_Byte(send_dat);
    recv_dat = FPGA_Read_Byte();

    while(1) {
        // 循环测试
        send_dat++;
        FPGA_Write_Byte(send_dat);
        recv_dat = FPGA_Read_Byte();
        delay_ms(100);
    }
}
版本 2:HAL 库实现(STM32F103,兼容新版开发)

HAL 库对 FSMC 做了封装,配置逻辑与标准库一致,仅函数名和结构体有差异,FPGA 端代码无需任何修改,核心地址映射0x60000000保持不变,可直接参考前文 EXMC 的 HAL 库代码,仅需修改引脚复用为 FSMC、寄存器时序参数即可。

四、FPGA 端适配逻辑(Verilog 代码,直接复用,无需修改)

FSMC 与 FPGA 通信的核心是识别 FSMC 的控制信号(NE1/NOE/NWE)电平变化,匹配异步读写时序,FPGA 端的 Verilog 代码与 EXMC 完全通用,因为二者的控制信号时序、数据线方向逻辑一致,无需任何修改,直接复制即可使用。

核心时序规则(FSMC 异步读写)
  1. 写操作(STM32→FPGA):STM32 拉低NE1(片选)+NWE(写),同时将数据放到D0~D7,FPGA 在NWE下降沿 / 低电平期间采样数据线,完成数据接收;
  2. 读操作(FPGA→STM32):STM32 拉低NE1(片选)+NOE(读),FPGA 检测到低电平后,将待发送数据放到D0~D7,STM32 在NOE低电平期间采样数据线,完成数据发送;
  3. 双向数据线:FPGA 端通过三态门实现,仅读操作时驱动数据线,其余时间为高阻态(由 STM32 驱动)。
Verilog 极简实现(8 位双向通信,适配所有 FSMC 型号)
module STM32_FSMC_FPGA(
    // STM32 FSMC 输入信号(直接接硬件)
    input FSMC_NE1,   // 片选线,低有效(对应 STM32 PD7)
    input FSMC_NOE,   // 读控制线,低有效(对应 STM32 PD4)
    input FSMC_NWE,   // 写控制线,低有效(对应 STM32 PD5)
    inout [7:0] FSMC_D, // 8 位双向数据线(D0~D7)

    // FPGA 内部接口(供后续逻辑调用,如 FIFO/数据处理)
    output reg [7:0] fpga_recv_dat, // FPGA 从 STM32 接收的数据
    output reg fpga_recv_flag,      // 接收完成标志(高电平有效)
    input [7:0] fpga_send_dat,      // FPGA 要发送给 STM32 的数据
    input fpga_send_en              // FPGA 发送使能(高电平有效)
);

    // ********** 双向数据线三态控制:核心 **********
    // 仅当 FSMC 读操作(NE1+NOE 低)且 FPGA 发送使能时,驱动数据线;否则高阻
    assign FSMC_D = (FSMC_NE1 == 1'b0 && FSMC_NOE == 1'b0 && fpga_send_en == 1'b1) ? fpga_send_dat : 8'hzz;

    // ********** 写操作:STM32 写 FPGA,NWE 下降沿采样数据 **********
    always @(negedge FSMC_NWE or posedge FSMC_NE1) begin
        if(FSMC_NE1 == 1'b1) // 片选拉高,复位接收标志和数据
        begin
            fpga_recv_dat <= 8'h00;
            fpga_recv_flag <= 1'b0;
        end
        else // 片选拉低,NWE 下降沿采样数据线数据
        begin
            fpga_recv_dat <= FSMC_D;
            fpga_recv_flag <= 1'b1; // 置位接收标志,供内部逻辑处理
        end
    end

    // ********** 接收标志消抖:防止多次触发(可选,根据需求删除) **********
    always @(posedge fpga_recv_flag) begin
        #10; // 延时 10 个 FPGA 时钟周期(可根据 FPGA 时钟调整)
        fpga_recv_flag <= 1'b0;
    end
endmodule
代码说明
  1. 双向数据线FSMC_D的三态控制是核心,必须严格判断NE1和NOE的低电平,否则会出现硬件总线冲突;
  2. 写操作采用NWE 下降沿采样,是最稳定的方式,避免数据线抖动导致的采样错误;
  3. fpga_recv_flag为接收完成标志,FPGA 内部的 FIFO、数据处理模块可通过该标志触发数据读取,实现流水线操作;
  4. 代码无 FPGA 型号限制,Altera/Xilinx/ 国产 FPGA 均可直接使用,只需调整延时参数#10(匹配 FPGA 主时钟即可)。

五、进阶扩展(提升传输效率,适配复杂场景)

基础版 8 位无地址线的配置可满足 90% 的场景,若需要更高速率、更多功能,可基于基础版做简单扩展,硬件和代码仅需少量修改:

1. 16 位数据宽度(速率翻倍)
  • STM32 端:将FSMC_MemoryDataWidth_8b改为FSMC_MemoryDataWidth_16b,引脚扩展D8~D15(STM32F103 的 PD8~PD13);
  • FPGA 端:将数据线从[7:0]改为[15:0],其余逻辑不变;
  • 效果:传输速率直接翻倍,8 位 100Mbps→16 位 200Mbps。
2. 增加地址线(多模块 / 多寄存器寻址)

若 FPGA 内部有多个功能模块(如采集模块、控制模块、显示模块),可增加 FSMC 地址线A0~An(如 A0/A1),通过地址区分不同模块:

  • 硬件:STM32 FSMC A0(PE8)/A1(PE9)直连 FPGA 输入 IO;
  • FPGA 端:增加地址判断逻辑,根据A0/A1的电平,将数据分发到不同内部模块;
  • STM32 端:读写地址改为0x60000000(A0=0)、0x60000001(A0=1),直接寻址不同模块。
3. FIFO 缓存(大数据块连续传输)

若需要传输图像、高速采集数据等大数据块,在 FPGA 端加入异步 FIFO(如深度 512/1024):

  • 写操作:STM32 连续写 FPGA,数据直接存入 FIFO,FPGA 后续慢慢处理;
  • 读操作:FPGA 将处理后的数据提前存入 FIFO,STM32 连续读 FIFO 即可;
  • 效果:解决 STM32 和 FPGA 的速率匹配问题,支持无间断大数据块传输。
4. 中断交互(异步触发,无需轮询)

增加 1 根 GPIO 中断线,实现异步触发传输,替代 STM32 轮询:

  • FPGA→STM32:FPGA 数据准备好后,拉低 GPIO 中断线,STM32 外部中断触发 FSMC 读操作;
  • STM32→FPGA:STM32 写数据后,拉低 GPIO 中断线,FPGA 中断触发数据读取;
  • 效果:减少 CPU 占用,提升系统实时性。

六、实操关键注意事项(避坑指南,新手必看)

  1. 时钟配置:STM32F103 的 FSMC 由HCLK 时钟驱动(最大 72MHz),时钟越高传输速率越快,务必将系统时钟配置为 72MHz(默认SystemInit()即可);
  2. 时序调整:若通信出现乱码 / 数据错误,优先增大时序参数(AddressSetupTime/DataSetupTime),从 0x02/0x03 改为 0x05/0x06,无需修改 FPGA 代码;
  3. 总线冲突:FPGA 端双向数据线必须严格做三态控制,禁止全程驱动数据线,否则会与 STM32 发生硬件总线冲突,烧毁引脚;
  4. 布线规则:FSMC 的数据线、控制线等长布线(板内通信误差控制在 500mil 内),减少信号延迟和串扰,数据线尽量走同一层;
  5. 引脚驱动:STM32F103 的 FSMC 引脚驱动能力为 20mA,FPGA 端无需上拉 / 下拉电阻,直接直连即可;
  6. 片选唯一:若使用多个子区(NE1~NE4),确保同一时间只有一个片选线为低电平,避免多个模块同时响应;
  7. 复位顺序:系统上电时,先复位 FPGA,再复位 STM32,避免 FPGA 未就绪时 STM32 发送数据导致通信错误。

七、FSMC 与其他通信方式的对比(FPGA 通信场景)

通信方式速率硬件复杂度开发难度CPU 占用适用场景
UART≤1Mbps极低极低中小数据量指令 / 状态交互
SPI几十 Mbps低中中中速小批量数据传输
普通 GPIO 并行20~50Mbps中高高无 FSMC/EXMC 的低端单片机
FSMC 并行50~150Mbps中中极低STM32F1/F4 与 FPGA 高速通信
EXMC 并行100~200Mbps中中极低STM32F7/H7 与 FPGA 高速通信

总结

FSMC 是 STM32F1/F4 系列与 FPGA 实现高速并行通信的核心硬核外设,核心思路与 EXMC 完全一致 ——将 FPGA 模拟为片外静态存储器,通过地址映射实现极简的读写操作。

其优势非常明显:速率比 SPI/UART 高 10~30 倍、CPU 占用极低、操作像访问内部数组一样简单,而硬件连接和 FPGA 端代码又足够极简,新手也能快速上手。

实操中,8 位无地址线的简化版配置即可满足绝大多数场景,若需要更高速率或更多功能,再做 16 位宽度、地址线、FIFO 缓存等扩展即可,所有扩展都基于基础版,无需重构代码。


适配 STM32F103 全型号 FSMC 代码(C8T6/VET6/ZET6 通用,精简引脚 + 注释)

以下代码做了极致精简,适配 STM32F103所有封装型号(小封装 C8T6 / 中封装 VET6 / 大封装 ZET6),保留8 位无地址线核心通信功能(90% 场景够用),逐行加注释且删除冗余配置,同时兼容标准库 V3.5(最稳定),FPGA 端代码完全通用无需修改。

核心适配说明
  1. 小封装兼容:仅使用 FSMC固定核心引脚,无多余扩展,C8T6 等小封装只需确认引脚引出即可;
  2. 时钟适配:默认 72MHz 系统时钟(F103 最大主频),FSMC 由 HCLK 驱动,速率最优;
  3. 时序通用:时序参数为保守值,适配所有 FPGA(Altera/Xilinx/ 国产),直接使用无乱码;
  4. 代码解耦:读写函数独立封装,主函数直接调用,新手可快速移植。

一、头文件与宏定义(核心地址映射)

// 引入标准库核心头文件(V3.5,需提前添加库文件到工程)
#include "stm32f10x.h"
#include "stm32f10x_fsmc.h"
#include "stm32f10x_gpio.h"
#include "delay.h" // 需自行实现毫秒/微秒延时(可选,测试用)

/********************* FSMC 核心宏定义 *********************/
#define FPGA_ADDR ((u8 *)0x60000000) // FPGA 映射地址:Bank1 子区 1,8 位(固定值)
#define u8 uint8_t // 简化类型定义(可选,根据习惯修改)
#define u16 uint16_t
#define u32 uint32_t

关键:0x60000000是 FSMC Bank1 子区 1固定起始地址,所有 F103 型号均一致,无需修改。

二、FSMC 引脚初始化函数(全型号通用,精简核心引脚)

引脚对应关系(F103 固定,所有型号一致)
FSMC 信号功能STM32 引脚引脚类型备注
D0~D78 位双向数据PD14/15、PE0~7复用推挽核心数据通道,不可更换
NE1片选(低有效)PD7复用推挽通信使能,Bank1 子区 1 唯一
NOE读控制(低有效)PD4复用推挽STM32 读 FPGA 时拉低
NWE写控制(低有效)PD5复用推挽STM32 写 FPGA 时拉低
/********************* FSMC 引脚初始化 *********************/
void FSMC_GPIO_Init(void) {
    GPIO_InitTypeDef GPIO_InitStruct; // GPIO 初始化结构体

    // 1. 使能时钟:GPIOD/GPIOE(FSMC 核心引脚所在端口)+ FSMC 外设
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD | RCC_APB2Periph_GPIOE, ENABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_FSMC, ENABLE); // FSMC 挂在 AHB 总线

    // 2. 配置 GPIOD:PD4(NOE)/PD5(NWE)/PD7(NE1)/PD14(D0)/PD15(D1)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7 | GPIO_Pin_14 | GPIO_Pin_15;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出(FSMC 固定模式)
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 高速(匹配 FSMC 速率)
    GPIO_Init(GPIOD, &GPIO_InitStruct);

    // 3. 配置 GPIOE:PE0~PE7(D2~D7,8 位数据线补全)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
    GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOE, &GPIO_InitStruct);
}
小封装 C8T6 适配注意

C8T6 为 LQFP48 封装,需确认 PE0~PE7、PD4/5/7/14/15 是否引出:

  • 若部分引脚未引出,可放弃 FSMC(改用 SPI/UART),C8T6 小封装优先用 SPI 做中高速通信;
  • 引出引脚的 C8T6 可直接使用上述代码,无需修改。

三、FSMC 核心初始化(8 位异步 NOR 模式,适配 FPGA)

时序参数为保守值,直接使用无乱码;若通信出错,仅需增大AddressSetupTime/DataSetupTime(如从 2/3 改为 5/6),其余配置固定。

/********************* FSMC 核心初始化 *********************/
void FSMC_FPGA_Init(void) {
    FSMC_NORSRAMInitTypeDef FSMC_NORSRAMInitStruct; // FSMC 主结构体
    FSMC_NORSRAMTimingInitTypeDef FSMC_TimingInitStruct; // FSMC 时序结构体

    /********** 1. 时序配置:异步模式,核心参数(通用适配所有 FPGA) **********/
    FSMC_TimingInitStruct.FSMC_AddressSetupTime = 0x02; // 地址建立时间:2 个 HCLK 周期
    FSMC_TimingInitStruct.FSMC_AddressHoldTime = 0x00; // 地址保持时间:0(无地址线,可省)
    FSMC_TimingInitStruct.FSMC_DataSetupTime = 0x03; // 数据建立时间:3 个 HCLK 周期(关键)
    FSMC_TimingInitStruct.FSMC_BusTurnAroundDuration = 0x00; // 总线周转时间:0(单设备)
    FSMC_TimingInitStruct.FSMC_CLKDivision = 0x00; // 时钟分频:0(异步模式无时钟)
    FSMC_TimingInitStruct.FSMC_DataLatency = 0x00; // 数据延迟:0(异步模式)
    FSMC_TimingInitStruct.FSMC_AccessMode = FSMC_AccessMode_A; // 访问模式 A(固定)

    /********** 2. FSMC 主配置:映射 FPGA 为外部 NOR Flash **********/
    FSMC_NORSRAMInitStruct.FSMC_Bank = FSMC_Bank1_NORSRAM1; // 选择 Bank1 子区 1(固定)
    FSMC_NORSRAMInitStruct.FSMC_DataAddressMux = FSMC_DataAddressMux_Disable; // 地址/数据分离
    FSMC_NORSRAMInitStruct.FSMC_MemoryType = FSMC_MemoryType_NOR; // NOR 模式(FPGA 通信唯一选择)
    FSMC_NORSRAMInitStruct.FSMC_MemoryDataWidth = FSMC_MemoryDataWidth_8b; // 8 位数据宽度(核心)
    FSMC_NORSRAMInitStruct.FSMC_BurstAccessMode = FSMC_BurstAccessMode_Disable; // 关闭突发(异步)
    FSMC_NORSRAMInitStruct.FSMC_WriteOperation = FSMC_WriteOperation_Enable; // 使能写操作(必须)
    FSMC_NORSRAMInitStruct.FSMC_WaitSignal = FSMC_WaitSignal_Disable; // 关闭等待信号(单设备)
    FSMC_NORSRAMInitStruct.FSMC_ExtendedMode = FSMC_ExtendedMode_Disable; // 关闭扩展模式
    FSMC_NORSRAMInitStruct.FSMC_NORSRAMTimingStruct = &FSMC_TimingInitStruct; // 绑定时序结构体
    FSMC_NORSRAMInitStruct.FSMC_NORSRAMTimingStruct2 = NULL; // 无扩展时序,置空

    /********** 3. 初始化并使能 FSMC **********/
    FSMC_NORSRAMInit(&FSMC_NORSRAMInitStruct); // 初始化 FSMC
    FSMC_NORSRAMCmd(FSMC_Bank1_NORSRAM1, ENABLE); // 使能 Bank1 子区 1(核心)
}

四、FSMC 读写函数(独立封装,直接调用)

核心亮点:像访问内部数组 / 变量一样读写 FPGA,无需复杂外设操作,新手秒会。

/********************* FSMC 读写函数(8 位) *********************/
// 读 1 字节:从 FPGA 读取 1 个 8 位数据
u8 FPGA_Read_Byte(void) {
    return *FPGA_ADDR; // 直接解引用映射地址,硬件自动完成读时序
}

// 写 1 字节:向 FPGA 写入 1 个 8 位数据
void FPGA_Write_Byte(u8 dat) {
    *FPGA_ADDR = dat; // 直接赋值给映射地址,硬件自动完成写时序
}

/********************* 扩展:连续读写 n 字节(大数据块传输) *********************/
// 连续读 n 字节:将 FPGA 数据读入 buf 数组
void FPGA_Read_nByte(u8 *buf, u16 n) {
    u16 i;
    for(i=0; i<n; i++) {
        buf[i] = *FPGA_ADDR; // 循环解引用,连续读
    }
}

// 连续写 n 字节:将 buf 数组数据写入 FPGA
void FPGA_Write_nByte(u8 *buf, u16 n) {
    u16 i;
    for(i=0; i<n; i++) {
        *FPGA_ADDR = buf[i]; // 循环赋值,连续写
    }
}

五、主函数测试代码(完整流程,直接移植)

包含系统初始化→FSMC 初始化→读写测试,加入延时便于调试,可直接替换为实际业务逻辑。

/********************* 主函数(测试用) *********************/
int main(void) {
    u8 send_dat = 0x5A; // 测试发送数据:0x5A
    u8 recv_dat = 0x00; // 测试接收数据
    u8 test_buf[10] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A}; // 连续写测试数组

    SystemInit(); // STM32 系统时钟初始化:默认 72MHz(F103 最大主频,必须)
    delay_init(); // 延时函数初始化(可选,根据自己的 delay.c 修改)

    FSMC_GPIO_Init(); // 第一步:初始化 FSMC 引脚
    FSMC_FPGA_Init(); // 第二步:初始化 FSMC 核心配置

    while(1) {
        /********** 单字节读写测试 **********/
        FPGA_Write_Byte(send_dat); // 向 FPGA 写 1 字节
        delay_ms(10); // 短延时,确保 FPGA 接收完成
        recv_dat = FPGA_Read_Byte();// 从 FPGA 读 1 字节(回读验证)

        /********** 连续 10 字节读写测试(可选) **********/
        FPGA_Write_nByte(test_buf, 10); // 向 FPGA 连续写 10 字节
        delay_ms(10);
        FPGA_Read_nByte(test_buf, 10); // 从 FPGA 连续读 10 字节到原数组

        send_dat++; // 数据自增,便于串口/示波器调试
        if(send_dat > 0xFF) send_dat = 0x00;
        delay_ms(100); // 主循环延时,降低刷新率
    }
}

六、FPGA 端 Verilog 代码(全型号通用,无需修改)

FPGA 端无需区分 STM32F103 型号,直接使用以下极简代码,与 FSMC 时序完美匹配,双向数据线三态控制做了严格判断,避免总线冲突。

module STM32F103_FSMC_FPGA(
    // STM32 FSMC 硬件接口(直接接引脚)
    input FSMC_NE1,   // 片选:低有效(PD7)
    input FSMC_NOE,   // 读控制:低有效(PD4)
    input FSMC_NWE,   // 写控制:低有效(PD5)
    inout [7:0] FSMC_D, // 8 位双向数据线(D0~D7)

    // FPGA 内部接口(供后续逻辑调用)
    output reg [7:0] fpga_recv_dat, // 从 STM32 接收的数据
    output reg fpga_recv_flag,      // 接收完成标志(高有效)
    input [7:0] fpga_send_dat,      // FPGA 要发送给 STM32 的数据
    input fpga_send_en              // FPGA 发送使能(高有效)
);

    // 核心:双向数据线三态控制(避免总线冲突)
    // 仅当 FSMC 读操作(NE1+NOE 低)且 FPGA 发送使能时,驱动数据线;否则高阻态
    assign FSMC_D = (FSMC_NE1 == 1'b0 && FSMC_NOE == 1'b0 && fpga_send_en == 1'b1) ? fpga_send_dat : 8'hzz;

    // FSMC 写操作:NE1 低+NWE 下降沿,采样 STM32 发送的数据(最稳定)
    always @(negedge FSMC_NWE or posedge FSMC_NE1) begin
        if(FSMC_NE1 == 1'b1) // 片选拉高,复位接收标志和数据
        begin
            fpga_recv_dat <= 8'h00;
            fpga_recv_flag <= 1'b0;
        end
        else // 片选拉低,NWE 下降沿采样数据线
        begin
            fpga_recv_dat <= FSMC_D;
            fpga_recv_flag <= 1'b1; // 置位接收标志,供 FPGA 内部逻辑处理
        end
    end

    // 接收标志消抖:防止多次触发(延时 10 个 FPGA 时钟周期,可根据主时钟调整)
    always @(posedge fpga_recv_flag) begin
        #10; fpga_recv_flag <= 1'b0;
    end
endmodule
FPGA 端使用说明
  1. fpga_recv_flag为接收完成中断标志,FPGA 内部逻辑可通过该标志触发数据处理;
  2. fpga_send_en为发送使能,FPGA 需发送数据时,置高该信号并给fpga_send_dat赋值即可;
  3. 代码无 FPGA 型号限制,Altera/EP4CE6/Xilinx/XC7A35 / 国产紫光同创均可直接使用。

七、不同型号适配速查表(一键对应)

STM32F103 型号封装FSMC 引脚是否全引出适配方案推荐通信速率
ZET6LQFP144是直接使用上述代码8 位~150Mbps
VET6LQFP100是直接使用上述代码8 位~120Mbps
C8T6LQFP48部分否引出则用,未引出换 SPISPI~30Mbps
RBT6LQFP64部分是引出引脚直接用,精简代码8 位~80Mbps

八、实操避坑指南(新手必看)

  1. 时钟必须 72MHz:SystemInit()默认初始化 72MHz,禁止改为低速(如 36MHz),否则 FSMC 速率骤降;
  2. 禁止修改引脚模式:FSMC 引脚必须为GPIO_Mode_AF_PP(复用推挽),改为通用 IO 会导致通信失败;
  3. 总线冲突防护:FPGA 端必须做三态控制,禁止全程驱动FSMC_D,否则会烧毁 STM32/FPGA 引脚;
  4. 共地与电平:STM32F103 和 FPGA 均为 3.3V,直接直连无需电平转换,必须多点共地(至少 2 处);
  5. 布线规则:数据线 / 控制线等长布线(板内误差≤500mil),尽量走同一层,减少串扰;
  6. 调试方法:若通信乱码,先增大 FSMC 时序参数(AddressSetupTime/DataSetupTime),再检查硬件连接。

九、工程移植步骤(零基础也会)

  1. 新建 STM32F103 标准库工程(V3.5),添加stm32f10x_fsmc.c/stm32f10x_gpio.c到工程;
  2. 将上述代码复制到main.c,添加自己的delay.c/delay.h(延时函数);
  3. 根据自己的 STM32 型号,确认 FSMC 引脚引出,硬件按引脚表连接;
  4. FPGA 端烧录上述 Verilog 代码,STM32 端烧录 C 代码,直接测试;
  5. 若需串口调试,添加串口初始化代码,将recv_dat通过串口打印即可。

目录

  1. 一、主流通信方式及实现方案
  2. 1. 通用 IO 口(GPIO)自定义协议(最简单)
  3. 2. UART 串口通信(最常用)
  4. 3. SPI 通信(高速串行)
  5. 4. I2C 通信(多设备组网)
  6. 5. 并行总线(超高速)
  7. 二、选型建议
  8. 总结
  9. 一、EXMC 核心原理:把 FPGA 当作“外部存储器”
  10. 1. EXMC 的关键特性(适配 FPGA 通信)
  11. 2. EXMC 的总线分类(FPGA 通信常用NOR Flash 模式)
  12. 二、硬件连接:STM32 EXMC(Bank1)↔ FPGA
  13. 核心硬件连接(8 位数据宽度)
  14. 简化版连接(无地址线,最常用)
  15. 三、STM32 端配置:EXMC 异步模式初始化
  16. 1. 关键步骤
  17. 2. 代码实现(STM32F4,HAL 库)
  18. 四、FPGA 端适配:模拟 EXMC 异步时序
  19. 1. EXMC 异步读写核心时序(8 位,低有效)
  20. 2. Verilog 代码实现(极简版,8 位双向通信)
  21. 代码说明
  22. 五、进阶扩展:提升传输效率
  23. 六、实操关键注意事项
  24. 七、EXMC 与其他通信方式的对比(FPGA 通信场景)
  25. 总结
  26. STM32 FSMC 接口(与 FPGA 高速通信)
  27. 一、FSMC 核心特性(适配 FPGA 通信)
  28. FSMC 与 EXMC 的核心差异(适配 FPGA 时重点关注)
  29. 二、FSMC 与 FPGA 的硬件连接(最常用:STM32F103+8 位无地址线)
  30. 核心原则
  31. 详细硬件连接表(8 位宽度,Bank1 子区 1)
  32. 引脚说明
  33. 三、STM32 端 FSMC 配置(STM32F103,标准库 / HAL 库双版本)
  34. 版本 1:标准库实现(STM32F103,最常用)
  35. 1. 头文件依赖
  36. 2. 地址映射定义(核心)
  37. 3. FSMC 引脚初始化(固定配置,直接复用)
  38. 4. FSMC 核心初始化(8 位异步 NOR 模式,适配 FPGA)
  39. 5. 极简读写函数(直接地址访问,核心亮点)
  40. 6. 主函数初始化与测试
  41. 版本 2:HAL 库实现(STM32F103,兼容新版开发)
  42. 四、FPGA 端适配逻辑(Verilog 代码,直接复用,无需修改)
  43. 核心时序规则(FSMC 异步读写)
  44. Verilog 极简实现(8 位双向通信,适配所有 FSMC 型号)
  45. 代码说明
  46. 五、进阶扩展(提升传输效率,适配复杂场景)
  47. 1. 16 位数据宽度(速率翻倍)
  48. 2. 增加地址线(多模块 / 多寄存器寻址)
  49. 3. FIFO 缓存(大数据块连续传输)
  50. 4. 中断交互(异步触发,无需轮询)
  51. 六、实操关键注意事项(避坑指南,新手必看)
  52. 七、FSMC 与其他通信方式的对比(FPGA 通信场景)
  53. 总结
  54. 适配 STM32F103 全型号 FSMC 代码(C8T6/VET6/ZET6 通用,精简引脚 + 注释)
  55. 核心适配说明
  56. 一、头文件与宏定义(核心地址映射)
  57. 二、FSMC 引脚初始化函数(全型号通用,精简核心引脚)
  58. 引脚对应关系(F103 固定,所有型号一致)
  59. 小封装 C8T6 适配注意
  60. 三、FSMC 核心初始化(8 位异步 NOR 模式,适配 FPGA)
  61. 四、FSMC 读写函数(独立封装,直接调用)
  62. 五、主函数测试代码(完整流程,直接移植)
  63. 六、FPGA 端 Verilog 代码(全型号通用,无需修改)
  64. FPGA 端使用说明
  65. 七、不同型号适配速查表(一键对应)
  66. 八、实操避坑指南(新手必看)
  67. 九、工程移植步骤(零基础也会)
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 多状态动态规划:买卖股票最佳时机系列问题解析
  • AI 产品经理学习路线:从零基础到大模型应用实战指南
  • C++微服务 UserServer 设计与实现
  • 基于YOLO与LLM的Web目标检测及人脸表情识别系统
  • 基于 Web 的高校工会管理系统设计与实现
  • 彻底告别 Python 版本混乱:uv 工具让环境管理变得轻松优雅
  • 高精度运算的加减乘除算法详解
  • 9 款 AI 写作工具辅助论文写作指南
  • ComfyUI 黎黎原上咩 7.0 整合包一键部署指南
  • Oracle WebLogic 代理插件未授权 RCE 漏洞检测与分析
  • 手机安全渗透测试新手入门指南
  • VsCode 远程 Copilot 调用 Claude Agent 报“无效请求”:参数配置排查与修复
  • C++ 协程与 Fiber:游戏开发中的异步编程模型应用
  • 35 道常见前端 Vue 面试题解析
  • 昇腾 NPU 部署 CodeLlama 大模型实战指南
  • Qwen3+Qwen Agent 智能体开发实战:接入 MCP 工具
  • C++ 二叉搜索树原理与高效实现
  • 算法实战:寻找数组中心下标与除自身外数组乘积(前缀和技巧)
  • HarmonyOS RcList 组件缩略图、角标与图标系统设计
  • C++ Google Test (gtest) 全面使用指南

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online