FPGA學習之路(六)之通用非同步收發傳輸器(UART)搭建

2020-08-13 10:28:13

寫在前面

之前在數電課上也有用NE555+CD4017構成的純數位電路搭過一個baud=4800的RS232協定的串列埠(原理圖見下圖),感覺還是蠻好玩的。於是今天今天用FPGA搭一下試試。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

RS232協定說明

一些客套介紹話

在下面 下麪的RS232字元發生器的設計開始之前,首先介紹RS232協定的規範。因爲這對整個RS232字元發生器的設計非常重要。
RS-232是美國電子工業聯盟(EIA)開發的序列數據通訊介面標準。原始全名爲EIA-RS-232(簡稱232、RS232)。它廣泛應用於計算機序列介面外圍裝置連線.RS-232C標準,其中EIA(電子工業協會)代表美國電子工業協會,RS(推薦標準)代表推薦標準,232爲識別號,C代表RS232(1969)第三次修訂版。有RS232B,RS232A。

同樣,這些介紹對寫程式沒啥用。作爲程式猿,我更希望知道它的時序圖/協定格式。
在这里插入图片描述
如上爲RS232協定中一幀數據的格式
以下解釋下圖上的幾個關鍵資訊:
0.每幀數據有1位起始位
1.每幀數據有5/6/7/8位元數據位
2.LSB最低有效位先出
3.1bit/2bit停止位
4.NONE/奇偶校驗停止位

關於波特率(Baud)

波特率指得是一秒鐘發送bit的個數。
板子上的是50M的晶振,如果要產生9600的波特率。最簡單的計算和處理方法是50M/9600=5208得到 在9600波特率下每發一個bit需要計數5208次。
一般在採樣信號時,我們會採用16倍採樣/64倍採樣來保證信號可靠。經過這樣設計後,採樣率爲5208倍,可以說是非常可靠了QAQ。還有,在採樣的時候,我們一般在信號中點進行採樣。這樣操作的原因,也是爲了保證信號的可靠。

Verilog

RX

`timescale 1ns / 1ps

module uart_rx_path(
	input clk_i,
	input uart_rx_i,
	
	output [7:0] uart_rx_data_o,
	output uart_rx_done,
	output baud_bps_tb			//for simulation
    );
    
parameter [12:0] BAUD_DIV     = 13'd5208;   //波特率時鐘,9600bps,50Mhz/9600=5208 (1bit信號在50M時鐘下計數(採樣)5208次)
parameter [12:0] BAUD_DIV_CAP = 13'd2604;   //波特率時鐘中間採樣點,50Mhz/9600/2=2604 

reg [12:0] baud_div=0;				          //波特率設定計數器
reg baud_bps=0;					              //數據採樣點信號
reg bps_start=0;					          //波特率啓動標誌
always@(posedge clk_i)
begin
	if(baud_div==BAUD_DIV_CAP)	    	     //當波特率計數器計數到採樣點時,產生採樣信號baud_bps
		begin
			baud_bps<=1'b1;
			baud_div<=baud_div+1'b1;
		end
	else if(baud_div<BAUD_DIV && bps_start)  //當波特率計數器啓動時,計數器累加
		begin
			baud_div<=baud_div+1'b1;
			baud_bps<=0;
		end
	else
		begin
			baud_bps<=0;
			baud_div<=0;
		end
end

reg [4:0] uart_rx_i_r=5'b11111;			    //數據接收快取器
always@(posedge clk_i)
begin
	uart_rx_i_r<={uart_rx_i_r[3:0],uart_rx_i};
end
//數據接收快取器,當連續接收到五個低電平時,即uart_rx_int=0時,作爲接收到起始信號
wire uart_rx_int=uart_rx_i_r[4] | uart_rx_i_r[3] | uart_rx_i_r[2] | uart_rx_i_r[1] | uart_rx_i_r[0];

reg [3:0] bit_num=0;	      //接收數據個數計數器
reg uart_rx_done_r=0;	      //數據接收完成暫存器
reg state=1'b0;

reg [7:0] uart_rx_data_o_r0=0;//數據接收過程中,數據快取器
reg [7:0] uart_rx_data_o_r1=0;//數據接收完成,數據暫存器

always@(posedge clk_i)
begin
	uart_rx_done_r<=1'b0;
	case(state)
		1'b0 : 
			if(!uart_rx_int)//當連續接收到五個低電平時,即uart_rx_int=0時,作爲接收到起始信號,啓動波特率時鐘
				begin
					bps_start<=1'b1;
					state<=1'b1;
				end
		1'b1 :			
			if(baud_bps)	//每次等待波特率採樣中心時,接收數據,放入數據快取器中
				begin
					bit_num<=bit_num+1'b1;
					if(bit_num<4'd9)	//接收1bit起始信號,8bit有效信號,1bit結束信號
						uart_rx_data_o_r0[bit_num-1]<=uart_rx_i;
				end
			else if(bit_num==4'd10) //接收完成時候,接收數據個數計數器清零,產生接收完成標誌位,並將數據寫入數據暫存器,關閉波特率時候
				begin
					bit_num<=0;
					uart_rx_done_r<=1'b1;
					uart_rx_data_o_r1<=uart_rx_data_o_r0;
					state<=1'b0;//進入狀態0,再次回圈檢測
					bps_start<=0;
				end
		default:;
	endcase
end

assign baud_bps_tb=baud_bps;//for simulation
assign uart_rx_data_o=uart_rx_data_o_r1;		
assign uart_rx_done=uart_rx_done_r;
endmodule

TX

`timescale 1ns / 1ps

module uart_tx_path(
	input clk_i,

	input [7:0] uart_tx_data_i,	         //待發送數據
	input uart_tx_en_i,			         //發送發送使能信號
	
	output uart_tx_o
);

parameter BAUD_DIV     = 13'd5208;      //波特率時鐘,9600bps,50Mhz/9600=5208,(1bit信號在50M時鐘下計數(採樣)5208次)
parameter BAUD_DIV_CAP = 13'd2604;      //波特率時鐘中間採樣點,50Mhz/9600/2=2604

reg [12:0] baud_div=0;			         //波特率設定計數器
reg baud_bps=0;				             //數據發送點信號,高有效
(* MARKDEBUG = "TRUE" *)reg [9:0] send_data=10'b1111111111;     //待發送數據暫存器,1bit起始信號+8bit有效信號+1bit結束信號
(* MARKDEBUG = "TRUE" *)reg [3:0] bit_num=0;	                //發送數據個數計數器
reg uart_send_flag=0;	                //數據發送標誌位
reg uart_tx_o_r=1;		                //發送數據暫存器,初始狀態位高

always@(posedge clk_i)
begin
	if(baud_div==BAUD_DIV_CAP)	        //當波特率計數器計數到數據發送中點時,產生採樣信號baud_bps,用來發送數據
		begin
			baud_bps<=1'b1;
			baud_div<=baud_div+1'b1;
		end
	else if(baud_div<BAUD_DIV && uart_send_flag)//數據發送標誌位有效期間,波特率計數器累加,以產生波特率時鐘
		begin
			baud_div<=baud_div+1'b1;
			baud_bps<=0;	
		end
	else
		begin
			baud_bps<=0;
			baud_div<=0;
		end
end

always@(posedge clk_i)
begin
	if(uart_tx_en_i)	//接收數據發送使能信號時,產生數據發送標誌信號
		begin
			uart_send_flag<=1'b1;
			send_data<={1'b1,uart_tx_data_i,1'b0};//待發送數據暫存器裝填,1bit起始信號0+8bit有效信號+1bit結束信號
		end
	else if(bit_num==4'd10)	//發送結束時候,清楚發送標誌信號,並清楚待發送數據暫存器內部信號
		begin
			uart_send_flag<=1'b0;
			send_data<=10'b1111_1111_11;
		end
end

always@(posedge clk_i)
begin
	if(uart_send_flag)	//發送有效時候
		begin
			if(baud_bps)//檢測發送點信號
				begin
					if(bit_num<=4'd9)
						begin
							uart_tx_o_r<=send_data[bit_num];	//發送待發送暫存器內數據,從低位到高位
							bit_num<=bit_num+1'b1;
						end
				end
			else if(bit_num==4'd10)
				bit_num<=4'd0;
		end
	else
		begin
			uart_tx_o_r<=1'b1;	//空閒狀態時,保持發送端位高電平,以備發送時候產生低電平信號
			bit_num<=0;
		end
end

assign uart_tx_o=uart_tx_o_r;

endmodule

UART Module

`timescale 1ns / 1ps

module Test05_project_CP2102_UART (
    input sys_clk,
    input sys_rst_n,
    input uart_rx,
    output uart_tx
    );

wire [7:0] uart_rx_data_o;
wire uart_rx_done;

uart_rx_path uart_rx_path_u (
    .clk_i(sys_clk), 
    .uart_rx_i(uart_rx), 
    .uart_rx_data_o(uart_rx_data_o), 
    .uart_rx_done(uart_rx_done)
    );

uart_tx_path uart_tx_path_u (
    .clk_i(sys_clk), 
    .uart_tx_data_i(uart_rx_data_o), 
    .uart_tx_en_i(uart_rx_done), 
    .uart_tx_o(uart_tx)
    );

endmodule