2024寒假一起练-用小脚丫FPGA完成秒表的制作
该项目使用了小脚丫webide,实现了9.9秒计时秒表的设计,它的主要功能为:使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。 秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。 秒表使用四个按钮输入:开始、停止、增量和清除(重置)。 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次); 停止输入使计数器停止递增,但使数码管显示当前计数器值; 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间; 复位/清除输入强制计数器值为零。。
标签
FPGA
数字逻辑
显示
秒表
bianshuyebianxie32
更新2024-04-01
54

项目需求:

  1. 计时功能: 实现基本的秒表计时功能,包括开始、暂停、继续、重置等操作。
  2. 计时精度: 确保计时精度高,可以达到毫秒甚至更高的精度要求。

需求分析

  1. 用户需要一个简单易用的计时工具,能够方便地进行计时操作。
  2. 用户希望能够清晰地看到计时结果,并能够进行操作控制(开始、暂停、重置等)。
  3. 用户可能需要记录计时结果,以便后续查看和分析。

实现的方式

设计计时器逻辑电路,包括计时功能和显示控制。

编写Verilog/VHDL代码实现计时器和按钮控制逻辑。

使用FPGA核心板的时钟源进行计时,确保准确性和稳定性。

将计时结果显示在七段显示器上。

进行仿真验证计时功能是否按预期工作。

在FPGA核心板上实现设计,并进行测试验证。

主要功能

  1. 使用七段显示器作为输出设备,在小脚丫FPGA核心板上创建一个2位数秒表。
  2.  秒表应从 0.0 秒计数到 9.9秒,然后翻转,计数值每0.1秒精确更新一次。
  3. 秒表使用四个按钮输入:开始、停止、增量和清除(重置)。
  4. 开始输入使秒表开始以10Hz时钟速率递增(即每0.1秒计数一次)
  5. 停止输入使计数器停止递增,但使数码管显示当前计数器值。
  6. 每次按下按钮时,增量输入都会导致显示值增加一次,无论按住增量按钮多长时间。
  7. 复位/清除输入强制计数器值为零。

功能框图

image.png

通过分频模块对12M的时钟信号进行分频,使其达到题目要求,再结合数码管显示驱动模块,把时间实时显示到数码管上,为了使按键可以正常的执行其功能,对按键进行消抖,用消抖后的信号对秒表进行相对控制。

代码

顶层模块:

module counter
(
clk , //时钟
rst , //复位
rst2,
rst3,
hold , //启动暂停按键
rst1 , //复位
hold1 ,
hold2 ,
hold3 ,
seg_led_1 , //数码管 1
seg_led_2 , //数码管 2
led //led
);
input clk,rst,rst1,rst2,rst3;
input hold,hold1,hold2,hold3;
output [8:0] seg_led_1,seg_led_2;
output reg [7:0] led;
wire clk1h; //10Hz 时钟
wire hold_pulse; //按键消抖后信号
reg hold_flag1; //按键标志位
wire hold_pulse1; //按键消抖后信号
reg hold_flag; //按键标志位
wire hold_pulse2; //按键消抖后信号
reg hold_flag2; //按键标志位
wire hold_pulse3; //按键消抖后信号
reg hold_flag3; //按键标志位
reg back_to_zero_flag ; //计时完成信号
reg [6:0] seg [9:0];
reg [7:0] seg1 [9:0];
reg [3:0] cnt_ge; //个位
reg [3:0] cnt_shi; //十位
reg [23:0] cnt=24'd0;//counter
reg cnt_flag=1'b0;//flag of the counter
reg increase_flag=1'b0;//flag of the increasement
reg pre_inc=1'b1;

initial
begin
seg[0] = 7'h3f; // 0 0011 1111
seg[1] = 7'h06; // 1 0000 0110
seg[2] = 7'h5b; // 2 0101 1011
seg[3] = 7'h4f; // 3 0100 1111
seg[4] = 7'h66; // 4 0110 0111
seg[5] = 7'h6d; // 5 0110 1101
seg[6] = 7'h7d; // 6 0111 1101
seg[7] = 7'h07; // 7 0000 0111
seg[8] = 7'h7f; // 8 0111 1111
seg[9] = 7'h6f; // 9 0110 1111


seg1[0] = 8'hbf; // 0. 1011 1111

seg1[1] = 8'h86; // 1. 1000 0110

seg1[2] = 8'hdb; // 2. 1101 1011

seg1[3] = 8'hcf; // 3. 1100 0111

seg1[4] = 8'he6; // 4. 1110 0111

seg1[5] = 8'hed; // 5. 1110 1101

seg1[6] = 8'hfd; // 6. 1111 1101

seg1[7] = 8'h87; // 7. 1000 0111

seg1[8] = 8'hff; // 8. 1111 1111

seg1[9] = 8'hef; // 9. 1110 1111

end

// 启动/暂停按键进行消抖

debounce U2 (

.clk(clk),

.rst(rst),

.key(hold),

.key_pulse(hold_pulse)

);

// 增量/清除按键进行消抖

debounce1 U3 (

.clk(clk),

.rst1(rst1),

.key1(hold1),

.key_pulse1(hold_pulse1)

);

// 增量/清除按键进行消抖

debounce3 U4 (

.clk(clk),

.rst3(rst3),

.key2(hold2),

.key_pulse2(hold_pulse2)

);

// 增量/清除按键进行消抖

debounce2 U5 (

.clk(clk),

.rst2(rst2),

.key3(hold3),

.key_pulse3(hold_pulse3)

);



// 用于分出一个 10Hz 的频率

divide #(.WIDTH(32),.N(120000)) U1 (

.clk(clk),

.rst_n(rst),

.clkout(clk1h)

);



//按键动作标志信号产生

always @ (posedge hold_pulse)



hold_flag <= 1;



//按键动作标志信号产生

always @ (posedge hold_pulse1)



hold_flag <= 0;



//按键动作标志信号产生

always @ (posedge hold_pulse2)



hold_flag2 <= 1;



//按键动作标志信号产生

always @ (posedge hold_pulse3)



hold_flag2 <= 0;



always @ (*)

if(!rst == 1)

back_to_zero_flag <= 0;

else if(cnt_shi==0 && cnt_ge==0)

back_to_zero_flag <= 1;

else

back_to_zero_flag <= 0;



always @(posedge clk)

begin

if (hold_pulse==1'b1)

begin

cnt_flag<=1'b1;

end

else if(hold_pulse1==1'b1)

begin

cnt_flag<=1'b0;

end

else if (hold_pulse3== 1'b1 )

begin

cnt<=24'd0;

cnt_ge<=4'd0;

cnt_shi<=4'd0;

end

else if (hold_pulse2 == 1'b0)

begin

if(pre_inc==1'b1)

begin

increase_flag<=1'b1;

end

pre_inc<=1'b0 ;

end

else if (hold_pulse2==1'b1)

begin

if(pre_inc==1'b0)

begin

pre_inc<=1'b1;

end

end

if (cnt_flag==1'b1)

begin



if(cnt_shi==4'd9)

begin

cnt_shi<=4'd0;

if(cnt_ge==4'd9)

begin

cnt_ge<=4'd0;

cnt_shi<=4'd0;

end

else

begin

cnt_ge<=cnt_ge+4'd1;

end

end

else

begin

cnt_shi<=cnt_shi+4'd1;

end

end

else

begin

if (increase_flag==1'b1)

begin

increase_flag<=1'b0;

if(cnt_shi==4'd9)

begin

cnt_shi<=4'd0;

if(cnt_ge==4'd9)

begin

cnt_ge<=4'd0;

cnt_shi<=4'd0;

end

else

begin

cnt_ge<= cnt_ge+4'd1;

end

end

else

begin

cnt_shi<=cnt_shi+4'd1;

end

end

end

end

always @(clk1h)

begin

cnt_ge <=cnt_ge +1;

end

//计时完成点亮 led

always @ ( back_to_zero_flag)

begin

if (back_to_zero_flag==1)

led = 8'b0;

else

led = 8'b11111111;

end

assign seg_led_1[8:0] = {2'b00,seg1[cnt_ge]};

assign seg_led_2[8:0] = {2'b00,seg[cnt_shi]};

endmodule

代码中包含了一个时钟触发的状态机,根据不同的输入信号(如hold_pulse、pre_inc等),控制计时器的计数、增加、重置等操作。

在代码中,根据不同的条件判断,会执行不同的操作,比如根据hold_pulse的状态设置cnt_flag、根据pre_inc的状态设置increase_flag、根据cnt_flag和increase_flag的状态来控制计时器的计数和增加等操作。

同时还有数码管的字库,也对数码管进行初始化。对数码管初始化以后又对时钟信号,按键信号进行中和,通过一系列的逻辑完成了数字秒表的功能


消抖模块:

module debounce (clk,rst,key,key_pulse);
parameter N = 1; //要消除的按键的数量
input clk;
input rst;
input [N-1:0] key; //输入的按键
output [N-1:0] key_pulse; //按键动作产生的脉冲
reg [N-1:0] key_rst_pre; //定义一个寄存器型变量存储上一个触发时的按键值
reg [N-1:0] key_rst; //定义一个寄存器变量储存储当前时刻触发的按键值
wire [N-1:0] key_edge; //检测到按键由高到低变化是产生一个高脉冲
always @(posedge clk or negedge rst)
begin
if (!rst)
begin
key_rst <= {N{1'b1}}; //初始化时给key_rst赋值全为1,{}中表示N个1
key_rst_pre <= {N{1'b1}};
end
else begin
key_rst<=key; //第一个时钟上升沿触发之后key的值赋给key_rst,同时key_rst的值赋给key_rst_pre
key_rst_pre<=key_rst; //非阻塞赋值。相当于经过两个时钟触发,key_rst存储的是当前时刻key的值,key_rst_pre存储的是前一个时钟的key的值
end
end
assign key_edge = key_rst_pre & (~key_rst);//脉冲边沿检测。当key检测到下降沿时,key_edge产生一个时钟周期的高电平
reg[17:0] cnt; //产生延时所用的计数器,系统时钟12MHz,要延时20ms左右时间,至少需要18位计数器
always @(posedge clk or negedge rst)
begin
if(!rst)
cnt <= 18'h0;
else if(key_edge)
cnt <= 18'h0;
else
cnt <= cnt + 1'h1;
end
reg [N-1:0] key_sec_pre; //延时后检测电平寄存器变量
reg [N-1:0] key_sec;
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec <= {N{1'b1}};
else if (cnt==18'h3ffff)
key_sec <= key;
end
always @(posedge clk or negedge rst)
begin
if (!rst)
key_sec_pre <= {N{1'b1}};
else
key_sec_pre <= key_sec;
end
assign key_pulse = key_sec_pre & (~key_sec);
endmodule

通过对按下信号延时再检测,判断按键是不是真的按下,通过一系列的逻辑,当按键按下以后,会向顶层模块发送一个key_pulse的高电平信号。

分频模块:

module divide ( clk,rst_n,clkout);
input clk,rst_n; //输入信号, 其中clk连接到FPGA的C1脚, 频率为12MHz
output clkout; //输出信号, 可以连接到LED观察分频的时钟
//parameter是verilog里常数语句
parameter WIDTH = 3; //计数器的位数, 计数的最大值为2**WIDTH-1
parameter N = 5; //分频系数, 请确保 N < 2**WIDTH-1, 否则计数会溢出
reg [WIDTH-1:0] cnt_p,cnt_n; //cnt_p为上升沿触发时的计数器, cnt_n为下降沿触发时的计数器
reg clk_p,clk_n; //clk_p为上升沿触发时分频时钟, clk_n为下降沿触发时分频时钟
//上升沿触发时计数器的控制
always @ (posedge clk or negedge rst_n ) //posedge和negedge是verilog表示信号上升沿和下降沿
begin
if(!rst_n)
cnt_p<=0;
else if (cnt_p==(N-1))
cnt_p<=0;
else cnt_p<=cnt_p+1; //计数器一直计数, 当计数到N-1的时候清零, 这是一个模N的计数器
end
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
clk_p<=0;
else if (cnt_p<(N>>1)) //N>>1表示右移一位, 相当于除以2去掉余数
clk_p<=0;
else
clk_p<=1;
end
always @ (negedge clk or negedge rst_n)
begin
if(!rst_n)
cnt_n<=0;
else if (cnt_n==(N-1))
cnt_n<=0;
else cnt_n<=cnt_n+1;
end
always @ (negedge clk)
begin
if(!rst_n)
clk_n<=0;
else if (cnt_n<(N>>1))
clk_n<=0;
else
clk_n<=1;
end
assign clkout = (N==1)?clk:(N[0])?(clk_p&clk_n):clk_p; //条件判断表达式
endmodule

通过对时钟脉冲的计数来实现0.1秒的信号输出。通过时钟的频率可以计算出单个脉冲的时间,再用所需时间除以单词脉冲时间就可以得到想要的时间,然后再定义一个变量,让其在完成计数时反转电平,这样就能得到一个固定频率的信号。

仿真:

都是用的官方的模块,,,

8eff6534d61eecaa14e34c0d7bad2b5.png



实物演示:

image.png

image.png

image.png

FPGA的资源利用说明

Design Summary:
Number of registers: 123 out of 4635 (3%)
PFU registers: 123 out of 4320 (3%)
PIO registers: 0 out of 315 (0%)
Number of SLICEs: 124 out of 2160 (6%)
SLICEs as Logic/ROM: 124 out of 2160 (6%)
SLICEs as RAM: 0 out of 1620 (0%)
SLICEs as Carry: 53 out of 2160 (2%)
Number of LUT4s: 247 out of 4320 (6%)
Number used as logic LUTs: 141
Number used as distributed RAM: 0
Number used as ripple logic: 106
Number used as shift registers: 0
Number of PIO sites used: 35 + 4(JTAG) out of 105 (37%)
Number of block RAMs: 0 out of 10 (0%)
Number of GSRs: 1 out of 1 (100%)
EFB used : No
JTAG used : No
Readback used : No
Oscillator used : No
Startup used : No
POR : On
Bandgap : On
Number of Power Controller: 0 out of 1 (0%)
Number of Dynamic Bank Controller (BCINRD): 0 out of 6 (0%)
Number of Dynamic Bank Controller (BCLVDSO): 0 out of 1 (0%)
Number of DCCA: 0 out of 8 (0%)
Number of DCMA: 0 out of 2 (0%)
Number of PLLs: 0 out of 2 (0%)
Number of DQSDLLs: 0 out of 2 (0%)
Number of CLKDIVC: 0 out of 4 (0%)
Number of ECLKSYNCA: 0 out of 4 (0%)
Number of ECLKBRIDGECS: 0 out of 2 (0%)

遇到的困难:

在编写四个功能按键时,起初就想着写一点简单的功能逻辑,但下进板子以后总会有各种各样的逻辑漏洞,于是就打算写一个状态机。当用状态机实现了四个按键的功能以后,又使用电子森林的AI助手对代码进行了优化,就有了这个最终的代码。

通过这次活动我学习到了很多FPGA的知识,加深了我对FPGA的兴趣,这次的入门活动,对我的影响意义深远。同时我也认识到这也是我们国家落后的一个方面,我会更加努力的学习。很荣幸可以参加这次的活动,感谢电子森林。

未来计划:

  1. 学习FPGA开发工具:熟悉FPGA厂商提供的开发工具,比如Xilinx Vivado、Quartus 和daimond等。这些工具提供了逻辑设计、综合、布局布线、仿真等功能,是进行FPGA设计的必备工具。
  2. 完成简单项目:从简单的项目开始,比如LED灯控制、按键检测等,逐步提升难度,实践所学的知识。通过项目实践,加深对FPGA设计的理解和掌握。
  3. 深入学习高级主题:学习FPGA的高级主题,比如时钟管理、DSP设计、高速通信接口设计等。这些知识将帮助你设计更复杂和功能强大的FPGA应用。
  4. 参与项目或竞赛:参与FPGA相关的项目或竞赛,与他人交流学习经验,拓展视野,提升实践能力。
  5. 持续学习和实践:FPGA技术在不断发展,保持学习的热情和持续实践是提升自己的关键。定期关注FPGA领域的最新技术和发展动态,不断提升自己的技能水平。
附件下载
秒表.zip
团队介绍
一只独狼
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2023 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号