一、軟件平臺與硬件平臺
軟件平臺:
1、操作系統:Windows-8.1
2、開發套件:ISE14.7
硬件平臺:
1、FPGA型號:XC6SLX45-2CSG324
2、USB轉UART芯片:Silicon Labs CP2102GM?
二、原理介紹
串口是串行接口(serial port)的簡稱,也稱為串行通信接口或COM接口。串口通信是指采用串行通信協議(serial communication)在一條信號線上將數據一個比特一個比特地逐位進行傳輸的通信模式。串口按電氣標準及協議來劃分,包括RS-232、RS-422、RS485等。其中最常用的就是RS-232接口。
RS-232接口有以下三個特性:
1、用了一個9針的連接器"DB-9"(早期的電腦有用25針的連接器"DB-25")
2、允許全雙工通信(即通過串口發送數據和接收數據可以同時進行)
3、通信的最大速率大約在10KBytes/s左右
現在一般都用USB轉串口線進行串口通信):
?
雖然DB-9接頭一共有9根線,但是實現串口通信只需要其中的3根線就可以了,分別是:
1、pin-2:RXD(receive data),接收串行數據
2、pin-3:TXD(transmit data),發送串行數據
3、pin-5:GND(ground),地線
在串口通信中,數據在1位寬的單條線路上進行傳輸,一個字節的數據要分為8次,由低位到高位按順序一位一位的進行傳送,這個過程稱為數據的"串行化(serialized)"過程。由于串口通信是一種異步通信協議,并沒有時鐘信號隨著數據一起傳輸,而且空閑狀態(沒有數據傳輸的狀態)的時候,串行傳輸線為高電平1,所以發送方發送一個字節數據之前會先發送一個低電平0,接收方收到這個低電平0以后就知道有數據要來了,準備開始接收數據從而實現一次通信。串口通信的時序如下圖所示:
串口通信的規范如下:
1、空閑狀態(沒有數據傳輸的狀態)下,串行傳輸線上為高電平1
2、發送方發送低電平0表示數據傳輸開始,這個低電平表示傳輸的起始位
3、8-bit的數據位(1 Byte)是從最低位開始發送,最高位最后發送
4、數據位的最高位發送完畢以后的下一位是奇偶校驗位,這一位可以省略不要,同時,當不發送奇偶校驗位的時候接收方也相應的不接收校驗位
5、最后一位是停止位,用高電平1表示停止位
下面以發送字節0x55為例來說明整個的發送過程:
先把0x55轉化成二進制為:01010101。顯然0x55的最低位bit 0是1,次低位bit 1是0,……..,最高位bit 7是0,由于串口是從最低位開始發送一個字節,所以0x55各個位的發送順序是1-0-1-0-1-0-1-0,波形如下圖所示
下面在給出一個波形,根據上面的規則也可以很容易判斷這是發送字節0x13的波形
接下來的最后一個問題是:串口傳輸的速度是多少?
實際上,串口傳輸的速度用波特率(baudrate)來指定。波特率表示的是每秒發送的比特數,單位是bps(bits-per-seconds),例如,1000 bauds表示1秒鐘發送了1000個比特,或者說每個比特持續的時間是1ms。關于串口發送的波特率是有一組標準的規定的,并不是隨便一個數字。常用的波特率標準有:
1、1200 bps
2、9600 bps (常用)
3、38400 bps
4、115200 bps (常用,而且通常情況下是我們能用的最快的波特率)
波特率為115200 bps時,每個比特持續的時間為(1/115200)=8.7us,所以發送8個bit(1 Byte)需要的時間是8*8.7us=69us。在不考慮奇偶校驗位的情況下,發送一個字節還需要發送額外的1個起始位和1個停止位,所以發送1個字節實際所需要的最少時間是10*8.7us=87us,這意味著1s(1000000us)中能發送的字節數為(1000000/87) = 11494,所以在波特率為115200bps的情況下,串口傳輸數據的速率約為11.5KB/s。而有些電腦的串口有時候需要一個更長的停止位,比如1.5位或2位的停止位,那么發送一個字節所需要的時間比只有一個比特停止位的情況所耗費的時間更長,在這種情況下,串口的傳輸速率會低于10.5KB/s。
通過上面一系列的總結以后,可以得出FPGA與PC之間的串口通信主要包括三個模塊:波特率產生模塊、發射模塊和接收模塊。
三、目標功能
1、編寫發送模塊的verilog代碼,并往PC上連續不斷發送0x00~0xff這些數據,PC上用串口調試助手進行接收并以16進制顯示出來
2、在第一個功能的基礎上編寫接收模塊的verilog代碼,接收模塊接收到第一個功能中發送模塊發送的數據以后,用接收到的并行數據的低四位驅動板上的四個LED燈
3、編寫一個頂層模塊把發送模塊和接收模塊均例化進去,然后從PC的串口調試助手上發送數據到FPGA,FPGA接收到數據以后把接收的數據返回給串口調試助手顯示
四、設計思路與Verilog代碼編寫
4.1、發送模塊波特率時鐘的設計與實現
本節以波特率為115200bps為例來說明波特率模塊設計方法,其余波特率可以以此類推。由于我的開發板上的時鐘為50MHz,周期T=20ns,而波特率為115200bps,所以1個bit持續的時間是8.7us,那么每個bit占用的周期數N=(8.7us / 20ns) = 434,所以可以定義一個計數器,每當計數器從0計數到433的時候就把計數器清零,然后在計數值為1(這個計數值最好比433的一半要小,這篇博客的最后一部分分析了原因)的情況下產生一個高脈沖。發射模塊只要檢測到這個高脈沖的到來就發送一個bit,這樣就實現了波特率為115200bps的串口數據發送。
而接收模塊的波特率時鐘產生邏輯與發送的波特率時鐘相比稍有不同。不同之處在于當接收模塊檢測到I_rs232_rxd的下降沿以后,表示有數據過來,準備開始接收數據了,由于一個bit持續的時間為434個時鐘周期,所以為了保證接收模塊接收數據的準確性,我們需要在434/2=217個周期,也就是數據的正中間位置的時候把輸入的數據接收并存起來。也就是說接收模塊的波特率時鐘要比發射模塊的波特率時鐘滯后數個周期
波特率產生模塊的框圖如下圖所示
其中:
I_clk是系統時鐘;
I_rst_n是系統復位;
I_tx_bps_en是發射模塊波特率使能信號,當I_tx_bps_en為1時O_bps_tx_clk才有時鐘信號輸出;
I_rx_bps_en是接收模塊波特率使能信號,當I_rx_bps_en為1時O_bps_rx_clk才有時鐘信號輸出。
波特率模塊的完整代碼如下:
module baudrate_gen ( input I_clk , // 系統50MHz時鐘 input I_rst_n , // 系統全局復位 input I_bps_tx_clk_en , // 串口發送模塊波特率時鐘使能信號 input I_bps_rx_clk_en , // 串口接收模塊波特率時鐘使能信號 output O_bps_tx_clk , // 發送模塊波特率產生時鐘 output O_bps_rx_clk // 接收模塊波特率產生時鐘 ); parameter C_BPS9600 = 5207 , //波特率為9600bps C_BPS19200 = 2603 , //波特率為19200bps C_BPS38400 = 1301 , //波特率為38400bps C_BPS57600 = 867 , //波特率為57600bps C_BPS115200 = 433 ; //波特率為115200bps parameter C_BPS_SELECT = C_BPS115200 ; //波特率選擇 reg [12:0] R_bps_tx_cnt ; reg [12:0] R_bps_rx_cnt ; /////////////////////////////////////////////////////////// // 功能:串口發送模塊的波特率時鐘產生邏輯 /////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_bps_tx_cnt <= 13'd0 ; else if(I_bps_tx_clk_en == 1'b1) begin if(R_bps_tx_cnt == C_BPS_SELECT) R_bps_tx_cnt <= 13'd0 ; else R_bps_tx_cnt <= R_bps_tx_cnt + 1'b1 ; end else R_bps_tx_cnt <= 13'd0 ; end assign O_bps_tx_clk = (R_bps_tx_cnt == 13'd1) ? 1'b1 : 1'b0 ; /////////////////////////////////////////////////////////// // 功能:串口接收模塊的波特率時鐘產生邏輯 /////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_bps_rx_cnt <= 13'd0 ; else if(I_bps_rx_clk_en == 1'b1) begin if(R_bps_rx_cnt == C_BPS_SELECT) R_bps_rx_cnt <= 13'd0 ; else R_bps_rx_cnt <= R_bps_rx_cnt + 1'b1 ; end else R_bps_rx_cnt <= 13'd0 ; end assign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1'b1) ? 1'b1 : 1'b0 ; endmodule
?
?
波特率模塊的ModelSim仿真圖為
4.2、發送模塊的設計與實現
有了波特率時鐘以后,就可以開始編寫發送模塊的內部邏輯了。發送模塊的結構框圖如下圖所示
? ? ? ?其中:
I_clk是系統時鐘;
I_rst_n是系統復位;
I_tx_start是開始發送信號,當檢測到I_tx_start為高電平時,立馬把輸入I_para_data[7:0]的數據串行化成單bit的發出去;
I_bps_tx_clk是發送模塊波特率時鐘信號,當檢測到I_bps_tx_clk為高的時候就發送1個bit;
I_para_data[7:0]是并行的8-bit數據;
O_rs232_txd是串行的bit數據流;
O_bps_clk_en是發射波特率時鐘啟動信號,當它為1是波特率產生模塊才能產生發射模塊的波特率時鐘;
O_tx_done是發送1字節數據完成的標志位,當一個字節發送完畢以后,O_tx_done產生一個高脈沖。
以發送字節0x55為例,發送模塊幾個關鍵信號的時序圖如下圖所示
發送模塊的代碼如下:
?
?
module uart_txd ( input I_clk , // 系統50MHz時鐘 input I_rst_n , // 系統全局復位 input I_tx_start , // 發送使能信號 input I_bps_tx_clk , // 發送波特率時鐘 input [7:0] I_para_data , // 要發送的并行數據 output reg O_rs232_txd , // 發送的串行數據,在硬件上與串口相連 output reg O_bps_tx_clk_en , // 波特率時鐘使能信號 output reg O_tx_done // 發送完成的標志 ); reg [3:0] R_state ; reg R_transmiting ; // 數據正在發送標志 ///////////////////////////////////////////////////////////////////////////// // 產生發送 R_transmiting 標志位 ///////////////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_transmiting <= 1'b0 ; else if(O_tx_done) R_transmiting <= 1'b0 ; else if(I_tx_start) R_transmiting <= 1'b1 ; end ///////////////////////////////////////////////////////////////////////////// // 發送數據狀態機 ///////////////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_state <= 4'd0 ; O_rs232_txd <= 1'b1 ; O_tx_done <= 1'b0 ; O_bps_tx_clk_en <= 1'b0 ; // 關掉波特率時鐘使能信號 end else if(R_transmiting) // 檢測發送標志被拉高,準備發送數據 begin O_bps_tx_clk_en <= 1'b1 ; // 發送數據前的第一件事就是打開波特率時鐘使能信號 if(I_bps_tx_clk) // 在波特率時鐘的控制下把數據通過一個狀態機發送出去,并產生發送完成信號 begin case(R_state) 4'd0 : // 發送起始位 begin O_rs232_txd <= 1'b0 ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd1 : // 發送 I_para_data[0] begin O_rs232_txd <= I_para_data[0] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd2 : // 發送 I_para_data[1] begin O_rs232_txd <= I_para_data[1] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd3 : // 發送 I_para_data[2] begin O_rs232_txd <= I_para_data[2] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd4 : // 發送 I_para_data[3] begin O_rs232_txd <= I_para_data[3] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd5 : // 發送 I_para_data[4] begin O_rs232_txd <= I_para_data[4] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd6 : // 發送 I_para_data[5] begin O_rs232_txd <= I_para_data[5] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd7 : // 發送 I_para_data[6] begin O_rs232_txd <= I_para_data[6] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd8 : // 發送 I_para_data[7] begin O_rs232_txd <= I_para_data[7] ; O_tx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd9 : // 發送 停止位 begin O_rs232_txd <= 1'b1 ; O_tx_done <= 1'b1 ; R_state <= 4'd0 ; end default :R_state <= 4'd0 ;
endcase end end else begin O_bps_tx_clk_en <= 1'b0 ; // 一幀數據發送完畢以后就關掉波特率時鐘使能信號 R_state <= 4'd0 ; O_tx_done <= 1'b0 ; O_rs232_txd <= 1'b1 ; end end endmodule
?
?
其中當檢測到輸入信號I_tx_start為高電平以后,發送模塊立即把R_transmiting信號拉高,表示開始要發送數據了,在R_transmiting為高電平的期間,打開波特率時鐘使能信號并且在波特率時鐘的控制下通過一個狀態機把并行數據發送出去,并產生發送完成信號O_tx_done,等O_tx_done為高以后再把R_transmiting拉低表示一次發送結束。
為了實現功能1的效果還需要編寫一個頂層模塊把波特率模塊和發送模塊例化進去并產生發送的信號,頂層模塊的代碼如下:
?
?
module uart_tx_top ( input I_clk , // 系統50MHz時鐘 input I_rst_n , // 系統全局復位 output O_rs232_txd // 發送的串行數據,在硬件上與串口相連 ); wire W_bps_tx_clk ; wire W_bps_tx_clk_en ; wire W_tx_start ; wire W_tx_done ; wire [7:0] W_para_data ; reg [7:0] R_data_reg ; reg [31:0] R_cnt_1s ; reg R_tx_start_reg ; assign W_tx_start = R_tx_start_reg ; assign W_para_data = R_data_reg ; ///////////////////////////////////////////////////////////////////// // 產生要發送的數據 ///////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_cnt_1s <= 31'd0 ; R_data_reg <= 8'd0 ; R_tx_start_reg <= 1'b0 ; end else if(R_cnt_1s == 31'd24_999_999) begin R_cnt_1s <= 31'd0 ; R_data_reg <= R_data_reg + 1'b1 ; R_tx_start_reg <= 1'b1 ; end else begin R_cnt_1s <= R_cnt_1s + 1'b1 ; R_tx_start_reg <= 1'b0 ; end end uart_txd U_uart_txd ( .I_clk (I_clk ), // 系統50MHz時鐘 .I_rst_n (I_rst_n ), // 系統全局復位 .I_tx_start (W_tx_start ), // 發送使能信號 .I_bps_tx_clk (W_bps_tx_clk ), // 波特率時鐘 .I_para_data (W_para_data ), // 要發送的并行數據 .O_rs232_txd (O_rs232_txd ), // 發送的串行數據,在硬件上與串口相連 .O_bps_tx_clk_en (W_bps_tx_clk_en ), // 波特率時鐘使能信號 .O_tx_done (W_tx_done ) // 發送完成的標志 ); baudrate_gen U_baudrate_gen ( .I_clk (I_clk ), // 系統50MHz時鐘 .I_rst_n (I_rst_n ), // 系統全局復位 .I_bps_tx_clk_en (W_bps_tx_clk_en ), // 串口發送模塊波特率時鐘使能信號 .I_bps_rx_clk_en ( ), // 串口接收模塊波特率時鐘使能信號 .O_bps_tx_clk (W_bps_tx_clk ), // 發送模塊波特率產生時鐘 .O_bps_rx_clk ( ) // 接收模塊波特率產生時鐘 ); endmodule
?
?
下載到板之前先用Modelsim仿一下看邏輯是否正確,仿之前把R_cnt_1s這個參數的上限值設置小一點,比如5000,可以加快仿真速度,下圖是仿真時序圖,顯然完全滿足設計要求。
仿真結束以后就綁定管腳然后下載到FPGA中,接著打開電腦的串口調試助手,下圖是我的電腦上的顯示效果:
4.3、接收模塊的設計與實現
波特率模塊和發送模塊都沒問題以后,就可以開始編寫接收模塊的代碼了。接收模塊的結構框圖如下圖所示
其中:
I_clk是系統時鐘;
I_rst_n是系統復位;
I_rx_start是開始發送信號,當I_rx_start一直為高電平時,接收模塊檢測到有數據就會接收;
I_bps_rx_clk是接收模塊波特率時鐘信號,當檢測到I_bps_rx_clk為高的時候就接收1個bit;
I_rs232_rx是串行的bit數據流;
O_para_data[7:0]是并行的8-bit數據;
O_bps_rx_clk_en是發射波特率時鐘啟動信號,當它為1是波特率產生模塊才能產生接收模塊的波特率時鐘;
O_rx_done是接收1字節數據完成的標志位,當一個字節接收完畢以后,O_rx_done產生一個高脈沖。
接收模塊與發射模塊的邏輯結構非常類似,但是由于接收模塊需要判斷串行數據流的起始位,所以還要加一段檢測串行數據流下降沿的邏輯,檢測串行數據流下降沿的代碼如下:
?
?
//////////////////////////////////////////////////////////////////////////////// // 功能:把 I_rs232_rxd 打的前兩拍,是為了消除亞穩態 // 把 I_rs232_rxd 打的后兩拍,是為了產生下降沿標志位 //////////////////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_rs232_rx_reg0 <= 1'b0 ; R_rs232_rx_reg1 <= 1'b0 ; R_rs232_rx_reg2 <= 1'b0 ; R_rs232_rx_reg3 <= 1'b0 ; end else begin R_rs232_rx_reg0 <= I_rs232_rxd ; R_rs232_rx_reg1 <= R_rs232_rx_reg0 ; R_rs232_rx_reg2 <= R_rs232_rx_reg1 ; R_rs232_rx_reg3 <= R_rs232_rx_reg2 ; end end // 產生I_rs232_rxd信號的下降沿標志位 assign W_rs232_rxd_neg = (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ;
?
?
這段邏輯一共把I_rs232_rxd信號打了四拍,其中前兩排是為了消除I_rs232_rxd的亞穩態(后面有時間專門討論亞穩態問題),后兩排用來產生I_rs232_rxd信號下降沿的標志位。
這里以接收0x55這個字節為例來演示接收模塊的幾個重要信號的時序圖如下圖所示:
接收模塊的完整代碼如下:
?
?
module uart_rxd ( input I_clk , // 系統50MHz時鐘 input I_rst_n , // 系統全局復位 input I_rx_start , // 接收使能信號 input I_bps_rx_clk , // 接收波特率時鐘 input I_rs232_rxd , // 接收的串行數據,在硬件上與串口相連 output reg O_bps_rx_clk_en , // 波特率時鐘使能信號 output reg O_rx_done , // 接收完成標志 output reg [7:0] O_para_data // 接收到的8-bit并行數據 ); reg R_rs232_rx_reg0 ; reg R_rs232_rx_reg1 ; reg R_rs232_rx_reg2 ; reg R_rs232_rx_reg3 ; reg R_receiving ; reg [3:0] R_state ; reg [7:0] R_para_data_reg ; wire W_rs232_rxd_neg ; //////////////////////////////////////////////////////////////////////////////// // 功能:把 I_rs232_rxd 打的前兩拍,是為了消除亞穩態 // 把 I_rs232_rxd 打的后兩拍,是為了產生下降沿標志位 //////////////////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_rs232_rx_reg0 <= 1'b0 ; R_rs232_rx_reg1 <= 1'b0 ; R_rs232_rx_reg2 <= 1'b0 ; R_rs232_rx_reg3 <= 1'b0 ; end else begin R_rs232_rx_reg0 <= I_rs232_rxd ; R_rs232_rx_reg1 <= R_rs232_rx_reg0 ; R_rs232_rx_reg2 <= R_rs232_rx_reg1 ; R_rs232_rx_reg3 <= R_rs232_rx_reg2 ; end end // 產生I_rs232_rxd信號的下降沿標志位 assign W_rs232_rxd_neg = (~R_rs232_rx_reg2) & R_rs232_rx_reg3 ; //////////////////////////////////////////////////////////////////////////////// // 功能:產生發送信號R_receiving //////////////////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_receiving <= 1'b0 ; else if(O_rx_done) R_receiving <= 1'b0 ; else if(I_rx_start && W_rs232_rxd_neg) R_receiving <= 1'b1 ; end //////////////////////////////////////////////////////////////////////////////// // 功能:用狀態機把串行的輸入數據接收,并轉化為并行數據輸出 //////////////////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin O_rx_done <= 1'b0 ; R_state <= 4'd0 ; R_para_data_reg <= 8'd0 ; O_bps_rx_clk_en <= 1'b0 ; end else if(R_receiving) begin O_bps_rx_clk_en <= 1'b1 ; // 打開波特率時鐘使能信號 if(I_bps_rx_clk) begin case(R_state) 4'd0 : // 接收起始位,但不保存 begin R_para_data_reg <= 8'd0 ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd1 : // 接收第0位,保存到R_para_data_reg[0] begin R_para_data_reg[0] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd2 : // 接收第1位,保存到R_para_data_reg[1] begin R_para_data_reg[1] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd3 : // 接收第2位,保存到R_para_data_reg[2] begin R_para_data_reg[2] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd4 : // 接收第3位,保存到R_para_data_reg[3] begin R_para_data_reg[3] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd5 : // 接收第4位,保存到R_para_data_reg[4] begin R_para_data_reg[4] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd6 : // 接收第5位,保存到R_para_data_reg[5] begin R_para_data_reg[5] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd7 :// 接收第6位,保存到R_para_data_reg[6] begin R_para_data_reg[6] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd8 : // 接收第7位,保存到R_para_data_reg[7] begin R_para_data_reg[7] <= I_rs232_rxd ; O_rx_done <= 1'b0 ; R_state <= R_state + 1'b1 ; end 4'd9 : // 接收停止位,但不保存,并把R_para_data_reg給輸出 begin O_para_data <= R_para_data_reg ; O_rx_done <= 1'b1 ; R_state <= 4'd0 ; end default:R_state <= 4'd0 ; endcase end end else begin O_rx_done <= 1'b0 ; R_state <= 4'd0 ; R_para_data_reg <= 8'd0 ; O_bps_rx_clk_en <= 1'b0 ; // 接收完畢以后關閉波特率時鐘使能信號 end end endmodule
?
?
在下載到開發板測試之前,可以先用ModelSim軟件對模塊進行一個功能仿真,方法是直接把接收模塊例化到上一小節測試發送模塊的例子中,例化的頂層代碼如下:
?
?
module uart_top ( input I_clk , // 系統50MHz時鐘 input I_rst_n , // 系統全局復位 output [3:0] O_led_out , output O_rs232_txd // 發送的串行數據,在硬件上與串口相連 ); wire W_bps_tx_clk ; wire W_bps_tx_clk_en ; wire W_bps_rx_clk ; wire W_bps_rx_clk_en ; wire W_tx_start ; wire W_tx_done ; wire W_rx_done ; wire [7:0] W_para_data ; wire [7:0] W_rx_para_data ; reg [7:0] R_data_reg ; reg [31:0] R_cnt_1s ; reg R_tx_start_reg ; assign W_tx_start = R_tx_start_reg ; assign W_para_data = R_data_reg ; assign O_led_out = W_rx_para_data[3:0] ; ///////////////////////////////////////////////////////////////////// // 產生要發送的數據 ///////////////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_cnt_1s <= 31'd0 ; R_data_reg <= 8'd0 ; R_tx_start_reg <= 1'b0 ; end else if(R_cnt_1s == 31'd5000) begin R_cnt_1s <= 31'd0 ; R_data_reg <= R_data_reg + 1'b1 ; R_tx_start_reg <= 1'b1 ; end else begin R_cnt_1s <= R_cnt_1s + 1'b1 ; R_tx_start_reg <= 1'b0 ; end end uart_txd U_uart_txd ( .I_clk (I_clk ), // 系統50MHz時鐘 .I_rst_n (I_rst_n ), // 系統全局復位 .I_tx_start (W_tx_start ), // 發送使能信號 .I_bps_tx_clk (W_bps_tx_clk ), // 波特率時鐘 .I_para_data (W_para_data ), // 要發送的并行數據 .O_rs232_txd (O_rs232_txd ), // 發送的串行數據,在硬件上與串口相連 .O_bps_tx_clk_en (W_bps_tx_clk_en ), // 波特率時鐘使能信號 .O_tx_done (W_tx_done ) // 發送完成的標志 ); baudrate_gen U_baudrate_gen ( .I_clk (I_clk ), // 系統50MHz時鐘 .I_rst_n (I_rst_n ), // 系統全局復位 .I_bps_tx_clk_en (W_bps_tx_clk_en ), // 串口發送模塊波特率時鐘使能信號 .I_bps_rx_clk_en (W_bps_rx_clk_en ), // 串口接收模塊波特率時鐘使能信號 .O_bps_tx_clk (W_bps_tx_clk ), // 發送模塊波特率產生時鐘 .O_bps_rx_clk (W_bps_rx_clk ) // 接收模塊波特率產生時鐘 ); uart_rxd U_uart_rxd ( .I_clk (I_clk ), // 系統50MHz時鐘 .I_rst_n (I_rst_n ), // 系統全局復位 .I_rx_start (1'b1 ), // 接收使能信號 .I_bps_rx_clk (W_bps_rx_clk ), // 接收波特率時鐘 .I_rs232_rxd (O_rs232_txd ), // 接收的串行數據,在硬件上與串口相連 .O_bps_rx_clk_en (W_bps_rx_clk_en ), // 波特率時鐘使能信號 .O_rx_done (W_rx_done ), // 接收完成標志 .O_para_data (W_rx_para_data ) // 接收到的8-bit并行數據 ); endmodule
?
?
仿真圖如下圖所示
?
由圖可以看到接收數據與發送的數據完全一致,說明邏輯沒有問題,接下來就綁定管腳然后把代碼下載到FPGA看看效果,正常的效果是,PC的串口調試助手一直按順序顯示00~FF這些數據,板上的LED燈的狀態與數據的低四位狀態相同。至此,功能二也全部實現完畢。
4.4、串口回顯功能的設計與實現
有了發射模塊和接收模塊以后,功能三的要求就很簡單了,直接寫一個頂層模塊,把串口的發送模塊與接收模塊例化進去就可以了,唯一要做的就是把接收模塊的接收完成標志位O_rx_done連接到發送模塊的I_tx_start上,把接收模塊的8-bit并行輸出總線O_para_data連接到發送模塊的8-bit并行輸入總線I_para_data上,下面直接給出頂層的代碼:
?
?
module uart_top ( input I_clk , // 系統50MHz時鐘 input I_rst_n , // 系統全局復位 input I_rs232_rxd , // 接收的串行數據,在硬件上與串口相連 output O_rs232_txd , // 發送的串行數據,在硬件上與串口相連 output [3:0] O_led_out ); wire W_bps_tx_clk ; wire W_bps_tx_clk_en ; wire W_bps_rx_clk ; wire W_bps_rx_clk_en ; wire W_rx_done ; wire W_tx_done ; wire [7:0] W_para_data ; assign O_led_out = W_para_data[3:0] ; baudrate_gen U_baudrate_gen ( .I_clk (I_clk ), // 系統50MHz時鐘 .I_rst_n (I_rst_n ), // 系統全局復位 .I_bps_tx_clk_en (W_bps_tx_clk_en ), // 串口發送模塊波特率時鐘使能信號 .I_bps_rx_clk_en (W_bps_rx_clk_en ), // 串口接收模塊波特率時鐘使能信號 .O_bps_tx_clk (W_bps_tx_clk ), // 發送模塊波特率產生時鐘 .O_bps_rx_clk (W_bps_rx_clk ) // 接收模塊波特率產生時鐘 ); uart_txd U_uart_txd ( .I_clk (I_clk ), // 系統50MHz時鐘 .I_rst_n (I_rst_n ), // 系統全局復位 .I_tx_start (W_rx_done ), // 發送使能信號 .I_bps_tx_clk (W_bps_tx_clk ), // 波特率時鐘 .I_para_data (W_para_data ), // 要發送的并行數據 .O_rs232_txd (O_rs232_txd ), // 發送的串行數據,在硬件上與串口相連 .O_bps_tx_clk_en (W_bps_tx_clk_en ), // 波特率時鐘使能信號 .O_tx_done (W_tx_done ) // 發送完成的標志 ); uart_rxd U_uart_rxd ( .I_clk (I_clk ), // 系統50MHz時鐘 .I_rst_n (I_rst_n ), // 系統全局復位 .I_rx_start (1'b1 ), // 接收使能信號 .I_bps_rx_clk (W_bps_rx_clk ), // 接收波特率時鐘 .I_rs232_rxd (I_rs232_rxd ), // 接收的串行數據,在硬件上與串口相連 .O_bps_rx_clk_en (W_bps_rx_clk_en ), // 波特率時鐘使能信號 .O_rx_done (W_rx_done ), // 接收完成標志 .O_para_data (W_para_data ) // 接收到的8-bit并行數據 ); endmodule
?
?
建立工程并綁定管腳以后下載到開發板中,利用串口調試助手的自動發送(自動發送的周期最好在200ms以上)功能我對波特率為9600bps和115200bps分別進行了測試,在9600bps的情況下我一共發送了1002512個字節,全部接受正確,115200bps波特率情況下一共發送了512325字節,也全部接受正確,邏輯基本穩定,歡迎大家繼續測。我第一次寫串口代碼的時候出現過在115200bps的情況下發送字節達到10萬以上的時候出現誤碼的情況,原因下一小節再說,上面的代碼已經把這個問題修復了,原因就出在波特率模塊上。至此,功能三已全部完成。
五、進一步思考
5.1、波特率模塊產生的O_bps_tx_clk滯后O_bps_rx_clk可能出現的問題
我最開始寫的波特率模塊如下:
?
?
module baudrate_gen ( input I_clk , // 系統50MHz時鐘 input I_rst_n , // 系統全局復位 input I_bps_tx_clk_en , // 串口發送模塊波特率時鐘使能信號 input I_bps_rx_clk_en , // 串口接收模塊波特率時鐘使能信號 output O_bps_tx_clk , // 發送模塊波特率產生時鐘 output O_bps_rx_clk // 接收模塊波特率產生時鐘 ); parameter C_BPS9600 = 5207 , //波特率為9600bps C_BPS19200 = 2603 , //波特率為19200bps C_BPS38400 = 1301 , //波特率為38400bps C_BPS57600 = 867 , //波特率為57600bps C_BPS115200 = 433 ; //波特率為115200bps parameter C_BPS_SELECT = C_BPS115200 ; //波特率選擇 reg [12:0] R_bps_tx_cnt ; reg R_bps_tx_clk_reg ; reg [12:0] R_bps_rx_cnt ; /////////////////////////////////////////////////////////// // 功能:串口發送模塊的波特率時鐘產生邏輯 /////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) begin R_bps_tx_cnt <= 13'd0 ; R_bps_tx_clk_reg <= 1'b0 ; end else if(I_bps_tx_clk_en == 1'b1) begin if(R_bps_tx_cnt == C_BPS_SELECT) begin R_bps_tx_cnt <= 13'd0 ; R_bps_tx_clk_reg <= 1'b1 ; end else begin R_bps_tx_cnt <= R_bps_tx_cnt + 1'b1 ; R_bps_tx_clk_reg <= 1'b0 ; end end else begin R_bps_tx_cnt <= 13'd0 ; R_bps_tx_clk_reg <= 1'b0 ; end end assign O_bps_tx_clk = R_bps_tx_clk_reg ; /////////////////////////////////////////////////////////// // 功能:串口接收模塊的波特率時鐘產生邏輯 /////////////////////////////////////////////////////////// always @(posedge I_clk or negedge I_rst_n) begin if(!I_rst_n) R_bps_rx_cnt <= 13'd0 ; else if(I_bps_rx_clk_en == 1'b1) begin if(R_bps_rx_cnt == C_BPS_SELECT) R_bps_rx_cnt <= 13'd0 ; else R_bps_rx_cnt <= R_bps_rx_cnt + 1'b1 ; end else R_bps_rx_cnt <= 13'd0 ; end assign O_bps_rx_clk = (R_bps_rx_cnt == C_BPS_SELECT >> 1'b1) ? 1'b1 : 1'b0 ; endmodule
?
?
其仿真如下所示:
當發送波特率時鐘使能信號打開以后,計數值計滿C_BPS_SELECT后才產生一個發送時鐘脈沖,而接收波特率時鐘只需要計滿C_BPS_SELECT的一半就產生了一個時鐘脈沖,這就導致在回顯實驗中,O_bps_tx_clk滯后于O_bps_rx_clk,而回顯實驗中我們直接把接收完成的標志直接接在了發送開始標志上,所以這就有可能導致,上一次的數據還沒發送完的時候這一次的數據已經來了,經過我的測試,使用上面的波特率邏輯,如果不做回顯實驗,一般沒問題,如果做回顯實驗,在波特率較高,比如115200bps和57600bps的情況下,數據量少的時候不會出錯,數據量大的時候一般都會有數據丟失,而在波特率較低的情況下,比如9600bps和2400bps,數據直接是接收一幀漏一幀,比如發送字符串abcdef,接收回來的是ace。我用ChipScope才抓出了這個原因。今后使用的時候要注意這個問題。
5.2、發送數據的狀態機和接收數據的狀態機可以用移位的方式來做
事實上那個狀態機的發送8-bit數據和接收8-bit數據的部分可以用移位的方法來做,這樣寫的代碼會更短更精煉。今后有空的時候自己在重新寫一次。
?
審核編輯:劉清
評論
查看更多