第七节课,理论课的部分我们讲完了锁存器触发器,并介绍了时序电路的基本概念。实践课的内容并不多,基于上两节课制作的关于数码管显示的程序,制作一个频率计。
简单介绍一下频率计:
频率计 又称为频率计数器,是一种专门对被测信号频率进行测量的电子测量仪器。频率计主要由四个部分构成:时基(T)电路、输入电路、计数显示电路以及控制电路。频率计最基本的工作原理为:当被测信号在特定时间段T内的周期个数为N时,则被测信号的频率f=N/T.
1. 所调用的数码管显示子程序
module display_7seg(
input clk,
input [15:0]data,
output [7:0]seg,
output [3:0]an
);
reg clk_500hz=0;
integer clk_cnt;
always @(posedge clk)
begin
if(clk_cnt>=100_000) begin clk_cnt<= 0; clk_500hz<=~clk_500hz;end
else begin clk_cnt<= clk_cnt+1; end
end
//位码 控制
reg [3:0]an = 4'b1000;
always@(posedge clk_500hz)
an <= {an[0],an[3:1]};
这一段程序也很好理解,当500hz的clk 信号上升沿过来的时候,位码的值开始一位一位的更替:如初始定义为 1000 —> 0100 —->0010 —-> 0001 —-> 1000 如此循环往复.
//段码 控制
reg [3:0]duanma_4;
always@(an)
case(an)
4'b1000: duanma_4 = data[3:0]; //装载最低位的 显示数据,16进制4位
4'b0100: duanma_4 = data[7:4];
4'b0010: duanma_4 = data[11:8];
4'b0001: duanma_4 = data[15:12]; //装载最高位的 显示数据,16进制4位
default: duanma_4 = 4'hf;
endcase
这一段的意思就是根据我们之前定义的位码的值来决定我们要显示哪位数(这里的位表示个位十位百位千位)。因为我们数码管显示是采用动态扫描多位显示器的原理(可参考课本《电子技术基础(数字部分)》P175 例4.4.5)。
具体的说来,就是 16位的data 用 每4 位表示一个十进制数 0~9 ,从而有个位、十位、百位、千位四段数据, 由位码控制的 4 位段码每次分别装载 个位、十位、百位、千位 的数据。
//显示译码器 译码
reg [7:0] seg;
always @(duanma_4) //显示数据译码,将4位16进制变成8位二进制
case(duanma_4) // 注意每个显示译码器的转载数据位分别是 dp g f e d c b a
04'h0: seg = ~8'b1100_0000;//04'h0: // aaaaaa
04'h1: seg = ~8'b1111_1001; // f b
04'h2: seg = ~8'b1010_0100; // f b
04'h3: seg = ~8'b1011_0000; // f b
04'h4: seg = ~8'b1001_1001; // gggggg
04'h5: seg = ~8'b1001_0010; // e c
04'h6: seg = ~8'b1000_0010; // e c
04'h7: seg = ~8'b1111_1000; // e c
04'h8: seg = ~8'b1000_0000; // dddddd dp
04'h9: seg = ~8'b1001_0000;
04'ha: seg = ~8'b1000_1000;
04'hb: seg = ~8'b1000_0011;
04'hc: seg = ~8'b1100_0110;
04'hd: seg = ~8'b1010_0001;
04'he: seg = ~8'b1000_0111;
04'hf: seg = ~8'b1000_1110;
default: seg = ~8'b1100_0000;
endcase
// assign weima = weimaCtl;
endmodule
这一部分的程序就是把段码装载的4位数字以十进制数0~9显示到 八段数码管(七段数码管+小数点dp)
所以关于数码管显示的总程序就是:
module display_7seg(
input clk,
input [15:0]data,
output [7:0]seg,
output [3:0]an
);
reg clk_500hz=0;
integer clk_cnt;
always @(posedge clk)
begin
if(clk_cnt>=100_000) begin clk_cnt<= 0; clk_500hz<=~clk_500hz;end
else begin clk_cnt<= clk_cnt+1; end
end
//位码 控制
reg [3:0]an = 4'b1000;
always@(posedge clk_500hz)
an <= {an[0],an[3:1]};
//段码 控制
reg [3:0]duanma_4;
always@(an)
case(an)
4'b1000: duanma_4 = data[3:0];
4'b0100: duanma_4 = data[7:4];
4'b0010: duanma_4 = data[11:8];
4'b0001: duanma_4 = data[15:12];
default: duanma_4 = 4'hf;
endcase
//显示解码
reg [7:0] seg;
always @(duanma_4)
case(duanma_4)
04'h0: seg = ~8'b1100_0000;//04'h0:
04'h1: seg = ~8'b1111_1001;
04'h2: seg = ~8'b1010_0100;
04'h3: seg = ~8'b1011_0000;
04'h4: seg = ~8'b1001_1001;
04'h5: seg = ~8'b1001_0010;
04'h6: seg = ~8'b1000_0010;
04'h7: seg = ~8'b1111_1000;
04'h8: seg = ~8'b1000_0000;
04'h9: seg = ~8'b1001_0000;
04'ha: seg = ~8'b1000_1000;
04'hb: seg = ~8'b1000_0011;
04'hc: seg = ~8'b1100_0110;
04'hd: seg = ~8'b1010_0001;
04'he: seg = ~8'b1000_0111;
04'hf: seg = ~8'b1000_1110;
default: seg = ~8'b1100_0000;
endcase
endmodule
create 一个 .v 文件输入上述代码,保存以备后续使用。
2. 定义频率计的程序
我们频率计实验输入的信号来自于实验室信号发生器。
module frequency_meter(
input clk,
input rst_n,
input exp_io0, // expand 扩展的io 0 号引脚
output [7:0]seg_cs_pin,
output [7:0]seg_data_0_pin,
output [7:0]seg_data_1_pin
);
wire rst;
wire pin;
assign rst = ~rst_n; // rst 在板子上是按键,按下时为下降沿低电平,这里为了方便后续编程使用rst上升沿,采用取反逻辑
assign pin = exp_io0; // 就是修改一个名字,方便后续编程
reg [1:0]seg_fifo;
wire bclk;
always @(posedge clk or posedge rst)
if (rst) seg_fifo <= 2'd0;
else seg_fifo <= {seg_fifo[0],pin}; // 像队列一样接收数据
assign bclk = (seg_fifo == 2'b01); //当seg_fifo为01的时候, bclk为1
频率计的输入信号从pin拐脚进来,例如:0-1-0-1-0-1-0-1。 这里 0-1 就算一个上升沿(同理1-0 是一个下降沿),seg_fifo在这里相当于一个两位的缓冲区,储存关于是输入引号pin是上升沿还是下降沿的信息,bclk就相当于pin信号的一个拷贝。
bclk这么定义的目的其实就是为了避免程序运行过程中带来的延时使得pin信号无法被精准的计数,如果直接对pin信号操作,pin自己在不断变化,又要运行计数程序,倒不如干脆定义一个缓冲区,pin信号随他自己怎么变,我们用缓冲区的信号来模拟pin信号(也就是bclk)。这里seg_fifo只有两位,在执行上可能和直接计数pin没有什么区别,但是如果缓冲区变大,计数精度的提高就会显现出来,当然缓冲区的处理难度也会提升,这是后话。
integer i; // 定义一个整数变量
reg [31:0]fre_cnt; // 对输入的频率frequency进行计数
reg [31:0] fre_dis; // 主要是保存fre_cnt 在1秒末时刻的值,以便于fre_cnt的重新计数
always@(posedge clk or posedge rst)
if (rst) begin i<=0; fre_cnt<=32'd0; fre_dis<=32'd0; end
else
if(i>=100_000_000) begin i<=0; fre_dis<=fre_cnt; fre_cnt<=32'd0; end
else
begin i<=i+1'd1;
if(bclk) fre_cnt<=fre_cnt+1'b1;
end
这一段代码的意思就是时钟信号来的时候,用 i 来计时钟脉冲的个数,同时bclk为1的时候,frequency counter(fre_cnt)就不断加一(计数),记满一秒内的频率之后就把数值给了 dre_dis 继续计数。 fre_dis 保存 fre_cnt 在1秒末时刻的值, 然后交给后续处理。
注意这里: if(bclk) fre_cnt<=fre_cnt+1'b1;
,
我们在定义bclk的时候assign bclk = (seg_fifo == 2'b01);
,
bclk 是由 seg_fifo 所截取的上升沿来决定取 1 值的,其余时候无论pin信号为1,位0 还是下降沿都为0,bclk的脉冲宽度为1个完整的clk脉冲周期,这是由运行always过程块的前提always@(posedge clk or posedge rst)
决定的。
在虽然bclk比原来pin的信号宽度窄,但是没有关系啊,我们要做的是频率计嘛! 只想计数而已。
wire [31:0] cnt_d;
wire [31:0] cnt;
wire [31:0] data;
assign cnt = fre_dis;
assign cnt_d = cnt - (cnt/1_0000_0000)*10000_0000;
assign data[31:28] = cnt_d/1000_0000;
assign data[27:24] = (cnt_d-data[31:28] * 1000_0000) / 100_0000;
assign data[23:20] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000) / 10_0000;
assign data[19:16] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000) / 1_0000;
assign data[15:12] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000 - data[19:16]*1_0000) / 1000;
assign data[11:8] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000 - data[19:16]*1_0000 - data[15:12]*1000) / 100;
assign data[7:4] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000 - data[19:16]*1_0000 - data[15:12]*1000 - data[11:8]*100) / 10;
assign data[3:0] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000 - data[19:16]*1_0000 - data[15:12]*1000 - data[11:8]*100 - data[7:4]*10) / 1;
就是把 fre_dis 的数据用cnt储存,并转换为cnt_d的形式,转换的目的在于为了用段码来提取cnt_d 的数据以便于在数码管上以 0 ~ 9 显示出来,用cnt储存 fre_dis的目的在于让 fre_dis 接收下一次 fre_cnt 的数据,上一次的数据已经被变量 cnt 储存。
display_7seg u1(
.clk(clk),
.data(data[31:16]),
.seg(seg_data_0_pin),
.an(seg_cs_pin[3:0])
);
display_7seg u2(
.clk(clk),
.data(data[15:0]),
.seg(seg_data_1_pin),
.an(seg_cs_pin[7:4])
);
endmodule
所以总的frequency_meter.v 程序就是:
module frequency_meter(
input clk,
input rst_n,
input exp_io0,
output [7:0]seg_cs_pin,
output [7:0]seg_data_0_pin,
output [7:0]seg_data_1_pin
);
wire rst;
wire pin;
assign rst = ~rst_n;
assign pin = exp_io0;
reg [1:0]seg_fifo;
wire bclk;
always @(posedge clk or posedge rst)
if (rst) seg_fifo <= 2'd0;
else seg_fifo <= {seg_fifo[0],pin};
assign bclk = (seg_fifo == 2'b01);
integer i;
reg [31:0]fre_cnt;
reg [31:0] fre_dis;
always@(posedge clk or posedge rst)
if (rst) begin i<=0; fre_cnt<=32'd0; fre_dis<=32'd0; end
else
if(i>=100_000_000) begin i<=0; fre_dis<=fre_cnt; fre_cnt<=32'd0; end
else
begin i<=i+1'd1;
if(bclk) fre_cnt<=fre_cnt+1'b1;
end
wire [31:0] cnt_d;
wire [31:0] cnt;
wire [31:0] data;
assign cnt = fre_dis;
assign cnt_d = cnt - (cnt/1_0000_0000)*10000_0000;
assign data[31:28] = cnt_d/1000_0000;
assign data[27:24] = (cnt_d-data[31:28] * 1000_0000) / 100_0000;
assign data[23:20] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000) / 10_0000;
assign data[19:16] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000) / 1_0000;
assign data[15:12] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000 - data[19:16]*1_0000) / 1000;
assign data[11:8] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000 - data[19:16]*1_0000 - data[15:12]*1000) / 100;
assign data[7:4] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000 - data[19:16]*1_0000 - data[15:12]*1000 - data[11:8]*100) / 10;
assign data[3:0] = (cnt_d-data[31:28] * 1000_0000 - data[27:24]*100_0000 - data[23:20]*10_0000 - data[19:16]*1_0000 - data[15:12]*1000 - data[11:8]*100 - data[7:4]*10) / 1;
display_7seg u1(
.clk(clk),
.data(data[31:16]),
.seg(seg_data_0_pin),
.an(seg_cs_pin[3:0])
);
display_7seg u2(
.clk(clk),
.data(data[15:0]),
.seg(seg_data_1_pin),
.an(seg_cs_pin[7:4])
);
endmodule
接下来就是关于约束文件了,下面是我使能的语句,大家只要注意set_property -dict {PACKAGE_PIN R1 IOSTANDARD LVCMOS33} [get_ports {sw0}]
就好了
set_property -dict {PACKAGE_PIN P17 IOSTANDARD LVCMOS33} [get_ports clk ]
set_property -dict {PACKAGE_PIN P15 IOSTANDARD LVCMOS33} [get_ports rst_n ]
set_property -dict {PACKAGE_PIN R11 IOSTANDARD LVCMOS33} [get_ports {btn_pin[0]}]
set_property -dict {PACKAGE_PIN R1 IOSTANDARD LVCMOS33} [get_ports {sw0}]
set_property -dict {PACKAGE_PIN F6 IOSTANDARD LVCMOS33} [get_ports {led[0]}]
set_property -dict {PACKAGE_PIN G4 IOSTANDARD LVCMOS33} [get_ports {led[1]}]
set_property -dict {PACKAGE_PIN G3 IOSTANDARD LVCMOS33} [get_ports {led[2]}]
set_property -dict {PACKAGE_PIN J4 IOSTANDARD LVCMOS33} [get_ports {led[3]}]
set_property -dict {PACKAGE_PIN H4 IOSTANDARD LVCMOS33} [get_ports {led[4]}]
set_property -dict {PACKAGE_PIN J3 IOSTANDARD LVCMOS33} [get_ports {led[5]}]
set_property -dict {PACKAGE_PIN J2 IOSTANDARD LVCMOS33} [get_ports {led[6]}]
set_property -dict {PACKAGE_PIN K2 IOSTANDARD LVCMOS33} [get_ports {led[7]}]
set_property -dict {PACKAGE_PIN K1 IOSTANDARD LVCMOS33} [get_ports {led[8]}]
set_property -dict {PACKAGE_PIN H6 IOSTANDARD LVCMOS33} [get_ports {led[9]}]
set_property -dict {PACKAGE_PIN H5 IOSTANDARD LVCMOS33} [get_ports {led[10]}]
set_property -dict {PACKAGE_PIN J5 IOSTANDARD LVCMOS33} [get_ports {led[11]}]
set_property -dict {PACKAGE_PIN K6 IOSTANDARD LVCMOS33} [get_ports {led[12]}]
set_property -dict {PACKAGE_PIN L1 IOSTANDARD LVCMOS33} [get_ports {led[13]}]
set_property -dict {PACKAGE_PIN M1 IOSTANDARD LVCMOS33} [get_ports {led[14]}]
set_property -dict {PACKAGE_PIN K3 IOSTANDARD LVCMOS33} [get_ports {led[15]}]
set_property -dict {PACKAGE_PIN G2 IOSTANDARD LVCMOS33} [get_ports {seg_cs_pin[0]}]
set_property -dict {PACKAGE_PIN C2 IOSTANDARD LVCMOS33} [get_ports {seg_cs_pin[1]}]
set_property -dict {PACKAGE_PIN C1 IOSTANDARD LVCMOS33} [get_ports {seg_cs_pin[2]}]
set_property -dict {PACKAGE_PIN H1 IOSTANDARD LVCMOS33} [get_ports {seg_cs_pin[3]}]
set_property -dict {PACKAGE_PIN G1 IOSTANDARD LVCMOS33} [get_ports {seg_cs_pin[4]}]
set_property -dict {PACKAGE_PIN F1 IOSTANDARD LVCMOS33} [get_ports {seg_cs_pin[5]}]
set_property -dict {PACKAGE_PIN E1 IOSTANDARD LVCMOS33} [get_ports {seg_cs_pin[6]}]
set_property -dict {PACKAGE_PIN G6 IOSTANDARD LVCMOS33} [get_ports {seg_cs_pin[7]}]
set_property -dict {PACKAGE_PIN B4 IOSTANDARD LVCMOS33} [get_ports {seg_data_0_pin[0]}]
set_property -dict {PACKAGE_PIN A4 IOSTANDARD LVCMOS33} [get_ports {seg_data_0_pin[1]}]
set_property -dict {PACKAGE_PIN A3 IOSTANDARD LVCMOS33} [get_ports {seg_data_0_pin[2]}]
set_property -dict {PACKAGE_PIN B1 IOSTANDARD LVCMOS33} [get_ports {seg_data_0_pin[3]}]
set_property -dict {PACKAGE_PIN A1 IOSTANDARD LVCMOS33} [get_ports {seg_data_0_pin[4]}]
set_property -dict {PACKAGE_PIN B3 IOSTANDARD LVCMOS33} [get_ports {seg_data_0_pin[5]}]
set_property -dict {PACKAGE_PIN B2 IOSTANDARD LVCMOS33} [get_ports {seg_data_0_pin[6]}]
set_property -dict {PACKAGE_PIN D5 IOSTANDARD LVCMOS33} [get_ports {seg_data_0_pin[7]}]
set_property -dict {PACKAGE_PIN D4 IOSTANDARD LVCMOS33} [get_ports {seg_data_1_pin[0]}]
set_property -dict {PACKAGE_PIN E3 IOSTANDARD LVCMOS33} [get_ports {seg_data_1_pin[1]}]
set_property -dict {PACKAGE_PIN D3 IOSTANDARD LVCMOS33} [get_ports {seg_data_1_pin[2]}]
set_property -dict {PACKAGE_PIN F4 IOSTANDARD LVCMOS33} [get_ports {seg_data_1_pin[3]}]
set_property -dict {PACKAGE_PIN F3 IOSTANDARD LVCMOS33} [get_ports {seg_data_1_pin[4]}]
set_property -dict {PACKAGE_PIN E2 IOSTANDARD LVCMOS33} [get_ports {seg_data_1_pin[5]}]
set_property -dict {PACKAGE_PIN D2 IOSTANDARD LVCMOS33} [get_ports {seg_data_1_pin[6]}]
set_property -dict {PACKAGE_PIN H2 IOSTANDARD LVCMOS33} [get_ports {seg_data_1_pin[7]}]
set_property -dict {PACKAGE_PIN B16 IOSTANDARD LVCMOS33} [get_ports {exp_io0} ]
接下来大家综合,运行试试吧!
放张实验效果:示波器为1087298Hz信号输入