串口通信
1、串口簡介
串行接口,COM接口,只需要兩根線就能實現兩台設備之間的通信。UART指的是異步的串行接口,通用異步收發。標准常用的是RS-232標准接口 現在電腦上沒有串口了,所以使用的是USB轉串口芯片,CH340芯片。
換句話說,只需要兩根數據線UART_RXD和UART_TXD,就能完成兩台設備之間的通信。
2、串口時序
兩根數據線各自獨立互不影響,二者的時序是相同的。不同之處是UART_RXD是主機MASTER發送給從機SLAVE,UART_TXD是SLAVE發送給MASTER。 由於兩根線的時序完全相同且獨立,下面以UART_TXD為例。 空閑狀態時,UART_TXD一直拉高,當要傳輸數據之前,拉低一個數據位,此后開始傳輸數據。數據之后有一個校驗位,校驗位之后是停止位,停止位之后進入下一個傳輸周期。至此,完成了一個數據包的傳輸。
注意:
(1)、傳輸的數據是從低比特位開始傳,比如101010,接受端的接受順序是010101。
(2)、傳輸數據的位數是MASTER與SLAVE約定好的,可以是4、5、6、7、8位,時序圖中是以八位為例。
(3)、校驗位一般是奇偶檢驗。當然,也可以選擇沒有檢驗位,前提是MASTER與SLAVE約定好,在SLAVE解析接收到的數據的時候,不安排校驗位的解析。
(4)、停止位,停止位是保證兩段傳輸之間一定要有間隔。兩段傳輸之間可以沒有空閑時間,但是,停止位一定要有。
3、時間的問題
從時序圖上可以看出,串口的發送和接受是沒有時鍾的,換句話說,這是一個異步時序。那么如何確定每個位所需要的時間就尤為重要。 這個問題的要點是波特率,每秒發送/接受單位的個數。我們使用的串口是以比特為單位,所以這里波特率與我們的比特率相同。常見的波特率的數值有9600,19200,38400,57600,115200等。以9600為例,表示一秒鍾發送/接受9600個比特。所有我們可以計算出單個bit所占用的時間為 1s/9600 = 104166ns。我們傳輸的起始標志位,傳輸的數據的每一位,校驗位(可有可無),在9600波特率的情況下,各自占據了104166ns的時間。 所以,假設從MASTER發送8'b11001110給SLAVE的的話,數據線的電平變化如下:
所以,FPGA在接收串口數據的時候,按照每個位的時間,設計計數器。假設是50MHz的時鍾,那么接受一個bit需要的時鍾周期是 104166ns/20ns = 5208個周期,所以在計數器數到5208/2 +1的時候,將UART_RXD的當前值寄存即可。注意接受數據的時候先接受到的是起始位,最后的空閑位置就沒有必要接受了。
4、代碼 項目要求:電腦通過串口發送八位數據給FPGA,FPGA通過這八位數據來控制八個LED燈的亮暗。波特率9600,無校驗位。 代碼如下: ``
1 module uart( 2 clk , 3 rst_n , 4 rx_uart , 5 led 6 ); 7 input clk ; 8 input rst_n ; 9 input rx_uart ; 10 output reg[7:0] led ; 11 12 reg flag_add; 13 //波特率9600 1s/9600 = 104166ns 14 //50M時鍾,一個周期是20ns,104166ns/20ns=5208個時鍾周期 15 //用計數器來計數時鍾周期,每到5208從rx_uart取出一個數據 16 //一個數據包是八個數據+一個起始位數據 17 18 //計數器cnt0用來計數每個bit的5208個周期 19 reg[12:0] cnt0 ; 20 wire add_cnt0 ; 21 wire end_cnt0 ; 22 23 always @(posedge clk or negedge rst_n)begin 24 if(!rst_n)begin 25 cnt0 <= 0; 26 end 27 else if(add_cnt0)begin 28 if(end_cnt0) 29 cnt0 <= 0; 30 else 31 cnt0 <= cnt0 + 1; 32 end 33 end 34 assign add_cnt0 = flag_add; 35 assign end_cnt0 = add_cnt0 && cnt0 == 5208 - 1 ; 36 37 //計數器1用來計數接受的bit數量 38 reg[3:0] cnt1 ; 39 wire add_cnt1 ; 40 wire end_cnt1 ; 41 42 always @(posedge clk or negedge rst_n)begin 43 if(!rst_n)begin 44 cnt1 <= 0; 45 end 46 else if(add_cnt1)begin 47 if(end_cnt1) 48 cnt1 <= 0; 49 else 50 cnt1 <= cnt1 + 1; 51 end 52 end 53 assign add_cnt1 = end_cnt0; 54 assign end_cnt1 = add_cnt1 && cnt1 == 9 - 1; 55 56 //flag_add的定義,rx_uart下降沿的時候拉高,end_cnt1的時候拉低 57 //需要一個rx_uart邊沿檢測電路,注意,rx_uart是一個異步信號,與本地時鍾無關。 58 //可以把rx_uart放進敏感列表,但是他會被FPGA誤認為是個時鍾,造成電路不穩定 59 //安全的做法:用本地時鍾對rx_uart進行延一拍,然后前后兩拍比較完成邊沿檢測 60 //但是,因為本項目中的rx_uart是個異步信號,可能在時鍾邊沿變化, 61 //不能滿足setup time 和 保持時間 所以就延兩拍更安全。(兩拍以后的信號是穩定信號) 62 reg rx_uart_ff0; 63 reg rx_uart_ff1; 64 reg rx_uart_ff2; 65 always@(posedge clk or negedge rst_n) 66 if(!rst_n)begin 67 rx_uart_ff0 <= 1; 68 rx_uart_ff1 <= 1; 69 rx_uart_ff2 <= 1; 70 end 71 else begin 72 rx_uart_ff0 <= rx_uart; 73 //rx_uart_ff0是異步信號打一拍的結果,不能做條件用。以后的可以 74 rx_uart_ff1 <= rx_uart_ff0; 75 rx_uart_ff2 <= rx_uart_ff1; 76 end 77 78 always@(posedge clk or negedge rst_n) 79 if(!rst_n)begin 80 flag_add <= 0; 81 end 82 else if(!rx_uart_ff1 & rx_uart_ff2) begin//下降沿 83 flag_add <= 1; 84 end 85 else if(end_cnt1) 86 flag_add <= 0; 87 88 always@(posedge clk or negedge rst_n) 89 if(!rst_n)begin 90 led <= 8'hff; 91 end 92 else if(add_cnt0 && cnt0 == 5208/2-1 && cnt1>0)begin 93 led[cnt1-1] <= rx_uart_ff1; 94 end 95 96 97 endmodule