FPGA:串口通信发送模块

avatar
作者
猴君
阅读量:0

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
再加上四舍五入和计算精度的误差,所以会造成误差。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!