一、UART简介
UART是一种通用串行数据总线,用于异步通信。该总线双向通信,实现全双工传输和接收。
二、UART的通信协议和传输时序
UART 通信 UART 首先将接收到的并行数据转换成串行数据来传输。消息帧从一个低位起始位开始,后面是 7 个或 8 个数据位,一个可用的奇偶位和一个或几个高位停止位。接收器发现开始位时它就知道数据准备发送,并尝试与发送器时钟频率同步。如果选择了奇偶校验,UART 就在数据位后面加上奇偶位。奇偶位可用来帮助错误校验。在接收过程中,UART 从消息帧中去掉起始位和结束位,对进来的字节进行奇偶校验,并将数据字节从串行转换成并行。
UART 传输时序如下图所示:
起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。
数据位:从最低位开始传送,靠时钟定位。
奇偶校验位:资料位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验资料传送的正确性。
停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。 由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
空闲位:处于逻辑“1”状态,表示当前线路上没有资料传送。
三、Verilog实现
由于 UART 是异步传输,没有传输同步时钟。为了能保证数据传输的正确性,UART 采用16 倍数据波特率的时钟进行采样。每个数据有 16 个时钟采样,取中间的采样值,以保证采样不会滑码或误码。一般 UART 一帧的数据位数为 8,这样即使每个数据有一个时钟的误差,接收端也能正确地采样到数据。
3.1 接收程序
`timescale 1ns / 1ps
module uartrx(clk, rx, dataout, rdsig, dataerror, frameerror);
input clk; //采样时钟
input rx; //UART数据输入
output dataout; //接收数据输出
output rdsig; //接收数据有效,高说明接收到一个字节
output dataerror; //数据出错指示
output frameerror; //帧出错指示
reg[7:0] dataout;
reg rdsig, dataerror;
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
/
//使用176个时钟接收一个数据(起始位、8位数据、奇偶校验位、停止位),每位占用16个时钟//
always @(posedge clk)
begin
if(receive == 1'b1)
begin
case (cnt)
8'd0: //0~15个时钟为接收第一个比特,起始位
begin
idle <= 1'b1;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd24: //16~31个时钟为第1个bit数据,取中间第24个时钟的采样值
begin
idle <= 1'b1;
dataout[0] <= rx;
presult <= paritymode^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd40: //47~32个时钟为第2个bit数据,取中间第40个时钟的采样值
begin
idle <= 1'b1;
dataout[1] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd56: //63~48个时钟为第3个bit数据,取中间第56个时钟的采样值
begin
idle <= 1'b1;
dataout[2] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd72: //79~64个时钟为第4个bit数据,取中间第72个时钟的采样值
begin
idle <= 1'b1;
dataout[3] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd88: //95~80个时钟为第5个bit数据,取中间第88个时钟的采样值
begin
idle <= 1'b1;
dataout[4] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd104: //111~96个时钟为第6个bit数据,取中间第104个时钟的采样值
begin
idle <= 1'b1;
dataout[5] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd120: //127~112个时钟为第7个bit数据,取中间第120个时钟的采样值
begin
idle <= 1'b1;
dataout[6] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b0;
end
8'd136: //143~128个时钟为第8个bit数据,取中间第136个时钟的采样值
begin
idle <= 1'b1;
dataout[7] <= rx;
presult <= presult^rx;
cnt <= cnt + 8'd1;
rdsig <= 1'b1; //接收数据有效
end
8'd152: //159~144个时钟为接收奇偶校验位,取中间第152个时钟的采样值
begin
idle <= 1'b1;
if(presult == rx)
dataerror <= 1'b0;
else
dataerror <= 1'b1; //如果奇偶校验位不对,表示数据出错
cnt <= cnt + 8'd1;
rdsig <= 1'b1;
end
8'd168: //160~175个时钟为接收停止位,取中间第168个时钟的采样值
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
3.2发送程序
`timescale 1ns / 1ps
module uarttx(clk, datain, wrsig, idle, tx);
input clk; //UART时钟
input [7:0] datain; //需要发送的数据
input wrsig; //发送命令,上升沿有效
output idle; //线路状态指示,高为线路忙,低为线路空闲
output tx; //发送数据信号
reg idle, 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
/
//使用168个时钟发送一个数据(起始位、8位数据、奇偶校验位、停止位),每位占用16个时钟//
always @(posedge clk)
begin
if(send == 1'b1) begin
case(cnt) //tx变低电平产生起始位,0~15个时钟为发送起始位
8'd0: begin
tx <= 1'b0;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd16: begin
tx <= datain[0]; //发送数据位的低位bit0,占用第16~31个时钟
presult <= datain[0]^paritymode;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd32: begin
tx <= datain[1]; //发送数据位的第2位bit1,占用第47~32个时钟
presult <= datain[1]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd48: begin
tx <= datain[2]; //发送数据位的第3位bit2,占用第63~48个时钟
presult <= datain[2]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd64: begin
tx <= datain[3]; //发送数据位的第4位bit3,占用第79~64个时钟
presult <= datain[3]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd80: begin
tx <= datain[4]; //发送数据位的第5位bit4,占用第95~80个时钟
presult <= datain[4]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd96: begin
tx <= datain[5]; //发送数据位的第6位bit5,占用第111~96个时钟
presult <= datain[5]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd112: begin
tx <= datain[6]; //发送数据位的第7位bit6,占用第127~112个时钟
presult <= datain[6]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd128: begin
tx <= datain[7]; //发送数据位的第8位bit7,占用第143~128个时钟
presult <= datain[7]^presult;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd144: begin
tx <= presult; //发送奇偶校验位,占用第159~144个时钟
presult <= datain[0]^paritymode;
idle <= 1'b1;
cnt <= cnt + 8'd1;
end
8'd160: begin
tx <= 1'b1; //发送停止位,占用第160~167个时钟
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