事件驅動有限狀態機(下)
--- 搶先遊戲與雙鍵密碼鎖
【設計二】:雙鍵密碼鎖設計
1. 圖 6 是根據雙按鍵密碼鎖應用系統的動作要領,所思考出來的電路圖架構,
整個系統分成六個模組元件來規劃:降頻電路模組(divider1)、按鍵整型電路 模組(buff_inv)、 按鍵次數計算電路模組(pb_count)、七節燈管顯示解碼電路 模組(b2seg)、拍手動作圖樣電路模組(led_sr)、以及雙按鍵密碼鎖控制電路模 組(pb2_lock)。
2. 如果在開鎖階段,當顯示器上出現「6」,且到這段時間內所有的 pb 按鍵值都
是與 6 個 DIP 開闗的相對位置設定值一樣,那麼 8 個 LED 指示燈會以「拍手」
狀閃亮著,恭喜已經開鎖成功了;圖 7 即為正式開鎖成功的圖例。
3. 當任何的開鎖計數階段內,只要有任何的 pb 按鍵值與 DIP 開闗的相對位置設 定值不一樣,那麼計數器就會不斷的繼續重複計數下去,表示開鎖已經失效,
而且系統已經進入「無可奈何」的狀態,只有「reset」才可以救回來;圖 8 正是開鎖不成功的圖例。
4. 採用多個模組的元件設計與電路圖組合,可以達到「分工合作」的團隊共模 設計理念。
圖 6
圖 7
圖 8
【程式範例 2-1】除頻器(divider1)
1. 圖 6 之系統電路中,包括有降低時脈速度之除頻器(divider),此模組幾乎會 出現於很多應用系統上,其目的當然是降低系統執行速度,以便人眼能夠跟 得上結果的可觀測性。
2. 此應用上,配合人的動作速度不快,而按鍵的彈跳現象又僅發生於數十毫秒
(ms)內,因此需要有數個 Hz 的頻率,當作系統時脈頻率。
3. 拍手動作圖樣電路模組也需要一個控制拍手速度的時脈,此時脈信號可以由
系統時脈供應,也可以由除頻器另行輸出一個時脈頻率給之。
【程式範例 2-1 之完整電路】
entity Divider1 is
Port ( clk : in std_logic;
clkm : out std_logic);
end Divider1;
architecture Behavioral of Divider1 is
signal clk_count : std_logic_vector(20 downto 0);
begin
div: process(clk) begin
if rising_edge(clk) then clk_count <= clk_count +1;
end if;
end process;
-- clkm1 <= clk_count(15); --- 供應拍手動作圖樣電路模組 clkm <= clk_count(20);
end Behavioral;
【程式範例 2-2】按鍵整形電路模組(buff_inv)
1. 按鍵整型電路模組的目的是供應一個極為乾淨的脈波,供應按鍵次數計算電
路模組(pb_count)、以及雙按鍵密碼鎖控制電路模組(pb2_lock)。
2. 只要讓工作時脈的週期高於 30ms,就可以有效去除按鍵彈跳現象。
【程式範例 2-2 之完整程式】
entity buff_inv is
Port ( clk : in STD_LOGIC;
In1 : in STD_LOGIC;
In2 : in STD_LOGIC;
Inv1 : out STD_LOGIC;
Inv2 : out STD_LOGIC);
end buff_inv;
architecture Behavioral of buff_inv is begin
process(clk,In1,In2) begin
if clk'event and clk='1' then Inv1 <= not In1;
Inv2 <= not In2;
end if;
end process;
end Behavioral;
【程式範例 2-3】按鍵次數計算電路模組(pb_count)
1. 在開鎖階段,最可能發生的狀態是:開鎖者突然忘記了自己已經輸入到第幾 個密碼,猶疑中會造成錯誤的;還有如果開鎖順序錯誤時,也不可以讓開鎖 者知曉到底是在那一道順序中開始出錯?否則多試幾次就能夠推論出來正 確的開鎖組合碼的,以 4-bit 的開鎖密碼來說,原本可能的組合為 24 =16 種 狀況,每猜到一個正確的按鍵順序,可能的組合數就變成為原先的一半,所 以不可以讓開鎖者在發生錯誤的當時,本應用系統就會出現任何的訊息來暗 示開鎖動作已經失效,否則將會有「洩底」之嫌疑。
2. 因此本應用範例就特別規劃一個計數器,除了做為按鍵次數的追蹤外,還做
為開鎖與否的指示;也就是說,只要計數器顯示值為「0」,表示正處於「等
待開鎖」階段;計數器顯示值小於「4」時,表示正處於「等待進行中」階 段;而當計數器顯示值等於「4」時,如果 LED 指示燈沒有出現「拍手」信 號,表示正處於「開鎖失效」階段;當然計數器顯示值大於「4」時,絕對 是處於「開鎖失效」階段的。
3. 本應用利用非同步之脈波信號的正緣觸發,做為計數器的計數脈波。
4. 非同步之脈波信號係來自 pb1 或是 pb1 的按鍵動作:
pb <= pb1 or pb2;
…….
if (pb'event and pb = '1') then count_s <= count_s+1;
end if;
5. 當計數值超過「4」,也代表著開鎖失敗,此計數信號除了提供計數指示外,
還可以編碼出各種其它的用途,例如送出一個警告的聲響、點亮警示燈、失 敗數計次、啟動監看攝影機等等。
【程式範例 2-3 之完整程式】
entity pb_count is
Port ( pb1 : in std_logic;
pb2 : in std_logic;
pb_count : out std_logic_vector(3 downto 0);
reset : in std_logic);
end pb_count;
architecture Behavioral of pb_count is signal pb : std_logic;
signal count_s : std_logic_vector(3 downto 0);
begin
process(reset,pb1,pb2) begin
pb <= pb1 or pb2;
if (reset = '0') then count_s <= "0000";
elsif (pb'event and pb = '1') then count_s <= count_s+1;
end if;
pb_count <= count_s;
end process;
end Behavioral;
【類題練習】
1. 請在開鎖失敗後,送出一個 1 秒中之雙音調警報信號。
【程式範例 2-4】七節燈管顯示解碼電路模組(b2seg)
1. 負責將按鍵次數計算之計數器的輸出解碼並驅動七節燈管顯示器。
2. 這是個典型的可重複使用之模組元件。
【程式範例 2-4 之完整程式】
entity b2seg is
Port ( data : in std_logic_vector(3 downto 0);
digit : out std_logic_vector(3 downto 0);
seg : out std_logic_vector(7 downto 0));
end b2seg;
architecture Behavioral of b2seg is begin
digit <= "0111";
seg <= "11000000" when data =0 else "11111001" when data =1 else "10100100" when data =2 else
"10110000" when data =3 else
"10011001" when data =4 else "10010010" when data =5 else
"10000010" when data =6 else
"11111000" when data =7 else
"10000000" when data =8 else "10010000" when data =9 else
"10001000" when data =10 else
"10000011" when data =11 else
"11000110" when data =12 else "10100001" when data =13 else
"10000110" when data =14 else
"10001110" when data =15 else
"11111111";
end Behavioral;
【程式範例 2-5】拍手動作圖樣電路模組(led_sr)
1. 拍手動作圖樣的輸出,需要先建立 LED 燈管的圖樣資料。
2. 建立圖樣資料的輸出顯示方式,比較容易設計,也不必花費腦力,不過,本
模組採用另類的純邏輯演算式子來設計,就當作參考設計好了;當然,此種 設計法是可以做為一種「智慧財產權」的保護措施。
3. 藉由旋轉移位暫存器以及組合邏輯來湊出「拍手」效果
pat <= pat(6 downto 0) & pat(7);
……
pattern(0) <= lockopen and (pat(0) or pat(7));
pattern(1) <= lockopen and (pat(1) or pat(6));
……
4. 如果將 LED 燈管的數量增加或是刪減,需要更有效的語法來簡化程式碼,
例如「for…loop」…。
【程式範例 2-5 之完整程式】
entity led_sr is
Port ( reset : in std_logic;
lockopen : in std_logic;
pattern : out std_logic_vector(7 downto 0);
clk : in std_logic);
end led_sr;
architecture Behavioral of led_sr is
signal pat : std_logic_vector(7 downto 0);
begin
process(clk,lockopen) begin
if reset = '0' then pat <= "00000001";
elsif clk'event and clk = '1' then
if lockopen = '1' then
pat <= pat(6 downto 0) & pat(7);
end if;
end if;
end process;
--- pattern(0) <= lockopen and (pat(0) or pat(7));
pattern(1) <= lockopen and (pat(1) or pat(6));
pattern(2) <= lockopen and (pat(2) or pat(5));
pattern(3) <= lockopen and (pat(3) or pat(4));
pattern(4) <= lockopen and (pat(4) or pat(3));
pattern(5) <= lockopen and (pat(5) or pat(2));
pattern(6) <= lockopen and (pat(6) or pat(1));
pattern(7) <= lockopen and (pat(7) or pat(0));
end Behavioral;
【類題練習】
1. 請 將 「 pattern : out std_logic_vector(7 downto 0); 」 換 成 「 pattern : out std_logic_vector(15 downto 0);」,改寫拍手動作並且模擬印證之。
2. 請以「for…loop」或其它敘述取代邏輯 or 運算,改寫拍手動作並且模擬印
證之。
【程式範例 2-6】雙按鍵密碼鎖控制電路模組(pb2_lock)
1. 圖 9 是根據雙按鍵密碼鎖的規格,所建立的狀態變遷圖。
2. 圖 9 的樣子看起來好像不是狀態變遷圖吧!不要懷疑,這是採用演算法狀態
機 ASM(Algorithm State Machine)來設計的狀態變遷圖,比起原來的狀態變 遷圖要明細些,也較容易轉成為 VHDL 程式碼。
3. 隨時只要看看 PB1 或是 PB2 的有無按鍵動作,就足以決定應該變遷到那一
個狀態去,不用去管到底該在什麼時刻才能夠按出有效的按鍵,因而把與時 間因素相當敏感的狀態變遷條件完全剃除掉。
4. 開始時(Reset 後),系統處於「s0」狀態,這時的 LED 不亮表示鎖住的狀況,
當按鍵動作發生時,就開始判斷該事件驅動行為的合法性,並且作出正確的 狀態變遷路徑,也就是說,當按下 PB1 時就把此動作與 DIP 密碼設定的 bit0(sw(0)) 相比較,如果該 bit0=0 就表示開鎖的第一個碼正確而得以進入 次一個開鎖碼的 s1 狀態,否則就會進入「萬劫不復」的 Sidle 狀態。
Pb1 Pb2
Sw(0) = ’0' Sw(0) = ’1'
S0 Led <- off
S1 Led <- off
Sidle Led <- off
1
0
1
1 0
Pb1 Pb2
Sw(1) = ’0' Sw(1) = ’1'
S2 Led <- off
1
0
1
1 0 0
1
0 0
Pb1 Pb2
Sw(2) = ’0' Sw(2) = ’1'
S3 Led <- off
1
0
1
1 0 0
1
0
Pb1 Pb2
Sw(3) = ’0' Sw(3) = ’1'
1
0
1
1 0 0
1
0 1
0
S4 Led <= open
圖 9
5. 同理當 bit0=1 且按下的是 PB2 按鍵,那麼就得以進入次一個開鎖碼的 s1 狀 態,否則會進入 Sidle 狀態。
6. 如果一直沒有按鍵動作呢?別緊張,不就是在 s0 狀態打轉罷了,不會受到
時脈正緣信號的出現或是時脈信號的速度來影響的。
when s0 =>
if (pb1='1') then --- pb1 push if (sw(0) = '0') then
next_state <= s10;
else
next_state <= Sidle;
end if;
elsif (pb2='1') then --- pb2 push if (sw(0) = '1') then
next_state <= s11;
else
next_state <= Sidle;
end if;
else
next_state <= s0;
end if;
7. 怎麼會多出 s10、s11 這兩個中間狀態呢?喔!原來是用來確定按鍵信號是
否已經回到原先的「off」位置的,避免過長的按鍵信號被誤以為是連續的 按鍵動作,由於唯有一個乾淨的脈波信號才能視為一次有效的按鍵,因此在 進入 s10、s11 狀態後,需要按鍵信號恢復到「0」狀況才會轉進入 s1,否則 就被鎖定在 s10、s11 狀態,一直等待到按鍵信號恢復原狀才得以脫離。
8. 其他的後續開鎖動作就「依樣畫葫蘆」了,這兒設定的密碼為 4-bit,所以
只要一路上正確的「過關斬將」,就能夠進入 s4 的「飛龍在天」開鎖階段。
【程式範例 2-6 之完整程式】
entity pb2_lock is
Port ( Reset : in std_logic;
clk : in std_logic;
pb1 : in std_logic;
pb2 : in std_logic;
sw : in std_logic_vector(3 downto 0);
lock_open : out std_logic);
end pb2_lock;
architecture Behavioral of pb2_lock is
type state_type is (s0,s1,s10,s11,s2,s20,s21,s3,s30,s31,s4,s40,s41,Sidle);
signal current_state,next_state : state_type ; begin
process(reset,clk,pb1,pb2,sw) begin
if (Reset = '0') then lock_open <= '0' ; current_state <= s0;
elsif (clk'event and clk = '1') then current_state <= next_state ;
end if;
--- case current_state is
when s0 =>
if (pb1='1') then --- pb1 push if (sw(0) = '0') then
next_state <= s10;
else
next_state <= Sidle;
end if;
elsif (pb2='1') then --- pb2 push if (sw(0) = '1') then
next_state <= s11;
else
next_state <= Sidle;
end if;
else
next_state <= s0;
end if;
when s10 =>
if (pb1 = '0') then --- pb1 release next_state <= s1;
else
next_state <= s10;
end if;
when s11 =>
if (pb2 = '0') then --- pb2 release next_state <= s1;
else
next_state <= s11;
end if;
--- when s1 =>
if (pb1='1') then if (sw(1) = '0') then
next_state <= s20;
else
next_state <= Sidle;
end if;
elsif (pb2='1') then if (sw(1) = '1') then
next_state <= s21;
else
next_state <= Sidle;
end if;
else
next_state <= s1;
end if;
when s20 =>
if (pb1 = '0') then next_state <= s2;
else
next_state <= s20;
end if;
when s21 =>
if (pb2 = '0') then next_state <= s2;
else
next_state <= s21;
end if;
--- when s2 =>
if (pb1='1') then if (sw(2) = '0') then
next_state <= s30;
else
next_state <= Sidle;
end if;
elsif (pb2='1') then if (sw(2) = '1') then
next_state <= s31;
else
next_state <= Sidle;
end if;
else
next_state <= s2;
end if;
when s30 =>
if (pb1 = '0') then next_state <= s3;
else
next_state <= s30;
end if;
when s31 =>
if (pb2 = '0') then next_state <= s3;
else
next_state <= s31;
end if;
--- when s3 =>
if (pb1='1') then if (sw(3) = '0') then
next_state <= s40;
else
next_state <= Sidle;
end if;
elsif (pb2='1') then if (sw(3) = '1') then
next_state <= s41;
else
next_state <= Sidle;
end if;
else
next_state <= s3;
end if;
when s40 =>
if (pb1 = '0') then next_state <= s4;
else
next_state <= s40;
end if;
when s41 =>
if (pb2 = '0') then next_state <= s4;
else
next_state <= s41;
end if;
--- when s4 =>
lock_open <= '1';
next_state <= s4;
when Sidle =>
next_state <= sidle;
end case;
end process;
end Behavioral;
【模擬波形】
1. 圖 10 為成功開鎖動作的模擬波形,這兒的開鎖密碼設定值為「1-0-0-1」,也 就是說按鍵的順序應該是「pb2-pb1-pb1-pb2」才可以打開鎖匙,因此從模擬 波形中可以看出原來處於 s0 的狀態,因著 pb2 的按鍵動作而轉往 s11 狀態,
pb2 放開後,就轉往 s1 狀態。
2. 接著又因為 pb1 的按鍵動作而轉往 s20,當 pb1 放開後,就轉往 s2 狀態。
3. 然後藉由 pb1 的按鍵動作而得以轉往 s30 狀態,又因 pb1 放開後,而轉往 s3 狀態。
4. 接連由 pb2 的按鍵動作而轉往 s41,最後的一道 pb2 的放開按鍵手續,終於 到達了 s4 狀態,此時 l_open 信號由「0」變成「1」,表示開鎖任務已經達 成
圖 10
5. 接著再來模擬錯誤開鎖程序所會造成的後果,看看是否會順利的進入「萬劫 不復」的狀態;由圖 11 的模擬波形中,當按鍵動作順序為「pb2-pb1-pb2…」, 那麼到達 s2 狀態後,對於接著到來的 pb2 的按鍵動作,終於導致 Sidle 狀態 的進駐,表示已經無法如願以償的來把鎖打開。
圖 11
【類題練習】
1. 請將「sw : in std_logic_vector(3 downto 0);」換成「sw : in std_logic_vector(7 downto 0);」,改寫雙按鍵密碼鎖控制電路並且模擬印證之。
【設計二實作】電路圖整合輸入方式
1. 將範例 2-1 至範例 2-6 之程式,分別驗證並且產生元件電路圖符號後,就可
以如圖 12 之 ISE 整合發展視窗下,建立出圖 6 之可合成、繞線、下載之電 路圖檔案。
2. 只要修改範例 2-1 之除頻器 divider,就可以讓按鍵彈跳的去除電路,符合各 種按鍵材質所造成的不同彈跳速度。
圖 12
【接腳設定】:(參考驗證實驗板) 1. 驗證板上配置表
N9 T9 R9 M10 N10 P10 T10 T11
DP G F E D C B A
S2 S3 S4 S5 S6 S7 S8 S9
G11 H10 H11 G12 J10 J11 K11 K12
S2 S3 S4 S5 S6 S7 S8 S9
D7 D8 D9 D10 D11 D12 D13 D14
M14 M13 K13 L13 M16 M15 L16 L14
D7 D8 D9 D10 D11 D12 D13 D14
S10-1 S10-2 S10-3 S10-4
ON DIP
E6 F7 F9 F10
S10-1 S10-2 S10-3 S10-4
2. 範例接腳設定:
NET "clk" LOC = P9;
NET "digit<0>" LOC = R13;
NET "digit<1>" LOC = P12;
NET "digit<2>" LOC = P11;
NET "digit<3>" LOC = N11;
NET "pb1" LOC = G11;
NET "pb2" LOC = H10;
NET "reset" LOC = K12;
NET "seg<0>" LOC = T11;
NET "seg<1>" LOC = T10;
NET "seg<2>" LOC = P10;
NET "seg<3>" LOC = N10;
NET "seg<4>" LOC = M10;
NET "seg<5>" LOC = R9;
NET "seg<6>" LOC = T9;
NET "seg<7>" LOC = N9;
NET "pattern<7>" LOC = L14;
NET "pattern<6>" LOC = L16;
NET "pattern<5>" LOC = M15;
NET "pattern<4>" LOC = M16;
NET "pattern<3>" LOC = L13;
NET "pattern<2>" LOC = K13;
NET "pattern<0>" LOC = M14;
NET "pattern<1>" LOC = M13;
NET "sw<0>" LOC = E6;
NET "sw<1>" LOC = F7;
NET "sw<2>" LOC = F9;
NET "sw<3>" LOC = F10;
【練習題】:
1. 設計一個專門量測 100KHz 以下之測頻計與時間間隔量測器。
2. 設計一個 100KHz 以上之測頻計。
3. 設計一具有小數點 2 位之 ms 解析度的智慧型脈波寬度量測計。
4. 設計一個樂透彩的中獎產生機器,也就是 1-42 之數字隨機出現,且六個數
字都不會重覆。
5. 設計一個 4 人的搶先應用系統:利用 4 個按鍵當輸入,利用 4 個七節燈管顯
示出每個人的按鍵順序,而「E」字表示犯規,「0」字表示尚未按下。例如
「0E21」,表示參賽#1 尚未按下,參賽#2 犯規,參賽#3 為第 2 個按下,而 參賽#4 為第 1 個按下。
6. 對於雙鍵密碼鎖,請加入聲音輸出電路,分別代表 sw1、sw2 之按鍵回授訊
息,以及開鎖成功、失敗之音效。