基于VHDL语言的数字电子钟设计

Posted Mount256

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于VHDL语言的数字电子钟设计相关的知识,希望对你有一定的参考价值。

这是在2021年10月底完成的一次VHDL课程设计,全程自己设计组装完成,现作为记录存档发布,大家也可以借鉴本文来完成自己的课程设计。(建议使用电脑阅读,本文有修改)

基于VHDL语言的数字电子钟设计

【内容摘要】 数字电子钟是一种用数字显示秒、分、时的记时装置,该数字电子钟的功能和特点有:时钟源产生1Hz时钟脉冲,用以提供“秒”的计数;设计两个六十进制的计数器对“分”、“秒”信号计数,二十四进制计数器对“时”信号计数;通过“时”、“分”校正电路进行时间的校正;通过组合逻辑电路实现报时功能和闹铃功能,由蜂鸣器发出提醒。本次课程设计使用Quartus II软件进行设计,所采用的硬件语言是VHDL

【关键词】电子钟 计数器 数字电子技术 EDA VHDL

1 课程设计和方案设计

1.1 EDA课程设计的目的与意义

电子技术是一门实践性很强的课程,加强工程训练,特别是技能的培养,对于培养工程人员的素质和能力具有十分重要的作用。在电子信息类本科教学中,电子技术课程设计是一个重要的实践环节,它包括选择课题、电子电路设计、组装、调试和编写总结报告等实践内容。

通过 EDA 课程设计,要实现以下两个目标:第一,让学生初步掌握 EDA 电子线路的试验、设计方法。即学生根据设计要求和性能参数,查阅文献资料,收集、分析类似电路的性能,并通过组装调试等实践活动,使电路达到性能指标;第二,课程设计为后续的毕业设计打好基础。毕业设计是系统的工程设计实践,而课程设计的着眼点是让学生开始从理论学习的轨道上逐渐引向实际运用,从已学过的定性分析、定量计算的方法,逐步掌握工程设计的步骤和方法,了解科学实验的程序和实施方法,同时,课程设计报告的书写,为今后从事技术工作撰写科技报告和技术资料打下基础。

1.2 本次课程设计的学习目标

1)掌握电子钟的设计、组装与调试方法。

2)熟悉Quartus II软件的使用方法。

3)提高动手能力,学会将理论知识与实践相结合,充分发挥个人与团队协作的能力。

1.3 设计任务与要求

1)时钟显示功能,能够以十进制显示“时”、“分”、“秒”。

2)具有校准时、分、秒的功能。

3)整点自动报时,在整点时,便自动发出鸣叫。

4)具有闹铃功能,可设置闹铃时间,到时便发出鸣叫。

2 基本实现思路及系统框图

2.1 基本实现思路

数字电子钟的计时功能由三个计数器实现,其中分、秒计时分别为60进制计数,小时计时为24进制计数器,且采用进位级联方式连接。

数字电子钟的校时功能由三个按键实现,秒校时是按下置零,分校时和时校时是通过长按按键以加快时钟频率来实现。

数字电子钟的整点报时功能需要检测时间是否到达整点,若是,则扬声器工作,否则,扬声器不工作。

数字电子钟的闹铃功能需要三个按键和另外三个计数器实现,即分、秒计时分别为60进制计数,小时计时为24进制计数器,但并不采用级联方式连接。三个按键连接计数器,设置时间是通过长按按键来实现。闹铃的检测实现思路与整点检测的类似。

数字电子钟的数码管需要显示正常时间或闹铃时间,为此可添加一个按键用以切换正常界面和闹铃界面。按键可作为二选一选择器的位选信号,数码管通过选择器来决定显示哪个时间,同时又不影响时间的计时。

2.2 电路整体控制逻辑

图2-1 数字电子钟整体逻辑框图

电路的整体逻辑框图如上图所示,这里需要做些说明:

一个具有计时、校时、报时、显示等基本功能的数字钟主要由分频器、计数器、显示器、校时、报时、闹铃等模块组成。
原始时钟信号经过分频器得到秒脉冲,秒脉冲送入计数器计数,计数结果通过“时”、“分”、“秒”译码器译码,并通过显示器显示时间。

按键A用来切换界面,可切换正常界面和闹铃界面。按键B、C、D用来调整时、分、秒,当位于正常界面时是用于校时的,当位于闹铃界面时是用于调整闹铃的。通过一个组合逻辑模块,便可以分辨按键在不同界面下的不同功能(限于篇幅,该模块在图中并未画出)。因此,这三个按键具有复用的功能。

报时模块接收来自上面三个正常时间计数器的输出,闹铃模块接收来自下面三个闹铃时间计数器的输出,它们均会持续检测是否满足响铃要求。

3 模块电路设计和仿真

3.1 分频器模块

分频器的作用是将原始时钟频率分为两种频率,前者供给正常时钟的时钟源,后者供给设置闹铃和校时的时钟源。

3.1.1 代码实现

实现原理是每隔N个原始时钟周期,输出的时钟信号将翻转一次,这样就得到新的时钟频率。完整代码如下:

【代码3-1 分频器】
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

-- 分频器

entity diver is
	port(
		clk:	in std_logic;	-- 原始时钟频率
		clk0:	out std_logic;	-- 正常计时分频
		clk1:	out std_logic	-- 设置闹铃和校时分频
	);
end diver;
 
architecture a of diver is
	signal m_clk0: std_logic;
	signal m_clk1: std_logic;
	constant c_cnt_0: integer:= 10;
	constant c_cnt_1: integer:= 50;
begin
	clk0 <= m_clk0;
	clk1 <= m_clk1;
	process(clk)
		variable cnt_0: integer range 0 to c_cnt_0;	
		variable cnt_1: integer range 0 to c_cnt_1; 	
	begin
		if (clk'event and clk = '1') then
			cnt_0:= cnt_0 + 1;
			cnt_1:= cnt_1 + 1;
			if (cnt_0 = c_cnt_0) then
				m_clk0 <= not m_clk0;
				cnt_0:= 0;
			end if;
			if (cnt_1 = c_cnt_1) then
				m_clk1 <= not m_clk1;
				cnt_1:= 0;
			end if;
		end if;
	end process;
end a;

3.1.2 仿真结果

使用软件自带仿真结果如下:

图3-1 分频器模块的仿真结果

可见,原始时钟在经过分频后产生了两种不同的时钟频率信号,说明达到预期效果,仿真成功。不过分频因子有待后续调试阶段作进一步调整。

3.2 计数器模块

时钟和闹铃的计数器,均由一个24进制计数器和两个60进制计数器组成。因此,时钟和闹铃计数器放在本节内容一并说明。

3.2.1 代码实现

计数器为含异步清零和同步使能的加法计数器,输入端有三个:输入时钟信号clk、异步清零端rst、同步使能端en;输出端为个位和十位的二进制数q0q1,以及进位信号cout

以下是秒/分计数器的完整代码:

【代码3-2 秒/分计数器】
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

-- 秒计时器

entity count_sec is
	port(
		clk, rst, en: in std_logic;				
		q0, q1: buffer std_logic_vector(3 downto 0); 
		cout: out std_logic
	);
end count_sec;

architecture a of count_sec is
	signal m_clk: std_logic;
	signal num0, num1: std_logic_vector(3 downto 0);
begin
	cout <= '1' when (num0 = "1001" and num1 = "0101" and en = '1') else '0';
	process (rst, clk)
	begin
		m_clk <= clk;
		if (rst = '0') then
			num0 <= "0000";
			num1 <= "0000";
		elsif (m_clk'event and m_clk = '0') then
			if (en = '1') then
				if (num0 = "1001") then
					num0 <= "0000";
					if (num1 = "0101") then
						num1 <= "0000";
					else
						num1 <= num1 + 1;
					end if;
				else
					num0 <= num0 + 1;
				end if;
			end if;
		end if;
	end process;
	q0 <= num0;
	q1 <= num1;
end a;

以下是时计数器的完整代码:

【代码3-3 时计数器】
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

-- 时计时器

entity count_hour is
	port(
		clk, rst, en: in std_logic;				
		q0, q1: buffer std_logic_vector(3 downto 0); 
		cout: out std_logic
	);
end count_hour;

architecture a of count_hour is
	signal m_clk: std_logic;
	signal num0, num1: std_logic_vector(3 downto 0);
begin
	cout <= '1' when (num0 = "0011" and num1 = "0010" and en = '1') else '0';
	process (rst, clk)
	begin
		m_clk <= clk;
		if (rst = '1') then
			num0 <= "0000";
			num1 <= "0000";
		elsif (m_clk'event and m_clk = '0') then
			if (en = '1') then
				if ((num0 = "0011") and (num1 = "0010")) then
					num0 <= "0000";
					num1 <= "0000";
				elsif (num0 = "1001") then
					num0 <= "0000";
					num1 <= num1 + 1;
				else
					num0 <= num0 + 1;
				end if;
			end if;
		end if;
	end process;
	q0 <= num0;
	q1 <= num1;
end a;

3.2.2 仿真结果

下面是秒计数器和分计数器(60进制计数器)的仿真结果:

图3-2 秒计数器和分计数器的仿真结果

下面是时计数器(24进制计数器)的仿真结果:

图3-3 时计数器的仿真结果

可见,两者均实现了预期效果,仿真成功。

3.3 校时模块

校时功能的具体实现是,当长按下调整按键时,时钟频率(即计数频率)加快,待时钟跳转到想要的时间时,松开按键,这样就完成了一次校时。

校时功能的实现原理十分巧妙,是通过一个二选一选择器来实现的,位选信号s连接到调整按键,两路信号ab分别是来自低级计数器的进位信号和较快频率的时钟信号,而输出端则连到计数器的进位信号。

这样,当不按按键时,选择器输出a信号,此时计数器以正常频率计数;当按下按键时,选择器输出b信号,此时计数器以较快频率计数,以便使用者进行校时。

3.3.1 代码实现

校时模块的本质即是二选一选择器,完整代码如下:

【代码3-4 校时模块】

LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;

ENTITY mux21a IS
	PORT ( a, b, s: IN  STD_LOGIC;
			y : OUT STD_LOGIC  );
END ENTITY mux21a;

ARCHITECTURE one OF mux21a IS
BEGIN
	PROCESS (a,b,s)
	BEGIN
		IF s = '0'  THEN  y <= a ; ELSE y <= b ;
		END IF;
	END PROCESS;
END ARCHITECTURE one ;

3.3.2 仿真结果

使用软件自带仿真结果如下:

图3-4 校时模块的仿真结果

可见其实现了预期效果,仿真成功。

3.4 报时模块

报时模块的具体实现是,当检测到分达到59,秒达到53、55、57、59时,蜂鸣器各响一次。

3.4.1 代码实现

输入端是来自时钟计数器的分和秒,输出端连接到蜂鸣器。代码的实现只需检测是否符合分达到59,秒达到53、55、57、59的情况,若是,则蜂鸣器信号为高电平,否则为低电平。完整代码如下:

【代码3-5 报时模块】
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

entity baoshi is
	port ( min0 ,min1 , sec0, sec1 :in std_logic_vector ( 3 downto 0 );
			speak: out std_logic );
end baoshi;

architecture cml of baoshi is
begin
	speak <= '1'	-- min1=5, min0=9, sec1=5  and  sec0=3, sec0=5, sec0=7, sec0=9
			when (min1="0101" and min0 ="1001" and sec1="0101")and ( sec0="0011" or sec0 ="0101" or sec0 ="0111" or sec0="1001") 
	else '0';
end cml;

3.4.2 仿真结果

使用软件自带仿真结果如下:

图3-5 报时模块的仿真结果

可见其实现了预期效果,仿真成功。

3.5 模式切换模块(按键复用模块)

之前已经提到,我们所设计的系统中有三个按键是用来调整时、分、秒的,由于系统有两种模式,若每种模式均配备三个按键就显得有些冗余了(这样就一共需要六个按键了)。

为了减少按键的个数,我们希望这三个按键可以在不同模式下设置正常时间和闹铃时间,此所谓“按键复用”。而且这样做还有一个好处是,不必担心在正常显示时间的状态下误触了设置闹铃的按键。为了实现以上功能,需要设计一个逻辑电路用来辨别是调整正常时间还是调整闹铃时间。

3.5.1 代码实现

输入端是来自模式切换按键key和调整时间按键的信号s,输出端有两个,一个连接到正常时间的部分s1,另一个连接到闹铃时间的部分s2。实现思路是:当s为高电平时,s2通过而s1屏蔽,此时按下key则是调整s2;当s为低电平时,s1通过而s2屏蔽,此时按下key则是调整s1。通过逻辑表,可以很容易得出代码如下:

【代码3-6 模式切换模块】
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

entity switch is
	port(
		key, s: in std_logic;
		s1, s2: out std_logic
	);
end switch;

architecture a of switch is
begin
	s1 <= key and (not s);
	s2 <= key and s;
end a;

3.5.2 仿真结果

使用软件自带仿真结果如下:

图3-6 模式切换模块的仿真结果

可见其实现了预期效果,仿真成功。

3.6 模式状态维持模块

由于我们希望按下切换按键时,模式状态就会切换且维持不变,所以我们使用了T触发器来对按键信号进行处理。当来自按键的输入信号为高电平时,输出信号q就会翻转一次信号;当来自按键的输入信号为低电平时,输出信号q状态不变。

3.6.1 代码实现

输入端有时钟clk和信号t,输出端为信号q。当时钟的上升沿来临时,若t为高电平,则信号q翻转一次;否则,信号q维持不变。完整代码如下:

【代码3-7 模式状态维持模块】
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
Use IEEE.STD_LOGIC_ARITH.ALL;
Use IEEE.STD_LOGIC_UNSIGNED.ALL;

ENTITY trigger IS
	port (t, clk: in std_logic;
			q: out std_logic);
end entity;

architecture bhv of trigger is
	signal temp: std_logic;
begin
	process(clk, t)
	begin
		if clk'event and clk='1' then
			if t='1' then
				temp <= not temp;
			else 
				temp <= temp;
			end if;
		end if;
		q <= temp;
	end process;
end bhv;

3.6.2 仿真结果

使用软件自带仿真结果如下:

图3-7 模式状态维持模块的仿真结果

可见其实现了预期效果,仿真成功。

3.7 闹铃检测模块

闹铃检测模块的具体实现是,当检测到时、分、秒均达到预设时间时,蜂鸣器响一次。

3.7.1 代码实现

输入端分别是来自时钟的时、分计数器(min/hour)和来自闹铃的时、分计数器(amin/ahour),输出端则连接到蜂鸣器。当检测到时、分、秒均达到预设时间时,蜂鸣器响一次。完整代码如下:

【代码3-8 闹铃检测模块】
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;

entity alarm is
	port(
		--sec0, sec1: in std_logic_vector(3 downto 0); 
		min0, min1: in std_logic_vector(3 downto 0); 
		hour0, hour1: in std_logic_vector(3 downto 0); 
		--asec0, asec1: in std_logic_vector(3 downto 0); 
		amin0, amin1: in std_logic_vector(3 downto 0); 
		ahour0, ahour1: in std_logic_vector(3 downto 0); 
		speak: out std_logic
	);
end alarm;

architecture a of alarm is
begin
	speak <= '1' when(min0=amin0 and min1=amin1 
						and hour0=ahour0 and hour1=ahour1)
		else '0';
end a;

3.7.2 仿真结果

假设闹铃设置为23:57:00,现实时间从23:57:00过渡到23:58:00,使用软件自带仿真结果如下:

图3-8 闹铃检测模块的仿真结果

可见,当现实时间到了23:57:00时,speak为高电平,此时蜂鸣器工作;当现实时间到了23:58:00时,speak为低电平,此时蜂鸣器不工作,说明实现了预期效果,仿真成功。

3.8 数码管显示切换模块

该模块的作用是,通过二选一选择器,数码管可以切换显示正常时间和闹铃时间。

3.8.1 代码实现

这是一个二选一选择器,输入信号有三个,位选信号来自切换模式按键,其余两个为a和b,分别来自正常计数器和闹铃计数器的输出q0和q1。完整代码如下:

【代码3-9 数码管显示切换模块】
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;

ENTITY mmux21a IS
	PORT ( a, b: in std_LOGIC_vector (3 downto 0);
			s: IN  STD_LOGIC;
			y : OUT std_LOGIC_vector (3 downto 0)  
		);
END ENTITY mmux21a;

ARCHITECTURE one OF mmux21a IS
BEGIN
	PROCESS (a,b,s)
	BEGIN
		IF s = '0'  THEN  y <= a ; ELSE y <= b ;
		END IF;
	END PROCESS;
END ARCHITECTURE one ;

3.8.2 仿真结果

使用软件自带仿真结果如下:

图3-9 数码管显示切换模块的仿真结果

可见其实现了预期效果,仿真成功。

4 顶层电路设计、仿真和调试

4.1 顶层电路设计

在各个模块都设计完成、仿真通过、达到预期效果以后,在顶层文件将模块实例化,接着以一定的方式连接起来。如下代码定义了整个系统的输入和输出的端口:

【代码4-1 顶层电路输入输出端口定义】
entity clock_top_1 is
	port(
		clk: in std_LOGIC;  --时钟源
		key_sec, key_min, key_hour: in std_LOGIC;  --校时设置/闹铃设置按键
		sec0, sec1: buffer std_logic_vector(3 downto 0); --显示时
		min0, min1: buffer std_logic_vector(3 downto 0); --显示分
		hour0, hour1: buffer std_logic_vector(3 downto 0); --显示秒
		key_shift: in std_LOGIC; --切换功能
		speak: out std_LOGIC --整点响铃/闹铃响铃
	);
end clock_top_1;

4.1.1 时钟源部分

创建两个信号,分别提供正常时钟频率(normal_clk)和较快时钟频率(fast_clk,用于校时和设置闹铃)。摘录代码如下:

【代码4-2 分频器实例化】
signal normal_clk, fast_clk : std_LOGIC;

--------分频---------
U1: diver port map (clk=>clk, clk0=>normal_clk, clk1=>fast_clk);

4.1.2 正常时间计数部分

定义信号,用于数码管的显示信号(sec0_n, sec1_n, min0_n, min1_n, hour0_n, hour1_n)(但并不是真的显示,因为还要通过模式切换模块来判断显示哪个界面);定义两个进位信号(min_clk, hour_clk),用于提供下一级计数器的时钟;三个计数器采取级联方式,进位信号作为下一级计数器的时钟源。

注意,除了秒计数器的rst端外,其他计数器都接入了低电平(复位端为高电平有效),这是因为秒计数器的校时方式只有复位方式。摘录代码如下:

【代码4-3 正常时间计数部分实例化】
signal min_clk, hour_clk : std_LOGIC;
signal sec0_n, sec1_n, min0_n, min1_n, hour0_n, hour1_n: std_logic_vector(3 downto 0);

--------正常时间计数器---------
U2: count_sec port map (clk=>normal_clk, rst=>key_sec_n, en=>'1', q0=>sec0_n, q1=>sec1_n, cout=>sec_cout);
U3: count_min port map (clk=>min_clk, rst=>'0', en=>'1', q0=>min0_n, q1=>min1_n, cout=>min_cout);
U4: count_hour port map (clk=>hour_clk, rst=>'0', en=>'1', q0=>hour0_n, q1=>hour1_n);

4.1.3 校时部分

位选信号s连接到调整按键(key_min_nkey_hour_n),两路信号a和b分别是来自低级计数器的进位信号(sec_coutmin_cout)和较快频率的时钟信号(fast_clk),而输出端则连到计数器的进位信号(min_clkhour_clk)。摘录代码如下:

【代码4-4 校时部分实例化】
signal key_sec_n, key_min_n, key_hour_n: std_LOGIC;

--------校时---------
U5: mux21a port map (a=>sec_cout, b=>fast_clk, s=>key_min_n, y=>min_clk);
U6: mux21a port map (a=>min_cout, b=>fast_clk, s=>key_hour_n, y=>hour_clk);

4.1.4 闹铃设置部分

定义了三个按键信号(key_sec_a, key_min_a, key_hour_a),用于设置闹铃时间;定义了用于数码管的显示信号(min0_a, min1_a, hour0_a, hour1_a)。

该部分与正常时间计数部分的接法类似,所不同的是,三个计数器的rst端均为低电平,且en端均接了按键信号,在高电平状态下就会计数。摘录代码如下:

【代码4-5 闹铃设置部分实例化】
signal sec0_a, sec1_a, min0_a, min1_a, hour0_a, hour1_a: std_logic_vector(3 downto 0);
signal key_sec_a, key_min_a, key_hour_a: std_LOGIC;

--------闹钟计数器---------
U17: count_sec port map (clk=>fast_clk, rst=>'0', en=>key_sec_a, q0=>sec0_a, q1=>sec1_a);
U18: count_min port map (clk=>fast_clk, rst=>'0', en=>key_min_a, q0=>min0_a, q1=>min1_a);
U19: count_hour port map (clk=>fast_clk, rst=>'0', en=>key_hour_a, q0=>hour0_a, q1=>hour1_a);

4.1.5 模式切换部分

T触发器是对模式状态起保持作用,其实例化如下:

【代码4-6 T触发器实例化】
signal key_shift1: std_LOGIC;

--------模式状态保持--------
U21: trigger port map (clk=>clk, t=>key_shift, q=>key_shift1);

输入端是模式切换按键key(key_shift1)和调整时间按键的信号s(key_sec, key_min, key_hour),输出端有两个,一个连接到正常时间的部分s1(key_sec_n, key_min_n, key_hour_n),另一个连接到闹铃时间的部分s2(key_sec_a, key_min_a, key_hour_a)。摘录代码如下:

【代码4-7 模式切换部分实例化】
--------模式切换(三按键复用)---------
U8: switch port map (key=>key_sec, s=>key_shift1, s1=>key_sec_n,s2=>key_sec_a);
U9: switch port map (key=>key_min, s=>key_shift1, s1=>key_min_n,s2=>key_min_a);
U10: switch port map (key=>key_hour, s=>key_shift1, s1=>key_hour_n, s2=>key_hour_a);

4.1.6 数码管显示切换部分

该部分对闹铃和正常时间部分的时、分、秒输出进行分辨后,选择显示两者之一的输出。摘录代码如下:

【代码4-8 数码管显示切换部分实例化】
--------数码管显示切换(二选一)---------
U11: mmux21a port map (s=>key_shift1, a=>sec0_n, b=>sec0_a, y=>sec0);
U12: mmux21a port map (s=>key_shift1, a=>sec1_n, b=>sec1_a, y=>sec1);
U13: mmux21a port map (s=>key_shift1, a=>min0_n, b=>min0_a, y=>min0);
U14: mmux21a port map (s=>key_shift1, a=>min1_n, b=>min1_a, y=>min1);
U15: mmux21a port map (s=>key_shift1, a=>hour0_n, b=>hour0_a, y=>hour0);
U16: mmux21a port map (s=>key_shift1, a=>hour1_n, b=>hour1_a, y=>hour1);

4.1.7 整点检测和闹铃检测部分

由于蜂鸣器的输入来源有两个,一是来自整点报时,二是来自闹铃,因此为解决两者冲突的矛盾,引入了或门。摘录代码如下:

【代码4-9 扬声器选择代码】
signal speak_a, speak_zd: std_LOGIC; --闹铃(a)/整点(zd)

--------扬声器--------
speak <= speak_a or speak_zd;

整点检测和闹铃检测的实例化代码如下:

【代码4-10 整点检测和闹铃检测的实例化】
--------整点检测---------
U7: baoshi port map (min0=>min0, min1=>min1, sec0=>sec0, sec1=>sec1, speak=>speak_zd);
--------闹铃检测---------
U20: alarm port map (min0=>min0_n, min1=>min1_n, hour0=>hour0_n, hour1=>hour1_n,
	amin0=>min0_a, amin1=>min1_a, ahour0=>hour0_a, ahour1=>hour1_a,
	speak=>speak_a );

至此,已完成所有模块的实例化和连接。

4.2 顶层电路仿真

使用软件仿真结果如下:

图4-1 顶层电路的仿真结果

从左到右:

(1)在图中的第一部分测试的是校时模块。在正常时间界面显示时,按下时校时按键和分校时按键时,分、时的计数频率加快了。松开按键后计数器正常计数。同时,刚开始时由于是整点,所以speak为高电平。

(2)在图中的第二部分测试的是闹铃模块。按下切换按键(key_shift)然后松开,数码管会切换显示闹铃的时间,按下时校时按键和分校时按键时,分、时计数器开始计数。松开校时按键,计数器停止计数。

(3)在图中的第三部分,再次按下切换按键,发现数码管又切换为正常时间,而且之前调闹铃的时候没有影响到正常时间的计数。

(4)在图中的最后一部分,再次按下切换按键进入闹铃模式,发现数码管显示的是之前已调好的闹铃时间。

总体来看,仿真结果基本符合预期。

4.3 顶层电路调试

4.3.1 管脚分配

引脚锁定如下图:

图4-2 引脚分配图

4.3.2 调试结果

将程序下载、进行硬件测试,下图是调试过程中的实验现象:

图4-3 硬件调试过程中的照片

通过一系列的测试,能正常计时、调整时间、报时响铃、以及闹铃响铃,实验现象基本符合预期。

5 总结

5.1 缺陷与不足

这次的课设存在以下几个缺陷与不足:

(1)秒表功能未实现,数字钟的功能未完善;

(2)调整时间的设计方法还不太好,我们的方案是按下按键,计数频率就会加快,而后来经过老师提醒,其实还可以将按键直接接在下一级的计数器上,这样按下一次按键就能调整时间,显然这个方案更加简洁;

(3)闹铃的响声到点只有一次,且声音很小。

而且我们没想到的是(或者说没有注意到),实验室板子的按键原本就有状态保持的功能,而我们却为了实现这个功能,多此一举地专门设计了一个模块,反而影响了最后的实验效果。可惜碍于时间关系就无法使其臻于完美了。总之,虽然主要功能实现了,但这次课设设计我们还不是特别满意。

5.2 心得体会

通过此次数字模拟电路课程设计,从最开始的选题,到最后的仿真结束,我们更深刻地掌握了数字钟设计的原理与过程。尽管课题所给出的各个题目相对简单,但一开始难免有些无从下手,毕竟课程设计不同于实验课,代码与逻辑图都需要我们自己思考并动手设计实践的。平常我们所学的基础课程与实验,都在此次实践当中将理论与实践相结合,它不但能巩固我们已经所学的理论知识,而且能提高我们的电子电路的设计水平,还能加强我们综合分析问题与解决问题的能力,进一步培养我们的实验技能和动手能力,启发我们的创新意识和创新思维。

我们在着手设计时,着重逻辑,将整个系统根据不同功能化分为多个模块,之后逐个攻破,最后再将其整合,这也是从此次课设所学习到的一种重要方法。所以之后静下心来,通过查找课本与课外资料,再加上指导老师所给的提示内容,我们仔细分析题目并思考后,大家互相协助,各抒己见,很快地就完成了课题。

经过这几周的 VHDL 课设,我们最终做出了数字钟电路,虽然过程比较艰辛,但这其中的兴奋自然是无法用言语表达的。在整个电路设计的过程中,我逐渐加强了对数字电路和 EDA 设计的了解和运用能力,对课本知识以及以前学过的知识有了一个更好的总结与理解。通过这次设计,我深刻体会到理论与实践的区别,了解了理论知识和实践相结合的重要意义,只有当我们亲自动手,将理论付之于实现,才能够将理论知识内化成自己的一部分。在这个过程中,我的动手能力和理论知识的掌握程度有了很大的提高,这将为自己今后的学习和工作打好了坚实的基础。最后,感谢同学和老师的点拨和帮助!

附录

模块清单

模块名称功能个数
diver分频器1
count_hour时计数器2
count_min分计数器2
count_hour时计数器2
mux21a二选一选择器(校时模块用)2
mmux21a二选一选择器(数码管显示用)6
baoshi整点检测1
alarm闹铃检测1
switch模式切换3
triggerT触发器1

模块总数:21

参考文献

[1] 阎石主编.数字电子技术基础[M].6版.北京:高等教育出版社,2016.

[2] 阎石,王红编.数字电子技术基础学习辅导与习题解答[M].6版.北京:高等教育出版社,2016.

[3] 潘松,黄继业编.EDA技术实用教程:VHDL版[M].6版.北京:科学出版社,2018.

[4] 江国强编.现代数字电路与系统设计:VHDL版[M]. 6版.北京:电子工业出版社,2018.

[5] 刘炳海编.从零开始学电子电路设计[M].北京:化学工业出版社,2019.

以上是关于基于VHDL语言的数字电子钟设计的主要内容,如果未能解决你的问题,请参考以下文章

电梯控制系统基于VHDL语言和状态机实现的电梯控制系统的设计,使用了状态机

基于FPGA的AD7303/ADCS7476模拟数字转换VHDL开发

基于FPGA的频率计设计 毕业论文。。请高手帮忙,有重谢。。

基于80C51单片机,编写电子时钟,要求有电路图、PCB图和代码(C语言或者VB)

FGPAVHDL实验

VHDL