
兩個基礎(chǔ)的串口實(shí)驗(yàn)
兩個基礎(chǔ)的串口實(shí)驗(yàn)
一.說明
這兩個實(shí)驗(yàn)都是根據(jù)特權(quán)的深入淺出玩轉(zhuǎn)FPGA上的兩個實(shí)例做的,分別是筆記16中的串口通信實(shí)驗(yàn)和筆記17中的基于FIFO的串口發(fā)送機(jī)設(shè)計(jì)。
二.實(shí)驗(yàn)過程
2.1 實(shí)驗(yàn)1 串口通信實(shí)驗(yàn)
2.1.1基本過程
這個實(shí)驗(yàn)的基本過程是PC機(jī)首先通過串口向FPGA發(fā)送數(shù)據(jù),F(xiàn)PGA每接收到一個單位的數(shù)據(jù),就馬上再通過串口向PC機(jī)發(fā)回接收到的數(shù)據(jù),借助于串口調(diào)試助手,可以觀察發(fā)送的數(shù)據(jù)和接收的數(shù)據(jù)是否相同。
2.1.2整體和子模塊功能分析
這個串口發(fā)送接收系統(tǒng)可分為4個子模塊,分別是串口接收模塊、串口接收波特率控制模塊、串口發(fā)送模塊、串口發(fā)送波特率控制模塊。其中,串口接收模塊根據(jù)串口幀格式將PC機(jī)向FPGA發(fā)送的串口數(shù)據(jù)依次讀取下來,完成串轉(zhuǎn)并的操作,將串口接收線上的數(shù)據(jù)存入一個8位的寄存器中,并且,串口接收模塊會給串口接收波特率控制模塊提供相應(yīng)的使能信號,使得接收波特率控制模塊會給串口接收模塊反饋相應(yīng)的滿足一定時序要求的串口數(shù)據(jù)采樣信號,最后,串口接收模塊還會給串口發(fā)送模塊提供一個發(fā)送使能信號(實(shí)際上是表示接收完成的一個信號),使得在FPGA完整地接收到一個單位的數(shù)據(jù)后,串口發(fā)送模塊再將數(shù)據(jù)送出去,而在其他時間,發(fā)送使能信號無效時,串口接收模塊將持續(xù)發(fā)送高電平信號;串口接收波特率控制模塊根據(jù)串口接收模塊提供的使能信號,再根據(jù)指定的波特率,輸出滿足波特率要求的采樣信號,將這個采樣信號輸出給串口接收模塊,從而串口模塊能夠從串口接收數(shù)據(jù)線上取得正確的數(shù)據(jù)鎖存起來;串口發(fā)送模塊在FPGA接收到一個完整的單位數(shù)據(jù)時(串口發(fā)送模塊通過串口接收模塊發(fā)出的使能信號知道這一點(diǎn)),再按照串口數(shù)據(jù)幀格式將這個數(shù)據(jù)發(fā)送出去,并且,和接收模塊類似,要使發(fā)送模塊發(fā)送的數(shù)據(jù)滿足串口數(shù)據(jù)幀格式,必須需要一個控制信號,這個信號由串口發(fā)送波特率控制模塊提供,串口發(fā)送模塊也必須給這個發(fā)送波特率控制模塊提供相應(yīng)的使能信號,這個使能信號在串口發(fā)送時期使能,其余時間均無效。
需要注意的是,上面的串口發(fā)送波特率控制模塊和串口接收波特率控制模塊在具體實(shí)現(xiàn)的時候,都是用同一個Verilog模塊進(jìn)行例化的,但是,進(jìn)行例化時,前面提到的那個使能信號是不同的,并且它們輸出的數(shù)據(jù)的流向也是不同的,所以,實(shí)際上,這是兩個完全獨(dú)立的模塊,這種方法稱為邏輯復(fù)制。
2.1.3Verilog實(shí)現(xiàn)代碼
(1)串口接收模塊
uart_rx.v
`timescale 1ns / 1psmodule uart_rx( //串口接收模塊 clk,rst_n, rs232_rx,clk_bps, bps_start,rx_int,rx_data );input clk; //50MHz主時鐘input rst_n; //低電平復(fù)位信號input rs232_rx; //RS232接收數(shù)據(jù)信號input clk_bps; //此時clk_bps的高電平為接收數(shù)據(jù)的中間采樣點(diǎn)output bps_start; //接收到數(shù)據(jù)后,波特率時鐘啟動信號置位output[7:0] rx_data; //接收數(shù)據(jù)寄存器,保存直至下一個數(shù)據(jù)來到 output rx_int; //接收數(shù)據(jù)中斷信號,接收到數(shù)據(jù)期間始終為高電平,傳送給 //串口發(fā)送模塊,使得串口正在進(jìn)行接收數(shù)據(jù)的時候,發(fā)送模塊不工作, //避免了一個完整的數(shù)據(jù)(1位起始位、8位數(shù)據(jù)位、1位停止位)還沒有 //接收完全時,發(fā)送模塊就已經(jīng)將不正確的數(shù)據(jù)傳送出去//-----------------------------------------------------------------------------//邊沿檢測程序,檢測rs232_rx信號,即串口線上傳向FPGA的信號的下降沿//這個下降沿信號表示一個串口數(shù)據(jù)幀的開始reg rs232_rx0,rs232_rx1,rs232_rx2,rs232_rx3; //接收數(shù)據(jù)寄存器,濾波用wire neg_rs232_rx; //表示數(shù)據(jù)線接收到下降沿always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin rs232_rx0 <= 1'b0; rs232_rx1 <= 1'b0; rs232_rx2 <= 1'b0; rs232_rx3 <= 1'b0; end else begin rs232_rx0 <= rs232_rx; rs232_rx1 <= rs232_rx0; rs232_rx2 <= rs232_rx1; rs232_rx3 <= rs232_rx2; endend//下面的下降沿檢測可以濾掉<20ns-40ns的毛刺(包括高脈沖和低脈沖毛刺),//這里就是用資源換穩(wěn)定(前提是我們對時間要求不是那么苛刻,因?yàn)檩斎胄盘柎蛄撕脦着模?//(當(dāng)然我們的有效低脈沖信號肯定是遠(yuǎn)遠(yuǎn)大于40ns的)assign neg_rs232_rx = rs232_rx3 & rs232_rx2 & ~rs232_rx1 & ~rs232_rx0; //接收到下降沿后neg_rs232_rx置高一個時鐘周期//----------------------------------------------------------------------------reg bps_start_r;assign bps_start = bps_start_r;reg[3:0] num; //移位次數(shù)reg rx_int; //接收數(shù)據(jù)中斷信號,接收到數(shù)據(jù)期間始終為高電平always @ (posedge clk or negedge rst_n) if(!rst_n) begin bps_start_r <= 1'b0; rx_int <= 1'b0; end else if(neg_rs232_rx) begin //接收到串口接收線rs232_rx的下降沿標(biāo)志信號 bps_start_r <= 1'b1; //啟動串口準(zhǔn)備數(shù)據(jù)接收 rx_int <= 1'b1; //接收數(shù)據(jù)中斷信號使能 end else if(num==4'd12) begin //接收完有用數(shù)據(jù)信息 bps_start_r <= 1'b0; //數(shù)據(jù)接收完畢,釋放波特率啟動信號 rx_int <= 1'b0; //接收數(shù)據(jù)中斷信號關(guān)閉 endreg[7:0] rx_data_r; //串口接收數(shù)據(jù)寄存器,保存直至下一個數(shù)據(jù)來到//-----------------------------------------------------------------------------assign rx_data = rx_data_r; reg[7:0] rx_temp_data; //當(dāng)前接收數(shù)據(jù)寄存器always @ (posedge clk or negedge rst_n) if(!rst_n) begin rx_temp_data <= 8'd0; num <= 4'd0; rx_data_r <= 8'd0; end //else if(rx_int) //特權(quán)的代碼中有這一句話,意思是在處于接收狀態(tài)時 //(rx_int=1表示處于接收狀態(tài))才進(jìn)行下面的處理,實(shí)際上 //只有處于接收狀態(tài)時,才有相應(yīng)的clk_bps信號,所以 //實(shí)際上不需要對rx_int信號進(jìn)行判斷 else begin //接收數(shù)據(jù)處理 if(clk_bps) begin //讀取并保存數(shù)據(jù),接收數(shù)據(jù)為一個起始位,8bit數(shù)據(jù),1或2個結(jié)束位 num <= num+1'b1; case (num) 4'd1: rx_temp_data[0] <= rs232_rx; //鎖存第0bit 4'd2: rx_temp_data[1] <= rs232_rx; //鎖存第1bit 4'd3: rx_temp_data[2] <= rs232_rx; //鎖存第2bit 4'd4: rx_temp_data[3] <= rs232_rx; //鎖存第3bit 4'd5: rx_temp_data[4] <= rs232_rx; //鎖存第4bit 4'd6: rx_temp_data[5] <= rs232_rx; //鎖存第5bit 4'd7: rx_temp_data[6] <= rs232_rx; //鎖存第6bit 4'd8: rx_temp_data[7] <= rs232_rx; //鎖存第7bit default: ; endcase end else if(num == 4'd12) begin //我們的標(biāo)準(zhǔn)接收模式下只有1+8+1(2)=11bit的有效數(shù)據(jù) num <= 4'd0; //接收到STOP位后結(jié)束,num清零 rx_data_r <= rx_temp_data;//把數(shù)據(jù)鎖存到數(shù)據(jù)寄存器rx_data中 end endendmodule
(2)波特率控制模塊
speed_select.v
`timescale 1ns / 1psmodule speed_select( clk,rst_n, bps_start,clk_bps );input clk; // 50MHz主時鐘input rst_n; //低電平復(fù)位信號input bps_start; //接收到數(shù)據(jù)后,波特率時鐘啟動信號置位 //或者開始發(fā)送數(shù)據(jù)時,波特率時鐘啟動信號置位output clk_bps; // clk_bps的高電平為接收或者發(fā)送數(shù)據(jù)位的中間采樣點(diǎn) //-----------------------------------------------------------------------------//以下波特率分頻計(jì)數(shù)值可參照上面的參數(shù)進(jìn)行更改//計(jì)算方法://以9600bps為例,9600bps表示每秒9600bit,則傳輸1bit需要10^9/9600=104166ns,//所以再我們使用50MHz的時鐘頻率的前提下,需要104166/20=5208個時鐘周期//5208個時鐘周期內(nèi)傳送了1bit位,則在中間的時刻處,進(jìn)行取樣(接收模塊)或者//將中間時刻作為發(fā)送數(shù)據(jù)的數(shù)據(jù)改變點(diǎn)(發(fā)送模塊) `define BPS_PARA 5207//波特率為9600時的分頻計(jì)數(shù)值`define BPS_PARA_2 2603//波特率為9600時的分頻計(jì)數(shù)值的一半,用于數(shù)據(jù)采樣//-----------------------------------------------------------------------------reg[12:0] cnt; //分頻計(jì)數(shù)reg clk_bps_r; //波特率時鐘寄存器//-----------------------------------------------------------------------------//reg[2:0] uart_ctrl;//uart波特率選擇寄存器//特權(quán)的代碼中有這一句,但是一直沒有用到//這里將其去掉,對我們的程序沒有影響//-----------------------------------------------------------------------------always @ (posedge clk or negedge rst_n) if(!rst_n) cnt <= 13'd0; else if((cnt == `BPS_PARA) || !bps_start) cnt <= 13'd0; //波特率計(jì)數(shù)清零 else cnt <= cnt+1'b1; //波特率時鐘計(jì)數(shù)啟動always @ (posedge clk or negedge rst_n) if(!rst_n) clk_bps_r <= 1'b0; else if(cnt == `BPS_PARA_2) clk_bps_r <= 1'b1; // clk_bps_r高電平為接收數(shù)據(jù)位的中間采樣點(diǎn),同時也作為發(fā)送數(shù)據(jù)的數(shù)據(jù)改變點(diǎn) else clk_bps_r <= 1'b0;assign clk_bps = clk_bps_r;endmodule
(3)串口發(fā)送模塊
uart_tx.v
`timescale 1ns / 1psmodule uart_tx( clk,rst_n, rx_data,rx_int,rs232_tx, clk_bps,bps_start ); input clk; // 50MHz主時鐘input rst_n; //低電平復(fù)位信號input clk_bps; // clk_bps_r高電平作為發(fā)送數(shù)據(jù)的數(shù)據(jù)改變點(diǎn)input[7:0] rx_data; //接收數(shù)據(jù)寄存器input rx_int; output rs232_tx; // RS232發(fā)送數(shù)據(jù)信號output bps_start; //接收或者要發(fā)送數(shù)據(jù),波特率時鐘啟動信號置位//------------------------------------------------------------------------------//邊沿檢測,檢測rx_int信號的下降沿,rx_int信號的下降沿表示接收完全reg rx_int0,rx_int1,rx_int2; //rx_int信號寄存器,捕捉下降沿濾波用wire neg_rx_int; // rx_int下降沿標(biāo)志位always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin rx_int0 <= 1'b0; rx_int1 <= 1'b0; rx_int2 <= 1'b0; end else begin rx_int0 <= rx_int; rx_int1 <= rx_int0; rx_int2 <= rx_int1; endend//捕捉到下降沿后,neg_rx_int拉高保持一個主時鐘周期assign neg_rx_int = ~rx_int1 & rx_int2;//------------------------------------------------------------------------------reg[7:0] tx_data; //待發(fā)送數(shù)據(jù)的寄存器reg bps_start_r;assign bps_start = bps_start_r;//------------------------------------------------------------------------------//reg tx_en; //發(fā)送數(shù)據(jù)使能信號,高有效//接收數(shù)據(jù)中斷信號,接收到數(shù)據(jù)期間始終為高電平,在該模塊中利用它的下降沿來啟動串口//發(fā)送數(shù)據(jù)實(shí)際上這個信號是不需要的,因?yàn)樵诖诎l(fā)送數(shù)據(jù)模塊,clk_bps信號會給發(fā)送//數(shù)據(jù)的always模塊提供一個時鐘周期寬的高電平信號,在這個時鐘周期內(nèi),會發(fā)送出去1bit//的數(shù)據(jù)信息或者控制信息(起始位、停止位)//而在其余時間(不發(fā)送的時間),由于串口發(fā)送數(shù)據(jù)模塊給發(fā)送波特率控制信號提供//的計(jì)數(shù)使能信號bps_start一直是無效,所以clk_bps一直保持低電平//所以此時不會發(fā)送數(shù)據(jù),而不需要專門用一個tx_en信號進(jìn)行控制//------------------------------------------------------------------------------reg[3:0] num;always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin bps_start_r <= 1'b0; //tx_en <= 1'b0; tx_data <= 8'd0; end else if(neg_rx_int) begin //接收數(shù)據(jù)完畢,準(zhǔn)備把接收到的數(shù)據(jù)發(fā)回去 bps_start_r <= 1'b1; tx_data <= rx_data; //把接收到的數(shù)據(jù)存入發(fā)送數(shù)據(jù)寄存器 //tx_en <= 1'b1; //進(jìn)入發(fā)送數(shù)據(jù)狀態(tài)中 end else if(num==4'd11) begin //數(shù)據(jù)發(fā)送完成,復(fù)位 bps_start_r <= 1'b0; //tx_en <= 1'b0; endendreg rs232_tx_r;assign rs232_tx = rs232_tx_r;always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin num <= 4'd0; rs232_tx_r <= 1'b1; end //else if(tx_en) begin else begin if(clk_bps) begin num <= num+1'b1; case (num) 4'd0: rs232_tx_r <= 1'b0; //發(fā)送起始位 4'd1: rs232_tx_r <= tx_data[0]; //發(fā)送bit0 4'd2: rs232_tx_r <= tx_data[1]; //發(fā)送bit1 4'd3: rs232_tx_r <= tx_data[2]; //發(fā)送bit2 4'd4: rs232_tx_r <= tx_data[3]; //發(fā)送bit3 4'd5: rs232_tx_r <= tx_data[4]; //發(fā)送bit4 4'd6: rs232_tx_r <= tx_data[5]; //發(fā)送bit5 4'd7: rs232_tx_r <= tx_data[6]; //發(fā)送bit6 4'd8: rs232_tx_r <= tx_data[7]; //發(fā)送bit7 4'd9: rs232_tx_r <= 1'b1; //發(fā)送結(jié)束位 default: rs232_tx_r <= 1'b1; endcase end else if(num==4'd11) num <= 4'd0; //復(fù)位 endendendmodule
(4)頂層模塊
uart_top.v
`timescale 1ns / 1psmodule uart_top(clk,rst_n,rs232_rx,rs232_tx);input clk; // 50MHz主時鐘input rst_n; //低電平復(fù)位信號input rs232_rx; // RS232接收數(shù)據(jù)信號output rs232_tx; // RS232發(fā)送數(shù)據(jù)信號wire bps_start1,bps_start2; //接收到數(shù)據(jù)后,波特率時鐘啟動信號置位wire clk_bps1,clk_bps2; // clk_bps_r高電平為接收數(shù)據(jù)位的中間采樣點(diǎn),同時也作為發(fā)送數(shù)據(jù)的數(shù)據(jù)改變點(diǎn) wire[7:0] rx_data; //接收數(shù)據(jù)寄存器,保存直至下一個數(shù)據(jù)來到wire rx_int; //接收數(shù)據(jù)中斷信號,接收到數(shù)據(jù)期間始終為高電平//----------------------------------------------------//下面的四個模塊中,speed_rx和speed_tx是兩個完全獨(dú)立的硬件模塊,可稱之為邏輯復(fù)制//(不是資源共享,和軟件中的同一個子程序調(diào)用不能混為一談)////////////////////////////////////////////speed_select speed_rx( .clk(clk), //波特率選擇模塊 .rst_n(rst_n), .bps_start(bps_start1), .clk_bps(clk_bps1) );uart_rx uart_rx1( .clk(clk), //接收數(shù)據(jù)模塊 .rst_n(rst_n), .rs232_rx(rs232_rx), .rx_data(rx_data), .rx_int(rx_int), .clk_bps(clk_bps1), .bps_start(bps_start1) );/////////////////////////////////////////// speed_select speed_tx( .clk(clk), //波特率選擇模塊 .rst_n(rst_n), .bps_start(bps_start2), .clk_bps(clk_bps2) );uart_tx uart_tx2( .clk(clk), //發(fā)送數(shù)據(jù)模塊 .rst_n(rst_n), .rx_data(rx_data), .rx_int(rx_int), .rs232_tx(rs232_tx), .clk_bps(clk_bps2), .bps_start(bps_start2) );endmodule
2.1.4測試程序的設(shè)計(jì)
這里編寫相應(yīng)的testbench文件對這個串口通信功能進(jìn)行功能仿真,testbench的核心是,模擬PC機(jī)按照串口幀格式給FPGA提供輸入串口信號,這里就簡單地模擬PC機(jī)每當(dāng)上一次發(fā)送完成后隔1ms再向FPGA發(fā)送下一個數(shù)據(jù),并且這里發(fā)送的數(shù)據(jù)就簡單得用循環(huán)發(fā)送兩個數(shù)據(jù)0xA9和0xD4來模擬。
Testbench的Verilog實(shí)現(xiàn)代碼如下:
Test_uart_top.vt
`timescale 1 ns/ 1 psmodule test_uart_top();reg clk;reg rs232_rx;reg rst_n;// wires wire rs232_tx;// assign statements (if any) uart_top i1 (// port map - connection between master ports and signals/registers .clk(clk), .rs232_rx(rs232_rx), .rs232_tx(rs232_tx), .rst_n(rst_n));initial clk = 0;always #10 clk = ~clk;initialbegin rst_n = 0; #60 rst_n = 1;end//這里模擬PC機(jī)循環(huán)發(fā)出兩字節(jié)的數(shù)據(jù)前一個數(shù)據(jù)是0xA9(8'b1010_1001),//后一個數(shù)據(jù)是0xD4(8'b1101_0100)//數(shù)據(jù)的格式采用1字節(jié)起始位(0)、8字節(jié)數(shù)據(jù)、1字節(jié)停止位(1)initial begin #100 rs232_rx = 1; forever begin //1ms時開始發(fā)送第一個數(shù)據(jù) #1000000 rs232_rx = 0; //第一個數(shù)據(jù)起始位--->0 #104166 rs232_rx = 1; //第一個數(shù)據(jù)0bit--->1 #104166 rs232_rx = 0; //第一個數(shù)據(jù)1bit--->0 #104166 rs232_rx = 0; //第一個數(shù)據(jù)2bit--->0 #104166 rs232_rx = 1; //第一個數(shù)據(jù)3bit--->1 #104166 rs232_rx = 0; //第一個數(shù)據(jù)4bit--->0 #104166 rs232_rx = 1; //第一個數(shù)據(jù)5bit--->1 #104166 rs232_rx = 0; //第一個數(shù)據(jù)6bit--->0 #104166 rs232_rx = 1; //第一個數(shù)據(jù)7bit--->1 #104166 rs232_rx = 1; //第一個數(shù)據(jù)停止位--->1 #104166 ; //停止位的持續(xù)時間 //1ms后開始發(fā)送第二個數(shù)據(jù) #1000000 rs232_rx = 0; //第二個數(shù)據(jù)起始位--->0 #104166 rs232_rx = 0; //第二個數(shù)據(jù)0bit--->0 #104166 rs232_rx = 0; //第二個數(shù)據(jù)1bit--->0 #104166 rs232_rx = 1; //第二個數(shù)據(jù)2bit--->1 #104166 rs232_rx = 0; //第二個數(shù)據(jù)3bit--->0 #104166 rs232_rx = 1; //第二個數(shù)據(jù)4bit--->1 #104166 rs232_rx = 0; //第二個數(shù)據(jù)5bit--->0 #104166 rs232_rx = 1; //第二個數(shù)據(jù)6bit--->1 #104166 rs232_rx = 1; //第二個數(shù)據(jù)7bit--->1 #104166 rs232_rx = 1; //第二個數(shù)據(jù)停止位--->1 #104166 ; //停止位的持續(xù)時間 endendinitialbegin #100000000 $stop;endendmodule
功能仿真的波形如下:
從圖中大概能夠看出,串口發(fā)送線(這里發(fā)送和接收均是相對于FPGA而言)上的數(shù)據(jù)實(shí)際上就是串口接收線上數(shù)據(jù)的一個延時。
將其中前10ms的波形放大,如下所示:
2.1.5板級驗(yàn)證結(jié)果
2.1.6實(shí)驗(yàn)總結(jié)
(1)首先,開發(fā)板的原理圖上,串口芯片那一部分有問題,如下所示:
實(shí)際上,進(jìn)行測量后,I/O160接到的是MAX232的9腳,I/O163接到的是MAX232的10腳。
(2)和特權(quán)同學(xué)代碼的兩個不同之處
① 特權(quán)同學(xué)在波特率控制模塊中,有這么一句:
reg[2:0]uart_ctrl;
定義了uart_ctrl這么一個信號,按照特權(quán)同學(xué)的注釋,是uart波特率選擇寄存器,但是后面的程序中一直沒有用到,所以在程序中將其去掉。
②在串口接收和串口發(fā)送模塊中,特權(quán)同學(xué)均用了一個信號用來表示正在接收數(shù)據(jù)和正在發(fā)送數(shù)據(jù),這兩個信號分別是rx_int信號和tx_en信號,實(shí)際上,在接收和發(fā)送具體的每個bit位時,不需要用這樣的信號進(jìn)行控制,因?yàn)?,?dāng)系統(tǒng)正處在接收狀態(tài)時,clk_bps會在相應(yīng)的采樣點(diǎn)提供一個時鐘周期寬度的高電平,在這個點(diǎn)上,接收模塊會對發(fā)向FPGA的串口線上的數(shù)據(jù)進(jìn)行采樣,而在其它時間(不在接收時),clk_bps一直保持低電平狀態(tài),接收模塊不采樣,系統(tǒng)處在發(fā)送狀態(tài)時同理,clk_bps也有這個規(guī)律,進(jìn)一步,考慮到rx_int信號是串口接收模塊uart_rx的輸出信號,用來給串口發(fā)送模塊uart_tx提供接收完成的信息,所以是必須的,但是在uart_rx模塊中,不需要rx_int信號對接收過程進(jìn)行控制,而tx_en信號則完全可以刪去。
下面以仿真的結(jié)果進(jìn)行說明:
上圖是對特權(quán)同學(xué)的Verilog描敘代碼進(jìn)行功能仿真的結(jié)果,從中可以看出,在rx_int信號和tx_en信號有效的期間,相應(yīng)的clk_bps均有相應(yīng)的高電平采樣信號或高電平使能信號,而在rx_int信號和tx_en信號無效的期間,相應(yīng)的clk_bps均保持為0,所以在相應(yīng)的代碼中,去掉了這兩個信號對接收和發(fā)送過程的控制語句(從前面的代碼注釋中可以看出去掉了哪些語句,實(shí)際上主要是去掉了兩個elseif語句)。
而且從上面的圖中,可以看出,改變前后其它信號的仿真結(jié)果完全相同。
(3)有關(guān)特權(quán)同學(xué)編寫的邊沿檢測程序的說明
最早看到這種邊沿檢測程序是在特權(quán)同學(xué)的深入淺出FPGA書上的鍵盤消抖實(shí)驗(yàn)中,當(dāng)時我是剛剛從同學(xué)那邊借來了一塊FPGA開發(fā)板,開始動手做FPGA方面的實(shí)驗(yàn),這個程序當(dāng)時就看了好久才懂,后來在用FPGA做其它的一些實(shí)驗(yàn)時,需要用到邊沿檢測時,都是套用那個鍵盤程序。。。。下面對這個串口通信實(shí)驗(yàn)中的邊沿檢測部分進(jìn)行說明。這一部分仿真結(jié)果:
從圖中可以看出,在100ns處,串口接收到的信號rs232_rx出現(xiàn)了第一個下降沿,結(jié)果,在130ns處,neg_rs232_rx信號出現(xiàn)了寬度是1個時鐘周期的高有效電平,這個信號用來作為串口開始接收數(shù)據(jù)的標(biāo)志,這段代碼實(shí)現(xiàn)下降沿檢測的原理是,rs232_rx、rs232_rx0、rs232_rx1、rs232_rx2、rs232_rx3這四個信號,每一個信號分別是前一個信號的一個時鐘周期的延時,所以rs232_rx0表示的是后面的數(shù)據(jù),rs232_rx3表示的是最前面的數(shù)據(jù),所以用這樣一個語句:
assignneg_rs232_rx=rs232_rx3&rs232_rx2&~rs232_rx1&~rs232_rx0;
表示當(dāng)rs232_rx上的數(shù)據(jù)是從1變到0時,neg_rs232_rx信號會出現(xiàn)一個時鐘周期長度的高電平。
2.2 實(shí)驗(yàn)2 基于FIFO的串口發(fā)送機(jī)設(shè)計(jì)
2.2.1說明
這個實(shí)驗(yàn)和特權(quán)同學(xué)的實(shí)驗(yàn)的大概思路是相同的,但是,具體的是看了特權(quán)同學(xué)的視頻以及書上的相關(guān)內(nèi)容后自己編寫相應(yīng)的Verilog代碼實(shí)現(xiàn)的。
2.2.2整體和子模塊功能分析
實(shí)現(xiàn)串口發(fā)送機(jī)的功能,分為3個子模塊,分別為發(fā)送數(shù)據(jù)產(chǎn)生模塊datagene、fifo_u模塊以及串口發(fā)送模塊uart_tx_top,其中,串口發(fā)送模塊和實(shí)驗(yàn)1類似,分為發(fā)送模塊和波特率控制模塊,但是加上了對FIFO進(jìn)行控制的部分。
這幾個模塊中,datagene模塊產(chǎn)生每隔1s遞增1的數(shù)據(jù)送到FIFO模塊的輸入端,并且產(chǎn)生FIFO的寫使能信號,使得FIFO在輸入端的數(shù)據(jù)穩(wěn)定時將輸入端的數(shù)據(jù)鎖存;FIFO模塊就是用ALTERA的FIFOIP核實(shí)現(xiàn)的,是一個數(shù)據(jù)寬度為8位的FIFO,uart_tx模塊一方面要產(chǎn)生FIFO的讀使能信號,這里我們?yōu)榱耸沟肍IFO每寫入一個數(shù)據(jù)就將這個數(shù)據(jù)輸出,所以讀使能信號在相應(yīng)的寫使能信號之后并在寫入下一個數(shù)據(jù)之前有效,另一方面,uart_tx模塊將FIFO輸出端的數(shù)據(jù)通過串口線輸出到PC機(jī)上。
2.2.3Verilog實(shí)現(xiàn)代碼
(1)數(shù)據(jù)產(chǎn)生模塊
datagene.v
module datagene( input clk, input rst_n, output reg fifo_wrreq, output reg [7:0] fifo_data );reg [25:0] cnt_1s; //定時1s的計(jì)數(shù)器always @(posedge clk or negedge rst_n)if(!rst_n) cnt_1s <= 0;else if(cnt_1s == 26'd50000000) cnt_1s <= 0;else cnt_1s <= cnt_1s + 1; always @(posedge clk or negedge rst_n) //輸出寫使能信號if(!rst_n) fifo_wrreq <= 0;else if(cnt_1s == 26'd25000000) //在兩個不同的數(shù)的中間FIFO讀取送到其上的數(shù) fifo_wrreq <= 1;else fifo_wrreq <= 0;always @(posedge clk or negedge rst_n) //每隔1s使送給FIFO的數(shù)遞增1if(!rst_n) fifo_data = 8'h0;else if(cnt_1s == 26'd50000000) fifo_data = fifo_data + 1;endmodule
(2)FIFO模塊
用QUARTUS中的MegaWizzard實(shí)現(xiàn)即可
如上圖所示,wrreq和rdreq分別是寫使能信號和讀使能信號,empty是表示FIFO是否是空的信號,當(dāng)FIFO空時,empty是1,否則,empty是0,empty信號將作為串口發(fā)送模塊的輸入信號,當(dāng)empty由低變到高的時候,表示FIFO的數(shù)據(jù)已讀出,串口發(fā)送模塊此時會將這個數(shù)據(jù)發(fā)到PC機(jī)上。
(3)串口發(fā)送模塊
和實(shí)驗(yàn)1相同,分成兩個部分,發(fā)送模塊和波特率選擇模塊(去掉了接收模塊),Verilog實(shí)現(xiàn)的代碼分別為:
① uart_tx.v
`timescale 1ns / 1psmodule uart_tx( input clk, input rst_n, input clk_bps, input [7:0] tx_data, input tx_start, output bps_start, output reg fifo_rdreq, output rs232_tx );reg tx_start0,tx_start1,tx_start2; //rx_int信號寄存器,捕捉下降沿濾波用wire neg_tx_start; // rx_int下降沿標(biāo)志位always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin tx_start0 <= 1'b0; tx_start1 <= 1'b0; tx_start2 <= 1'b0; end else begin tx_start0 <= tx_start; tx_start1 <= tx_start0; tx_start2 <= tx_start1; endendassign neg_tx_start = tx_start1 & ~tx_start2; //捕捉到上升沿后,neg_tx_start拉高保持一個主時鐘周期//這一句話表示前一時刻是1,后一時刻是0,所以是下降沿,//這里,tx_start2保存的是前一時刻的值,tx_start1保存的是后一時刻的值reg bps_start_r;reg[3:0] num;always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin bps_start_r <= 1'b0; end else if(neg_tx_start) begin //接收數(shù)據(jù)完畢,準(zhǔn)備把接收到的數(shù)據(jù)發(fā)回去 bps_start_r <= 1'b1; end else if(num==4'd11) begin //數(shù)據(jù)發(fā)送完成,復(fù)位 bps_start_r <= 1'b0; endendassign bps_start = bps_start_r;reg rs232_tx_r;assign rs232_tx = rs232_tx_r;always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin num <= 4'd0; rs232_tx_r <= 1'b1; end else begin if(clk_bps) begin num <= num+1'b1; case (num) 4'd0: rs232_tx_r <= 1'b0; //發(fā)送起始位 4'd1: rs232_tx_r <= tx_data[0]; //發(fā)送bit0 4'd2: rs232_tx_r <= tx_data[1]; //發(fā)送bit1 4'd3: rs232_tx_r <= tx_data[2]; //發(fā)送bit2 4'd4: rs232_tx_r <= tx_data[3]; //發(fā)送bit3 4'd5: rs232_tx_r <= tx_data[4]; //發(fā)送bit4 4'd6: rs232_tx_r <= tx_data[5]; //發(fā)送bit5 4'd7: rs232_tx_r <= tx_data[6]; //發(fā)送bit6 4'd8: rs232_tx_r <= tx_data[7]; //發(fā)送bit7 4'd9: rs232_tx_r <= 1'b1; //發(fā)送結(jié)束位 default: rs232_tx_r <= 1'b1; endcase end else if(num==4'd11) num <= 4'd0; //復(fù)位 end end //----------------------------------------------------------------//下面的這一部分代碼是在原來的串口發(fā)送模塊中添加的。用來產(chǎn)生FIFO讀使能信號//仿照data_gene模塊產(chǎn)生FIFO寫使能信號的過程,產(chǎn)生一個FIFO讀使能信號reg [25:0] cnt_1s; //定時1s的計(jì)數(shù)器always @(posedge clk or negedge rst_n)if(!rst_n) cnt_1s <= 0;else if(cnt_1s == 26'd50000000) cnt_1s <= 0;else cnt_1s <= cnt_1s + 1; always @(posedge clk or negedge rst_n) //輸出讀使能信號if(!rst_n) fifo_rdreq <= 0;else if(cnt_1s == 26'd37500000) //在兩個不同的數(shù)的3/4的時刻FIFO輸出送到其上的數(shù) fifo_rdreq <= 1; //uart串口模塊讀取這個數(shù)值并送往PC機(jī)else //注意這個讀使能信號和寫使能信號之間的時序關(guān)系 fifo_rdreq <= 0;//-------------------------------------------------------------------endmodule
② speed_select.v
`timescale 1ns / 1psmodule speed_select( clk,rst_n, bps_start,clk_bps );input clk; // 50MHz主時鐘input rst_n; //低電平復(fù)位信號input bps_start; //接收到數(shù)據(jù)后,波特率時鐘啟動信號置位output clk_bps; // clk_bps的高電平為接收或者發(fā)送數(shù)據(jù)位的中間采樣點(diǎn) //以下波特率分頻計(jì)數(shù)值可參照上面的參數(shù)進(jìn)行更改`define BPS_PARA 5207 //波特率為9600時的分頻計(jì)數(shù)值`define BPS_PARA_2 2603 //波特率為9600時的分頻計(jì)數(shù)值的一半,用于數(shù)據(jù)采樣reg[12:0] cnt; //分頻計(jì)數(shù)reg clk_bps_r; //波特率時鐘寄存器//----------------------------------------------------------reg[2:0] uart_ctrl; // uart波特率選擇寄存器//----------------------------------------------------------always @ (posedge clk or negedge rst_n) if(!rst_n) cnt <= 13'd0; else if((cnt == `BPS_PARA) || !bps_start) cnt <= 13'd0; //波特率計(jì)數(shù)清零 else cnt <= cnt+1'b1; //波特率時鐘計(jì)數(shù)啟動always @ (posedge clk or negedge rst_n) if(!rst_n) clk_bps_r <= 1'b0; else if(cnt == `BPS_PARA_2) clk_bps_r <= 1'b1; // clk_bps_r高電平為接收數(shù)據(jù)位的中間采樣點(diǎn),同時也作為發(fā)送數(shù)據(jù)的數(shù)據(jù)改變點(diǎn) else clk_bps_r <= 1'b0;assign clk_bps = clk_bps_r;endmodule
③串口發(fā)送頂層模塊
uart_tx_top.v
module uart_tx_top( input clk, input rst_n, input tx_start, input [7:0] tx_data, output fifo_rdreq, output rs232_tx );wire bps_start;wire clk_bps;speed_select speed_select_tx ( clk,rst_n, bps_start,clk_bps );uart_tx uart_tx( clk,rst_n, clk_bps, tx_data, tx_start, bps_start, fifo_rdreq, rs232_tx ); endmodule
2.2.4測試程序的設(shè)計(jì)
這個測試文件中只需給定時鐘信號和復(fù)位信號即可。
test_fifouart.vt
// Generated on "07/14/2007 17:44:19" // Verilog Test Bench template for design : fifouart// // Simulation tool : ModelSim-Altera (Verilog)// `timescale 1 ns/ 1 psmodule test_fifouart();reg clk;reg rst_n;// wires wire rs232_tx;// assign statements (if any) fifouart i1 (// port map - connection between master ports and signals/registers .clk(clk), .rs232_tx(rs232_tx), .rst_n(rst_n));initial clk = 0;always #10 clk = ~clk;initial begin rst_n = 0; #100 rst_n = 1;end initial #3000000000 $stop; endmodule
功能仿真的波形如下所示:
觀察其中輸出0x03部分的波形,如下所示:
從圖中可以看出,依次輸出的是0x03的低位到高位數(shù)據(jù)。
2.2.5板級驗(yàn)證結(jié)果
從圖中可以看出,F(xiàn)PGA將存入FIFO中的數(shù)據(jù)依次向PC機(jī)發(fā)送。
2.2.6實(shí)驗(yàn)總結(jié)
(1)在進(jìn)行板級驗(yàn)證的時候,遇到了一個問題,當(dāng)我們將復(fù)位信號分配到開發(fā)板上的一個按鍵的時候,出現(xiàn)了如下的結(jié)果:
從圖中可以看出,時不時地會自動復(fù)位,但是當(dāng)我們將復(fù)位信號分配到這塊FPGA的全局復(fù)位端時,結(jié)果就是正確的,這是不是按鍵抖動的原因????
(2)從前面仿真的波形結(jié)果中可以看出,pos_tx_start信號和fifo_rdreq信號非常相近,只不過放大后能夠看出,pos_tx_start信號實(shí)際上比fifo_rdreq信號延時了3個時鐘周期,再重新審視pos_tx_start信號的產(chǎn)生過程,首先,串口發(fā)送模塊uart_tx在特定的時刻發(fā)出一個時鐘周期寬的fifo_rdreq信號,將FIFO中的數(shù)據(jù)從FIFO中讀出,然后FIFO的empty輸出端將由低變高,串口發(fā)送模塊uart_tx中通過邊沿檢測發(fā)現(xiàn)了這么一個上升沿,然后在這個上升沿之后使pos_tx_start信號在一個時鐘周期內(nèi)有效。再來看pos_tx_start信號的作用,其作用就是在它有效的那個時鐘周期內(nèi),使能bps_start信號,使得bps_start信號從這個時刻開始一直到一個單位的數(shù)據(jù)幀發(fā)送完成時都是有效的,從而波特率的計(jì)數(shù)器能夠在這一段時間進(jìn)行技術(shù)并輸出控制串口發(fā)送模塊輸出數(shù)據(jù)的控制信號,所以,完全可以去掉這么一個邊沿檢測的程序,直接用fifo_rdreq信號代替pos_tx_start信號,這樣做的話,僅僅是bps_start信號提前有效了3個時鐘周期,而3時鐘周期和在發(fā)送一個單位的數(shù)據(jù)幀時,bps_start有效的總時間相比,是微乎其微的,所以對結(jié)果幾乎沒有影響。
愛華網(wǎng)


