ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

GD32 EXMC与FPGA高速并行通信方案:架构设计、时序配置与调试实战

GD32 EXMC与FPGA高速并行通信方案:架构设计、时序配置与调试实战 简介面向嵌入式开发者的GD32与FPGA通信参考工程以STM32F4标准库的FMC功能程序为基础成功移植到GD32F470的EXMC外设实现两者间异步NORSRAM模式A通信。工程包含完整单片机端源码FPGA部分配合博客阐述适合需要掌握GD32 EXMC配置、时序匹配及跨平台代码迁移的工程师参考。压缩包共209个文件核心代码由41个h头文件与35个c源文件构成另有编译生成的d、o、crf中间文件、uvprojx等工程文件以及hex烧录文件可分别用于源码阅读、工程复现与固件烧写整体约6.96MB。已有2050人学习下载代码结构完整可直接导入工程查看GPIO复用、EXMC时序寄存器及NOR/PSRAM控制逻辑对理解FMC到EXMC的寄存器差异和实际板级调试具有明确参考价值。1. 为什么选 EXMC 和 GD32 做 FPGA 通信桥做嵌入式这些年GD32 和 FPGA 打交道是我觉得性价比最高的一组组合。FPGA 擅长并行处理和自定义时序GD32 擅长跑逻辑控制和协议栈两者互补之后能处理很多单芯片搞不定的场景比如高速数据采集、图像预处理、多通道传感器同步。而连接它们的接口我首选 EXMC也就是 GD32 的外部存储器控制器相当于 STM32 里的 FSMC/FMC但它有自己的寄存器体系和时序逻辑。选 EXMC 而不是 SPI、UART 或者 I2C核心原因就一个带宽和实时性。SPI 理论速率看着还行但一上协议、握手、应答实际有效吞吐掉得厉害UART 更不用提做控制没问题做批量数据搬运就是折磨。EXMC 不一样它是存储器总线接口从 GD32 的角度看FPGA 就是一块“内存”你在代码里写一个地址读一个地址数据就过去了不需要一堆通讯协议栈也不占用 CPU 大量时间做分包组包而是靠地址映射和片选信号直接读写吞吐能力和响应速度都能拉满。这个项目我做的时候用的是 GD32F450 系列最高主频 200MHzEXMC 支持 8 位或 16 位数据总线宽度可以寻址多个存储区域每个区域有独立的片选信号。FPGA 端我用的是入门级器件逻辑资源不需要太强但它能提供灵活的外部时序响应。GD32 这边只需要配置好 EXMC 的时序参数然后像读 SRAM 一样操作地址FPGA 通过地址译码和数据总线拿到数据整个过程干净利落。如果你原来用过 STM32 的 FSMC 和 FPGA 通信那你会发现 GD32 的 EXMC 基本思路是类似的但细节上有不少需要重新确认的地方。接下来我把这个项目的全部设计思路、关键配置和踩坑过程写下来希望对正在做类似方案的你有点参考价值。2. EXMC 通信方案的核心设计思路2.1 EXMC 与 FPGA 对接的本质EXMC 和 FPGA 对接本质上是把一个并行总线设备挂在 GD32 的总线上FPGA 扮演外部器件的角色通过总线接口解析地址和数据。GD32 的 EXMC 通常支持 NOR Flash、PSRAM、SRAM 和 NAND Flash 这类设备我们利用的是 SRAM 模式因为它的时序最简单读写操作都是异步的FPGA 端容易实现。可以把 EXMC 理解成一个快递柜GD32 是发件人FPGA 是收件人地址线是柜号数据线是包裹内容读信号和写信号就是送货和取货的动作。GD32 说“我要往这个格子放东西”FPGA 听见了就把数据取走反过来 GD32 说“我要取某个格子的东西”FPGA 就把数据放到总线上。整个流程不需要额外的通讯协议只要双方都遵守约定的时序数据就能正确流动。这种设计带来的最大优势是GD32 可以用最简单的代码访问 FPGA 内部寄存器或缓冲区。比如一个数据采集系统FPGA 不断采集 ADC 的数据并放入内部双口 RAMGD32 只需周期性地从固定地址读取就能拿到最新的数据。反过来GD32 给 FPGA 下发配置参数也只需要写几个地址即可不需要串口一帧一帧地解析。2.2 总线宽度规划8 位还是 16 位设计第一步是决定数据总线宽度。GD32 EXMC 支持 8 位和 16 位两种模式。我建议根据两个因素考虑一是通信数据量的大小二是 FPGA 引脚资源的富余程度。如果只是传控制命令和状态量比如几十个寄存器的读写8 位总线完全够用接线也少FPGA 引脚可以省下来做别的事情。如果是批量数据交互比如图像数据、波形采样数据16 位总线效率翻倍一次读写就能搬运两个字节CPU 的循环次数少一半。我的项目里有图像数据传输需求所以选了 16 位模式。这意味着 FPGA 端的双向数据总线有 16 根加上地址线、控制线总共大约 25 根左右布局布线时要注意总线等长和信号完整性特别是数据速率较高的时候线长差太大会导致建立保持时间不够。另外还需要注意字节序的问题。16 位模式下GD32 是小端格式低字节在偶地址高字节在奇地址FPGA 端在做数据拼接时要保持一致否则会出现高低字节互换的经典 bug。2.3 Bank 区域映射与地址规划GD32 的 EXMC 通过不同片选信号区分多个外部存储区域。具体到 F4 系列通常有多个 BANK每个 BANK 对应一个片选引脚地址空间是固定的。我在项目里只使用了一个 BANK把 FPGA 挂在片选 0 上对应的起始地址是 0x60000000。这部分地址空间被映射到 GD32 的内部寻址范围CPU 直接读写该地址就能触发 EXMC 总线周期。地址规划上我把 FPGA 内部的寄存器按功能划分到不同的地址偏移。比如偏移 0x0000-0x00FF 放控制寄存器偏移 0x0100 以后放数据缓冲区。FPGA 解码地址线时只关注低位地址用高位地址做区域选择这样可以扩展更多的内部地址空间。一定要注意地址对齐的问题16 位模式下地址线错位规则和 8 位模式不同通常是 A0 不参与数据对齐内部偏移从 A1 开始计算FPGA 端译码时也要对应处理。这在调试时特别容易出问题我在第一次上电测试时往地址 0x60000000 写值FPGA 端看到地址出现在 0x00000001 上排查了很久才发现是地址线错位导致。这个问题后面会在常见问题里详细展开。3. 硬件连接与关键信号设计3.1 信号线清单和连接要点GD32 的 EXMC 和 FPGA 连接核心信号可以分为四组下面是我实际用的连接方式信号类型信号名方向说明数据线DATA[15:0]双向16 位数据总线必须加双向缓冲控制地址线ADDR[15:0]输出用于内部寄存器或缓冲区寻址按需连接控制线EXMC_NE输出片选信号低有效选中 FPGA控制线EXMC_NOE输出读使能低有效控制线EXMC_NWE输出写使能低有效时序上GD32 EXMC 在写操作时先拉低片选然后地址稳定再拉低写使能数据在写使能期间保持有效最后释放写使能完成锁存。读操作类似GD32 发送地址后拉低读使能FPGA 需要在一个规定时间内把数据放到总线上然后 GD32 在上升沿采样数据。双方必须严格匹配这些时间参数尤其是 FPGA 的数据输出延迟。硬件连接上我有几个建议。第一数据总线一定要加上下拉电阻或总线缓冲器防止总线悬浮导致 FPGA 端误判电平。第二FPGA 端的数据总线 IO 必须设置为三态并在片选和读写信号无效时输出高阻态否则多个设备驱动总线会造成冲突。第三GD32 和 FPGA 之间如果距离比较远建议在关键控制线上加串联电阻通常 22Ω 到 33Ω可以减少信号反射。3.2 FPGA 端总线时序逻辑怎么设计FPGA 端是整个通信的从机需要解析 GD32 发起的总线操作。我的做法是用 Verilog 写一个简易的从机接口模块核心是一个状态机监控片选信号、读信号和写信号的变化。当检测到片选有效且写信号出现下降沿时锁存地址总线和数据总线并根据地址把数据写入对应的内部寄存器组这个很好理解。读操作需要注意时序。GD32 给出地址并拉低读使能后FPGA 必须在一个很短的时间内完成地址译码并把数据推到数据总线上。这个时间通常只有几十纳秒如果 FPGA 内部逻辑链路过长很容易超时。解决方法是在 FPGA 内部做一次流水线提前把地址译码结果准备好或者使用 Block RAM 的异步读端口能极大缩短输出延迟。时钟方面如果 GD32 和 FPGA 有独立的时钟源建议在 FPGA 端用同步逻辑重新采样控制信号避免亚稳态。我当时用的是 GD32 的 EXMC 时钟作为 FPGA 端参考时钟通过一个全局时钟引脚进入 FPGA保证控制信号和内部逻辑同步调试时省了很多麻烦。4. 软件代码实现与配置要点4.1 初始化 EXMC 控制器的关键配置GD32 的固件库对 EXMC 做了封装但不同系列的库函数名和结构体会略有差异。以我的 GD32F450 项目为例关键配置代码如下void exmc_init(void) { exmc_gpio_config(); exmc_norsram_parameter_struct exmc_norsram_init_struct; exmc_norsram_struct_para_init(exmc_norsram_init_struct); exmc_norsram_init_struct.norsram_region EXMC_BANK0_NORSRAM_REGION0; exmc_norsram_init_struct.write_mode EXMC_ACCESS_MODE_A; exmc_norsram_init_struct.data_width EXMC_NORSRAM_DATA_WIDTH_16B; exmc_norsram_init_struct.memory_type EXMC_MEMORY_TYPE_SRAM; exmc_norsram_init_struct.address_setup_time 5; exmc_norsram_init_struct.address_hold_time 5; exmc_norsram_init_struct.data_setup_time 8; exmc_norsram_init_struct.write_setup_time 5; exmc_norsram_init_struct.write_hold_time 5; exmc_norsram_init_struct.data_latency_time 0; exmc_norsram_init_struct.read_bus_latency_time 0; exmc_norsram_init_struct.write_bus_latency_time 0; exmc_norsram_init_struct.address_data_mux DISABLE; exmc_norsram_init_struct.burst_mode DISABLE; exmc_norsram_init_struct.wait_polarity EXMC_CONTROL_WAIT_POLARITY_LOW; exmc_norsram_init_struct.write_enable ENABLE; exmc_norsram_init_struct.wait_signal DISABLE; exmc_norsram_init_struct.extend_mode DISABLE; exmc_norsram_init(exmc_norsram_init_struct); exmc_norsram_enable(); }注意看这里我用的memory_type是 SRAM不是 NOR Flash。这个区别很重要NOR Flash 模式会有额外的命令锁存时序SRAM 模式就是纯粹的地址锁存和读写FPGA 端实现起来更直接。ACCESS_MODE_A和ACCESS_MODE_B的区别在于读和写时序的差异Mode A 适合 SRAMMode B 更适合 NOR Flash。我选 Mode A也建议你用 Mode A。时序参数address_setup_time、data_setup_time等需要根据 FPGA 端的实际响应速度来调整这个没有固定答案只能测试中优化。4.2 GPIO 复用配置GPIO 配置是很多人容易忽略的地方。EXMC 引脚不像普通 GPIO 那样设置输入输出就行必须配置为复用推挽模式。GD32 的固件库中 GPIO 初始化结构体里gpio_mode要设为GPIO_MODE_AF_PP然后调用gpio_pin_af_config把引脚复用到 EXMC 功能上。我曾经看到有人把数据引脚配置成普通推挽输出结果读数据死活不对因为普通输出模式下引脚没有三态能力当 FPGA 往总线放数据时GD32 的引脚还在主动驱动总线两个输出必然打架。所以AF_PP这个配置是必须的不是为了好看是电气特性上的硬性要求。引脚分配上不同型号的 GD32 支持的引脚映射不一样甚至同一型号不同封装也有差异。设计 PCB 之前一定先查数据手册的引脚定义表确认使用的引脚号支持 EXMC 复用功能避免后续飞线或者改板。4.3 读写操作封装初始化完成后EXMC 的读写就是访问指针非常简单但建议封装成函数方便做错误处理和调试日志#define FPGA_BASE_ADDR (0x60000000u) #define FPGA_REG_CTRL (0x0000u) #define FPGA_REG_STATUS (0x0002u) #define FPGA_DATA_BUFFER (0x0100u) static volatile uint16_t *fpga_reg_ctrl (volatile uint16_t *)(FPGA_BASE_ADDR FPGA_REG_CTRL); static volatile uint16_t *fpga_reg_status (volatile uint16_t *)(FPGA_BASE_ADDR FPGA_REG_STATUS); static volatile uint16_t *fpga_data_buffer (volatile uint16_t *)(FPGA_BASE_ADDR FPGA_DATA_BUFFER); uint16_t fpga_read_reg(uint32_t offset) { return *(volatile uint16_t *)(FPGA_BASE_ADDR offset); } void fpga_write_reg(uint32_t offset, uint16_t value) { *(volatile uint16_t *)(FPGA_BASE_ADDR offset) value; }这套封装很薄但好处是后续如果要加缓存、统计读写次数、或者做断言检查只需要在函数内部增加逻辑所有调用方不用改动。我在实际项目中还用volatile关键字避免编译器优化导致漏读漏写这也是低级但致命的错误。读操作返回后建议先判断一下状态寄存器的值验证 FPGA 是否已经准备好数据。我们这边踩过坑GD32 读取数据时 FPGA 刚好在更新缓冲区拿到一半新一半旧的数据。后来在 FPGA 端加了数据有效标志GD32 先查询标志再读数据问题就解决了。5. 调试记录波形分析与典型问题排查5.1 用逻辑分析仪验证时序是否匹配接口通信调试最忌讳的就是闷头猜问题。我调试 EXMC 和 FPGA 通信第一件事永远是接逻辑分析仪把片选、读使能、写使能、地址线、数据线全部抓出来跟数据手册上的时序图做对比。GD32 的 EXMC 时序可以在参考手册里找到对应模式下的波形图对照检查地址建立时间、数据建立时间是否符合预期。我第一次上板调试写操作波形没有任何问题但读操作出现数据一直读到 0xFFFF 的情况。用逻辑分析仪抓波形发现FPGA 的数据总线根本就没被驱动起来一直停留在高阻态。后来定位到 FPGA 端读操作的三态门控制逻辑写反了片选无效时将数据输出了片选有效时反而高阻。这种问题如果没有逻辑分析仪很难定位到 FPGA 内部。5.2 地址错位问题排查16 位总线模式下地址线对齐是个高频问题。GD32 的 EXMC 在 16 位宽度时内部地址总线的处理方式和 8 位不同。如果用 16 位模式低位地址线 A0 不参与寻址实际上会偏移一位。比如你想访问偏移 0x0002需要给的地址线信号是 FROM 0x0001 对应的地址线状态。FPGA 端译码时要么主动把地址线右移一位再解析要么在定义寄存器偏移时就考虑到这个错位。我的做法是 FPGA 端在顶层接口把地址线addr[15:1]拿出来做内部译码这样内部寄存器偏移定义和 GD32 端完全一致不会搞混。这个问题的隐蔽性在于如果你只写地址 0x0000 和 0x0001它们对应的物理地址线完全相同根本测不出问题但一旦访问地址 0x0002 和 0x0003就会出乱子。我建议在联调初期先用一个读写测试函数逐个地址写一个固定值再读回来比对结果这样能快速发现错位。5.3 时序裕量与速度优化EXMC 的时序参数不是越大越好也不是越小越好需要找到双方都能接受的平衡点。参数太大单次读写耗时过长高速传输时 CPU 被拖累参数太小建立时间不够数据采样不稳定。我在实际调优时先用相对宽松的参数保证功能正确然后用逻辑分析仪观察信号建立和保持的裕量。比如数据建立时间从data_setup_time 8开始功能正常后逐步减小到 5、4每次修改后跑一轮压力测试验证上千次读写没有错误才算稳定。硬件上如果布线质量差信号上出现过冲和振铃稍微宽松的参数可以掩盖这些问题但产品的长期可靠性不能依赖这个还是要回归到信号完整性上解决。另外提到 GD32 和 FPGA 之间的通信volatile和内存屏障的问题也要注意。虽然你的读写是直接映射的地址访问但编译器可能会因为优化而调整访问顺序尤其是在做批量读写时。我的建议是对于 FPGA 端一些有前后依赖关系的寄存器操作逗留之间不要依赖编译顺序必要的时候可以用__DMB()或者直接不优化某些函数避免底层硬件操作被编译器重排。5.4 常见问题速查表现象可能原因解决方法写入后 FPGA 端数据全为 0地址线错位或写使能时序不满足检查 EXMC 的地址映射抓波形确认 NWE 时序读回数据一直是 0xFFFFFPGA 没有驱动数据总线检查 FPGA 端三态门逻辑确认读操作时序偶发读写数据错误时序裕量不足或信号质量问题适当增加 data_setup_time检查线路串阻和地线系统卡死程序跑飞EXMC 总线访问超时导致硬件异常确认 BANK 地址空间是否正确是否访问了不存在的地址和 FPGA 握手正常但性能远低于预期每次读写之间等待过长开启 EXMC 的突发模式或者用 DMA 搬运数据阅读项目时很多人还会混淆 EXMC 的 Bank 0 和 Bank 1 及不同寄存器配置。GD32 的 EXMC 在不同型号上支持的区域数量不完全一致有的只有 Bank0有的支持多 Bank。如果设计中要用到多个片选必须确认芯片手册里的区域划分并检查 Bank 间地址是否重叠。6. 后续扩展DMA 搬运和大数据块传输EXMC 通信一个非常值得扩展的方向是利用 DMA 做数据搬运。GD32 的 DMA 控制器可以配置为内存到内存或者内存到外设模式这里我们可以配置为从 FPGA 的地址空间读取数据到内部 SRAM而不经过 CPU 逐字搬运大幅提高吞吐效率。配置 DMA 时要注意几件事数据宽度必须和 EXMC 总线宽度一致传输方向不要搞混外设地址就是 FPGA 的基地址内存地址是缓冲区地址传输大小按字节或半字计算。我用 DMA 配合 EXMC 做了一次 64KB 数据块的批量读取测试效果对比非常明显。在 CPU 循环读取模式下读 64KB 数据耗时约 2 毫秒切换 DMA 后降到几十微秒级别差距超过一个数量级。如果你的项目里有实时性要求高的批量数据传输场景强烈建议加 DMA不要省这个功能。还有一个可以扩展的点是用 EXMC 的突发传输模式。GD32 的 EXMC 支持同步突发读取在 NOR Flash 模式下可以配置突发长度但 SRAM 模式下的支持力度要看具体芯片型号。我那边最终没有用突发模式因为 FPGA 端实现同步时序相对复杂DMA 方案已经满足需求但如果你对带宽有极致要求可以深入研究一下。7. 关于接口选择的一点个人体会项目做完之后如果再让我选一次我还是会选 EXMC 而不是 SPI 或者并口 GPIO 模拟。GPIO 模拟总线的方案听起来简单灵活但实际速率根本提不上去而且 CPU 被占得死死的完全没有扩展性。SPI 虽然接线少但在双方向高吞吐场景下协议开销和中断处理成本都不低。EXMC 最大的价值在于它把 FPGA 完美地抽象成了一块内存读和写变成了最简单的地址操作整个软件框架清晰了很多。与此同时它也有代价就是对硬件设计的要求高时序调试相对复杂算法工程师需要快思考慢思考调整参数时要多看波形。最后分享一个小经验第一次做 EXMC 和 FPGA 通信的时候不要一上来就追求复杂功能先做一个最小系统点亮一个 LED 或者读写一个寄存器验证时序通了再扩展功能。高速路上栽跟头往往是地基没打好。当时我为了验证时序写了一个简单的寄存器回环测试GD32 写一个值到 FPGA 的寄存器FPGA 把值原样读出来经过几百次循环比对无误后才敢继续往下加功能。这个习惯希望你也有。本文还有配套的精品资源点击获取
返回列表