1. 项目背景与核心价值
在工业控制和嵌入式系统领域,现场设备程序升级一直是个让人头疼的问题。传统方式要么需要工程师带着烧录器跑现场,要么就得拆设备取芯片,费时费力还影响生产。我们团队最近用纯Verilog实现的FPGA串口升级方案,完美解决了这个痛点。
这个方案最吸引人的地方在于:整个升级流程完全基于串口通信,不需要任何外部处理器参与,从协议解析到Flash操作全部用Verilog硬件描述语言实现。实测在Xilinx Artix-7平台上,仅占用不到800个LUT资源,却能稳定支持115200bps的升级速率。对于需要长期野外工作的设备,这种"一根串口线走天下"的升级方式简直是运维人员的福音。
2. 整体架构设计思路
2.1 为什么选择纯Verilog方案
市面上常见的FPGA升级方案大致分三类:一是依赖外部MCU通过JTAG或SelectMAP接口操作,二是使用厂商提供的软核处理器(如MicroBlaze)运行升级程序,三是基于硬核处理器(如Zynq的PS端)。我们选择纯Verilog实现主要基于以下考量:
- 资源利用率:在资源受限的低端FPGA上,软硬核处理器动辄占用数千LUT,而我们的协议解析+Flash控制器仅需几百LUT
- 实时性保障:硬件逻辑实现的协议解析可以严格保证时序,避免处理器方案可能出现的响应延迟
- 部署便捷性:无需维护额外的处理器固件,整个升级逻辑与主设计一起编译生成单一bitstream文件
2.2 核心模块分解
整个系统由五个关键模块组成:
- UART协议解析器:负责字节接收、波特率检测和帧同步
- 命令分发器:解析上位机指令(擦除/写入/校验等)
- Flash控制器:实现SPI接口时序和特定Flash芯片的操作序列
- 双Bank切换器:管理两个镜像区域的切换逻辑
- 看门狗模块:防止升级过程中断导致设备变砖
verilog复制module uart_upgrade (
input wire clk,
input wire rst_n,
input wire uart_rx,
output wire uart_tx,
output wire spi_cs,
output wire spi_sck,
output wire spi_mosi,
input wire spi_miso
);
// 各模块实例化代码...
endmodule
3. 关键实现细节解析
3.1 自定义轻量级协议设计
考虑到FPGA资源限制,我们设计了极简的通信协议:
code复制| 同步头(0xAA55) | 命令字(1B) | 数据长度(2B) | 数据(NB) | CRC16(2B) |
协议特点包括:
- 使用固定的0xAA55作为帧头,便于硬件检测
- 支持7种基本命令:握手、擦除、写入、读取、校验、重启、回退
- CRC校验采用CCITT标准多项式(0x1021)
- 单帧最大支持1024字节数据段,适合Flash页写入粒度
重要提示:帧间隔必须大于3个字节时间,否则可能因连续帧导致解析错误。实测在115200bps下,建议上位机发送间隔至少300μs。
3.2 Flash操作的安全实现
我们支持的主流SPI Flash芯片(如Winbond W25Q系列)有几个关键注意事项:
- 写使能时序:必须在每个页编程或扇区擦除前单独发送WREN指令
- 状态轮询:写入/擦除操作后要持续读取状态寄存器直到BUSY位清零
- 页边界处理:跨页写入时需要自动拆分操作,这是最容易出bug的地方
verilog复制// 典型的页编程状态机片段
always @(posedge clk) begin
case(state)
SEND_WREN: begin
spi_cs <= 1'b0;
spi_data <= 8'h06; // WREN指令
if(spi_done) state <= PAGE_PROG;
end
PAGE_PROG: begin
spi_data <= {8'h02, flash_addr[23:16], flash_addr[15:8], flash_addr[7:0]};
state <= SEND_DATA;
end
// 后续状态省略...
endcase
end
4. 双Bank镜像切换机制
4.1 存储布局设计
我们采用经典的A/B双Bank设计,每个Bank包含:
- 起始处的4KB引导区(含版本信息和CRC校验码)
- 紧接着的1MB主镜像区
- 末尾的256字节配置区(保存启动标志等元数据)
code复制Flash地址空间布局:
0x000000 +-------------------+
| Bootloader |
+-------------------+
| Bank A (1MB) |
+-------------------+
| Config Area A |
0x100000 +-------------------+
| Bootloader |
+-------------------+
| Bank B (1MB) |
+-------------------+
| Config Area B |
0x200000 +-------------------+
4.2 安全切换流程
切换Bank时需要严格遵循以下步骤:
- 对新镜像进行全区域CRC校验
- 写入目标Bank的标志位(但保持当前启动Bank不变)
- 发送重启命令,由Bootloader根据标志位决定加载哪个Bank
- 启动成功后自动清除旧Bank的标志位
这个设计确保了即使在切换过程中断电,设备也能回退到旧版本正常运行。
5. 上位机工具开发要点
虽然FPGA端是纯Verilog实现,但好的上位机工具能极大提升用户体验。我们基于Python开发的命令行工具主要实现以下功能:
- 自动波特率检测:通过发送特定同步字符测量响应时间
- 差分烧录:仅编程发生变化的Flash扇区,大幅缩短升级时间
- 进度可视化:实时显示传输进度和预计剩余时间
- 版本回滚:支持一键恢复到之前的任一稳定版本
python复制# 简化的Python通信示例
def send_cmd(ser, cmd, data=b''):
packet = b'\xaa\x55' + bytes([cmd])
packet += len(data).to_bytes(2, 'big')
packet += data
packet += crc16(packet).to_bytes(2, 'big')
ser.write(packet)
return wait_ack(ser)
6. 实测性能与优化技巧
在Xilinx Artix-7 XC7A35T平台上实测结果:
- 资源占用:782 LUTs / 432 FFs
- 最大时钟频率:85MHz(远高于串口需求)
- 升级速度:115200bps下约90秒完成1MB镜像传输
- 功耗表现:升级过程中整机功耗增加不到5mW
几个关键优化点:
- 接收缓冲设计:采用双缓冲乒乓操作,避免因处理延迟导致数据丢失
- SPI时钟分频:动态调整SPI时钟(最高25MHz),适配不同Flash型号
- 并行CRC计算:使用16位并行CRC模块,不成为时序瓶颈
7. 常见问题排查指南
7.1 连接不稳定问题
- 现象:频繁出现CRC错误或超时
- 排查步骤:
- 检查串口电平是否匹配(3.3V TTL)
- 测量波特率实际偏差(建议小于2%)
- 确认Flow Control已禁用
- 尝试降低波特率(如改为57600bps)
7.2 写入失败问题
- 现象:特定地址段写入后校验失败
- 可能原因:
- Flash芯片存在坏块(需执行全片擦除检查)
- 电源不稳定导致写入电压不足
- 时钟抖动过大(建议添加示波器测量)
7.3 版本切换异常
- 现象:重启后仍运行旧版本
- 解决方案:
- 检查Config Area的写入是否成功
- 确认Bootloader版本是否支持当前切换协议
- 测量复位信号是否正常(至少保持1ms低电平)
8. 扩展应用场景
这套方案经过简单适配,可以扩展到更多有趣的应用:
- 多节点级联升级:通过UART串联多个设备,用不同地址标识实现批量升级
- 无线升级网关:配合蓝牙/WiFi模组实现无线OTA功能
- 生产自动化编程:在生产线末端通过串口一次性完成固件烧录和功能测试
- 安全增强版本:增加AES加密验证,防止未授权固件写入
在实际部署中,我们发现配合简单的脚本自动化,可以把这个方案集成到CI/CD流程中,实现FPGA镜像的持续交付。一个典型的应用场景是智能电表集群,通过集中器下发升级指令,一晚上就能完成上千个节点的无感升级。