感谢野火开源教程的支持!EmbedFire – 东莞野火电子技术有限公司
本实验采用阳极6位数码管,其中段选是共用的。
一、实验目标
让六位数码管显示从十进制数 0 开始计数,每 0.1s 加 1,一直到加到十进制数 999999。到达 999999 之后回到 0 开始重新计数。
二、电路图
2.1 六位数码管电路图
2.2 八段数码管示意图
1.3 数码管编码译码表
1.4 数码管数据驱动电路
数码管驱动原理图
74HC595 是一个 8 位串行输入、并行输出的位移缓存器。其内部具有 8 位移位寄存器 和一个存储器,具有三态输出功能。本实验采用两片串行74HC595实现数码管的位选和段选,共8(段选)+ 6(位选)=14位宽数据。14位数据输出形式为 {段选[0:7],位选[5:0]}。
三、关键设计技术
3.1 基于视觉暂留的数码管动态扫描
视频其实是由一帧帧图片组成的,只不过在一秒钟内播放的图片数量足够多时,我们就觉得它一直在动,这种视频就是视觉暂留现象。在本次实验中利用该原理,每次只选中某一位的数码管,然后输入相应的段选信息完成一位数码管的显示。每循环分别选中6位数码管显示6次,实现对一个6位数据的一轮显示。
3.2 基于BCD码的整数 个、十、百、千、万、十万位数字分离
在本实验中,我们希望只是输进去一个十进制整数就实现动态数码管的显示,但是对于每一位数码管,我们则需要对应的整数位来进行编码。比如整数23,我们首先要分离解码得到十位2,再对2进行编码得到8‘b0010_0101,输入到数码管段选管脚才能使得数码管在相应的位选位置正常显示。由于在FPGA中使用浮点运算、除法、求余数操作是很耗费资源的,因此我们需要一种新的方法来进行整数位分离工作,这种方法就是8421码方法。
转换分离步骤示意图
如上图所示,十进制数 234 其对应的二进制数为 1110_1010,首先第一步我们在其前面补上若干个 0,那么这个 0 的数量是如何决定的呢?参与转换的十进制有多少位,就需 要多少个相应的 BCD 码,比如 234,该十进制数是 3 位,而一位十进制数的 BCD 码是4位,所以这里我们就需要 12 位 BCD 码,故我们就在前面补 12 个 0。其余位数的十进制补 0 数量也是这样进行计算。
第二步我们需要进行判断运算移位操作,首先判断每一个 BCD 码其对应的十进制数是 否大于 4,如果大于 4 就对 BCD 码做加 3 操作,若小于等于 4 就让其值保持不变。当对每 一个 BCD 码进行判断运算后,都需要将运算后的数据像左移 1 位。移完位后我们仍按前面 所述进行判断运算,判断运算后需再次移位,以此循环,当我们进行 8 次判断移位后的 BCD 码部分数据就是我们转换的数据,如图 26-6 所示,当第 8 次移位后的 8421BCD 码数 据对应的十进制正是 234。这里需要注意的是我们输入转换的二进制码有多少位我们就需 要进行多少次判断移位操作,这里输入的是 8 位二进制,我们就进行 8 次判断移位操作。
3.3 循环步长的分解与计数
*例子分析(基于BCD编码器块)
由实验目标可知,本次需要显示的最大数字为999_999,则按照上述BCD码原理需要6(整数位数)*4(一位整数位数需要的bcd码位数)=24位宽二进制数据才能描述数字999_999,又因为999_999需要20位二进制数保存,因此我们首先需要设计一个20+24=44位宽缓存器,以统计修改或者输出数据。
其次,由于bcd码的特性,20 bit表示的999_999一共需要移位20次,并且在之前有1次补零操作,在之后有1次做移位结束以后的数据处理和数据输出,因此每次循环过程我们需要20+1+1=22个步骤,我们需要一个计数器去表示它作为循环。
接下来我们需要确定22个步骤一个循环里面,每个步骤最短需要做几件事。由3.2可知,我们要在每一个步骤里进行判断是否要加3和移位惭怍两个小步,因此我们一个步骤的时钟周期是2个系统时钟周期,。我们可以设计一个计数器来实现0~1的两个状态计数,可是这是不必要的,因为一个1位二进制的翻转就能解决这个问题。综上,我们还需要设计二分频的一个时钟信号。
小结:在程序设计之前我们要理清楚我们的一个循环计数过程需要的步骤(取步骤最多的那个子线程为准)。并且设置一个统一的数据缓存器作为线程间数据交换的统一管理媒介,而不是各自设计一个存储空间,这不利于统一输入输出格式管理,增加算法判断条件复杂性。
3.4 使用状态枚举方法 代替 复杂逻辑判断动态生成相应状态
在本次实验中,如果我们需要显示一个数 - 756,那么我们希望数码管输出**-765,其中*代表数码管熄灭不显示。同理我们需要显示 -32,则数码管应显示***-32,。显示456,数码管显示***456.面对特殊情况 -999_999,由于已经超过6位数码管显示999_999。
面对上述的功能要求,在c语言软件编程中,我们寄希望于设计一系列判断逻辑条件来动态计算决定数码管每一位是怎么显示的,这样的代码无疑更加“算法”、适用性更高、更加优美。但是也带来了算法逻辑设计复杂、输出的不同状态的条件更难判断。这意味着,对于我们显示一个未知位数的数字是很有优势的。
但是对于数字IC或者FPGA来说,分配给某个模块的输入输出数据位宽或者存储空间往往是确定大小的,因此面对较大单位数据量时,我们仍然推荐使用上述算法设计方法。但是相反地,当面对较小的数据算法时,我们可以直接枚举所有的可能来完成算法的快速设计。本实验中,仅仅只有6位数码管的形式,一次我们使用条件判断枚举显示状态的方式来做。
四、模块框架设计
* 我们将这个实验大块top_seg_595分成三个功能块:
1. data_gen 显示数据生成模块
2. seg_danamic 数码管动态显示驱动模块(BCD转码子块bcd_8421+综合块seg_danamic本身,用于小数点显示+负号显示+数位显示的逻辑规则制定)
3. hc595_ctrl 74HC595 控制模块(用于将数据打包成74HC595 硬件接受的形式,并输出74HC595驱动时钟)
* 数据块的功能约束设计为:
1. data_gen 显示数据0~999_999,设定数据更新频率为0.1s加1。
2. bcd_8421 将整数位进行分解,且越快越好。由3.2可知,最小步长是两个系统时钟周期(移位+判断加三)。本实验的系统时钟频率为50MHZ。那么每个步骤就是40ns,一次bcd在6位整数下需要22个步骤(0步:计算数数位补齐 + 1~20步:6位整数需要20位二进制数,因此需要移位20次+21步:赋值输出各个整数位),则整个bcd转码时间为22*40 = 0.88us。
3. seg_danamic 数码管动态显示驱动模块,设计需要每1ms更新一次位选,也就是说数码管更新显示动态扫描频率为1000HZ。
4. hc595_ctrl需要输出74HC595芯片的移位存储频率,我们设计为系统时钟的4分频即12MHZ(74HC595芯片手册里要求在20MHZ以下,所以二分频的25MHZ不行)。即每80ns芯片74HC595会移入芯片读取一位数据,本实验中共需要读取14位数据即{段选seg[0:7],位选sel[5:0]},则有80*14 = 1.12us一个周期的74HC595存储其数据克隆周期。
五、设计子程序模块
5.1 data_gen 模块设计
* 模块管脚分布及管脚数据位宽设计
data:数据最大值999_999是20位二进制的数据。
point:6位数码管,6个数码管显示位。
sign: 负号输出标志信号,高有效。
* 数据生成波形图绘制
其中要注意的小技巧是,cnt_flag是在cnt_100ms = 4999_998时跳变为高电平,在cnt_100ms = 0时跳变为低电平的。这是一个占空比很小的方波,并不是占空比50%的方波。当cnt_flag为高本设置为输出条件时(条件判断触发依然是系统时钟上升沿时进行的),这有助于data输出的频率和标准系统时钟频率对齐,不会发生传输延时。(这其实是由物理时钟传输介质材质决定的,系统时钟传输介质比较好,寄存器之间的信号传输介质延时性能较差)
* 模块代码
`timescale 1ns/1ns
// Module Name : data_gen
// Project Name : top_seg_595
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 生成数码管显示数据(000_000 ~ 999_999)
module data_gen
#(
parameter CNT_MAX = 23'd4999_999, //100ms计数值
parameter DATA_MAX= 20'd999_999 //显示的最大值
)
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
output reg [19:0] data , //数码管要显示的值
output wire [5:0] point , //小数点显示,高电平有效
output reg seg_en , //数码管使能信号,高电平有效
output wire sign //符号位,高电平显示负号
);
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//reg define
reg [22:0] cnt_100ms ; //100ms计数器
reg cnt_flag ; //100ms标志信号
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
//不显示小数点以及负数
assign point = 6'b000_000;
assign sign = 1'b0;
//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-999_999
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data <= 20'd0;
else if((data == DATA_MAX) && (cnt_flag == 1'b1))
data <= 20'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
5.2 bcd_8421 模块设计
* 模块管脚分布及管脚数据位宽设计
整数位为6位,因此有6个输出引脚。个位最大的数是9即4’b1001 ,因此被设置为4位输出位宽。
* 数据生成波形图绘制
波形设置详情请结合3.2和3.3的解释。
* 模块代码
`timescale 1ns/1ns
// Module Name : bcd_8421
// Project Name : top_seg_595
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 二进制数转BCD码
module bcd_8421
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [19: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码
);
//reg define
reg [4:0] cnt_shift ; //移位判断计数器
reg [43:0] data_shift ; //移位判断数据寄存器
reg shift_flag ; //移位判断标志信号
//cnt_shift:从0到21循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_shift <= 5'd0;
else if((cnt_shift == 5'd21) && (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~20时进行移位判断操作
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_shift <= 44'b0;
else if(cnt_shift == 5'd0) // 补0
data_shift <= {24'b0,data};
else if((cnt_shift <= 20) && (shift_flag == 1'b0)) // 判断要不要加3
begin
data_shift[23:20] <= (data_shift[23:20] > 4) ? (data_shift[23:20] + 2'd3) : (data_shift[23:20]);
data_shift[27:24] <= (data_shift[27:24] > 4) ? (data_shift[27:24] + 2'd3) : (data_shift[27:24]);
data_shift[31:28] <= (data_shift[31:28] > 4) ? (data_shift[31:28] + 2'd3) : (data_shift[31:28]);
data_shift[35:32] <= (data_shift[35:32] > 4) ? (data_shift[35:32] + 2'd3) : (data_shift[35:32]);
data_shift[39:36] <= (data_shift[39:36] > 4) ? (data_shift[39:36] + 2'd3) : (data_shift[39:36]);
data_shift[43:40] <= (data_shift[43:40] > 4) ? (data_shift[43:40] + 2'd3) : (data_shift[43:40]);
end
else if((cnt_shift <= 20) && (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;
//当计数器等于20时,移位判断操作完成,对各个位数的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;
end
else if(cnt_shift == 5'd21) // 数据输出
begin
unit <= data_shift[23:20];
ten <= data_shift[27:24];
hun <= data_shift[31:28];
tho <= data_shift[35:32];
t_tho <= data_shift[39:36];
h_hun <= data_shift[43:40];
end
endmodule
5.3 seg_danamic 模块设计
* 模块管脚分布及管脚数据位宽设计
8段数码管即段选seg[0:7],6位数码管即位选sel[5:0]。
* 数据生成波形图绘制
dot_disp:当前数码管显示的小数点,我们输入的 point 信号是点亮第二个数码管的小 数点,而我们的数码管是低电平点亮,所以这里当扫描到第二个数码管时让 dot_disp 信号 为低即可
data_reg:数码管待显示内容寄存器,因为这里我们假设输入要显示的十进制数为 9876,并且显示负号,所以前五个数码管就会显示-9876 的数值,此时最高位数码管什么都 不显示,我们用 X 表示,所以这里六个数码管显示的内容就是:X-9876。
data_disp:当前点亮数码管显示的值。若我们此时点亮的是第一个数码管,那么我们 就需要给第一个数码管显示值 6,若刷新到第二个数码管,那么我们就需要给第二个数码 管显示值 7,以此类推;当刷新到第五个数码管时,此时显示的是负号,那么我们该如何 表示呢?这里我们让该信号的值为 10 来表示,也就是说当 data_disp 的值为 10 时就让数码 管显示负号,同理这里我们定义 data_disp 的值为 11 时让数码管什么也不显示,即不点亮 数码管。(小技巧:这里就像状态机一样,对所有可能显示的状态进行编码)
设计思想可参考3.4 。
* 模块代码
`timescale 1ns/1ns
// Module Name : seg_dynamic
// Project Name : top_seg_595
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 数码管动态显示
module seg_dynamic
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [19:0] data , //数码管要显示的值
input wire [5:0] point , //小数点显示,高电平有效
input wire seg_en , //数码管使能信号,高电平有效
input wire sign , //符号位,高电平显示负号
output reg [5:0] sel , //数码管位选信号
output reg [7:0] seg //数码管段选信号
);
//parameter define
parameter CNT_MAX = 16'd49_999; //数码管刷新时间计数最大值
//wire define
wire [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 ; //十万位数
//reg define
reg [23:0] data_reg ; //待显示数据寄存器
reg [15:0] cnt_1ms ; //1ms计数器
reg flag_1ms ; //1ms标志信号
reg [2:0] cnt_sel ; //数码管位选计数器
reg [5:0] sel_reg ; //位选信号
reg [3:0] data_disp ; //当前数码管显示的数据
reg dot_disp ; //当前数码管显示的小数点
// ******枚举显示情况
//data_reg:控制数码管显示数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_reg <= 24'b0;
//若显示的十进制数的十万位为非零数据或需显示小数点,则六个数码管全显示
else if((h_hun) || (point[5]))
data_reg <= {h_hun,t_tho,tho,hun,ten,unit};
//若显示的十进制数的万位为非零数据或需显示小数点,则值显示在5个数码管上
//打比方我们输入的十进制数据为20’d12345,我们就让数码管显示12345而不是012345
else if(((t_tho) || (point[4])) && (sign == 1'b1))//显示负号
data_reg <= {4'd10,t_tho,tho,hun,ten,unit};//4'd10我们定义为显示负号
else if(((t_tho) || (point[4])) && (sign == 1'b0))
data_reg <= {4'd11,t_tho,tho,hun,ten,unit};//4'd11我们定义为不显示
//若显示的十进制数的千位为非零数据或需显示小数点,则值显示4个数码管
else if(((tho) || (point[3])) && (sign == 1'b1))
data_reg <= {4'd11,4'd10,tho,hun,ten,unit};
else if(((tho) || (point[3])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,tho,hun,ten,unit};
//若显示的十进制数的百位为非零数据或需显示小数点,则值显示3个数码管
else if(((hun) || (point[2])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd10,hun,ten,unit};
else if(((hun) || (point[2])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,4'd11,hun,ten,unit};
//若显示的十进制数的十位为非零数据或需显示小数点,则值显示2个数码管
else if(((ten) || (point[1])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd11,4'd10,ten,unit};
else if(((ten) || (point[1])) && (sign == 1'b0))
data_reg <= {4'd11,4'd11,4'd11,4'd11,ten,unit};
//若显示的十进制数的个位且需显示负号
else if(((unit) || (point[0])) && (sign == 1'b1))
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd10,unit};
//若上面都不满足都只显示一位数码管
else
data_reg <= {4'd11,4'd11,4'd11,4'd11,4'd11,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;
else
cnt_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;
else
flag_1ms <= 1'b0;
//cnt_sel:从0到5循环数,用于选择当前显示的数码管
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_sel <= 3'd0;
else if((cnt_sel == 3'd5) && (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 <= 6'b000_000;
else if((cnt_sel == 3'd0) && (flag_1ms == 1'b1))
sel_reg <= 6'b000_001;
else if(flag_1ms == 1'b1)
sel_reg <= sel_reg << 1;
else
sel_reg <= sel_reg;
//控制数码管的位选信号,使六个数码管轮流显示
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个数码管赋十万位值
default:data_disp <= 4'b0 ;
endcase
else
data_disp <= data_disp;
//dot_disp:小数点低电平点亮,需对小数点有效信号取反
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dot_disp <= 1'b1;
else if(flag_1ms == 1'b1)
dot_disp <= ~point[cnt_sel];
else
dot_disp <= dot_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 <= {dot_disp,7'b100_0000}; //显示数字0
4'd1 : seg <= {dot_disp,7'b111_1001}; //显示数字1
4'd2 : seg <= {dot_disp,7'b010_0100}; //显示数字2
4'd3 : seg <= {dot_disp,7'b011_0000}; //显示数字3
4'd4 : seg <= {dot_disp,7'b001_1001}; //显示数字4
4'd5 : seg <= {dot_disp,7'b001_0010}; //显示数字5
4'd6 : seg <= {dot_disp,7'b000_0010}; //显示数字6
4'd7 : seg <= {dot_disp,7'b111_1000}; //显示数字7
4'd8 : seg <= {dot_disp,7'b000_0000}; //显示数字8
4'd9 : seg <= {dot_disp,7'b001_0000}; //显示数字9
4'd10 : seg <= 8'b1011_1111 ; //显示负号
4'd11 : 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 <= 6'b000_000;
else
sel <= sel_reg;
//---------- bsd_8421_inst ----------
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码
);
endmodule
5.4 hc595_ctrl 模块设计
* 模块管脚分布及管脚数据位宽设计
设计详情请看章节四,并结合章节二中的74芯片介绍。
* 数据生成波形图绘制
shcp:移位寄存器时钟,上升沿时将数据写入移位寄存器中。我们在 ds 数据的中间状 态拉高产生上升沿,这样可以使 shcp 采得的 ds 数据更加稳定。如波形图所示我们在 cnt=2 时拉高,cnt=0 时拉低,即可产生该时钟,其频率即为系统时钟四分频(12.5MHz)。
stcp:存储寄存器时钟。当我们 14 位数码管控制信号传输完之后我们需要拉高一个 stcp 时钟来将信号存入存储寄存器之中。最后一个数据是在 cnt_bit=13 且 cnt=2 时传输的, 所以我们就在下一个时钟(cnt_bit=13 且 cnt=3 时)将 stcp 拉高一个时钟产生上升沿即可。
ds:串行数据输出(对我们 FPGA 芯片来说其是输出,对 74HC595 来说其是输入, stcp 和 shcp 信号也是如此)。这里我们回到原理图 (第二章),可以看到第二片的 Q5 引脚连到 了数码管的 DIG6,也就是最右侧的数码管,而我们最右侧数码管对应的是我们位选信号的 最低位,即 sel[0]。所以我们第一位应传输的数据为 sel[0],依次类推根据原理图传输相应 的数据,具体的传输数据如波形图所示。当一次数据传完之后再次回到状态 0 开始新一轮 的数码管信号传输。
oe:存储寄存器数据输出使能信号,低电平有效,这里我们将复位信号取反的值赋给 该信号即可。
* 模块代码
`timescale 1ns/1ns
// Module Name : hc595_ctrl
// Project Name : seg_595_static
// Target Devices: Altera EP4CE10F17C8N
// Tool Versions : Quartus 13.0
// Description : 595控制模块
module hc595_ctrl
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低有效
input wire [5:0] sel , //数码管位选信号
input wire [7:0] seg , //数码管段选信号
output reg stcp , //数据存储器时钟
output reg shcp , //移位寄存器时钟
output reg ds , //串行数据输入
output wire oe //使能信号,低有效
);
//reg define
reg [1:0] cnt_4 ; //分频计数器
reg [3:0] cnt_bit ; //传输位数计数器
//wire define
wire [13:0] data ; //数码管信号寄存
//将数码管信号寄存
assign data = {seg[0],seg[1],seg[2],seg[3],seg[4],seg[5],seg[6],seg[7],sel};
//将复位取反后赋值给其即可
assign oe = ~sys_rst_n;
//分频计数器:0~3循环计数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_4 <= 2'd0;
else if(cnt_4 == 2'd3)
cnt_4 <= 2'd0;
else
cnt_4 <= cnt_4 + 1'b1;
//cnt_bit:每输入一位数据加一
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3 && cnt_bit == 4'd13)
cnt_bit <= 4'd0;
else if(cnt_4 == 2'd3)
cnt_bit <= cnt_bit + 1'b1;
else
cnt_bit <= cnt_bit;
//stcp:14个信号传输完成之后产生一个上升沿
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
stcp <= 1'b0;
else if(cnt_bit == 4'd13 && cnt_4 == 2'd3)
stcp <= 1'b1;
else
stcp <= 1'b0;
//shcp:产生四分频移位时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
shcp <= 1'b0;
else if(cnt_4 >= 4'd2)
shcp <= 1'b1;
else
shcp <= 1'b0;
//ds:将寄存器里存储的数码管信号输入即
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
ds <= 1'b0;
else if(cnt_4 == 2'd0)
ds <= data[cnt_bit];
else
ds <= ds;
endmodule
5.5 大模块 top_seg_595的子模块连线整合
*整合关系及子模块链接
* 模块代码
`timescale 1ns/1ns
module top_seg_595#(
parameter CNT_MAX = 23'd4999_999, //100ms计数值, 数据产生频率
parameter DATA_MAX= 20'd999_999 //显示的最大值
)
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
output wire stcp , //输出数据存储寄时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds , //串行数据输入
output wire oe //输出使能信号
);
// data_gen输出
wire [19:0] data ; //数码管要显示的值
wire [5:0] point ; //小数点显示,高电平有效top_seg_595
wire seg_en ; //数码管使能信号,高电平有效
wire sign ; //符号位,高电平显示负号
// seg_dymamic 输出
wire [5:0] sel; // 位选
wire [7:0] seg;
// 数据子模块实例化
data_gen #(
.CNT_MAX (CNT_MAX ),
.DATA_MAX (DATA_MAX)
)
data_gen_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.data (data ),
.point (point ),
.sign (sign ),
.seg_en (seg_en )
);
//转码器子模块实例化
seg_dynamic seg_dynamic_inst(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低有效
.data (data ), //数码管要显示的值
.point (point ), //小数点显示,高电平有效
.sign (sign ), //符号位,高电平显示负号
.seg_en (seg_en ), //数码管使能信号,高电平有效
.sel (sel ), //输出数码管位选信号
.seg (seg ) //输出数码管段选信号
);
//74H芯片驱动子模块实例化
hc595_ctrl(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低有效
.sel (sel ), //输出数据存储寄时钟
.seg (seg ), //移位寄存器的时钟输入
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
endmodule
至此我们的动态数码管显示例程已经介绍完毕了。