通信协议篇——UART串口通信

通信协议篇——UART串口通信

1.简介

UART是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。

2.原理

通信方式

串口的通信方式为串行通信,按位发送和接收字节,将并行数据转换为串行数据流发送出去,将接受的串行数据流转换为并行数据。完成串口通信,只需要三根线,接收(rx)、发送(tx)和地线。确定通信双方的波特率数据格式一致,是实现串口通信的前提。

一般情况下,设备之间的通信方式可以分为并行通信和串行通信。它们的区别是:

并行通信 串行通信
传输原理 数据各个位同时传输 数据按位顺序传输
优点 速度快 占用引脚资源少
缺点 占用引脚资源多 速度相对较慢
数据格式

UART通信按位发送和接收数据包,每个数据包有固定的格式:

起始位 数据位 奇偶校验位 停止位
长度 1 8 1 1、1.5、2
波特率

这是一个衡量符号传输速率的参数。指的是信号被调制以后在单位时间内的变化,即单位时间内载波参数变化的次数,如每秒钟传送240个字符,而每个字符格式包含10位(1个起始位,1个停止位,8个数据位),这时的波特率为240Bd,比特率为10位*240个/秒=2400bps。通常电话线的波特率为14400,28800和36600。波特率可以远远大于这些值,但是波特率和距离成反比,波特率越高,信号能够有效传输的距离越短。

在串口通信中,可以把波特率理解为每秒钟传输的数据包的个数,把比特率理解为每秒钟传输的bit数。但在实际应用中,波特率和比特率常常被混淆,通常在串口通信中常说的波特率,其实指的是比特率,串口通信用比特率描述数据传输速率。例如,常用的比特率有9600bps、14400bps、115200bps等。实际使用中,通常会把单位bps省略,希望大家能够知道这其实指的是比特率。

数据位

数据位传输的是有效的信息,具体内容由用户自己定义。在串口通信中,标准的数据位长度为8位,代表一个字节的长度。扩展的ASCII码是0~255,刚好占8位长度,所以利用8位数据位几乎就可以传输所有字符了。

起始位和停止位

串口通信的两根数据线rx和tx,在空闲状况下都是处于高电平的。所以,一个数据包的起始位通常是‘0’。当检测到接收数据线rx的下降沿时,即表明线路上来了一个数据包,请做好接收准备。起始位非常重要,它使得通信双方能够区分有效数据位和空闲位,这是实现通信的前提。

而停止位则在数据包的末尾,通常是‘1’,使得线路恢复高电平,即空闲状态。停止位也常被当做分隔开两个数据包的关键点,只有当线路恢复高电平,下一个起始位来临时,才能够检测到下降沿。一般规定两个数据包之间至少需要有1到2个空闲位(即停止位长度可为1到2位)。

奇偶校验位

奇偶校验是串口通信中的一种简单的检错方式,它的原理是使得数据位与奇偶校验位的总的‘1’的个数为奇数或偶数,若为奇数则是奇校验,若为偶数则是偶校验。例如,如果数据是“001”,那么对于奇校验,校验位为’0‘;对于偶校验,校验位为’1‘。当然,不使用奇偶校验位也是可以的。

传输时序

通信协议篇——UART串口通信_第1张图片

即:

起始位 D0 D1 D2 D3 D4 D5 D6 D7 校验位 停止位
Value 0 X X X X X X X X X 1

(数据位采用低位在前,高位在后的传输方式,即LSB First,MSB Last)

具体通信过程

发送过程:

当检测到发送命令的上升沿,启动数据发送。先发送起始位‘0’;再按照低位在前、高位在后的顺序发送数据位;然后根据数据位异或计算得到的奇(偶)校验值,作为校验位发送;最后发送停止位‘1’。具体时序如下:

外部:数据准备→发送命令置‘1’;

内部:检测发送命令上升沿→计数器启动→发送起始位、数据位、奇偶校验位、停止位→计数器清零→检测发送命令上升沿。

接收过程:

当检测到接收数据线rx的下降沿时,表示一个数据包的起始位到来,启动数据接收。先接收起始位,仅作检测作用;再接收数据位,存储在数据寄存器中,当接受完最后一位数据位时,数据读取完成标志置‘1’;再检验奇偶校验位,若不正确则数据错误标志置‘1’;再接收停止位‘1’,若不正确则帧错误标志置‘1’。具体时序如下:

内部:检测rx下降沿→计数器启动→接收起始位‘0’→接收数据位→接收完最后一位数据,数据读取完成标志置‘1’→检验奇偶校验位→接收停止位‘1’→计数器清零→检测rx下降沿;

外部:检测数据读取完成标志上升沿→读取所接收数据。

计数器:

串口通信中,时钟一般设置为波特率的多倍频(如16倍),这是因为只有在每一位的中心位置读取数据,才能确保数据的正确性,靠近位的边沿读取数据的话,容易受到器件本身电平跳变快慢的影响,具有很大的不稳定性。当时钟是16倍的波特率时,每一位的长度有16个时钟周期,我们可以选择在第8个时钟上升沿读取数据,或者取第7、8、9个时钟所读取数据相与,以保证每一位数据的准确性。

计数器的计数范围需要根据时钟和数据包的长度来设置,若一帧数据有11位长,时钟是波特率的16倍,则最大计数值应当设定为176(由于取每一位的中值,所以一般设定为176-8=168),当达到计数器最大值时,表示一帧数据发送或接收完成,此时将所有控制信号复位,计数器清零。

标准接口:

name description length
clk 时钟 1
rst 复位信号 1
rx 接收数据线 1
dataout 接收数据 8
rdsig 数据读取完成标志 1
data_error 数据错误标志 1
frame_error 帧错误标志 1
tx 发送数据线 1
datain 发送数据 8
wrsig 数据发送命令 1

3.程序实现

一个串口模块可以分成3个小模块——时钟模块、接收模块和发送模块。

RTL视图:

通信协议篇——UART串口通信_第2张图片

时钟模块:

`timescale 1ns / 1ps
//
//Module Name:	clkdiv
//说明:串口波特率为9600,采样时钟为154.6KHz(16倍频)
//
module clkdiv
	(
		input clk50, 		//系统时钟
		input rst_n,      //收入复位信号
		output reg clkout //采样时钟输出
	);

reg [15:0] cnt;

//分频进程, 50Mhz的时钟326分频,得到波特率9600的16倍频
always @(posedge clk50 or negedge rst_n)   
begin
  if (!rst_n) begin
     clkout <=1'b0;
	  cnt<=0;
  end	  
  else if(cnt == 16'd162) begin
    clkout <= 1'b1;
    cnt <= cnt + 16'd1;
  end
  else if(cnt == 16'd325) begin
    clkout <= 1'b0;
    cnt <= 16'd0;
  end
  else begin
    cnt <= cnt + 16'd1;
  end
end
endmodule

接收模块:

`timescale 1ns / 1ps
///
// Module name:	uartrx.v
// 说明:16个clock接收一个bit,16个时钟采样,取中间的采样值
///
module uartrx
	(
		input clk, 		//采样时钟
		input rst_n,   //复位信号
		input rx,      //UART数据输入
		output reg[7:0] dataout, //接收数据输出
		output reg rdsig, 	//数据读取完成标志
		output reg dataerror, //数据出错指示
		output reg frameerror //帧出错指示
	);

reg [7:0] cnt;
reg rxbuf, rxfall, receive;
parameter paritymode = 1'b0;
reg presult, idle;

always @(posedge clk)   //检测线路的下降沿
begin
  rxbuf <= rx;
  rxfall <= rxbuf & (~rx);
end


//启动串口接收程序

always @(posedge clk)
begin
  if (rxfall && (~idle)) begin//检测到线路的下降沿并且原先线路为空闲,启动接收数据进程  
    receive <= 1'b1;
  end
  else if(cnt == 8'd168) begin //接收数据完成
    receive <= 1'b0;
  end
end


//串口接收程序, 16个时钟接收一个bit

always @(posedge clk or negedge rst_n)
begin
  if (!rst_n) begin
     idle<=1'b0;
	  cnt<=8'd0;
     rdsig <= 1'b0;	 
	  frameerror <= 1'b0; 
	  dataerror <= 1'b0;	  
	  presult<=1'b0;
  end	  
  else if(receive == 1'b1) begin
		case (cnt)
		8'd0:begin
			idle <= 1'b1;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b0;
		end
		8'd24:begin                 //接收第0位数据
			idle <= 1'b1;
			dataout[0] <= rx;
			presult <= paritymode^rx;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b0;
		end
		8'd40:begin                 //接收第1位数据  
			idle <= 1'b1;
			dataout[1] <= rx;
			presult <= presult^rx;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b0;
		end
		8'd56:begin                 //接收第2位数据   
			idle <= 1'b1;
			dataout[2] <= rx;
			presult <= presult^rx;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b0;
		end
		8'd72:begin               //接收第3位数据   
			idle <= 1'b1;
			dataout[3] <= rx;
			presult <= presult^rx;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b0;
		end
		8'd88:begin               //接收第4位数据    
			idle <= 1'b1;
			dataout[4] <= rx;
			presult <= presult^rx;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b0;
		end
		8'd104:begin            //接收第5位数据    
			idle <= 1'b1;
			dataout[5] <= rx;
			presult <= presult^rx;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b0;
		end
		8'd120:begin            //接收第6位数据    
			idle <= 1'b1;
			dataout[6] <= rx;
			presult <= presult^rx;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b0;
		end
		8'd136:begin            //接收第7位数据   
			idle <= 1'b1;
			dataout[7] <= rx;
			presult <= presult^rx;
			cnt <= cnt + 8'd1;
			rdsig <= 1'b1;
		end
		8'd152:begin            //接收奇偶校验位    
			idle <= 1'b1;
			if(presult == rx)
			  dataerror <= 1'b0;
			else
			  dataerror <= 1'b1;       //如果奇偶校验位不对,表示数据出错
			cnt <= cnt + 8'd1;
			rdsig <= 1'b1;
		end
		8'd168:begin
		  idle <= 1'b1;
		  if(1'b1 == rx)
			 frameerror <= 1'b0;
		  else
			 frameerror <= 1'b1;      //如果没有接收到停止位,表示帧出错
		  cnt <= cnt + 8'd1;
		  rdsig <= 1'b1;
		end
		default: begin
			cnt <= cnt + 8'd1;
		end
		endcase
   end	
   else begin
	  cnt <= 8'd0;
	  idle <= 1'b0;
	  rdsig <= 1'b0;	 
	end
end
endmodule

发送模块:

`timescale 1ns / 1ps
//
// Module Name:    uarttx 
// 说明:16个clock发送一个bit, 一个起始位,8个数据位,一个校验位,一个停止位
//
module uarttx
	(
		input clk, 				//UART时钟
		input rst_n,         //系统复位
		input[7:0] datain,   //需要发送的数据
		input wrsig,         //发送命令,上升沿有效
		output reg idle,     //线路状态指示,高为线路忙,低为线路空闲
		output reg tx			//发送数据信号
	);

reg send;
reg wrsigbuf, wrsigrise;
reg presult;	//奇偶校验位
reg[7:0] cnt;             //计数器
parameter paritymode = 1'b0;	//偶校验


//检测发送命令wrsig的上升沿

always @(posedge clk)
begin
   wrsigbuf <= wrsig;
   wrsigrise <= (~wrsigbuf) & wrsig;  
end


//启动串口发送程序

always @(posedge clk)
begin
  if (wrsigrise &&  (~idle))  //当发送命令有效且线路为空闲时,启动新的数据发送进程
  begin
     send <= 1'b1;
  end
  else if(cnt == 8'd168)      //一帧数据发送结束
  begin
     send <= 1'b0;
  end
end


//串口发送程序, 16个时钟发送一个bit

always @(posedge clk or negedge rst_n)
begin
  if (!rst_n) begin
         tx <= 1'b1;
         idle <= 1'b0;
			cnt<=8'd0;
			presult<=1'b0;
  end		
  else if(send == 1'b1)  begin
    case(cnt)                 //产生起始位
    8'd0: begin
         tx <= 1'b0;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd16: begin
         tx <= datain[0];    //发送数据0位
         presult <= datain[0]^paritymode;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd32: begin
         tx <= datain[1];    //发送数据1位
         presult <= datain[1]^presult;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd48: begin
         tx <= datain[2];    //发送数据2位
         presult <= datain[2]^presult;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd64: begin
         tx <= datain[3];    //发送数据3位
         presult <= datain[3]^presult;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd80: begin 
         tx <= datain[4];    //发送数据4位
         presult <= datain[4]^presult;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd96: begin
         tx <= datain[5];    //发送数据5位
         presult <= datain[5]^presult;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd112: begin
         tx <= datain[6];    //发送数据6位
         presult <= datain[6]^presult;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd128: begin 
         tx <= datain[7];    //发送数据7位
         presult <= datain[7]^presult;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd144: begin
         tx <= presult;      //发送奇偶校验位
         presult <= datain[0]^paritymode;
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd160: begin
         tx <= 1'b1;         //发送停止位            
         idle <= 1'b1;
         cnt <= cnt + 8'd1;
    end
    8'd168: begin
         tx <= 1'b1;             
         idle <= 1'b0;       //一帧数据发送结束
         cnt <= cnt + 8'd1;
    end
    default: begin
         cnt <= cnt + 8'd1;
    end
   endcase
  end
  else  begin
    tx <= 1'b1;
    cnt <= 8'd0;
    idle <= 1'b0;
  end
end
endmodule

顶层模块:

`timescale 1ns/1ps

//module name:uart_demo
//说明:	串口波特率为9600
//			接收数据,rx下降沿开始读取,rdsig上升沿表示数据包读取完成
//			发送数据,检测到wrsig上升沿,开始发送数据


module uart_demo
	(
		input clk_50MHz,
		input rst,
		input rx,
		input[7:0] datain,
		input wrsig,
		
		output clk_uart,
		output tx,
		output rdsig,
		output[7:0] dataout
	);
	

//clkdiv
clkdiv clkdiv_inst
	(
		.clk50(clk_50MHz),
		.rst_n(rst),
		.clkout(clk_uart)
	);

//uart rx
uartrx uartrx_inst
	(
		.clk(clk_uart),
		.rst_n(rst),
		.rx(rx),
		.dataout(dataout), 
		.rdsig(rdsig)
	);

//uart tx	
uarttx uarttx_inst
	(
		.clk(clk_uart), 
		.rst_n(rst), 
		.datain(datain), 
		.wrsig(wrsig),
		.tx(tx)
	);
	
endmodule

你可能感兴趣的:(FPGA,FPGA,UART串口通信,Verilog)