引言:本文介绍数码管显示译码基本工作原理及Verilog HDL驱动代码编写,进一步熟练掌握FPGA入门基础知识。
1.概述
数码管是显示屏其中一类,通过对其不同的管脚输入相对的电流,会使其发亮,从而显示出数字能够显示 时间、日期、温度等所有可用数字表示的参数。
图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
全部0条评论
快来发表一下你的评论吧 !