UART協議及其Verilog實現


概述

Uart是個縮寫,全稱是通用異步收發傳輸器(Universal Asynchronous Receiver/Transmitter)。單向傳輸只需要單線。異步傳輸的意思是沒有同步時鍾來同步發送端和接受端的數據,所以在數據之前添加起始位,之后添加結束位,以此來判斷傳輸過程的開始和結束。當接收端檢測到開始位,即開始以特定的頻率來接收輸入的bit位,這個特定的頻率稱為波特率。發送端和接收端要在大致相同的波特率下工作,才可以保證傳輸的正確性(最多相差10%)。

數據包的構成

Uart協議的傳輸數據被整合成數據包,每個數據包包含1位起始位,5-9位的數據位(具體決定於需求等因素),1位可選的奇偶校驗位和1-2位的停止位。如下圖所示:

Uart數據包的構成

起始位(start bit)

數據傳輸線空閑的時候保持低電平,當開始傳輸時,拉低一個時鍾周期,這就是起始位。當接受端檢測到數據線由高到低的變化時便開始以約定的波特率來接收上述的數據包。

數據幀(data frame)

這是實際需要傳輸的數據。如果使用奇偶校驗功能的話,可以傳輸5-8位的數據;如果不使用奇偶校驗功能,則可以傳輸9位。一般由最低位開始傳輸。

奇偶校驗位(parity)

用於接收端判斷接收到的數據的正誤。當接受端接收到數據幀后,計算其中1的個數是奇數個還是偶數個。如果奇偶校驗位是0(偶校驗),那么數據幀中1的個數應該是一個偶數。如果奇偶校驗位是1(奇校驗),那么數據幀中1的個數應該是奇數。當奇偶校驗位與數據匹配時,傳輸沒有錯誤。但是如果奇偶校驗位是0,但1的個數是奇數或者奇偶校驗位是1,個數卻是偶數,則數據傳輸過程中發生了變化。奇偶校驗只有粗略判斷正誤的功能,沒有改正的能力。

停止位(stop bits)

高電平保持1-2個時鍾周期表示1-2位停止位,即停止位為高電平。

以上參考:BASICS OF UART COMMUNICATION

波特率

波特率和比特率

比特率:每秒鍾傳輸的二進制位數(bit),表示有效數據的傳輸速率,單位是b/s 、bit/s、比特/秒,讀作:比特每秒。

波特率:波特率可以被理解為單位時間內傳輸符號的個數(傳符號率),通過不同的調制方法可以在一個符號上負載多個比特信息。

比特率和波特率在數值上有如下關系:

\[I=S \cdot \log _{2} N \]

其中I 為傳信率(比特率),S 為波特率,N 為每個符號負載的信息量,而\(\log _{2} N\)以比特為單位。

以RS232為例,假設目前“波特率”為 9600, 則此RS232的比特率計算為

\[I=S \cdot \log _{2} N=9600 \cdot \log _{2} 2=9600 b i t / s \]

常有人把RS232之N 誤以為是每個“符號”(symbol)所夾帶的訊息量為\(2^8\),但實際上每一個“位元”(bit)即為一個“符號”(symbol)。

計算機通信中,波特率與比特率雖在數值上相等,但是它們的意義並不相同。

以上參考:波特率

常見波特率

9600、19200 、38400 、57600 、115200、230400、460800、921600

時鍾與波特率的計算

FPGA 主頻如果為50M,則時鍾周期就是20ns。若數據發送速率為9600bps,則一位數據需要的時間為1000000000/9600=104167ns,則FPGA 傳送一位需要翻轉104167/20=5208個周期才可傳送一位,所以程序中需計數5208才可滿足9600bps。

簡單一點就是時鍾頻率除以波特率就是需要的計數。

Verilog模塊詳解

全部rtl和tb

參考鏈接,建議固定位寬和不需要奇偶校驗,使用此博文中的簡潔描述

tx_clk_gen.v

發送波特率生成模塊,在tx_starttx_done兩信號有效的間隙生成選擇的波特率時鍾信號。思路如上一節所述!

支持的波特率:9600、19200 、38400 、57600 、115200、230400、460800、921600,可由參數配置。

相應Verilog描述:

`timescale 1ns / 1ps
module tx_clk_gen
#(
	parameter	CLK_FREQUENCE	= 50_000_000,		//hz
				BAUD_RATE		= 9600		 		//9600、19200 、38400 、57600 、115200、230400、460800、921600
)
(
	input					clk			,	//system_clk
	input					rst_n		,	//system_reset
	input					tx_done		,	//once_tx_done
	input					tx_start	,	//once_tx_start
	output	reg				bps_clk		 	//baud_rate_clk
);

localparam	BPS_CNT	=	CLK_FREQUENCE/BAUD_RATE-1,
			BPS_WD	=	log2(BPS_CNT);

reg	[BPS_WD-1:0] count;
reg c_state;
reg n_state;
//FSM-1			1'b0:IDLE	1'b1:send_data
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)
		c_state <= 1'b0;
	else
		c_state <= n_state;
end
//FSM-2
always @(*) begin
	case (c_state)
		1'b0: n_state = tx_start ? 1'b1 : 1'b0;
		1'b1: n_state = tx_done ? 1'b0 : 1'b1;
		default: n_state = 1'b0;
	endcase
end
//FSM-3 FSM's output(count_en) is equal to c_state

//baud_rate_clk_counter
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)
		count <= {BPS_WD{1'b0}};
	else if (!c_state)
		count <= {BPS_WD{1'b0}};
	else begin
		if (count == BPS_CNT) 
			count <= {BPS_WD{1'b0}};
		else
			count <= count + 1'b1;
	end
end
//baud_rate_clk_output
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)
		bps_clk <= 1'b0;
	else if (count == 'd1)
		bps_clk <= 1'b1;
	else
		bps_clk <= 1'b0;
end
//get_the_width_of_
function integer log2(input integer v);
  begin
	log2=0;
	while(v>>log2) 
	  log2=log2+1;
  end
endfunction

endmodule

uart_frame_tx.v

數據幀發送模塊,支持通過參數設定波特率、奇偶檢驗位及數據位寬。采用狀態機和移位寄存器實現。當有校驗位時則發送檢驗位;若沒有校驗位則直接發送停止位(發送兩次停止位),如下所示。

`timescale 1ns / 1ps
module uart_frame_tx
#(
	parameter	CLK_FREQUENCE	= 50_000_000,		//hz
				BAUD_RATE		= 9600		,		//9600、19200 、38400 、57600 、115200、230400、460800、921600
				PARITY			= "NONE"	,		//"NONE","EVEN","ODD"
				FRAME_WD		= 8					//if PARITY="NONE",it can be 5~9;else 5~8
)
(
	input						clk			,	//system_clk
	input						rst_n		,	//system_reset
	input						frame_en	,	//once_tx_start
	input		[FRAME_WD-1:0]	data_frame	,	//data_to_tx
	output	reg					tx_done		,	//once_tx_done
	output	reg					uart_tx		 	//uart_tx_data
);

wire	bps_clk;

tx_clk_gen
#(
	.CLK_FREQUENCE	(CLK_FREQUENCE),		//hz
	.BAUD_RATE		(BAUD_RATE	)	 		//9600、19200 、38400 、57600 、115200、230400、460800、921600
)
tx_clk_gen_inst
(
	.clk			( clk		 ),		//system_clk
	.rst_n			( rst_n		 ),		//system_reset
	.tx_done		( tx_done	 ),		//once_tx_done
	.tx_start		( frame_en	 ),		//once_tx_start
	.bps_clk		( bps_clk	 ) 		//baud_rate_clk
);

localparam	IDLE		=	6'b00_0000	,
			READY		=	6'b00_0001	,
			START_BIT	=	6'b00_0010	,
			SHIFT_PRO	=	6'b00_0100	,
			PARITY_BIT	=	6'b00_1000	,
			STOP_BIT	=	6'b01_0000	,
			DONE		=	6'b10_0000	;

wire	[1:0]	verify_mode;
generate
	if (PARITY == "ODD")
		assign verify_mode = 2'b01;
	else if (PARITY == "EVEN")
		assign verify_mode = 2'b10;
	else
		assign verify_mode = 2'b00;
endgenerate

reg		[FRAME_WD-1:0]	data_reg	;
reg		[log2(FRAME_WD-1)-1:0] cnt	;
reg						parity_even	;
reg 	[5:0]			cstate		;
reg		[5:0]			nstate		;

always @(posedge clk or negedge rst_n) begin
	if (!rst_n)
		cnt <= 'd0;
	else if (cstate == SHIFT_PRO & bps_clk == 1'b1) 
		if (cnt == FRAME_WD-1)
			cnt <= 'd0;
		else
			cnt <= cnt + 1'b1;
	else
		cnt <= cnt;
end
//FSM-1
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)
		cstate <= IDLE;
	else
		cstate <= nstate;
end
//FSM-2
always @(*) begin
	case (cstate)
		IDLE		: nstate = frame_en ? READY : IDLE	;
		READY		: nstate = (bps_clk == 1'b1) ? START_BIT : READY;
		START_BIT	: nstate = (bps_clk == 1'b1) ? SHIFT_PRO : START_BIT;
		SHIFT_PRO	: nstate = (cnt == FRAME_WD-1 & bps_clk == 1'b1) ? PARITY_BIT : SHIFT_PRO;
		PARITY_BIT	: nstate = (bps_clk == 1'b1) ? STOP_BIT : PARITY_BIT;
		STOP_BIT	: nstate = (bps_clk == 1'b1) ? DONE : STOP_BIT;
		DONE		: nstate = IDLE;
		default		: nstate = IDLE;
	endcase
end
//FSM-3
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		data_reg <= 'd0;
		uart_tx <= 1'b1;
		tx_done <= 1'b0;
		parity_even <= 1'b0;
	end else begin
		case (nstate)
			IDLE		: begin
							data_reg <= 'd0;
							tx_done <= 1'b0;
							uart_tx <= 1'b1;
						end
			READY		: begin
							data_reg <= 'd0;
							tx_done <= 1'b0;
							uart_tx <= 1'b1;
						end
			START_BIT	: begin
							data_reg <= data_frame;
							parity_even <= ^data_frame;		//生成偶校驗位
							uart_tx <= 1'b0;
							tx_done <= 1'b0;
						end
			SHIFT_PRO	: begin
							if(bps_clk == 1'b1) begin
								data_reg <= {1'b0,data_reg[FRAME_WD-1:1]};
								uart_tx <= data_reg[0];
							end else begin
								data_reg <= data_reg;
								uart_tx <= uart_tx;
							end
							tx_done <= 1'b0;
						end
			PARITY_BIT	: begin
							data_reg <= data_reg;
							tx_done <= 1'b0;
							case (verify_mode)
								2'b00: uart_tx <= 1'b1;		//若無校驗多發一位STOP_BIT
								2'b01: uart_tx <= ~parity_even;
								2'b10: uart_tx <= parity_even;
								default: uart_tx <= 1'b1;
							endcase
						end
			STOP_BIT	: uart_tx <= 1'b1;
			DONE		: tx_done <= 1'b1;
			default		:  begin
							data_reg <= 'd0;
							uart_tx <= 1'b1;
							tx_done <= 1'b0;
							parity_even <= 1'b0;
						end
		endcase
	end
end

function integer log2(input integer v);
  begin
	log2=0;
	while(v>>log2) 
	  log2=log2+1;
  end
endfunction

endmodule

uart_frame_rx.v

數據接收模塊的主要描述如下:

module uart_frame_rx
#(
	parameter	CLK_FREQUENCE	= 50_000_000,		//hz
				BAUD_RATE		= 9600		,		//9600、19200 、38400 、57600 、115200、230400、460800、921600
				PARITY			= "NONE"	,		//"NONE","EVEN","ODD"
				FRAME_WD		= 8					//if PARITY="NONE",it can be 5~9;else 5~8
)
(
	input						clk			,		//sys_clk
	input						rst_n		,		
	input						uart_rx		,		
	output	reg	[FRAME_WD-1:0]	rx_frame	,		//frame_received,when rx_done = 1 it's valid
	output	reg					rx_done		,		//once_rx_done
	output	reg					frame_error	 		//when the PARITY is enable if frame_error = 1,the frame received is wrong
);

wire			sample_clk		;
wire			frame_en		;		//once_rx_start
reg				cnt_en			;		//sample_clk_cnt enable
reg		[3:0]	sample_clk_cnt	;		
reg		[log2(FRAME_WD+1)-1:0]		sample_bit_cnt	;
wire			baud_rate_clk	;

localparam	IDLE		=	5'b0_0000,
			START_BIT	=	5'b0_0001,
			DATA_FRAME	=	5'b0_0010,
			PARITY_BIT	=	5'b0_0100,
			STOP_BIT	=	5'b0_1000,
			DONE		=	5'b1_0000;

reg	[4:0]	cstate;
reg [4:0]	nstate;
//
wire	[1:0]	verify_mode;
generate
	if (PARITY == "ODD")
		assign verify_mode = 2'b01;
	else if (PARITY == "EVEN")
		assign verify_mode = 2'b10;
	else
		assign verify_mode = 2'b00;
endgenerate
//detect the start condition--the negedge of uart_rx
reg		uart_rx0,uart_rx1,uart_rx2,uart_rx3;

always @(posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		uart_rx0 <= 1'b0;
		uart_rx1 <= 1'b0;
		uart_rx2 <= 1'b0;
		uart_rx3 <= 1'b0;
	end else begin
		uart_rx0 <= uart_rx ;
		uart_rx1 <= uart_rx0;
		uart_rx2 <= uart_rx1;
		uart_rx3 <= uart_rx2;
	end
end
//negedge of uart_rx-----start_bit
assign frame_en = uart_rx3 & uart_rx2 & ~uart_rx1 & ~uart_rx0;

always @(posedge clk or negedge rst_n) begin
	if (!rst_n) 
		cnt_en <= 1'b0;
	else if (frame_en) 
		cnt_en <= 1'b1;
	else if (rx_done) 
		cnt_en <= 1'b0;
	else
		cnt_en <= cnt_en;
end

assign baud_rate_clk = sample_clk & sample_clk_cnt == 4'd8;

always @(posedge clk or negedge rst_n) begin
	if (!rst_n) 
		sample_clk_cnt <= 4'd0;
	else if (cnt_en) begin
		if (baud_rate_clk) 
			sample_clk_cnt <= 4'd0;
		else if (sample_clk)
			sample_clk_cnt <= sample_clk_cnt + 1'b1;
		else
			sample_clk_cnt <= sample_clk_cnt;
	end else 
		sample_clk_cnt <= 4'd0;
end
//the start_bit is the first one (0),then the LSB of the data_frame is the second(1) ......
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) 
		sample_bit_cnt <= 'd0;
	else if (cstate == IDLE)
		sample_bit_cnt <= 'd0;
	else if (baud_rate_clk)
		sample_bit_cnt <= sample_bit_cnt + 1'b1;
	else
		sample_bit_cnt <= sample_bit_cnt;
end
//read the readme
reg		[1:0]	sample_result	;
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) 
		sample_result <= 1'b0;
	else if (sample_clk) begin
		case (sample_clk_cnt)
			4'd0:sample_result <= 2'd0;
			4'd3,4'd4,4'd5: sample_result <= sample_result + uart_rx;
			default: sample_result <= sample_result;
		endcase
	end
end
//FSM-1
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) 
		cstate <= IDLE;
	else 
		cstate <= nstate;
end
//FSM-2
always @(*) begin
	case (cstate)
		IDLE		: nstate = frame_en ? START_BIT : IDLE ;
		START_BIT	: nstate = (baud_rate_clk & sample_result[1] == 1'b0) ? DATA_FRAME : START_BIT ;
		DATA_FRAME	: begin
						case (verify_mode[1]^verify_mode[0])
							1'b1: nstate = (sample_bit_cnt == FRAME_WD & baud_rate_clk) ? PARITY_BIT : DATA_FRAME ;		//parity is enable
							1'b0: nstate = (sample_bit_cnt == FRAME_WD & baud_rate_clk) ? STOP_BIT : DATA_FRAME ;		//parity is disable
							default: nstate = (sample_bit_cnt == FRAME_WD & baud_rate_clk) ? STOP_BIT : DATA_FRAME ;	//defasult is disable
						endcase
					end
		PARITY_BIT	: nstate = baud_rate_clk ? STOP_BIT : PARITY_BIT ;
		STOP_BIT	: nstate = (baud_rate_clk & sample_result[1] == 1'b1) ? DONE : STOP_BIT ;
		DONE		: nstate = IDLE;
		default: nstate = IDLE;
	endcase
end
//FSM-3
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		rx_frame	<= 'd0;
		rx_done		<= 1'b0;
		frame_error	<= 1'b0;
	end else begin
		case (nstate)
			IDLE		: begin
							rx_frame	<= 'd0;
							rx_done		<= 1'b0;
							frame_error	<= 1'b0;
						end 
			START_BIT	: begin
							rx_frame	<= 'd0;
							rx_done		<= 1'b0;
							frame_error	<= 1'b0;
						end 
			DATA_FRAME	: begin
							if (sample_clk & sample_clk_cnt == 4'd6) 
								rx_frame <= {sample_result[1],rx_frame[FRAME_WD-1:1]};
							else
								rx_frame	<= rx_frame;
							rx_done		<= 1'b0;
							frame_error	<= 1'b0;
						end 
			PARITY_BIT	: begin
							rx_frame	<= rx_frame;
							rx_done		<= 1'b0;
							if (sample_clk_cnt == 4'd8)
							frame_error	<= ^rx_frame ^ sample_result[1];
							else
							frame_error	<= frame_error;
						end 
			STOP_BIT	: begin
							rx_frame	<= rx_frame;
							rx_done		<= 1'b0;
							frame_error	<= frame_error;
						end 
			DONE		: begin
							frame_error	<= frame_error;
							rx_done		<= 1'b1;
							rx_frame	<= rx_frame;
						end 
			default: begin
							rx_frame	<= rx_frame;
							rx_done		<= 1'b0;
							frame_error	<= frame_error;
						end 
		endcase
	end
end

rx_clk_gen
#(
	.CLK_FREQUENCE	(CLK_FREQUENCE	),	//hz
	.BAUD_RATE		(BAUD_RATE		)	//9600、19200 、38400 、57600 、115200、230400、460800、921600
)
rx_clk_gen_inst
(
	.clk			( clk		 )	,
	.rst_n			( rst_n		 )	,
	.rx_start		( frame_en	 )	,
	.rx_done		( rx_done	 )	,
	.sample_clk	 	( sample_clk )	
);	

function integer log2(input integer v);
  begin
	log2=0;
	while(v>>log2) 
	  log2=log2+1;
  end
endfunction
endmodule

根據uart協議,數據傳輸線空閑時位高電平,數據傳輸以一位低電平的起始位開始,因此准確檢測起始位是數據成功傳輸的關鍵。由於接受端和發送端是異步的,需要專門的邊沿檢測電路來捕捉下降沿。這里采用4個移位寄存器,連續采集4個時鍾上升沿時的數據,通過對比前兩個時刻和后兩個時刻的數據線的狀態來得到數據線准確的下降沿,獲得准確的開始接收條件。

在簡單的串口接收中,我們通常選取一位數據的中間時刻進行采樣,因為此時數據最穩定,但是在工業環境中,存在着各種干擾,在干擾存在的情況下,如果采用傳統的中間時刻采樣一次的方式,采樣結果就有可能受到干擾而出錯。為了濾除這種干擾,這里采用多次采樣求概率的方式。如下圖,將一位數據平均分成9個時間段,對位於中間的三個時間段進行采樣。然后對三個采樣結果進行統計判斷,如果某種電平狀態在三次采樣結果中占到了兩次及以上,則可以判定此電平狀態即為正確的數據電平。例如4、5、6時刻采樣結果分別為1、1、0,那么就取此位解碼結果為1,否則,若三次采樣結果為0、1、0,則解碼結果就為0。即3次采樣為a,b,c,則結果為a&b | b&c |a&c,顯而易見此結果是全加器的進位,參考鏈接

數據采樣示例

rx_clk_gen.v

所以采樣時鍾應該為波特率時鍾的9倍,Verilog描述如下:

`timescale 1ns / 1ps

module rx_clk_gen
#(
	parameter	CLK_FREQUENCE	= 50_000_000,	//hz
				BAUD_RATE		= 9600		 	//9600、19200 、38400 、57600 、115200、230400、460800、921600
)
(
	input					clk			,
	input					rst_n		,
	input					rx_start	,
	input					rx_done		,
	output	reg				sample_clk	 
);

localparam	SMP_CLK_CNT	=	CLK_FREQUENCE/BAUD_RATE/9 - 1,
			CNT_WIDTH	=	log2(SMP_CLK_CNT)			 ;

reg		[CNT_WIDTH-1:0]	clk_count	;
reg		cstate;
reg		nstate;
//FSM-1	1'b0:IDLE 1'b1:RECEIVE
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) begin
		cstate <= 1'b0;
	end else begin
		cstate <= nstate;
	end
end
//FSM-2
always @(*) begin
	case (cstate)
		1'b0: nstate = rx_start ? 1'b1 : 1'b0;
		1'b1: nstate = rx_done ? 1'b0 : 1'b1 ;
		default: nstate = 1'b0;
	endcase
end
//FSM-3 FSM's output(clk_count_en) is equal to cstate

//sample_clk_counter
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) 
		clk_count <= 'd0;
	else if (!cstate) 
		clk_count <= 'd0;
	else if (clk_count == SMP_CLK_CNT)
		clk_count <= 'd0;
	else
		clk_count <= clk_count + 1'b1;
end
//generate sample_clk = 9xBAUD_RATE
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) 
		sample_clk <= 1'b0;
	else if (clk_count == 1'b1) 
		sample_clk <= 1'b1;
	else 
		sample_clk <= 1'b0;
end
//get the width of sample_clk_counter
function integer log2(input integer v);
  begin
	log2=0;
	while(v>>log2) 
	  log2=log2+1;
  end
endfunction

endmodule

總結

顧及的功能太多,比如奇偶校驗,位寬設定等,最終的描述不簡潔。但是功能基本都實現了,把思路和代碼沉淀在這里。Verilog和本文多處借鑒他人成果,都已給出參考鏈接,侵刪。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM