FPGA新手必看:EGo1开发板数码管动态显示实战(附完整代码)
FPGA实战EGo1开发板数码管动态显示全解析第一次拿到EGo1开发板时最让我兴奋的就是那排数码管了——它们像是FPGA世界的Hello World却又比简单的LED复杂得多。数码管动态显示是每个FPGA初学者必须掌握的技能它不仅涉及硬件控制逻辑还需要理解人眼视觉暂留的巧妙应用。本文将带你从零开始用Verilog实现一个稳定、高效的数码管动态显示系统避开那些我当初踩过的坑。1. 动态显示的核心原理数码管本质上是由多个LED组成的显示器件。以常见的七段数码管为例它包含7个条形LED和1个小数点LED。如果采用静态驱动方式每个数码管需要8个IO口4个数码管就需要32个IO口——这对资源有限的FPGA来说简直是灾难。动态显示技术又称扫描显示的聪明之处在于它利用了人眼的视觉暂留效应。当刷新频率足够高时通常50Hz以上人眼就无法察觉闪烁会认为所有数码管是同时点亮的。这种技术通过快速轮流点亮各个数码管大幅减少了所需的IO口数量。实现动态显示需要两组信号位选信号(an)决定当前点亮哪个数码管共阴极数码管对应位给低电平段选信号(seg)决定当前数码管显示什么数字或字符output reg [7:0] seg; // 段选信号(a~gdp) output reg [3:0] an; // 位选信号(4个数码管)典型的刷新频率设置在1kHz左右每个数码管点亮约250μs。频率太低会导致肉眼可见的闪烁太高则可能因LED亮度不足导致显示暗淡。2. EGo1开发板数码管电路分析EGo1开发板使用的是共阴极四位数码管这意味着段选信号(a~g,dp)需要高电平来点亮对应段位选信号(an0~an3)低电平时选中对应数码管硬件连接方式如下表所示FPGA引脚连接目标说明seg[0]a段数码管底部水平段seg[1]b段右上垂直段seg[2]c段右下垂直段seg[3]d段底部水平段seg[4]e段左下垂直段seg[5]f段左上垂直段seg[6]g段中间水平段seg[7]dp小数点an[0]DIG1最右侧数码管an[1]DIG2右起第二个数码管an[2]DIG3右起第三个数码管an[3]DIG4最左侧数码管注意不同厂商的开发板可能使用不同的引脚定义务必查阅EGo1的官方原理图确认连接方式。3. Verilog代码实现详解让我们构建一个完整的动态显示模块它可以显示4位十六进制数。以下是模块的输入输出定义module top_led_dynamic( output reg [7:0] seg, // 段选信号 output reg [3:0] an, // 位选信号 input wire clk, // 系统时钟(100MHz) input wire rst, // 复位信号 input wire [3:0] in3, in2, in1, in0 // 4位输入数字 );3.1 数码管编码表首先定义数字0-F对应的段码。由于是共阴极数码管需要给要点亮的段高电平// 共阴极数码管编码表(段亮为1) parameter _0 ~8hc0; // 0b11000000 parameter _1 ~8hf9; // 0b11111001 parameter _2 ~8ha4; // 0b10100100 ... parameter _f ~8h8e; // 0b10001110 parameter _err ~8hcf; // 错误显示E提示~运算符用于将常见的共阳极编码转换为共阴极编码。例如0的共阳极编码是0xc011000000取反后得到共阴极编码。3.2 扫描计数器与分频为了产生1kHz的扫描频率每个数码管250Hz我们需要对100MHz系统时钟进行分频parameter N 18; // 分频系数 reg [N-1:0] regN; // 分频计数器 always (posedge clk or posedge rst) begin if (rst) begin regN 0; end else begin regN regN 1; end end这里使用18位计数器的高2位来选择当前显示的数码管always (*) begin case (regN[N-1:N-2]) 2b00: begin an 4b0001; hex_in in0; end // 显示第1位 2b01: begin an 4b0010; hex_in in1; end // 显示第2位 2b10: begin an 4b0100; hex_in in2; end // 显示第3位 2b11: begin an 4b1000; hex_in in3; end // 显示第4位 default: begin an 4b1111; hex_in in3; end endcase end3.3 段码选择逻辑根据当前要显示的数字选择对应的段码always (*) begin case (hex_in) 4h0: seg _0; 4h1: seg _1; ... 4hf: seg _f; default: seg _err; // 非法输入显示E endcase end4. 常见问题与调试技巧4.1 显示闪烁或暗淡可能原因及解决方案刷新频率不合适频率太低50Hz肉眼可见闪烁频率太高5kHzLED点亮时间太短导致亮度不足最佳范围500Hz-2kHz驱动能力不足FPGA直接驱动可能电流不够可考虑使用三极管或专用驱动芯片如74HC2454.2 显示内容错乱检查清单确认数码管是共阴极还是共阳极检查段码编码表是否正确验证位选信号极性是否正确使用逻辑分析仪抓取seg和an信号4.3 资源优化技巧如果需要显示更多内容可以考虑时间复用交替显示不同内容如数字和字母亮度调节通过PWM控制显示亮度BCD编码减少输入数据宽度// 示例PWM调光 reg [3:0] pwm_cnt; always (posedge clk) pwm_cnt pwm_cnt 1; wire seg_drive (pwm_cnt brightness) ? 1b1 : 1b0; assign seg {8{seg_drive}} seg_value;5. 进阶应用显示动态数据基础版只能显示静态数字让我们扩展它来显示不断变化的数据// 添加数据更新逻辑 reg [15:0] display_data; always (posedge clk) begin if (data_update) begin display_data new_data; end end // 分配各位数据 wire [3:0] in0 display_data[3:0]; wire [3:0] in1 display_data[7:4]; wire [3:0] in2 display_data[11:8]; wire [3:0] in3 display_data[15:12];对于需要显示十进制数的场景可以添加BCD转换模块module bin2bcd( input [7:0] bin, output reg [3:0] hundreds, output reg [3:0] tens, output reg [3:0] ones ); integer i; always (*) begin hundreds 4d0; tens 4d0; ones 4d0; for (i7; i0; ii-1) begin if (hundreds 5) hundreds hundreds 3; if (tens 5) tens tens 3; if (ones 5) ones ones 3; hundreds hundreds 1; hundreds[0] tens[3]; tens tens 1; tens[0] ones[3]; ones ones 1; ones[0] bin[i]; end end endmodule调试这种动态显示系统时我最喜欢的方法是先让所有数码管显示相同内容确认基本功能正常后再测试位选逻辑。记得第一次实现时我花了两个小时才发现是因为段码表里的A编码错了——这种细节问题往往最难发现但解决后的成就感也最大。