设计规划
8个数码管显示从十进制0开始计数,每0.1s加1,一直加到十进制数9999_9999,然后回到0重新计数。
理论基础
上一节介绍了静态显示,每个数码管显示的数字是一样的,但是实际生活中远远不能满足需求。因此我们要学习显示不同字符的数码管驱动方式-动态驱动。静态驱动的原理是位选信号为1111_1111,即8个数码管同时选中,再用段选信号让数码管显示理想数值,那么8个数码管的字符就一样。那么如果我们每次只选中一个数码管,再利用段选信号让它显示理想数值,就可以显示不同数值了。由于视觉暂留以及数码管的余晖效应 即使数码管不是同时点亮,我们在视觉效果上看到的也是同时点亮。(视觉暂留:视觉影像不会立即消失,动画就是利用这个原理,当一秒内帧数达到24就会感觉比较流畅。余晖效应:停止供电发光二极管亮度会维持一段时间。)实验证明,扫描间隔为1ms,即一秒点亮1000次,不会有闪烁感。那么我们只需要第1ms点亮第一个数码管,第2ms点亮第二个数码管…
bcd码:2-10进制码,将1位十进制数和4位二进制数对应的码。8421码比较常见,第0位权重为1,第1位权重为2,第三位权重为4,第4位权重为8,0对应0000,1对应0001,...,9对应1001。那么例如一个二进制数10010,对应十进制是18,对应bcd码是0001_1000。
编写代码
通过系统框图可以看出,分为6个模块:数据生成模块,二进制转bcd模块,数码管动态显示驱动模块,74hc595模块,数码管动态显示模块,顶层模块。
1、数据生成模块data_gen
应该具有输入:时钟和复位,输出:数据data和使能seg_en。中间信号有100ms计数器cnt_100ms,标志位cnt_flag。
module data_gen#(parameter cnt_max = 23'd4999_999, //100ms计数值parameter data_max= 27'd9999_9999 //显示的最大值)(input wire sys_clk , //系统时钟,频率50mhzinput wire sys_rst_n , //复位信号,低电平有效output reg [26:0] data , //数码管要显示的值output reg seg_en //数码管使能信号,高电平有效);//reg definereg [22:0] cnt_100ms ; // 100ms计数器reg cnt_flag ; //100ms标志信号 //cnt_100ms:用50mhz时钟从0到4999_999计数即为100ms always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_100ms <= 23'd0; else if(cnt_100ms == cnt_max) cnt_100ms <= 23'd0; else cnt_100ms <= cnt_100ms + 1'b1; //cnt_flag:每100ms产生一个标志信号 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_flag <= 1'b0; else if(cnt_100ms == cnt_max - 1'b1) cnt_flag <= 1'b1; else cnt_flag <= 1'b0; //数码管显示的数据:0-9999_9999 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) data <= 27'd0; else if((data == data_max) && (cnt_flag == 1'b1)) data <= 27'd0; else if(cnt_flag == 1'b1) data <= data + 1'b1; else data <= data; //数码管使能信号给高即可 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) seg_en <= 1'b0; else seg_en <= 1'b1; endmodule参数定义:首先确定计数和显示的最大值,计数100ms,用50mhz的时钟需要从0记到4999_999,显示的最大值是999_999,前者是23位,后者是27位。
cnt_100ms:复位有效和记满时归零,其他情况+1
cnt_flag:复位有效时归0,计满时拉高,其他情况都是保持低电平
data:复位有效时归0,数据达到最大值且标志位拉高时+1(数据要在999_999保持0.1ms,如果计满就+1就只能维持一个时钟周期,这就是flag的意义),其他情况保持
seg_en:复位有效时归0,其他情况都是高电平有效。
2、bcd模块bcd_8421
输入是时钟、复位和数据data,data是2进制数,要先转换成10进制,再将10进制的每一位转换成bcd码,才能使每一个数码管显示对应的字符。我们看二进制码如何变为bcd码。输出是8个数码管对应8位,每位转换bcd码是4位。输出是:个位,十位,百位,千位,万位,十万位,百万位,千万位。中间信号是移位判断计数器cnt_shift,移位判断数据寄存器data_shift,移位判断标志信号shift_flag。
以十进制数234为例,二进制是1110_1010,bcd码应该是0010_0011_1000,那么先进行补0操作,输入的二进制码不足12位,在高位补0。再将二进制码的最高位作为bcd的最低位进行移位,并判断每一个bcd码其对应的十进制数是否大于4,如果大于4就对bcd码做加3操作,若小于等于4就让其值保持不变。然后继续移位,每次移位后都要进行判断。
module bcd_8421(input wire sys_clk , //系统时钟,频率50mhzinput wire sys_rst_n , //复位信号,低电平有效input wire [26:0] data , //输入需要转换的数据output reg [3:0] unit , //个位bcd码output reg [3:0] ten , //十位bcd码output reg [3:0] hun , //百位bcd码output reg [3:0] tho , //千位bcd码output reg [3:0] t_tho , //万位bcd码output reg [3:0] h_hun , //十万位bcd码output reg [3:0] t_hun , //百万位bcd码output reg [3:0] h_tho //千万位bcd码 );//reg definereg [4:0] cnt_shift ; //移位判断计数器 reg [58:0] data_shift ; //移位判断数据寄存器 reg shift_flag ; //移位判断标志信号 //cnt_shift:从0到28循环计数 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) cnt_shift <= 5'd0; else if((cnt_shift == 5'd28) && (shift_flag == 1'b1)) cnt_shift <= 5'd0; else if(shift_flag == 1'b1) cnt_shift <= cnt_shift + 1'b1; else cnt_shift <= cnt_shift; //data_shift:计数器为0时赋初值,计数器为1~27时进行移位判断操作 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) data_shift <= 59'b0; else if(cnt_shift == 5'd0) data_shift <= {32'b0,data}; else if((cnt_shift <= 27) && (shift_flag == 1'b0)) begin data_shift[30:27] 4) ? (data_shift[30:27] + 2'd3) : (data_shift[30:27]); data_shift[34:31] 4) ? (data_shift[34:31] + 2'd3) : (data_shift[34:31]); data_shift[38:35] 4) ? (data_shift[38:35] + 2'd3) : (data_shift[38:35]); data_shift[42:39] 4) ? (data_shift[42:39] + 2'd3) : (data_shift[42:39]); data_shift[46:43] 4) ? (data_shift[46:43] + 2'd3) : (data_shift[46:43]); data_shift[50:47] 4) ? (data_shift[50:47] + 2'd3) : (data_shift[50:47]); data_shift[54:51] 4) ? (data_shift[54:51] + 2'd3) : (data_shift[54:51]); data_shift[58:55] 4) ? (data_shift[58:55] + 2'd3) : (data_shift[58:55]); end else if((cnt_shift <= 27) && (shift_flag == 1'b1)) data_shift <= data_shift < < 1; else data_shift <= data_shift; //shift_flag:移位判断标志信号,用于控制移位判断的先后顺序 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) shift_flag <= 1'b0; else shift_flag <= ~shift_flag; //当计数器等于28时,移位判断操作完成,对各个位数的bcd码进行赋值 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) begin unit <= 4'b0; ten <= 4'b0; hun <= 4'b0; tho <= 4'b0; t_tho <= 4'b0; h_hun <= 4'b0; t_hun <= 4'b0; h_tho <= 4'b0; end else if(cnt_shift == 5'd28)begin unit <= data_shift[30:27]; ten <= data_shift[34:31]; hun <= data_shift[38:35]; tho <= data_shift[42:39]; t_tho <= data_shift[46:43]; h_hun <= data_shift[50:47]; t_hun <= data_shift[54:51]; h_tho <= data_shift[58:55]; end endmodulecnt_shift:移位判断计数器,data有27位,一共要判断27次,cnt_shift位数为5位,从0开始计数到28,其中1-27是移位判断操作。复位有效时归0,计满且flag为高点平(移位结束)时归0,flag为高点平(每次移位结束)都+1,其他情况保持不变
data_shift:移位判断数据寄存器,移位数据放在高位,data数据放在低位,输出数据一共要32位,data有27位,一共需要59位。计数器cnt_shift为0时赋初值,计数器为1-27时移位判断。复位有效时归0,计数器为0时赋初值,高32位为0,后27位是data初值。当计数器不到27且flag为低电平(还没开始判断时),就要开始判断移位了,具体是:判断data_shift[30:27]是否大于4(0-26是data,27-30是移位数据的低4位),大于4则将[30:27]+3,不满足就保持原值。...判断data_shift[58:55]是否大于4,大于4则+3,不满足保持原值,判断结束后,data_shift左移一位,其他情况维持不变
shift_flag:随着时钟周期高低变化的移位判断标志信号,复位有效时归0,其他情况取反,即一个时钟周期内为低电平表示判断,下一个时钟周期内为高点平表示移位
输出:复位有效时,个位-千万位的输出都为0。由于data有27位,要判断移位27次,计数器计数到27时结束最后一次判断移位,计数到28时进行输出的赋值。个位的4位bcd码是移位数据的低四位data_shift[31:27],千万位的4位bcd码是移位数据的高4位data_shift[58:55]
3、数码管动态显示驱动模块seg_dynamic
我们由上一节知道数码管是由位选和段选信号进行选择和控制的,那么这个模块里要将输入的data转换成对应的段选位选信号。因此,输入为时钟、复位、data、seg_en,输出为sel,seg。因为第1ms第一个数码管亮,第2ms第二个数码管亮,这里需要用1ms时钟去控制位选信号即哪个数码管亮。每100ms数码管显示的十进制数要+1,因此时钟还要控制段选信号即选中的数码管显示什么值。中间信号有实例化后的输出unit,...,h_tho,data_reg,cnt_1ms,flag_1ms,cnt_sel,sel_reg,data_disp。data_reg是数码管带显示内容寄存器,假设输入要显示的十进制数是9999_9999,那么不考虑符号时8个数码管显示9999_9999。cnt_1ms时1ms计数器。flag_1ms是标志信号,计满时拉高,控制位选。cnt_sel是位选数码管计数器,8个数码管每1ms换一个亮,也就是每个数码管8ms亮一次。cnt_sel从0-7计数,相当于给数码管编码。sel_reg是数码管位选信号寄存器,选中点亮的数码管后给他显示的值。data_disp:当前点亮数码管显示的值。
module seg_dynamic(input wire sys_clk , //系统时钟,频率50mhzinput wire sys_rst_n , //复位信号,低有效input wire [26:0] data , //数码管要显示的值input wire seg_en , //数码管使能信号,高电平有效output reg [7:0] sel , //数码管位选信号output reg [7:0] seg //数码管段选信号);//parameter defineparameter cnt_max = 16'd49_999; //数码管刷新时间计数最大值//wire definewire [3:0] unit ; //个位数wire [3:0] ten ; //十位数wire [3:0] hun ; //百位数wire [3:0] tho ; //千位数wire [3:0] t_tho ; //万位数wire [3:0] h_hun ; //十万位数wire [3:0] t_hun ;wire [3:0] h_tho ;//reg definereg [31:0] data_reg ; //待显示数据寄存器reg [15:0] cnt_1ms ; //1ms计数器reg flag_1ms ; //1ms标志信号reg [2:0] cnt_sel ; //数码管位选计数器reg [7:0] sel_reg ; //位选信号reg [3:0] data_disp; //当前数码管显示的数据//data_reg:控制数码管显示数据always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)data_reg <= 32'b0;//不考虑小数点,若显示的数据是8位都非0,则8个数码管全显示else if(h_tho)data_reg <= {h_tho,t_hun,h_hun,t_tho,tho,hun,ten,unit};//若显示的数据7位非0,比如1234567不是显示01234567,则7个数码管亮else if(t_hun)data_reg <= {4'd10,t_hun,h_hun,t_tho,tho,hun,ten,unit};//4'd10我们定义为不显示//若显示的数据6位非0,则6个数码管亮else if(h_hun)data_reg <= {4'd10,4'd10,h_hun,t_tho,tho,hun,ten,unit};//若显示的数据5位非0,则5个数码管亮else if(t_tho)data_reg <= {4'd10,4'd10,4'd10,t_tho,tho,hun,ten,unit};//若显示的数据4位非0,则4个数码管亮else if(tho)data_reg <= {4'd10,4'd10,4'd10,4'd10,tho,hun,ten,unit};//若显示的数据3位非0,则3个数码管亮else if(hun)data_reg <= {4'd10,4'd10,4'd10,4'd10,4'd10,hun,ten,unit};//若显示的数据2位非0,则2个数码管亮else if(ten)data_reg <= {4'd10,4'd10,4'd10,4'd10,4'd10,4'd10,ten,unit};//若上面都不满足都只显示一位数码管elsedata_reg <= {4'd10,4'd10,4'd10,4'd10,4'd10,4'd10,4'd10,unit};//cnt_1ms:1ms循环计数always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_1ms <= 16'd0;else if(cnt_1ms == cnt_max)cnt_1ms <= 16'd0;elsecnt_1ms <= cnt_1ms + 1'b1;//flag_1ms:1ms标志信号always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)flag_1ms <= 1'b0;else if(cnt_1ms == cnt_max - 1'b1)flag_1ms <= 1'b1;elseflag_1ms <= 1'b0;//cnt_sel:从0到7循环数,用于选择当前显示的数码管always@(posedge sys_clk or negedge sys_rst_n)if(sys_rst_n == 1'b0)cnt_sel <= 3'd0;else if((cnt_sel == 3'd7) && (flag_1ms == 1'b1)) cnt_sel <= 3'd0; else if(flag_1ms == 1'b1) cnt_sel <= cnt_sel + 1'b1; else cnt_sel <= cnt_sel;//数码管位选信号寄存器 always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) sel_reg <= 8'b0000_0000; else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1)) sel_reg <= 8'b0000_0001; else if(flag_1ms == 1'b1) sel_reg <= sel_reg < < 1; else sel_reg <= sel_reg; //控制数码管的位选信号,使8个数码管轮流显示always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) data_disp <= 4'b0; else if((seg_en == 1'b1) && (flag_1ms == 1'b1)) case(cnt_sel) 3'd0: data_disp <= data_reg[3:0] ; //给第1个数码管赋个位值 3'd1: data_disp <= data_reg[7:4] ; //给第2个数码管赋十位值 3'd2: data_disp <= data_reg[11:8] ; //给第3个数码管赋百位值 3'd3: data_disp <= data_reg[15:12]; //给第4个数码管赋千位值 3'd4: data_disp <= data_reg[19:16]; //给第5个数码管赋万位值 3'd5: data_disp <= data_reg[23:20]; //给第6个数码管赋十万位值 3'd6: data_disp <= data_reg[27:24]; //给第7个数码管赋百万位值 3'd7: data_disp <= data_reg[31:28]; //给第8个数码管赋千万位值 default:data_disp <= 4'b0; endcase elsedata_disp <= data_disp;//控制数码管段选信号,显示数字always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) seg <= 8'b1111_1111; else case(data_disp) 4'd0 : seg <= {1'b1,7'b100_0000}; //显示数字0 4'd1 : seg <= {1'b1,7'b111_1001}; //显示数字1 4'd2 : seg <= {1'b1,7'b010_0100}; //显示数字2 4'd3 : seg <= {1'b1,7'b011_0000}; //显示数字3 4'd4 : seg <= {1'b1,7'b001_1001}; //显示数字4 4'd5 : seg <= {1'b1,7'b001_0010}; //显示数字5 4'd6 : seg <= {1'b1,7'b000_0010}; //显示数字6 4'd7 : seg <= {1'b1,7'b111_1000}; //显示数字7 4'd8 : seg <= {1'b1,7'b000_0000}; //显示数字8 4'd9 : seg <= {1'b1,7'b001_0000}; //显示数字9 4'd10 : seg <= 8'b1111_1111 ; //不显示任何字符 default:seg <= 8'b1100_0000;endcase//sel:数码管位选信号赋值always@(posedge sys_clk or negedge sys_rst_n) if(sys_rst_n == 1'b0) sel <= 8'b0000_0000; elsesel <= sel_reg;bcd_8421 bcd_8421_inst ( .sys_clk (sys_clk ), //系统时钟,频率50mhz .sys_rst_n (sys_rst_n), //复位信号,低电平有效 .data (data ), //输入需要转换的数据 .unit (unit ), //个位bcd码 .ten (ten ), //十位bcd码 .hun (hun ), //百位bcd码 .tho (tho ), //千位bcd码 .t_tho (t_tho ), //万位bcd码 .h_hun (h_hun ), //十万位bcd码 .t_hun (t_hun ), //百万位bcd码 .h_tho (h_tho ) //千万位bcd码 );endmodule参数定义:1ms计数2500000个,最大计数值49_999。
data_reg:控制数码管要显示的数据,复位有效时归0,因为没有小数点,我们考虑到有几位就只让几个数码管进行显示。不显示的数码管定义为4'd11
cnt_1ms:复位有效或计满时归0,其他情况+1
flag_1ms:1ms标志位,复位有效时归0,计满数值-1时拉高,其他情况保持低电平
cnt_sel:数码管编号,从0-7计数,复位有效时归0,计满7个数且标志位拉高时归0,标志位拉高时+1,其他情况保持不变
控制数码管位选信号:复位有效时data_disp归0,使能为高电平有效且标志信号为高时,分情况讨论,cnt_sel为0,第一个数码管赋data_reg的低四位,第一个数码管显示个位,...,需要有default语句,data_disp赋0即可,其他情况,data_disp保持不变
控制数码管段选信号:复位有效时seg所有位赋1表示每一段都不点亮,其他分情况讨论,data_disp为0时数码管显示0,0对应的段选码上一节讨论了,最高位是小数点不用都为1表示灭,低7位对应abcdefg=100_0000,以此类推,加上default语句
sel:数码管位选信号赋值,复位有效时归0,其他情况将sel_reg赋值给
实例化bcd
4、74hc595控制模块直接用上一节的代码,需要注意的是hc595_ctrl.v中第23行对data的赋值,把最右边的数码管作为个位比较符合我们的书写习惯,所以位选信号的顺序要改,原语句是assign data={sel,seg[0],seg[1],...,seg[7]};要改做assign data={sel[0],sel[1],...,sel[7],seg[0],seg[1],...,seg[7]};否则,数码管最左边是个位,不符合书写习惯。
5、数码管动态显示模块
该模块主要是对数码管动态显示驱动模块和74hc595控制模块的实例化,以及对应信号的连接
module seg_595_dynamic(input wire sys_clk , //系统时钟,频率50mhzinput wire sys_rst_n , //复位信号,低有效input wire [26:0] data , //数码管要显示的值input wire seg_en , //数码管使能信号,高电平有效output wire stcp , //输出数据存储寄时钟output wire shcp , //移位寄存器的时钟输入output wire ds //串行数据输入);//wire define wire [7:0] sel; //数码管位选信号 wire [7:0] seg; //数码管段选信号seg_dynamic seg_dynamic_inst ( .sys_clk (sys_clk ), //系统时钟,频率50mhz .sys_rst_n (sys_rst_n), //复位信号,低有效 .data (data ), //数码管要显示的值 .seg_en (seg_en ), //数码管使能信号,高电平有效 .sel (sel ), //数码管位选信号 .seg (seg ) //数码管段选信号);hc595_ctrl hc595_ctrl_inst ( .sys_clk (sys_clk ), //系统时钟,频率50mhz .sys_rst_n (sys_rst_n), //复位信号,低有效 .sel (sel ), //数码管位选信号 .seg (seg ), //数码管段选信号 .en (1'b1), .stcp (stcp ), //输出数据存储寄时钟 .shcp (shcp ), //移位寄存器的时钟输入 .ds (ds ) //串行数据输入 );endmodule两个模块的实例化
6、顶层模块top_seg_595
顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接
module top_seg_595(input wire sys_clk , //系统时钟,频率50mhzinput wire sys_rst_n , //复位信号,低电平有效output wire stcp , //输出数据存储寄时钟output wire shcp , //移位寄存器的时钟输入output wire ds //串行数据输入); //wire define wire [26:0] data ; //数码管要显示的值 wire seg_en ;//数码管使能信号,高电平有效 data_gen data_gen_inst ( .sys_clk (sys_clk ), //系统时钟,频率50mhz .sys_rst_n (sys_rst_n), //复位信号,低电平有效 .data (data ), //数码管要显示的值 .seg_en (seg_en) //数码管使能信号,高电平有效 ); seg_595_dynamic seg_595_dynamic_inst ( .sys_clk (sys_clk ), //系统时钟,频率50mhz .sys_rst_n (sys_rst_n ), //复位信号,低有效 .data (data ), //数码管要显示的值 .seg_en (seg_en ), //数码管使能信号,高电平有效 .stcp (stcp ), //输出数据存储寄时钟 .shcp (shcp ), //移位寄存器的时钟输入 .ds (ds ) //串行数据输入 ); endmodule数据产生和动态显示模块的实例化
编写testbench
`timescale 1ns/1nsmodule tb_top_seg_595();//wire definewire stcp ; //输出数据存储寄时钟wire shcp ; //移位寄存器的时钟输入wire ds ; //串行数据输入 //reg define reg sys_clk ; reg sys_rst_n ; //对sys_clk,sys_rst_n赋初始值 initial begin sys_clk = 1'b1; sys_rst_n <= 1'b0; #100 sys_rst_n <= 1'b1; end //clk:产生时钟 always #10 sys_clk <= ~sys_clk; //重新定义参数值,缩短仿真时间 defparam top_seg_595_inst.seg_595_dynamic_inst.seg_dynamic_inst. cnt_max=19; defparam top_seg_595_inst.data_gen_inst.cnt_max = 49; top_seg_595 top_seg_595_inst ( .sys_clk (sys_clk ), //系统时钟,频率50mhz .sys_rst_n (sys_rst_n ), //复位信号,低电平有效 .stcp (stcp ), //输出数据存储寄时钟 .shcp (shcp ), //移位寄存器的时钟输入 .ds (ds )//串行数据输入 ); endmoduletestbench已经很熟悉了。
对比波形
先看6个模块的从属关系,再分别检查6个模块的波形,比较容易debug。
数据产生模块
后面几个模块不细列。
分配管脚
和上一节一样
中国彩电行业以价换量,将为行业带来新的发展空间
18650锂电池的优缺点
海信冰箱真空X系列:“太空级”保鲜指引行业新航向
为什么LED电源芯片SM7012能够替换进口的VIPer12A?
泰科电子推出采用MHP技术的过流保护器件
基于FPGA的数码管动态显示
激光雷达与毫米波雷达的区别与联系
证监会披露小米CDR招股书,小米概念股多股涨停
只用0.1s!比指纹解锁还要快,vivo X20面部识别技术将改变世界
每日公告:惠伦晶体、汉威科技、华工科技、中京电子、传艺科技、传艺科技、景旺电子、西部超导
焊接机器人的主要应用领域
人工智能技术的应用扩张了智慧水利市场规模
电源高频变压器的设计方法简介
小米6高清拆解图赏
人脸识别技术本无对错 只是人心是有善恶的
新能源汽车推广应用政策盘点(2017年)
处于爆发临界点的物联网需要合作才能生存
哈希竞猜游戏系统开发Hash算法
混合云和多云谁对企业更有优势
PULSAR模拟电压可变衰减器特性