FPGA:串口通信发送模块

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÷960015208T,即需要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 3161=61T
一次发送10位数据,相差
1 6 × 10 = 5 3 T \frac{1}{6}×10=\frac{5}{3}T 61×10=35T
再加上四舍五入和计算精度的误差,所以会造成误差。

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值