串口監視系統設計
實驗任務
實驗目的
本實驗主要學習串口(UART)總線工作原理、協議及相關知識,練習如何使用FPGA驅動CP2102模塊實現串口通信設計,同時復習上節中掃描式數碼管模塊的實例化應用。
本文引用地址:http://www.ex-cimer.com/article/202312/453843.htm設計框圖
根據前面的實驗解析我們可以得知,該設計可以拆分成三個功能模塊實現,
頂層模塊DisplayCtl通過實例化兩個子模塊并將對應的信號連接,最終實現串口監視系統的總體設計。UART通信是全雙工的,接收和發送是兩個獨立的設計,本實驗只需要接收數據,串口通信有兩個關鍵因素:傳輸格式和傳輸速率,我們可以用兩個模塊分別實現: * Baud:控制UART通信數據傳輸速率。 * UartRx:根據數據傳輸速率節拍控制UART通信數據格式。
實驗原理
UART接口介紹
在嵌入式領域里說的串口一般就是說的UART接口,通用異步收發傳輸器(Universal Asynchronous Receiver/Transmitter),通常稱作UART,是一種通用串行數據總線,用于異步通信。該總線雙向通信,可以實現全雙工傳輸和接收。
在系統或計算機中說的串口一般就是說的RS232接口,也叫COM口,也叫DB9,老式的電腦和臺式機上一般都有這個接口,接口有9個引腳,最重要的三個引腳:TXD、RXD、GND,基本通信邏輯與UART完全一致,為了增加串口通信的抗干擾能力,RS232串行通信接口定義了自己的電平標準,采用負邏輯電平,它定義+5~+12V為低電平,而-12~-5V為高電平,相當于在UART的基礎上增加驅動器,將原來UART通信電平標準調整為RS232的電平標準,通信原理如下:
隨著技術的發展,各種通信接口種類越來越多,方案越來越穩定,成本越來越低,體積越來越小巧,RS232串口通信接口方案逐漸被拋棄,取而代之的是各種更高速,更穩定,更小巧的接口,USB就是其中應用較廣的,為了實現UART通信,一種USB轉UART的方案被廣泛應用,常用的USB轉UART方案有CP2102、FT232、CH340等等
我們STEP BaseBoard V3.0底板集成的UART通信模塊就是采用CP2102方案,FPGA通過UART總線驅動CP2102實現USB和UART之間的數據通信,最終實現FPGA與電腦之間的數據傳輸,UART通信的時序如下。
UART模塊連接
STEP BaseBoard V3.0底板上的基于CP2102方案的UART通信模塊電路圖如下:
上圖為基于CP2102方案的UART通信模塊電路圖,可以看到CP2102方案非常簡潔,無需外置USB通信時鐘晶體(內部集成),CP2102芯片TXD和RXD分別與FPGA芯片RXD和TXD連接,同時兩個信號都連接了LED燈,這樣當UART通信時,隨著數據傳輸對應LED燈也會快速閃爍,起到UART通信指示燈的作用。CP2102芯片DTR和RTS通過兩個三極管搭建流控電路,連接WIFI模塊ESP8266-12F,使用UART模塊燒寫ESP8266模塊的固件時就無需手動進入固件燒寫模式了,這個會在后續涉及WIFI通信的實驗中詳細介紹,這里可以不用理會。
UART驅動實現
SPI、I2C、UART總線對比表:
SPI總線 | I2C總線 | UART總線 |
SS | TXD | |
SDA | RXD | |
MOSI/MISO |
對于SPI總線,通信雙方在總線使能的情況下,通過SCK的上升沿或下降沿觸發完成總線數據的采樣,這樣通信雙方就可以準確的接收到對方傳送的數據了。對于I2C總線,通信接收方通過SCL的高電平觸發完成總線數據的采樣。綜上,SPI總線中的SCK和I2C總線中的SCL在通信中起到時鐘的作用,接收方都是根據時鐘的對應狀態采樣數據,最終保證通信能夠正常進行。
對于UART總線,TXD和RXD分別用于發送和接收數據,相當于兩根獨立工作的單線總線,沒有了時鐘線的配合,那么接收端應該怎樣獲取發送端傳輸的數據呢?其實也是有方法的,那就是通信雙方需要約定好UART總線數據傳輸的
和 。UART的數據傳輸速度用波特率來描述,也就是UART每秒接收或發送的數據位。例如9600波特率表示每秒鐘發送或接收9600比特的數據,即發送端需要將發送的每個數據位保持對應的時間,計算如下:
小腳丫硬件上使用12MHz的時鐘晶振,如果以12MHz時鐘信號作為系統時鐘,使用計數器延時完成UART通信數據采樣,那么計數器延時計數終值計算如下:
因為波特率是協議里約定的,為保證協議的通用性和靈活性,波特率參數有固定的選項,不可以隨意設置(如果UART通信雙方都是自己編程的,可以根據自己的要求定義自己需要的波特率,這種情況除外),波特率參數選項很多,大家可以打開串口調試助手工具找到波特率配置列表查看,我們比較常用的波特率值有以下幾種:
UART常用波特率:
1200 | 4800 | 9600 | 38400 | 115200 |
關于時序格式在前面UART接口介紹部分也簡單說了一下,通信過程中時序依次為:起始位、數據位、校驗位、停止位、空閑位,其中數據位可以是5~8位,本設計我們使用8位數據,校驗位可以省略,最后確定的時序格式如下:
前面所說的通信速率和時序格式其實就是UART通信中的兩個重要的參數,需要傳輸的數據根據通信速率的節拍按照UART的時序格式輸出,就可以實現UART通信了,可以按照下面三個步驟實現。
例如,將8‘h73和8’h5a通過UART發送的時序,紅色箭頭為波特率對應的節拍點
對于UART發送數據來說,波特率節拍是自己產生的,數據是自己主動發出的,邏輯相對簡單,而當UART接收數據的時候,因為不確定對方什么時候發送數據,所以需要對RX信號持續檢測,當檢測到有數據傳送時,根據約定的波特率節拍采樣,可以按照下面三個步驟實現。
例如,當UART的RX端接收到數據8‘h73和8’h5a的時候,紅色箭頭為檢測到數據傳輸的點,綠色箭頭為對應的采樣節拍點(采樣點在數據中間最是穩定)。
通過以上理論,我們了解了UART發送和接收數據的整個流程,兩個過程中我們都需要波特率節拍,那么我們就可以設計一個節拍模塊Baud,這樣我們的發送和接收都可以實例化節拍模塊用于產生對應波特率的節拍信號。
節拍模塊Baud設計實現:
節拍模塊Baud的端口程序實現如下:
module Baud # ( parameter BPS_PARA = 1250 //12MHz時鐘時參數1250對應9600的波特率 ) ( input clk, //系統時鐘 input rst_n, //系統復位,低有效 input bps_en, //接收或發送時鐘使能 output reg bps_clk //接收或發送時鐘輸出 );
設計一個計數器用于分頻產生對應波特率節拍信號,因為UART隨時可能接收數據,所以節拍模塊必須隨時待命,保持計數器清零,當需要節拍信號時精準地輸出。
計數器設計程序實現如下:
reg [12:0] cnt;//計數器計數滿足波特率時鐘要求 always @ (posedge clk or negedge rst_n) begin if(!rst_n) cnt <= 1'b0; else if((cnt >= BPS_PARA-1)||(!bps_en)) //當時鐘信號不使能(bps_en為低電平)時,計數器清零并停止計數 cnt <= 1'b0; //當時鐘信號使能時,計數器對系統時鐘計數,周期為BPS_PARA個系統時鐘周期 else cnt <= cnt + 1'b1; end
當bpsen(高有效)使能,計數器計數周期由參數BPSPARA來決定,前面數據接收時序部分了解到,從RX檢測到下降沿開始計數器工作,到數據采樣點需要半個節拍的時間,而數據發送時只要保證相鄰兩個節拍點之間的時間為一個計數器周期即可,所以我們可以在計數器計數到中值時產生一個脈沖信號充當節拍信號。
節拍信號產生程序實現如下:
//產生相應波特率的時鐘節拍,接收模塊將以此節拍進行UART數據接收 always @ (posedge clk or negedge rst_n) begin if(!rst_n) bps_clk <= 1'b0; else if(cnt == (BPS_PARA>>1)) //右移一位等于除以2,終值BPS_PARA為數據更替點,中值數據穩定,做采樣點 bps_clk <= 1'b1; else bps_clk <= 1'b0; end
發送模塊Uart_Tx設計實現:
前級電路通過txdatavalid和txdatain將需要發送的數據傳輸進來,當txdatavalid有脈沖信號時,txdatain信號為有效數據,拼接起始位和停止位后賦值給txdatar,同時控制節拍使能信號使能并自鎖,然后等發送完10bit數據后解除使能。
數據發送控制程序實現如下:
output reg bps_en; //發送時鐘使能 input bps_clk; //發送時鐘輸入 input tx_data_valid; //發送數據有效脈沖 input [7:0] tx_data_in; //要發送的數據 output reg uart_tx; //UART發送輸出 reg [3:0] num; reg [9:0] tx_data_r; //融合了起始位和停止位的數據//驅動發送數據操作 always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin bps_en <= 1'b0; tx_data_r <= 10'd0; end else if(tx_data_valid && (!bps_en))begin bps_en <= 1'b1; //當檢測到接收時鐘使能信號的下降沿,表明接收完成,需要發送數據,使能發送時鐘使能信號 tx_data_r <= {1'b1,tx_data_in,1'b0}; end else if(num==4'd10) begin bps_en <= 1'b0; //一次UART發送需要10個時鐘信號,然后結束 end end
UART數據發送時序程序實現如下:
//當處于工作狀態中時,按照發送時鐘的節拍發送數據 always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin num <= 1'b0; uart_tx <= 1'b1; end else if(bps_en) begin if(bps_clk) begin num <= num + 1'b1; uart_tx <= tx_data_r[num]; end else if(num>=4'd10) num <= 4'd0; end end
將節拍模塊Baud和發送模塊Uart_tx實例化并連接,完成發送功能的設計,如下
接收模塊Uart_Rx設計實現:
首先對RX信號多級緩存消除亞穩態,同時檢測下降沿,程序實現如下:
input uart_rx; //UART接收輸入 reg uart_rx0,uart_rx1,uart_rx2; //多級延時鎖存去除亞穩態 always @ (posedge clk) begin uart_rx0 <= uart_rx; uart_rx1 <= uart_rx0; uart_rx2 <= uart_rx1; end //檢測UART接收輸入信號的下降沿 wire neg_uart_rx = uart_rx2 & ~uart_rx1;
當檢測RX有下降沿后,使能節拍使能信號,同時自鎖直到完成接收操作后再復位節拍使能信號。程序實現如下:
reg [3:0] num; //接收時鐘使能信號的控制 always @ (posedge clk or negedge rst_n) begin if(!rst_n) bps_en <= 1'b0; else if(neg_uart_rx && (!bps_en)) //當空閑狀態(bps_en為低電平)時檢測到UART接收信號下降沿,進入工作狀態(bps_en為高電平),控制時鐘模塊產生接收時鐘 bps_en <= 1'b1; else if(num==4'd9) //當完成一次UART接收操作后,退出工作狀態,恢復空閑狀態 bps_en <= 1'b0; end
根據節拍信號完成UART總線的數據采樣,得到8位有效數據,程序實現如下:
reg [7:0] rx_data;//當處于工作狀態中時,按照接收時鐘的節拍獲取數據 always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin num <= 4'd0; rx_data <= 8'd0; end else if(bps_en) begin if(bps_clk) begin num <= num + 1'b1; if(num<=4'd8) rx_data[num-1] <= uart_rx1; //先接受低位再接收高位,8位有效數據 end else if(num == 4'd9) begin //完成一次UART接收操作后,將獲取的數據輸出 num <= 4'd0; end end else begin num <= 4'd0; end end
當UART接收操作完成后,將得到的8位有效數據輸出給后級電路,程序實現如下:
//將接收的數據輸出,同時控制輸出有效信號產生脈沖 always @ (posedge clk or negedge rst_n) begin if(!rst_n) begin rx_data_out <= 8'd0; rx_data_valid <= 1'b0; end else if(num == 4'd9) begin rx_data_out <= rx_data; rx_data_valid <= 1'b1; end else begin rx_data_out <= rx_data_out; rx_data_valid <= 1'b0; end end
最后將節拍模塊Baud和接收模塊Uart_rx實例化并連接,完成發送功能的設計,如下
整個UART驅動設計是由兩個獨立的功能組合而成:發送功能部分和接收功能部分。UART功能總體設計框圖如下:
當我們需要UART發送數據的時候只需要實例化發送功能部分設計,需要UART接收數據的時候只需要實例化接收功能部分設計,例如本設計中FPGA驅動UART模塊接收電腦串口調試助手發出的數據,所以我們就只需要實例化接收功能部分設計即可。
系統總體實現
剛剛學習了UART通信模塊,本設計只需要使用接收功能部分設計,每一次通信都會得到一個8位數據,怎樣將8位數據對應得數據顯示在數碼管上呢?我來先來了解一下UART接受到的8位數據與要顯示數字的關系
上圖為電腦端友善串口調試助手的界面,當我們將硬件連接,在串口設置串口選定串口對應的端口,并按上圖配置波特率、數據位、校驗位、停止位、流控等,點擊開始建立連接,接下來我們就可以在串口發送窗口輸入要發送的數據,點擊發送后數據傳輸出去。在發送設置有兩個選項:ASCII和Hex ,
我們設計一個32位的移位寄存器對應8位數碼管,按照BCD碼格式每4位表示一個數字,每次接收到UART數據都存到移位寄存器中,同時控制數碼管顯示相應的數碼管位,Decoder程序實現如下:
`ifdef HEX_FORMAT //如果用define定義過HEX_FORMAT //采用16進制格式,接收到的數據等于數值本身 wire [7:0] seg_data_r = rx_data_out; //移位寄存器,對應8位數碼管數據BCD碼 always @ (posedge rx_data_valid or negedge rst_n) begin if(!rst_n) seg_data <= 1'b0; else seg_data <= {seg_data[23:0],seg_data_r}; end //移位寄存器,對應8位數碼管數據顯示使能 always @ (posedge rx_data_valid or negedge rst_n) begin if(!rst_n) data_en <= 1'b0; else data_en <= {data_en[5:0],2'b11}; end`else //采用字符格式,接收到的數據為字符ASCII碼值,與數字值相差48 wire [7:0] seg_data_r = rx_data_out - 8'd48; //移位寄存器,對應8位數碼管數據BCD碼 always @ (posedge rx_data_valid or negedge rst_n) begin if(!rst_n) seg_data <= 1'b0; else seg_data <= {seg_data[27:0],seg_data_r[3:0]}; end //移位寄存器,對應8位數碼管數據顯示使能 always @ (posedge rx_data_valid or negedge rst_n) begin if(!rst_n) data_en <= 1'b0; else data_en <= {data_en[6:0],1'b1}; end `endif
上面程序中ifdef……
else……endif語句為預編譯指令,與C預演類似。如果我們使用串口助手Hex(16進制)格式發送數據,需要在程序中使用define定義參數HEX_FORMAT,如果使用ASCII格式發送數據,則不需要定義。 <code verilog>
define HEXFORMAT 串口助手使用Hex格式發送時定義HEX_FORMAT,否則不定義 </code> 綜合后的設計框圖如下:
#### 實驗步驟 - 雙擊打開Quartus Prime工具軟件; - 新建工程:File → New Project Wizard(工程命名,工程目錄選擇,設備型號選擇,EDA工具選擇); - 新建文件:File → New → Verilog HDL File,鍵入設計代碼并保存; - 設計綜合:雙擊Tasks窗口頁面下的Analysis & Synthesis對代碼進行綜合; - 管腳約束:Assignments → Assignment Editor,根據項目需求分配管腳; - 設計編譯:雙擊Tasks窗口頁面下的Compile Design對設計進行整體編譯并生成配置文件; - 程序燒錄:點擊Tools → Programmer打開配置工具,Program進行下載; - 觀察設計運行結果。 #### 實驗現象 使用兩根Micro-USB線同時連接核心板和底板的USB接口,將程序下載到FPGA中,數碼管處于不顯示的狀態,打開電腦上的串口調試助手,按照前面圖片配置相應參數,在數據發送窗口輸入數字,點擊發送觀察底板數碼管的變化,重新輸入數字,點擊發送再次觀察底板數碼管的變化。
評論