一、問題描述
題目九:出租車計價器設計(平臺實現)★★
完成簡易出租車計價器設計,選做停車等待計價功能。
1、基本功能:
(1)起步8元/3km,此后2元/km;
(2)里程指示信號為每前進50米一個高電平脈沖,上升沿有效;顯示行駛公里數,精確到0.1km。(模擬時速40km/h)
(3)前進里程開始之前顯示價錢,精確到0.1元;
(4)用兩個按鍵分別表示開始行程和結束行程。
2、選做功能:
(1)增加一個停車等待/恢復行程按鈕,用2個數碼管顯示等待時間,精確到0.1分鐘。
(2)等候費1元/min,計價精度為0.1元。
二、設計方案
????????根據題設分析得到出租車的計費工作原理分成以下4個階段:
(1)出租車起步開始計費,首先顯示起步價(設計起步費為8.0元),當出租車在3km的行駛里程以內,只收起步價8.0元;
(2)出租車行駛超過3km后,按每公里2元計費(在8.0元基礎上每行駛1km車費加2.0元),車費依次累加;
(3)出租車暫停行駛(如行駛中遇紅燈或中途由于堵車等意外情況而停車),按停止時間進行計費,每1分鐘計費1.0元;
(4)若行程終止,則車費清零,等待下一次計費的開始。
????????對應主要可分為以下三大模塊去實現:
(1)秒分頻模塊:
????????脈沖生成模塊保證整個系統的同步工作,對于電路板上提供的100Hz時鐘脈沖信號進行分頻處理,得到題目中需要用到的秒分頻信號,便于后續計量數據模塊對于信號的處理。
(2)計量控制模塊:
????????計量數據模塊主要有三個部分組成,分別是計價部分、計時部分和計程部分,這三個部分是出租車計價器系統多功能實現的基礎與保證。
????????計價部分又可以分為兩個內容,其一是在出租車正常行駛的過程中根據不同的收費標準分段將里程數折算為對應的價格費用,如本題中在起步3km以內固定收費8.0元,而超出3km起步里程后對后續的每公里里程折算為2元的價格費用;其二是在出租車暫停行駛的情況下,將等候時間折算為對應的價格費用,如本題中將每分鐘折算為1.0元。
????????與此同時,出租車需要實現開始行程、停車等待、恢復行程和結束行程四個動作,因此控制出租車的狀態需要設計三個按鍵,用來選擇出租車的啟動、暫停和終止,對應按鍵按下時將對應的電平從低電平翻轉為高電平,并將此信號送往控制模塊產生相應的響應動作。
(3)譯碼顯示模塊:
????????譯碼顯示模塊用于將出租車的實時里程數、價格費用和等待時間顯示出來。
????????① 用2個數碼管顯示實時里程數;
????????② 用2個數碼管顯示等待時間;
????????③ 用4個數碼管顯示價格費用。
系統頂層框圖如下:
三、系統實現
1、基本流程
(1)設計輸入:運用VHDL硬件描述語言根據題目所要求實現的功能和自己設計的拓展部分進行電路設計(開發軟件:Quartus Ⅱ 9.0);
(2)文件處理:對設計輸入的文件進行編譯檢查、邏輯化簡、改進優化等一系列步驟,最后生成對應的編程文件;
(3)仿真驗證:對設計處理的編程文件進行仿真測試,以驗證程序是否符合題目給出的要求和設計的功能是否可以實現;
(4)元器件編程:將對應的VHDL硬件描述語言的編程代碼數據下載至具體的可編程元器件中;
(5)硬件測試:將編寫好的系統程序載入到實驗電路板上并按題目要求進行測試(硬件:EDA-I實驗板,如下圖)。
2、程序流程圖
3、代碼說明
(1)分頻:根據題目要求首先設置秒計時,即先完成1s分頻;再根據50米給出一個高脈沖,設置4.5s分頻;對于精確到0.1元的計價,設計6s分頻;對于精確到0.1km的里程,設計9s分頻。
p1:process(rst, clk) -- 1s 分頻begin if rst = '0' then if clk'event and clk = '1' then if count_1 = 99 then count_1 <= 0;clk1hz <= '1'; else count_1 <= count_1 + 1;clk1hz <= '0'; end if;end if; end if; end process; p2:process(rst, clk) -- 4.5s 分頻begin if rst = '0' then if clk'event and clk = '1' then if count_2 = 449 then count_2 <= 0;clklhz_1 <= '1'; else count_2 <= count_2 + 1;clklhz_1 <= '0'; end if;end if; end if; end process; p3:process(rst, clk) -- 6s 分頻begin if rst = '0' then if clk'event and clk = '1' then if count = 599 then count <= 0;clklhz_2 <= '1'; else count <= count + 1;clklhz_2 <= '0'; end if;end if; end if; end process; p4:process(rst, clk) -- 9s 分頻begin if rst = '0' then if clk'event and clk = '1' then if count_3 = 899 then count_3 <= 0;clklhz_3 <= '1'; else count_3 <= count_3 + 1;clklhz_3 <= '0'; end if;end if; end if; end process;
(2)段選與片選:對需要顯示在數碼管上的信號量設計譯碼方案,并根據實際情況分出的不同情況進行不同的段選與片選。
p9:process(clk2)beginif clk2'event and clk2 = '1' thencase show iswhen "000" =>show <= "001";pianxuan <= "11111110";bt <= c0;flag <= 0;when "001" =>show <= "010";pianxuan <= "11111101";bt <= c1;flag <= 1;when "010" =>show <= "011";pianxuan <= "11111011";bt <= c2;flag <= 0;when "011" =>show <= "100";pianxuan <= "11110111";bt <= c3;flag <= 0;when "100" =>show <= "101";pianxuan <= "11101111";bt <= k0;flag <= 0;when "101" =>show <= "110";pianxuan <= "11011111";bt <= k1;flag <= 1;when "110" =>show <= "111";pianxuan <= "10111111";bt <= m0;flag <= 0;when "111" =>show <= "000";pianxuan <= "01111111";bt <= m1;flag <= 0;end case;end if;end process;p10:process(bt, flag)beginif flag = 1 thencase bt iswhen "0000" => duanxuan <= "11111110"; --0when "0001" => duanxuan <= "10110000";when "0010" => duanxuan <= "11101101";when "0011" => duanxuan <= "11111001";when "0100" => duanxuan <= "10110011";when "0101" => duanxuan <= "11011011";when "0110" => duanxuan <= "11011111";when "0111" => duanxuan <= "11110000";when "1000" => duanxuan <= "11111111";when "1001" => duanxuan <= "11111011"; --9when others => NULL;end case;elsif flag = 0 thencase bt iswhen "0000" => duanxuan <= "01111110"; --0when "0001" => duanxuan <= "00110000";when "0010" => duanxuan <= "01101101";when "0011" => duanxuan <= "01111001";when "0100" => duanxuan <= "00110011";when "0101" => duanxuan <= "01011011";when "0110" => duanxuan <= "01011111";when "0111" => duanxuan <= "01110000";when "1000" => duanxuan <= "01111111";when "1001" => duanxuan <= "01111011"; --9when others => NULL;end case; end if;end process;
end;
(3)里程計數:根據3km內和超出3km部分進行計價模式切換,并設計完成進位部分和重置部分。
p7:process(rst, start, mile_clk) --里程計數begin if rst = '1' then k0 <= "0000"; k1 <= "0000"; mode <= '0';elsif clklhz_3'event and clklhz_3 = '1' thenif wait_signal = '0' and start = '1' then if k1 & k0="00110000" then --超過3kmmode <= '1'; end if; if k0 = "1001" then k0 <= "0000"; if k1 = "1001" then k1 <= "0000"; else k1 <= k1 + '1'; end if; else k0 <= k0 + '1'; end if; end if;end if; end process;
(4)等待時間計數:此處為了方便觀察實驗結果,我將顯示分鐘改為了顯示秒鐘,但仍按分鐘對應的秒數進行進位(0-59s)。
p5:process(rst, clk1hz, start, wait_signal) --等待時間計數begin if rst = '1' then --乘客離開m0 <= "0000";m1 <= "0000"; elsif start = '0' then --沒開車wait_clk <= '0'; elsif clklhz_2'event and clklhz_2 = '1' then if wait_signal = '1' then --停車if sec = 9 thensec <= 0;if m0 = "1001" then m0 <= "0000"; if m1 = "0101" thenm1 <= "0000"; else m1 <= m1 + '1'; end if; else m0 <= m0 + '1'; end if;else wait_clk <= '0'; sec <= sec + 1;if m0 = "1001" then m0 <= "0000"; if m1 = "0101" thenm1 <= "0000"; else m1 <= m1 + '1'; end if; else m0 <= m0 + '1'; end if; end if; else wait_clk <= '0'; end if; end if; end process;
(5)計費:根據不同的行程狀態對應不同的時鐘信號cost_clk,按50m上升沿時鐘頻率更新計算費用,并完成進位部分和重置。
p6:process(rst, clklhz_1, clklhz_2) --檢測mile上升沿beginif wait_clk = '1' or rst = '1' thenmile_clk <= '0';elsif rst = '0' and wait_clk = '0' thenmile_clk <= clklhz_1;end if; end process; cost_clk <= clklhz_2 when wait_signal = '1' else mile_clk when mode = '1' else '0'; p8:process(rst, start, cost_clk) --計費begin if rst = '1' then --計價結束c0 <= "0000";c1 <= "0000"; c2 <= "0000";c3 <= "0000"; elsif start = '1' and mode = '0' then --還在起步范圍c0 <= "0000";c1 <= "1000"; --8.0元c2 <= "0000"; c3 <= "0000"; elsif cost_clk'event and cost_clk = '1' then --50m每個上升沿/中途停車等待if mode = '1' and start = '1' then if c0 = "1001" then c0 <= "0000"; if c1 = "1001" then c1 <= "0000"; if c2 = "1001" then c2 <= "0000";if c3 = "1001" thenc3 <= "0000";elsec3 <= c3 + '1';end if;else c2 <= c2 + '1'; end if; else c1 <= c1 + '1'; end if; else c0 <= c0 + '1'; end if; end if;end if; end process;
(6)消除抖動:通過延時計數的方法將不連續的輸入脈沖信號調整為穩定的輸出信號。
xiaodou:process(clk, start_in, wait_signal_in, rst_in)beginif clk'event and clk = '1' thenif count < 1 thencount <= count + 1;elsecount <= 0;if start_in = '1' thenstart <= '1';elsif wait_signal_in = '1' thenwait_signal <= '1';elsif rst_in = '1' thenrst <= '1';end if;end if;end if;end process;
4、完整代碼
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;
entity taxi is
port(clk:in std_logic; --時鐘信號start:inout std_logic;wait_signal:inout std_logic;rst:inout std_logic;numViewOutputSg:out std_logic_vector(7 downto 0);numViewOutputBt:out std_logic_vector(7 downto 0);clk2: in std_logic --高頻時鐘);
end; architecture bhv of taxi is signal mile_clk,clk1hz,clklhz_1,clklhz_2,clklhz_3:std_logic; --clklhz:分頻后的時鐘信號signal count:integer range 0 to 599;signal count_1:integer range 0 to 99; signal count_2:integer range 0 to 449;signal count_3:integer range 0 to 899;signal sec:integer range 0 to 59 :=0; --秒數signal c0,c1,c2,c3:std_logic_vector(3 downto 0); --費用(從低到高)signal k0,k1,m0,m1:std_logic_vector(3 downto 0); --k指公里 m指時間signal en0:std_logic :='0'; signal wait_clk,cost_clk:std_logic; signal temp:std_logic;signal show:std_logic_vector(2 downto 0):="000";signal bt:std_logic_vector(3 downto 0);signal flag:integer range 0 to 1;--signal rst:std_logic; --判斷是否停車--signal start:std_logic; --使能信號--signal wait_signal:std_logic; --停車信號
begin
--xiaodou:process(clk,start_in,wait_signal_in,rst_in)
-- begin
-- if clk'event and clk='1' then
-- if count<1 then
-- count<=count+1;
-- else
-- count<=0;
-- if start_in='1' then
-- start<='1';
-- elsif wait_signal_in='1' then
-- wait_signal<='1';
-- elsif rst_in='1' then
-- rst<='1';
-- end if;
-- end if;
-- end if;
-- end process;U1:process(rst,clk) -- 1s 分頻begin if rst='0' then if clk'event and clk='1' then if count_1=99 then count_1<=0;clk1hz<='1'; else count_1<=count_1+1;clk1hz<='0'; end if;end if; end if; end process; U0:process(rst,clk) -- 4.5s 分頻begin if rst='0' then if clk'event and clk='1' then if count_2=449 then count_2<=0;clklhz_1<='1'; else count_2<=count_2+1;clklhz_1<='0'; end if;end if; end if; end process; U6:process(rst,clk) -- 6sbegin if rst='0' then if clk'event and clk='1' then if count=599 then count<=0;clklhz_2<='1'; else count<=count+1;clklhz_2<='0'; end if;end if; end if; end process; U9:process(rst,clk) -- 9sbegin if rst='0' then if clk'event and clk='1' then if count_3=899 then count_3<=0;clklhz_3<='1'; else count_3<=count_3+1;clklhz_3<='0'; end if;end if; end if; end process; U2:process(rst,clk1hz,start,wait_signal) --等待時間計數begin if rst='1' then --乘客離開m0<="0000";m1<="0000"; elsif start='0' then --沒開車wait_clk<='0'; elsif clk1hz'event and clk1hz='1' then if wait_signal='1' then --停車if sec=59 thensec<=0;elsif ((sec+1) mod 6)=0 thensec<=sec+1; if m0="1001" then m0<="0000"; if m1="0101" thenm1<="0000"; else m1<=m1+'1'; end if; else m0<=m0+'1'; end if;else wait_clk<='0'; sec<=sec+1;if m0="1001" then m0<="0000"; if m1="0101" thenm1<="0000"; else m1<=m1+'1'; end if; else m0<=m0+'1'; end if; end if; else wait_clk<='0'; end if; end if; end process; U3:process( rst,clklhz_1,clklhz_2) --檢測mile上升沿beginif wait_clk='1' or rst='1' thenmile_clk<='0';elsif rst='0' and wait_clk='0' thenmile_clk<=clklhz_1;end if; end process; cost_clk<=clklhz_2 when wait_signal='1' else mile_clk when en0='1' else '0'; U4:process(rst,start,mile_clk) --里程計數begin if rst='1' then k0<="0000"; k1<="0000"; en0<='0';elsif clklhz_3'event and clklhz_3='1' thenif wait_signal='0' and start='1' then if k1 & k0="00000011" then --超過3kmen0<='1'; end if; if k0="1001" then k0<="0000"; if k1="1001" then k1<="0000"; else k1<=k1+'1'; end if; else k0<=k0+'1'; end if; end if;end if; end process; U5:process( rst,start,cost_clk) --計費begin if rst='1' then --計價結束c0<="0000";c1<="0000"; c2<="0000";c3<="0000"; elsif start='1' and en0='0' then --還在起步范圍c0<="0000";c1<="1000"; --8.0元c2<="0000"; c3<="0000"; elsif cost_clk'event and cost_clk='1' then --50m每個上升沿/中途停車等待if en0='1' and start='1' then if c0="1001" then c0<="0000"; if c1="1001" then c1<="0000"; if c2="1001" then c2<="0000";if c3="1001" thenc3<="0000";elsec3<=c3+'1';end if;else c2<=c2+'1'; end if; else c1<=c1+'1'; end if; else c0<=c0+'1'; end if; end if;end if; end process;p4:process(clk2)beginif clk2'event and clk2='1' thencase show iswhen "000" =>show <= "001";numViewOutputSg <= "11111110";bt <= c0;flag <= 0;when "001" =>show <= "010";numViewOutputSg <= "11111101";bt <= c1;flag <= 1;when "010" =>show <= "011";numViewOutputSg <= "11111011";bt <= c2;flag <= 0;when "011" =>show <= "100";numViewOutputSg <= "11110111";bt <= c3;flag <= 0;when "100" =>show <= "101";numViewOutputSg <= "11101111";bt <= k0;flag <= 0;when "101" =>show <= "110";numViewOutputSg <= "11011111";bt <= k1;flag <= 1;when "110" =>show <= "111";numViewOutputSg <= "10111111";bt <= m0;flag <= 0;when "111" =>show <= "000";numViewOutputSg <= "01111111";bt <= m1;flag <= 0;end case;end if;end process p4;p5:process(bt, flag)beginif flag = 1 thencase bt iswhen "0000" => numViewOutputBt <= "11111110";--0when "0001" => numViewOutputBt <= "10110000";when "0010" => numViewOutputBt <= "11101101";when "0011" => numViewOutputBt <= "11111001";when "0100" => numViewOutputBt <= "10110011";when "0101" => numViewOutputBt <= "11011011";when "0110" => numViewOutputBt <= "11011111";when "0111" => numViewOutputBt <= "11110000";when "1000" => numViewOutputBt <= "11111111";when "1001" => numViewOutputBt <= "11111011";--9when others => NULL;end case;elsif flag = 0 thencase bt iswhen "0000" => numViewOutputBt <= "01111110";--0when "0001" => numViewOutputBt <= "00110000";when "0010" => numViewOutputBt <= "01101101";when "0011" => numViewOutputBt <= "01111001";when "0100" => numViewOutputBt <= "00110011";when "0101" => numViewOutputBt <= "01011011";when "0110" => numViewOutputBt <= "01011111";when "0111" => numViewOutputBt <= "01110000";when "1000" => numViewOutputBt <= "01111111";when "1001" => numViewOutputBt <= "01111011";--9when others => NULL;end case;end if;end process p5;
end;
四、仿真
五、測試
1、初始狀態(行程未開始)
2、行程開始,計價開始(3km內)
3、里程達到3km
4、里程超過3km(切換計價模式)
?5、停車等待,開始按時計價
?6、行程繼續(切換計價模式)
?7、行程結束
?8、演示視頻
EDA出租車計價系統演示視頻(2x)
六、課程學習或實驗過程中出現的問題
1、對端口模式的理解不透徹,導致在分析教材部分例題和其他代碼的過程中出現問題,尤其以雙向端口(INOUT)最難學習與掌握;
2、使用EDA實驗板時,對于設置好的按鈕在進行按鍵操作的過程中,信號穩定的前后出現了多個不穩定的脈沖現象,而正常情況下一次按鍵操作理論上應只產生一個邊沿信號脈沖(如下圖所示);
3、本題中需要實現對價格費用、里程和等待時間的顯示,而在VHDL硬件描述語言的編程設計過程中,在Pin Planner(引腳規劃器)的設置中各個數字都出現了4個引腳選擇,而EDA實驗板上只有8個數碼管而不夠使用;
4、?實驗過程中設置引腳后發現沒有解決小數點的問題,在應該精確到0.1的地方沒有顯示小數點;
七、對各種問題的解決過程、方法和結果
(注:序號對應第六部分中的問題)
1、對于之前看過的一道程序中的以下部分源碼(其中,程序中DataB為雙向端口INOUT):
......
DataB <= Din when CE = '1' and Rd = '0' else
Others => 'Z';
Dout <= DataB when CE = '1' and Rd = '1' else
Others => '1';
......
? ? ? 通過查閱相關書籍及在CSDN等平臺上查找相關的資料,了解到教材上僅僅只提到了端口的雙向模式允許信號雙向傳輸(即既可以進入實體,也可以離開實體),可代替IN、OUT和BUFFER。但在實際編程時還必須要注意的細節是,當雙向端口DataB作為輸出且空閑時,必須將其設為高阻態掛起,即在上述程序的部分代碼中需要有“Others => 'Z'”這一條語句,否則實現后會導致端口死鎖;而當雙向端口DataB作為有效輸入時,DataB輸出必須處于高阻態,即在上述程序的部分代碼中需要有“CE = '1' and Rd = '1'”這一條語句,否則同樣也會出現問題。
2、通過對實驗電路板的結構分析以及在其他相關課程(如計算機組成原理)的學習中,了解到本次實驗中使用的實驗電路板的按鍵為機械式開關結構,由于機械式開關的核心部件為彈性金屬簧片,因而在開關切換的瞬間會在接觸點出現來回彈跳的現象,因此雖然看上去只是進行了一次按鍵,結果卻可能是在按鍵的信號穩定的前后出現了多個不規則的脈沖,如果將這樣的脈沖信號直接送給微處理器進行掃描采集的話,將可能把按鍵穩定前后出現的脈沖信號當作按鍵信號,這就會導致人為的一次按鍵但是微處理器以為是執行多次按鍵的現象。因此為了確保按鍵識別的準確性,應當使得在按鍵信號產生抖動的情況之下禁止進入狀態輸入,為此就必須對按鍵進行消抖處理,消除出現抖動時的不穩定的、隨機的電壓信號。測試過程中發現機械式按鍵的抖動次數、抖動時間、抖動波形都是隨機的,而不同類型的按鍵其最長抖動時間也有差別,進一步查閱相關資料可知抖動時間的長短和按鍵的機械特性有關,一般為5-10ms,但是有些按鍵的抖動時間可達到20?ms,甚至更長。所以在具體設計過程中要具體分析,根據實際情況來調整設計。
3、分析代碼后發現,在設置端口數據時將端口寬度設置為4(如下):
????????未對輸出端口進行分組處理,因此會導致每個數字都按照四位二進制的形式有4個輸出,因此可以將原先定義在結構體內的信號經譯碼后打包送到對應輸出端口(一種可行的解決方案如下):
4、根據設置的信號量分析可知,應當在c1和k1處固定顯示小數點,經過分析決定設置一個flag標記位,在片選模塊中單獨標記c1和k1兩處(flag=1),其余部分不做標記(flag=0),最后在段選模塊中根據flag的值進行輸出,即flag=1時段選信號的第一位置“1”,即顯示小數點,而flag=0時則相反。
八、總結
????????通過本次數字系統設計實驗設計,首先我學到了很多關于數字邏輯和數字系統的知識,基本理解了一些系統的設計理念以及設計方案的制定和流程。同時通過對VHDL硬件描述語言的學習,我深刻的感受到了軟硬件相結合的強大,基于軟件設計硬件的方法十分高效,同時代碼相對于電路更具備可讀性,能更好的理解系統設計原理和方法。本次我選擇的出租車計價器設計難度中等偏上,在我完成實驗設計的過程中遇到了很多問題,比如對VHDL硬件描述語言的基本語法掌握得不夠熟練,亦或者是對數字邏輯的相關知識和一些基礎的實驗元件的認識不足,因此設計過程舉步維艱,對信號量的設計也有很多的不合理之處,比如輸出信號量cost、kilogram和minute,最早設計的時候采取的方案是單獨設置輸出信號量out static_logic_vector(3 downto 0),輸出對應的二進制數,但沒有考慮到數碼管的譯碼部分,從而導致無法正確選擇合適的引腳進行數碼顯示。之后我意識到這個問題后決定將其包裝一下,對四位BCD碼進行譯碼后,再送入后續改進設計的段選模塊和片選模塊進行輸出,從而解決了這一問題。在完成實驗之后我也嘗試思考一些實驗過程中的問題,嘗試去改進一些方法策略,后來發現了按鍵抖動的問題之后設計了消除抖動的模塊,為了進行更好的演示將等待時間計時的分鐘顯示改為秒鐘顯示……通過不斷的思考,我也漸漸掌握了一定的設計能力,培養了創新思想。但僅僅是對VHDL硬件描述語言的掌握是遠遠不夠完成實驗的,管腳的連接、實驗板的操作或是軟件的安裝使用都出現過問題,而只是學習書本上的知識也是不夠的,書本上的知識往往偏向理論,實際實驗涉及到的范圍往往更廣,因此也應該要不斷學習,自覺拓展知識面,開拓視野,一步一步的完成每一件學習任務,這樣才能更好的掌握EDA這門課程。