基于UART的FPGA外部Flash烧写方案:从Bootloader到远程升级的全流程解析
2026/9/20 20:44:21 网站建设 项目流程

简介:面向嵌入式开发者的智多晶UART-FLASH设计方案,聚焦通过UART接口实现Flash编程与固件在线升级,适用于设备维护、日志存储等场景。压缩包约1.61MB,共97个文件,其中34个C头文件与21个C源文件构成完整工程代码,另有汇编、Tcl脚本、hex/bin固件镜像、工程配置及串口在线升级说明文档,覆盖从源码到烧录的完整链路。已有315人学习/下载。内容包含UART通信协议解析、Flash读写与擦除流程、CRC错误检测与重传机制、安全保护策略等细节,并附带RTC固件、Flash扩展工具和串口调试助手,方便开发者对照验证。通过该资源可快速掌握UART-FLASH的初始化、命令交互、数据传输与固件切换方法,为嵌入式系统的远程升级与数据管理提供可直接落地的工程参考。 有段时间我经常跑外地调试设备。设备用的是智多晶的FPGA,程序放在外挂的SPI Flash里。出门在外最怕的就是改完逻辑发现没带下载器,或者下载器驱动在客户电脑上装不起来。后来我把UART-FLASH这套烧写方案在项目里正式落地——只用一根USB转串口线,就能完成对FPGA外部配置Flash的擦除、写入和校验,甚至能支撑现场和远程的固件升级。这套方案适合所有用智多晶FPGA做产品、又经常需要跟现场打交道的工程师,尤其是设备已经部署出去、不方便开盖插JTAG的场景,几乎是刚需。

下面把我在智多晶平台上从方案设计到实际落地、再到踩坑修复的整个过程完整讲一遍。内容不涉及厂商私有协议,属于通用做法,照着搭就能跑。

1. 为什么要把烧写链路从JTAG换成串口:一次现场事故带来的需求

先说我自己碰到的真实场景。那次去客户现场改一版逻辑,板子装在一个密封壳里,JTAG座子在PCB内侧,要拆四颗螺丝,排线还压在电源模块底下。客户只留了一个调试串口出来,下载器又没带。最后只能让同事远程把新bitstream发过来,我用串口线连上板子,配合一个临时写的Bootloader把数据写进Flash,板子重启后新逻辑直接跑起来。从那以后,UART-FLASH就从“临时救火工具”变成了我项目里的标准配置。

1.1 UART-FLASH到底解决了哪几类问题

第一类是现场维护。产品交付后外壳基本不拆,JTAG座子往往被结构件挡住,但串口调试口一般会预留。有了UART-FLASH,现场升级只需要一条串口线和一台笔记本,不需要开盖、不需要专用下载器。

第二类是远程升级。串口后面接一个4G透传模块或者WiFi串口服务器,PC端工具在办公室就能把固件推过去。配合Bootloader里的双镜像机制,升级失败还能自动回退,设备不会变砖。

第三类是产线批量烧写。很多生产工装只有串口接口,一台工装拖好几块板子轮流烧,比每次插JTAG效率高得多。产线上对速度更敏感,可以把波特率拉高,后面我会专门算一笔时间账。

1.2 这条链路和JTAG烧写的本质区别

JTAG烧写走的是FPGA芯片内部的调试访问端口,由下载器直接控制Flash,本质上是“外部设备操作FPGA再操作Flash”。UART-FLASH则走的是“上位机 → UART → FPGA内部Bootloader逻辑 → SPI Flash”这条路径。Bootloader可以是芯片出厂固化的,也可以是我们自己用逻辑写进去的。

智多晶平台上并不是所有型号都内置了串口Bootloader,所以更通用的做法是:先用JTAG往FPGA里烧一个“烧写器工程”(也叫Golden Image),这个工程上电后主动从Flash固定地址加载用户逻辑,同时监听串口命令。后续所有升级都走串口,不再需要JTAG。这套方案完全用FPGA普通逻辑资源和外部SPI Flash实现,不依赖芯片私有功能,换到其他FPGA平台也能平移。

2. 方案选型与硬件链路:从串口到Flash的每一步

UART-FLASH不是光写逻辑就完事,硬件链路先得把基础打对。链路从上到下依次是:PC端工具 → USB转串口芯片 → FPGA的UART引脚 → 内部Bootloader → SPI控制器 → 外部SPI Flash。任何一个环节出问题,烧写都会失败。

2.1 器件选型:Flash、串口芯片和电平标准

SPI Flash我优先选Winbond W25Q系列或GigaDevice GD25Q系列,容量按项目bitstream大小的4到8倍留。比如用户工程bitstream约256KB,Bootloader占一个镜像区,用户镜像占一个区,再加一个出厂备份区和配置参数区,总共至少要1MB以上,所以W25Q64(8MB)属于非常稳妥的起步选择。选Flash时注意区分标准SPI和QSPI,第一版方案建议只用标准SPI四线(SCK、CS_N、MOSI、MISO),兼容性和稳定性最好,等跑通后再考虑上QSPI提速。

UART接口这边,如果板子上有USB转串口芯片(CH340、CP2102、FT232都可以),直接连FPGA的UART_RX/UART_TX;如果是直连TTL电平,则需要自己接一个USB转TTL小板。要注意的是:FPGA的UART引脚和Flash引脚最好都放在同一个IO Bank,且该Bank的VCCO电压一致。我见过有人把Flash放在3.3V的Bank,UART放在1.8V的Bank,结果串口数据一直乱码,排查半天才发现是电平域不统一导致的问题。

2.2 引脚分配与PCB走线需要注意的细节

  • SPI四根信号线尽量短,走线长度差控制在几厘米以内,避免高速通信时时钟和数据偏移过大。
  • CS_N、MISO、MOSI上加上拉电阻,一般4.7kΩ到10kΩ。Flash在FPGA未配置期间引脚是高阻态,不加下拉或上拉容易受到干扰,上电瞬间可能误触发写操作。
  • 配置模式引脚要预留拨码开关或0欧电阻位。FPGA上电时根据这些引脚的电平决定从哪个接口加载配置,调试阶段方便在JTAG模式和主SPI模式之间切换。具体引脚命名和组合逻辑要查智多晶对应型号的手册,不同器件定义不一样,千万别想当然照搬其他厂家的习惯。
  • 串口的TX/RX如果走线较长,加TVS管防静电。现场设备经常在恶劣环境里插拔串口线,不加保护容易打坏FPGA引脚。

2.3 一个容易被忽略的点:Flash的IO电压版本

Flash的VCC和IO电压必须匹配FPGA的Bank电压。3.3V FPGA配3.3V Flash是最常见的组合,但如果你用的是1.8V Bank,就得选W25Q64JW这类1.8V版本。我之前把3.3V Flash接到1.8V Bank上,逻辑分析仪抓波形看着完全正常,但Flash就是写不进去,最后查手册才发现Flash的IO识别电平下限是2.0V左右,1.8V信号根本达不到高电平阈值。这种问题不写进经验帖,靠现场排查真的能卡一整天。

3. Bootloader工程的逻辑实现:串口协议与Flash控制状态机

Bootloader是整个UART-FLASH方案的核心。它要做的事情很纯粹:上电后等待串口命令,收到合法命令后对Flash做擦除、写入、校验,最后跳转加载用户逻辑。代码量不大,但协议设计和状态机健壮性直接决定现场升级的成败。

3.1 串口通信帧结构设计

我用的帧格式如下,每一帧固定以帧头开始,帧尾结束,中间所有字段参与CRC32校验。CRC用CRC-32/MPEG-2标准,FPGA里用LFSR实现,PC端用软件算,两边对齐。

字段长度(字节)说明
帧头2固定0xAA 0x55
命令字1握手/擦除/写页/读校验/跳转/状态查询
地址4大端序,Flash目标地址
长度2数据区长度,最大256
数据0-256写页命令时携带一页数据
CRC324对上述所有字段计算
帧尾2固定0x0D 0x0A

握手流程是:PC发送握手帧,Bootloader收到后回ACK和设备版本号,如果CRC不对则回NAK,PC端超时1秒重发。这个设计能快速判断串口链路是否正常,避免一上来就盲目擦写Flash。

3.2 SPI Flash操作状态机的关键设计

Bootloader内部是一套典型的状态机:

IDLE → CMD_DECODE → FLASH_ERASE → WAIT_ERASE_BUSY → PAGE_PROGRAM → WAIT_PROGRAM_BUSY → VERIFY → ACK → IDLE

每个环节都有几个硬性要求:

  • 写Flash前必须先发送写使能命令(0x06),否则Flash直接忽略写操作。
  • 擦除和页编程完成后必须轮询Flash状态寄存器(0x05)的BUSY位,直到bit0变0。不能只靠固定延时,因为Flash擦除时间受温度和芯片个体差异影响很大。
  • 页编程一次最多写256字节,跨页必须拆包。发0x02命令、24位地址、数据,等待BUSY位结束后再发下一包。
  • 全程使用SPI Mode 0(CPOL=0,CPHA=0),这是绝大多数SPI Flash和FPGA的SPI控制器默认支持的模式,通用性最好。

我把写数据路径上的FIFO深度设成512字节,PC端按256字节一页发送,FPGA收到一页后写入Flash,写完回ACK再发下一页。这样即使UART中断响应不及时,数据也不会溢出丢失。

3.3 校验策略:不能只依赖CRC

每页数据写入前,Bootloader就对这一页做CRC32实时累加,同时PC端也在算同样的值。所有页写完后再做一次整体校验:PC端发送读校验命令,Bootloader把Flash中的数据读回来做CRC比对,结果回传给PC,两边一致才算升级成功。

更稳妥的做法是再加一道“启动标志”机制:Flash的配置区里固定放一个标志字,升级开始时把标志写成0xA5A5A5A5(表示升级中),全部完成后改写成0x5A5A5A5A(表示完成)。上电时Bootloader检测到这个标志不对,就认为上次升级异常中断,自动进入等待重新升级的状态,防止设备启动一个半截写入的镜像。

4. 烧写时效与可靠性:波特率、超时和断点续传

UART-FLASH方案经常被质疑的一点是速度慢。确实是慢,但慢不等于不能用,关键在于把时间账算清楚,并且把可靠性做足。

4.1 115200波特率下烧写256KB需要多久

8N1格式下,一个字节要传10bit(1起始位+8数据位+1停止位)。115200bps的有效数据吞吐率是11520字节/秒。256KB的bitstream理论传输时间是:

256 × 1024 / 11520 ≈ 22.8秒

再加上帧头、地址、长度、CRC这些协议开销,以及每页等待ACK的往返时间,实际传输大概要30到40秒。如果之前没擦除Flash,还要加上擦除时间。W25Q64扇区擦除典型45ms,全片擦除大概几十秒。所以整个升级流程压在60到90秒是比较真实的预期。

如果嫌慢,可以把波特率提到460800或者921600。USB转串口芯片配合质量好一点的线材,跑921600很常见,传输部分能从30秒压到4到6秒,但Flash擦除时间省不掉。产线批量烧写时我一般用921600,现场调试用115200,稳定性优先。

4.2 可靠性的几个工程细节

  • 每个数据包带序号,PC端重发时会带上包序号,Bootloader通过序号判断是不是重复包,避免重复写入。
  • 超时策略分两级:PC端发包后500ms内没收到ACK就重发;Bootloader内部接收状态机空闲超时则回到IDLE,等待下一帧帧头,防止半包数据卡死链路。
  • 擦除操作放在写数据之前统一做,不要写一页擦一页,否则擦除时间会拖慢整体流程。
  • 如果对掉电特别敏感,可以在Flash里做一个“临时缓冲镜像区”:新镜像先写到临时区,全部校验通过后再用一条“切换命令”把启动标志指向新镜像地址,整个过程中旧镜像始终保留。这个做法在远程升级场景里价值很大。

5. 上电加载流程与镜像回退机制

烧写只是手段,最终目的是让FPGA上电后能从Flash正确加载用户逻辑。这个过程看起来简单,实际有坑。

5.1 配置模式引脚与主SPI加载

FPGA上电后,会先读配置模式引脚的电平,决定从哪个接口读取配置数据。UART-FLASH方案里,Bootloader完成升级后会把FPGA重新配置一次,此时需要让器件进入主SPI模式,由FPGA作为SPI主机主动从外部Flash读取用户镜像。

智多晶具体型号的配置模式引脚命名和电平组合,在数据手册里有专门一节,做PCB时必须认真核对。我在第一个版本里直接把某知名厂家的引脚映射习惯套过来,结果FPGA一直进不了主SPI模式,后来仔细查手册才发现引脚号和有效电平完全不同。这一步没有任何捷径,只能一页一页看手册。

5.2 Flash布局:给双镜像和配置区留位置

推荐的分区方式如下:

地址范围内容说明
0x000000 - 0x0FFFFFBootloader(Golden Image)上电后最先加载,包含UART-FLASH逻辑
0x100000 - 0x1FFFFF用户镜像A正常运行的应用逻辑
0x200000 - 0x2FFFFF出厂固件/备份镜像用于紧急回退
0x3F0000 - 0x3FFFFF配置区启动标志、版本号、CRC等

Bootloader的体积通常几十KB,用户镜像根据资源使用情况可能几百KB。Flash容量按我说的“4到8倍”预留,就是为了给多个镜像和配置区腾出空间,后续扩展也不至于推倒重来。

5.3 回退逻辑:别让设备变砖

Bootloader上电后先读配置区的启动标志和当前应启动的镜像地址。如果用户镜像CRC校验失败或者启动标志显示“升级未完成”,就做两件事:如果存在出厂固件且出厂固件CRC有效,则直接跳转到出厂固件地址启动;否则停留在Bootloader模式,通过串口等待重新升级。

我见过很多公司做远程升级不考虑回退,结果现场升级升到一半断电,设备从此变砖,只能寄回厂家拆壳用JTAG刷。有了这一层回退机制,最坏情况也只是设备停在出厂版本,设备还能工作,后面找时间重新升级一次就行。

6. 实测踩坑记录:我在智多晶平台上踩过的五个坑

这套方案写出来看着顺畅,实际落地过程一波三折。下面这几个坑我觉得值得单独列出来,供后面做的人参考。

6.1 串口丢字节,问题出在波特率误差

第一版联调时,PC端发给Bootloader的握手包经常没响应,用串口助手看发出去的字节都正常,但FPGA端收到的是乱码。排查方法是用逻辑分析仪抓UART波形,测出实际波特率是114300左右,跟标称115200差了将近0.8%。USB转串口芯片在不同线材和驱动状态下,波特率误差不是固定值。后来我把Bootloader里的UART接收改成按实际测量值重新计算分频参数,同时换上质量好一点的串口线,问题才彻底解决。这也是为什么我建议现场维护时优先用115200,劣质线材高波特率下丢字节概率会明显变大。

6.2 Flash擦除超时:固定延时不可靠

刚开始图省事,擦除命令发完后直接延时500ms再继续。结果有批Flash在低温环境下全片擦除要40多秒,Bootloader早就超时返回错误了。后来改成轮询状态寄存器BUSY位,擦多久都不怕。这个改动同时对不同批次Flash的个体差异免疫,属于治本的做法。

6.3 校验全过但加载失败:字节序的锅

这个坑是最隐蔽的。写页命令读回校验CRC全部正确,但复位后FPGA就是起不来,用JTAG读Flash内容跟PC端发的原始文件一对比,发现数据字节没问题,但每个字节内部的bit顺序反了。原因在于我当时用的Flash和FPGA的SPI控制器在位序定义上不一致,一个按MSB first传输,一个按LSB first传输。解决方法是写Flash前在Bootloader里把每个字节做一次位反转,或者直接在PC端工具里先把数据按位反转再发送。遇到这类问题,先用JTAG把Flash原始内容读出来跟源文件做二进制对比,很快就能定位。

6.4 SPI时钟分频参数写错

Bootloader里SPI时钟由FPGA系统时钟分频产生。第一次我把分频参数算错,SCK跑到了接近50MHz,读操作勉强正常,写操作时不时失败。Flash的数据手册里明确写着读时钟和写时钟的频率上限不同,扇区擦除、写状态寄存器这类命令保守一点最好压在1MHz以下。后来我把SPI时钟统一分到10MHz以内,读写都稳定了。这里没有性能焦虑的必要,Bootloader的瓶颈本来就在串口,SCK快一点慢一点都不影响整体时间。

6.5 电平不匹配导致Flash写不进去

前面硬件章节提到的1.8V Bank配3.3V Flash的问题,实际定位过程很费劲。逻辑分析仪抓波形看起来都对,CLK、MOSI、CS都正常,Flash就是不响应写命令。最后查Flash手册发现是电平阈值问题。这个经验我建议所有人在画板阶段就确认一遍:FPGA的Bank电压、Flash的IO电压、串口芯片的电平,三者必须在同一电压域,或者中间加电平转换芯片。

写在后面的一些体会

这套UART-FLASH方案我在智多晶平台上的几个项目里跑了一年多,现场升级成功率明显比之前拆壳插JTAG高很多。关键点其实不在技术本身,而在设计阶段就要把串口引出、配置模式引脚、Flash分区、Bootloader和用户镜像分开管理这些都规划好。Bootloader代码尽量少动,每次只升级用户镜像,稳定性会好很多。如果第一次接触这个方案,建议先用最小验证板把串口协议和Flash状态机跑通,再往正式项目里集成,能省掉大量反复排查的时间。最后提醒一句:别小看那几页数据手册里关于配置模式和Flash时序的表格,我的大部分坑都是栽在“想当然”这三个字上。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询