1. 项目概述:FPGA汉字波形显示系统
这个项目实现了一个相当有趣的FPGA应用——通过信号波形发生器在示波器上直接显示汉字。相比传统的七段数码管或LCD显示,这种方案有几个独特的优势:首先是显示效果新颖,直接在示波器上呈现汉字波形;其次是高度可定制,可以显示任意汉字,包括生僻字;最后是刷新率高,在Cyclone IV FPGA上实测50MHz时钟下显示效果非常流畅。
核心原理其实并不复杂:将汉字字形转换为16×16点阵的二进制数据(1表示点亮,0表示熄灭),存储在FPGA的ROM中作为字库。然后通过扫描控制器模块,将这些点阵数据转换为行扫描信号和列数据信号输出。当我们将这两个信号分别接入示波器的X轴和Y轴时,就能在屏幕上看到完整的汉字图形。
提示:这个项目非常适合作为FPGA学习的进阶实践,涉及了存储器使用、时序控制、外设接口等多个重要概念。
2. 系统设计与核心模块
2.1 汉字点阵数据存储
汉字显示的核心是点阵数据的存储和处理。我们采用16×16点阵,每个汉字需要256位(32字节)的存储空间。在Verilog中,我们可以用parameter定义字模数据:
verilog复制parameter [255:0] HANZI_HAN = {
8'h00,8'h20,8'h10,8'h0C,8'h43,8'h80,8'h60,8'h1E,
8'h01,8'h01,8'h7F,8'h01,8'h01,8'h01,8'h01,8'h01,
8'h00,8'h00,8'h00,8'hFE,8'h00,8'h00,8'h00,8'h00,
8'h40,8'h20,8'h10,8'h0C,8'h03,8'h00,8'h00,8'h00
};
每个8位十六进制数对应点阵的一行像素状态。例如0x7F(二进制01111111)表示中间6个像素点亮,两侧各1个像素熄灭。这种表示方法既节省存储空间,又便于程序处理。
2.2 扫描控制器设计
扫描控制器是整个系统的核心,负责按照特定时序输出行扫描信号和列数据信号。其Verilog实现如下:
verilog复制module display_controller(
input clk,
output reg [15:0] row,
output reg [15:0] col
);
reg [4:0] cnt = 0;
always @(posedge clk) begin
cnt <= cnt + 1;
// 行扫描信号生成
row <= 16'h0001 << cnt[3:0];
// 列数据输出
col <= char_data[cnt];
if(cnt == 31) cnt <= 0;
end
endmodule
这个模块的工作原理:
- 每个时钟周期计数器cnt加1
- 行扫描信号是1左移cnt[3:0]位,实现逐行扫描
- 列数据从字库ROM中读取对应行的像素数据
- 每32个时钟周期完成一次完整刷新(16行×2次,提高显示稳定性)
2.3 字库扩展方案
手动输入每个汉字的点阵数据显然不现实,我们可以利用现成的字库文件。UCDOS的HZK16字库是常用的16×16点阵字库,包含GB2312标准的所有汉字。我们可以用Python脚本将其转换为FPGA可用的格式:
python复制with open('hzk16', 'rb') as f:
data = f.read()
offset = (94*(qh-1)+(wh-1))*32
print(''.join([f'{x:02X}' for x in data[offset:offset+32]]))
这个脚本计算指定汉字在字库文件中的偏移量,并输出对应的32字节十六进制数据。将这些数据导入Quartus的Memory Initialization File (.mif)中,就可以在FPGA中构建完整的汉字库。
3. 硬件连接与实现细节
3.1 示波器接口设计
要在示波器上显示汉字,需要将FPGA的输出信号正确连接到示波器:
- 行扫描信号(row)连接到示波器的X轴输入
- 列数据信号(col)连接到示波器的Y轴输入
- 建议使用示波器的XY模式观察显示效果
行扫描信号是依次激活每一行的信号,而列数据则包含了该行所有像素的亮灭状态。这种扫描方式与CRT显示器的原理类似。
3.2 时钟与时序考虑
系统时钟频率的选择需要考虑两个因素:
- 刷新率:时钟频率越高,刷新率越高,显示越稳定
- 示波器带宽:信号变化速度不能超过示波器的采样能力
在Cyclone IV FPGA上,50MHz的时钟频率是一个较好的平衡点。此时:
- 每行显示时间 = 32周期 × 20ns = 640ns
- 完整刷新周期 = 16行 × 640ns = 10.24μs
- 刷新率 ≈ 97.6kHz,远高于人眼的视觉暂留频率
3.3 信号调理电路
为了获得最佳的显示效果,可能需要在FPGA输出和示波器输入之间添加简单的信号调理电路:
- 电阻分压网络:将FPGA的3.3V输出调整到示波器的最佳输入范围
- 低通滤波器:滤除高频噪声,使波形更清晰
- 缓冲放大器:增强信号驱动能力
这些电路可以显著提高显示质量,特别是在使用较低带宽的示波器时。
4. Quartus工程实现
4.1 工程设置与编译
在Quartus Prime 9.0中创建工程的步骤:
- 新建工程,选择正确的FPGA器件型号
- 添加Verilog源文件(display_controller.v等)
- 创建Memory Initialization File (.mif)存储汉字点阵数据
- 设置引脚分配,将输入输出信号分配到实际物理引脚
- 编译工程,解决所有错误和警告
注意:确保为时钟信号分配全局时钟引脚,以获得最佳时序性能。
4.2 SignalTap逻辑分析仪调试
Quartus自带的SignalTap逻辑分析仪是调试这个项目的利器:
- 在工程中添加SignalTap Logic Analyzer
- 设置采样时钟(通常使用系统时钟)
- 添加需要观察的信号(row、col等)
- 设置触发条件(如row信号的变化)
- 编译并下载到FPGA
- 在SignalTap窗口中观察波形
当正确显示"电"字时,可以看到波形呈现出明显的"闪电"形状的跳变沿,这是验证系统工作正常的重要标志。
4.3 资源利用率优化
对于简单的汉字显示系统,FPGA资源利用率通常不是问题。但如果要显示大量汉字或实现更复杂的功能,可以考虑以下优化:
- 使用Block RAM存储字库,而非逻辑单元
- 对显示控制器进行流水线优化
- 采用更高效的编码方式存储点阵数据
- 在满足显示需求的前提下,降低时钟频率
在Cyclone IV EP4CE6上实测,基础汉字显示系统仅使用不到5%的逻辑单元和10%的存储器资源。
5. 常见问题与解决方案
5.1 显示模糊或不稳定
可能原因及解决方法:
- 时钟频率过高 → 降低时钟频率,或优化信号调理电路
- 信号幅度不匹配 → 调整示波器增益或添加分压电路
- 接地不良 → 检查所有接地连接,确保共地
5.2 汉字显示不完整
排查步骤:
- 检查字模数据是否正确加载到ROM
- 验证扫描控制器的计数逻辑是否正确
- 确认行扫描信号和列数据的同步关系
- 使用SignalTap观察内部信号波形
5.3 资源不足问题
当需要显示大量汉字时,可能会遇到存储资源不足的情况,解决方案:
- 使用压缩算法存储点阵数据
- 采用分时加载机制,动态更新字库
- 选择具有更大Block RAM资源的FPGA型号
6. 扩展应用与进阶玩法
这个基础框架可以扩展出许多有趣的应用:
- 动态显示:实现汉字滚动、闪烁等特效
- 多汉字显示:通过时分复用显示多个汉字
- 交互控制:添加按键或串口接口,实时改变显示内容
- 图形显示:不仅限于汉字,可以显示任意自定义图形
- 彩色显示:使用多通道示波器或矢量示波器实现彩色效果
我在实际项目中尝试过通过UART接收PC端发送的汉字编码,实时更新显示内容。配合Python脚本,可以实现任意文本的即时显示,效果相当惊艳。
