久久久久久久999_99精品久久精品一区二区爱城_成人欧美一区二区三区在线播放_国产精品日本一区二区不卡视频_国产午夜视频_欧美精品在线观看免费

 找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 5144|回復: 0
打印 上一主題 下一主題
收起左側

FPGA學習–串口發(fā)送模塊與驗證

[復制鏈接]
跳轉到指定樓層
樓主
ID:108531 發(fā)表于 2016-3-12 22:12 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
本帖最后由 51hei人人 于 2016-3-12 22:14 編輯

      
一、Rs232串口協(xié)議
串口通信指串口按位(bit)發(fā)送和接受字節(jié)。雖然比并行通信要慢,但是其物理線路簡單并且通信距離
長,可達到1200米。
物理連接:








A發(fā)送數(shù)據(jù)時通過tx將數(shù)據(jù)一位一位的傳輸給B的rx ,表現(xiàn)出來的就是tx線的高低電平,B就可以
通過rx來檢測高低電平來確定數(shù)據(jù)1、0。
由于A、B之間并沒有時鐘線,不能像I2C那樣,可以通過時鐘為高時檢測數(shù)據(jù)腳的電平狀態(tài)來確定數(shù)
據(jù),那么在串口協(xié)議中B應當如何確定何時采集rx端口的電平來作為數(shù)據(jù)呢?我們常常可以聽到波特率為9600或者115200等這些數(shù)值,那么B就是通過這個數(shù)值來確定何時采集rx端口的電平。
        
以9600為例子,一幀數(shù)據(jù)的格式(10位):起始位、數(shù)據(jù)域(8bit)、停止位。
9600波特率 -->  9600Hz  -->  1/9600(周期)  -->  0.001041666666666667(秒) –->  約 104167(ns)
也就是說A的tx發(fā)送的每一個位的數(shù)據(jù)所保持的時間都必須在 104167ns 這個時間。而B也必須在這
個時間內至少采集一次rx的電平狀態(tài)來得到數(shù)據(jù)。即A、B雙方都是以相同的速度去發(fā)送、采集數(shù)據(jù)。
啟動發(fā)送時,先將Tx拉低作為啟動信號,發(fā)送結束后則拉高Tx作為停止信號,空閑時Tx應為高電平狀態(tài)。
串口發(fā)送模塊所必須具備的兩個部分:
1、波特率的產生
采用計數(shù)分頻的使能時鐘方式產生波特率,那么計數(shù)值應如何計算呢。
9600bps 約等于 104167ns,假如系統(tǒng)時鐘為 50MHz,那么一個時鐘周期為 (1/50)*1000 = 20ns。
104167ns / 20ns = 5208次,即數(shù)系統(tǒng)時鐘數(shù)5208次即為104167ns。

      
baud_set
   
      
波特率
   
      
波特率周期
   
      
波特率計數(shù)值
   
      
System_clk_period = 20計數(shù)值
      
(從0開始計算所以-1)
   
      
0
   
      
9600
   
      
104167ns
   
      
104167/ System_clk_period
   
      
5208-1
   
      
1
   
      
19200
   
      
52083ns
   
      
52083/ System_clk_period
   
      
2604-1
   
      
2
   
      
38400
   
      
26041ns
   
      
26041/ System_clk_period
   
      
1302-1
   
      
3
   
      
57600
   
      
17361ns
   
      
17361/ System_clk_period
   
      
868-1
   
      
4
   
      
115200
   
      
8680ns
   
      
8680/ System_clk_period
   
      
434-1
   
2、數(shù)據(jù)發(fā)送模塊
二、FPGA 程序框圖
串口發(fā)送模塊的端口框圖:
        
        輸入:
Send_En:發(fā)送使能
        Data_Byte[7:0]:要發(fā)送的數(shù)據(jù)
        Baud_Set[2:0]:波特率選擇
        Clk:系統(tǒng)時鐘
        Rst_n:復位信號
        
輸出:
Rs232_Tx:數(shù)據(jù)發(fā)送引腳
Tx_Done:發(fā)送完成通知信號(1:表示發(fā)送完成)
UART_state:模塊工作狀態(tài)(1:正在發(fā)送數(shù)據(jù) 0:發(fā)送完成或空閑狀態(tài))
        串口發(fā)送模塊詳細結構圖:

功能模塊描述:

        DR_LUT:查表模塊,根據(jù) Baud_Set[2:0] 選擇的波特率去查表得到計數(shù)分頻所需要的計數(shù)值即bps_DR[15:0]。
        Div_Cnt: 計數(shù)分頻模塊,根據(jù)bps_DR[15:0] 來產生bps_clk作為tx發(fā)送數(shù)據(jù)位的節(jié)拍,即來一個bps_clk就發(fā)送一個數(shù)據(jù)位。該受en_cnt信號控制,en_cnt 為 0 時Div_Cnt模塊不計數(shù),也就不會產生 bps_clk,也就不會發(fā)送數(shù)據(jù)。
        bps_cnt:數(shù)據(jù)位計數(shù)模塊,對bps_clk進行計數(shù),輸出bps_cnt_q[3:0],用于控制發(fā)送的數(shù)據(jù)位數(shù),完成一幀數(shù)據(jù)長度的控制,當數(shù)到第11個bps_clk時會置高Tx_Done信號。
        MUX10:10選1多路器,根據(jù)bps_cnt_q[3:0] 來輸出起始位、8位數(shù)據(jù)、停止位來設置 Rs232_Tx 信號。其實這里應該是11選1多路器,第0個為輸出停止位信號。
        MUX2_1、MUX2_2:二選一多路器,用于形成具有優(yōu)先級的狀態(tài)控制機制。當Send_En信號為1時,那么MUX2_1就會直接忽略MUX2_2    ,當Send_En信號為0時才會根據(jù)MUX2_2的選擇來控制UART_state.
整個邏輯控制流程:
1、當Send_En置高一個時鐘周期時 [MUX2_1] 輸出1到UART_state和en_cnt,此時 UART_state和en_cnt均為1。
下一個時鐘來臨之后,[MUX2_1] 取 [MUX2_2] 的狀態(tài),由于 [MUX2_2] 取自Tx_Done信號,而Tx_Done為0,所以 [MUX2_2] 取的是en_cnt的信號,即UART_state  ==  en_cnt  ==  [MUX2_2] ==  1。
只要Tx_Done信號為1,則 [MUX2_2] 就會選擇輸出0,從而改變 UART_state、en_cnt信號,注意 [bsp_cnt] 模塊的clr信號也受Tx_Done控制。
2、en_cnt為1觸發(fā) [Div_Cnt] 模塊工作,[Div_Cnt] 開始以bps_DR[15:0] 所設置的計數(shù)間隔輸出bps_clk信號。
3、bsp_cnt模塊檢測到bps_clk,開始數(shù)bps_clk個數(shù),并輸出bps_cnt_q[3:0] 給 [MUX10] 多路器。bps由于clr信號來自Tx_Done信號,所以clr為0,不會清0計數(shù)。若bps_cnt_1[3:0]等于11,即bps_clk的個數(shù)為11,則輸出1給Tx_Done,出現(xiàn)連鎖反應:
1、clr信號變?yōu)?:bsp_cnt模塊計數(shù)清零
2、[MUX2_2] 輸出0到 [MUX2_1] 再到UART_start 再到 en_cnt 導致 [Div_Cnt] 停止輸出bsp_clk。
3、整個發(fā)送模塊也就停止發(fā)送數(shù)據(jù)。
4、MUX10:通過視頻中所寫的代碼來看,這里應該是11選1多路器,0為Tx空閑時的狀態(tài),即為高電平,1為起始位,2~9為要發(fā)送的數(shù)據(jù),即Data_Byte[7:0]。10則是停止位。根據(jù)bps_cnt_q[3:0]來確定要選擇數(shù)據(jù)幀的哪一個位輸出到r_R232_Tx。
5、至此,整個邏輯部分完成。
三、代碼實現(xiàn)
代碼1:(代碼與視頻所寫的有點不太一樣,修改了幾句代碼是為了盡量符合上面的框圖設計)
module mytest(clk, rst_n, data_byte, send_en, baud_set, rs232_tx, tx_done, uart_state);

    input clk;                        // 系統(tǒng)時鐘
    input rst_n;                    // 復位
    input[7:0] data_byte;        // 要發(fā)送的數(shù)據(jù)
    input send_en;                    // 啟動發(fā)送
    input[2:0] baud_set;            // 波特率選擇
   
    output reg rs232_tx;
    output reg tx_done;                // 發(fā)送完畢通知    1:發(fā)送完畢 0:正在發(fā)送
    output reg uart_state;            // 發(fā)送狀態(tài) 1:正在發(fā)送數(shù)據(jù) 0:空閑狀態(tài)
   
    reg bps_clk;                    // 波特率時鐘
    wire en_cnt;                    // 計數(shù)使能 1:使能 0:失能
    reg[15:0] div_cnt;            // 分頻計數(shù)器
    reg[15:0] bps_dr;                // 分頻計數(shù)最大值
    reg[3:0]    bps_cnt;                // 波特率時鐘計數(shù)器
    wire clr;                        // 清零信號
    reg[7:0] r_data_byte_buff;    // 緩沖區(qū),用于存儲需要發(fā)送的數(shù)據(jù),避免在發(fā)送過程中數(shù)據(jù)突然改變
   
   
    localparam START_BIT    = 1'b0;
    localparam STOP_BIN    = 1'b1;
   
   
    // 串口工作狀態(tài)
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            uart_state <= 1'b0;
        else if(send_en)
            uart_state <= 1'b1;
        else if(tx_done)                // 發(fā)送完畢
            uart_state <= 1'b0;
        else
            uart_state <= uart_state;
   
    end
   
    assign en_cnt = uart_state;
   
    // 用于啟動發(fā)送時鎖存即將要發(fā)送的數(shù)據(jù)
    // 這樣就可以避免在發(fā)送的過程中數(shù)據(jù)突然改變導致發(fā)送的數(shù)據(jù)不正確。
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            r_data_byte_buff <= 8'd0;
        else if(send_en)               
            r_data_byte_buff <= data_byte;    // 啟動發(fā)送則鎖存最新的數(shù)據(jù)
        else
            r_data_byte_buff <= r_data_byte_buff;
   
    end
   
    // 【DR_LUT】 通過查表的方式將波特率轉換為對應的分頻計數(shù)最大值
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            bps_dr <= 16'd5207;                // 9600bps
        else begin
            case(baud_set)        // 查找表
                0:bps_dr <= 16'd5207;            // 9600bps
                1:bps_dr <= 16'd2603;         // 19200bps
                2:bps_dr <= 16'd1301;         // 38400bps
                3:bps_dr <= 16'd0867;            // 57600bps
                4:bps_dr <= 16'd0433;            // 115200bps
                default:bps_dr <= 16'd5207;    // 9600bps            
            endcase
        end
   
    end
   
    // 【Div_Cnt】 計數(shù)功能
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            div_cnt <= 16'd0;
        else if(en_cnt)    begin        
            if(div_cnt == bps_dr)
                div_cnt <= 16'd0;
            else
                div_cnt <= div_cnt + 1'b1;     
        end else
            div_cnt <= 16'd0;
   
    end
   
    // 【Div_Cnt】 bps_clk 時鐘產生
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            bps_clk <= 1'b0;
        else if(div_cnt == 16'd1)        // 當計數(shù)器剛開始計數(shù)時就產生一個時鐘
            bps_clk <= 1'b1;                // 這樣就相當于啟動發(fā)送時就立即開始發(fā)送數(shù)據(jù)
        else
            bps_clk <= 1'b0;
   
    end
   
     // 【bps_cnt】 bps 計數(shù)(即發(fā)送的數(shù)據(jù)位數(shù)計數(shù))
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            bps_cnt <= 4'd0;
        else if(clr)
            bps_cnt <= 4'd0;
        else if(bps_clk)
            bps_cnt <= bps_cnt + 1'b1;
        else
            bps_cnt <= bps_cnt;
   
    end
   
    // 【MUX10】、【r_R232_Tx】 盡量避免組合邏輯直接輸出,輸出是有毛刺的可能會出現(xiàn)不太穩(wěn)定的情況
    // 發(fā)送數(shù)據(jù)模塊
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            rs232_tx <= STOP_BIN;            // 起始位為低電平,所以空閑時為高電平即停止位
        else begin
        
            case(bps_cnt)
                0:rs232_tx <= STOP_BIN;        // 空閑時 bps_cnt 會一直為 0
                1:rs232_tx <= START_BIT;        // 起始位
                2:rs232_tx <= r_data_byte_buff[0];
                3:rs232_tx <= r_data_byte_buff[1];
                4:rs232_tx <= r_data_byte_buff[2];
                5:rs232_tx <= r_data_byte_buff[3];
                6:rs232_tx <= r_data_byte_buff[4];
                7:rs232_tx <= r_data_byte_buff[5];
                8:rs232_tx <= r_data_byte_buff[6];
                9:rs232_tx <= r_data_byte_buff[7];
                10:rs232_tx <= STOP_BIN;        // 停止位
                default:rs232_tx <= STOP_BIN;
            endcase
                    
        end
   
    end
   
    // 檢測一幀數(shù)據(jù)是否發(fā)送完成
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            tx_done <= 1'b0;
        else if(bps_cnt == 4'd11)
            tx_done <= 1'b1;
        else
            tx_done <= 1'b0;
    end

    assign clr = tx_done;            // 當完成一幀數(shù)據(jù)發(fā)送之后清除 bps 計數(shù)器
   
endmodule
完整的時序圖

分析:


問題:tx_done、bps_cnt 會分別維持兩個時鐘周期的 1 和 11

原因:
因為當 bps_cnt 變?yōu)?11 的時候,需要等第2個時鐘周期才會被采樣到。當采樣到之后 tx_done = 1,因為 assign clr 也立即變?yōu)?1 ,而 clr 為 1 的時候也需要等第3個時鐘周期才能被 bps_cnt 采樣到變?yōu)?0,而 bps_cnt 為 0 時,需要等到第4個時鐘周期才能被 tx_done 采樣,才會變?yōu)?0 。
代碼2:(代碼與視頻所修改的方式不太一樣,修改了幾句代碼是為了盡量符合上面的框圖設計)
module mytest(clk, rst_n, data_byte, send_en, baud_set, rs232_tx, tx_done, uart_state);

    input clk;                        // 系統(tǒng)時鐘
    input rst_n;                    // 復位
    input[7:0] data_byte;        // 要發(fā)送的數(shù)據(jù)
    input send_en;                    // 啟動發(fā)送
    input[2:0] baud_set;            // 波特率選擇
   
    output reg rs232_tx;
    output wire tx_done;                // 發(fā)送完畢通知    1:發(fā)送完畢 0:正在發(fā)送
    output reg uart_state;            // 發(fā)送狀態(tài) 1:正在發(fā)送數(shù)據(jù) 0:空閑狀態(tài)
   
    reg bps_clk;                    // 波特率時鐘
    wire en_cnt;                    // 計數(shù)使能 1:使能 0:失能
    reg[15:0] div_cnt;            // 分頻計數(shù)器
    reg[15:0] bps_dr;                // 分頻計數(shù)最大值
    reg[3:0]    bps_cnt;                // 波特率時鐘計數(shù)器
    wire clr;                        // 清零信號
    reg[7:0] r_data_byte_buff;    // 緩沖區(qū),用于存儲需要發(fā)送的數(shù)據(jù),避免在發(fā)送過程中數(shù)據(jù)突然改變
   
   
    localparam START_BIT    = 1'b0;
    localparam STOP_BIN    = 1'b1;
   
   
    // 串口工作狀態(tài)
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            uart_state <= 1'b0;
        else if(send_en)
            uart_state <= 1'b1;
        else if(tx_done)                // 發(fā)送完畢
            uart_state <= 1'b0;
        else
            uart_state <= uart_state;
   
    end
   
    assign en_cnt = uart_state;
   
    // 用于啟動發(fā)送時鎖存即將要發(fā)送的數(shù)據(jù)
    // 這樣就可以避免在發(fā)送的過程中數(shù)據(jù)突然改變導致發(fā)送的數(shù)據(jù)不正確。
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            r_data_byte_buff <= 8'd0;
        else if(send_en)               
            r_data_byte_buff <= data_byte;    // 啟動發(fā)送則鎖存最新的數(shù)據(jù)
        else
            r_data_byte_buff <= r_data_byte_buff;
   
    end
   
    // 【DR_LUT】 通過查表的方式將波特率轉換為對應的分頻計數(shù)最大值
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            bps_dr <= 16'd5207;                // 9600bps
        else begin
            case(baud_set)        // 查找表
                0:bps_dr <= 16'd5207;            // 9600bps
                1:bps_dr <= 16'd2603;         // 19200bps
                2:bps_dr <= 16'd1301;         // 38400bps
                3:bps_dr <= 16'd0867;            // 57600bps
                4:bps_dr <= 16'd0433;            // 115200bps
                default:bps_dr <= 16'd5207;    // 9600bps            
            endcase
        end
   
    end
   
    // 【Div_Cnt】 計數(shù)功能
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            div_cnt <= 16'd0;
        else if(en_cnt)    begin        
            if(div_cnt == bps_dr)
                div_cnt <= 16'd0;
            else
                div_cnt <= div_cnt + 1'b1;     
        end else
            div_cnt <= 16'd0;
   
    end
   
    // 【Div_Cnt】 bps_clk 時鐘產生
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            bps_clk <= 1'b0;
        else if(div_cnt == 16'd1)        // 當計數(shù)器剛開始計數(shù)時就產生一個時鐘
            bps_clk <= 1'b1;                // 這樣就相當于啟動發(fā)送時就立即開始發(fā)送數(shù)據(jù)
        else
            bps_clk <= 1'b0;
   
    end
   
     // 【bps_cnt】 bps 計數(shù)(即發(fā)送的數(shù)據(jù)位數(shù)計數(shù))
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            bps_cnt <= 4'd0;
        else if(clr)
            bps_cnt <= 4'd0;
        else if(bps_clk)
            bps_cnt <= bps_cnt + 1'b1;
        else
            bps_cnt <= bps_cnt;
   
    end
   
    // 【MUX10】、【r_R232_Tx】 盡量避免組合邏輯直接輸出,輸出是有毛刺的可能會出現(xiàn)不太穩(wěn)定的情況
    // 發(fā)送數(shù)據(jù)模塊
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            rs232_tx <= STOP_BIN;            // 起始位為低電平,所以空閑時為高電平即停止位
        else begin
        
            case(bps_cnt)
                0:rs232_tx <= STOP_BIN;        // 空閑時 bps_cnt 會一直為 0
                1:rs232_tx <= START_BIT;        // 起始位
                2:rs232_tx <= r_data_byte_buff[0];
                3:rs232_tx <= r_data_byte_buff[1];
                4:rs232_tx <= r_data_byte_buff[2];
                5:rs232_tx <= r_data_byte_buff[3];
                6:rs232_tx <= r_data_byte_buff[4];
                7:rs232_tx <= r_data_byte_buff[5];
                8:rs232_tx <= r_data_byte_buff[6];
                9:rs232_tx <= r_data_byte_buff[7];
                10:rs232_tx <= STOP_BIN;        // 停止位
                default:rs232_tx <= STOP_BIN;
            endcase
                    
        end
   
    end
    /*
    // 檢測一幀數(shù)據(jù)是否發(fā)送完成 // 采用此種方式會導致 tx_done、bps_cnt 會分別維持兩個時鐘周期的 1 和 11
    // 因為當 bps_cnt 變?yōu)?11 的時候,需要等第2個時鐘周期才會被采樣到。當采樣到之后 tx_done = 1,而 clr 也立即變?yōu)?1 ,
    // 而 clr 為 1 的時候也需要等第3個時鐘周期才能被 bps_cnt 采樣到變?yōu)?0
    // 而 bps_cnt 為 0 時,需要等到第4個時鐘周期才能被 tx_done 采樣,才會變?yōu)?0
    always@(posedge clk, negedge rst_n) begin
   
        if(!rst_n)
            tx_done <= 1'b0;
        else if(bps_cnt == 4'd11)
            tx_done <= 1'b1;
        else
            tx_done <= 1'b0;
   
    end
    */
    // 【r_Tx_Done】 為了避免 tx_done 這里采用直接賦值的方式來避免出現(xiàn)延遲一個時鐘的現(xiàn)象
    assign tx_done = bps_cnt == 4'd11 ? 1'b1 : 1'b0;
   
    assign clr = tx_done;            // 當完成一幀數(shù)據(jù)發(fā)送之后清除 bps 計數(shù)器
   
endmodule
修改后:
板級驗證:
        時間太晚,就不做板級實驗了。

   


分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏1 分享淘帖 頂 踩
回復

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術交流QQ群281945664

Powered by 單片機教程網

快速回復 返回頂部 返回列表
主站蜘蛛池模板: av毛片 | 亚洲一区视频 | 一呦二呦三呦国产精品 | 在线成人免费视频 | 日韩欧美一区二区三区免费观看 | 久久国产欧美日韩精品 | 欧美一级免费黄色片 | 日韩精品 电影一区 亚洲 | 日韩综合网 | 七七婷婷婷婷精品国产 | 国产91 在线播放 | 日日天天 | 国产精品国产a级 | 精品少妇一区二区三区日产乱码 | 久久久精品网站 | 欧美激情综合五月色丁香小说 | 亚洲欧美日韩在线 | 香蕉久久av| 久久1区| 黄色成人在线网站 | 视频1区| 久久精品91| 天天玩天天操天天干 | 欧美xxxx色视频在线观看免费 | 成人在线a | 精品成人av | 欧美一级二级三级 | 久久综合色综合 | 国产精品99久久久精品免费观看 | 欧美8一10sex性hd| 亚洲天堂免费 | 日韩欧美国产一区二区 | 日韩国产一区二区 | 热99| 国产精品一区二区三区在线 | 亚洲精品v日韩精品 | 99亚洲精品 | 中文字幕在线观看第一页 | avtt国产| 日韩欧美视频网站 | av网址在线 |