?? myself_uart_vhdl.txt
字號:
總體設計思路:
1)預計實現效果:通過串口調試助手,由PC發送數據給小系統板,板子接收到的數據在數碼管上即時顯示
并且通過發送模塊發送回PC,在串口調試助手上顯示出來。
2)模塊劃分:六大模塊,1:波特率發送模塊(clock_tx);2:fpga_to_pc 發送模塊 (uart_tx);3:采樣時鐘模塊(uart_re);
4:pc_to_fpga接收模塊(uart_re);5:數碼管顯示模塊(segment);6:頂層模塊(top_level).
3)基本設定:板上晶振頻率40M,發送數據波特率為19200,數據格式為1bit起始位(0)+8bit有效數據+1bit停止位(1)構成一幀數據.
注:
1)一般編寫程序在quartus中完成,在該目錄下新建modelsim的仿真目錄,
調用modelsim仿真時,添加quartus中的原始設計文件時,
若選擇copy形式,則quartus與modelsim中文件互相不影響;若選擇refenrence形式,
則quartus中設計文件改變時,會影響到modelsim中文件.
2)若要精確的指向電平跳變點,可以使用波形操作界面上的圖標find next transition.
3)判斷“=”需要USE IEEE.STD_LOGIC_UNSIGNED.ALL; '_'不能使用在STD_LOGIC_VECTOR中
--模塊1:clock_tx:發送時鐘模塊,即波特率發生器.
1) 設定
預設發送數據波特率為19200,分頻時鐘需要利用計數器來實現,計數值為40M/19200=2084.
2) 設計
通過計數器來分頻,在原始時鐘周期下計數,判斷滿計數值一半時清零,同時對中間值取反,否則自加.則該中間值即為所分頻率.
3) 仿真
在modelsim中進行功能仿真,原始時鐘為40M(25000ns),分頻周期為52150000ns。
4) 調試
下載程序,本部分沒有出現問題.
--design file:clock_tx
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY clock_tx IS
PORT(
clk_in: IN STD_LOGIC;
rst: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END clock_tx;
ARCHITECTURE behav OF clock_tx IS
SIGNAL clk_count: STD_LOGIC_VECTOR(15 DOWNTO 0);
SIGNAL clk_bit: STD_LOGIC;
BEGIN
PROCESS(rst,clk_in)
BEGIN
IF rst='0' THEN
clk_count<="0000000000000000";
clk_bit<='0';
ELSIF clk_in'EVENT AND clk_in='1' THEN
IF(clk_count=1042) THEN
clk_count<="0000000000000000";
clk_bit<=not clk_bit;
ELSE
clk_count<=clk_count+1;
END IF;
END IF;
END PROCESS;
clk_out<=clk_bit;
END behav;
--testbench: tb1
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY tb1 IS
END tb1;
ARCHITECTURE be OF tb1 IS
COMPONENT clock_tx
PORT(
clk_in: IN STD_LOGIC;
rst: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
SIGNAL clk_in: STD_LOGIC;
SIGNAL rst: STD_LOGIC;
SIGNAL clk_out: STD_LOGIC;
CONSTANT clk_period: time:=25 ns;
BEGIN
uut:clock_tx PORT MAP(clk_in=>clk_in,rst=>rst,clk_out=>clk_out);
clk_gen: PROCESS
BEGIN
clk_in<='0';
WAIT FOR clk_period/2;
clk_in<='1';
WAIT FOR clk_period/2;
END PROCESS;
rst_gen: PROCESS
BEGIN
rst<='0';
WAIT FOR clk_period;
rst<='1';
WAIT ;
END PROCESS;
END be;
--模塊2 uart_tx:發送模塊
1) 設定
根據模塊1生成的發送時鐘波特率,按位將數據通過串口發送給PC機,在串口通信助手上顯示出來.
2)設計
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY uart_tx IS
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
send_data: IN STD_LOGIC; --來自接收模塊,表示一幀數據準備好,等待本模塊接收.
data_in: IN STD_LOGIC_VECTOR(7 DOWNTO 0); --data from uart_re
busy: OUT STD_LOGIC; --when high ,it means data is sending
data_out: OUT STD_LOGIC --parallel data in , serial data out
);
END uart_tx;
ARCHITECTURE behav OF uart_tx IS
COMPONENT clock_tx
PORT(
clk_in: IN STD_LOGIC;
rst: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
COMPONENT clock_re
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
SIGNAL clk_tx: STD_LOGIC;
SIGNAL data_count: INTEGER RANGE 0 TO 10;
SIGNAL data_buf: STD_LOGIC_VECTOR(9 DOWNTO 0);
SIGNAL data_temp: STD_LOGIC_VECTOR(7 DOWNTO 0);
signal clk_re: std_logic:='0';
signal num: std_logic_vector(2 downto 0);
BEGIN
uut: clock_tx PORT MAP(clk_in,rst,clk_tx);
uut_re:clock_re PORT MAP(rst,clk_in,clk_re);
PROCESS(rst,clk_in)
BEGIN
IF rst='0' THEN
data_temp<="11111111";
ELSIF clk_in'EVENT AND clk_in='1' THEN
IF send_data='1' THEN --uart_re模塊數據準備好,將其接收進來.
data_temp<=data_in;
else
data_temp<=data_temp;
END IF;
END IF;
END PROCESS;
process(rst,clk_re) --因為send_data=1只持續很短的時間,約為1個clk_re周期,所以敏感信號采樣clk_re
begin
if rst='0' then
data_count<=10;
num<="000";
elsif clk_re'event and clk_re='1' then
if send_data='1' and data_count=10 then --數據接收進來,同時總線空閑.
data_count<=0; --重新開始一次新的發送
elsif data_count<10 then
num<=num+"001";
if num="111" then --8個clk_re周期構成一個clk_tx周期,在此周期下數據位加1
num<="000";
data_count<=data_count+1; --data_count為10后,不清0,保證接收一幀數據,發送一幀數據.
end if;
else
data_count<=data_count; --對應send_data/=1 and data_count=10
end if;
end if;
end process;
--之所以data_count要計數到10,是因為根據上一進程,計數歸零是在接收進來數據的同時,
--而此時clk_tx并沒有觸發,參考modelsim仿真波形可知,發送第一位數據的起始,是在data_count
--數值為0和1處,所以,要發送完10位數據,停止位所占的時間在data_count為9時開始,在為10時
--才能結束,因此data_count必須計數到10,否則有可能檢測不到停止位,從而產生錯誤.
process(rst,clk_tx)
begin
if rst='0' then
data_buf<="1111111111"; --保證總線空閑時被拉高
elsif clk_tx'event and clk_tx='1' then
if data_count=0 then --data_count為0時,裝入待發送數據
data_buf<='1' & data_temp & '0';
elsif data_count<10 then --移位發送
data_buf<='1' & data_buf(9 downto 1); --高位為1,保證發送完一幀后,總線拉高.
elsif data_count=10 then --為10后,data_buf為全1,保證總線拉高.
data_buf<=data_buf;
end if;
end if;
end process;
busy<='1' WHEN data_count<10 ELSE '0';
data_out<=data_buf(0) ; --發送最低位.
END behav;
testbench: tb2
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
ENTITY tb2 IS
END tb2;
ARCHITECTURE be OF tb2 IS
COMPONENT uart_tx IS
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
send_data: IN STD_LOGIC;
data_in: IN STD_LOGIC_VECTOR(7 DOWNTO 0);
busy: OUT STD_LOGIC;
data_out: OUT STD_LOGIC
);
END COMPONENT;
SIGNAL rst: STD_LOGIC;
SIGNAL clk_in: STD_LOGIC;
SIGNAL send_data: STD_LOGIC;
SIGNAL data_in: STD_LOGIC_VECTOR(7 DOWNTO 0);
SIGNAL busy: STD_LOGIC;
SIGNAL data_out: STD_LOGIC;
CONSTANT clk_in_period: time:=25ns;
CONSTANT clk_tx_period: time:=50064ns;
BEGIN
uut: uart_tx PORT MAP
(rst=>rst,clk_in=>clk_in,send_data=>send_data,data_in=>data_in,busy=>busy,data_out=>data_out);
clk_gen: PROCESS
BEGIN
clk_in<='0';
WAIT FOR clk_in_period/2;
clk_in<='1';
WAIT FOR clk_in_period/2;
END PROCESS;
rst_gen: PROCESS
BEGIN
rst<='0';
WAIT FOR clk_tx_period;
rst<='1';
WAIT ;
END PROCESS;
others_gen: PROCESS
BEGIN
send_data<='0';
data_in<="00000000";
WAIT FOR clk_tx_period;
send_data<='1';
data_in<="10101010";
WAIT FOR clk_tx_period/8;
send_data<='0';
data_in<="11111111";
WAIT;
END PROCESS;
END be;
--模塊3 接收時鐘
1)設定:
接收時鐘是波特率時鐘的8倍頻,即clk_re=clk_tx/8.
這樣可以保證在發送時鐘周期的特定時刻對數據進行采樣.
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
--USE IEEE.NUMERIC_STD.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY clock_re IS
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END clock_re;
ARCHITECTURE behav OF clock_re IS
SIGNAL clk_count: STD_LOGIC_VECTOR(9 DOWNTO 0);
SIGNAL clk_bit: STD_LOGIC;
BEGIN
PROCESS(rst,clk_in)
BEGIN
IF rst='0' THEN
clk_count<="0000000000";
clk_bit<='0';
ELSIF clk_in'EVENT AND clk_in='1' THEN
IF(clk_count=130) THEN
clk_count<="0000000000";
clk_bit<=not clk_bit;
ELSE
clk_count<=clk_count+1;
END IF;
END IF;
END PROCESS;
clk_out<=clk_bit;
END behav;
--testbench:tb3
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY tb3 IS
END tb3;
ARCHITECTURE be OF tb3 IS
COMPONENT clock_re
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
SIGNAL clk_in: STD_LOGIC;
SIGNAL rst: STD_LOGIC;
SIGNAL clk_out: STD_LOGIC;
CONSTANT clk_period: time:=25ns;
BEGIN
uut:clock_re PORT MAP(clk_in=>clk_in,rst=>rst,clk_out=>clk_out);
clk_gen: PROCESS
BEGIN
clk_in<='0';
WAIT FOR clk_period/2;
clk_in<='1';
WAIT FOR clk_period/2;
END PROCESS;
rst_gen: PROCESS
BEGIN
rst<='0';
WAIT FOR 25ns;
rst<='1';
WAIT ;
END PROCESS;
END be;
--模塊4 接收模塊
1)設定
-- 在接收數據時,如果接收時鐘與發送時鐘相同的話,則只需要輕微的頻率偏移,就能產生極大的誤差,
--所以,為了提高接收的準確性,減少誤碼率,接收時鐘一般都必須采用高頻時鐘,本例中,每一位數據都采用8倍頻的
--波特率來對數據進行采樣,即在每發送一位數據的時間內,接收模塊已經進行了8次接收,這樣保證了數據的正確性。
--接收數據起始位與停止位采用四次采樣判斷,即在第2,3,4,5個接收數據時鐘周期,對數據進行判斷,在起始位,若四次采樣均為0,
--則判定該位為0,即為有效的起始位,在停止位,則四次為1,判定為1.
--接收數據有效部分采用三次采樣判斷,即在第4,5,6個接收數據時鐘周期,對數據進行判斷,也即是在第4,5,6次采樣值上,
--判斷接收到的數據的值,判斷時采用多數表決原則,即三次采樣中有兩次為某個值,則判斷為該值。
2)設計
采樣狀態機實現,分為四個狀態,初始化,采樣,得到數值,數據準備好.
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
ENTITY uart_re IS
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
ser_in: IN STD_LOGIC;--來自外部(PC機)的串行輸入數據
data_ready: INOUT STD_LOGIC;--一幀數據接收完畢,可以輸出進行處理.
data_out: OUT STD_LOGIC_VECTOR(7 DOWNTO 0)--有效數據8位并行輸出
);
END uart_re;
ARCHITECTURE behav OF uart_re IS
COMPONENT clock_re
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
COMPONENT clock_tx
PORT(
rst: IN STD_LOGIC;
clk_in: IN STD_LOGIC;
clk_out: OUT STD_LOGIC
);
END COMPONENT;
--串口接收一般采用狀態機實現,定義為:初始化,采樣,得到數據,幀格式準備好.
TYPE uart_states IS (idle,sampling,get_data,data_ok);
SIGNAL state: uart_states;
SIGNAL clk_re: STD_LOGIC;
SIGNAL clk_tx: STD_LOGIC;
SIGNAL ser_buf1: STD_LOGIC; --級聯D觸發器,接收輸入數據,根據跳變沿判斷起始位到來.
SIGNAL ser_buf2: STD_LOGIC;
SIGNAL bit_count: STD_LOGIC_VECTOR(3 DOWNTO 0);--位計數器
SIGNAL data_buf: STD_LOGIC_VECTOR(9 DOWNTO 0);
SIGNAL ser_sig2: STD_LOGIC;--采樣值寄存器
SIGNAL ser_sig3: STD_LOGIC;
SIGNAL ser_sig4: STD_LOGIC;
SIGNAL ser_sig5: STD_LOGIC;
SIGNAL ser_sig6: STD_LOGIC;
SIGNAL sample: STD_LOGIC; --采樣點處理所得結果值
SIGNAL sample_count: STD_LOGIC_VECTOR(2 DOWNTO 0);--采樣個數計數值
BEGIN
uut_re:clock_re PORT MAP(rst,clk_in,clk_re);
uut_tx:clock_tx PORT MAP(rst,clk_in,clk_tx);
process(rst,clk_tx)
begin
if rst='0' then
ser_buf1<='1'; --對應總線空閑時為1
ser_buf2<='1';
elsif clk_tx'event and clk_tx='1' then --clk_tx觸發,因為輸入串行數據在clk_tx周期下變化,保證能夠采樣到變化值,
ser_buf1<=ser_in; --又保證了ser_buf1/2的值會保持足夠長的時間,使得clk_re時鐘下能夠采樣得到
ser_buf2<=ser_buf1; --所需要的正確值.
end if;
end process;
process(rst,clk_re) --clk_re觸發,保證采樣
begin
if rst='0' then
state<=idle; --復位下為初始狀態
data_ready<='0';--不發送數據
sample_count<="000";--采樣計數器清0
bit_count<="0000"; --位計數器清0
sample<='1'; --采樣結果值置1,與sampling狀態下第一個起始位采樣值為0區分
ser_sig2<='1';--采樣寄存器初始值全為1,保證了采樣起始位時,根據要求,必須全變化為0方能得到代表起始位的0
ser_sig3<='1';
ser_sig4<='1';
ser_sig5<='1';
ser_sig6<='1';
data_buf<="0000000000";
elsif clk_re'event and clk_re='1' then
case state is
when idle=>
if ser_buf2='1' and ser_buf1='0' then --ser_buf1先變化,此處對應為接收到一個下跳變
state<=sampling; --檢測到下跳變脈沖,啟動采樣
else
state<=idle;
end if;
data_ready<='0';
data_buf<="1111111111"; --對應總線空閑掛起為1
bit_count<="0000";
sample_count<="000";
when sampling=>
if bit_count="0000" then --start bit,代表起始位
--采樣賦值來源是根據ser_buf1,個人理解也是針對先變化的這個值,
--參考一些程序是根據ser_buf2,
--總之可以先仿真,再根據仿真圖上波形,來決定是ser_buf1/2能夠得到正確采樣結果.
--或者可以在實際運行時,分別比較兩者的正確性.
if sample_count="001" then --第二個采樣點
ser_sig2<=ser_buf1;
elsif sample_count="010" then --第三個采樣點
ser_sig3<=ser_buf1;
elsif sample_count="011" then --第四個采樣點
ser_sig4<=ser_buf1;
elsif sample_count="100" then --第五個采樣點
ser_sig5<=ser_buf1;
end if;
--采樣結果值采樣或邏輯,即全為0時,sample為方為1
--采樣結果值應該在本狀態下給出,若在get_data中給出,因為信號賦值的延遲,
--將導致被判斷的采樣結果值不是當前值,而是前一個數值,導致錯誤.
sample<=ser_sig2 or ser_sig3 or ser_sig4 or ser_sig5;
elsif bit_count="1001" then --stop bit,停止位
if sample_count="001" then
ser_sig2<=ser_buf1;
elsif sample_count="010" then
ser_sig3<=ser_buf1;
elsif sample_count="011" then
ser_sig4<=ser_buf1;
elsif sample_count="100" then
ser_sig5<=ser_buf1;
end if;
sample<=ser_sig2 and ser_sig3 and ser_sig4 and ser_sig5;
else --有效數據位(8位)
if sample_count="011" then --第四個采樣點
ser_sig4<=ser_buf1;
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -