FPGA的大作业我选择了交通灯控制系统的设计,此课程只有2个学分,因此只需要在相应软件仿真出结果即可。以下是我的设计报告,当时写的匆忙,没有对代码进行优化改进,但仿真结果是正确的,可以给大家提供一下思路。
一、任务分析
分析设计要求可以看出,本系统设计数码管动态显示、译码器、计数器分频方面的知识。
1.输入和输出
-
输入
clk:时钟输入(50MHz)。
special:特殊情况发生输入。
-
输出
l1、l2:数码管位选。
display_1、display_2:数码管段选。
out_1、out_2:红绿灯选择。
2.多进程
-
reg进程:分频计数
-
红绿灯的闪烁和数码管显示数字的变化都需要计时,计时单位为s(秒)时,设计最简单。因此,我将50MHz频率的clk驱动分频,这部分运用了计数器知识。
-
从题目中看出,红绿灯50s为一个循环,我使用信号counter来记录时间,时钟每过1秒钟,counter就加1,counter的值不能超过50。
-
-
com进程:红绿灯显示
-
通过counter的值就可以判断出东西方和南北方的灯为什么颜色。我使用自定义枚举类型来表示红绿灯的三种状态。
-
在判断红绿灯状态的同时,还应该计算出数码管应该显示的数字为多少。
-
-
smg进程:数码管显示
- 将com进程中计算出的数码管数字在数码管上显示出来。计算数码管十位上的显示数字时用10取模,计算数码管个位上的显示数字时用10取余。
- 由于数码管位选仅有两个,我使用一个变量的0和1分别表示数码管的十位显示和个位显示。
- 数码管的段选运用了译码器相关知识。
3.特殊情况
-
当bit类型信号special为’1’时,进入特殊情况处理。
-
在com进程中,只有当special为’0’时,counter才会加1。目的是在特殊情况时,数码管的数字不会发生变化。
-
我额外定义了一个bit类型信号change,时钟每过一秒钟,它就会取反。使用它的目的是在特殊情况时,实现数码管闪烁。
4.注意
需要格外注意的是,reg、com、smg三个进程的敏感信号。
reg:process(clk,special)
com:process(counter,special)
红绿灯显示在每隔1秒钟判断或special特殊情况到来时判断即可
reg:process(clk,change,special)
数码管为动态扫描,只有速度够快,才能使人眼无法察觉数码管的交替显示,因此需要clk时钟信号作为敏感信号。
二、quartus ii 仿真
1.VHDL语言描述
library ieee;
use ieee.std_logic_1164.all;
entity fisrt is
--两组数码管(段选+位选),两组灯
port(clk:in std_logic;
special:in bit;
--数码管段选
l1,l2:out std_logic_vector(1 downto 0);
--数码管位选
display_1,display_2:out std_logic_vector(6 downto 0);
--红绿灯
out_1,out_2:out std_logic_vector(2 downto 0));
end fisrt;
architecture behav of fisrt is
type FSM is (s0,s1,s2);
signal LED_1,LED_2:FSM;--自定义枚举类型
signal counter:integer range 0 to 49;--共50s
signal smg_1,smg_2:integer range 0 to 24;--数码管显示数字
signal change:bit;--每一秒钟用于变化的量
begin
--进程reg用于分频
reg:process(counter,special)
variable clk1:integer range 0 to 9999;--50MHz分频为1Hz
variable clk2:integer range 0 to 4999;
begin
if clk'event and clk='1' then
if clk1=9999 then
clk1:=0;
if clk2=4999 then
clk2:=0;
if special='0' then
change<=not change;
if counter=49 then
counter<=0;
else counter<=counter+1;
end if;
end if;
else clk2:=clk2+1;
end if;
else clk1:=clk1+1;
end if;
end if;
end process reg;
--进程com用于红绿灯显示
com:process(clk,counter,special)
begin
if special='1' then
out_1<="100";
out_2<="100";
else
if counter<20 then
LED_1<=s0;smg_1<=20-counter;
LED_2<=s0;smg_2<=25-counter;
elsif counter<25 then
LED_1<=s1;smg_1<=25-counter;
LED_2<=s0;smg_2<=25-counter;
elsif counter<45 then
LED_1<=s2;smg_1<=50-counter;
LED_2<=s1;smg_2<=45-counter;
else LED_1<=s2;smg_1<=50-counter;
LED_2<=s2;smg_2<=50-counter;
end if;
case LED_1 is
when s0=>out_1<="010";--绿灯亮
when s1=>out_1<="001";--黄灯亮
when s2=>out_1<="100";--红灯亮
end case;
case LED_2 is
when s0=>out_2<="100";--红灯亮
when s1=>out_2<="010";--绿灯亮
when s2=>out_2<="001";--黄灯亮
end case;
end if;
end process com;
--进程smg用于数码管显示
smg:process(clk,change,special)
variable a,b:bit;
variable c1,c2:integer range 0 to 10;
begin
if clk'event and clk='1' then
a:=not a;
b:=not b;
case a is
when '0'=> l1<="01";c1:=smg_1 rem 10;--取个位数
when '1'=> l1<="10";c1:=smg_1 mod 10;--取十位数
end case;
case b is
when '0'=> l2<="01";c2:=smg_2 rem 10;
when '1'=> l2<="10";c2:=smg_2 mod 10;
end case;
if special='1' and change='1' then --特殊情况时,数码管1s闪烁一次
c1:=10;c2:=10;
end if;
case c1 is
when 0 =>display_1<="0111111";
when 1 =>display_1<="0000110";
when 2 =>display_1<="1011011";
when 3 =>display_1<="1001111";
when 4 =>display_1<="1100110";
when 5 =>display_1<="1101101";
when 6 =>display_1<="1111101";
when 7 =>display_1<="0000111";
when 8 =>display_1<="1111111";
when 9 =>display_1<="1100011";
when others=>display_1<="0000000";
end case;
case c2 is
when 0 =>display_2<="0111111";
when 1 =>display_2<="0000110";
when 2 =>display_2<="1011011";
when 3 =>display_2<="1001111";
when 4 =>display_2<="1100110";
when 5 =>display_2<="1101101";
when 6 =>display_2<="1111101";
when 7 =>display_2<="0000111";
when 8 =>display_2<="1111111";
when 9 =>display_2<="1100011";
when others=>display_2<="0000000";
end case;
end if;
end process smg;
end behav;
2.仿真结果
- RTL Viewer
- 时序仿真
为了方便仿真,我将clk1和clk2去除,将clk设置为1Hz频率的时钟信号,下图为修改后代码。这样仿真的缺点就是,数码管无法显示出完整数字,在每过一秒钟,交替显示十位数码管和个位数码管。
仿真结果如下:
三、总结
1.收获
设计交通信号灯控制系统,我熟悉了计数器、译码器、动态数码管显示的知识,加深对它们的工作原理的理解,更加熟练的使用case、if等语句。同时,也进一步熟悉EDA软件——quartusii,提高代码构思能力。
2.问题与解决
-
编译过程中出现很多语法错误,例如process进程忘记加begin,定义的整型类型忘记加range,变量赋值语句的符号为“ := ”,if语句没有加then,忘记写“end if”…
这些语法错误在编译后的error中查找并改正。
-
构思VHDL代码结构时,最大的困难就是数码管显示数字和特殊情况处理。
-
我将数码管显示数字的计算和判断红绿灯代码放到了一起,利用counter信号50s一循环,红绿灯50s一循环,在根据红、绿、黄灯的持续时间,就可以将数码管数字计算出来。
-
特殊情况是最难处理的地方。它不仅要求在此时红灯亮,还需要让数码管闪烁。我将数码管设置为1s闪烁,具体实现方法是,在com进程让change信号每过1s取反,在smg进程中,当special=‘1’ 并且change='1’时,数码管不显示数字。
-
3.改进与优化
-
数码管显示数字部分可以看出,这个程序在显示数字小于10时,两位数码管显示为"0X",也就是说十位上的数码管有数据显示,这是不符合现实的,因此,可以更改为以下代码。
--smg进程部分 case a is when '0'=> l1<="01";c1:=smg_1 rem 10;--取个位数 when '1'=> l1<="10";c1:=smg_1 mod 10;--取十位数 if c1=0 then c1:=10;--此时数码管不显示 end if; end case; case b is when '0'=> l2<="01";c2:=smg_2 rem 10; when '1'=> l2<="10";c2:=smg_2 mod 10; if c2=0 then c2:=10;--此时数码管不显示 end if; end case;
-
通过理解题目内容可以看出,东西方和南北方的数码管显示和灯颜色的选择是相似的,可以设计子程序,通过函数调用或过程调用优化代码。但因本人能力有限,没有写出具体实现方法。