買了一個開發板學習FPGA,找到的各種東西就記錄在這個博客里了,同時也方便把自己不會的問題找到的結果記錄一下,都是自己手打,所以可能說的話不那么嚴謹,不那么精准,看到的人要帶着自己的思考去看,記住盡信書不如無書,哈哈哈。。。。。。
一、UART是什么?
UART是一種通用串行數據總線,也就是用於數據傳輸。是用於主機與輔助設備進行通信。這里的主機理解為計算機,計算機內部采用並行數據,輔助設備采用串行數據。中間需要設備進行數據轉換,這也決定了UART工作原理是將傳輸數據的每個字符一位接一位地傳輸。UART采用異步傳輸模式,異步傳輸將比特分成小組進行傳送,小組可以是8位的1個字符或更長。發送方可以在任何時刻發送這些比特組,而接收方從不知道它們會在什么時候到達。例如計算機鍵盤與主機的通信。UART用於遠距離傳輸較為適合。可以數據同時發送和接收。
起始位:先發出一個邏輯”0”信號,表示傳輸字符的開始。
數據位:可以是5~8位邏輯”0”或”1”。如ASCII碼(7位),擴展BCD碼(8位)。
校驗位:數據位加上這一位后,使得“1”的位數應為偶數(偶校驗)或奇數(奇校驗)。
停止位:它是一個字符數據的結束標志。可以是1位、1.5位、2位的高電平。
空閑位:處於邏輯“1”狀態,表示當前線路上沒有資料傳送。
二、UART串口通信一般包括部分
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通信,最主要還是理解異步和串口這兩個東西,方便遠距離傳輸,串口一位一位傳輸,犧牲了時間降低時序要求。
