详解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,这样数据即使有一个时钟的误差,接收端也能正确地采到数据。

本文采用的比特率是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
---------------------
作者:emperor_strange
来源:CSDN
原文:https://blog.csdn.net/emperor_strange/article/details/89311933

最新文章