文章目录

前言

一、UART简介

1、基本概念

2、UART协议

3、波特率简介

二、UART串口回环实验

1、设计思路

2、程序代码

① 串口接收模块

② 串口发送模块

③ 串口顶层模块

④ 串口仿真模块

3、仿真验证

总结


前言

在我们进行FPGA设计时,常常会用到一些数据通信接口,这些通信接口都是有着特定的功能以及协议的,其中最常见的莫过于串口uart了,它对于每一个做硬件和嵌入式软件的人来说,几乎就是一个必备的工具,用来调试一个带MCU或者CPU的系统。

串口uart是一种非常通用的设备接口,可以实现不同硬件间的通信,对于FPGA开发来说,串口也同样可以实现FPGA开发板与电脑PC端的通信,下面我们就来简单介绍一下串口uart的基本协议及功能。


一、UART简介

1、基本概念

通用异步收发传输器,英文全称为Universal Asynchronous Receiver/Transmitter,简称UART,是一种异步收发传输器,在发送数据通过将并行数据转换成串行数据进行传输,在接收数据时将串行数据转换成并行数据。

串行通信分为同步串行通信和异步串行通信。同步串行通信即需要时钟的参与,通信双方需要在同一时钟的控制下,同步传输数据;异步串行通信则不需要时钟的干预,通信双方使用各种的时钟来控制数据的发送和接收。uart属于异步串行通信,即没有时钟信号来同步或验证从发送器发送并由接收器接收的数据,这就要求发送器和接收器必须事先就时序参数达成一致。

UART是通用异步收发器的简称,它包括了RS232、RS422、RS423、RS449以及RS485等接口标准和总线规范标准,UART是异步串行通信接口的总称。而RS323、RS422、RS423、RS449和RS485等是对应各种异步串行通信的接口标准和总线标准,它规定了通信接口的电器特性、传输速率以及接口的机械特性等内容。

2、UART协议

UART串口通信需要两根信号线来实现,一根用于串口发送数据,一根用于串口接收数据。UART串口传输的数据被组织成数据包,每个数据包包含了一个起始位,5至9个数据位,可选的奇偶校验位和1或者2个停止位,如下图所示。

UART串口协议规定,当总线处于空闲状态时信号线的状态为高电平,表示当前线路上没有数据传输。

起始位:开始进行数据传输时发送方要先发送一个低电平来表示传输字符的开始。

数据位:起始位之后就需要传输数据,数据位可以是5~9位,构成一个字符,一般是8位,先发送最低位后发送最高位。

奇偶校验位:奇偶校验位是用来检验数据在传输过程中是否出错。在奇校验时,发送方应使数据位中1的个数与校验位中1的个数为奇数,接收方在接收数据时,对1的个数进行检测,若1的个数不为奇数个,则说明数据在传输过程中存在差错。偶校验则相反。

停止位:数据结束标志,可以是1位或者2位的高电平。由于数据在传输线上是定时传输的,并且每一个设备有自己的时钟,很可能在通信中两台设备之间出现了小小的不同步,因此停止位不仅仅是表示数据传输的结束,并且提供计算机校正时钟的机会。停止位越多,数据传输月稳定,但是数据传输速度越慢。

3、波特率简介

在电子通信领域,波特(Baud)即调制速率,指的是有效数据讯号调制载波的速率,即单位时间内载波调制状态变化的次数。

波特率表示每秒钟传送码元符号的个数,它是对符号传输速率的一种度量,用单位时间内载波调制状态改变的次数来表示,1波特指每秒传输1个字符。

数据传输速率使用波特率来表示,单位bps(bits per second),常见的波特率有9600、19200、38400、57600、115200等。例如将串口波特率设置位115200bps,那么传输一个bit需要的时间是1/115200 ≈ 8.68us。


二、UART串口回环实验

1、设计思路

实验任务:通过电脑端的串口调试助手向FPGA发送数据,FPGA通过串口接收数据并将接受到的数据发送给上位机,实现串口回环功能。

接收模块(RX):通过检测起始位来表示数据传输的开始,在波特率中间时刻去采样总线上的数据,最后将数据进行串并转换。

发送模块(TX):将并行数据转换成串行数据,然后在串行数据帧头加上起始位,帧尾加上停止位,发送给上位机。

2、程序代码

本次程序设计中没有用到奇偶校验位,一帧数据为8bit,停止位为1位,波特率可供选择(代码为115200),FPGA的系统晶振时钟为50MHZ。

① 串口接收模块

/*===============================*filename: uart_rx.vdescription : 串口接收模块time: 2022-12-22 author: 卡夫卡与海*================================*/module uart_rx(inputclk ,//时钟50MHZinputrst_n ,//复位inputuart_rx ,//rx数据线input [2:0]baud_sel,//波特率选择output reg[7:0]po_data ,//接收的数据output reg po_flag//数据使能);//参数定义parameter SCLK = 50_000_000;//系统时钟50MHZ//波特率选择parameter BAUD_9600 = SCLK/9600,BAUD_19200= SCLK/19200 ,BAUD_38400= SCLK/38400 ,BAUD_57600= SCLK/57600 ,BAUD_115200 = SCLK/115200;//信号定义reg uart_rx_1;//同步、打拍reg uart_rx_2;wirerx_nedge ;//下降沿检测reg start_flag ;//起始标志reg work_en;//工作使能reg [15:0]cnt_baud ;//波特率计数器reg [15:0]BAUD_NUM ;reg bit_flag ;//接收数据使能reg [3:0] cnt_bit;//bit计数器reg [7:0] rx_data;//数据reg rx_flag;//数据标志//同步、打拍检测下降沿always @(posedge clk or negedge rst_n)beginif(!rst_n)beginuart_rx_1 <= 1'b1;uart_rx_2 <= 1'b1;endelse beginuart_rx_1 <= uart_rx;uart_rx_2 <= uart_rx_1;endendassign rx_nedge = uart_rx_2 && ~uart_rx_1;//start_flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginstart_flag <= 1'b0;endelse if(rx_nedge && work_en == 1'b0)beginstart_flag <= 1'b1;endelse beginstart_flag <= 1'b0;endend//work_enalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginwork_en <= 1'b0;endelse if(start_flag == 1'b1)beginwork_en <= 1'b1;endelse if(cnt_bit == 4'd8 && bit_flag == 1'b1)beginwork_en <= 1'b0;endelse beginwork_en <= work_en;endend//cnt_baudalways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_baud <= 16'd0;endelse if((cnt_baud == BAUD_NUM - 1) || (work_en == 1'b0))begincnt_baud <= 16'd0;endelse if(work_en == 1'b1)begincnt_baud <= cnt_baud + 1'b1;endend//BAUD_NUMalways @(*)begincase(baud_sel)3'd0 : BAUD_NUM = BAUD_9600;3'd1 : BAUD_NUM = BAUD_19200 ;3'd2 : BAUD_NUM = BAUD_38400 ;3'd3 : BAUD_NUM = BAUD_57600 ;3'd4 : BAUD_NUM = BAUD_115200;default : BAUD_NUM = BAUD_115200;endcaseend//bit_flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginbit_flag > 1) - 1)beginbit_flag <= 1'b1;endelse beginbit_flag <= 1'b0;endend//cnt_bitalways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_bit <= 4'd0;endelse if((cnt_bit == 4'd8) && (bit_flag == 1'b1))begincnt_bit <= 4'd0;endelse if(bit_flag == 1'b1)begincnt_bit <= cnt_bit + 1'b1;endend//rx_dataalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginrx_data =4'd1)&&(cnt_bit<=4'd8)&&(bit_flag==1'b1))beginrx_data <= {uart_rx_2,rx_data[7:1]};endend//rx_flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginrx_flag <= 1'b0;endelse if((cnt_bit == 4'd8) && (bit_flag == 1'b1))beginrx_flag <= 1'b1;endelse beginrx_flag <= 1'b0;endend//输出//po_dataalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginpo_data <= 8'b0;endelse if(rx_flag == 1'b1)beginpo_data <= rx_data;endend//po_flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginpo_flag <= 1'b0;endelse beginpo_flag <= rx_flag;endendendmodule

② 串口发送模块

/*===============================*filename: uart_tx.vdescription : 串口发送模块time: 2022-12-22 author: 卡夫卡与海*================================*/module uart_tx(input clk,//时钟50MHZinput rst_n,//复位input[2:0]baud_sel ,//波特率选择input[7:0]pi_data,//数据input pi_flag,//数据使能outputreg uart_tx //tx数据线);//参数定义parameter SCLK = 50_000_000;//系统时钟50MHZ//波特率选择parameter BAUD_9600 = SCLK/9600,BAUD_19200= SCLK/19200 ,BAUD_38400= SCLK/38400 ,BAUD_57600= SCLK/57600 ,BAUD_115200 = SCLK/115200;//信号定义regwork_en;//工作使能reg [15:0] cnt_baud ;//波特率计数器reg [15:0] BAUD_NUM ;regbit_flag ;//bit标志信号reg [3:0]cnt_bit;//bit计数器//work_enalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginwork_en <= 1'b0;endelse if(pi_flag == 1'b1)beginwork_en <= 1'b1;endelse if((cnt_bit == 4'd9) && (bit_flag == 1'b1))beginwork_en <= 1'b0;endend//cnt_baudalways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_baud <= 16'd0;endelse if((work_en == 1'b0) || (cnt_baud == BAUD_NUM - 1))begincnt_baud <= 16'd0;endelse if(work_en == 1'b1)begincnt_baud <= cnt_baud + 1'b1;endend//BAUD_NUMalways @(*)begincase(baud_sel)3'd0 : BAUD_NUM = BAUD_9600;3'd1 : BAUD_NUM = BAUD_19200 ;3'd2 : BAUD_NUM = BAUD_38400 ;3'd3 : BAUD_NUM = BAUD_57600 ;3'd4 : BAUD_NUM = BAUD_115200;default : BAUD_NUM = BAUD_115200;endcaseend//bit_flag always @(posedge clk or negedge rst_n)beginif(!rst_n)beginbit_flag <= 1'b0;endelse if(cnt_baud == 16'd1)beginbit_flag <= 1'b1;endelse beginbit_flag <= 1'b0;endend//cnt_bitalways @(posedge clk or negedge rst_n) beginif(!rst_n)begincnt_bit <= 4'd0;endelse if((cnt_bit == 4'd9)&&(bit_flag == 1'b1))begincnt_bit <= 4'd0;endelse if((work_en == 1'b1)&&(bit_flag == 1'b1))begincnt_bit <= cnt_bit + 1'b1;endend//输出uart_txalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginuart_tx <= 1'b1;endelse if(bit_flag == 1'b1)begincase(cnt_bit)0 : uart_tx <= 1'b0;//起始位1 : uart_tx <= pi_data[0];2 : uart_tx <= pi_data[1];3 : uart_tx <= pi_data[2];4 : uart_tx <= pi_data[3];5 : uart_tx <= pi_data[4];6 : uart_tx <= pi_data[5];7 : uart_tx <= pi_data[6];8 : uart_tx <= pi_data[7];9 : uart_tx <= 1'b1;//停止位default : uart_tx <= 1'b1;endcaseendendendmodule

③ 串口顶层模块

/*==============================*filename: uart_top.vdescription : 串口顶层模块time: 2022-12-22 author: 卡夫卡与海*================================*/module uart_top(input clk,input rst_n,input uart_rx,outputuart_tx);//波特率选择/*常用波特率选择:baud_sel == 3'd0 :波特率为:9600baud_sel == 3'd1 :波特率为:19200baud_sel == 3'd2 :波特率为:38400baud_sel == 3'd3 :波特率为:57600baud_sel == 3'd4 :波特率为:115200 */wire [2:0]baud_sel ;//波特率选择assign baud_sel = 3'd4;//波特率 = 115200//信号定义wire [7:0]data ;wireflag ;//模块例化//串口接收模块uart_rx u_uart_rx(/*input*/.clk (clk ),/*input*/.rst_n (rst_n ),/*input*/.uart_rx (uart_rx ),/*input [2:0]*/.baud_sel(baud_sel),/*output reg[7:0]*/.po_data (data),/*output reg */.po_flag (flag) );//串口发送模块uart_tx u_uart_tx(/*input*/.clk (clk ),/*input*/.rst_n (rst_n ),/*input [2:0]*/.baud_sel(baud_sel),/*input [7:0]*/.pi_data (data),/*input*/.pi_flag (flag),/*outputreg*/.uart_tx (uart_tx ));endmodule

④ 串口仿真模块

/*========================================*filename: uart_top_tb.vdescription : 串口顶层模块仿真文件time: 2022-12-22 author: 卡夫卡与海*========================================*/`timescale 1ns/1ns module uart_top_tb();reg clk ;reg rst_n ;reg rx;wiretx;//产生时钟initial beginclk = 1'b1;rx= 1'b1;forever #10clk = ~clk;end//产生复位initial beginrst_n = 1'b0;#20;rst_n = 1'b1;end//产生激励task rx_bit(input [7:0] data);integer i;for(i = 0 ; i < 10 ; i = i + 1)begincase(i)0:rx <= 1'b0;//起始位1:rx <= data[0];2:rx <= data[1];3:rx <= data[2];4:rx <= data[3];5:rx <= data[4];6:rx <= data[5];7:rx <= data[6];8:rx <= data[7];9:rx <= 1'b1;//停止位endcase#(434*20);//根据不同的波特率,延时的时间不同endendtasktask rx_byte();integer j;for(j = 0 ; j < 8 ; j = j + 1)rx_bit(j);endtaskinitial begin#200rx_byte();end//模块例化uart_top u_uart_top(/*input */.clk (clk),/*input */.rst_n (rst_n),/*input */.uart_rx (rx ),/*output*/.uart_tx (tx ));endmodule

3、仿真验证

通过对我们项目工程的仿真来看,显示接收到的数据为0~7共八个字节的数据,发送给上位机的数据也为0~7,说明我们的功能能够正确的接收并发送数据。

通过放大波形图可以看到,波特率计数器总共计数434次,波特率为115200,系统时钟为50MHZ,则50_000_000/115200≈434,波特率计数器正确。我们的采样时刻是在波特率计数器的中间时刻去采样,这样能够保证采样到的数据的稳定性,这里的bit标志信号在波特率计数器计数到217时拉高,表示此刻进行数据的采样操作。

最后就是进行串并转换了,接收模块将串行数据转换成并行数据传给发送模块,而发送模块将接收模块传入的并行数据转换成串行数据通过uart_tx信号线发送给上位机,而上位机通过串口调试助手将接收到的数据串并转换并打印出来显示在屏幕上。


总结

UART串口通信协议还是比较简单的,在编写代码时最重要的是思路的完整性,要有一个全局的概念,不能写了一部分就不知道接下来要干嘛了。实现UART串口通信的思路还有很大,也可以通过前面讲过的状态机的方法来实现,还可以在中间调用FIFO IP核,我这里这个算是一个简单的串口回环实现方式了,在后续的项目中还得加以改进和优化。