VHDL实战:从静态到滚动的数码管学号显示系统设计

张开发
2026/4/12 11:53:12 15 分钟阅读

分享文章

VHDL实战:从静态到滚动的数码管学号显示系统设计
1. 数码管显示系统设计入门第一次接触FPGA开发板上的数码管时我盯着那排发光二极管看了半天完全不明白怎么让它显示我的学号。后来才知道这看似简单的显示背后藏着不少门道。数码管本质上是由多个LED组成的显示器件常见的有七段和八段两种。要让它们正确显示数字需要同时控制段选决定显示什么形状和位选决定哪个数码管亮。在VHDL中设计数码管驱动最基础的就是静态显示。比如要显示学号20230315的后六位030315我们需要做三件事首先用计数器产生扫描时钟然后通过数据选择器确定当前显示的数字最后用译码器将数字转换为对应的段码。这听起来简单但新手常会遇到段码反相、显示闪烁等问题。我当初就犯过把共阴和共阳数码管的段码搞混的低级错误结果显示的数字全是反的。2. 静态显示实现详解2.1 时钟分频模块设计开发板上的晶振通常是50MHz直接用来驱动数码管会快得看不清。我们需要先做时钟分频把频率降到人眼舒适的范围内。这里有个经验值扫描频率最好在100Hz-1kHz之间。频率太低会闪烁太高则可能造成视觉残留。process(clk) begin if rising_edge(clk) then if counter 50000 then -- 50MHz分频到1kHz clk_1k not clk_1k; counter 0; else counter counter 1; end if; end if; end process;2.2 数据选择与译码静态显示的核心是一个简单的查找表LUT。我们需要为每个数字预先定义好对应的段码。注意不同型号的数码管段序可能不同建议先用万用表测试确认。比如对于共阳数码管数字0的段码可能是11000000对应a~g段。with digit select seg 11000000 when 0000, -- 0 11111001 when 0001, -- 1 10100100 when 0010, -- 2 ... -- 其他数字 01111111 when others; -- 小数点3. 动态扫描进阶技巧3.1 扫描时序控制要实现六位数码管的稳定显示必须采用动态扫描技术。原理是利用人眼的视觉暂留特性快速轮流点亮每个数码管。关键是要确保每个数码管的点亮时间均匀我建议使用环形计数器来实现process(clk_1k) begin if rising_edge(clk_1k) then case scan_cnt is when 0 sel 011111; digit num0; when 1 sel 101111; digit num1; ... when 5 sel 111110; digit num5; when others null; end case; scan_cnt (scan_cnt 1) mod 6; end if; end process;3.2 亮度均衡处理动态扫描时常见的问题是不同位数码管亮度不一致。解决方法有两个一是优化扫描时序确保每个管子的导通时间相同二是在硬件上增加限流电阻。我在实验室发现当扫描频率超过200Hz后亮度差异就基本看不出来了。4. 滚动显示完整实现4.1 数据缓冲设计要实现学号的平滑滚动需要建立一个数据缓冲区。我的做法是用一个24位的移位寄存器假设学号是8位存储完整学号然后每500ms左移一次process(clk_1hz) begin if rising_edge(clk_1hz) then buffer_reg buffer_reg(19 downto 0) buffer_reg(23 downto 20); end if; end process;4.2 平滑滚动算法滚动效果的关键在于控制移动速度。太快会看不清数字太慢又显得卡顿。经过多次实验我发现每秒移动1-2个数字的节奏最合适。可以通过调整移位时钟的频率来控制速度-- 1Hz时钟生成 process(clk) begin if rising_edge(clk) then if cnt_1hz 25000000 then -- 50MHz分频到1Hz clk_1hz not clk_1hz; cnt_1hz 0; else cnt_1hz cnt_1hz 1; end if; end if; end process;5. 调试经验与常见问题第一次烧录程序后我的数码管显示乱码。排查发现是段选信号顺序接反了。建议在引脚约束文件里做好标注# 数码管段选信号 set_property PACKAGE_PIN J1 [get_ports {seg[0]}] set_property PACKAGE_PIN L1 [get_ports {seg[1]}] ... # 位选信号 set_property PACKAGE_PIN H2 [get_ports {sel[0]}] set_property PACKAGE_PIN K2 [get_ports {sel[1]}]另一个常见问题是显示闪烁。这通常是扫描频率太低导致的可以尝试以下解决方法检查分频计数器设置是否正确确保没有在组合逻辑中产生锁存器用示波器观察实际扫描频率6. 性能优化建议完成基础功能后我尝试了几种优化方案。最有效的是采用流水线设计将扫描显示和数据更新分开处理。这样即使在进行复杂计算时显示也不会出现卡顿-- 显示流水线 process(clk_1k) begin if rising_edge(clk_1k) then -- 第一阶段选择当前显示的数字 digit num_buf(scan_cnt*43 downto scan_cnt*4); -- 第二阶段数字译码 seg seg_lut(to_integer(unsigned(digit))); -- 第三阶段位选更新 sel not (1 scan_cnt); -- 位选信号低有效 end if; end process;对于资源紧张的CPLD还可以优化查找表实现。比如用算术运算代替完整的LUT虽然代码复杂些但能节省不少逻辑单元。

更多文章