串口UART學習筆記(一)


        買了一個開發板學習FPGA,找到的各種東西就記錄在這個博客里了,同時也方便把自己不會的問題找到的結果記錄一下,都是自己手打,所以可能說的話不那么嚴謹,不那么精准,看到的人要帶着自己的思考去看,記住盡信書不如無書,哈哈哈。。。。。。

         一、UART是什么?

         UART是一種通用串行數據總線,也就是用於數據傳輸。是用於主機與輔助設備進行通信。這里的主機理解為計算機,計算機內部采用並行數據,輔助設備采用串行數據。中間需要設備進行數據轉換,這也決定了UART工作原理是將傳輸數據的每個字符一位接一位地傳輸。UART采用異步傳輸模式,異步傳輸將比特分成小組進行傳送,小組可以是8位的1個字符或更長。發送方可以在任何時刻發送這些比特組,而接收方從不知道它們會在什么時候到達。例如計算機鍵盤與主機的通信。UART用於遠距離傳輸較為適合。可以數據同時發送和接收。

                                                1,異步傳輸是面向字符的傳輸,而同步傳輸是面向比特的傳輸。

                                                2,異步傳輸的單位是字符同步傳輸的單位是幀。
                                                3,異步傳輸通過字符起始和停止碼抓住再同步的機會,而同步傳輸則是在數據中抽取同步信息。
                                                4,異步傳輸對時序的要求較低,同步傳輸往往通過特定的時鍾線路協調時序。
                                                5,異步傳輸相對於同步傳輸效率較低。
我的理解是對時序要求是采用UART的原因,數據到達需要給出到達信號也解釋了UART采用8位1字符的串行數據輸出模式,也解釋了協議中起始位與停止位的重要性。
uart指通用異步收發傳輸器,本質上是硬件,用來異步傳輸數據。RS232是一種物理層協議,規定了特定的接口標准。https://www.zhihu.com/question/22632011 
 
進行數據傳輸時,需要考慮時鍾同步問題,傳輸速率有一個很重要的概念。波特率是衡量資料傳送速率的指標。表示每秒鍾傳送的符號數(symbol)。一個符號代表的信息量(比特數)與符號的階數有關。例如資料傳送速率為120字符/秒,傳輸使用256階符號,每個符號代表8bit,則波特率就是120baud,比特率是120*8=960bit/s。位 bit (比特)(Binary Digits):存放一位二進制數,即 0 或 1,最小的存儲單位。字節Byte:8個二進制位為一個字節(B),最常用的單位。
常用波特率9600,19200,38400,115200等。
其中各位的意義如下: 
起始位:先發出一個邏輯”0”信號,表示傳輸字符的開始。 
數據位:可以是5~8位邏輯”0”或”1”。如ASCII碼(7位),擴展BCD碼(8位)。 
校驗位:數據位加上這一位后,使得“1”的位數應為偶數(偶校驗)或奇數(奇校驗)。 
停止位:它是一個字符數據的結束標志。可以是1位、1.5位、2位的高電平。 
空閑位:處於邏輯“1”狀態,表示當前線路上沒有資料傳送。

      二、UART串口通信一般包括部分
    任何程序都包括一個主控制程序,因為是雙向通信,還需要串口發送程序,串口接收程序,時鍾控制程序。舉例來分別解釋四個程序的代碼。
      這里采用50MHz的系統時鍾,產生UART時鍾信號產生和發送的波特率為9600bps。為了保證采樣的時候不會發生誤碼或者滑碼,我們對於一位數據不只采用一個時鍾周期進行數據采集,而采用16個時鍾周期。那么就需要對時鍾進行分頻處理。50,000,000/(16*9600),分頻系數取整為326 。偶數分頻比較簡單,此處采用了326,至於奇數分頻以及如何做出占空比為50%以后整理。
 
 
 1 `timescale 1ns / 1ps
 2 //////////////////////////////////////////////////////////////////////////////////
 3 // Module Name:    clkdiv
 4 // 產生一個波特率9600的16倍頻的時鍾,9600*16= 153600
 5 // 相當於50MHz的326分頻,50000000/153600=326
 6 //////////////////////////////////////////////////////////////////////////////////
 7 module clkdiv(clk50, clkout);
 8 input clk50;              //系統時鍾
 9 output clkout;          //采樣時鍾輸出
10 reg clkout;
11 reg [15:0] cnt;
12 
13 //分頻進程,對50Mhz的時鍾326分頻
14 always @(posedge clk50)   
15 begin
16   if(cnt == 16'd162)
17   begin
18     clkout <= 1'b1;
19     cnt <= cnt + 16'd1;
20   end
21   else if(cnt == 16'd325)
22   begin
23     clkout <= 1'b0;
24     cnt <= 16'd0;
25   end
26   else
27   begin
28     cnt <= cnt + 16'd1;
29   end
30 end
31 endmodule

分頻比較簡單,我寫的另一個,

 1  `timescale 1ns / 1ps
 2 
 3  module clkdiv(clk50, clkout);
 4  input clk50;              //系統時鍾
 5  output clkout;          //采樣時鍾輸出
 6  reg clkout;
 7  reg [15:0] cnt;
 8 
 9 always @(posedge clk50)
10     begin
11       if (cnt < 16'd162)
12       cnt <= cnt + 16'b1;
13      else  if  (cnt == 16'd162)
14       begin
15           clkout <= ~clkout;
16           cnt <= 16'b0;
17       end
18      end

三、串口發送程序

     UART采用異步傳輸,就涉及起始位與停止位,下面是代碼例子,我的總結用紅筆標出。

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Module Name:    uarttx 
  4 // 說明:16個clock發送一個bit,
  5 //////////////////////////////////////////////////////////////////////////////////
  6 module uarttx(clk, datain, wrsig, idle, tx);
  7 input clk;                //UART時鍾
  8 input [7:0] datain;       //需要發送的數據
  9 input wrsig;              //發送命令,上升沿有效
 10 output idle;              //線路狀態指示,高為線路忙,低為線路空閑
 11 output tx;                //發送數據信號
 12 reg idle, tx;
 13 reg send;
 14 reg wrsigbuf, wrsigrise;
 15 reg presult;
 16 reg[7:0] cnt;             //計數器
 17 parameter paritymode = 1'b0;
 18 
 19 //檢測發送命令是否有效,判斷wrsig的上升沿            //先檢測發送命令是否有效,然后判斷線路狀態
 20 always @(posedge clk)
 21 begin
 22    wrsigbuf <= wrsig;
 23    wrsigrise <= (~wrsigbuf) & wrsig;         //邊沿檢測,檢測發送命令
 24 end
 25 
 26 always @(posedge clk)
 27 begin
 28   if (wrsigrise &&  (~idle))  //當發送命令有效且線路為空閑時,啟動新的數據發送進程
 29   begin
 30      send <= 1'b1;
 31   end
 32   else if(cnt == 8'd168)      //一幀資料發送結束
 33   begin
 34      send <= 1'b0;
 35   end
 36 end
 37 
 38 /////////////////////////////////////////////////////////////////////////
 39 //使用168個時鍾發送一個數據(起始位、8位數據、奇偶校驗位、停止位),每位占用16個時鍾// //停止位為8個時鍾周期
 40 ////////////////////////////////////////////////////////////////////////
 41 always @(posedge clk)
 42 begin
 43   if(send == 1'b1)  begin
 44     case(cnt)                 //tx變低電平產生起始位,0~15個時鍾為發送起始位
 45     8'd0: begin
 46          tx <= 1'b0;
 47          idle <= 1'b1;
 48          cnt <= cnt + 8'd1;
 49     end
 50     8'd16: begin
 51          tx <= datain[0];    //發送數據位的低位bit0,占用第16~31個時鍾
 52          presult <= datain[0]^paritymode;     //奇偶校驗位的獲取
53 idle <= 1'b1; 54 cnt <= cnt + 8'd1; 55 end 56 8'd32: begin 57 tx <= datain[1]; //發送數據位的第2位bit1,占用第47~32個時鍾 58 presult <= datain[1]^presult; 59 idle <= 1'b1; 60 cnt <= cnt + 8'd1; 61 end 62 8'd48: begin 63 tx <= datain[2]; //發送數據位的第3位bit2,占用第63~48個時鍾 64 presult <= datain[2]^presult; 65 idle <= 1'b1; 66 cnt <= cnt + 8'd1; 67 end 68 8'd64: begin 69 tx <= datain[3]; //發送數據位的第4位bit3,占用第79~64個時鍾 70 presult <= datain[3]^presult; 71 idle <= 1'b1; 72 cnt <= cnt + 8'd1; 73 end 74 8'd80: begin 75 tx <= datain[4]; //發送數據位的第5位bit4,占用第95~80個時鍾 76 presult <= datain[4]^presult; 77 idle <= 1'b1; 78 cnt <= cnt + 8'd1; 79 end 80 8'd96: begin 81 tx <= datain[5]; //發送數據位的第6位bit5,占用第111~96個時鍾 82 presult <= datain[5]^presult; 83 idle <= 1'b1; 84 cnt <= cnt + 8'd1; 85 end 86 8'd112: begin 87 tx <= datain[6]; //發送數據位的第7位bit6,占用第127~112個時鍾 88 presult <= datain[6]^presult; 89 idle <= 1'b1; 90 cnt <= cnt + 8'd1; 91 end 92 8'd128: begin 93 tx <= datain[7]; //發送數據位的第8位bit7,占用第143~128個時鍾 94 presult <= datain[7]^presult; 95 idle <= 1'b1; 96 cnt <= cnt + 8'd1; 97 end 98 8'd144: begin 99 tx <= presult; //發送奇偶校驗位,占用第159~144個時鍾 //將計算結果作為奇偶校驗碼輸出 100 presult <= datain[0]^paritymode; //無意思,此行計算結果無用,多余
101 idle <= 1'b1; 102 cnt <= cnt + 8'd1; 103 end 104 8'd160: begin 105 tx <= 1'b1; //發送停止位,占用第160~167個時鍾 106 idle <= 1'b1; 107 cnt <= cnt + 8'd1; 108 end 109 8'd168: begin 110 tx <= 1'b1; 111 idle <= 1'b0; //一幀資料發送結束 112 cnt <= cnt + 8'd1; 113 end 114 default: begin 115 cnt <= cnt + 8'd1; 116 end 117 endcase 118 end 119 else begin 120 tx <= 1'b1; 121 cnt <= 8'd0; 122 idle <= 1'b0; 123 end 124 end 125 endmodule

 


 

四、串口接收程序

      成為UART接收信號,將一位一位的串口數據轉化為並行數據。

  1 `timescale 1ns / 1ps
  2 //////////////////////////////////////////////////////////////////////////////////
  3 // Module name    uartrx.v
  4 // 說明:          16個clock接收一個bit,16個時鍾采樣,取中間的采樣值
  5 //////////////////////////////////////////////////////////////////////////////////
  6 module uartrx(clk, rx, dataout, rdsig, dataerror, frameerror);
  7 input clk;             //采樣時鍾
  8 input rx;              //UART數據輸入
  9 output dataout;        //接收數據輸出
 10 output rdsig;          //接收數據有效,高說明接收到一個字節 ,來區分數據處於接收狀態或者接收控制信號
 11 output dataerror;      //數據出錯指示
 12 output frameerror;     //幀出錯指示
 13 
 14 reg[7:0] dataout;
 15 reg rdsig, dataerror;
 16 reg frameerror;
 17 reg [7:0] cnt;
 18 reg rxbuf, rxfall, receive;
 19 parameter paritymode = 1'b0;
 20 reg presult, idle;
 21 
 22 always @(posedge clk)   //檢測線路rx的下降沿, 線路空閑的時候rx為高電平
 23 begin
 24   rxbuf <= rx;
 25   rxfall <= rxbuf & (~rx);      //下降沿檢測,檢測是否接收到接收信號
26 end 27 28 always @(posedge clk) 29 begin 30 if (rxfall && (~idle)) //檢測到線路的下降沿並且原先線路為空閑,啟動接收數據進程 31 begin 32 receive <= 1'b1; //開始接收數據 33 end 34 else if(cnt == 8'd168) //接收數據完成 35 begin 36 receive <= 1'b0; 37 end 38 end 39 40 ///////////////////////////////////////////////////////////////////////// 41 //使用176個時鍾接收一個數據(起始位、8位數據、奇偶校驗位、停止位),每位占用16個時鍾// 42 //////////////////////////////////////////////////////////////////////// 43 always @(posedge clk) 44 begin 45 if(receive == 1'b1) 46 begin 47 case (cnt) 48 8'd0: //0~15個時鍾為接收第一個比特,起始位 49 begin 50 idle <= 1'b1; 51 cnt <= cnt + 8'd1; 52 rdsig <= 1'b0; 53 end 54 8'd24: //16~31個時鍾為第1個bit數據,取中間第24個時鍾的采樣值 //在發送程序中,第0位數據在16個時鍾周期后開始傳輸,接收過程中, 55 begin //從第24個時鍾周期開始接收第0位數據,保證信號被采集。 56 idle <= 1'b1; 57 dataout[0] <= rx; 58 presult <= paritymode^rx; 59 cnt <= cnt + 8'd1; 60 rdsig <= 1'b0; 61 end 62 8'd40: //47~32個時鍾為第2個bit數據,取中間第40個時鍾的采樣值 63 begin 64 idle <= 1'b1; 65 dataout[1] <= rx; 66 presult <= presult^rx; 67 cnt <= cnt + 8'd1; 68 rdsig <= 1'b0; 69 end 70 8'd56: //63~48個時鍾為第3個bit數據,取中間第56個時鍾的采樣值 71 begin 72 idle <= 1'b1; 73 dataout[2] <= rx; 74 presult <= presult^rx; 75 cnt <= cnt + 8'd1; 76 rdsig <= 1'b0; 77 end 78 8'd72: //79~64個時鍾為第4個bit數據,取中間第72個時鍾的采樣值 79 begin 80 idle <= 1'b1; 81 dataout[3] <= rx; 82 presult <= presult^rx; 83 cnt <= cnt + 8'd1; 84 rdsig <= 1'b0; 85 end 86 8'd88: //95~80個時鍾為第5個bit數據,取中間第88個時鍾的采樣值 87 begin 88 idle <= 1'b1; 89 dataout[4] <= rx; 90 presult <= presult^rx; 91 cnt <= cnt + 8'd1; 92 rdsig <= 1'b0; 93 end 94 8'd104: //111~96個時鍾為第6個bit數據,取中間第104個時鍾的采樣值 95 begin 96 idle <= 1'b1; 97 dataout[5] <= rx; 98 presult <= presult^rx; 99 cnt <= cnt + 8'd1; 100 rdsig <= 1'b0; 101 end 102 8'd120: //127~112個時鍾為第7個bit數據,取中間第120個時鍾的采樣值 103 begin 104 idle <= 1'b1; 105 dataout[6] <= rx; 106 presult <= presult^rx; 107 cnt <= cnt + 8'd1; 108 rdsig <= 1'b0; 109 end 110 8'd136: //143~128個時鍾為第8個bit數據,取中間第136個時鍾的采樣值 111 begin 112 idle <= 1'b1; 113 dataout[7] <= rx; 114 presult <= presult^rx; 115 cnt <= cnt + 8'd1; 116 rdsig <= 1'b1; //接收數據有效 117 end 118 8'd152: //159~144個時鍾為接收奇偶校驗位,取中間第152個時鍾的采樣值 119 begin 120 idle <= 1'b1; 121 if(presult == rx) 122 dataerror <= 1'b0; 123 else 124 dataerror <= 1'b1; //如果奇偶校驗位不對,表示數據出錯 125 cnt <= cnt + 8'd1; 126 rdsig <= 1'b1; 127 end 128 8'd168: //160~175個時鍾為接收停止位,取中間第168個時鍾的采樣值 129 begin 130 idle <= 1'b1; 131 if(1'b1 == rx) 132 frameerror <= 1'b0; 133 else 134 frameerror <= 1'b1; //如果沒有接收到停止位,表示幀出錯 135 cnt <= cnt + 8'd1; 136 rdsig <= 1'b1; 137 end 138 default: 139 begin 140 cnt <= cnt + 8'd1; 141 end 142 endcase 143 end 144 else 145 begin 146 cnt <= 8'd0; 147 idle <= 1'b0; 148 rdsig <= 1'b0; 149 end 150 end 151 endmodule

五、控制程序

     這里主要學習程序的調用,如何用總的控制程序完成UART數據傳輸。

 1 `timescale 1ns / 1ps
 2 //////////////////////////////////////////////////////////////////////////////////
 3 // Module Name:    uart_test 
 4 // 
 5 //////////////////////////////////////////////////////////////////////////////////
 6 module uart_test(clk50, rx, tx, reset);
 7 input clk50;
 8 input reset;
 9 input rx;
10 output tx;
11 
12 wire clk;       //clock for 9600 uart port
13 wire [7:0] txdata,rxdata;     //串口發送數據和串口接收數據
14 
15 
16 
17 //產生時鍾的頻率為16*9600
18 clkdiv u0 (
19         .clk50                   (clk50),               //50Mhz的晶振輸入                     
20         .clkout                  (clk)                  //16倍波特率的時鍾                        
21  );
22 
23 //串口接收程序
24 uartrx u1 (
25         .clk                     (clk),                 //16倍波特率的時鍾 
26       .rx                       (rx),                     //串口接收
27         .dataout                 (rxdata),              //uart 接收到的數據,一個字節                     
28       .rdsig                   (rdsig),               //uart 接收到數據有效 
29         .dataerror               (),
30         .frameerror              ()
31 );
32 
33 //串口發送程序
34 uarttx u2 (
35         .clk                     (clk),                  //16倍波特率的時鍾  
36        .tx                      (tx),                      //串口發送
37         .datain                  (txdata),               //uart 發送的數據   
38       .wrsig                   (wrsig),                //uart 發送的數據有效  
39       .idle                    ()     
40     
41  );

54 endmodule

就像c語言里調用子函數一樣,每個 模塊是並行運行的, 各個模塊連接完成整個系統需要一個頂層文件(top-module) 。 頂層文件 通過調用、連接低層模塊的實例來實現復雜的功能。

 

 

學習UART通信,最主要還是理解異步和串口這兩個東西,方便遠距離傳輸,串口一位一位傳輸,犧牲了時間降低時序要求。

 
 
 
 
 
 
 
 
 
 


免責聲明!

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



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