简介:面向嵌入式开发者的智多晶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 | 写页命令时携带一页数据 |
| CRC32 | 4 | 对上述所有字段计算 |
| 帧尾 | 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 - 0x0FFFFF | Bootloader(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时序的表格,我的大部分坑都是栽在“想当然”这三个字上。
本文还有配套的精品资源,点击获取