基于BASYS3开发板的秒表设计以及应用

本文详细介绍了基于BASYS3开发板设计的一款秒表装置,利用时钟使能模块实现百分之一秒、一秒、一分钟的计时,并通过七段数码管显示。设计中采用时钟使能而非分频,提高系统稳定性,同时实现了计时模式切换和上下计数功能。通过模块化设计,包括时钟使能、计数器、位选和数码管显示等,确保了系统的可靠性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

期末课设计,在这里记录一下开发思路。

1.摘要:

这是一款基于BASYS3开发板的秒表装置,其功能有:通过七段数码管显示当前秒表秒数,通过按键选择向上计时和向下计时,通过按键切换模式,实现左二数码管显示分钟,右二数码管显示秒和左二数码管显示秒,右二数码管显示百分之一秒两种模式的切换。设计亮点有,一、利用时钟使能得到不同的时钟而不是使用时钟分频来获得不同的时钟,提高了系统稳定性。二、显示界面符合实际。三、资源使用量少。

关键词:BASYS3 七段数码管 时钟使能

2.设计思路介绍

首先设计一个百分之一秒的时钟使能模块,其能记录主时钟上升沿并每百分之一秒输出一个使能信号,然后通过级联的方式,将百分之一秒使能信号作为一秒时钟使能模块的时钟触发信号,一秒时钟使能模块每秒输出秒使能信号,将秒使能信号作为分钟时钟使能模块的时钟触发信号,实现百分之一秒、一秒、一分钟定时器的设计。然后分别将三者的时钟使能信号传到三个计数器用于记录各个计时器使能信号的个数,接着通过一个选择器将需要显示的计数器的计数值传到显示模块,在显示模块中通过除数和取余的操作得出计数值的个位和十位,然后通过BCD译码器将相应信号给到相应数码管,同时设计一个200Hz的位选频率发生器,实现数码管的扫描显示。

(注:为了方便展示,这里并未给出reset信号和stop信号的功能,但实际项目中具有该功能)

3.模块介绍

3.1时钟模块

3.1.1 时钟分频和时钟使能模块介绍

板子上的晶振频率为100MHZ,但我们需要的信号是百分之一秒,即100Hz的时钟信号(给百分之一秒提供时钟信号)。实现的方法有两种,一种是使用分频器的方式,一种是使用时钟使能的方式。分频方式即在目标频率的前半个周期让时钟电平为正,后半个周期反转电平信号(以偶分频为例),此项目通过计算可知当计数100MHz的时钟的上升沿499999次时,100Hz的时钟信号取反,即可获得100Hz的时钟频率。而时钟使能方法是指在通过计数器记录时钟的次数,当计数器达到目标次数时输出一个使能信号,以此项目为例,当计数100MHz时钟信号999999次后输出使能信号,便可获得100Hz的信号。

                 时钟分频ASM图                                                                   时钟使能ASM图

3.1.2时钟分频和时钟使能的选择

在很多设计中,虽然内部不同模块的处理速度不同,但由于这些时钟是同源的,可以将它们转化为单一时钟处理;在ASIC中可以通过STA约束让分频始终和源时钟同相,但FPGA由于器件本身和工具的限制,分频时钟和源时钟的Skew不容易控制(使用锁相环分频是个例外),难以保证分频时钟和源时钟同相,因此推荐的方法是使用时钟使能,通过使用时钟使能可以避免时钟“满天飞”的情况,进而避免了不必要的亚稳态发生,在降低设计复杂度的同时也提高了设计的可靠性。[1]

3.1.3 时序测试:

1.时钟分频时序图

 

可见其达到计数值后翻转电平

注:由于测试需要,这里将计数初值设为4,

2.时钟使能时序图

 

可见其只有在计数值到达时会进行使能输出,其余时间输出为0

注:由于测试需要,这里将计数初值设为9,

3.总定时器时序

 

可见可以通过三者级联实现预设功能。

3.1.4 时钟使能模块代码

module clock_enable(

input wire clock,

input wire reset,

input wire stop,

input wire [31:0] number,

output reg enable

);

reg [31:0] counter;



always @(posedge clock or posedge reset or posedge stop)

begin

if (reset) begin   //reset信号判断

counter <= 0;   //有效清零

enable <= 0;

end

else if (stop) begin  //stop信号判断

counter <= counter; //有效保持

enable <= enable;

end

else  begin

if (counter >= number) begin   //如果计数值大于给定值,使能输出

counter <= 0;

enable <= 1;

end

else begin

counter <= counter + 1;  //未达到计数值,继续

enable <= 0;

end

end

end

endmodule

3.2 计数器模块

3.2.1 模块介绍

为了记录定时器记录的时间并把提供数码管将要显示的值,我这里使用三个计数器分别上面提到的三个计数器计数,以得到相应的时间。计数器内部设有进位判断逻辑,限制s、min的值在59内,百分之一秒的值在99内

 

计数器ASM图

3.2.2 时序测试

计数器时序图

 

可见其工作正常,每一个使能信号到达时,对于时钟计数器计数加1.

3.2.3 计数器代码

module display_number(

input wire clock,

input wire reset,

input wire stop,

input wire en_ms,

input wire up,

output reg [6:0] displayed_number

);

always @(posedge clock or posedge reset or posedge stop)

begin

if (reset)

displayed_number <= 0;  //计数器清零

else if (stop)

displayed_number <= displayed_number; //计数器保持

else

begin

if (en_ms) begin  //判断是否为百分之一秒计数,控制其值在100以下

case(up)

1'b1:

if(displayed_number>=99)

displayed_number <= 0;

else displayed_number <= displayed_number + 1'b1; //向上计数

1'b0:if(displayed_number<=0)

displayed_number <= 99;

else displayed_number <= displayed_number - 1'b1; //向下计数

default:displayed_number <= displayed_number;

endcase

end

else //若不是百分之一秒计数,控制值在60以下

case(up)

1'b1:

if(displayed_number>=59)

displayed_number <= 0;

else displayed_number <= displayed_number + 1'b1;//向上计数

1'b0:if(displayed_number<=0)

displayed_number <= 59;

else displayed_number <= displayed_number - 1'b1;//向下计数

default:displayed_number <= displayed_number;

endcase

end

end

endmodule

3.3 位选模块

3.3.1动态扫描原理介绍

动态扫描显示利用了时分原理和人的视觉暂留现象。例如,4位扫描数码显示.器将时间划分为4个扫描周期: 周期1一>周期2一>周期3一>周期4,每个周期只选通一位数据。在周期1显示第1个数码,周期2显示第2个数码管。在扫描四个周期后,又重新按顺序循环。如果扫描的速度足够快,人的感觉就象4个数码同时显示。

3.3.2模块设计

本项目数码管有四个,所以我定义了一个两位宽的位选信号LED_activating_counter来进行位选,这个位选模块的原理就是利用寄存器refresh的长度进行计时,这里refresh为18位寄存器,每1/100MHz*2^18次方刷新一次,即2.6ms刷新一次

3.3.3模块代码

always @(posedge clock_100Mhz or posedge reset) //refresh为18位寄存器

begin

if(reset==1)

refresh_counter <= 0;

else

refresh_counter <= refresh_counter + 1;

end

assign LED_activating_counter = refresh_counter[17:16];//每1/100MHz*2^18次方刷新一次,即2.6ms刷新一次

3.4取值模块

3.4.1取值原理介绍

因为数据为整型存储,将十位数除以10便能得到10位,以10取余便能得到个位,以27为例,除以10整数剩2,取出十位,以10取余得到7,取出个位

3.4.2 模块设计

首先利用选择器将需要显示的时间的计数器值传入取值模块中,然后取值模块通过取余和除10的方法获得计数值的个位和十位,然后将其值存到LED_BCD寄存器中,为后续的显示电路提供BCD码。

取值模块AMS图

3.4.3时序测试:

模块时序图

可看出段选信号变换正常。

(注:由于测试需要,这里测试时间只给了1ms,而位选信号变换是2.6ms,所以这里位选值为0是正常的)

多路选择器代码

always @(posedge clock_100Mhz)

begin

if(choice==1)     //choice为1就数码管低两位显示百分之一秒,高两位显示秒。

begin temp_display[7:0]<= displayed_number_10ms;

temp_display[15:8]<=displayed_number_s;end

Else  //否则数码管低两位显示秒,高两位显示分钟。

begin temp_display[7:0]<= displayed_number_s;

temp_display[15:8]<=displayed_number_min;end

End

取值模块代码

module display(

input wire [1:0] LED_activating_counter,

input wire [15:0] displayed_number,

output reg [3:0] Anode_Activate,

output reg [6:0] LED_BCD

);

always @(*)

begin

case (LED_activating_counter)

2'b00: begin

Anode_Activate = 4'b0111;

// 数码管1亮

LED_BCD = displayed_number[15:8]/10;

// 取高八位的十位

end

2'b01: begin

Anode_Activate = 4'b1011;

// 数码管2亮

LED_BCD = displayed_number[15:8]%10;

// 取高八位的个位

end

2'b10: begin

Anode_Activate = 4'b1101;

// 数码管3亮

LED_BCD = displayed_number[7:0]/10;

// 取低八位的十位

end

2'b11: begin

Anode_Activate = 4'b1110;

// 数码管4亮

LED_BCD = displayed_number[7:0]%10;

// 取低八位的个位   

end

endcase

end

endmodule

3.5数码管显示模块

3.5.1七段数码管显示原理

数码管是一种半导体发光器件,其基本单元是发光二极管。数码管按段数分为七段数码管和八段数码管,八段数码管比七段数码管多一个发光二极管单元(多一个小数点显示);按发光二极管单元连接方式分为共阳极数码管和共阴极数码管。共阳数码管是指将所有发光二极管的阳极接到一起形成公共阳极(COM)的数码管。共阳数码管在应用时应将公共极COM接到+5V,当某一字段发光二极管的阴极为低电平时,相应字段就点亮。当某一字段的阴极为高电平时,相应字段就不亮。共阴数码管是指将所有发光二极管的阴极接到一起形成公共阴极(COM)的数码管。共阴数码管在应用时应将公共极COM接到地线GND上,当某一字段发光二极管的阳极为高电平时,相应字段就点亮。当某一字段的阳极为低电平时,相应字段就不亮。[2]

四位数码管引脚图

 

 

BCD-七段显示译码器

实验中运用到的数码管为八段共阳极数码管,当小数点对应的发光二极管为高电平时,小数点不显示。

3.5.2 七段数码管显示代码:

always @(*)

begin

case (LED_BCD_change)

4'b0000: LED_out = 7'b0000001; // "0"     

4'b0001: LED_out = 7'b1001111; // "1"

4'b0010: LED_out = 7'b0010010; // "2"

4'b0011: LED_out = 7'b0000110; // "3"

4'b0100: LED_out = 7'b1001100; // "4"

4'b0101: LED_out = 7'b0100100; // "5"

4'b0110: LED_out = 7'b0100000; // "6"

4'b0111: LED_out = 7'b0001111; // "7"

4'b1000: LED_out = 7'b0000000; // "8"     

4'b1001: LED_out = 7'b0000100; // "9"

default: LED_out = 7'b0000001; // "0"

endcase

end
  1. 总体项目测试

4.1测试代码

`timescale 1ns/1ns

module Seven_segment_LED_Display_Controller_Test();

reg clock_100Mhz;

reg reset;

reg stop;

reg [1:0] choice;

reg up_down_choice;

wire [3:0] Anode_Activate;

wire [6:0] LED_out;

Seven_segment_LED_Display_Controller dut (

.clock_100Mhz(clock_100Mhz),

.reset(reset),

.stop(stop),

.choice(choice),

.up_down_choice(up_down_choice),

.Anode_Activate(Anode_Activate),

.LED_out(LED_out)

);



// Testbench code

initial begin

// Initialize inputs

clock_100Mhz = 0;

reset = 1;

stop = 0;

choice = 0;

up_down_choice=1;

#10 reset=0;// Test with 10ms time scale

#100000 up_down_choice=0;

#100010 reset=1;

#100020 reset=0;

#100030 stop=1;

#100040 stop=0;

//up_down_choice = 0;

end

always #10 clock_100Mhz=~clock_100Mhz;

endmodule

4.2时序图

 

可看出其工作输出正常

  1. 上板测试
    1. 约束文件
//下排按键左起第一个控制reset信号,第二个控制stop信号,第三个模式切换,第四个向上向下计数模式切换

# Clock signal

set_property PACKAGE_PIN W5 [get_ports clock_100Mhz]       

set_property IOSTANDARD LVCMOS33 [get_ports clock_100Mhz]

set_property PACKAGE_PIN R2 [get_ports reset]     

set_property IOSTANDARD LVCMOS33 [get_ports reset]

set_property PACKAGE_PIN T1 [get_ports stop]     

set_property IOSTANDARD LVCMOS33 [get_ports stop]

set_property PACKAGE_PIN U1 [get_ports choice]     

set_property IOSTANDARD LVCMOS33 [get_ports choice]

set_property PACKAGE_PIN W2 [get_ports up_down_choice]     

set_property IOSTANDARD LVCMOS33 [get_ports up_down_choice]



#seven-segment LED display

set_property PACKAGE_PIN W7 [get_ports {LED_out[6]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {LED_out[6]}]

set_property PACKAGE_PIN W6 [get_ports {LED_out[5]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {LED_out[5]}]

set_property PACKAGE_PIN U8 [get_ports {LED_out[4]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {LED_out[4]}]

set_property PACKAGE_PIN V8 [get_ports {LED_out[3]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {LED_out[3]}]

set_property PACKAGE_PIN U5 [get_ports {LED_out[2]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {LED_out[2]}]

set_property PACKAGE_PIN V5 [get_ports {LED_out[1]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {LED_out[1]}]

set_property PACKAGE_PIN U7 [get_ports {LED_out[0]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {LED_out[0]}]

set_property PACKAGE_PIN U2 [get_ports {Anode_Activate[0]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {Anode_Activate[0]}]

set_property PACKAGE_PIN U4 [get_ports {Anode_Activate[1]}]                    

set_property IOSTANDARD LVCMOS33 [get_ports {Anode_Activate[1]}]

set_property PACKAGE_PIN V4 [get_ports {Anode_Activate[2]}]               

set_property IOSTANDARD LVCMOS33 [get_ports {Anode_Activate[2]}]

set_property PACKAGE_PIN W4 [get_ports {Anode_Activate[3]}]          

set_property IOSTANDARD LVCMOS33 [get_ports {Anode_Activate[3]}]

    1. 现象

 

现象正确,功能正常,可以实现清零,暂停,计时模式切换,上下计数模式切换

实验总结

本项目使用时钟使能信号对不同时间需求进行计数,效果很好,实现了预期功能,计时功能与手机计时器对比误差在可接受范围内,实验成功。

实验心得:这次实验是第一次进行这样大规模的硬件语言代码编写,遇到了很多问题,也对项目建设有了更深的理解。

  1. 体会到了寄存器位宽对工程的影响,有时一个位宽设置错误会导致很多预料不到的错误,需要花很多时间取排查,以后在位宽设置时一定会注意。
  2. 对阻塞赋值和非阻塞赋值有了更深入的理解,赋值方式错了真的会造成很多意外的bug。
  3. 设计时序逻辑时一定要清晰,不然实现不了功能也找不到原因。
  4. 分模块化编程可以使代码编写更加直观,也使逻辑更加清晰,同时增加了代码的复用性,减少了重复性工作。

参考文献

  1. verilog的时钟分频与时钟使能https://www.elecfans.com/d/1972527.html
  2. 七段数码管 知乎盐选 | Arduino 编程 (zhihu.com)

现在小孩子玩的最多的玩具就是手机了,不禁感慨,我们小时候都是咋过来的,有个游戏机,可以玩个贪吃蛇、俄罗斯方块就不错了。可以自己设计个贪吃蛇游戏玩玩,重温童年的经典,也让现在小孩子知道,珍惜当下美好的生活。 功能实现说明: 此游戏较为简单,没有设置多余障碍物,只设置了四周的墙壁,贪食蛇所吃的苹果随机刷新,当蛇装上墙壁或者自己的身体,游戏结束。 此游戏使用五个按键,利用Basys3上的按钮,四个方向键,一个重新开始游戏按钮,一个操作开关。使用7位数码管进行计分,每吃到一个苹果分数+1。使用VGA显示游戏界面。 DIY动手指南: Step1:材料准备 硬件: Basys3开发板 VGA连接线及VGA显示器一台 软件平台:Vivado2016.4 Step2:系统框架 系统主要由6部分组成,分别是按键输入模块、控制模块、数据路径模块、随机生成 模块、VGA显示模块和数码管显示模块。 Step3:程序设计 上图是本程序的RTL级视图。 下面我们对于各个模块进行分析。 1.键盘扫描 我们一共设置了五个按键,分别执行up,down,left,right,restart这五个功能。 以up_key_press为例,介绍消抖的算法。 在每个时钟高电平时并行执行以下两条语句 up_key_press<=0; up_key_last<=0; 当有按键按下时,每100ms(cnt=5_0000) last=up,last输出比up滞后一个周期,若up_key_last==0&&up==1,则说明按键按下,press输出置1。 2.控制模块 当打开开始开关并按下任意一个方向键时,游戏开始,当游戏结束时闪烁5下,重新开始游戏。 3.数据路径模块 cube_x,cube_y表示一整条蛇身体各节的格坐标。is_exist有16位,即蛇体最长为16*1格,每一位对应一个格,1为该格显示,0则不显示(图中虚框)。每吃下一个苹果蛇长度增加1,相应exist位置变为1。 蛇每次移动,对应的cube[x] = cube[x+1],即后一位的身子会移动到前一位的位置,蛇头根据按下的按钮来判断是撞到了墙,还是撞到了身子,还是移动到了下一个位置。 当蛇头和苹果重合时,生成一个增加身长的信号,并在计数器上+1。 4.随机生成模块 采用伪随机数的产生方法,比较笨拙。 每个时钟周期random_num都在变,而我们吃下苹果的时刻却因走法、按键的时间等有所不同,所以不同时刻吃下苹果后下一个苹果出现的地方近似随机~ 5.VGA显示模块 使用640*480分辨率,需要分频为25MHz的时钟信号。 6.数码管显示模块 当有增长身长信号产生时,分数也对应增加,数码管动态扫描显示分数。 Step4:演示
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值