由于可編程邏輯器件技術(shù)的快速發(fā)展,F(xiàn)PGA的功能日益強(qiáng)大,其開發(fā)周期短、可重復(fù)編程的優(yōu)點也越來越明顯,在FPGA芯片上集成UART串口功能模塊并和其他模塊組合可以很方便地實現(xiàn)一個能與其他設(shè)備進(jìn)行串行通信的片上系統(tǒng)。本文采用STEP-MAX10M08核心板和STEP Base Board V3.0底板來完成串口監(jiān)視系統(tǒng)設(shè)計,可以拆分成三個功能模塊實現(xiàn):
Uart_Bus: UART串口通信設(shè)計,實現(xiàn)串口通信數(shù)據(jù)傳輸。
Decoder:將UART模塊接收到的數(shù)據(jù)轉(zhuǎn)換成用于數(shù)碼管顯示的BCD碼。
Segment_scan:通過驅(qū)動底板掃描式數(shù)碼管將串口接收的數(shù)據(jù)顯示出來。
頂層模塊Display_Ctl通過實例化兩個子模塊并將對應(yīng)的信號連接,最終實現(xiàn)串口監(jiān)視系統(tǒng)的總體設(shè)計。UART通信是全雙工的,接收和發(fā)送是兩個獨立的設(shè)計,本實驗只需要接收數(shù)據(jù),串口通信有兩個關(guān)鍵因素:傳輸格式和傳輸速率,我們可以用兩個模塊分別實現(xiàn):
Baud:控制UART通信數(shù)據(jù)傳輸速率。
Uart_Rx:根據(jù)數(shù)據(jù)傳輸速率節(jié)拍控制UART通信數(shù)據(jù)格式。
Top-Down層次設(shè)計
模塊結(jié)構(gòu)設(shè)計
1. UART接口介紹
通用異步收發(fā)傳輸器(Universal AsynchronousReceiver/Transmitter),通常稱作UART,是一種通用串行數(shù)據(jù)總線,用于異步通信。該總線雙向通信,可以實現(xiàn)全雙工傳輸和接收。
UART通信接口
RS232串行通信接口
在系統(tǒng)或計算機(jī)中說的串口一般就是說的RS232接口,接口有9個引腳,最重要的三個引腳:TXD、RXD、GND,基本通信邏輯與UART完全一致,為了增加串口通信的抗干擾能力,RS232串行通信接口定義了自己的電平標(biāo)準(zhǔn),采用負(fù)邏輯電平,它定義+5~+12V為低電平,而-12~-5V為高電平,相當(dāng)于在UART的基礎(chǔ)上增加驅(qū)動器,將原來UART通信電平標(biāo)準(zhǔn)調(diào)整為RS232的電平標(biāo)準(zhǔn),通信原理如下:
RS232串口通信
隨著技術(shù)的發(fā)展,RS232串口通信接口方案逐漸被拋棄,為了實現(xiàn)UART通信,一種USB轉(zhuǎn)UART的方案被廣泛應(yīng)用,常用的USB轉(zhuǎn)UART方案有CP2102、FT232、CH340等等
USB轉(zhuǎn)UART通信
本文所用的STEP Base Board V3.0底板集成的UART通信模塊就是采用CP2102方案,F(xiàn)PGA通過UART總線驅(qū)動CP2102實現(xiàn)USB和UART之間的數(shù)據(jù)通信,最終實現(xiàn)FPGA與電腦之間的數(shù)據(jù)傳輸,UART通信的時序如下。
UART通信時序
起始位:先發(fā)出一個邏輯 0 信號,表示傳輸字符的開始。
數(shù)據(jù)位:可以是5~8位邏輯 0 或 1 。如ASCII碼( 7位) ,擴(kuò)展BCD碼( 8位) 。
校驗位:數(shù)據(jù)位加上這一位后,使得 1 的位數(shù)應(yīng)為偶數(shù)(偶校驗)或奇數(shù)(奇校驗)。
停止位:它是一個字符數(shù)據(jù)的結(jié)束標(biāo)志。可以是1位、1.5位、2位的高電平。
空閑位:處于邏輯 1 狀態(tài),表示當(dāng)前線路上沒有資料傳送。
2. UART模塊連接
STEP Base Board V3.0底板上的基于CP2102方案的UART通信模塊電路圖如下:
UART通信模塊電路
上圖為基于CP2102方案的UART通信模塊電路圖,可以看到CP2102方案非常簡潔,無需外置USB通信時鐘晶體(內(nèi)部集成),CP2102芯片TXD和RXD分別與FPGA芯片RXD和TXD連接,同時兩個信號都連接了LED燈,這樣當(dāng)UART通信時,隨著數(shù)據(jù)傳輸對應(yīng)LED燈也會快速閃爍,起到UART通信指示燈的作用。CP2102芯片DTR和RTS通過兩個三極管搭建流控電路,連接WIFI模塊ESP8266-12F,使用UART模塊燒寫ESP8266模塊的固件時就無需手動進(jìn)入固件燒寫模式了,這個會在后續(xù)涉及WIFI通信的實驗中詳細(xì)介紹,這里可以不用理會。
3. UART驅(qū)動實現(xiàn)
SPI、I2C、UART總線對比表:
SPI總線
I2C總線
UART總線
SS
SCL
TXD
SCK
SDA
RXD
MOSI/MISO
對于UART總線,TXD和RXD分別用于發(fā)送和接收數(shù)據(jù),相當(dāng)于兩根獨立工作的單線總線,沒有了時鐘線的配合,那么接收端應(yīng)該怎樣獲取發(fā)送端傳輸?shù)臄?shù)據(jù)呢?其實也是有方法的,那就是通信雙方需要約定好UART總線數(shù)據(jù)傳輸?shù)耐ㄐ潘俾屎蜁r序格式。
通信速率:
UART的數(shù)據(jù)傳輸速度用波特率來描述,也就是UART每秒接收或發(fā)送的數(shù)據(jù)位。例如9600波特率表示每秒鐘發(fā)送或接收9600比特的數(shù)據(jù),即發(fā)送端需要將發(fā)送的每個數(shù)據(jù)位保持對應(yīng)的時間,計算如下:
1s / 9600 =1000000us / 9600 = 104.17us
小腳丫硬件上使用12MHz的時鐘晶振,如果以12MHz時鐘信號作為系統(tǒng)時鐘,使用計數(shù)器延時完成UART通信數(shù)據(jù)采樣,那么計數(shù)器延時計數(shù)終值計算如下:
12M / 9600 = 1250
因為波特率是協(xié)議里約定的,為保證協(xié)議的通用性和靈活性,波特率參數(shù)有固定的選項,不可以隨意設(shè)置(如果UART通信雙方都是自己編程的,可以根據(jù)自己的要求定義自己需要的波特率,這種情況除外),波特率參數(shù)選項很多,大家可以打開串口調(diào)試助手工具找到波特率配置列表查看,我們比較常用的波特率值有以下幾種:
UART常用波特率:
1200
4800
9600
38400
115200
時序格式:
關(guān)于時序格式在前面UART接口介紹部分也簡單說了一下,通信過程中時序依次為:起始位、數(shù)據(jù)位、校驗位、停止位、空閑位,其中數(shù)據(jù)位可以是5~8位,本設(shè)計我們使用8位數(shù)據(jù),校驗位可以省略,最后確定的時序格式如下:
本實驗UART通信時序
前面所說的通信速率和時序格式其實就是UART通信中的兩個重要的參數(shù),需要傳輸?shù)臄?shù)據(jù)根據(jù)通信速率的節(jié)拍按照UART的時序格式輸出,就可以實現(xiàn)UART通信了,可以按照下面三個步驟實現(xiàn)。
將需要發(fā)送的數(shù)據(jù)與起始位和停止位組成10bit位寬的數(shù)據(jù)
計數(shù)器計數(shù)延遲產(chǎn)生相應(yīng)波特率需要的時序節(jié)拍
數(shù)據(jù)按照(起始位—bit0~bit7—停止位)的時序串行輸出
例如,將8‘h73和8’h5a通過UART發(fā)送的時序,紅色箭頭為波特率對應(yīng)的節(jié)拍點
UART發(fā)送數(shù)據(jù)實例
對于UART發(fā)送數(shù)據(jù)來說,波特率節(jié)拍是自己產(chǎn)生的,數(shù)據(jù)是自己主動發(fā)出的,邏輯相對簡單,而當(dāng)UART接收數(shù)據(jù)的時候,因為不確定對方什么時候發(fā)送數(shù)據(jù),所以需要對RX信號持續(xù)檢測,當(dāng)檢測到有數(shù)據(jù)傳送時,根據(jù)約定的波特率節(jié)拍采樣,可以按照下面三個步驟實現(xiàn)。
檢測UART的RXD信號的下降沿(自鎖,完成接收后再解鎖繼續(xù)檢測)
接收采樣時,采樣點應(yīng)該在計數(shù)器的中值點進(jìn)行
將采樣后的數(shù)據(jù)按照UART時序的要求重新組成8bit的數(shù)據(jù)
例如,當(dāng)UART的RX端接收到數(shù)據(jù)8‘h73和8’h5a的時候,紅色箭頭為檢測到數(shù)據(jù)傳輸?shù)狞c,綠色箭頭為對應(yīng)的采樣節(jié)拍點(采樣點在數(shù)據(jù)中間最是穩(wěn)定)。
UART接收數(shù)據(jù)實例
通過以上理論,我們了解了UART發(fā)送和接收數(shù)據(jù)的整個流程,兩個過程中我們都需要波特率節(jié)拍,那么我們就可以設(shè)計一個節(jié)拍模塊Baud,這樣我們的發(fā)送和接收都可以實例化節(jié)拍模塊用于產(chǎn)生對應(yīng)波特率的節(jié)拍信號。
節(jié)拍模塊Baud設(shè)計實現(xiàn):
節(jié)拍模塊Baud的端口程序?qū)崿F(xiàn)如下:
module Baud #
(
parameter BPS_PARA =1250//12MHz時鐘1250對應(yīng)9600波特率
)
(
input clk, //系統(tǒng)時鐘
input rst_n, //系統(tǒng)復(fù)位,低有效
input bps_en, //接收或發(fā)送時鐘使能
output reg bps_clk //接收或發(fā)送時鐘輸出
);
設(shè)計一個計數(shù)器用于分頻產(chǎn)生對應(yīng)波特率節(jié)拍信號,因為UART隨時可能接收數(shù)據(jù),所以節(jié)拍模塊必須隨時待命,保持計數(shù)器清零,當(dāng)需要節(jié)拍信號時精準(zhǔn)地輸出。
計數(shù)器設(shè)計程序?qū)崿F(xiàn)如下:
//計數(shù)器計數(shù)滿足波特率時鐘要求
always@(posedge clk ornegedge rst_n)begin
if(!rst_n)
cnt <=1'b0;
elseif((cnt >= BPS_PARA-1)||(!bps_en))// bps_en=0時,計數(shù)器復(fù)位
cnt <=1'b0;
else// bps_en=1時,計數(shù)器工作,周期為BPS_PARA個系統(tǒng)時鐘周期
cnt <= cnt +1'b1;
end
當(dāng)bps_en(高有效)使能,計數(shù)器計數(shù)周期由參數(shù)BPS_PARA來決定,前面數(shù)據(jù)接收時序部分了解到,從RX檢測到下降沿開始計數(shù)器工作,到數(shù)據(jù)采樣點需要半個節(jié)拍的時間,而數(shù)據(jù)發(fā)送時只要保證相鄰兩個節(jié)拍點之間的時間為一個計數(shù)器周期即可,所以我們可以在計數(shù)器計數(shù)到中值時產(chǎn)生一個脈沖信號充當(dāng)節(jié)拍信號。
節(jié)拍信號產(chǎn)生程序?qū)崿F(xiàn)如下:
//產(chǎn)生相應(yīng)波特率的時鐘節(jié)拍,接收模塊將以此節(jié)拍進(jìn)行UART數(shù)據(jù)接收
always@(posedge clk ornegedge rst_n)begin
if(!rst_n)
bps_clk <=1'b0;
elseif(cnt ==(BPS_PARA>>1))//中值數(shù)據(jù)穩(wěn)定,做采樣點
bps_clk <=1'b1;
else
bps_clk <=1'b0;
end
發(fā)送模塊Uart_Tx設(shè)計實現(xiàn):
前級電路通過tx_data_valid和tx_data_in將需要發(fā)送的數(shù)據(jù)傳輸進(jìn)來,當(dāng)tx_data_valid有脈沖信號時,tx_data_in信號為有效數(shù)據(jù),拼接起始位和停止位后賦值給tx_data_r,同時控制節(jié)拍使能信號使能并自鎖,然后等發(fā)送完10bit數(shù)據(jù)后解除使能。
數(shù)據(jù)發(fā)送控制程序?qū)崿F(xiàn)如下:
output reg bps_en, //發(fā)送時鐘使能
input bps_clk, //發(fā)送時鐘輸入
input tx_data_valid, //發(fā)送數(shù)據(jù)有效脈沖
input [7:0] tx_data_in, //要發(fā)送的數(shù)據(jù)
output reg uart_tx //UART發(fā)送輸出
reg [9:0] tx_data_r; //融合了起始位和停止位的數(shù)據(jù)
//根據(jù)接收數(shù)據(jù)的完成,驅(qū)動發(fā)送數(shù)據(jù)操作
always@(posedge clk ornegedge rst_n)begin
if(!rst_n)begin
bps_en <=1'b0;
tx_data_r <=10'd0;
endelseif(tx_data_valid &&(!bps_en))begin
bps_en <=1'b1; //當(dāng)需要發(fā)送數(shù)據(jù)時,使能節(jié)拍使能信號
tx_data_r <={1'b1,tx_data_in,1'b0};
endelseif(num==4'd10)begin
bps_en <=1'b0; //一次UART發(fā)送需要10個時鐘信號,然后結(jié)束
end
end
UART數(shù)據(jù)發(fā)送時序程序?qū)崿F(xiàn)如下:
//當(dāng)處于工作狀態(tài)中時,按照發(fā)送時鐘的節(jié)拍發(fā)送數(shù)據(jù)
always@(posedge clk ornegedge rst_n)begin
if(!rst_n)begin
num <=1'b0;
uart_tx <=1'b1;
endelseif(bps_en)begin
if(bps_clk)begin //根據(jù)節(jié)拍發(fā)送數(shù)據(jù)
num <= num +1'b1;
uart_tx <= tx_data_r[num]; //先發(fā)低位后發(fā)高位
endelseif(num>=4'd10)
num <=4'd0;
end
end
將節(jié)拍模塊Baud和發(fā)送模塊Uart_tx實例化并連接,完成發(fā)送功能的設(shè)計,如下
UART發(fā)送功能設(shè)計實現(xiàn)
接收模塊Uart_Rx設(shè)計實現(xiàn):
首先對RX信號多級緩存消除亞穩(wěn)態(tài),同時檢測下降沿,程序?qū)崿F(xiàn)如下:
當(dāng)檢測RX有下降沿后,使能節(jié)拍使能信號,同時自鎖直到完成接收操作后再復(fù)位節(jié)拍使能信號。程序?qū)崿F(xiàn)如下:
//接收時鐘使能信號的控制
always@(posedge clk ornegedge rst_n)begin
if(!rst_n)
bps_en <=1'b0;
elseif(neg_uart_rx &&(!bps_en))//當(dāng)檢測到傳輸,使能節(jié)拍使能信號
bps_en <=1'b1;
elseif(num==4'd9) //完成UART接收操作,復(fù)位節(jié)拍使能信號
bps_en <=1'b0;
end
根據(jù)節(jié)拍信號完成UART總線的數(shù)據(jù)采樣,得到8位有效數(shù)據(jù),程序?qū)崿F(xiàn)如下:
reg [7:0] rx_data;
//當(dāng)處于工作狀態(tài)中時,按照接收時鐘的節(jié)拍獲取數(shù)據(jù)
always@(posedge clk ornegedge rst_n)begin
if(!rst_n)begin
num <=4'd0;
rx_data <=8'd0;
endelseif(bps_en)begin
if(bps_clk)begin
num <= num +1'b1;
if(num<=4'd8) rx_data[num-1]<= uart_rx1;//先低位后高位
endelseif(num ==4'd9)begin
num <=4'd0;
end
endelsebegin
num <=4'd0;
end
end
當(dāng)UART接收操作完成后,將得到的8位有效數(shù)據(jù)輸出給后級電路,程序?qū)崿F(xiàn)如下:
//將接收的數(shù)據(jù)輸出,同時控制輸出有效信號產(chǎn)生脈沖
always@(posedge clk ornegedge rst_n)begin
if(!rst_n)begin
rx_data_out <=8'd0;
rx_data_valid <=1'b0;
endelseif(num ==4'd9)begin
rx_data_out <= rx_data;
rx_data_valid <=1'b1;
endelsebegin
rx_data_out <= rx_data_out;
rx_data_valid <=1'b0;
end
end
最后將節(jié)拍模塊Baud和接收模塊Uart_rx實例化并連接,完成發(fā)送功能的設(shè)計,如下
UART接收功能設(shè)計實現(xiàn)
整個UART驅(qū)動設(shè)計是由兩個獨立的功能組合而成:發(fā)送功能部分和接收功能部分。UART功能總體設(shè)計框圖如下:
當(dāng)我們需要UART發(fā)送數(shù)據(jù)的時候只需要實例化發(fā)送功能部分設(shè)計,需要UART接收數(shù)據(jù)的時候只需要實例化接收功能部分設(shè)計,例如本設(shè)計中FPGA驅(qū)動UART模塊接收電腦串口調(diào)試助手發(fā)出的數(shù)據(jù),所以我們就只需要實例化接收功能部分設(shè)計即可。
4. 系統(tǒng)總體實現(xiàn)
剛剛學(xué)習(xí)了UART通信模塊,本設(shè)計只需要使用接收功能部分設(shè)計,每一次通信都會得到一個8位數(shù)據(jù),怎樣將8位數(shù)據(jù)對應(yīng)得數(shù)據(jù)顯示在數(shù)碼管上呢?我來先來了解一下UART接受到的8位數(shù)據(jù)與要顯示數(shù)字的關(guān)系
串口調(diào)試助手界面
上圖為電腦端友善串口調(diào)試助手的界面,當(dāng)我們將硬件連接,在串口設(shè)置串口選定串口對應(yīng)的端口,并按上圖配置波特率、數(shù)據(jù)位、校驗位、停止位、流控等,點擊開始建立連接,接下來我們就可以在串口發(fā)送窗口輸入要發(fā)送的數(shù)據(jù),點擊發(fā)送后數(shù)據(jù)傳輸出去。在發(fā)送設(shè)置有兩個選項:ASCII和Hex ,
當(dāng)選擇ASCII的時候,通過UART發(fā)出的數(shù)據(jù)是數(shù)據(jù)窗口中字符的ASCII碼值,每個字符的ASCII碼值都是8位數(shù)據(jù),所以窗口中字符數(shù)量與UART傳輸?shù)拇螖?shù)是相等的,同時數(shù)字的值與ASCII碼值相差48,例如數(shù)字0的ASCII碼值為48。
當(dāng)選擇Hex的時候,通過UART發(fā)出的數(shù)據(jù)(必須是16進(jìn)制數(shù)據(jù))就是數(shù)據(jù)窗口中的數(shù)據(jù)本身,這樣每次UART傳輸都會發(fā)送兩個數(shù)字,如果只發(fā)送一個數(shù)字,則高位補(bǔ)零組成8位數(shù)據(jù),例如發(fā)送數(shù)字1,實際UART傳輸?shù)臄?shù)據(jù)為8‘h01。
我們設(shè)計一個32位的移位寄存器對應(yīng)8位數(shù)碼管,按照BCD碼格式每4位表示一個數(shù)字,每次接收到UART數(shù)據(jù)都存到移位寄存器中,同時控制數(shù)碼管顯示相應(yīng)的數(shù)碼管位,Decoder程序?qū)崿F(xiàn)如下:
`ifdef HEX_FORMAT //如果`define定義過HEX_FORMAT,綜合`else前面的程序
//采用16進(jìn)制格式,接收到的數(shù)據(jù)等于數(shù)值本身
wire[7:0] seg_data_r = rx_data_out;
//移位寄存器,對應(yīng)8位數(shù)碼管數(shù)據(jù)BCD碼
always@(posedge rx_data_valid ornegedge rst_n)begin
if(!rst_n) seg_data <=1'b0;
else seg_data <={seg_data[23:0],seg_data_r};
end
//移位寄存器,對應(yīng)8位數(shù)碼管數(shù)據(jù)顯示使能
always@(posedge rx_data_valid ornegedge rst_n)begin
if(!rst_n) data_en <=1'b0;
else data_en <={data_en[5:0],2'b11};
end
`else //否則綜合`else后面的程序
//采用字符格式,接收到的數(shù)據(jù)為字符ASCII碼值,與數(shù)字值相差48
wire[7:0] seg_data_r = rx_data_out -8'd48;
//移位寄存器,對應(yīng)8位數(shù)碼管數(shù)據(jù)BCD碼
always@(posedge rx_data_valid ornegedge rst_n)begin
if(!rst_n) seg_data <=1'b0;
else seg_data <={seg_data[27:0],seg_data_r[3:0]};
end
//移位寄存器,對應(yīng)8位數(shù)碼管數(shù)據(jù)顯示使能
always@(posedge rx_data_valid ornegedge rst_n)begin
if(!rst_n) data_en <=1'b0;
else data_en <={data_en[6:0],1'b1};
end
`endif
上面程序中`ifdef……`else……`endif語句為預(yù)編譯指令,與C預(yù)演類似。如果我們使用串口助手Hex(16進(jìn)制)格式發(fā)送數(shù)據(jù),需要在程序中使用define定義參數(shù)HEX_FORMAT,如果使用ASCII格式發(fā)送數(shù)據(jù),則不需要定義。
`define HEX_FORMAT //串口助手使用Hex格式發(fā)送時定義HEX_FORMAT,否則不定義
綜合后的設(shè)計框圖如下:
到這一步,串口監(jiān)視系統(tǒng)設(shè)計就完成了。使用兩根Micro-USB線同時連接核心板和底板的USB接口,將程序下載到FPGA中,數(shù)碼管處于不顯示的狀態(tài),打開電腦上的串口調(diào)試助手,按照前面圖片配置相應(yīng)參數(shù),在數(shù)據(jù)發(fā)送窗口輸入數(shù)字,點擊發(fā)送觀察底板數(shù)碼管的變化,重新輸入數(shù)字,點擊發(fā)送再次觀察底板數(shù)碼管的變化。