上篇的代码源于正点原子的的例程,但是经过对比,我还是更喜欢黑金版本的思路,因为逻辑更加清晰。
实验效果:与上篇相同,计时显示。个位为ms,十位为s…
顶层结构图:
1.seg_decoder.v
此模块是数码管位的解码模块,将数据转换为数码管的段选码输出给后续的扫描模块。对应是顶层结构图中右侧的六个,因为有六个位,所以每个位都对应一个此模块。
module seg_decoder
(
input[3:0] bin_data, // 数码管一个位的数据
output reg[6:0] seg_data // 数码管的段码
);
always@(*)
begin
case(bin_data) //数字判断(0~f)
4'd0:seg_data <= 7'b100_0000;
4'd1:seg_data <= 7'b111_1001;
4'd2:seg_data <= 7'b010_0100;
4'd3:seg_data <= 7'b011_0000;
4'd4:seg_data <= 7'b001_1001;
4'd5:seg_data <= 7'b001_0010;
4'd6:seg_data <= 7'b000_0010;
4'd7:seg_data <= 7'b111_1000;
4'd8:seg_data <= 7'b000_0000;
4'd9:seg_data <= 7'b001_0000;
4'ha:seg_data <= 7'b000_1000;
4'hb:seg_data <= 7'b000_0011;
4'hc:seg_data <= 7'b100_0110;
4'hd:seg_data <= 7'b010_0001;
4'he:seg_data <= 7'b000_0110;
4'hf:seg_data <= 7'b000_1110;
default:seg_data <= 7'b111_1111;
endcase
end
endmodule
2.seg_scan.v
此为数码管的扫描模块,原理与之前相同,快速扫描六个位,利用视觉暂留现象形成动态。
module seg_scan
(
input clk, //系统时钟
input rst_n, //复位信号
output reg[5:0] seg_sel, //数码管位选输出
output reg[7:0] seg_data, //当前显示位的段选码输出
input[7:0] seg_data_0, //个位的段选码,由上面的解码器模块得到
input[7:0] seg_data_1, //十位的段选码
input[7:0] seg_data_2, //百位的段选码
input[7:0] seg_data_3, //千位的段选码
input[7:0] seg_data_4, //万位位的段选码
input[7:0] seg_data_5 //十万位的段选码
);
parameter SCAN_FREQ = 200; //扫描频率,此处的扫描频率是指6个位全部扫描一遍的频率,200Hz->5ms
parameter CLK_FREQ = 50_000_000; //时钟频率
parameter SCAN_COUNT = CLK_FREQ /(SCAN_FREQ * 6) - 1;
reg[31:0] scan_timer; //动态扫描计数器
reg[3:0] scan_sel; //位选计数器
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
scan_timer <= 32'd0;
scan_sel <= 4'd0;
end
else if(scan_timer >= SCAN_COUNT) //按位进行扫描显示
begin
scan_timer <= 32'd0;
if(scan_sel == 4'd5)
scan_sel <= 4'd0;
else
scan_sel <= scan_sel + 4'd1; //每计到1个周期位选数加1,用来显示下一个位
end
else
begin
scan_timer <= scan_timer + 32'd1;
end
end
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
seg_sel <= 6'b111111;
seg_data <= 8'hff;
end
else
begin
case(scan_sel)
//个位数码管显示
4'd0:
begin
seg_sel <= 6'b11_1110; // 最低位为0,即个位显示
seg_data <= seg_data_0; // 个位的段选码
end
//十位数码管显示
4'd1:
begin
seg_sel <= 6'b11_1101;
seg_data <= seg_data_1;
end
//...
4'd2:
begin
seg_sel <= 6'b11_1011;
seg_data <= seg_data_2;
end
4'd3:
begin
seg_sel <= 6'b11_0111;
seg_data <= seg_data_3;
end
4'd4:
begin
seg_sel <= 6'b10_1111;
seg_data <= seg_data_4;
end
4'd5:
begin
seg_sel <= 6'b01_1111;
seg_data <= seg_data_5;
end
default:
begin
seg_sel <= 6'b11_1111;
seg_data <= 8'hff;
end
endcase
end
end
endmodule
3.count_m10.v
10进制的计数模块。此模块的使用是实例化多个此模块,然后将一个模块的输出t与下一个模块输入en相连,从而将多个此模块串联在一起。具体可看主模块。
module count_m10
(
input clk,
input rst_n,
input en, // 此计数器使能信号,可看作前一个的进位信号
input clr, // 同步复位
output reg[3:0]data, // 计数器输出值
output reg t // 给出下一个计数器的使能信号,可看作向下一个发出进位信号
);
always@(posedge clk or negedge rst_n)
begin
if(rst_n==0)
begin
data <= 4'd0;
t <= 1'd0;
end
else if(clr)
begin
data <= 4'd0;
t <= 1'd0;
end
else if(en) //如果使能,则开始计数
begin
if(data==4'd9)
begin
t<= 1'b1; //计到9后产生进位信号
data <= 4'd0;//计数器复位
end
else
begin
t <= 1'b0;
data <= data + 4'd1;
end
end
else
t <= 1'b0;
end
endmodule
4.seg_test.v
主模块
module seg_test
(
input clk,
input rst_n,
output[5:0]seg_sel, // 位选输出
output[7:0]seg_data // 段选输出
);
reg[31:0] timer_cnt; // 计数器
reg en_1hz; //
//最底层计数器语句,每1ms给出一个信号
always@(posedge clk or negedge rst_n)
begin
if(rst_n == 1'b0)
begin
en_1hz <= 1'b0;
timer_cnt <= 32'd0;
end
else if(timer_cnt >= 32'd4_999_999) //1ms
begin
en_1hz <= 1'b1;
timer_cnt <= 32'd0;
end
else
begin
en_1hz <= 1'b0;
timer_cnt <= timer_cnt + 32'd1;
end
end
//实例化6个10进制计数器模块并串联,形成6位10进制计数器
wire[3:0] count0;
wire t0;
count_m10 count10_m0
(
.clk (clk),
.rst_n (rst_n),
.en (en_1hz),
.clr (1'b0),
.data (count0),
.t (t0)
);
wire[3:0] count1;
wire t1;
count_m10 count10_m1
(
.clk (clk),
.rst_n (rst_n),
.en (t0),
.clr (1'b0),
.data (count1),
.t (t1)
);
wire[3:0] count2;
wire t2;
count_m10 count10_m2
(
.clk (clk),
.rst_n (rst_n),
.en (t1),
.clr (1'b0),
.data (count2),
.t (t2)
);
wire[3:0] count3;
wire t3;
count_m10 count10_m3
(
.clk (clk),
.rst_n (rst_n),
.en (t2),
.clr (1'b0),
.data (count3),
.t (t3)
);
wire[3:0] count4;
wire t4;
count_m10 count10_m4
(
.clk (clk),
.rst_n (rst_n),
.en (t3),
.clr (1'b0),
.data (count4),
.t (t4)
);
wire[3:0] count5;
wire t5;
count_m10 count10_m5
(
.clk (clk),
.rst_n (rst_n),
.en (t4),
.clr (1'b0),
.data (count5),
.t (t5)
);
//实例化6个位的解码模块
wire[6:0] seg_data_0;
seg_decoder seg_decoder_m0
(
.bin_data (count5),
.seg_data (seg_data_0)
);
wire[6:0] seg_data_1;
seg_decoder seg_decoder_m1
(
.bin_data (count4),
.seg_data (seg_data_1)
);
wire[6:0] seg_data_2;
seg_decoder seg_decoder_m2
(
.bin_data (count3),
.seg_data (seg_data_2)
);
wire[6:0] seg_data_3;
seg_decoder seg_decoder_m3
(
.bin_data (count2),
.seg_data (seg_data_3)
);
wire[6:0] seg_data_4;
seg_decoder seg_decoder_m4
(
.bin_data (count1),
.seg_data (seg_data_4)
);
wire[6:0] seg_data_5;
seg_decoder seg_decoder_m5(
.bin_data (count0),
.seg_data (seg_data_5)
);
//实例化动态扫描模块
seg_scan seg_scan_m0
(
.clk (clk),
.rst_n (rst_n),
.seg_sel (seg_sel),
.seg_data (seg_data),
.seg_data_0 ({1'b1,seg_data_0}), //小数点段控制,低电平有效
.seg_data_1 ({1'b1,seg_data_1}),
.seg_data_2 ({1'b1,seg_data_2}),
.seg_data_3 ({1'b1,seg_data_3}),
.seg_data_4 ({1'b1,seg_data_4}),
.seg_data_5 ({1'b1,seg_data_5})
);
endmodule
以上代码的逻辑思路十分清晰使用起来也十分简单。如果后续模块过多,还可以把解码器模块和扫描模块进行二次封装。