數碼顯示型計時器的HDL設計與FPGA板級除錯
作者:毛蘢瑋 / Saint
掘金:https://juejin.im/user/5aa1f89b6fb9a028bb18966a
微博:https://weibo.com/5458277467/profile?topnav=1&wvr=6&is_all=1
GitHub:github.com/saint-000
CSDN: https://me.csdn.net/qq_40531974
數碼顯示型計時器的HDL設計與FPGA板級除錯
摘 要
數字鐘是典型的電子電路的應用,而基於FPGA的數字鐘電路具有更大的靈活性和通用性。本文介紹了基於FPGA開發板的數字鐘設計的基本構想,所提供的功能,基本的模組和控制邏輯。本時鐘系統主晶片採用xc3s200 PQ208-4c,具有顯示時間及計數功能。時間採用24小時迴圈計數,通過按鍵控制數字鐘的開啟和關閉。
**關鍵詞:**FPGA;模組;迴圈計數。
1引言
VHDL結合FPGA可以方便地,可重複利用地實現各種設計,本文主要基於之前的設計原理,設計數字鐘功能所需要的模組和功能邏輯,並在FPGA(開發板;Spartan-3系列xc3s200 )中實現。
2 設計概述
設計一個數字時鐘,具有時分、秒計數顯示。鑑於所提供的功能,電路應當包括以下三大模組:控制模組,分頻模組,計時模組和顯示模組。
3 設計原理
3.1各模組介紹
控制模組包括了開和關兩個按鍵,start端定義管腳約束為P113-S18;reset端定義管腳約束為P111-S17
分頻模組主要是給需要的模組提供特定頻率的時鐘訊號;在實驗中將時鐘分為兩個頻率 bclk1<=count(24);–用於計時
bclk2<=count(14);–用於掃描數碼管
模組程式碼如下:
library IEEE; use IEEE.STD_LOGIC_1164.all; use IEEE.STD_LOGIC_UNSIGNED.all; use IEEE.STD_LOGIC_ARITH.all; entity div_freq is port( clk,clr : in STD_LOGIC; bclk1,bclk2 : out STD_LOGIC ); end div_freq; --}} End of automatically maintained section architecture behav of div_freq is signal count:std_logic_vector(24 downto 0):="0000000000000000000000000"; signal q : std_logic:='0'; begin process(clk,clr) begin if (clr='0') then count<="0000000000000000000000000"; elsif(clk'event and clk='1') then --上升沿來臨時 if (count="1111111111111111111111111")then count<="0000000000000000000000000"; else count<=count+1; end if; end if; end process; bclk1<=count(24);--用於計時 bclk2<=count(14);--用於掃描數碼管 -- enter your statements here -- end behav;
計時模組包括了秒、分、時計數模組,通過不同數位的計數器實現,並提供給顯示模組顯示輸出;
程式碼如下:
U4:秒的個位
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
use IEEE.STD_LOGIC_ARITH.all;
entity cnt10 is
port(
reset : in STD_LOGIC;
en : in STD_LOGIC;
clk : in STD_LOGIC;
carry : out STD_LOGIC;
q : out STD_LOGIC_VECTOR(3 downto 0)
);
end cnt10;
--}} End of automatically maintained section
architecture rtl of cnt10 is
signal qs:std_logic_vector(3 downto 0):="0000";
signal ca:std_logic:='0';
begin
process (clk)
begin
if (rising_edge(clk)) then
if (reset='1')then
qs<="0000";
elsif (en='1') then
if (qs="1001")then --計數到9,共有10個時鐘上升沿
qs<="0000";
ca<='0';
else
qs<=qs+1;
ca<='0';
end if ;
end if ;
end if ;
q<=qs;
end process;
process (ca)
begin
carry<=ca and en ;
end process ;
-- enter your statements here --
end rtl;
U5:秒的十位
實體大致一樣的,此處不再列舉
process (clk)
begin
if (rising_edge(clk)) then
if (reset='1')then
qs<="0000";
elsif (en='1') then
if (qs="0110")then --計數到6,清零,不產生進位
qs<="0000";
ca<='0';
else
qs<=qs+1;
ca<='0';
end if ;
end if ;
end if ;
q<=qs;
end process;
process(ca,en)
begin
carry<= ca and en ;
end process;
end rtl;
U6,U8都為十位計數器,U7,U9都為6位計數器;此處不再一一列舉
掃描按鍵顯示:
程式碼如下:
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use IEEE.STD_LOGIC_ARITH.ALL;
use IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY KEYSCAN IS
PORT (
clk : IN std_logic;
rst : IN std_logic;
en : out std_logic;
row : OUT std_logic_vector(3 DOWNTO 0); -- 行線
column : IN std_logic_vector(3 DOWNTO 0); -- 列線
dataout : OUT std_logic_vector(7 DOWNTO 0)--數碼管顯示資料
);
END KEYSCAN;
ARCHITECTURE arch OF KEYSCAN IS
SIGNAL div_cnt : std_logic_vector(24 downto 0):="0000000000000000000000000";
SIGNAL scan_key : std_logic_vector(3 DOWNTO 0):="0000"; --掃描碼暫存器
SIGNAL key_code : std_logic_vector(3 DOWNTO 0):="0000";
SIGNAL dataout_tmp : std_logic_vector(7 DOWNTO 0):="00000000";
BEGIN
PROCESS(clk,rst)
BEGIN
IF (NOT rst = '1') THEN
div_cnt <= "0000000000000000000000000";
ELSIF(clk'EVENT AND clk = '1')THEN
div_cnt <= div_cnt + 1;
END IF;
END PROCESS;
PROCESS(div_cnt(20 downto 19))
BEGIN
CASE div_cnt(20 downto 19) IS
WHEN "00"=> scan_key<="1110";
WHEN "01"=> scan_key<="1101";
WHEN "10"=> scan_key<="1011";
WHEN "11"=> scan_key<="0111";
WHEN OTHERS=>NULL;
END CASE;
END PROCESS;
PROCESS(clk,rst)
BEGIN
IF (NOT rst = '1') THEN
key_code <= "0000";
elsif(rising_edge(clk))then
CASE scan_key IS --檢測何處有鍵按下
when"1110"=>
case column is
when "1110"=> key_code <="0000";
when "1101"=> key_code <="0001";
when "1011"=> key_code <="0010";
when "0111"=> key_code <="0011";
when others=>null;
end case;
when "1101"=>
case column is
when "1110"=> key_code <="0100";
when "1101"=> key_code <="0101";
when "1011"=> key_code <="0110";
when "0111"=> key_code <="0111";
when others=>null;
end case;
when "1011"=>
case column is
when "1110"=> key_code <="1000";
when "1101"=> key_code <="1001";
when "1011"=> key_code <="1010";
when "0111"=> key_code <="1011";
when others=>null;
end case;
when "0111"=>
case column is
when "1110"=> key_code <="1100";
when "1101"=> key_code <="1101";
when "1011"=> key_code <="1110";
when "0111"=> key_code <="1111";
when others=>null;
end case;
when others => key_code <="1111";
end case;
end if;
end process;
process(key_code)--顯示鍵值
begin
case key_code is
when "0000"=> dataout_tmp <="11000000";
when "0001"=> dataout_tmp <="11111001";
when "0010"=> dataout_tmp <="10100100";
when "0011"=> dataout_tmp <="10110000";
when "0100"=> dataout_tmp <="10011001";
when "0101"=> dataout_tmp <="10010010";
when "0110"=> dataout_tmp <="10000010";
when "0111"=> dataout_tmp <="11111000";
when "1000"=> dataout_tmp <="10000000";
when "1001"=> dataout_tmp <="10010000";
when "1010"=> dataout_tmp <="10001000";
when "1011"=> dataout_tmp <="10000011";
when "1100"=> dataout_tmp <="11000110";
when "1101"=> dataout_tmp <="10100001";
when "1110"=> dataout_tmp <="10000110";
when "1111"=> dataout_tmp <="10001110";
when others=> null;
end case;
end process;
row <= scan_key;
dataout <= dataout_tmp;
en <='1';
end arch;
顯示模組包括數碼管的位選和段選,通過資料選擇器實現位選。
位選程式碼如下:
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_unsigned.all;
use IEEE.STD_LOGIC_arith.all;
entity \select\ is
port(
sec1 : in STD_LOGIC_VECTOR(3 downto 0);
sec2 : in STD_LOGIC_VECTOR(3 downto 0);
min1 : in STD_LOGIC_VECTOR(3 downto 0);
min2 : in STD_LOGIC_VECTOR(3 downto 0);
hour1 : in STD_LOGIC_VECTOR(3 downto 0);
hour2 : in STD_LOGIC_VECTOR(3 downto 0);
sel : in STD_LOGIC_VECTOR(3 downto 0);
cout : out STD_LOGIC_VECTOR(3 downto 0);
com : out STD_LOGIC_VECTOR(5 downto 0)
);
end \select\;
--}} End of automatically maintained section
architecture rtl of \select\ is
begin
process(sel)
begin
case sel is
when "0000"=>cout<=sec1;com<="000001";--選中最低位的數碼管有效
when "0001"=>cout<=sec2;com<="000010";
when "0010"=>cout<=min1;com<="000100";
when "0011"=>cout<=min2;com<="001000";
when "0100"=>cout<=hour1;com<="010000";
when "0101"=>cout<=hour2;com<="100000";
when others=>null;
end case ;
end process;
-- enter your statements here --
end rtl;
段選程式碼如下:
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
use IEEE.STD_LOGIC_ARITH.all;
entity decorder4_8 is
port(
A : in STD_LOGIC_VECTOR(3 downto 0);
Y : out STD_LOGIC_VECTOR(7 downto 0)
);
end decorder4_8;
--}} End of automatically maintained section
architecture rtl of decorder4_8 is
begin
process(A)
begin
case(A) is --hp,g,f,e,d,c,b,a,“1”有效
when "0000"=>Y<="00111111"; --顯示數字0
when "0001"=>Y<="00000110"; --顯示數字1
when "0010"=>Y<="01011011"; --顯示數字2
when "0011"=>Y<="01001111"; --顯示數字3
when "0100"=>Y<="01100110"; --顯示數字4
when "0101"=>Y<="01101101"; --顯示數字5
when "0110"=>Y<="01111101"; --顯示數字6
when "0111"=>Y<="00000111"; --顯示數字7
when "1000"=>Y<="01111111"; --顯示數字8
when "1001"=>Y<="01101111"; --顯示數字9
when others=>Y<="10000000";
end case;
end process;
-- enter your statements here --
end rtl;
3.2系統方案
採用同步電路,匯流排結構,主要功能集中在模組內部,模組較為獨立,模組間連線簡單,易於擴充套件,本次設計採用此方案
3.3時鐘系統整體介紹
由分頻器從33.8688MHZ晶振中得到1HZ訊號給計數器提供標準時鍾,用於計時,針對時,分,秒分別設計一組6位和十位計數器對應具體的十位和個位,記滿進位。在通過資料選擇器對掃描數碼管位選,此處用的是bclk2,即分頻器分出的另一時鐘訊號,用來掃描數碼管,顯示是要考慮段選顯示,用四八譯碼器實現。最後輸出至SG段選和COM位選。
3.4頂層程式碼:
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
use IEEE.STD_LOGIC_ARITH.all;
entity add_all is
port(
CLK : in STD_LOGIC;
RESET : in STD_LOGIC;
START : in STD_LOGIC;
SG : out STD_LOGIC_VECTOR(7 downto 0):="00000000";
COM : out STD_LOGIC_VECTOR(5 downto 0):="000000"
);
end add_all;
--}} End of automatically maintained section
architecture rtl of add_all is
component div_freq
port (clk:in std_logic;
bclk: out std_logic );
end component ;
component cnt10
port (
reset : in STD_LOGIC;
en : in STD_LOGIC;
clk : in STD_LOGIC;
carry : out STD_LOGIC;
q : out STD_LOGIC_VECTOR(3 downto 0)
);
end component ;
component cnt_6
port (
clk : in STD_LOGIC;
en : in STD_LOGIC;
reset : in STD_LOGIC;
carry : out STD_LOGIC;
q : out STD_LOGIC_VECTOR(3 downto 0)
);
end component ;
component decorder4_8
port (
A : in STD_LOGIC_VECTOR(3 downto 0);
Y : out STD_LOGIC_VECTOR(7 downto 0)
);
end component ;
signal clk_temp:std_logic ;
signal en1,en2,en3,en4,en5,en6:std_logic ;
signal q1,q2,q3,q4,q5,q6:std_logic_vector(3 downto 0) ;
signal qs:std_logic_vector(3 downto 0):="0000";
begin
u1:div_freq
port map (clk=>CLK,bclk=>clk_temp);
process(clk_temp,qs)
begin
if (rising_edge(clk_temp)) then
if qs="0101" then --計數到5,有6個時鐘上升沿。對div_2k再進行6分頻
qs<="0000";
else
qs<=qs+1;
end if ;
end if ;
case qs is
when "0000"=>COM<="000001"; --com端最低位為高電平,選通此位數碼管
when "0001"=>COM<="000010";
when "0010"=>COM<="000100";
when "0011"=>COM<="001000";
when "0100"=>COM<="010000";
when "0101"=>COM<="100000";
when others => null ;
end case ;
end process ;
c1:cnt10
port map (reset=>RESET,en=>START,clk=>clk_temp, --最低位
carry=>en1,q=>q1);
c2:cnt10
port map (reset=>RESET,en=>en1,clk=>clk_temp, --秒
carry=>en2,q=>q2);
c3:cnt10
port map (reset=>RESET,en=>en2,clk=>clk_temp, --分,個位
carry=>en3,q=>q3);
c4:cnt_6
port map (reset=>RESET,en=>en3,clk=>clk_temp, --分,十位
carry=>en4,q=>q4);
c5:cnt10
port map (reset=>RESET,en=>en4,clk=>clk_temp, --時,個位
carry=>en5,q=>q5);
c6:cnt_6
port map (reset=>RESET,en=>en5,clk=>clk_temp, --時,十位
carry=>en6,q=>q6);
d1:decorder4_8
port map (A=>q1,Y=>SG); --將計數器產生的四位二進位制數傳到譯碼器譯碼輸出
d2:decorder4_8
port map (A=>q2,Y=>SG);
d3:decorder4_8
port map (A=>q3,Y=>SG);
d4:decorder4_8
port map (A=>q4,Y=>SG);
d5:decorder4_8
port map (A=>q5,Y=>SG);
d6:decorder4_8
port map (A=>q6,Y=>SG);
end rtl;
3.5 管腳約束
3.6代表性波形模擬
正常計數時,當秒計數器計數到59時,再來一個時鐘脈衝,則秒計數器清零,而進位則作為分計數器的計數脈衝,使時計數器加一。(00:00:59=>00:01:00)
當分計數器計數到59時,再來一個時鐘脈衝,則分計數器清零,而進位則作為時計數器的計數脈衝,使時計數器加一。(00:59:59=>01:00:00)
3.7實驗截圖:
4 總結
4.1實驗改進:
在實驗中,發現忽略了時計數模組是二十四進位制的,所以對實驗的改進如下:
時鐘時計數子模組:
時計數子模組是由一個24進位制計數器組成,正常計數時,當時計數器計數到23時,再來一個脈衝,則時計數器清零,重新開始新一輪的計數。
時的計時電路可以用如下方式實現:
程式碼如下:
library IEEE;
use IEEE.STD_LOGIC_1164.all;
use IEEE.STD_LOGIC_UNSIGNED.all;
use IEEE.STD_LOGIC_ARITH.all;
entity clock24 is
port(
clk : in STD_LOGIC; --時鐘
en : in STD_LOGIC; --使能端
reset : in STD_LOGIC; --復位
load : in STD_LOGIC; --置數
d : in STD_LOGIC; --輸入端
carry : out STD_LOGIC; --進位
QH: BUFFER STD LOGIC VECTOR(3 DOWNTO 0);
QL: BUFFER STD LOGIC VECTOR(3 DOWNTO 0)
);
end clock24;
--}} End of automatically maintained section
architecture rtl of clock24 is
process (clk,reset)
begin
if(reset='0')then
QH<="0000";
QL<="0000"; --非同步復位
elsif (rising_edge(clk)) then
if (load='1')then
QH<=D(7DOWNTO 4);
QL<=D(3DOWNTO 0);
elsif(en='1')then
if(QL=9 or (QH=2 and QL=3))then
QL<=0000;
if(QH=2)then
QH<="0000";
else
QH<=QH+1;
end if
else
QL<=QL+1;
end if;
end if;
end process;
end rtl;
4.2實驗心得:
(1)通過這次電子設計,我更加明白了團隊合作的重要性。僅僅是做一個數字鍾,工作量就相當大,不是一個人所能獨立完成的。
(2)我對FPGA的設計和模擬產生了更深刻的認識和理解,也對之前學到的知識和做過的實驗有了更深刻的認識。熟悉編譯環境、實用軟體,也通過軟體鞏固了自己的知識。
(3)在實驗除錯過程會遇到各種問題,我們耐心解決,在解決問題時也用到了排除法,比如在ise軟體下下載bit檔案時發現軟體視窗沒有彈出如下頁面:
後來依次排查FPGA開發板,JTAG聯結器和杜邦線,一一排除問題後,發現原來的聯結器的線接觸不良。
排除問題後匯入BIT流檔案,測試FPGA。