引言:本文介绍数码管显示译码基本工作原理及Verilog HDL驱动代码编写,进一步熟练掌握FPGA入门基础知识。
1.概述
数码管是显示屏其中一类,通过对其不同的管脚输入相对的电流,会使其发亮,从而显示出数字能够显示 时间、日期、温度等所有可用数字表示的参数。
由于它的价格便宜,使用简单,在电器特别是家电领域应用极为广泛,空调、热水器、冰箱等等。绝大多数热水器用的都是数码管,其他家电也用液晶屏与荧光屏。
2. 硬件原理
如图2所示,数码管按段数分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管单元(多一个小数点显示)。按能显示多少个“8”可分为1 位、2 位、4 位等等数码管。
如图2所示,按发光二极管单元连接方式分为共阳极数码管和共阴极数码管。
图2:数码管共阴极(左侧)和共阳极(右侧)接法
共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管,共阴数码管在应用时应将公共极 COM接到地线 GND 上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮,当某一字段的阳极为低电平时,相应字段就不亮。共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管,共阳数码管在应用时应将公共极COM 接到+3.3V(高电平),当某一字段发光二极管的阴极为低电平时,相应字段就点亮,当某一字段的阴极为高电平时,相应字段就不亮。图3:4位数码管共阳极接法
如图3所示,4位数码管的每段全部连接在一起,如果想动态控制每位数码管,就不能将四位数码管的COM端连接在一起,必须分别控制每位数码管的COM端,即增加位选择控制信号。8位数码管共阳极连接原理图如图4所示。
图4:8位数码管原理图
如图4所示,8位数码管通过Sel[7..0]8个数据IO实现位选择,通过Disp[7..0]8个数据IO实现段选择。由于FPGA IO口驱动电流能力弱,增加三极管可提高驱动能力。如图4所示,基极为低电平,三极管导通,数码管被选中,被选中的数码管相应的段发光显示。由于4位数码管的的数据端(ABCDEFG)是公用的,而每一位的数码管的公共阳极(COM)是单独,若某一位数码管显示字符,则这一位的公共阳极就要连接到低电平。
3. 软件实现
数码管显示有静态显示和动态显示两种方式。
在静态显示中,只考虑段选信号。在不同的时刻,各个位选信号保持不变,并根据真值表,选择要显示的数字或者字母。由于多位数码管段信号是连在一起的,所以这种情况下每位数码管显示的内容是想通过的。要想每位数码管显示不同内容,只能采用动态显示方式。
在动态显示中,需要将位选信号考虑进来。在不同的时刻,各个位的位选信号随时改变,并根据真值表,选择显示不同的数字或者字母。
3.1 数码管静态显示
根据图4硬件原理图,数码管位选择信号为低电平“0”时,同时数码管段为低电平“0”时,点亮数码管内部LED发光二极管。
数码管静态显示代码如下所示。
/*************************************************
数码管静态显示按键计数值。
*************************************************/
//位选信号
assign segma_sel_o = 8'h00; //使能所有数码管位
//段选信号,关闭DP点显示
always @(posedge sys_clk_i or negedge rst_n_i) begin
if(!rst_n_i) begin
segma_disp_o <= 8'b0000001_1;
end
else begin
case(cnt) //按键计数值
4'd0: segma_disp_o <= 8'b0000001_1; //"0"
4'd1: segma_disp_o <= 8'b1001111_1; //"1"
4'd2: segma_disp_o <= 8'b0010010_1; //"2"
4'd3: segma_disp_o <= 8'b0000110_1; //"3"
4'd4: segma_disp_o <= 8'b1001100_1; //"4"
4'd5: segma_disp_o <= 8'b0100100_1; //"5"
4'd6: segma_disp_o <= 8'b0100000_1; //"6"
4'd7: segma_disp_o <= 8'b0001111_1; //"7"
4'd8: segma_disp_o <= 8'b0000000_1; //"8"
4'd9: segma_disp_o <= 8'b0000100_1; //"9"
4'd10: segma_disp_o <= 8'b0001000_1; //"A"
4'd11: segma_disp_o <= 8'b1100000_1; //"B"
4'd12: segma_disp_o <= 8'b0110001_1; //"C"
4'd13: segma_disp_o <= 8'b1000010_1; //"D"
4'd14: segma_disp_o <= 8'b0110000_1; //"E"
4'd15: segma_disp_o <= 8'b0111000_1; //"F"
endcase
end
end
测试结果如下:
3.2 数码管动态显示
数码管动态动态扫描显示,实际上是利用了两个现象:人眼的视觉暂留特性和数码管的余晖效应。人眼在观察景物时,光信号传入到大脑神经需要经过一段时间,光的作用结束之后我们的视觉影像并不会立刻的消失,这种残留的视觉被称为后像,这种现象就被称为视觉暂留;数码管的余晖效应是什么意思呢?当我们停止向发光二极管供电时,发光二极管的亮度仍能够维持一段时间。我们的动态扫描利用这两个特性就实现了数码管的动态显示。
(1)数码管位扫描控制代码:编写计数器cnt_scanf,周期性产生数码管选择信号seg_wei_num,根据seg_wei_num解码对应的需要使能数码管位segma_wei_o。
// 计数器,控制数码管位
reg [23:0] cnt_scanf;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i) begin
cnt_scanf <= 24'd0;
end
else begin
if(cnt_scanf == timer_scanf_i)
cnt_scanf <= 24'd0;
else
cnt_scanf <= cnt_scanf + 24'd1;
end
end
//数码管扫描序号
reg [2:0] segma_wei_num;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i) begin
segma_wei_num <= 3'd0;
end
else begin
if(cnt_scanf == timer_scanf_i)
segma_wei_num <= segma_wei_num + 3'd1;
else
segma_wei_num <= segma_wei_num;
end
end
//解码选中的数码管
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i) begin
segma_wei_o <= 8'b1111_1111;
end
else begin
case(segma_wei_num)
3'd0: segma_wei_o <= 8'b1110_1111; //选中第1个数码管
3'd1: segma_wei_o <= 8'b1101_1111; //选中第2个数码管
3'd2: segma_wei_o <= 8'b1011_1111; //选中第3个数码管
3'd3: segma_wei_o <= 8'b0111_1111; //选中第4个数码管
3'd4: segma_wei_o <= 8'b1111_1110; //选中第5个数码管
3'd5: segma_wei_o <= 8'b1111_1101; //选中第6个数码管
3'd6: segma_wei_o <= 8'b1111_1011; //选中第7个数码管
3'd7: segma_wei_o <= 8'b1111_0111; //选中第8个数码管
default: segma_wei_o <= 8'b1111_1111;
endcase
end
end
(2)数码管段显示控制代码:根据当前使能的数码管位segma_wei_num,获取当前数码管位需要显示的数据信息led_duan,然后根据led_duan数据解码映射到需要显示的每段发光二极管,数码管DP段单独控制。
//获取当前数码管位DP段
always @(*) begin
case(segma_wei_num)
3'd0: segma_duan_o[0] <= i_led0_data[4]; // 第1个数码管DP段数据
3'd1: segma_duan_o[0] <= i_led1_data[4]; // 第2个数码管DP段数据
3'd2: segma_duan_o[0] <= i_led2_data[4]; // 第3个数码管DP段数据
3'd3: segma_duan_o[0] <= i_led3_data[4]; // 第4个数码管DP段数据
3'd4: segma_duan_o[0] <= i_led4_data[4]; // 第5个数码管DP段数据
3'd5: segma_duan_o[0] <= i_led5_data[4]; // 第6个数码管DP段数据
3'd6: segma_duan_o[0] <= i_led6_data[4]; // 第7个数码管DP段数据
3'd7: segma_duan_o[0] <= i_led7_data[4]; // 第8个数码管DP段数据
endcase
end
//获取当前数码管位其他段数据
reg [3:0] led_duan;
always @(*) begin
case(segma_wei_num)
3'd0: led_duan <= i_led0_data[3:0]; // 第1个数码管其他段数据
3'd1: led_duan <= i_led1_data[3:0]; // 第2个数码管其他段数据
3'd2: led_duan <= i_led2_data[3:0]; // 第3个数码管其他段数据
3'd3: led_duan <= i_led3_data[3:0]; // 第4个数码管其他段数据
3'd4: led_duan <= i_led4_data[3:0]; // 第5个数码管其他段数据
3'd5: led_duan <= i_led5_data[3:0]; // 第6个数码管其他段数据
3'd6: led_duan <= i_led6_data[3:0]; // 第7个数码管其他段数据
3'd7: led_duan <= i_led7_data[3:0]; // 第8个数码管其他段数据
endcase
end
//解码数码管段数据
always @(*) begin
case(led_duan)
4'd0: segma_duan_o[7:1] <= 7'b0000001; //"0"
4'd1: segma_duan_o[7:1] <= 7'b1001111; //"1"
4'd2: segma_duan_o[7:1] <= 7'b0010010; //"2"
4'd3: segma_duan_o[7:1] <= 7'b0000110; //"3"
4'd4: segma_duan_o[7:1] <= 7'b1001100; //"4"
4'd5: segma_duan_o[7:1] <= 7'b0100100; //"5"
4'd6: segma_duan_o[7:1] <= 7'b0100000; //"6"
4'd7: segma_duan_o[7:1] <= 7'b0001111; //"7"
4'd8: segma_duan_o[7:1] <= 7'b0000000; //"8"
4'd9: segma_duan_o[7:1] <= 7'b0000100; //"9"
4'd10: segma_duan_o[7:1] <= 7'b0001000; //"A"
4'd11: segma_duan_o[7:1] <= 7'b1100000; //"B"
4'd12: segma_duan_o[7:1] <= 7'b0110001; //"C"
4'd13: segma_duan_o[7:1] <= 7'b1000010; //"D"
4'd14: segma_duan_o[7:1] <= 7'b0110000; //"E"
4'd15: segma_duan_o[7:1] <= 7'b0111000; //"F"
endcase
end
测试结果如下:
欢迎关注FPGA技术实战公众号,喜欢就多多转发吧!