進階項目(3)UART串口通信程序設計講解


寫在前面的話

UART串行接口簡稱串口,是我們各類芯片最常用的一種異步通信接口,通過串口我們就可以建立起計算機和我們實驗板之間的通信和控制關系,也就是我們通常所說的上下位機通信。串口可以說是不同平台互相通信、控制的一個最基本的接口。

項目需求

設計一個UART控制器,當控制器從上位機接收到數據以后,馬上將數據輸出,發送回上位機,完成“回環測試”。

 UART的原理分析

要實現UART通信,首先我們需要用到一個外部的電平轉換芯片MAX232,其具體配置電路如下:

注解:

MAX232芯片是美信(MAXIM)公司專為RS_232標准串口設計的單電源電平轉換芯片,使用+5V單電源供電

主要特點:

符合所有的RS_232技術標准

只需要單一+5V電源供電

片載電荷泵具有升壓、電源極性翻轉能力,能夠產生+10V和-10V電壓

功耗低,典型供電電流5MA

內部集成2個RS_232驅動器

高集成度,片外最低只需四個電容即可工作

 

由原理圖可以看出,最終我們FPGA需要控制的其實也就是兩條信號線:

RXD和TXD,分別為數據接收線和數據發送線。

那么接下來,問題就變得簡單了,既然只有兩條線,那么我們只需要關注其數據收發時序即可,時序圖如下:

UART數據格式:

 說明:在此實驗中,無奇偶校驗位,則一幀數據為十位。(奇偶校驗是一種校驗代碼傳輸正確性的方法。根據被傳輸的一組二進制代碼的數位中“1”的個數是奇數或偶數來進行校驗。采用奇數的稱為奇校驗,反之,稱為偶校驗。采用何種校驗是事先規定好的。通常專門設置一個奇偶校驗位,用它使這組代碼“1”的個數為奇數或偶數。若用奇校驗,則當接收端收到這組代碼時,校驗“1”的個數是否為奇數,從而確定傳輸代碼的正確性。)

UART接收時,采集一幀數據的中間8位有效位,忽略開始位與停止位;在UART發送時,將發送的並行8位數據轉為串行數據,並添加開始位與停止位。

UART中的一幀數據為10位,空閑時均為高電平,在檢測到開始位(低電平)之后,開始采集8位有效數據位(低位在前),再將停止位置為高電平即可。

通過前面的學習,我們已經了解了UART的數據格式,那么,傳輸速率如何控制呢?這就涉及到了一個波特率的概念:

波特率是衡量數據傳輸速率的指針。表示為每秒鍾傳送的二進制位數(bit),例如資料傳送速率為120字符/秒,而每一個字符為10位,則其傳送的波特率為10*120=1200波特(bit)。此實驗中設置波特率為9600bit/s。

系統架構

 模塊功能說明:bps_rx模塊為串口接收數據的速率控制模塊,當使能信號rx_en為高電平時,bps_rx模塊內部計數器開始計數,按照設定好的波特率,輸出控制數據采集的尖峰脈沖信號rx_sel_data和有效數據位的計數值rx_num。

模塊功能說明:uart_rx為串口串行數據的接收模塊,數據從端口rs232_rx輸入,在采集控制信號rx_sel_data和有效位計數器rx_num的控制下,進行串並轉換,從端口rx_d[7:0]輸出。tx_en為發送控制模塊的使能信號,當Uart_rx模塊接收數據完畢以后,置高信號tx_en啟動數據發送,將采集到的數據rx_d[7:0]發送到上位機。

 模塊功能說明:bps_tx模塊為串口發送數據的速率控制模塊,當使能信號tx_en為高電平時,bps_tx模塊內部計數器開始計數,按照設定好的波特率,輸出控制數據發送的尖峰脈沖信號tx_sel_data和有效數據位的計數值tx_num。

模塊功能說明:uart_tx為串口串行數據的發送模塊,並行數據從端口rx_d[7:0]輸入,在采集控制信號tx_sel_data和有效位計數器tx_num的控制下,進行並串轉換,從端口rs232_tx輸出。

 

模塊功能介紹

模塊名

功能描述

bps_rx

控制串口接收數據的速率

uart_rx

接收串口串行數據

bps_tx

控制串口發送數據的速率

uart_tx

發送串口串行數據

uart

頂層連接

端口和內部連線描述


頂層模塊端口介紹

端口名

端口說明

clk

系統時鍾輸入

rst_n

系統復位

rs232_tx

數據輸出端口

rs232_rx

數據輸入端口

內部連線

連線名

連線說明

rx_en

bps_rx開始計數的使能信號

rx_sel_data

控制數據采集的尖峰脈沖信號

rx_num

接收有效數據位的計數值

rx_d

接收到的數據

tx_en

發送控制模塊的使能信號

tx_sel_data

控制數據發送的尖峰脈沖信號

tx_num

發送有效數據位的計數值

代碼解釋

數據接收模塊波特率生成

/****************************************************          

 *   Engineer      :   夢翼師兄

 *   QQ             :   761664056

 *   The module function:控制串口接收數據的速率

*****************************************************/

00 module bps_rx(        

01 clk,   //系統時鍾50MHz

02 rst_n, //低電平復位

03 rx_en,    //使能信號:串口接收或發送開始

04 rx_sel_data,  //波特率計數的中心點(采集數據的使能信號)

05 rx_num   //一幀數據0-9

06 );

07 //模塊輸入

08 input    clk;   //系統時鍾50MHz

09 input    rst_n; //低電平復位

10 input   rx_en;    //使能信號:串口接收開始

11 //模塊輸出

12 output reg   rx_sel_data;  //波特率計數的中心點(采集數據的使能信號)

13 output reg [3:0] rx_num;   //一幀數據0-9

14 //設置參數

15 parameter bps_div   = 13'd5207,

16   bps_div_2 = 13'd2603;

17

18 //接收標志位:接收到使能信號rx_en后,將標志位flag拉高,當信號rx_num計完一幀數據后拉低

19 reg       flag;

20 always@(posedge clk or negedge rst_n)   

21 if(!rst_n)

22 flag <= 0;

23 else if(rx_en)

24 flag <= 1;

25 else if(rx_num == 4'd10)

26 flag <= 0;

27

28 //波特率計數

29 reg [12:0] cnt;

30 always@(posedge clk or negedge rst_n)

31 if(!rst_n)

32 cnt  <= 13'd0;

33   else if(flag && cnt < bps_div)

34 cnt <= cnt + 1'b1;

35   else

36 cnt <= 13'd0;

37

38 //規定接收數據的范圍:即一幀數據(10位:1位開始位,8位數據位,1位結束位)

39 always@(posedge clk or negedge rst_n)

40 if(!rst_n)

41 rx_num <= 4'd0;

42 else if(rx_sel_data && flag)

43 rx_num <= rx_num + 1'b1;

44 else if(rx_num == 4'd10)

45 rx_num <= 1'd0;

46

47 //數據在波特率的中間部分采集:即接收數據的使能信號

48 always@(posedge clk or negedge rst_n)

49 if(!rst_n)

50 rx_sel_data <= 1'b0;

51 else if(cnt == bps_div_2)//中間取數是為了產生尖峰脈沖,尖峰脈沖為采集數據的使能信號,用來把握速率

52 rx_sel_data <= 1'b1;

53 else

54 rx_sel_data <= 1'b0;

55

56 endmodule

本模塊20~26行負責控制flag的值,flag為高電平代表一幀數據正在傳輸。29~36行為波特率計數器flag有效時計數器開始計數48~54行負責在波特率計數值的中間部位生成尖峰脈沖rx_sel_data兩個尖峰脈沖之間的間隔為5207個系統時鍾周期,滿足9600bps波特率。39~45在尖峰脈沖作用下接收數據計數器開始標定有效幀比特位

數據接收模塊

/****************************************************          

 *   Engineer      :   夢翼師兄

 *   QQ             :   761664056

 *   The module function:接收串口串行數據

*****************************************************/

00  module uart_rx(               

01                  clk,            //50MHZ時鍾

02                  rst_n,          //低電平復位

03                  rs232_rx,      //輸入串行數據

04                  rx_num,        //一幀數據控制位

05                  rx_sel_data,  //波特率計數的中心點(采集數據的使能信號)

06                  rx_en,         //使能信號:啟動接收波特率計數

07                  tx_en,    //使能信號:在接收完數據后,開始啟動發送數據模塊

08                  rx_d      //將采集數據的有效8位串行數據轉化為並行數據

09                );

10      //模塊輸入                    

11      input            clk;         //50MHZ時鍾

12      input            rst_n;       //低電平復位

13      input            rs232_rx;   //輸入串行數據

14      input [3:0]      rx_num;    //一幀數據控制位

15      input            rx_sel_data; //波特率計數的中心點(采集數據的使能信號)

16      //模塊輸出

17      output          rx_en;  //使能信號:啟動接收波特率計數

18      output reg      tx_en;  //使能信號:在接收完數據后,開始啟動發送數據模塊

19      output reg [7:0] rx_d; //將采集數據的有效8位串行數據轉化為並行數據

20  //檢測低電平(開始位)

21  reg in_1,in_2;

22  always@(posedge clk or negedge rst_n)

23      if(!rst_n)

24          begin

25              in_1 <= 1'b1;

26              in_2 <= 1'b1;

27          end

28      else

29          begin

30              in_1 <= rs232_rx;

31              in_2 <= in_1;

32          end

33

34  assign  rx_en = in_2 &(~in_1); //當檢測由高變低的過程后,使能信號拉高

35          

36  //確保在一幀數據的中間8位進行數據的讀取,讀取完成后,使能信號tx_en控制串口發送模塊

37  reg [7:0] rx_d_r;

38  always@(posedge clk or negedge rst_n)

39      if(!rst_n)

40          begin

41              rx_d_r <= 8'd0;

42              rx_d  <= 8'd0;

43          end

44      else if(rx_sel_data)

45          case(rx_num)

46              0:;                     //忽略開始位

47              1: rx_d_r[0] <= rs232_rx;//采集中間8位有效數據

48              2: rx_d_r[1] <= rs232_rx;

49              3: rx_d_r[2] <= rs232_rx;

50              4: rx_d_r[3] <= rs232_rx;

51              5: rx_d_r[4] <= rs232_rx;

52              6: rx_d_r[5] <= rs232_rx;

53              7: rx_d_r[6] <= rs232_rx;

54              8: rx_d_r[7] <= rs232_rx;

55              9: rx_d  <= rx_d_r;      //鎖存采集的8位有效位(忽略停止位)

56              default:;

57          endcase

58  //使能信號:在完成接收以后立即拉高tx_en(啟動發送模塊)

59  always@(posedge clk or negedge rst_n) 

60      if(!rst_n)

61          tx_en <= 0;

62      else if(rx_num == 9 && rx_sel_data) //在接收停止位之后拉高一個時鍾

63          tx_en <= 1;

64      else

65          tx_en <= 0; 

66  endmodule

22~34行的作用是下降沿檢測,電路檢測到rs232_rx下降沿出現說明一幀數據開始傳輸。38~57行在波特率采樣控制信號rx_sel_data的控制下將串行數據進行串並轉換逐位存儲到中間並行寄存器rx_d_r59~65當數據采集完畢以后,負責輸出發送使能信號tx_en,觸發數據發送模塊啟動,將采集到的數據發送到上位機。

數據發送模塊波特率生成

/****************************************************          

 *   Engineer      :   夢翼師兄

 *   QQ             :   761664056

 *   The module function:控制串口發送數據的速率

*****************************************************/

00  module bps_tx(              

01              clk,   //系統時鍾50MHz

02              rst_n, //低電平復位

03              tx_en,    //使能信號:串口發送開始

04              

05              tx_sel_data,  //波特率計數的中心點(采集數據的使能信號)

06              tx_num   //一幀數據0-9

07      );

08      //模塊輸入

09      input            clk;   //系統時鍾50MHz

10      input            rst_n; //低電平復位

11      input            tx_en;    //使能信號:串口發送開始

12      //模塊輸出

13      output reg       tx_sel_data;  //波特率計數的中心點

14      output reg [3:0] tx_num;   //一幀數據0-9

15  //設置參數

16  parameter bps_div   = 13'd5207,

17            bps_div_2 = 13'd2603;

18

19  //發送標志位:接收到使能信號tx_en后,將標志位flag拉高,當信號tx_num計完一幀數據后拉低

20  reg       flag;

21  always@(posedge clk or negedge rst_n)   

22      if(!rst_n)

23          flag <= 0;

24      else if(tx_en)

25          flag <= 1;

26      else if(tx_num == 4'd10)

27          flag <= 0;  

28          

29  //波特率計數

30  reg [12:0] cnt;

31  always@(posedge clk or negedge rst_n)

32      if(!rst_n)

33          cnt  <= 13'd0;

34    else if(flag && cnt < bps_div)

35          cnt <= cnt + 1'b1;

36    else

37 cnt <= 13'd0;

38

39 //規定發送數據的范圍:即一幀數據(10位:1位開始位,8位數據位,1位結束位)

40 always@(posedge clk or negedge rst_n)

41 if(!rst_n)

42 tx_num <= 4'd0;

43 else if(tx_sel_data && flag)

44 tx_num <= tx_num + 1'b1;

45 else if(tx_num == 4'd10)

46 tx_num <= 1'd0;

47

48 //數據在波特率的中間部分采集:即發送數據的使能信號

49 always@(posedge clk or negedge rst_n)

50 if(!rst_n)

51 tx_sel_data <= 1'b0;

52 else if(cnt == bps_div_2)//中間取數是為了產生尖峰脈沖,尖峰脈沖為采集數據的使能信號,用來把握速率

53 tx_sel_data <= 1'b1;

54 else

55 tx_sel_data <= 1'b0;

56

57 endmodule

本模塊確定了發送數據與接收數據的有效范圍並且進行分頻計數(在此例中,使用的波特率為9600bps,時鍾為50MHZ,則分頻計數值為5207)。該模塊功能和bps_rx模塊功能類似。

數據發送模塊

/****************************************************          

 *   Engineer      :   夢翼師兄

 *   QQ             :   761664056

 *   The module function:發送串口串行數據

*****************************************************/

00 module uart_tx(                 

01 clk,              //50MHZ時鍾

02 rst_n,            //低電平復位

03 tx_num,           //一幀數據控制位

04 tx_sel_data,     //波特率計數的中心點(采集數據的使能信號)

05 rx_d,             //8位數據(即輸入數據)

06 rs232_tx         //uart發送信號(一幀數據)

07 );

08 //模塊輸入

09 input        clk;              //50MHZ時鍾

10 input       rst_n;            //低電平復位

11 input [3:0]  tx_num;              //一幀數據控制位

12 input        tx_sel_data;         //波特率計數的中心點(采集數據的使能信號)

13 input [7:0]  rx_d;             //8位數據(即輸入數據)

14 //模塊輸出

15 output reg   rs232_tx;          //uart發送信號(一幀數據)

16 //在串口發送的過程中,確保發送1位開始位,8位有效數據位,1位結束位

17 always@(posedge clk or negedge rst_n)

18 if(!rst_n)

19 rs232_tx <= 1'b1;

20 else if(tx_sel_data)

21 case(tx_num)

22 0: rs232_tx  <= 1'b0;  //開始位為低電平

23 1: rs232_tx  <= rx_d[0];

24 2: rs232_tx  <= rx_d[1];

25 3: rs232_tx  <= rx_d[2];

26 4: rs232_tx  <= rx_d[3];

27 5: rs232_tx  <= rx_d[4];

28 6: rs232_tx  <= rx_d[5];

29 7: rs232_tx  <= rx_d[6];

30 8: rs232_tx  <= rx_d[7];

31 9: rs232_tx  <= 1'b1;  //結束位為高電平

32   default: rs232_tx  <= 1'b1;  //串口的其它空閑位均要拉至高電平

33   endcase

34

35 endmodule

20~31行在發送使能信號tx_sel_data作用下,rs232_tx首先發送啟動信號然后將並行數據逐位輸出完成並串轉換,八位數據發送完畢以后發送停止信號結束數據傳輸

頂層模塊

/****************************************************          

 *   Engineer      :   夢翼師兄

 *   QQ             :   761664056

 *   The module function:負責頂層連接

*****************************************************/

00 module uart(  

01 clk,     //系統時鍾50MHz

02 rst_n,   //低電平復位

03 rs232_rx, //uart發送信號

04 rs232_tx  //uart接收信號

05 );

06 //系統輸入

07 input   clk;     //系統時鍾50MHz

08 input   rst_n;   //低電平復位

09 input   rs232_rx; //uart發送信號

10 //系統輸出

11 output  rs232_tx;  //uart接收信號

12

13 //內部信號:模塊內部的接口信號,比如模塊bps_rx的輸入信號en,通過內部信號rx_en與模塊uart_rx的輸出信號en相連

14

15 wire  rx_en;

16 wire  tx_en;

17 wire [7:0] rx_d;

18 wire [3:0] rx_num,tx_num;

19

20 //模塊例化

21 bps_rx bps_rx(                //串口接收波特率計數模塊

22 .clk(clk),

23 .rst_n(rst_n),

24 .rx_en(rx_en),

25 .rx_num(rx_num),

26 .rx_sel_data(rx_sel_data)

27 );

28

29 uart_rx uart_rx(             //串口接收模塊

30 .clk(clk),

31 .rx_d(rx_d),

32 .rst_n(rst_n),

33 .rs232_rx(rs232_rx),

34 .rx_en(rx_en),

35 .rx_num(rx_num),

36 .rx_sel_data(rx_sel_data),

37 .tx_en(tx_en)

38 );

39

40 bps_tx bps_tx(             //串口發送波特率計數模塊

41 .clk(clk),

42 .rst_n(rst_n),

43 .tx_en(tx_en),

44 .tx_num(tx_num),

45 .tx_sel_data(tx_sel_data) 

46 );

47 uart_tx uart_tx( //串口發送模塊

48 .clk(clk),

49 .rst_n(rst_n),

50 .rx_d(rx_d),

51 .rs232_tx(rs232_tx),

52 .tx_num(tx_num),

53 .tx_sel_data(tx_sel_data)

54 );

55

56 endmodule

 編寫完可綜合代碼之后,查看RTL視圖如下:

 

 

RTL視圖可以看出,代碼綜合以后得到的電路結構和我們設計的系統框圖一致,說明頂層邏輯連接正確,接下來編寫測試代碼如下:

/****************************************************          

 *   Engineer      :   夢翼師兄

 *   QQ             :   761664056

 *   The module function:測試uart 

*****************************************************/

00 `timescale 1ns/1ps   //設置仿真時間單位與精度分別為1ns/1ns

01

02 module  uart_tb;     

03 //系統輸入

04 reg  clk;   //系統時鍾50MHz

05 reg  rst_n; //低電平復位

06 reg  rs232_rx; //uart發送信號

07 //系統輸出

08 wire rs232_tx;//uart接收信號

09 //例化

10 uart  uart(              

11 .clk(clk), //系統時鍾50MHz

12 .rst_n(rst_n), //低電平復位

13 .rs232_rx(rs232_rx), //uart發送信號

14 .rs232_tx(rs232_tx) //uart發送信號

15 );

16  

17 initial              

18 begin

19 clk = 0; rst_n = 0; rs232_rx= 1;  //在復位階段,將激勵賦初值

20 #200.1  rst_n = 1;    //延時200ns后停止復位

21 //模擬發送一幀數據(發送時間的延時根據所設定的波特率計算)

22 #200    rs232_rx= 0;//開始位

23 #110000 rs232_rx= 0;//發送數據8'ha4 (8'b0110_0100)

24 #110000 rs232_rx= 1;

25 #110000 rs232_rx= 1;

26 #110000 rs232_rx= 0;

27 #110000 rs232_rx= 0;

28 #110000 rs232_rx= 1;

29 #110000 rs232_rx= 0;

30 #110000 rs232_rx= 0;

31 #110000 rs232_rx= 1;//結束位

32 #1500000 $stop;   //仿真1500000ns后停止仿真

33 end

34 always #10  clk = ~clk;   //時鍾的表示,即每隔10ns翻轉一次,一個周期的時間即為20ns,時鍾為1/20ns = 50MHZ

35 endmodule

22~31行模擬了上位機數據的輸入用來測試我們設計的串口模塊是否能夠進行正常的數據收發

 仿真分析

從波形上可以很清楚的看到,接收和發送的波形完全相同,說明我們的設計正確。

 

 


免責聲明!

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



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