FPGA的串口通讯(UART)

协议简介:
UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。
其中每一位(Bit)的意义如下:
起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。
数据位:紧接着起始位之后。数据位的个数可以是4、5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。
奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性。
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
空闲位:处于逻辑“1”状态,表示当前线路上没有数据传送。
UART工作原理
发送数据过程:空闲状态,线路处于高电位;当收到发送数据指令后,拉低线路一个数据位的时间T,接着数据按低位到高位依次发送,数据发送完毕后,接着发送奇偶校验位和停止位(停止位为高电位),一帧数据发送结束。
接收数据过程:空闲状态,线路处于高电位;当检测到线路的下降沿(线路电位由高电位变为低电位)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶校验位是否正确,如果正确则通知后续设备准备接收数据或存入缓存。
由于UART是异步传输,没有传输同步时钟。为了能保证数据传输的正确性,UART采用16倍数据波特率的时钟进行采样。每个数据有16个时钟采样,取中间的采样值,以保证采样不会滑码或误码。一般UART一帧的数据位数为8,这样数据即使有一个时钟的误差,接收端也能正确地采到数据。
FPGA的串口通讯(UART)_第1张图片
本文采用的比特率是9600bps,也就是1s传输9600bit的数据,也可以不采用16倍数据波特率的时钟进行采样,因为在此设计的是用50M系统时钟产生的时序驱动,所以采样率远比16倍数据波特率高。没有必要再去分时钟去计数16倍频。
计算传输一次的计数时间:
计数时间 = 1000000000ns/9600 = 104166.7ns
50MHz的时钟周期为20ns,所以计数传输一个比特的次数为104166.7 / 20 = 5208
UART_RX:(接收部分程序)

`timescale 1ns / 1ps
module uart_rx(

               input               clk          ,		
               input               rst_n        ,	
               input               din          ,
               output  reg[7:0]    dout         ,		
               output  reg         dout_vld     
               );

parameter    	   BPS	  =	5208;	//9600波特率
reg   [14:0]        cnt0         ;
wire                add_cnt0     ;
wire                end_cnt0     ;

reg   [ 3:0]        cnt1         ;
wire                add_cnt1     ;
wire                end_cnt1     ;

reg                 rx0          ;	
reg                 rx1          ;	
reg                 rx2          ;	
wire                rx_en        ;

reg                 flag_add     ;

//对数据的跨时钟处理,防止出现亚稳态
always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		rx0 <= 1'b1;
        rx1 <= 1'b1;
        rx2 <= 1'b1;
	end
	else begin
		rx0 <= din;
        rx1 <= rx0;
        rx2 <= rx1;
	end
end

assign rx_en = rx2 & ~rx1;	
//检测到下降沿,即空闲位从1置为0,数据传输开始

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end

assign add_cnt0 = flag_add;
assign end_cnt0 = add_cnt0 && cnt0==BPS-1 ;

always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==9-1 ; //由于是接收程序,此处也不设校验位,所以只需要接收数据就可以,后面的第10位必然位停止位,可以不理,节省资源

always @ (posedge clk or negedge rst_n)begin
	if(!rst_n) begin
		flag_add <= 1'b0;
	end
	else if(rx_en) begin		
		flag_add <= 1'b1;	
	end
    else if(end_cnt1) begin		
		flag_add <= 1'b0;	
	end
end

always @ (posedge clk or negedge rst_n)begin
	if(!rst_n) begin
		dout <= 8'd0;
	end
	else if(add_cnt0 && cnt0==BPS/2-1 && cnt1!=0) begin		//在中间时刻采样,此时的数据比较稳定,从低位到高位依次采样
	    dout[cnt1-1]<= rx2 ;
	end
end


//传输完数据信号
always @ (posedge clk or negedge rst_n)begin
	if(!rst_n) begin
		dout_vld <= 1'b0;
	end
    else if(end_cnt1) begin		
		dout_vld <= 1'b1;	
	end
	else begin	
        dout_vld <= 1'b0;			
	end
end

endmodule

UART_TX(发送)

//删掉din_vld 和rdy信号之后,只需要把计数器cnt0计数条件改为add_cnt0 = 1,就可以了
`timescale 1ns / 1ps
	
module uart_tx(
                 input             clk    ,			
                 input             rst_n  ,		
                 input [7:0]       din    ,
                 input             din_vld,	 //开始发送信号,如果你要一直发送可以删掉此信号,本人是从模块中剪下来的代码,懒得删改了
                 output reg        rdy    ,  //类似接收的完成信号,表示完成了一个字节的传输,同din_vld信号一样,不要可以删掉
                 output reg        dout   
             );

parameter         BPS    = 5208;

reg   [7:0]       tx_data_tmp;	

reg               flag_add   ;
reg   [14:0]      cnt0       ;
wire              add_cnt0   ;
wire              end_cnt0   ;

reg   [ 3:0]      cnt1       ;
wire              add_cnt1   ;
wire              end_cnt1   ;

wire  [ 9:0]      data       ;

always  @(posedge clk or negedge rst_n)begin
    if(rst_n==1'b0)begin
        flag_add <= 0;
    end
    else if(flag_add==0 && din_vld)begin
        flag_add <= 1;
    end
    else if(end_cnt1)begin
        flag_add <= 0;
    end
end

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        cnt0 <= 0;
    end
    else if(add_cnt0)begin
        if(end_cnt0)
            cnt0 <= 0;
        else
            cnt0 <= cnt0 + 1;
    end
end

assign add_cnt0 = flag_add;
assign end_cnt0 = add_cnt0 && cnt0==BPS-1 ;

always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt1 <= 0;
    end
    else if(add_cnt1)begin
        if(end_cnt1)
            cnt1 <= 0;
        else
            cnt1 <= cnt1 + 1;
    end
end

assign add_cnt1 = end_cnt0;
assign end_cnt1 = add_cnt1 && cnt1==10-1 ;


always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		tx_data_tmp <=8'd0;
	end
	else if(flag_add==1'b0 && din_vld) begin	
		tx_data_tmp <= din;	
	end
end

always @ (posedge clk or negedge rst_n) begin
	if(!rst_n) begin
		dout <= 1'b1;
	end
	else if(add_cnt0 && cnt0==1-1)begin
        dout<= data[cnt1];
    end 
end

assign  data = {1'b1,tx_data_tmp,1'b0}; //传输时是从低到高 data = {停止位,数据[7],数据[6] ~ 数据[0],起始位};

always  @(*)begin
    if(din_vld || flag_add)
        rdy = 1'b0;
    else
        rdy = 1'b1;
end

endmodule

你可能感兴趣的:(接口类)