FPGA基礎知識極簡教程(6)UART通信與移位寄存器的應用

在這裏插入圖片描述


寫在前面

相關博文1單獨介紹了各種類型的移位寄存器,其中就包括串行輸入並行輸出移位寄存器(SIPO)以及並行輸入串行輸出移位寄存器 (PISO)。
移位寄存器有如下功能:

  1. 將數據延遲一定數量的時鐘週期
  2. 將串行數據轉換爲並行數據
  3. 將並行數據轉換爲串行數據

第一種功能很常見,就是我們通常所說的對某某信號打幾拍處理或者同步幾拍等等,這是時序控制中常用的手段,如下:
4bit數據同步器
這個4bit的移位寄存器可以對輸入數據同步4拍!

對於後兩種功能纔是我今天想說的,那就是串並轉換以及並串轉換,其用途之一就是在UART中,下面我們將首先介紹UART相關知識最後引出PISO以及SIPO在UART中的使用情況。


正文

關於UART的介紹

UART(Universal Asynchronous Receiver/Transmitter)代表“通用異步接收器/發送器”,它是由Digital Equipment Corporation的Gordon Bell在1960年代開發的。 “通用”部分是關於數據格式和傳輸速度是可配置的事實。
UART能夠以幾種不同的模式進行通信:

  • 全雙工(設備輪流發送和接收);
  • 半雙工(通信雙方同一時間只能單向收發);
  • 單工(僅在一個方向上,沒有規定接收設備將信息發送回發送設備)。
    因爲UART的通信方式可配置,所以發送和接收UART都需要以完全相同的方式進行配置,以確保成功進行通信。

基本的 UART 系統提供強大、中速、全雙工通信,只有三個信號:Tx(傳輸的串行數據)、Rx(接收的串行數據)和接地。與其他協議(如 SPI 和I2C)不同,不需要時鐘信號,因爲用戶向 UART 硬件提供了必要的計時信息。

通信方式

UART通信過程

通用異步收發器(UART)被設計爲與其他UART通信,儘管它們通常本身並不直接生成通信。僅發送和接收信號。發送UART將從主板接收一個字節,然後使用其PISO(並行輸入串行輸出)內部移位寄存器首先發送一個“起始”位,以與接收UART交流即將傳輸信息的信息。然後,信息字節一次發送一次,在收到預期的位數後,再發送一個“停止”位,從而使通信線路變高。接收UART獲取位流,並使用其SIPO(串行輸入並行輸出)移位寄存器使數據可用於其主機控制器。通過單線或其他介質進行數字信息(位)的串行傳輸比通過多線進行並行傳輸的成本低。

下圖爲UART通信的具體過程:
uart數據構成

空閒,無數據狀態爲高電平或上電。 這是電報的歷史性遺產,其中將線路保持在高位以表明線路和發送器未損壞。每個字符都被構造爲邏輯低起始位,數據位(可能是奇偶校驗位)和一個或多個停止位。 在大多數應用程序中,最低的數據位(該圖左側的數據)首先被傳輸,但是也有例外(例如IBM 2741打印終端)。

起始位:起始位向接收器發出信號,通知一個新字符即將到來。

數據位:接下來的五到九位數據,取決於所使用的代碼集,代表字符。 數據幀包含要傳輸的實際數據。如果使用奇偶校驗位,它可以是 5 位,最多 8 位長。如果未使用奇偶校驗位,則數據幀可以是 9 位長。在大多數情況下,數據首先以最低的位發送。

如果使用奇偶校驗位,它將被放置在所有數據位之後。 通過強制邏輯高位的數量始終爲偶數(對於偶數奇偶校驗)或奇數(對於奇數奇偶校驗),奇偶校驗位提供了一種粗略的錯誤檢測機制-如果在傳輸過程中某位發生翻轉,則邏輯高位的數量將與所選的奇偶校驗模式不匹配。 當然,如果將兩位翻轉,該策略就會失效,因此奇偶校驗位遠非防彈(安全)。 如果您非常需要無差錯的通訊,建議您使用CRC。

下一位或兩位始終處於標記(邏輯高電平,即“ 1”)狀態,稱爲停止位。他們向接收器發信號,說明角色已完成。

由於起始位爲邏輯低(0),而停止位爲邏輯高(1),因此字符之間始終至少有兩個保證的信號變化。

波特率:可以傳輸數據的近似速率(以比特/秒或 bps 表示)。更精確的定義是頻率(以 bps 爲單位),對應於傳輸一位數字數據所需的時間(以秒爲單位)。 例如,對於 9600 波特系統,一位需要 1/(9600 bps) = 104.2 μs。系統實際上無法每秒傳輸 9600 位有意義的數據,因爲開銷位可能需要額外的時間,並且可能還需要一字節傳輸之間的延遲。

bit period

數據位的採樣與同步:

如果沒有某種時鐘機制,標準數字數據是毫無意義的。下圖說明了原因:

採樣示意

典型的數據信號只是邏輯低電平和邏輯高之間的轉換電壓。接收器只有在知道何時對信號進行採樣時,才能將這些邏輯狀態正確轉換爲數字數據。這可以使用單獨的時鐘信號輕鬆完成,例如,發射器更新時鐘每個上升沿的數據信號,然後接收器對每個下降邊緣的數據進行採樣。

但是,正如名稱"通用異步接收器/發射機"所暗示的那樣,UART 接口不使用時鐘信號來同步 Tx 和 Rx 設備。那麼,接收器如何知道何時對發射機的數據信號進行採樣?

發射機根據其時鐘信號生成位流,然後接收器的目標是在每個比特週期的中間使用其內部時鐘信號對傳入數據進行採樣。在位週期的中間採樣不是必須的,但它是最佳的,因爲接近位週期開始或結束的採樣會使系統在接收機和發射機之間的時鐘頻率差異時不太可靠。

接收器序列從起始位的下降沿開始。這是發生關鍵同步過程時。接收器的內部時鐘與發射機的內部時鐘完全獨立,換句話說,第一個下降沿可以對應於接收器時鐘週期中的任何點:

採樣位置示意圖

爲了確保接收機時鐘的有源邊緣可以在位週期的中間附近發生,發送到接收器模塊的波特速率時鐘的頻率遠遠高於實際波特速率(乘數 8 或 16,甚至 32)。

假設一個位週期對應於 16 個接收器時鐘週期。在這種情況下,同步和採樣可以按如下方式進行:

接收過程由起始位的下降沿啓動。
接收器等待 8 個時鐘週期,以便建立接近位週期中間的採樣點。
然後,接收器等待 16 個時鐘週期,從而將其帶到第一個數據位週期的中間。
第一個數據位被採樣並存儲在接收寄存器中,然後模塊等待另外 16 個時鐘週期,然後採樣第二個數據位。
此過程重複,直到採樣和存儲所有數據位,然後停止位的上升沿將 UART 接口返回到其空閒狀態。

接收器採樣示意圖

UART、RS232以及TTL之間的關係

關於這幾者之間的關係,知乎上的一個大神說的比較好(一般不理伸手黨兼噴子),個人比較認同(文章後面會給出參考鏈接,見參考鏈接7):

UART:在通信和計算機科學中,Serial communication是一個通用概念,泛指所有的串行的通信協議,如RS232、USB、I2C、SPI、1-Wire、Ethernet等。

COM口和RS232

COM口是指針對串行通信協議的一種端口,是PC上異步串行通信口的簡寫,大部分爲9針孔D型。COM口裏分RS232,RS422和RS485,傳輸功能依次遞增。由於歷史原因,IBM的PC外部接口配置爲RS232,成爲實際上的PC界默認標準。所以,現在PC機的COM口均爲RS232。若配有多個異步串行通信口,則分別稱爲COM1、COM2…

RS232或者RS485,它更多的是規定電氣特性和各個引腳的功能定義,如 用-3V— -15V之間的任意電平表示邏輯“1” ;用+3V — +15V電平表示邏輯“0”,這裏採用的是負邏輯。

TTL

TTL全名是晶體管-晶體管邏輯集成電路(Transistor-Transistor Logic),這種串行通信,對應的物理電平,始終是在0V和Vcc之間,其中常見的Vcc是5V或3.3V。TTL 高電平1是>=2.4V,低電平0是<=0.5V(對於5V或3.3V電源電壓),這裏是正邏輯。TTL接口在Minnow板子上如圖:

TTL接口
關係

UART更多關注規定編碼格式的標準,如波特率(baud rate)、幀格式和波特率誤差等等。RS232和TTL更多是電平標準和電壓,他們在軟件協議層面是一樣的,如對於同樣傳輸0b01010101來說,RS232和TTL的時序對比:

電平比較
如何分辨究竟是TTL還是RS232呢?一般來說,由SOC芯片引腳直接引出的一般是TTL,其高低電平不需要任何轉換,可以由芯片之間驅動,節省費用;而中間接有轉換芯片的可能就是RS232了,可以根據電路圖的芯片型號google即可。

另一個原則是RS232通常出現在傳統的PC和服務器領域,TTL通常用於嵌入式設備。

UART的使用場合

uart使用場合
爲了正確操作,必須將發送和接收UART設置爲相同的位速度,字符長度,奇偶校驗和停止位。 接收UART可能會檢測到一些不匹配的設置,併爲主機系統設置一個“ framing error”標誌位。 在特殊情況下,接收UART將產生不穩定的殘缺字符流,並將其傳輸到主機系統。

與連接到調制解調器的個人計算機一起使用的典型串行端口使用8個數據位,無奇偶校驗和1個停止位。 對於這種配置,每秒ASCII字符數等於比特率除以10。

一些成本非常低的家用計算機或嵌入式系統省去了UART,並使用CPU採樣輸入端口的狀態或直接操縱輸出端口進行數據傳輸。 雖然佔用大量CPU(因爲CPU時序很關鍵),但因此可以省去UART芯片,從而節省了金錢和空間。 該技術稱爲位敲打。

有關UART的總結

  • UART,中文翻譯爲通用異步收發器,因此通信雙方是異步的,這也就意味着發送器和接收器不共享同一個時鐘,各自產生自己的時鐘。

  • 發送設備與接收設備要以同樣的速率發送或接收數據,速率可以分爲以下幾種:

UART的收發速率

  • 通信雙方使用同樣的幀結構,這樣才能保證異步通信的正確性。

  • UART幀的構成:

幀構成

起始位與結束位

低位先傳輸

奇偶檢驗位

總結

UART的Verilog實現

看了很多版本的UART收發實現,代碼設計風格不一樣,但其實原理都是一致的,例如當發送數據時,肯定需要按照波特率的速率發送串行數據,那這個波特率我們是先利用系統時鐘分頻得到波特率時鐘,用這個波特率時鐘發送串行數據呢?還是直接利用系統時鐘,通過計數的方式,計數從多少到多少發送一個串行數據,之後計數從多少到多少發送第二個數據呢?

波特率問題

我想都是可以的,例如博客:
波特率產生
這是我之前參考互聯網上資料寫的一個波特率產生的模塊,用到了分頻的思想!
假如我們約定uart的波特率爲115200bps,我們使用的系統時鐘爲2MHz,那麼2MHz要多少分頻可以達到115200bps呢?
如下計算 :
2000_000/115200=17.361111111111111111111111111111

可以採用17分頻的方法來產生這樣的波特率。
儘管有一點誤差,但完全不影響,uart發送完一幀數據之後距離發送下一幀數據之間還是有一定間隔的,不影響下一幀數據的發送,至於接收,更是不在話下!也就是說波特率具有一定的容錯範圍,引用知乎大佬的一段內容:

波特率是有一定的容錯範圍的,例如,STM32配置成115200波特率,每10ms發送一個30字節的字符串,串口芯片用的CH340,上位機波特率設置成113000-121000也可以接收,無亂碼,差不多正負2000的波特率,這容錯範圍也太大了,當然如果發送頻率太快,數據量太大,誤碼率肯定會大大增加,所以還是建議通信雙方使用同樣的波特率以減少誤差。

鏈接見參考資料11!

如果使用計數的方式,其實也存在波特率問題,同樣加入系統時鐘爲2MHz,波特率爲115200bps,那麼根據:

2000_000/115200=17.361111111111111111111111111111

我們肯定也是計數17個發送位數據,和前面先生成波特率時鐘信號無二差別!

那下面的內容,我結合兩種寫法,給出自己能夠理解的設計方式:


發送模塊波特率產生模塊

我拒絕花裏胡哨的操作,因爲我這個腦子想到博文:波特率產生提供的第二種方法太費勁,因此,我直接進行分配,產生波特率時鐘!
系統時鐘爲2MHz,波特率爲115200bps,因此進行17分頻(佔空比爲1:17):

`timescale 1ns / 1ps
module baud_gen(
	input clk,
	input enable,
	output BaudTick
    );

	reg BaudTick_mid = 0;
	assign BaudTick = BaudTick_mid;
	reg [4:0] baud_count = 0;
	
	always@(posedge clk) begin
		if(enable) begin
			if(baud_count < 16) begin
				baud_count <= baud_count + 1;
				BaudTick_mid <= 0;
			end
			else if(baud_count == 16) begin
				baud_count <= 0;
				BaudTick_mid <= 1;
			end
			else ;		
		end
		else begin
			baud_count <= 0;
			BaudTick_mid <= 0;
		end	
	end		
endmodule


對這個模塊進行仿真,仿真文件爲:

`timescale 1ns / 1ps
module baud_gen_tb(
    );

	
	reg enable;
	reg clk;
	wire Baud_Tick;
	
	initial begin
		clk = 0;
		forever 
			# 250 clk = ~clk;
	
	end
	initial enable = 1;
	
	baud_gen u0(
	.clk(clk),
	.enable(enable),
	.BaudTick(Baud_Tick)
	);
	

endmodule

仿真波形爲:
波特率產生仿真波形
符合預期!


發送模塊

對於發送模塊的設計,發送8位數據,採用偶校驗方式,算上起始位與結束位,總共11位。
發送一幀數據的發送模塊設計如下:

`timescale 1ns / 1ps
module uart_tran(
	//input 
	input clk,
	input rst_n,
	input [7:0] data_in, //data needed to be transmitted
	input trig, //transmit data when the posedge trig

	//output
	output busy, //busy is asserted when the data is transmitting
	output reg tx //output serial data


    );
	parameter IDLE = 0, START_BIT = 1, BIT1 = 2, BIT2 = 3, BIT3 = 4, BIT4 = 5;

	parameter BIT5 = 6, BIT6 = 7, BIT7 = 8, BIT8 = 9, POLARITY = 10, STOP_BIT = 11;

	wire BaudTick;

	reg trig_r, trig_posedge;
	//get the posedge of the trig
	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			trig_r <= 0;
			trig_posedge <= 0;
		end
		else begin
			trig_r <= trig;
			trig_posedge <= (~trig_r)&trig;
		end
	end

	//generate odd or even polarity bit
	wire odd_bit;   //奇校驗位 = ~偶校驗位
	wire even_bit;  //偶校驗位 = 各位異或
	wire polarity_bit = even_bit;  //偶校驗
	// wire POLARITY_BIT = odd_bit;   //奇校驗

	assign even_bit = ^data_in; //even_bit = data_in[0] ^ data_in[1] ^ .....
	assign odd_bit = ~even_bit;

	assign busy = (cur_state == IDLE) ? 0 : 1 ; //busy is dessertted when the current state is IDLE

	// reg [10 : 0] data_in_buf;
	// always @ (posedge clk)
	// begin
 //   		if(!rst_n)
 //       		data_in_buf <= 11'b0;
 //   		else if(trig_posedge & (~busy))    //只讀取一次數據,一幀數據發送過程中,改變輸入無效
 //      		data_in_buf <= {1'b1, polarity_bit, data_in[7:0], 1'b0};   //數據幀拼接
	// end



	//state variable define
	reg [3:0] cur_state, nxt_state = IDLE;
	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			cur_state <= IDLE;
		end
		else begin
			cur_state <= nxt_state;
		end
	end

	always@(*) begin
		case(cur_state)
			IDLE: begin
				if(trig_posedge) nxt_state = START_BIT;
			end
			START_BIT: begin
				if(BaudTick) nxt_state = BIT1;
			end
			BIT1: begin
				if(BaudTick) nxt_state = BIT2;
			end
			BIT2: begin
				if(BaudTick) nxt_state = BIT3;
			end
			BIT3: begin
				if(BaudTick) nxt_state = BIT4;
			end
			BIT4: begin
				if(BaudTick) nxt_state = BIT5;
			end
			BIT5: begin
				if(BaudTick) nxt_state = BIT6;
			end
			BIT6: begin
				if(BaudTick) nxt_state = BIT7;
			end
			BIT7: begin
				if(BaudTick) nxt_state = BIT8;
			end
			BIT8: begin
				if(BaudTick) nxt_state = POLARITY;
			end
			POLARITY: begin
				if(BaudTick) nxt_state = STOP_BIT;
			end
			STOP_BIT: begin
				if(BaudTick) nxt_state = IDLE;
			end
			default: begin
				if(BaudTick) nxt_state = IDLE;
			end
		endcase
	end

	reg tx_mid = 1;
	always@(*) begin
		case(cur_state)
			IDLE: begin
				if(trig_posedge) tx_mid = 1;
			end
			START_BIT: begin
				if(BaudTick) tx_mid = 0;
			end
			BIT1: begin
				if(BaudTick) tx_mid = data_in[0];
			end
			BIT2: begin
				if(BaudTick) tx_mid = data_in[1];
			end
			BIT3: begin
				if(BaudTick) tx_mid = data_in[2];
			end
			BIT4: begin
				if(BaudTick) tx_mid = data_in[3];
			end
			BIT5: begin
				if(BaudTick) tx_mid = data_in[4];
			end
			BIT6: begin
				if(BaudTick) tx_mid = data_in[5];
			end
			BIT7: begin
				if(BaudTick) tx_mid = data_in[6];
			end
			BIT8: begin
				if(BaudTick) tx_mid = data_in[7];
			end
			POLARITY: begin
				if(BaudTick) tx_mid = polarity_bit;
			end
			STOP_BIT: begin
				if(BaudTick) tx_mid = 1;
			end
			default: begin
				if(BaudTick) tx_mid = 1;
			end
		endcase
	end


	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			tx <= 1;
		end
		else begin
			tx <= tx_mid;
		end
	end


	baud_gen inst_baud_gen(
		.clk(clk),
		.enable(1),
		.BaudTick(BaudTick)
		);

endmodule

測試文件:

`timescale 1ns / 1ps
module uart_tran_tb(
    );

	
	//input 
	reg clk;
	reg rst_n;
	reg [7:0] data_in; //data needed to be transmitted
	reg trig; //transmit data when the posedge trig

	//output
	wire busy; //busy is asserted when the data is transmitting
	wire tx;//output serial data
	
	initial begin
		clk = 0;
		forever 
			# 250 clk = ~clk;
	
	end

	initial begin
		rst_n = 0;
		trig = 0;
		#1000 rst_n = 1;
		trig = 1;
		data_in = 8'b1011_0011;
		#1000
		@(negedge clk) trig = 0;

	end



	
	uart_tran inst_uart_tran(
		.clk(clk),
		.rst_n(rst_n),
		.data_in(data_in),
		.trig(trig),
		.busy(busy),
		.tx(tx)
		);
	

endmodule

測試波形圖如下:
發送模塊波形圖
可見,符合預期!

接收模塊

下面給出接收一幀數據的設計!
對於串口接收數據,通常採用過採樣的方式採樣數據,對串口接收數據採樣多少次合適呢?
我們都知道在本博文一開始設計串口數據發送的時候,由於波特率爲115200bps,也就是一位數據8.68us,但我們的時鐘週期爲0.5us,也就是時鐘頻率爲2MHz,那麼需要17個週期發送一個數據。
那麼問題 來了,我們接收的時候多少個時鐘接收一個數據呢?
爲了保持我們的約定,我們也是17個時鐘接收一個數據,也就是我們所謂的過採樣係數爲17,或者說我們對一個數據採樣17次,之後取中間數據作爲我們接收的串行值,這就比較保險。

給出設計文件:

`timescale 1ns / 1ps
/////////////////////////////////////
// Engineer: Reborn Lee
/////////////////////////////////////


module uart_rec(
	input clk,
	input rst_n,
	input rx,
	output reg [7:0] rx_data_out,
	output reg rx_data_out_ready,
	output reg err_check,
	output reg err_frame

    );

	wire odd_bit;   //奇校驗位 = ~偶校驗位
	wire even_bit;  //偶校驗位 = 各位異或
	wire POLARITY_BIT;   //本地計算的奇偶校驗

	assign even_bit = ^rx_data_out;     //一元約簡,= data_in[0] ^ data_in[1] ^ .....
	assign odd_bit = ~even_bit;
	assign POLARITY_BIT = even_bit;  //偶校驗



	// the negedge of rx
	reg rx_r, rx_rr, rx_negedge;
	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			rx_r <= 1;
			rx_rr <= 1;
			rx_negedge <= 0;
		end
		else begin
			rx_r <= rx;
			rx_rr <= rx_r;
			rx_negedge <= rx_r&(~rx);
		end
	end

	reg receive;
	always @(posedge clk or negedge rst_n) begin
		if (~rst_n) begin
			// reset
			receive <= 0;
		end
		else if (rx_negedge) begin
			receive <= 1;
		end
		else if(rx_state == 4'b0010 && sampleNow) begin
			receive <= 0;
		end
		else begin
			;
		end
	end


	reg [4:0] OversamplingCnt = 0;

	always @(posedge clk) begin
		// OversamplingCnt <= (receive == 0) ? 1'd0 : OversamplingCnt + 1'd1;
		OversamplingCnt <= (receive == 1 && OversamplingCnt <= 15) ? OversamplingCnt + 1 : 0;
	end
		
		
	wire sampleNow = OversamplingCnt == 8;

	reg [3:0] rx_state = 4'b0000;
	always @(posedge clk)
		case(rx_state)
			4'b0000: if(receive) begin
						rx_state <= 4'b0001;  
					 end
			4'b0001: if(sampleNow) rx_state <= 4'b1000;  // sync start bit to sampleNow
			4'b1000: if(sampleNow) rx_state <= 4'b1001;  // bit 0
			4'b1001: if(sampleNow) rx_state <= 4'b1010;  // bit 1
			4'b1010: if(sampleNow) rx_state <= 4'b1011;  // bit 2
			4'b1011: if(sampleNow) rx_state <= 4'b1100;  // bit 3
			4'b1100: if(sampleNow) rx_state <= 4'b1101;  // bit 4
			4'b1101: if(sampleNow) rx_state <= 4'b1110;  // bit 5
			4'b1110: if(sampleNow) rx_state <= 4'b1111;  // bit 6
			4'b1111: if(sampleNow) rx_state <= 4'b0100;  // bit 7
			4'b0100: if(sampleNow) rx_state <= 4'b0010;  // polarity bit
			4'b0010: if(sampleNow) rx_state <= 4'b0000;  // stop bit
			default: rx_state <= 4'b0000;
		endcase


	always @(posedge clk)
		if(sampleNow && rx_state[3]) rx_data_out <= {rx_rr, rx_data_out[7:1]};

	always @(posedge clk) begin
		rx_data_out_ready <= (sampleNow && rx_state==4'b0100);  // make sure a stop bit is received
	end


//校驗錯誤:奇偶校驗不一致
always @ (posedge clk)
begin
    if(!rst_n)
        err_check <= 0;
    else if(rx_state == 4'b0100 && sampleNow)
    begin
        if(POLARITY_BIT != rx_rr)      //奇偶校驗正確
            err_check <= 1;         //鎖存     
    end
end

//幀錯誤:停止位不爲1
always @ (posedge clk)
begin
    if(!rst_n)
        err_frame <= 0;
    else if(rx_state == 4'b0010 && sampleNow)
    begin
        if(rx_rr != 1)        //停止位
            err_frame <= 1;
    end
end
endmodule

我認爲這裏有必要解釋一些小細節,關於裏面的時序關係的解釋如下:
我們認爲接收數據幀的起點是數據幀的下降沿,這是因爲數據幀的第一個數據是起始數據,而起始數據爲0:

	// the negedge of rx
	reg rx_r, rx_rr, rx_negedge;
	always@(posedge clk or negedge rst_n) begin
		if(~rst_n) begin
			rx_r <= 1;
			rx_rr <= 1;
			rx_negedge <= 0;
		end
		else begin
			rx_r <= rx;
			rx_rr <= rx_r;
			rx_negedge <= rx_r&(~rx);
		end
	end

這是下降沿標誌相對於數據起點來說已經延遲了1個時鐘,而接收數據有效標誌receive,檢測到下降沿的時候,置1,如下:

reg receive;
	always @(posedge clk or negedge rst_n) begin
		if (~rst_n) begin
			// reset
			receive <= 0;
		end
		else if (rx_negedge) begin
			receive <= 1;
		end
		else if(rx_state == 4'b0010 && sampleNow) begin
			receive <= 0;
		end
		else begin
			;
		end
	end

因此,其相對於數據起點,延遲了二拍!
而receive爲1的時候我們開始計數,如下:

reg [4:0] OversamplingCnt = 0;

	always @(posedge clk) begin
		// OversamplingCnt <= (receive == 0) ? 1'd0 : OversamplingCnt + 1'd1;
		OversamplingCnt <= (receive == 1 && OversamplingCnt <= 15) ? OversamplingCnt + 1 : 0;
	end

我們取計數的大致中間位置的採樣值爲接收數據值。
爲了保持數據與計數值保持時序上一致,也即是計數爲0的時候,數據開始!
我們的處理是用對數據延遲兩拍作爲實際要採樣的對象!
我們來提前看看效果:
接收數據仿真波形圖

測試文件:

`timescale 1ns / 1ps
 
module uart_rec_tb(
    );
	reg clk;
	reg rst_n;
	reg rx;
	wire [7:0] rx_data_out;
	wire rx_data_out_ready;
	wire err_check;
	wire err_frame;
 
	
	parameter DATA0 = 11'b11010101010; //從低位開始發送
	reg [10:0] data_in;
	
	initial begin
		clk = 0;
		forever 
			#250 clk = ~clk;
	end

	initial begin
		rst_n = 0;
		data_in = DATA0;
		rx = 1;
		#2000
		rst_n = 1;

		for(integer i = 0; i <= 10; i = i + 1) begin
			#8500
				rx = data_in[i];
		end
		
		#8680 rx = 1;
		#20000 $finish;
	
	end
	
	uart_rec inst_uart_sec(
		.clk(clk),
		.rst_n(rst_n),
		.rx(rx),
		.rx_data_out(rx_data_out),
		.rx_data_out_ready(rx_data_out_ready),
		.err_check(err_check),
		.err_frame(err_frame)
		);
	

endmodule

仿真波形:

仿真波形1
由於我們發送的數據爲01010101,因此對於偶校驗來說,校驗位應該爲0,但是我們在仿真文件中給出校驗位爲1,測試一下能不能檢測到校驗位出錯!
偶校驗檢測波形
好了,暫時就到這裏吧。

UART和移位寄存器之間的關係?

爲什麼說UART中使用了移位寄存器呢?
可以看如下代碼:
接受模塊

always @(posedge clk)
		if(sampleNow && rx_state[3]) rx_data_out <= {rx_rr, rx_data_out[7:1]};

接受模塊,將串行數據轉換爲並行數據!
這裏並不是一個週期將移位一次,而是有條件的!因此,可以看做是移位寄存器的變體版本!

發送模塊,我已經把它寫成狀態機了,這裏就不說了,其實可以寫成別的形式,和接收模塊類似!
不再多說!


參考資料


交個朋友

不得不說,這篇博客用了我兩天時間,當然睡覺居多,但也已經花費很多精力了,但完成這篇博客並更深刻理解了UART還是很值得的!如果你喜歡,也請給點個贊吧(在看)(關注)!創作不易,這裏感謝了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章