FPGA:串口通信发送模块
- 1、串口通信的概念及分类
- (1)串口通信概念
- (2)串口通信分类
- 2、UART协议
- (1)FPGA实现UART协议发送模块思路
- (2)Verilog设计文件
- a.波特率选择模块
- b.单个比特发送模块
- c.位状态计数器
- d.延时计数器
- e.数据保存寄存器
- f.位发送逻辑
- g.led翻转逻辑
- h.使能信号en_send逻辑
- (3)verilog测试文件(tb)
- 3、仿真测试
- 4、误差分析
1、串口通信的概念及分类
(1)串口通信概念
外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式。
(2)串口通信分类
首先,按照时钟进行划分,可以分为异步串口通信和同步串口通信。异步串口通信是一种无需发送时钟信号的通信方式。数据传输通过起始位、数据位、奇偶校验位和停止位来同步。同步串口通信使用时钟信号来同步发送方和接收方的数据传输。
其次,按照工作方式进行划分,可以分为全双工通信和半双工通信。全双工通信允许同时发送和接收数据。而半双工通信指数据传输只能在一个方向上进行,即一个时间点只能发送或接收数据。
常见异步串口通信方式:RS-232、RS-422、RS-485以及UART
常见同步串口通信方式:SPI、I²C
常见全双工通信:RS-232、UART、SPI
常见半双工通信:RS485等
目前比较流行的还有一些无线技术进行串口通信,常见的标准包括:蓝牙技术或者Zigbee、LoRa等无线协议。
本文基于Xilinx开发板实现UART串口的第一部分,即串口发送。
2、UART协议
UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是一种异步串口通信方式。它属于异步串口通信的范畴,因为它不使用时钟信号来同步发送方和接收方的数据传输。UART使用起始位、数据位、可选奇偶校验位和停止位进行数据传输。下面来一一阐述一下各个位的作用以及常用的参数。
(1)数据位:单个UART数据传输在开始到停止期间可发送的数据的位数,可选值5、6、7、8。默认为8。
(2)波特率:每秒钟可通信的比特的个数,常见有9600、19200、38400、115200等。
(3)奇偶校验位:进行奇偶校验,有奇校验和偶校验两种,奇校验让原有数据序列中(包括要加上的校验位)1的个数为奇数。如1101000(0),使得整体1的个数为奇数个。偶校验同理。
(4)起始位:起始位是一个逻辑低电平(0)。当 UART 空闲时,数据线处于逻辑高电平(1)。因此,当一个数据帧开始时,数据线会从高电平变为低电平,这个电平变化标志着数据传输的开始。
(5)停止位:标志传输完成。停止位是一个逻辑高电平(1)。UART 通常使用 1 个或 1.5 个或 2 个停止位,这取决于通信双方的设置。
(1)FPGA实现UART协议发送模块思路
本例中,发送部分由1位起始位(0),8位数据位,1位停止位(1)组成。波特率有四种选择9600、19200、38400、115200。每一秒发送一次(10位)数据,以波特率9600而言,发送1位数据需要
1
÷
9600
=
1
9600
s
1÷9600=\frac{1}{9600}s
1÷9600=96001s,对应时钟周期数为
1
0
9
n
s
÷
20
n
s
÷
1
9600
≈
5208
T
10^9ns÷20ns÷\frac{1}{9600}≈5208T
109ns÷20ns÷96001≈5208T,即需要5208个时钟周期。需要13位二进制对单个数据位传输时进行计数。我们可以借助下图来帮助我们理解(起始位和结束位忽略):
刚才的13位波特率计数器就是对里面的每一个比特传输是否完成进行计数。
接下来,我们需要设计一个位状态计数器,用来判断当前发送到哪一位了,如果发送到第10位,此时需要置0。一共有10位数据,所以需要4位二进制设计位状态计数器。
然后还需要设计一个一秒的计数器,一秒钟在之前计算过,为50_000_000个时钟周期,需要26位二进制进行计数。此外我们再设计一个led灯,每次发送完成后led灯翻转一次。
(2)Verilog设计文件
基本信号定义如下:
module uart_baud(
Data,Reset_n,Baud,clk,uart_tx,led
);
input[7:0] Data;
input[2:0] Baud; // 提供四种不同波特的计数器,分别为9600,19200,38400,115200
input clk;
input Reset_n;
output reg uart_tx;
output reg led;
a.波特率选择模块
提供了四种不同波特率,分别是9600、19200、38400、115200,需要提供一个两位状态寄存器作为波特选择模块,这里使用三位,方便读者后续进行波特率扩充。单个比特传输最慢的波特率为9600,其需要 1 9600 s \frac{1}{9600}\mathrm{s} 96001s,即5208个时钟周期,之前计算,需要13位二进制进行计数。
input[2:0] Baud; // 提供四种不同波特的计数器,分别为9600,19200,38400,115200
reg [12:0] baud_param; //9600的时间只需要13位
always@(posedge clk or negedge Reset_n)
if (!Reset_n)
baud_param<=1'b0;
else
begin
case(Baud)
0:baud_param = 1000000000/9600/20-1;
1:baud_param = 1000000000/19200/20-1;
2:baud_param = 1000000000/38400/20-1;
3:baud_param = 1000000000/115200/20-1;
endcase
end
b.单个比特发送模块
单个比特发送模块可分以下几个判断:如果复位信号来临,就让比特计数器为0。根据上一部分图片可以知道传输结束后有一部分“空闲等待”的时间,这个时间不发送比特,所以需要一个使能信号en_send,有效时才发送数据,当en_send有效,我们需要判断当前计数器是否已经到了波特计数器的临界点,例如9600波特率为5208-1,如果到临界点则置零,否则自加。
reg en_send; //使能信号定义
reg [12:0] baud_div_cnt;
always@(posedge clk or negedge Reset_n)
if (!Reset_n)
baud_div_cnt<=1'b0;
else if(en_send) // 使能信号有效
begin
if(baud_div_cnt==baud_param) //如果到了临界点
baud_div_cnt<=1'b0;
else
baud_div_cnt<= baud_div_cnt+1'b1;
end
else
baud_div_cnt<=1'b0;
c.位状态计数器
一共有10个位,即1个起始位(低电平),8个数据位,1个结束位(高电平)。所以需要4位二进制进行状态计数。如果复位信号来临,状态置零。如果在波特计数器计时满一次(代表一位传输完成),这时需要判断是否已到结束位,如果在,则置零,否则置1。
reg [3:0] bit_cnt;
always@(posedge clk or negedge Reset_n)
if (!Reset_n)
bit_cnt<=1'b0;
else if(baud_div_cnt==baud_param)
begin
if(bit_cnt==9) // 当前已发送到结束位且已完成
bit_cnt<=1'b0;
else
bit_cnt<=bit_cnt+1'b1;
end
d.延时计数器
计时一秒, 1 s = 1 0 9 n s 1\mathrm{s}=10^9\mathrm{ns} 1s=109ns,而一个时钟周期 20 n s 20\mathrm{ns} 20ns,所以一秒钟有 1 0 9 ÷ 20 = 50000000 T 10^9\div20=50000000\mathrm{T} 109÷20=50000000T,需要26位二进制。
// 延时计数器,计时1秒钟
parameter MCNT_DLY=50_000_000-1;
reg [25:0] Delay_cnt;
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
Delay_cnt<=1'b0;
else if(Delay_cnt== MCNT_DLY)
Delay_cnt<=1'b0;
else
Delay_cnt <= Delay_cnt+1'b1;
e.数据保存寄存器
在每次传输的时候,需要保证数据在传输过程内不发生变化,所以需要保存下每一次开始数据,可以设置一个相同位的寄存器,即8位寄存器保存对应数据,在每次传输完成后保存下当前数据。
reg [7:0] r_data;
// 保存Data数据
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
r_data<=0;
else if(Delay_cnt == MCNT_DLY)
r_data <= Data;
f.位发送逻辑
主要负责uart端口的输出,如果复位信号来临,令它为1,因为起始位为0,这样容易进行区分,还需判断此时是否在“空闲等待”阶段,如果空闲阶段也要令uart为1,其余的时候值根据前面定义的bit_cnt,即位状态进行判断。
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
uart_tx<=0;
else if(en_send==0)
uart_tx<=1;
else
case(bit_cnt)
0:uart_tx<=1'b0;
1:uart_tx<=r_data[0];
2:uart_tx<=r_data[1];
3:uart_tx<=r_data[2];
4:uart_tx<=r_data[3];
5:uart_tx<=r_data[4];
6:uart_tx<=r_data[5];
7:uart_tx<=r_data[6];
8:uart_tx<=r_data[7];
9:uart_tx<=1'b1;
default uart_tx<=uart_tx;
endcase
g.led翻转逻辑
当复位信号有效时,led为0,即不亮,否则当每次数据(10位)传输完成时,led等翻转一次。即需要满足条件bit_cnt==9,表示计数到结束位,还要保证结束位传输结束才翻转,即当前的波特率计数器到达对应的值,即baud_div_cnt == baud_param。
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
led<=1'b0;
else if((bit_cnt==9) && (baud_div_cnt==baud_param))
led<=!led;
h.使能信号en_send逻辑
使能信号,经过之前分析,可知当复位有效,不使能;当在空闲态的时候,不使能;当每一秒结束,即延时计数器满了的时候,使能,表示可再次发送数据。
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
en_send<=1'b0;
else if((bit_cnt==9) && (baud_div_cnt==baud_param))
en_send<=1'b0;
else if(Delay_cnt==MCNT_DLY)
en_send<=1'b1;
endmodule
以上就是所有模块的实现原理以及具体代码实现,为了方便大家运行测试,已经将整体代码整合在下方:
module uart_baud(
Data,Reset_n,Baud,clk,uart_tx,led
);
input[7:0] Data;
input[2:0] Baud; // 提供四种不同波特的计数器,分别为9600,19200,38400,115200
input clk;
input Reset_n;
output reg uart_tx;
output reg led;
reg en_send; // 波特率使能信号
reg [7:0] r_data;
parameter MCNT_DLY=50_000_000-1;
// 波特率选择功能
reg [12:0] baud_param; //9600的时间只需要13位
always@(posedge clk or negedge Reset_n)
if (!Reset_n)
baud_param<=1'b0;
else
begin
case(Baud)
0:baud_param = 1000000000/9600/20-1;
1:baud_param = 1000000000/19200/20-1;
2:baud_param = 1000000000/38400/20-1;
3:baud_param = 1000000000/115200/20-1;
endcase
end
// 波特率计数器
reg [12:0] baud_div_cnt;
always@(posedge clk or negedge Reset_n)
if (!Reset_n)
baud_div_cnt<=1'b0;
else if(en_send)
begin
if(baud_div_cnt==baud_param)
baud_div_cnt<=1'b0;
else
baud_div_cnt<= baud_div_cnt+1'b1;
end
else
baud_div_cnt<=1'b0;
// 位状态计数器
reg [3:0] bit_cnt;
always@(posedge clk or negedge Reset_n)
if (!Reset_n)
bit_cnt<=1'b0;
else if(baud_div_cnt==baud_param)
begin
if(bit_cnt==9)
bit_cnt<=1'b0;
else
bit_cnt<=bit_cnt+1'b1;
end
// 延时计数器,计时1秒钟
reg [25:0] Delay_cnt;
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
Delay_cnt<=1'b0;
else if(Delay_cnt== MCNT_DLY)
Delay_cnt<=1'b0;
else
Delay_cnt <= Delay_cnt+1'b1;
// 保存Data数据
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
r_data<=0;
else if(Delay_cnt == MCNT_DLY)
r_data <= Data;
// 位发送逻辑
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
uart_tx<=0;
else if(en_send==0)
uart_tx<=1;
else
case(bit_cnt)
0:uart_tx<=1'b0;
1:uart_tx<=r_data[0];
2:uart_tx<=r_data[1];
3:uart_tx<=r_data[2];
4:uart_tx<=r_data[3];
5:uart_tx<=r_data[4];
6:uart_tx<=r_data[5];
7:uart_tx<=r_data[6];
8:uart_tx<=r_data[7];
9:uart_tx<=1'b1;
default uart_tx<=uart_tx;
endcase
// led翻转逻辑
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
led<=1'b0;
else if((bit_cnt==9) && (baud_div_cnt==baud_param))
led<=!led;
// 使能信号定义
always@(posedge clk or negedge Reset_n)
if(!Reset_n)
en_send<=1'b0;
else if((bit_cnt==9) && (baud_div_cnt==baud_param))
en_send<=1'b0;
else if(Delay_cnt==MCNT_DLY)
en_send<=1'b1;
endmodule
(3)verilog测试文件(tb)
测试文件主要就是进行例化和信号的初始化,在这里主要进行三个测试,首先复位后让波特率为9600,延时 30 m s 30\mathrm{ms} 30ms传输两个不同的数据,分别为10101010和01010101,然后修改波特率为19200,数据仍然是01010101,波特率增大一倍,所以传输时间应该缩小到原来的一半,待会可以从仿真波形查看。具体代码如下:
`timescale 1ns / 1ps
module uart_baud_tb();
reg [7:0] Data;
reg [2:0] Baud;
reg clk;
reg Reset_n;
wire uart_tx;
wire led;
uart_baud uart_baud_inst( //例化模块
.Data(Data),
.Reset_n(Reset_n),
.Baud(Baud),
.clk(clk),
.uart_tx(uart_tx),
.led(led)
);
//减少仿真的时间,使MCNT_DLY在测试文件缩小到原来的1/100,即10ms发送一次
defparam uart_baud_inst.MCNT_DLY=500000-1;
initial clk<=1;
always #10 clk<=!clk;
initial begin
Reset_n<=0;
Data<=0;
#201; //复位结束
Baud<=0; // 波特率为9600
Reset_n<=1;
Data<=8'b10101010; // 初始化Data值
#30000000; // 延时30ms
Data<=8'b01010101; // 修改Data值
#30000000;// 延时30ms
Baud<=1; // 修改波特率为19200
Data<=8'b01010101; // Data值保持不变
#30000000;
$stop;
end
endmodule
3、仿真测试
首先查看整体的状态,如下所示:
基本实现了数据发送和led灯在每次数据发送完成后翻转,接下来放大波形,具体查看每次发送的数据以及时间计算波特率。
再看一下30ms后发送的数据
再查看波特率修改为原来两倍后的结果
520.7800 × 2 = 1041.56 ( μ s ) 520.7800×2=1041.56(\mathrm{μs}) 520.7800×2=1041.56(μs),而上面显示的是 1041.58 μ s 1041.58\mathrm{μs} 1041.58μs,那少的部分哪里去了呢?
4、误差分析
先说结论,我们在计算的时候,小数部分我们舍弃了导致的误差,这是因为Verilog里面没有浮点型的运算,所以要用整数替换。
计算9600波特率传输一个比特的时间为
1
0
9
÷
9600
÷
20
=
5208
1
3
T
10^9\div9600\div20=5208\frac{1}{3}T
109÷9600÷20=520831T而我们计算的时候是按照
5208
T
5208T
5208T计算的,同理
1
0
9
÷
19200
÷
20
=
2604
1
6
T
10^9\div19200\div20=2604\frac{1}{6}T
109÷19200÷20=260461T按照
2604
T
2604T
2604T计算,所以单个数据之间相差
1
3
−
1
6
=
1
6
T
\frac{1}{3}-\frac{1}{6}=\frac{1}{6}T
31−61=61T
一次发送10位数据,相差
1
6
×
10
=
5
3
T
\frac{1}{6}×10=\frac{5}{3}T
61×10=35T
再加上四舍五入和计算精度的误差,所以会造成误差。