MCU集成-常用接口uart

uart

1、什么是UART?
        UART全称通用异步收发传输器,主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。在异步串口的传输中是不需要时钟的,而是通过特定的时序来标志传输的开始(起始位--由高到低)和结束(结束位,拉高)。一个uart模块中包含了发射模块和接收模块,所以芯片中的uart模块既可以用来接收数据,又可以用来发射数据。

2、串口的组成
2.1、串口的物理层
        UART 通信只有两根信号线, tx和 rx,如图所示,对于 PC 来说它的 tx 要和对于 FPGA来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接。

b98b01d2a3084af2a2a424a1206546ca.png

2.2、UART协议
        UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位(可选)和停止位,如图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。

935945645b7c4f569737861a30cde564.png

        校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时, 对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查1的个数是否为偶数。 

        举例,要输出一组8-bit数据,为“11101000”,该组数据共有4个“1”。若使用奇校验,则校验位应为1,传输数据实际为8-bit数据+1-bit奇校验位,即“111010001”;若使用偶校验,则校验位应为0,传输数据实际为8-bit数据+1-bit偶校验位,即“111010000”。(奇偶校验容易忘记,增加到《总结我的遗忘知识点》这一个文章中

        UART通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为5、6、7、8位,其中8位数据位是最常用的,在实际应用中一般都选择8位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认), 1.5或2位。

        串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位 /秒),常用的波特率有9600、19200、38400、57600以及115200等。如波特率9600则代表每秒传输9600bit数据,以串口发送1个字节10bit算(起始位1bit+数据8bit+停止位1bit+NO校验位),则传输1个字节需要的时间是1*10/9600秒。 

uart发送模块

        需要说明的是,uart_tx_data为需要发送的一个字节的数据,uart_tx_en为发送使能位,当其拉高,则代表此时通过串口发送数据线发送数据uart_tx_data。

3.2、设计思路

        一帧数据:数据位8位,起始位和停止位各1位,无奇偶校验

假设波特率为9600,则发送一个bit的时间为1s/9600,一个数据的传输共10bit(数据位8位,起始位和停止位各1位),则共需要1s/9600;假设系统时钟为50MHz,则其周期为20ns,那么发送一个bit所需要的系统周期数为(1s/9600)/ 20ns ≈ 5208(个)。在发送过程中使用一个计数器计数,计数区间为(0~5208-1),这样的区间一共10个(一个字节需要发送10个bit);此外还需一个计数器对发送的bit数计数(每当上一个计数器计数到5207则表示发送完了一个bit),计数区间(0~9)
在发送过程,根据计数器的值(发送bit计数器),对发送数据线进行操作。
若发送bit计数器 = 0,则代表此时需要发送起始位;
若发送bit计数器 = 1,则代表此时需要发送发送数据的最低位LSB(数据的发送总是低位在前,高位在后);
······
若发送bit计数器 = 8,则代表此时需要发送发送数据的最高位MSB;
若发送bit计数器 = 9,则代表此时需要发送停止位;
发送数据线在不处于发送状态时需拉高,以满足UART时序的空闲状态

module uart_tx
#(
	parameter	integer	BPS		= 9_600		,	//发送波特率
	parameter 	integer	CLK_FRE	= 50_000_000	//主时钟频率
)
(
//系统接口
	input 			sys_clk			,			//系统时钟
	input 			sys_rst_n		,			//系统复位,低电平有效
//用户接口	
	input	[7:0] 	uart_tx_data	,			//需要通过UART发送的数据,在uart_tx_en为高电平时有效
	input			uart_tx_en		,			//发送有效,当其为高电平时,代表此时需要发送的数据有效
//UART发送	
	output	reg		uart_tx_done	,			//成功发送1BYTE数据后拉高一个周期
	output 	reg		uart_txd					//UART发送数据线tx
);
 
//param define
localparam	integer	BPS_CNT  = CLK_FRE / BPS;	//根据波特率计算传输每个bit需要计数多个系统时钟
localparam	integer	BITS_NUM = 10			;	//发送格式确定需要发送的bit数,10bit = 1起始位 + 8数据位 + 1停止位
 
//reg define
reg 		tx_state			;				//发送标志信号,拉高代表发送过程正在进行
reg [7:0]  	uart_tx_data_reg	;				//寄存要发送的数据
reg [31:0] 	clk_cnt				;				//计数器,用于计数发送一个bit数据所需要的时钟数
reg [3:0]  	bit_cnt				;				//bit计数器,标志当前发送了多少个bit
 
 
//当发送使能信号到达时,寄存待发送的数据以免后续变化、丢失
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		uart_tx_data_reg <=8'd0;
	else if(uart_tx_en)							//要发送有效的数据
		uart_tx_data_reg <= uart_tx_data;		//寄存需要发送的数据			
	else 
		uart_tx_data_reg <= uart_tx_data_reg;
end		
 
//当发送使能信号到达时,进入发送过程
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		tx_state <=1'b0;	
	else if(uart_tx_en)												
		tx_state <= 1'b1;						//发送信号有效则进入发送过程
	//发送完了最后一个数据则退出发送过程		
	else if((bit_cnt == BITS_NUM - 1'b1) && (clk_cnt == BPS_CNT - 1'b1))		
		tx_state <= 1'b0;                                          		
	else 
		tx_state <= tx_state;	
end
 
//发送数据完毕后拉高发送完毕信号一个周期,指示一个字节发送完毕
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		uart_tx_done <=1'b0;
	//发送数据完毕后拉高发送完毕信号一个周期 		
	else if((bit_cnt == BITS_NUM - 1'b1) && (clk_cnt == BPS_CNT - 1'b1))	                                         	
		uart_tx_done <=1'b1;										
	else 
		uart_tx_done <=1'b0;
end
 
//进入发送过程后,启动时钟计数器与发送个数bit计数器
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		clk_cnt <= 32'd0;
		bit_cnt <= 4'd0;
	end
	else if(tx_state) begin										//在发送状态
		if(clk_cnt < BPS_CNT - 1'd1)begin						//一个bit数据没有发送完
			clk_cnt <= clk_cnt + 1'b1;							//时钟计数器+1
			bit_cnt <= bit_cnt;									//bit计数器不变
		end					
		else begin												//一个bit数据发送完了	
			clk_cnt <= 32'd0;									//清空时钟计数器,重新开始计时
			bit_cnt <= bit_cnt+1'b1;							//bit计数器+1,表示发送完了一个bit的数据
		end					
	end					
	else begin													//不在发送状态
		clk_cnt <= 32'd0;                   					//清零
		bit_cnt <= 4'd0;                    					//清零
	end
end
 
//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		uart_txd <= 1'b1;										//默认为高状态,空闲状态
	else if(tx_state)                                  			//处于发送状态
		case(bit_cnt)											//数据发送从低位到高位
			4'd0: uart_txd <= 1'b0;								//起始位,拉低
			4'd1: uart_txd <= uart_tx_data_reg[0];     			//发送最低位数据
			4'd2: uart_txd <= uart_tx_data_reg[1];     			//
			4'd3: uart_txd <= uart_tx_data_reg[2];     			//
			4'd4: uart_txd <= uart_tx_data_reg[3];     			//
			4'd5: uart_txd <= uart_tx_data_reg[4];     			//
			4'd6: uart_txd <= uart_tx_data_reg[5];     			//
			4'd7: uart_txd <= uart_tx_data_reg[6];     			//
			4'd8: uart_txd <= uart_tx_data_reg[7];     			//发送最高位数据
			4'd9: uart_txd <= 1'b1;								//终止位,拉高
			default:uart_txd <= 1'b1;			
		endcase			
	else 														//不处于发送状态
		uart_txd <= 1'b1;										//默认为高状态,空闲状态
end
 
endmodule

uart接收模块

设计思路:需要对应其发送模块确定数据多少位,有无校验码之类的问题,这都是传输之前需要商量好的。
        数据位8位,起始位和停止位各1位,无奇偶校验
        串口的传输是以起始位开始的,而起始位是将数据线拉低 ,所以我们需要捕捉数据线的下降沿,将接收数据线打拍3次,捕捉其下降沿。当捕捉到接收数据线的下降沿,拉高接收标志信号,标志模块进入接收过程;当接收完10个bit后,拉低接收标志信号,标志接收过程结束
假设波特率为9600,则传输一个bit的时间为1s/9600,一个数据的传输共10bit(数据位8位,起始位和停止位各1位),则共需要1s/960;假设系统时钟为50MHz(参数化以便适应不同的系统频率),则其周期为20ns,那么传输一个bit所需要的系统周期数为(1s/960)/ 20ns ≈ 5208(个)。在接收过程中使用一个计数器计数,计数区间为(0~5208-1),这样的区间一共10个(一个字节需要传输10个bit);此外还需一个计数器对接收的bit数计数(每当上一个计数器计数到5207则表示接收完了一个bit),计数区间(0~9)。
在接收过程,根据计数器的值(接收bit计数器),在每个bit计数器的中间接收数据,将其移位寄存(在电平中间数据最稳定)
若接收bit计数器 = 0,则代表是起始位,不需要接收
若接收bit计数器 = 1,则代表此时接收到数据的最低位LSB(数据的传输总是低位在前,高位在后),将其赋值给寄存数据的最低位;
······
若接收bit计数器 = 8,则代表此时接收到数据的最高位MSB,将其赋值给寄存数据的最高位;
若接收bit计数器 = 9,则代表是停止位,不需要接收。

module uart_rx
#(
	parameter	integer	BPS		= 9_600		,		//发送波特率
	parameter 	integer	CLK_FRE	= 50_000_000		//输入时钟频率
)	
(	
//系统接口
	input 				sys_clk			,			//50M系统时钟
	input 				sys_rst_n		,			//系统复位
//UART接收线	
	input 				uart_rxd		,			//接收数据线
//用户接口	
	output reg 			uart_rx_done	,			//数据接收完成标志,当其为高电平时,代表接收数据有效
	output reg [7:0]	uart_rx_data				//接收到的数据,在uart_rx_done为高电平时有效
);
 
//param define
localparam	integer	BPS_CNT = CLK_FRE / BPS;		//根据波特率计算传输每个bit需要多个系统时钟	
//reg define
reg 			uart_rx_d1		;					//寄存1拍
reg 			uart_rx_d2		;					//寄存2拍
reg 			uart_rx_d3		;					//寄存3拍
reg [31:0]		clk_cnt			;					//计数器,用于计数发送一个bit数据所需要的时钟数
reg [3:0]  		bit_cnt			;					//bit计数器,标志当前发送了多少个bit
reg 			rx_en			;					//接收标志信号,拉高代表接收过程正在进行
reg [7:0]		uart_rx_data_reg;					//接收数据寄存
//wire define				
wire 			neg_uart_rxd	;					//接收数据线的下降沿
 
assign	neg_uart_rxd = uart_rx_d3 & (~uart_rx_d2);	//捕获数据线的下降沿,用来标志数据传输开始
 
//将数据线打3拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:捕获下降沿
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		uart_rx_d1 <= 1'b0;
		uart_rx_d2 <= 1'b0;
		uart_rx_d3 <= 1'b0;
	end
	else begin
		uart_rx_d1 <= uart_rxd;
		uart_rx_d2 <= uart_rx_d1;
		uart_rx_d3 <= uart_rx_d2;
	end		
end
 
//捕获到数据下降沿(起始位0)后,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		rx_en <= 1'b0;
	else begin 
		if(neg_uart_rxd )								
			rx_en <= 1'b1;
		//接收完第9个数据(终止位)将传输开始标志位拉低,标志传输结束,判断高电平
		else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'b1) && (uart_rx_d3 == 1'b1) )
			rx_en <= 1'b0;
		else 
			rx_en <= rx_en;			
	end
end
 
//当数据传输到终止位时,拉高传输完成标志位,并将数据输出
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		uart_rx_done <= 1'b0;
		uart_rx_data <= 8'd0;
	end	
	//结束接收后,将接收到的数据输出
	else if((bit_cnt == 4'd9) && (clk_cnt == BPS_CNT >> 1'd1) && (uart_rx_d3 == 1'b1))begin		
		uart_rx_done <= 1'b1;									//仅仅拉高一个时钟周期
		uart_rx_data <= uart_rx_data_reg;	
	end							
	else begin					
		uart_rx_done <= 1'b0;									//仅仅拉高一个时钟周期
		uart_rx_data <= uart_rx_data;
	end
end
 
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		bit_cnt <= 4'd0;
		clk_cnt <= 32'd0;
	end
	else if(rx_en)begin					            			//在接收状态
		if(clk_cnt < BPS_CNT - 1'b1)begin           			//一个bit数据没有接收完
			clk_cnt <= clk_cnt + 1'b1;              			//时钟计数器+1
			bit_cnt <= bit_cnt;                     			//bit计数器不变
		end                                         			
		else begin                                  			//一个bit数据接收完了	
			clk_cnt <= 32'd0;                       			//清空时钟计数器,重新开始计时
			bit_cnt <= bit_cnt + 1'b1;              			//bit计数器+1,表示接收完了一个bit的数据
		end                                         			
	end                                             			
		else begin                                  			//不在接收状态
			bit_cnt <= 4'd0;                        			//清零
			clk_cnt <= 32'd0;                       			//清零
		end		
end
 
//在每个数据的传输过程正中(数据比较稳定)将数据线上的数据赋值给数据寄存器
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		uart_rx_data_reg <= 8'd0;                            	//复位无接收数据
	else if(rx_en)                                           	//处于接收状态
		if(clk_cnt == BPS_CNT >> 1'b1) begin                 	//传输过程正中(数据比较稳定)
			case(bit_cnt)			                         	//根据位数决定接收的内容是什么
				4'd1:uart_rx_data_reg[0] <= uart_rx_d3;        	//LSB最低位
				4'd2:uart_rx_data_reg[1] <= uart_rx_d3;        	//
				4'd3:uart_rx_data_reg[2] <= uart_rx_d3;        	//
				4'd4:uart_rx_data_reg[3] <= uart_rx_d3;        	//
				4'd5:uart_rx_data_reg[4] <= uart_rx_d3;        	//
				4'd6:uart_rx_data_reg[5] <= uart_rx_d3;        	//
				4'd7:uart_rx_data_reg[6] <= uart_rx_d3;        	//
				4'd8:uart_rx_data_reg[7] <= uart_rx_d3;        	//MSB最高位
				default:;                                    	//1和9分别是起始位和终止位,不需要接收
			endcase                                          	
		end                                                  	
		else                                                 	//数据不一定稳定就不接收
			uart_rx_data_reg <= uart_rx_data_reg;            
	else
		uart_rx_data_reg <= 8'd0;								//不处于接收状态
end	
 
endmodule

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值