简单理解:单片机怎么和FPGA通信

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

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

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

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

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

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

软件实现(以 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 个子区(CE1~CE4),每个子区对应独立的片选线(NCS1~NCS4),可分别映射不同的 FPGA 逻辑模块,支持多设备扩展。

EXMC Bank1 子区片选引脚核心控制线地址 / 数据线
子区 1(CE1)NCS1NOE(读)、NWE(写)A0~A25、D0~D31
子区 2(CE2)NCS2NOE、NWEA0~A25、D0~D31

二、硬件连接: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 时拉低
GNDGND强推:多点共地,降低干扰
简化版连接(无地址线,最常用)

如果仅实现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 拉低NCS1NWE,同时将数据放到D0~D7,FPGA 在NWE下降沿 / 低电平期间采样D0~D7的数据,完成写接收;
  • 读操作:STM32 拉低NCS1NOE,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 的AsyncSetupTimeAsyncHoldTime(增大数值,增加建立 / 保持时间);
  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 位双向数据线PD14~PD15、PE0~PE7双向 IO 口FPGA 核心数据传输通道
NE1片选线(低有效)PD7输入 IO 口FPGA 通信使能,低电平有效
NOE读控制线(低有效)PD4输入 IO 口STM32 读 FPGA 时拉低
NWE写控制线(低有效)PD5输入 IO 口STM32 写 FPGA 时拉低
GND任意 GND 引脚任意 GND 引脚多点共地,强推 2 处以上

引脚说明

  1. STM32F103 的 FSMC 数据线D0~D7PD14、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); // 使能Bank1子区1 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三态控制是核心,必须严格判断NE1NOE的低电平,否则会出现硬件总线冲突;
  2. 写操作采用NWE 下降沿采样,是最稳定的方式,避免数据线抖动导致的采样错误;
  3. fpga_recv_flag为接收完成标志,FPGA 内部的 FIFO、数据处理模块可通过该标志触发数据读取,实现流水线操作;
  4. 代码无 FPGA 型号限制,Altera/XCilinx/ 国产 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. 时钟必须 72MHzSystemInit()默认初始化 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通过串口打印即可。

Read more

Neo4j 知识讲解与在线工具使用教程

图数据库领域的核心工具 ——Neo4j,同时详细拆解其在线预览控制台(https://console-preview.neo4j.io/)的使用方法,以及查询工具(https://console-preview.neo4j.io/tools/query)的模块功能。 一、Neo4j 核心知识铺垫 在使用工具前,我们需要先理解 Neo4j 的本质和核心概念,这是后续操作的基础。 1. 什么是 Neo4j? Neo4j 是世界上最流行的原生图数据库(Native Graph Database),专门用于存储、查询和分析 “实体之间的关联关系”。它与我们熟悉的 MySQL 等关系型数据库的核心差异的是: * 关系型数据库(MySQL):用 “表 + 行 + 外键” 间接表示关联,查询多表关联时需频繁 JOIN,效率低; * 图数据库(Neo4j)

By Ne0inhk
【无人机】无人机路径规划算法

【无人机】无人机路径规划算法

目录 一、引言:无人机与路径规划算法 二、路径规划算法基础 (一)定义与重要性 (二)规划目标与约束条件 三、常见路径规划算法详解 (一)A * 算法 (二)Dijkstra 算法 (三)RRT(快速扩展随机树)算法 (四)蚁群算法 四、算法应用实例与效果展示 (一)不同场景下的算法应用 (二)算法性能对比数据 五、算法的优化与发展趋势 (一)现有算法的优化策略 (二)结合新技术的发展方向 六、挑战与展望 (一)面临的技术挑战 (二)未来应用前景 七、结论 一、引言:无人机与路径规划算法 在科技飞速发展的今天,无人机作为一种极具创新性的技术产物,已深度融入我们生活的方方面面,

By Ne0inhk
从0到1打造RISC-V智能家居中控:硬件+固件+通信全链路实战

从0到1打造RISC-V智能家居中控:硬件+固件+通信全链路实战

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * 从0到1打造RISC-V智能家居中控:硬件+固件+通信全链路实战 🏠💡 * 为什么选择RISC-V?🤔 * 系统整体架构概览 🧩 * 第一步:硬件选型与电路搭建 🔌 * 主控芯片选择 * 外设连接 * 第二步:开发环境搭建 🛠️ * 安装步骤(以Ubuntu为例) * 第三步:裸机驱动开发(Bare Metal)⚡ * 示例1:DHT11温湿度读取(Bit-banging) * 示例2:BH1750光照传感器(I2C) * 第四步:引入FreeRTOS实现多任务调度 🔄 * 第五步:Wi-Fi连接与MQTT通信 ☁️📡 * 连接Wi-Fi * MQTT客户端(使用esp-mqtt库) * 第六步:BLE本地控制(无需Wi-Fi)📱

By Ne0inhk