最近在实验室折腾FPGA点阵屏显示,发现用Verilog实现动态显示效果比传统单片机方案灵活多了。这次基于16×16 LED点阵屏开发了一套完整的汉字显示系统,支持多汉字滚动、暂停/启动、左右移动和速度调节等功能。系统在Intel Cyclone IV和Xilinx Artix-7开发板上都通过了实测验证,核心代码采用纯Verilog实现,移植性相当不错。
这个项目的核心价值在于:
特别适合有一定FPGA基础的开发者学习,或者作为电子设计竞赛的参考项目。下面我会从设计思路到具体实现,把整个开发过程中的关键技术和踩坑经验都详细分享出来。
系统采用经典的三级流水架构:
code复制[字模ROM] -> [显示缓冲区] -> [扫描控制]
↑ ↑ ↑
[字符选择] [位移控制] [时钟分频]
时钟域划分:
原始方案使用case语句硬编码,虽然直观但扩展性差。改进后的ROM存储方案:
verilog复制parameter CHAR_NUM = 8; // 支持8个汉字
parameter ROM_DEPTH = CHAR_NUM * 32; // 每个汉字32字节
reg [15:0] char_rom [0:ROM_DEPTH-1];
initial begin
// 使用$readmemh从文件初始化ROM
$readmemh("font_data.hex", char_rom);
end
字模提取技巧:
注意:Quartus综合时会自动将数组推断为ROM资源,无需手动例化ROM IP
为解决闪烁问题,采用双缓冲技术:
verilog复制reg [15:0] display_buf[0:15]; // 前台缓冲
reg [15:0] prepare_buf[0:15]; // 后台缓冲
always @(posedge clk_1khz) begin
if (buf_switch) begin
// 整帧切换
for (int i=0; i<16; i=i+1)
display_buf[i] <= prepare_buf[i];
end
// 行扫描逻辑...
end
缓冲切换时机:
关键时序参数:
verilog复制reg [3:0] row_cnt;
reg [15:0] row_sel;
always @(posedge clk_1khz) begin
// 行计数器
row_cnt <= (row_cnt == 15) ? 0 : row_cnt + 1;
// 消隐控制
if (row_cnt == 15)
row_sel <= 16'h0000; // 消隐
else
row_sel <= (1 << row_cnt);
// 数据锁存
row_data <= display_buf[row_cnt];
end
实测发现:消隐时间不足会导致鬼影,建议用示波器实测行选信号边沿
位移控制核心逻辑:
verilog复制reg [4:0] shift_cnt; // 0-31位移量
wire [5:0] virt_col = col_idx + shift_cnt; // 虚拟列坐标
// 字符索引计算
assign char_idx = virt_col[5:4]; // 每字符16列(4bit)
assign char_row = virt_col[3:0]; // 字符内行号
// 边界处理
always @(posedge shift_clk) begin
if (dir) // 右移
shift_cnt <= (shift_cnt == 31) ? 0 : shift_cnt + 1;
else // 左移
shift_cnt <= (shift_cnt == 0) ? 31 : shift_cnt - 1;
end
跨字符处理技巧:
可编程时钟分频器设计:
verilog复制reg [23:0] speed_reg = 24'd50_000; // 默认速度
reg [23:0] div_cnt;
always @(posedge clk_50m) begin
if (div_cnt >= speed_reg) begin
shift_clk <= ~shift_clk;
div_cnt <= 0;
end else begin
div_cnt <= div_cnt + 1;
end
end
速度档位推荐值:
| 档位 | 分频系数 | 近似速度 |
|---|---|---|
| 慢速 | 500,000 | 0.5Hz |
| 中速 | 50,000 | 5Hz |
| 快速 | 5,000 | 50Hz |
注意:实际速度受主时钟频率影响,需根据开发板调整基准值
问题现象:切换行时出现短暂错误显示
解决方案:
verilog复制// 行切换时插入死区时间
if (row_cnt == 15)
row_sel <= 16'h0000; // 全消隐
实测对比:
| 方案 | 鬼影程度 | 硬件复杂度 |
|---|---|---|
| 无处理 | 严重 | 低 |
| 纯软件消隐 | 轻微 | 低 |
| 硬件+软件 | 无 | 中 |
数据通路存在的跨时钟域:
同步器实现示例:
verilog复制// 两级触发器同步链
reg [1:0] speed_sync;
always @(posedge clk_50m) begin
speed_sync <= {speed_sync[0], speed_new};
if (speed_sync[1] != speed_sync[0])
speed_reg <= speed_new;
end
重要:所有异步控制信号都必须同步,否则会导致亚稳态
机械按键抖动特性:
状态机消抖算法:
verilog复制localparam IDLE = 2'b00;
localparam DETECT = 2'b01;
localparam CONFIRM = 2'b10;
always @(posedge clk_1khz) begin
case(state)
IDLE: if (key_in) state <= DETECT;
DETECT: begin
cnt <= cnt + 1;
if (cnt > 15) state <= CONFIRM;
end
CONFIRM: if (!key_in) state <= IDLE;
endcase
end
滤波参数建议:
引脚分配技巧:
tcl复制# 在.qsf文件中添加约束
set_location_assignment PIN_C14 -to row_sel[0]
set_instance_assignment -name IO_STANDARD "3.3-V LVTTL" -to row_sel[*]
时钟配置:
SignalTap II调试:
差异点处理:
时钟资源:
verilog复制// 替换Altera PLL为Xilinx MMCM
clk_wiz_0 clk_gen (
.clk_out1(clk_50m),
.clk_out2(clk_1khz),
.reset(reset),
.locked(pll_locked),
.clk_in1(sys_clk)
);
ILA调试配置:
约束文件示例:
xdc复制create_clock -period 20.000 [get_ports sys_clk]
set_property PACKAGE_PIN R4 [get_ports {row_sel[0]}]
set_property IOSTANDARD LVCMOS33 [get_ports {row_sel[*]}]
Cyclone IV EP4CE10 vs Artix-7 XC7A35T:
| 资源类型 | Altera用量 | Xilinx用量 |
|---|---|---|
| 逻辑单元 | 320 LUTs | 285 LUTs |
| 寄存器 | 128 FF | 142 FF |
| 块存储器 | 1 M9K | 1 BRAM |
| 时钟资源 | 1 PLL | 1 MMCM |
移植经验:
测试用例设计:
静态显示测试
动态功能测试
test复制1. 上电默认显示"测试"
2. 按左键:文字左移
3. 按右键:文字右移
4. 速度+/-键调节滚动速度
5. 暂停键冻结显示
边界测试:
常见问题:
解决方案:
硬件措施:
软件措施:
verilog复制// 分散刷新策略
always @(posedge clk_1khz) begin
if (row_cnt % 2 == 0)
row_data <= display_buf[row_cnt];
end
现象:高速滚动时亮度不均
优化方案:
灰度调制:
verilog复制// PWM调光
reg [3:0] pwm_cnt;
always @(posedge clk_1m) pwm_cnt <= pwm_cnt + 1;
assign led_drive = (row_data & (pwm_cnt < brightness));
非线性校正:
verilog复制// Gamma校正表
wire [3:0] adj_brightness = gamma_lut[raw_brightness];
实测效果对比:
| 方案 | 均匀性 | 硬件成本 |
|---|---|---|
| 固定占空比 | 差 | 低 |
| PWM调光 | 良 | 中 |
| Gamma校正 | 优 | 高 |
扩展设计:
字库存储优化:
Unicode编码处理:
verilog复制// 编码转换示例
wire [15:0] gb_code = {8'hB2, 8'hE2}; // "测"的GB2312编码
assign rom_addr = (gb_code - 16'hA1A1) * 32;
字库压缩技术:
已实现特效:
淡入淡出:
verilog复制// 亮度渐变
always @(posedge clk_1hz) begin
if (fade_dir)
brightness <= brightness + 1;
else
brightness <= brightness - 1;
end
弹跳效果:
verilog复制// 垂直位置计算
assign y_offset = amplitude * sin(phase_cnt);
马赛克过渡:
远程控制方案:
UART接口:
code复制[头] [命令] [参数] [校验]
0x55 0x01 0x05 0x5B
WiFi模块对接:
典型应用场景: