注:通过学习小梅哥的笔记代码,以下是我做的仿真及总结。不当处请指点。

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter)是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。在嵌入式设计中,UART用于主机与辅助设备通信,如汽车音响与外接AP之间的通信,与PC机通信包括与监控调试器和其它器件,如EEPROM通信。

值得注意的是,必须要分清UART,串口,RS232等概念:
UART是异步串行通信总称。
RSXXX(X代表数字)是一种标准,对应各种异步串行通信的接口标准和总线标准。

首先串口的概念一张图介绍

uart0_txd android uart0_txd android git_数据

下面是具体实现过程
串口发送代码思路:就是一个简单的并转串的过程,值得注意的是波特率的设置
串口接收代码思路:由于传输可能引起信号失真,所以在接收端并不是一个简单的并转串的过程。
难点:1、分频+波特率设置,接收的信号通过把1bit数据分为多份,容易想混。
2、把1bit数据分为多份,取中间部分,1位数据累加(即5个数中3个为1则判定为1)。
3、判定是否为起始信号时,采用3位开始数据的累加,当大于2则判定非起始信号。
注意:1、接收的数据要打两拍,消除亚稳态的影响。
2、uart接收的标志信号(在接收这字节信号时拉高),设下降沿来时拉高,接收完毕和开始信号检测错误拉低。
3、设一个缓存器,缓存接收到的串行数据,当接收完一字节数据后,再把这个缓存器的值给出去。

一、uart_tx(uart的串口发送)

uart0_txd android uart0_txd android git_Verilog_02


上面是uart发送一个字节的时序图,下面来介绍波特率的概念。波特率是传输数据的速度,若波特率为9600bps(9600bit per second)就是每秒传送9600bit的数据,可以得到传送1bit数据需要1/9600=104167ns,这时若用50M时钟来计数,50M时钟的时钟周期为1/50M=20ns。那么计104167/20-1=5207个数,则产生1bit数据。用查找变来设置可以复用的波特率000:9600bps; 001:19200bps; 010:8400bps; 01157600bps; 100:15200bps;

首先产生波特率时钟(即每bit产生一个计数时钟)

reg 	[15:0]		bps_DR;//分频计数最大值
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		bps_DR <= 16'd5207;
	else begin
		case(baud_set)
			0:bps_DR <= 16'd5207; //9600bps
			1:bps_DR <= 16'd2603; //19200bps
			2:bps_DR <= 16'd1301; //38400bps
			3:bps_DR <= 16'd867; //57600bps
			4:bps_DR <= 16'd433; //115200bps
			default:bps_DR <= 16'd5207;
		endcase
	end
	
	reg bps_clk; //波特率时钟
	reg [15:0]div_cnt;//分频计数器
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		div_cnt <= 16'd0;
	else if(uart_state_tx)begin		//处于发送状态时,uart_state为1
		if(div_cnt == bps_DR)		//当传输完一个数据时,分频计数器置0重新计数
			div_cnt <= 16'd0;
		else
			div_cnt <= div_cnt + 1'b1;
	end
	else
		div_cnt <= 16'd0;
		
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		bps_clk <= 1'b0;
	else if(div_cnt == 16'd1)
		bps_clk <= 1'b1;
	else
		bps_clk <= 1'b0;

然后再用产生的波特率时钟来计数,计满11个数,开始清零(同时产生一个传输结束信号Tx_Done)。由此来传输一个字节的数据

接着在使能信号send_en有效的时候发送状态uart_state_tx为1,当波特率时钟计数达到11时发送状态uart_state_tx为0,其余时间uart_state_tx保持。

最后用查找表实现串口数据(Rs232_Tx )的发送

localparam START_BIT = 1'b0;
	localparam STOP_BIT = 1'b1;
	always@(posedge clk or negedge rst_n)
	if(!rst_n)
		Rs232_Tx <= 1'b1;
	else begin
		case(bps_cnt)
			0:Rs232_Tx <= 1'b1;
			1:Rs232_Tx <= START_BIT;
			2:Rs232_Tx <= r_data_byte[0];
			3:Rs232_Tx <= r_data_byte[1];
			4:Rs232_Tx <= r_data_byte[2];
			5:Rs232_Tx <= r_data_byte[3];
			6:Rs232_Tx <= r_data_byte[4];
			7:Rs232_Tx <= r_data_byte[5];
			8:Rs232_Tx <= r_data_byte[6];
			9:Rs232_Tx <= r_data_byte[7];
			10:Rs232_Tx <= STOP_BIT;
			default:Rs232_Tx <= 1'b1;
		endcase
	end

附上串口发送数据仿真图:

uart0_txd android uart0_txd android git_uart0_txd android_03

二、uart_rx(uart的串口接收)

由于传送数据的时候容易受到干扰导致数据出现误差,想要避免此误差,采用把1bit的数据分为16个部分,取中间的6个部分为有效,若这6个部分为1大于3个则这bit数据判断为1,若6个部分为0大于3个则这bit数据判断为0,若6个部分为1的个数等于为0的个数则判断传输环境恶劣数据不处理。uart串口发送时序图以及下面的每bit量化16部分时序图对比理解。

uart0_txd android uart0_txd android git_uart_04


首先把接收到的串口数据(Rs232_Tx )打两拍实现同步时钟域,消除亚稳态,同时产生一个下降沿信号nedege。剩下的同上,产生一个采样时钟(对比串口发送的波特率时钟),这个采样时钟是波特率时钟的16倍(把每bit数据分成16部分,则实现一个部分的接收就需要快16倍)。举9600bps的波特率例,1/9600=104167ns,104167/20/16-1=325。同上用查找表表示波特率时钟,并在数据传输状态(uart_state)有效时,开始计数产生一个比波特率时钟大16倍的采样时钟(用于采集1bit中16部分中每个部分数据)。

//波特率选择器
reg [15:0]bps_DR;
always @(posedge clk or negedge rst_n)
 if(!rst_n)
	bps_DR <= 16'd325;
 else begin
	case(baud_set)
		0:bps_DR <= 16'd325;
		1:bps_DR <= 16'd162;
		2:bps_DR <= 16'd80;
		3:bps_DR <= 16'd53;
		4:bps_DR <= 16'd26;
		default:bps_DR <= 16'd325;
	endcase
 end

//产生采样时钟bps_clk,即波特率时钟的16倍
reg uart_state; 
reg [15:0]div_cnt;
reg bps_clk;
always @(posedge clk or negedge rst_n)
 if(!rst_n)
	div_cnt <= 16'd0;
 else if(uart_state)begin
	if(div_cnt == bps_DR)
		div_cnt <= 16'd0;
	else
		div_cnt <= div_cnt + 1'b1;
 end
 else
	div_cnt <= 16'd0;
	
always @(posedge clk or negedge rst_n)
 if(!rst_n)
	bps_clk <= 1'b0;
 else if(div_cnt == 16'd1)
	bps_clk <= 1'b1;
 else
	bps_clk <= 1'b0;

然后对采样时钟进行计数,计满159个数即10bit数据(每个数据16个部分)||(计到12个数时&&START_BIT > 2),计数清零重新计数。tip:计12个并且起始位大于2个是检测起始位是否出错。
再当计满159个数即一个字节的数据后,实现Rx_Done信号拉高。

reg [7:0]bps_cnt;
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		bps_cnt <= 8'd0;
	else if(bps_cnt == 8'd159 || (bps_cnt == 8'd12 && (START_BIT > 2)))
		bps_cnt <= 8'd0;
	else if(bps_clk)
		bps_cnt <= bps_cnt + 1'b1;
	else
		bps_cnt <= bps_cnt;

接着我们回到串口发送部分,当发送完一个字节的数据后,最后的1bit数据为stopt_bit=1’b1,并且数据保持。那么我们在串口接收到的数据的开始数据start_bit=0则必定产生一个下降沿,在这个下降沿产生的时候,接收数据的使能(uart_state_rx)有效,当接收完一个完整的字节数据(Rx_Done)||(计到12个数时&&START_BIT > 2),(tip:计12个并且起始位大于2个是检测起始位是否出错)接收数据的使能(uart_state_rx)无效,其余时间使能保持。

再接着采集数据接收模块的设计。由于中间的采样时间段对应的采样时钟值为6、7、8、9、10、11,通过规律可得下一组为22~27,同理每隔16个为下一组采样有效值(6+16=22)

//采样数据接收模块设计	
	reg [2:0]r_data_byte [7:0];
	always @(posedge clk or negedge rst_n)
	if(!rst_n)begin
		START_BIT 	   <= 3'd0;
		r_data_byte[0] <= 3'd0; 
		r_data_byte[1] <= 3'd0;
		r_data_byte[2] <= 3'd0; 
		r_data_byte[3] <= 3'd0;
		r_data_byte[4] <= 3'd0; 
		r_data_byte[5] <= 3'd0;
		r_data_byte[6] <= 3'd0;
		r_data_byte[7] <= 3'd0;
		STOP_BIT	   <= 3'd0;
	end
	else if(bps_clk)begin
		case(bps_cnt)
			0:begin
				START_BIT 	   <= 3'd0;
				r_data_byte[0] <= 3'd0;
				r_data_byte[1] <= 3'd0;
				r_data_byte[2] <= 3'd0;
				r_data_byte[3] <= 3'd0;
				r_data_byte[4] <= 3'd0;
				r_data_byte[5] <= 3'd0;
				r_data_byte[6] <= 3'd0;
				r_data_byte[7] <= 3'd0;
				STOP_BIT 	   <= 3'd0;
			end
			6,7,8,9,10,11:				START_BIT 	   <= START_BIT 	 + s1_Rs232_Rx;
			22,23,24,25,26,27:			r_data_byte[0] <= r_data_byte[0] + s1_Rs232_Rx;
			38,39,40,41,42,43:			r_data_byte[1] <= r_data_byte[1] + s1_Rs232_Rx;
			54,55,56,57,58,59:			r_data_byte[2] <= r_data_byte[2] + s1_Rs232_Rx;
			70,71,72,73,74,75:			r_data_byte[3] <= r_data_byte[3] + s1_Rs232_Rx;
			86,87,88,89,90,91:			r_data_byte[4] <= r_data_byte[4] + s1_Rs232_Rx;
			102,103,104,105,106,107:	r_data_byte[5] <= r_data_byte[5] + s1_Rs232_Rx;
			118,119,120,121,122,123:	r_data_byte[6] <= r_data_byte[6] + s1_Rs232_Rx;
			134,135,136,137,138,139:	r_data_byte[7] <= r_data_byte[7] + s1_Rs232_Rx;
			150,151,152,153,154,155:	STOP_BIT 	   <= STOP_BIT 		 + s1_Rs232_Rx;
			default:																	//保持
				begin
					START_BIT 	   <= START_BIT;
					r_data_byte[0] <= r_data_byte[0];
					r_data_byte[1] <= r_data_byte[1];
					r_data_byte[2] <= r_data_byte[2];
					r_data_byte[3] <= r_data_byte[3];
					r_data_byte[4] <= r_data_byte[4];
					r_data_byte[5] <= r_data_byte[5];
					r_data_byte[6] <= r_data_byte[6];
					r_data_byte[7] <= r_data_byte[7];
					STOP_BIT	   <= STOP_BIT;
				end
		endcase
	end

uart0_txd android uart0_txd android git_uart0_txd android_05


uart0_txd android uart0_txd android git_uart0_txd android_06

tip:在这里引入一个fpga中二维数组的定义:其实它并不是真正的二维数组,而是有多个寄存器组成的RAM或ROM。下面举一个例子:

reg  [7:0] data [0:99]  				//定义一个二维数组(100个8位的寄存器)
assign dout = data [88][7:5]		//取其中第88个寄存器的高3位为输出
//注意个数的书写问题,定义的时候reg [7:0]data [99:0]与上述是等价的,调用的时候必须个数在前,位数在后。
//其实在这里个数的思想就相当于在fifo中深度的思想一个道理。

最后,状态模块的判断设计,由于我们在1bit中划分16个部分并取中间6个部分为有效,判断6个部分有三个以上的1为1,有三个以下的0为0。其实在这里就是用计数(cnt)来数这6个部分的个数,当个数大于3d(011b)则判断为1,即判断二进制数的最高位是否为1,为1则这bit数据判断为1,为0则这bit数据判断为0。

//数据状态判定模块设计
	always @(posedge clk or negedge rst_n)
	if(!rst_n)
		data_byte <= 8'd0;
	else if(bps_cnt == 8'd159)begin
		data_byte[0] <= r_data_byte[0][2];
		data_byte[1] <= r_data_byte[1][2];
		data_byte[2] <= r_data_byte[2][2];
		data_byte[3] <= r_data_byte[3][2];
		data_byte[4] <= r_data_byte[4][2];
		data_byte[5] <= r_data_byte[5][2];
		data_byte[6] <= r_data_byte[6][2];
		data_byte[7] <= r_data_byte[7][2];
	end 
//这里把这八个3位寄存器的最高位赋值过去,因为若最高位为1,那么6个部分的计数大于3,若最高位为0,那么这6个部分计数小于3

附上串口接收数据仿真图

uart0_txd android uart0_txd android git_数据_07