如果不看分析步骤,需要了解代码,可以直接跳到第四节。
RS232 通信接口标准,通用异步收发传输器(Universal Asynchronous Receiver/Transmitter,UART)是一种异步收发传输器,其在数据发送时将并行数据转换成串行数据来传输,在数据接收时将接收到的串行数据转换成并行数据,可以实现全双工传输和接收。
UART 是异步串行通信的总称(UART 是一种协议)。而 RS232、RS449、RS423、RS422 和 RS485 等,是对应各种异步串行通信口的接口标准和总线标准,它们规定了通信口的电气特性、传输速率、连接特性和接口的机械特性等内容。若系统存在多个 UART接口,则可分别称为 COM1、COM2 等。
RS-232 是美国电子工业联盟(EIA)制定的串行数据通信的接口标准,原始编号全称是 EIA-RS-232(简称 232,RS232),被广泛用于计算机串行接口外设连接。
DB9 接口的针脚定义如图:
通常使用RXD、TXD、GND这三个信号
UART 通信在使用前需要做多项设置,最常见的设置包括数据位数、波特率大小、奇偶校验类型和停止位数。
数据位(Data bits):该参数定义单个 UART 数据传输在开始到停止期间发送的数据位数。可选择为:5、6、7 或者 8(默认)。
波特率(Baud):是指从一设备发到另一设备的波特率,即每秒钟可以通信的数据比特个数。典型的波特率有 300, 1200, 2400, 9600, 19200, 115200 等。一般通信两端设备都要设为相同的波特率,但有些设备也可设置为自动检测波特率。通俗的说,就是一个数据的高电平/低电平持续的时间,是一个最基本的时间单位。
串口全部都是使用的二进制,所以波特率就是比特率。
奇偶校验类型(Parity Type):是用来验证数据的正确性。奇偶校验一般不使用,如果使用,则既可以做奇校验(Odd)也可以做偶校验(Even)。在偶校验中,因为奇偶校验位会被相应的置 1 或 0(一般是最高位或最低位),所以数据会被改变以使得所有传送的数位(含字符的各数位和校验位)中“1”的个数为偶数;在奇校验中,所有传送的数位(含字符的各数位和校验位)中“1”的个数为奇数。奇偶校验可以用于接受方检查传输是否发送生错误,如果某一字节中“1”的个数发生了错误,那么这个字节在传输中一定有错误发生。如果奇偶校验是正确的,那么要么没有发生错误,要么发生了偶数个的错误。如果用户选择数据长度为 8 位,则因为没有多余的比特可被用来作为奇偶校验位,因此就叫做“无奇偶校验(Non)”。
停止位(Stop bits):在每个字节的数据位发送完成之后,发送停止位,来标志着一次数据传输完成,同时用来帮助接受信号方硬件重同步。可选择为:1(默认)、1.5 或者 2 位。
在 RS-232 标准中,最常用的配置是 8N1(即八个数据位、无奇偶校验、一个停止位),其发送一个字节时序图如图:
按照一个完整的字节包括一位起始位、8 位数据位、一位停止位即总共十位数据来算, 要想完整的实现这十位数据的发送,就需要 11 个波特率时钟脉冲,第 1 个脉冲标记一次传输的起始,第 11 个脉冲标记一次传输的结束。
bps_clk的电平宽度是一个系统时钟周期,如:50MHz,是20ns;bps_clk高电平时,uart_tx的值更新一位。
如果波特率是9600,传输一位的时间为104.1666……ns
1s=1109ns;109ns / 9600 =104,166.666……ns=104.1666……us
如果波特率是115200,传输一位的时间为8,680.555……ns
1s=1109ns;109ns / 115200 =8,680.555n……ns=8.680……us
1、串口通信模块设计的目的是用来发送数据的,因此需要有一个数据输入端口θ
2、串口通信,支持不同的波特率,所以需要有一个波特率设置端口。
3、串口通信的本质就是将8位的并行数据通过一根信号线.在不同的时刻传输并行数据的不同位,通过多个时刻,最终将8位并行数据全部传出。
4、串口通信以1位的低电平标志串行传输的开始,待8位数据传输完成之后,再以1位的高电平标志传输的结束。
5、控制信号,控制并转串模块什么开始工作。什么时候一个数据发送完成?须有一个发送开始信号,以及一个发送完成信号。
module uart_byte_tx(
clk,
reset_n,
data,
send_en,
baud_set,
uart_tx,
tx_done
);
input clk;//模块全局时钟 50MHz
input reset_n;//模块全局复位信号
input [7:0]data;//待传输 8bit 数
input send_en;//发送使能信号
input [2:0]baud_set;//baud set 波特率设置,支持 5 种,baud set为 3 位
output uart_tx;//串口发送信号输出
output tx_done;//发送结束信号,一个时钟周期高电平
endmodule
在波特率时钟生成模块中,计数器需要的计数值与波特率之间的关系如下表所示,其中系统时钟周期为System_clk_period,这里为 20ns(因为是50MHz)。如果接入到该模块的时钟频率为其他值,需要根据具体的频率值修改该参数。典型的波特率有 300, 1200, 2400, 9600(波特率一般都大于9600), 19200, 115200 等。
波特率为300,对应时间3333333ns最小时间单元:3333333ns/20ns(即时钟频率50HZ)=16666次(2进制18位)
波特率为9600,对应时间104167ns最小时间单元:104167ns/20ns(即时钟频率50HZ)=5208次(2进制13位)
波特率为115200,对应时间8680ns最小时间单元:8680ns/20ns(即时钟频率50HZ)=434次=2’b110110010(9位)
计数器由波特率(波特率一般都大于9600)决定
//生成一个时钟计数器
reg [17:0]div_cnt;
always@(posedge clk or negedge reset_n)
if(!reset_n)
div_cnt<=0;
else if(div_cnt==bps_DR-1)//bps_DR由波特率决定
div_cnt<=0;
else
div_cnt<=div_cnt + 1'b1;
增加一个send_en来控制,将上面代码修改为下面代码
//生成一个时钟计数器
reg [17:0]div_cnt;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
div_cnt<=0;
else if(send_en)begin
if(div_cnt==bps_DR-1)//bps_DR由波特率决定
div_cnt<=0;
else
div_cnt<=div_cnt + 1'b1;
end
else
div_cnt<=0;
end
波特率的设置
//波特率的设置
//baud_set = 0 就让波特率 = 9600 ;
//baud_set = 1 就让波特率 = 19200 ;
//baud_set = 2 就让波特率 = 38400 ;
//baud_set = 3 就让波特率 = 57600 ;
//baud_set = 4 就让波特率 = 115200 ;
reg [17:0]bps_DR;//波特率寄存器,因为要和div_cnt做比较,所以也是[17:0]
always@(*)begin
if(!reset_n)
bps_DR <= 1000000000/9600/20;
else begin
case(baud_set)//使用组合逻辑来写,常量可以计算出来,FPGA保留整数,不存在除不尽的状态
0:bps_DR <= 1000000000/9600/20;//0:bps_DR <= 16'd5208;
1:bps_DR <= 1000000000/19200/20;
2:bps_DR <= 1000000000/38400/20;
3:bps_DR <= 1000000000/57600/20;
4:bps_DR <= 1000000000/115200/20;
default:bps_DR <= 1000000000/9600/20;
endcase
end
end
通过对波特率时钟进行计数,来确定数据发送的循环状态。10个信号需要11个点来确定,参照1.3时序图所示
//数据发生的循环状态,共10个信号
//计数器计数11位,二进制用4位来表示
reg [3:0]bps_cnt;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
bps_cnt<=0;
else if(bps_cnt==bps_DR-1)begin//bps_cnt== 4'd11
if(bps_cnt==11)
bps_cnt<=0;
else
bps_cnt<=bps_cnt + 1'b1;
end
end
修改由bps_clk来控制的代码
//分析后,bps_cnt也受send_en控制
//数据发生的循环状态,共10个信号
//计数器计数11位,二进制用4位来表示
wire bps_clk;
assign bps_clk=(div_cnt==1);
reg [3:0]bps_cnt;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
bps_cnt<=0;
else if(send_en)begin
if(bps_clk)begin
if(bps_cnt==10)
bps_cnt<=0;
else
bps_cnt<=bps_cnt + 1'b1;
end
end
else
bps_cnt<=0;
end
数据传输状态控制模块设计
还有一个十选一多路器 ,作用是根据 bps_cnt 的值来确定数据传输的状态。如1.3时序图所示,在不同的波特率时钟计数值时,有对应的传输数据。
always@(posedge clk or negedge reset_n)begin
if(!reset_n)begin
uart_tx<=1'b0;tx_done<=1'b0;
end
else begin
case(bps_cnt)
0:begin uart_tx<=1'b0;tx_done<=1'b0;end
1:uart_tx<=data[0];
2:uart_tx<=data[1];
3:uart_tx<=data[2];
4:uart_tx<=data[3];
5:uart_tx<=data[4];
6:uart_tx<=data[5];
7:uart_tx<=data[6];
8:uart_tx<=data[7];
9:uart_tx<=1'b1;//此处确保stop是持续一个脉冲
10:begin uart_tx<=1'b1;tx_done<=1'b1;end
default:uart_tx<=1'b1;
endcase
添加使能端控制
//让send_en控制div_cnt
reg [17:0]div_cnt;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
div_cnt<=0;
else if(send_en)begin
if(div_cnt==bps_DR-1)//bps_DR由波特率决定
div_cnt<=0;
else
div_cnt<=div_cnt + 1'b1;
end
else
div_cnt<=0;
end
目前,基本功能可以实现了
整合第3步的所有代码,形成下面设计文件。
`timescale 1ns / 1ps
module uart_byte_tx(
clk,
reset_n,
data,
send_en,
baud_set,
uart_tx,
tx_done
);
input clk;//模块全局时钟 50MHz
input reset_n;//模块全局复位信号
input [7:0]data;//待传输 8bit 数
input send_en;//发送使能信号
input [2:0]baud_set;//baud set 波特率设置,支持5种,baud set 为3位
output reg uart_tx;//串口发送信号输出
output reg tx_done;//发送结束信号,一个时钟周期高电平
//波特率的设置
//baud_set = 0 就让波特率 = 9600 ;
//baud_set = 1 就让波特率 = 19200 ;
//baud_set = 2 就让波特率 = 38400 ;
//baud_set = 3 就让波特率 = 57600 ;
//baud_set = 4 就让波特率 = 115200 ;
reg [17:0]bps_DR;//波特率寄存器,因为要和div_cnt做比较,所以也是[17:0]
always@(*)begin
if(!reset_n)
bps_DR <= 1000000000/9600/20;
else begin
case(baud_set)//使用组合逻辑来写,常量可以计算出来,FPGA保留整数,不存在除不尽的状态
0:bps_DR <= 1000000000/9600/20;//0:bps_DR <= 16'd5208;
1:bps_DR <= 1000000000/19200/20;
2:bps_DR <= 1000000000/38400/20;
3:bps_DR <= 1000000000/57600/20;
4:bps_DR <= 1000000000/115200/20;
default:bps_DR <= 1000000000/9600/20;
endcase
end
end
//让send_en控制div_cnt
reg [17:0]div_cnt;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
div_cnt<=0;
else if(send_en)begin
if(div_cnt==bps_DR-1)//bps_DR由波特率决定
div_cnt<=0;
else
div_cnt<=div_cnt + 1'b1;
end
else
div_cnt<=0;
end
wire bps_clk;
assign bps_clk=(div_cnt==1);
//分析后,bps_cnt也受send_en控制
//数据发生的循环状态,共10个信号
//计数器计数11位,二进制用4位来表示
reg [3:0]bps_cnt;
always@(posedge clk or negedge reset_n)begin
if(!reset_n)
bps_cnt<=0;
else if(send_en)begin
// if(bps_cnt==1)begin
if(bps_clk)begin
if(bps_cnt==10)
bps_cnt<=0;
else
bps_cnt<=bps_cnt + 1'b1;
end
end
else
bps_cnt<=0;
end
always@(posedge clk or negedge reset_n)begin
if(!reset_n)begin
uart_tx<=1'b0;tx_done<=1'b0;
end
else begin
case(bps_cnt)
0:begin uart_tx<=1'b0;tx_done<=1'b0;end
1:uart_tx<=data[0];
2:uart_tx<=data[1];
3:uart_tx<=data[2];
4:uart_tx<=data[3];
5:uart_tx<=data[4];
6:uart_tx<=data[5];
7:uart_tx<=data[6];
8:uart_tx<=data[7];
9:uart_tx<=1'b1;//此处确保stop是持续一个脉冲
10:begin uart_tx<=1'b1;tx_done<=1'b1;end
default:uart_tx<=1'b1;
endcase
end
end
endmodule
写仿真文件快捷方法:
1.复制端口声明,例化连接,ctrl可以实现列编辑,在端口名前加点。
2.复制输入端口,直接将input替换成reg。在vivado中,单击鼠标右键,选择replace in files,可以选择设计文件/仿真文件的替换 。
`timescale 1ns / 1ps
module uart_byte_tx_tb( );
reg clk;
reg reset_n;
reg [7:0]data;
reg send_en;
reg [2:0]baud_set;
wire uart_tx;
wire tx_done;
uart_byte_tx uart_byte_tx_inst(
.clk(clk),
.reset_n(reset_n),
.data(data),
.send_en(send_en),
.baud_set(baud_set),
.uart_tx(uart_tx),
.tx_done(tx_done)
);
initial clk=1;
always #10 clk=~clk;//20ns一个周期
//对输入进行赋值
initial begin
reset_n=0;
data=0;
send_en=0;
baud_set=4;//115200速度快,仿真时间短
#201
reset_n=1;
#100
data=8'h57;
send_en=1;
#20
@(posedge tx_done)//死循环,一直等待TX_done的到来,再执行下一语句
send_en=0;
#20000
data=8'h75;
send_en=1;
#20
@(posedge tx_done)
#20000
send_en=0;
$stop;
end
endmodule
testbench的新语法 @(posedge TX_done),死循环,一直等待TX_done的到来,再执行下一语句