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