uart通訊協議


本次設計的源碼在http://download.csdn.net/detail/noticeable/9912383 下載

實驗目的:通過uart通訊協議的編寫,了解FPGA的通訊協議編寫的方法。

 

實驗現象:FPAG可以通過USB轉TTL工具向電腦收發數據。

 

相關知識點:1、uart通訊協議是什么及其內容。2、in system surce and probes(editor)調試工具的使用。

  

 

關於串行通訊:串口通訊協議是一種主機之間常用的通訊協議,通過模塊按位發送和接收字節,可以達到通訊的目的,其通訊只需要三根根數據線(GND,RXD,TXD)即可完成。其中串口通訊最重要的參數是波特率,數據位,停止位和奇偶校驗位,下面來一一講解這                              些參數的作用。

        (1)波特率:波特率是串口的通訊速率,常見的比特率為1200bps、4800bps、9600bps、38400bps、115200bps、256000bps、500000bps,這里bps的意思是bit/s,因此可以知道,波特率實際上是每秒可以傳輸的bit的個數,由此可知波特率與時鍾是直接掛鈎的,比如波特率位9600bps,那么時鍾就是9600hz,即每秒內產生9600個時鍾,每個時鍾周期發送1bit的數據。(這里注意:波特率越高,傳輸距離越短)

          波特率分頻計數器的方法:

 

 

        (2)數據位:數據位可以在通訊過程中告知另一個主機,完成一次通訊過程中真正有效的數據是多少位,完成對傳輸數據的位數的校驗。

        (3)起始位、停止位:起始位位於單個數據包的第一位,停止位位於單個包的最后一位,每個包都是一個字節, 另一台主機接收到停止位時就知道信號發送已經完成了。

         (4)奇偶校驗位:奇偶校驗是通訊協議中的一種簡單的檢錯方法,其有時鍾檢錯方式,:偶、奇、高和低。當然沒有校驗位也是可以的。對於偶和奇校驗的情況,串口會設置校驗位(數據位后面的一位),用一個值確保傳輸的數據有偶個或者奇個邏輯高位。例如,如果數據是011,那么對於偶校驗,校驗位為0,保證邏輯高的位數是偶數個。如果是奇校驗,校驗位為1,這樣就有3個邏輯高位。高位和低位不真正的檢查數據,簡單置位邏輯高或者邏輯低校驗。這樣使得接收設備能夠知道一個位的狀態,有機會判斷是否有噪聲干擾了通信或者是否傳輸和接收數據是否不同步。

 

 

項目設計:

本次設計發送模塊的整體結構體如下                                       發送時序圖

 

 

  首先按照系統框圖編寫tx項目源碼:

 

      

發生時序圖

 

 源碼程序如下

 

module uart_tx(
                                clk,
                                rst_n,
                               send_en,
                                baud_set,
                                tx_done,
                                rs232_tx,
                                data_byte,
                                uart_state
                            );
        input clk;
        input rst_n;
        input [7:0]data_byte;//數據發送寄存器
        input send_en;
        input [2:0]baud_set;
        
        output reg rs232_tx;                    //輸出引腳
        output reg tx_done;                    //傳輸完成的標志位
        output reg uart_state;                //uart_state傳送狀態
        
        reg [15:0]div_cnt;        //分頻計數器
        reg bps_clk;                    //波特率時鍾
        reg [15:0]bps_dr;            //分頻計數最大值
        reg [3:0]bps_cnt;            //波特率計數時鍾
        reg [7:0]r_data_byte;//發送緩存區
        
        localparam start_bit=1'b0;
      localparam stop_bit=1'b1;
        
        //具有優先級的信號控制,產生控制信號
        always@(posedge clk or negedge rst_n)
                    if(!rst_n)
            uart_state<=0;
            else if(send_en==1'b1)
            uart_state<=1;
            else if(bps_cnt == 4'd11)                //假設穿的是一組8位的數據+起始位+停止位
        uart_state <= 1'b0;
            else 
            uart_state<=uart_state;
            
                    //發送過程中需要保證數據是穩定的,選用一個寄存器將數據先緩存起來
            always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                    r_data_byte<=8'd0;
                    else if(send_en)
                    r_data_byte<=data_byte;
        
                        //設計查找表DR_LUT,通過查找表設置波特率
              //1/波特率/20ns
always@(posedge clk or negedge rst_n) if(!rst_n) bps_dr<=16'd5207; else begin case (baud_set) 0:bps_dr<=16'd5207;//9600 1:bps_dr<=16'd2603;//19200 2:bps_dr<=16'd1301;//28400 3:bps_dr<=16'd867;//57600 4:bps_dr<=16'd433;//115200 // 5:bps_dr<=16'd5207; // 6:bps_dr<=16'd5207; // 7:bps_dr<=16'd5207; default bps_dr<=16'd5207;//9600 endcase end //分頻計數器 always@(posedge clk or negedge rst_n) if(!rst_n) div_cnt<=16'd0; else if(uart_state)begin if(div_cnt==bps_dr)//到達計數最大值時清零 div_cnt<=16'd0; else div_cnt<=div_cnt+1'b1; end else div_cnt<=16'd0; //單周期的波特率時鍾產生 always@(posedge clk or negedge rst_n) if(!rst_n) bps_clk<=1'b0; else if(div_cnt==16'd1) bps_clk<=1; else bps_clk<=0; //設計波特率計數器 always@(posedge clk or negedge rst_n) if(!rst_n) bps_cnt<=4'b0; else if (bps_cnt == 4'd11) bps_cnt<=4'b0; else if (bps_clk) bps_cnt<=bps_cnt+1'b1; else bps_cnt<=bps_cnt; //發送完成信號 always@(posedge clk or negedge rst_n) if(!rst_n) tx_done<=1'b0; else if(bps_cnt==4'd11) tx_done<=1'b1; else tx_done <=1'b0; //數據發送,即一個十選一的多路器 always@(posedge clk or negedge rst_n) if(!rst_n) rs232_tx<=1'b1; else begin case(bps_cnt) 0:rs232_tx<=1'b1; 1:rs232_tx<=start_bit;//起始位 2:rs232_tx<=r_data_byte[0]; 3:rs232_tx<=r_data_byte[1]; 4:rs232_tx<=r_data_byte[2]; 5:rs232_tx<=r_data_byte[3]; 6:rs232_tx<=r_data_byte[4]; 7:rs232_tx<=r_data_byte[5]; 8:rs232_tx<=r_data_byte[6]; 9:rs232_tx<=r_data_byte[7]; 10:rs232_tx<=stop_bit;//結束位,本次設計不設奇偶校驗位 default rs232_tx<=1'b1; endcase end endmodule

 

 

 編寫testbench文件

`timescale 1ns/1ns
`define clock_period 20

module uart_tx_tb;
        reg clk;
        reg rst_n;
        reg [7:0]data_byte;
        reg send_en;
        reg [2:0]baud_set;
        
        wire rs232_tx;
        wire tx_done;
        wire uart_state;
        uart_tx            uart_tx0(
                                                            .clk(clk),
                                                            .rst_n(rst_n),
                                                            .send_en(send_en),
                                                           .baud_set(baud_set),
                                                            .tx_done(tx_done),
                                                            .rs232_tx(rs232_tx),
                                                            .data_byte(data_byte),
                                                            .uart_state(uart_state)
                                                        );
                                                        
                                initial clk=1;
                                always#(`clock_period/2)  clk=~clk;
                                initial begin 
                                rst_n<=1'b0;
                                data_byte<=8'd0;
                                send_en<=1'd0;
                                baud_set=3'd4;
                                #(`clock_period*20+1)
                                rst_n<=1'b1;
                                    #(`clock_period*50+1)
                                    data_byte<=8'haa;
                                send_en<=1'd1;
                                #(`clock_period)
                                send_en<=1'd0;
                                @(posedge tx_done)//等待傳輸完成的上升沿
                                #(`clock_period*500)//重新發送
                                data_byte<=8'h55;
                                send_en<=1'd1;
                                #(`clock_period)
                                send_en<=1'd0;
                                #(`clock_period*500)
                                $stop;
                                
                                end
                                
                                
                                
                                
                            
        endmodule 

 

仿真結果如下,可以看到,每當有一個send_en 時,databyte都會將串口數據輸出出來。

        

 

將之前用到的key_filter文件添加進來,作為傳輸的控制信號。

 添加IP核以便使用in system sources and probe editor 工具

 

 將.v文件添加到file中,編寫uart_top

module uart_top(clk ,rst_n,rs232_tx,key_in,led);

                    input key_in;
                    input clk;
                    input rst_n;
                    output rs232_tx;
                    wire send_en;
                    wire [7:0]data_byte;
                    output led;
                    wire key_flag,key_state;
                    assign send_en=key_flag&!key_state;//按鍵檢測成功且為低電平時,發生使能
                    
                uart_tx            uart_tx1(
                                                            .clk(clk),
                                                            .rst_n(rst_n),
                                                            .send_en(send_en),
                                                           .baud_set(3'd0),
                                                            .tx_done(),
                                                            .rs232_tx(rs232_tx),
                                                            .data_byte(data_byte),
                                                            .uart_state(led)//將串口狀態用LED顯示出來
                                                        );
                                key_filter key_filter0(
                                                                .clk(clk),
                                                                .rst_n(rst_n),
                                                                .key_in(key_in),
                                                                .key_flag(key_flag),
                                                                .key_state(key_state)
                                                                );
                         ISSP  ISSP0(
                                                        .probe(),
                                                        .source(data_byte)
                                                        );
endmodule

 

 

 進行引腳分配,進行板級驗證。

由於DE1-SOC上並未板載USB轉TTL模塊,這里需要自備一個,然后將模塊的RXD口連接到開發板上的GPIO_0D1口上,打開串口調試軟件並通過USB轉TTL模塊連接到FPGA上(波特率設為9600),按下KEY1后,可以看到開發板上的LEDR0快速閃爍一下,串口調試助手接收到00兩個數據

 

 

 

打開in system sources and probes editor,

修改格式為hex格式,然后輸入任意數據,我這里輸入66,

 然后再按一下key1,可以看到串口調試軟件顯示出來66,即我們給的source值,這里發送端的設計就完成了,下面繼續按相同的方法設計接收端的協議。

 

 

 

 

 

UART數據接收部分:

 之前已經講過了uart 發送端的時序圖,對應的,理論上接收端的時序圖如下,一般采樣在每一位數據的中點是最穩定的。

 

但是在工業等復雜的環境中,電磁場的干擾是很強的,所以在這里需要進行抗干擾處理,需要多次采樣求概率來進行接收,進行改進后的單bit數據接收方式示意圖

 

 

同樣編寫Verilog代碼:

module uart_rx(clk,
                                    rs232_rx,
                                    baud_set,
                                    rst_n,
                                    data_byte,
                                    rx_done
                                    );
                                    input clk;
                        input                rs232_rx;
                        input             [2:0]baud_set;
                        input             rst_n;
                        output    reg     [7:0]data_byte;
                        output     reg        rx_done;
                        reg s0_rs232_rx,s1_rs232_rx;//兩個同步寄存器
                        reg tmp0_rs232_rx,tmp1_rs232_rx;//數據寄存器
                        wire nedege;
                        reg [15:0]bps_dr;//分頻計數器計數最大值
                        reg [15:0]div_cnt;//分頻計數器
                        reg uart_state;
                        reg bps_clk;
                        reg [7:0]bps_cnt;
                        reg [2:0]r_data_byte [7:0];//前面[2:0]是每一位數據的存儲寬度,[7:0]指位寬
                            reg [2:0] start_bit,stop_bit;
                        
                        
                        //異步信號同步處理,消除亞穩態,有疑惑的看之前的按鍵消抖部分
                        always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                    begin
                    s0_rs232_rx<=1'b0;
                        s1_rs232_rx<=1'b0;
                        end
                        else 
                        begin 
                            s0_rs232_rx<=rs232_rx;
                        s1_rs232_rx<=s0_rs232_rx;
                        end
                                //數據寄存
                        always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                    begin
                    tmp0_rs232_rx<=1'b0;
                        tmp1_rs232_rx<=1'b0;
                        end
                        else 
                        begin 
                            tmp0_rs232_rx<=s1_rs232_rx;
                                    tmp1_rs232_rx<=tmp0_rs232_rx;
                        end
                        assign nedege=tmp0_rs232_rx&tmp1_rs232_rx;//下降沿檢測
                        
                        
                        
                    //波特率設置模塊
                    //10^9/波特率/20ns/這里為了穩定1bit數據會采16次所以還要除16
                    
                    always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                bps_dr<=16'd324;
                    else begin 
                    case (baud_set)
                    0:bps_dr<=16'd324;//9600
                    1:bps_dr<=16'd162;//19200
                    2:bps_dr<=16'd80;//28400
                    3:bps_dr<=16'd53;//57600
                    4:bps_dr<=16'd26;//115200
                        default
                        bps_dr<=16'd324;//9600
                    endcase 
                    end
                    //分頻計數器
        always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                    div_cnt<=16'd0;
                    else if(uart_state)begin 
                    if(div_cnt==bps_dr)//到達計數最大值時清零
                            div_cnt<=16'd0;
                            else div_cnt<=div_cnt+1'b1;
                                                end
                        else 
                        div_cnt<=16'd0;
    
                                //單周期的波特率時鍾產生
                    always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                    bps_clk<=1'b0;
                    else if(div_cnt==16'd1)
                        bps_clk<=1;
                        else
                        bps_clk<=0;
            
                                    //設計波特率計數器
                always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                bps_cnt<=8'b0;
            else if(bps_cnt == 8'd159 | (bps_cnt == 8'd12 && (start_bit > 2)))//到12位的時候,start_bit>2說明接收錯誤,起始位不對
                            bps_cnt <= 8'd0;//接收完成或者開始檢測到錯誤信號,波特率時鍾停止
                else if (bps_clk)
                        bps_cnt<=bps_cnt+1'b1;
                else 
                        bps_cnt<=bps_cnt;
            
                //接收完成信號
                always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                    rx_done<=1'b0;
                    else if(bps_cnt==8'd159)
                    rx_done<=1'b1;
                    else 
                    rx_done <=1'b0;
                    
                
                            //數據讀取,對每次采樣進行求和值 
                    always@(posedge clk or negedge rst_n)
                    if(!rst_n)begin 
                            start_bit=3'd0;
                            r_data_byte[0]<=3'd0;
                            r_data_byte[1]<=3'd0;
                            r_data_byte[2]<=3'd0;
                            r_data_byte[3]<=3'd0;
                            r_data_byte[4]<=3'd0;
                            r_data_byte[5]<=3'd0;
                            r_data_byte[6]<=3'd0;
                            r_data_byte[7]<=3'd0;
                            stop_bit<=3'd0;
                            end 
                            else if(bps_clk)begin
        case(bps_cnt)
            0:begin
                    start_bit = 3'd0;
                    r_data_byte[0] <= 3'd0;
                    r_data_byte[1] <= 3'd0;
                    r_data_byte[2] <= 3'd0;
                    r_data_byte[3] <= 3'd0;
                    r_data_byte[4] <= 3'd0;
                    r_data_byte[5] <= 3'd0;
                    r_data_byte[6] <= 3'd0;
                    r_data_byte[7] <= 3'd0;
                    stop_bit = 3'd0;            
                    end 
                        6,7,8,9,10,11:start_bit <= start_bit + s1_rs232_rx;
                        22,23,24,25,26,27:r_data_byte[0] <= r_data_byte[0] + s1_rs232_rx;
                        38,39,40,41,42,43:r_data_byte[1] <= r_data_byte[1] + s1_rs232_rx;
                        54,55,56,57,58,59:r_data_byte[2] <= r_data_byte[2] + s1_rs232_rx;
                        70,71,72,73,74,75:r_data_byte[3] <= r_data_byte[3] + s1_rs232_rx;
                        86,87,88,89,90,91:r_data_byte[4] <= r_data_byte[4] + s1_rs232_rx;
                        102,103,104,105,106,107:r_data_byte[5] <= r_data_byte[5] + s1_rs232_rx;
                        118,119,120,121,122,123:r_data_byte[6] <= r_data_byte[6] + s1_rs232_rx;
                        134,135,136,137,138,139:r_data_byte[7] <= r_data_byte[7] + s1_rs232_rx;
                        150,151,152,153,154,155:stop_bit <= stop_bit + s1_rs232_rx;
                    default:
                begin                                    
                    start_bit = start_bit;
                    r_data_byte[0] <= r_data_byte[0];
                    r_data_byte[1] <= r_data_byte[1];
                    r_data_byte[2] <= r_data_byte[2];
                    r_data_byte[3] <= r_data_byte[3];
                    r_data_byte[4] <= r_data_byte[4];
                    r_data_byte[5] <= r_data_byte[5];
                    r_data_byte[6] <= r_data_byte[6];
                    r_data_byte[7] <= r_data_byte[7];
                    stop_bit = stop_bit;                        
                end
        endcase
    end
                    
        
                                        always@(posedge clk or negedge rst_n)//數據提取
                                    if(!rst_n)
                                    data_byte <= 8'd0;
                                else if(bps_cnt == 8'd159)begin
                                    data_byte[0] <= r_data_byte[0][2];
                                    data_byte[1] <= r_data_byte [1][2];
                                    data_byte[2] <= r_data_byte[2][2];
                                    data_byte[3] <= r_data_byte[3][2];
                                    data_byte[4] <= r_data_byte[4][2];
                                    data_byte[5] <= r_data_byte[5][2];
                                    data_byte[6] <= r_data_byte[6][2];
                                    data_byte[7] <= r_data_byte[7][2];
                                end    
                                
                                //控制邏輯
                                
                        always@(posedge clk or negedge rst_n)
                    if(!rst_n)
                        uart_state <= 1'b0;
                        else if(nedege)
                            uart_state <= 1'b1;
                            else if(rx_done || (bps_cnt == 8'd12 && (start_bit > 2)))    //接收完成或者到12位的時候,start_bit>2說明接收錯誤,起始位不對
                            uart_state <= 1'b0;//關閉傳輸狀態位
                        else
                            uart_state <= uart_state;        

endmodule

                    
                    
                    
                    

編寫testbench並添加路徑,因為testbench中調用了uart_rx,所以在添加路徑的時候需要將uart.v文件添加到路徑中去

`timescale 1ns/1ns
`define clock_period 20
module uart_rx_tb;
                        reg             rst_n;
                        reg             clk;
                        reg                rs232_rx;
                        wire            rs232_tx;
                        reg             [2:0]baud_set;
                        wire             rx_done;
                        wire             tx_done;
                        wire             [7:0]data_byte_r;
                        reg              [7:0]data_byte_t;
                        reg             send_en;
                      wire             uart_state;        
    
                        
                        

                uart_rx        uart_rx1(.clk(clk),
                                                    .rs232_rx(rs232_tx),    //用輸入值作為讀取值
                                                    .baud_set(baud_set),
                                                    .rst_n(rst_n),
                                                    .data_byte(data_byte_r),
                                                    .rx_done(rx_done)
                                                    );

                            uart_tx            uart_tx2(
                                                                                .clk(clk),
                                                                                .rst_n(rst_n),
                                                                                .send_en(send_en),
                                                                                .baud_set(baud_set),
                                                                                .tx_done(tx_done),
                                                                                .rs232_tx(rs232_tx),
                                                                                .data_byte(data_byte_t),
                                                                                .uart_state(uart_state)//將串口狀態用LED顯示出來
                                                                            );
                                            initial clk=1;
                                                                always#(`clock_period/2)  clk=~clk;
                                                                initial begin 
                                                                        rst_n<=1'b0;
                                                                        data_byte_t<=8'd0;
                                                                        send_en<=1'd0;
                                                                        baud_set=3'd4;
                                                                        #(`clock_period*20+1)
                                                                        rst_n<=1'b1;
                                                                            #(`clock_period*50+1)
                                                                            data_byte_t<=8'haa;
                                                                        send_en<=1'd1;
                                                                        #(`clock_period)
                                                                        send_en<=1'd0;
                                                                        @(posedge tx_done)//等待傳輸完成的上升沿
                                                                        #(`clock_period*5000)//重新發送
                                                                        data_byte_t<=8'h55;
                                                                        send_en<=1'd1;
                                                                        #(`clock_period)
                                                                        send_en<=1'd0;
                                                                        #(`clock_period*500)
                                                                        $stop;
                                                                        end
endmodule 

將uart_rx設為頂層文件,編譯后進行仿真,仿真圖形如下:

新建ISSP IP核,添加8個探針

 

在uart_top中將rs_232_rx添加進來

module uart_top(clk ,rst_n,rs232_rx,rs232_tx,key_in,led);

                    input key_in;
                    input clk;
                    input rst_n;
                    input  rs232_rx;
                    output rs232_tx;
                    wire send_en;
                    wire [7:0]data_byte_t;
                    reg [7:0]data_byte_r;
                    wire [7:0]data_rx;
                    output led;
                    wire key_flag,key_state;
                    wire rx_done;
                    assign send_en=key_flag&!key_state;//按鍵檢測成功且為低電平時,發生使能
                    
                uart_tx            uart_tx1(
                                                            .clk(clk),
                                                            .rst_n(rst_n),
                                                            .send_en(send_en),
                                                           .baud_set(3'd0),
                                                            .tx_done(),
                                                            .rs232_tx(rs232_tx),
                                                            .data_byte(data_byte_t),
                                                            .uart_state(led)//將串口狀態用LED顯示出來
                                                        );
                    uart_rx        uart_rx2(.clk(clk),
                                                    .rs232_rx(rs232_rx),    //用輸入值作為讀取值
                                                    .baud_set(3'd0),
                                                    .rst_n(rst_n),
                                                    .data_byte(data_rx),//接收到的數據傳遞給data_rx;
                                                    .rx_done(rx_done)
                                                    );

                                key_filter key_filter0(
                                                                .clk(clk),
                                                                .rst_n(rst_n),
                                                                .key_in(key_in),
                                                                .key_flag(key_flag),
                                                                .key_state(key_state)
                                                                );
                    
                     ISSP     ISSP(
                                                            .probe(),//調用一個ISSP 發送數據
                                                            .source(data_byte_t)
                                                            );
                                 ISSP1  ISSP2(
                                                        .probe(data_byte_r),//調用一個ISSP 接收數據
                                                        .source()
                                                        );
                        
                                            //應為data_rx可能沒有接收成功,為錯誤量,所以需要一個中間量來進行緩沖
                                        always@(posedge clk or negedge rst_n)
                                        if(!rst_n)
                                            data_byte_r <= 8'd0;
                                        else if(rx_done)
                                            data_byte_r<= data_rx;
                                        else
                                            data_byte_r<= data_byte_r;
endmodule

 

 分配RX引腳給GPIO0_D0,然后將程序燒寫到FPGA中,將GPIO0-D0與GPIO0_D1連着直接用杜邦線連接,打開ISSP工具,將兩者都設置為hex顯示,且將第二個ISSP工具設置為循環掃描,在第一個ISSP中輸入數據,按下按鍵KEY1后,看到第二個ISSP 有相應的數據變化。

此時說明整個協議編寫完成 了可以完成通訊了

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM