UART 通信 协议 (一)


前言

        UART,Universal Asynchronous Receiver/Transmitter,通用异步收发器。是一种最为简单的通信协议。在发送数据时,将并行的数据转为串行;数据接收时,将串行数据转为并行数据。UART 可以实现全双工通信。本文将以FPGA作为硬件平台,通过编程实现,UART 数据发送。包括功能仿真、板级验证。

        设计平台:Vivado IDE 2018.3

        硬件平台:武汉芯路恒科技ACX720开发板

        (建议PC端查看)


一、UART 协议

        UART 通信协议包括有很多,RS232、RS449、RS423、RS422、RS485等,他们各自规定了通信口的电气特性、传输速率、连接特性、接口机械特性等。本文主要介绍RS232串行数据通信的接口标准,广泛应用在计算机串行接口外设。针脚定义和功能如下:

         功能说明:

针脚功能
针脚序号功能名称英文全称意义
Pin1DCDData Carrier Detect数据载波检测
Pin2RXDReceive Data数据接收线
Pin3TXDTransmit Data数据发送线
Pin4DTRData Terminal Ready电脑准备好
Pin5GNDGround地线
Pin6DSRData Set Ready调制解调器准备好
Pin7RTSRequest To Send电脑要求调制解调器提交数据
Pin8CTSClear To Send调制解调器通知电脑传数据
Pin9RIRing Indicator调制解调器通知电脑有振铃

UART 协议要点:

  1. 数据位:UART 通信时,传输的数据位数;一般可选择:5、6、7、8位;
  2. 波特率:通信的收发设备双方传输数据的速率,单位:bps;常用的波特率:300、1200、2400、9600、19200、115200bps;收发双方的通信波特率要彼此匹配(相等)。
  3. 奇偶校验位:在数据后跟随一位奇偶校验位,以在接收端判断传输差错情况。奇校验,即增加一位校验位,使得1的个数为奇数个;偶校验,即增加一位校验位,使得1的个数为偶数个。数据位数等于8时,无校验位。
  4. 停止位:数据位发送完后,发送停止位。

RS232标准中,常用的配置是8位数据加1位停止位。时序图为:

二、编程实现

        模块总体设计,根据设计的功能需求,设计一个RS232标准的 UART 发送模块。

 输入输出说明:

UART 发送模块的输入输出说明
名称方向位宽含义
I_CLKI1时钟信号(50MHz)
I_Rst_nI1复位信号,低电平有效(异步复位)
I_VALIDI1输入有效信号,有效时表示输入的数据可进行传输
I_BAUD_SELECTI3波特率选择
I_DATAI8待传输数据
O_FINISHO1高电平有效,有效时表示,一次发送完成
O_DATAO1输出的串行数据
O_STATEO1发送模块的状态,空闲时为0

 波特率选择:

波特率选择
I_BAUD_SELECT波特率(bps)
0300
11200
22400
39600
419200
538400
657600
7115200

设计程序:

module 		UART_TX 		(

//--------------------输入端口列表-----------------------//

input									I_CLK,
input									I_Rst_n,
input									I_VALID,//注意 有效信号要伴随 有效的数据信号 一同送入 ,是 *窄脉冲* 信号
input				[7:0]				I_DATA,
input				[2:0]				I_BAUD_SELECT,
    

//--------------------输出端口列表-----------------------//

output		reg							O_STATE,
output		reg							O_DATA,
output		reg							O_FINISH	
    
);

//------------------内部参数、变量定义-------------------//
//波特率定义
localparam	BAUD_300		= 					18'd166667-1;
localparam	BAUD_1200		= 					18'd41667-1;
localparam	BAUD_2400		= 					18'd20833-1;
localparam	BAUD_9600		= 					18'd5208-1;
localparam	BAUD_19200		= 					18'd2604-1;
localparam	BAUD_38400		= 					18'd1302-1;
localparam	BAUD_57600		= 					18'd868-1;
localparam	BAUD_115200		= 					18'd434-1;

//计数器相关
reg				 	[17:0]				R_COUNTER_MAX;
reg				 	[17:0]				R_COUNTER;//为产生 波特率时钟所用的计数器

reg				 	[3:0]				R_BIT_CONUTER;//记录 传输的 BIT数
//波特率时钟
reg				 						R_BAUD_CLK;

//输入寄存
reg				 	[7:0]				R_I_DATA;


//--------------------模块的程序设计---------------------//

//保证数据稳定性,对输入数据寄存(打一拍)
always @ (posedge I_CLK or negedge I_Rst_n)
    begin
        if(!I_Rst_n)
        	begin
        		R_I_DATA <= 0;
        	end
        else
            begin
                if (I_VALID) 
                    begin
                        R_I_DATA <= I_DATA;
                    end
                else 
                    begin
                        R_I_DATA <= R_I_DATA;
                    end           
            end              
    end

//波特率选择
always @ (posedge I_CLK or negedge I_Rst_n)
    begin
        if(!I_Rst_n)
        	begin
        		R_COUNTER_MAX <= 0;
        	end
        else 
        	begin
        	    case (I_BAUD_SELECT) 
        	        0:
        	            begin
        	                R_COUNTER_MAX <= BAUD_300;
        	            end
        	        1:
        	            begin
        	                R_COUNTER_MAX <= BAUD_1200;
        	            end
        	        2:
        	            begin
        	                R_COUNTER_MAX <= BAUD_2400;
        	            end
        	        3:
        	            begin
        	                R_COUNTER_MAX <= BAUD_9600;
        	            end
        	        4:
        	            begin
        	                R_COUNTER_MAX <= BAUD_19200;
        	            end
        	        5:
        	            begin
        	                R_COUNTER_MAX <= BAUD_38400;
        	            end
        	        6:
        	            begin
        	                R_COUNTER_MAX <= BAUD_57600;
        	            end
        	        7:
        	            begin
        	                R_COUNTER_MAX <= BAUD_115200;
        	            end                   
        	        default:
        	            begin
        	                R_COUNTER_MAX <= BAUD_9600;
        	            end
        	    endcase
        	end        
    end

//生成波特率时钟
always @ (posedge I_CLK or negedge I_Rst_n)
    begin
        if(!I_Rst_n)
        	begin
        		R_COUNTER <= 0;
        	end
        else if(O_STATE) 
        	begin
        	    if (R_COUNTER == R_COUNTER_MAX) 
        	        begin
        	            R_COUNTER <= 0;
        	        end
        	    else 
        	    	begin
        	    	    R_COUNTER <= R_COUNTER + 1;
        	    	end   
        	end
        else 
            begin
                R_COUNTER <= 0;
            end
        
    end
//脉冲宽度为 1个时钟周期 的窄脉冲
always @ (posedge I_CLK or negedge I_Rst_n)
    begin
        if(!I_Rst_n)
        	begin
        		R_BAUD_CLK <= 0;
        	end
        else 
        	begin
        	    if (R_COUNTER == 1) 
        	        begin
        	            R_BAUD_CLK = 1;
        	        end
        	    else 
        	    	begin
        	    	    R_BAUD_CLK = 0;
        	    	end    
        	end       
    end

//波特率时钟的脉冲计数器
always @ (negedge I_CLK or negedge I_Rst_n)
    begin
        if(!I_Rst_n)
        	begin
        		R_BIT_CONUTER <= 0;
        	end
        else 
        	begin
        	    if (R_BIT_CONUTER == 4'd11) 
        	        begin
        	            R_BIT_CONUTER <= 0;
        	        end
        	    else if(R_BAUD_CLK)
        	            begin
        	                R_BIT_CONUTER <= R_BIT_CONUTER + 1;
        	            end
        	    else 
        	        begin
        	            R_BIT_CONUTER <= R_BIT_CONUTER;
        	        end                
        	end       
    end

//发送完成 标志信号
always @ (posedge I_CLK or negedge I_Rst_n)
    begin
        if(!I_Rst_n)
        	begin
        		O_FINISH <= 0;
        	end
        else 
        	begin
        	    if (R_BIT_CONUTER == 4'd11) 
        	        begin
        	            O_FINISH <= 1;
        	        end
        	    else 
        	        begin
        	            O_FINISH <= 0;
        	        end        	            
        	end        
    end

//模块工作的 状态信号
always @ (posedge I_CLK or negedge I_Rst_n)
    begin
        if(!I_Rst_n)
        	begin
        		O_STATE <= 0;
        	end
        else if(I_VALID)
        	begin
        	    O_STATE <= 1;
        	end
        else if(R_BIT_CONUTER == 4'd11)
            begin
                O_STATE <= 0;
            end
        else 
            begin
                O_STATE <= O_STATE;            
            end                
    end

//UART 串行输出 先发低位
always @ (posedge I_CLK or negedge I_Rst_n)
    begin
        if(!I_Rst_n)
        	begin
        		O_DATA <= 1;
        	end
        else 
        	begin
        	    case (R_BIT_CONUTER) 
        	        1:
        	            begin
        	                O_DATA <= 0;//起始位
        	            end
        	        2:
        	            begin
        	                O_DATA <= R_I_DATA[0];
        	            end
        	        3:
        	            begin
        	                O_DATA <= R_I_DATA[1];
        	            end
        	        4:
        	            begin
        	                O_DATA <= R_I_DATA[2];
        	            end
        	        5:
        	            begin
        	                O_DATA <= R_I_DATA[3];
        	            end
        	        6:
        	            begin
        	                O_DATA <= R_I_DATA[4];
        	            end
        	        7:
        	            begin
        	                O_DATA <= R_I_DATA[5];
        	            end
        	        8:
        	            begin
        	                O_DATA <= R_I_DATA[6];
        	            end
        	        9:
        	            begin
        	                O_DATA <= R_I_DATA[7];
        	            end
        	        10:
        	            begin
        	                O_DATA <= 1;//停止位
        	            end
        	        default:
        	            begin
        	                O_DATA <= 1;
        	            end
        	    endcase       	    
        	end       
    end

endmodule

三、仿真验证

编写 testbench 进行行为级仿真:

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

//-----------------被测试模块输入接口声明--------------------//

reg									I_CLK;
reg									I_Rst_n;
reg									I_VALID;
reg				[7:0]				I_DATA;
reg				[2:0]				I_BAUD_SELECT;
	

//----------------被测试模块的输出接口声明-------------------//

wire								O_STATE;
wire								O_DATA;
wire								O_FINISH;	
    
//-----------------------测试程序---------------------------//
//产生激励时钟
`define			CLK_PERIOD		20	
initial 		I_CLK			=	0 ;
always #(`CLK_PERIOD/2) I_CLK	=	~ I_CLK;

//初始化控制信号
	initial
		begin
			I_Rst_n = 0;
			I_DATA = 8'b0;
			I_VALID = 0;
			I_BAUD_SELECT = 3'd3;
			#(`CLK_PERIOD * 10 +2);
			I_Rst_n = 1;
			#(`CLK_PERIOD * 10);

			//发送数据 1
			I_DATA = 8'b0110_1001;
			I_VALID = 1;
			#(`CLK_PERIOD * 1);
			I_VALID = 0;

			@(posedge O_FINISH);
			#(`CLK_PERIOD * 10);

			//发送数据 2
			I_DATA = 8'b1110_1011;
			I_VALID = 1;
			#(`CLK_PERIOD * 1);
			I_VALID = 0;

			@(posedge O_FINISH);
			#(`CLK_PERIOD * 10);
			$stop;			
		end

//被测试模块例化

UART_TX inst_UART_TX
	(
		.I_CLK         (I_CLK),
		.I_Rst_n       (I_Rst_n),
		.I_VALID       (I_VALID),
		.I_DATA        (I_DATA),
		.I_BAUD_SELECT (I_BAUD_SELECT),
		.O_STATE       (O_STATE),
		.O_DATA        (O_DATA),
		.O_FINISH      (O_FINISH)
	);

endmodule

仿真波形:

四、板级验证

板级验证的基本思路:用VIO IP 核控制 UART_TX 模块。首先生成 VIO IP核。

1、产生、配置 VIO IP 核

 

 2、例化 VIO IP ,编写顶层文件

 顶层文件:

module 		TOP_UART_TX_TEST 		(

//--------------------输入端口列表-----------------------//

input									I_CLK,
input									I_Rst_n,
    

//--------------------输出端口列表-----------------------//

output									O_DATA,
output									O_STATE	//用板子上的 LED 指示
    
);

//------------------内部参数、变量定义-------------------//

wire									I_VALID;
wire			[7:0]					I_DATA;

wire									W_VALID;

reg				 						R_1_VALID;
reg				 						R_2_VALID;

//--------------------模块的程序设计---------------------//
//上升沿检测
always @ (posedge I_CLK)
    begin
        R_1_VALID <= W_VALID;
        R_2_VALID <= R_1_VALID;
    end

assign	I_VALID		= 	R_1_VALID & !R_2_VALID;

//例化 VIO IP
VIO_CTRL_UART_TX your_instance_name (
  .clk(I_CLK),               	// input wire clk
  .probe_out0(W_VALID),  		// output wire [0 : 0] probe_out0
  .probe_out1(I_DATA)  			// output wire [7 : 0] probe_out1
);

//例化 UART_TX 模块
UART_TX inst_UART_TX
	(
		.I_CLK         (I_CLK),
		.I_Rst_n       (I_Rst_n),
		.I_VALID       (I_VALID),
		.I_DATA        (I_DATA),
		.I_BAUD_SELECT (3'b011),//波特率 9600
		.O_STATE       (O_STATE),
		.O_DATA        (O_DATA),
		.O_FINISH      ()
	);

endmodule

3、综合

 打开综合后的网表

 设置IO规划

 分配引脚、电平

 实现

 生成比特流

打开硬件管理器

连接仿真器、设备上电

打开串口接收:

参考说明

[1].小梅哥 Xilinx FPGA 自学教程 v2.0

[2].博客-1

[3].博客-2