FPGA开发——UART串口通信(使用FIFO IP核作为缓存,在接收模块后添加检验)

一、UART简介

1、概述

 UART:是一种硬件功能,是一种主要采用异步串行通信方式的通用异步收发传输器。它通过使用通信接口(例如 RS232、RS422、RS485)来处理通信(即时序要求和数据帧),UART主要的功能:在发送时将并行数据转换为串行数据来传输;在接收时将串行数据转换为并行数据。

UART的三个接口标准RS232、RS422、RS485主要区别:

接口标准逻辑1逻辑说明优缺点
RS232-15V+15V负逻辑电平;
3线全双工;
点对点双向通信。
传输速度相对较低;
传输距离短 。
RS422差值电压 +(2~5)V差值电压 - (2~5)V差分传输;
4线全双工;
点对多,主从通信;
抗干扰能力强;
传输速度高;
传输距离远。
RS485差值电压 +(2~5)V差值电压 -(2~5)V差分传输;
2线半双工;
多点双向通信;
能实现多个发送;
接收设备双向通信。

 本次使用的初学者FPGA开发板上,所用的串口接口为RS232,是负逻辑电平为逻辑1,3线全双工的接口。UART 提供了一种广泛采用且廉价的方法来实现不同设备之间的全双工或半双工数据交换。

简单说明一下并行传输和串行传输:
  (1)并行传输:数据的各个位用多条数据线同时进行传输。
  (2)串行传输:将数据分成一位一位的在同一条传输线上逐个传输。
并行传输及串行传输

图1.1 并行通信和串行通信


简单说明一下单工和半双工以及全双工
  (1)单工:数据只能沿一个方向传输;
  (2)半双工:数据传输可以沿两个方向,但需要分时进行;
  (3)全双工:数据可以同时进行双向传输。

在这里插入图片描述

图1.2 单工、半双工、全双工


 使用FPGA进行UART串口通信的收发实验时,对其硬件实现功能了解个大概就足够了。软件进行UART串口通信的收发实验,主要是在通信协议下的格式,以及数据的传输速率进行设计。

2、通信协议

 (1)数据格式
 UART传输数据的格式:从一个低位起始位 开始,后面是5~8个数据位,一个可用的奇偶检验位和一个或几个高位停止位。只有当接收器发现起始位时,才准备发送数据,并尝试与发送器时钟频率同步。如果选择了奇偶,UART就在数据位后面加上奇偶位。奇偶位可用来帮助错误校验(本次实验没有添加校验位)。
UART数据传输格式

图1.3 UART的数据传输格式

 UART的数据格式既是发送的数据帧格式,也是接收的数据格式。在串口通信中,收发数据都是在数据线上一位一位的传输,因此在收发数据的时候要进行串并转换(可通过移位寄存器来实现)。

 在UART中,信号线上共有两种状态, 分别用逻辑1(高电平)和逻辑0(低电平)来区分,在空闲时, 数据线应该保持在逻辑高电平状态。
  其中各部分的具体意义如下:
 1)起始位(Start Bit):先发出一个逻辑0信号, 表示传输字符的开始。
 2)数据位(Data Bits):可以是5~8位逻辑0或1. 如ASCII码(8位)。
 3)校验位(Parity Bit):数据位加上这一位后, 使得1的位数应为偶数(偶校验)或奇数(奇校验)。
 4)停止位(Stop Bit):它是一个字符数据的结束标志。 可以是1位、1.5位、2位的高电平。
 5)空闲位:处于逻辑1状态, 表示当前线路上没有数据传输。


 (2)波特率
 串口通信的速率用波特率表示,它表示每秒传输的二进制数据的位数,单位是bps(位/秒),常用的波特率有9600、19200、38400、57600、115200等,本次实验只选用了9600波特率下的UART。
 以FPGA的系统时钟频率50MHz为基础,在9600波特率传输速率下,发送模块和接收模块的发送接收的时钟频率(时钟宽度)计算为:时钟频率为50MHz,说明1s发送或接收50_000_000bit数据,数据传输的速率要求为1s发送或接收9600bit数据,所以可讲50_000_000 / 9600 即可得到9600波特率下的时钟频率(计数值的最大值)



二、FIFO说明

1、FIFO 简介

 FIFO (First Input First Output):先进先出队列,是 FPGA 开发中使用频率很高的单元,主要应用在需要数据缓冲且数据符合先进先出规律的同步或异步场合。对于FIFO,最简单的形象对比,可以比作成在超市排队结账,先来的先结账,后来的后结账,当然其中去掉了插队这种特种情况。

 就个人理解,FIFO IP核使用频率高是因为它在作为缓存单元时,读写相对独立:写入数据时,只要FIFO未满,无论读出端是否付出数据,都不影响写入端写入数据;读出数据时,只要FIFO内有数据,无论写入端是否写入数据,都不影响读出端读出数据。这个条件,给予了读写两端相应的自由空间操作,例如不同的传送速率,不同的传输判断条件等等。

2、Quartus II软件中FIFO IP核的调用

 通过Quartus II软件调用IP核功能调出FIFO IP核设置页面:

  • 此次实验设置的FIFO为8bit输入,8bit输出,深度为1024,同步复位
    数据格式设置

  • 输出信号引脚设置,虽然用到的只有epmty以及almost full,但基本都勾上了
    输出引脚设置

  • 输出数据的形式为检验到读使后,才读出数据
    数据输出形式


  • 生成FIFO IP核以及inst文件 生成文件


三、系统框图

1、实现功能

 通过从电脑上的串口助手中发送数据,经过UART传输到FPGA的接收端;FPGA接受数据,并检验数据,在FIFO未满时,将其写入FIFO中;当FIFO中存有数据,FPGA通过UART将检验后的数据回返到电脑,在电脑上可通过串口助手观察到检验之后的数据。
 所以具体所需要实现的功能:
 (1)接收功能:
  根据UART传输的数据格式,在检测到开始标识后,将接受的数据(8bit)寄存,时钟到停止位时,将数据传到检验模块进行检验。模块内需要进行波特率计数,传输的1bit位数计数,通过计数值,每接收1byte的数据产生1byte数据接收结束标志给检验模块。
 (2)检验功能
  接收来之UART_RXD模块的接收数据,根据UART_RXD模块的1byte数据接收结束标志对每一位数据检验,并产生写使能,将数据写入FIFO。
 (3)发送功能
  只要判断FIFO有数据(未空),即可读出数据进行发送。其中模块内也需要进行波特率计数,传输的1bit位数计数,通过计数值,每发送1byte的数据产生读使能,读FIFO中的下一字节的数据。


2、模块设计

 根据所需要实现的功能可以得到如下的模块设计框图:
模块框图

图2.1 系统框图
 PC机与FPGA之间分别由两条数据线连接,分别是PC发送数据使得FPGA接收的RXD,以及通过FPGA内部校验之后的数据发送TXD,以达到数据接收数据检验后回返,其中对每个模块进行简单介绍:

  (1)顶层模块

UAER

引脚I/0BIT引脚说明
clkI1系统时钟
rst_nI1低电平复位
rxdI1PC端输入的串行数据
txdO8PC端接收的串行数据

  (2)UART RXD
UART_RXD

引脚I/0BIT引脚说明
clkI1系统时钟
rst_nI1低电平复位
rxdI1PC端输入的串行数据
fifo_rayI1FIFO未满,低电平有效
dinO8FPGA接收的并行数据
rxd_vldO1接收完有效的一包8bit并行数据,高有效

  (3)UART VERIFY

VERIFY

引脚I/0BIT引脚说明
clkI1系统时钟
rst_nI1低电平复位
din8FPGA接收的并行数据
rxd_vldI1接收完有效的一包8bit并行数据,高有效
dout_verifyO8检验完一包8bit的有效接收数据
fifo_wrenO1fifo的写使能,高有效

  (4)FIFO

FIFO

引脚I/0BIT引脚说明
clock(clk)I1系统时钟
aclr(rst_n)I1低电平复位
rdreq(fifo_rden)I1FIFO读使能,高有效
wrreq(fifo_wren)I1FIFO写使能,低有效
data(dout_verify)I8FPGA接收的并行数据
almost_full()O1接收完有效的一包8bit并行数据,高有效
empty()O1接收完有效的一包8bit并行数据,高有效
q(dout)O8FPGA接收的并行数据

  (5)UART TXD

UART_TXD

引脚I/0BIT引脚说明
clkI1系统时钟
rst_nI1低电平复位
doutI8从FIFO内读出的数据
fifo_rdenO1FIFO的读出使能,高有效
txdO1FPGA输出的串行数据

四、代码实现

1、顶层模块

 在Quartus II中,代码仿真之后顶层模块的原理框图
顶层模块原理框图

图2.2 顶层模块原理框图

 在顶层模块中需要例化四个模块:
  (1)uart_rxd:接收模块
  (2)uart_verfy:FPGA内部校验模块
  (3)fifo_8b_8b:调用FIFO IP核,8bit输入8bit输出
  (4)uart_txd:发送模块

  顶层模块代码

module UART_top(
                //INPUT    
                input    clk,          //System clock
                input    rst_n,        //Reset signal is inverted
                input    rxd,          //uart rxd
						  
                //OUTPUT
                output wire    txd     //uart txd
                );

//=======================================================================//
wire          fifo_rden;         //FIFO send enable
wire          din_vld;           //AOS 1 byte number send end enable
wire          almost_full;
wire          almost_empty;
wire          empty;		  
wire          full; 
wire [7:0]    data;                //AOS 1 byte send data    
wire [7:0]    q;          
wire [7:0]    usedw;
wire [7:0]    dout_verify; 
wire [7:0]    din;
  

//=======================================================================// 
//uart rxd
uart_rxd U1_uart_rxd(
                     .clk              ( clk             ),
                     .rst_n            ( rst_n           ),
                     .rxd              ( rxd             ),
                     .fifo_rdy         ( almost_full     ),
							
                     .din              ( din             ),
                     .din_vld          ( din_vld         )		
                     ); 
							
//=======================================================================//
//inst a fifo: 8bit input and 8bit output 
fifo_8b_8b fifo_8b_8b_inst (
                            .aclr          ( ~rst_n        ),     //Reset signal is inverted
                            .clock         ( clk           ),     //System clock  
                            .data          ( data          ),     //Write data     
                            .rdreq         ( fifo_rden     ),     //Read enable
                            .wrreq         ( fifo_wren     ),     //Write enable
                            .empty         ( empty         ),     //FIFO is null signal
                            .full          ( full          ),     //FIFO full signal
                            .almost_full   ( almost_full   ),
                            .almost_empty  ( almost_empty  ),
									 
                            .q             ( q             ),     //Read data
                            .usedw         ( usedw         )      //Amount of available data
                            );

//=======================================================================// 
//uart txd
uart_txd U2_uart_txd(    
                     .clk              ( clk             ),
                     .rst_n            ( rst_n           ),
                     .txd_vld          ( empty           ),
							
                     .dout             ( q               ),						
							  
                     .fifo_rden        ( fifo_rden       ),
                     .txd              ( txd             )
                      );
//=======================================================================//   
uart_verify U3_uart_verify(
                           .clk             (clk        ),
                           .rst_n           (rst_n      ),
                           .din_vld         (din_vld    ),
								
                           .din             (din        ),
									
                           .dout_verify     (data       ),
                           .fifo_wren       (fifo_wren  )
                           );
//=======================================================================//    
endmodule


2、接收模块(uart_rxd)

 无论是接收模块还是发送模块至少都需要两种计数器,第一种功能的计数器是:根据UART的传输协议,以及确定好的传输波特率,基于FPGA的系统时钟频率,计数UART结束或发送每1bit数据的频率(时钟宽度):

		if(rxd_flag!=0)
		begin
			if(clk_cnt==BPS_CNT-1)
			begin
				clk_cnt<=0;
			end
			else
			begin
				clk_cnt<=clk_cnt+32'd1;
			end
		end
		else
		begin
			clk_cnt<=32'd0;
		end	

 该计数器下可设置一个产生采集信号的计数器,每当计数值达到中间位置时,采集信号有效,对数据进行寄存:

		if(clk_cnt==BPS_CNT_1-1)	//在分频计数的中间部分启动信号采集
		begin
			rxd_sele<=1'b1;			//采集信号时钟沿落在有效数据中间
		end
		else
		begin
			rxd_sele<=1'b0;
		end

 第二种功能的计数器是,对FPGA每接收一位1bit的数据进行计数,即当uart_rxd接收有效数据下,且处于采集信号rxd_sele有效,计数值进行加1,而在rxd_flag无效时,rxd_num为零,这是根据rxd_flag的有效判断确定的:
		if(rxd_flag!=0)
		begin
			if( rxd_sele !=0 )
			begin
				rxd_num <= rxd_num+1'd1;
			end
			else
			begin
				rxd_num <= rxd_num;
			end
		end
		else
		begin
			if(rxd_num==4'd10)
			begin
				rxd_num<=4'd0;
			end
			else
			begin
				rxd_num<=rxd_num;
			end
		end

 对uart_flag进行说明,uart_flga是uart_rxd在作用时的标志符,高电平有效,当检测到接收数据的开始位,即从PC端传输的rxd信号为下降沿时开始有效,一直持续到接收到停止位(也就是第九位数据拉低),再一次检测到数据的开始位为下降沿,有效。主要是作为uart_rxd的一个有效标志位,接收数据存入FPGA的内存寄存器中:

			if(rxd_en!=0)
			begin
				rxd_flag<=1'b1;
			end
			else
			begin
				if(rxd_num==4'd10)
				begin
					rxd_flag<=1'b0;
				end
				else
				begin
					rxd_flag<=rxd_flag;
				end
			end		

 数据的开始位为下降沿的判断实现代码,即当检测到,停止位之后(高电平),txd为低电平(起始位):

always @ (posedge clk or negedge rst_n)
begin
	if(rst_n==0)
	begin
		rxd_d0<=1'b1;
		rxd_d1<=1'b1;
	end
	else
	begin
		rxd_d0<=rxd;
		rxd_d1<=rxd_d0;
	end
end	

//  Detect falling edge   //
assign  rxd_en = rxd_d1 & ( ~rxd_d0 );		

 uart_rxd接收数据寄存的功能实现,在采集信号有效的情况下,根据计数值的位置将数据从低位到高位进行寄存:
	if( rxd_sele !=0 )
	begin
		case( rxd_num )
			4'd0	:	;//忽略开始位
			4'd1	:	begin	din_r[0] <= rxd;	end	//采集中间八位有效数据
			4'd2	:	begin	din_r[1] <= rxd;	end 
			4'd3	:	begin	din_r[2] <= rxd;	end 
			4'd4	:	begin	din_r[3] <= rxd;	end 
			4'd5	:	begin	din_r[4] <= rxd;	end 
			4'd6	:	begin	din_r[5] <= rxd;	end 
			4'd7	:	begin	din_r[6] <= rxd;	end 
			4'd8	:	begin	din_r[7] <= rxd;	end 
			4'd9	:	begin	din_d <= din_r;		end//锁存采集的 8 位有效数据
			default	:	;
		endcase
	end

  接收模块的整体代码

module uart_rxd(
                input  clk,
                input  rst_n,
                input  rxd,
                input  fifo_rdy,
					 
                output wire [7:0]    din,
                output wire          din_vld				 
               );
//=======================================================================//
parameter    CLK = 50_000_000;         //FPGA System clock frequency
parameter    BPS = 9_600;              //Baud rate
localparam   BPS_CNT = CLK / BPS;  	   //
localparam   BPS_CNT_1 = BPS_CNT / 2;	// 			
//=======================================================================//
reg           din_vld_r;					
reg [35:0]    clk_cnt;
reg           rxd_flag;
reg           rxd_sele;
reg           rxd_d0;			//同步
reg           rxd_d1;			//打拍
reg [3:0]     rxd_num;
reg [7:0]     din_r;
reg [7:0]     din_d;
//=======================================================================//
wire	rxd_en;   //Detect falling edge
//=======================================================================//
//  Synchronization and 1 beat Send enable(rxd_vld)   
always @ (posedge clk or negedge rst_n)
begin
	if(rst_n==0)
	begin
		rxd_d0<=1'b1;
		rxd_d1<=1'b1;
	end
	else
	begin
		rxd_d0<=rxd;
		rxd_d1<=rxd_d0;
	end
end	
//  Detect falling edge   //
assign  rxd_en = rxd_d1 & ( ~rxd_d0 );		
//=======================================================================//
always @ ( posedge clk or negedge rst_n )
begin
	if(rst_n==0)
	begin
		rxd_flag <= 1'b0;
	end
	else
	begin
		if( fifo_rdy ==0 )
		begin
			if(rxd_en!=0)
			begin
				rxd_flag<=1'b1;
			end
			else
			begin
				if(rxd_num==4'd10)
				begin
					rxd_flag<=1'b0;
				end
				else
				begin
					rxd_flag<=rxd_flag;
				end
			end
		end
		else
		begin
			rxd_flag <= 0;
		end
	end
end
//=======================================================================//
always @ (posedge clk or negedge rst_n)
begin
	if(rst_n==0)
	begin
		clk_cnt<=31'd0;
	end
	else
	begin
		if(rxd_flag!=0)
		begin
			if(clk_cnt==BPS_CNT-1)
			begin
				clk_cnt<=0;
			end
			else
			begin
				clk_cnt<=clk_cnt+32'd1;
			end
		end
		else
		begin
			clk_cnt<=32'd0;
		end
	end
end
//=======================================================================//
always@(posedge clk or negedge rst_n)
begin 
	if(rst_n==0)
	begin
		rxd_sele<=1'b0;
	end
	else
	begin
		if(clk_cnt==BPS_CNT_1-1)	//在分频计数的中间部分启动信号采集
		begin
			rxd_sele<=1'b1;			//采集信号时钟沿落在有效数据中间
		end
		else
		begin
			rxd_sele<=1'b0;
		end
	end
end
//=======================================================================//
// rxd_num: receive  data bit count   
always @ (posedge clk or negedge rst_n)
begin
	if(rst_n==0)
	begin
		rxd_num<=4'd0;
	end
	else
	begin
		if(rxd_flag!=0)
		begin
			if( rxd_sele !=0 )
			begin
				rxd_num <= rxd_num+1'd1;
			end
			else
			begin
				rxd_num <= rxd_num;
			end
		end
		else
		begin
			if(rxd_num==4'd10)
			begin
				rxd_num<=4'd0;
			end
			else
			begin
				rxd_num<=rxd_num;
			end
		end
	end
end
//=======================================================================//
// uart receive data   
always@(posedge clk or negedge rst_n)
begin 
	if(rst_n==0)
	begin 
		din_d <= 8'd0; 
		din_r <= 8'd0;
	end
	else 
	if( rxd_sele !=0 )
	begin
		case( rxd_num )
			4'd0	:	;//忽略开始位
			4'd1	:	begin	din_r[0] <= rxd;	end	//采集中间八位有效数据
			4'd2	:	begin	din_r[1] <= rxd;	end 
			4'd3	:	begin	din_r[2] <= rxd;	end 
			4'd4	:	begin	din_r[3] <= rxd;	end 
			4'd5	:	begin	din_r[4] <= rxd;	end 
			4'd6	:	begin	din_r[5] <= rxd;	end 
			4'd7	:	begin	din_r[6] <= rxd;	end 
			4'd8	:	begin	din_r[7] <= rxd;	end 
			4'd9	:	begin	din_d <= din_r;		end//锁存采集的 8 位有效数据
			default	:	;
		endcase
	end
end
//=======================================================================//
//  Generate UART receive end enable  
always @ ( posedge clk or negedge rst_n )
begin
	if( rst_n == 0 )
	begin
		din_vld_r <= 0;
	end
	else
	begin
		if( rxd_num == 10 )
		begin
			din_vld_r <= 1;
		end
		else
		begin
			din_vld_r <= 0;
		end
	end
end
//=======================================================================//
assign din_vld = din_vld_r;
assign din = din_d;
//=======================================================================//
endmodule



3、检验模块(uart_verfy)

 检验模块的实现非常简单,只要检测到接收模块的1byte数据接收接收变标志位就进行检验,检验完毕后产生FIFO的写使能,将检验完毕的数据写入FIFO中,检验的条件是接收的数据以ASCII码的形式为0~9和空格时,正常输出,不然数据输出为ASCII码的大写字母A。


  检验模块的整体代码

module uart_verify(
                   input            clk,
                   input            rst_n,
                   input            din_vld,
                   input [7:0]		din,
						
                   output wire [7:0]   dout_verify,
                   output wire         fifo_wren
                   );
//=======================================================================//						
reg	data_vld;
reg	fifo_wren_r1;			//
reg	fifo_wren_r2;			//	
reg   fifo_wren_r3;					
reg	verify_d0;			    //
reg	verify_en;			    //
reg [7:0]  dout_verify_r;
//=======================================================================//
always @ ( posedge clk or negedge rst_n )
begin
	if( rst_n==0 )
	begin
		data_vld <= 0;
	end
	else
	begin
		if(( din<=8'b0011_1001 && din >= 8'b0011_0000 ) || ( din == 8'b0010_0000 ))
		begin	
			data_vld <= 1;
		end
		else
		begin
			data_vld <= 0;
		end
	end
end
//=======================================================================//
//Delay 1 CLK clock, and start outputting dout after determining whether DIN is a number 0-9 and a space_ verify
always @ ( posedge clk or negedge rst_n )
begin
	if( rst_n == 0 )
	begin
		verify_d0 <= 1'b1;
		verify_en <= 1'b1;
	end
	else
	begin
		verify_d0 <= din_vld;
		verify_en <= verify_d0;
	end
end
//=======================================================================//
always @ ( posedge clk or negedge rst_n )
begin
	if( rst_n == 0 )
	begin
		fifo_wren_r1 <= 1'b0;
		fifo_wren_r2 <= 1'b0;
		fifo_wren_r3 <= 1'b0;
	end
	else
	begin
		fifo_wren_r1 <= din_vld;
		fifo_wren_r2 <= fifo_wren_r1;
		fifo_wren_r3 <= fifo_wren_r2;
	end
end	

assign fifo_wren = fifo_wren_r3;
//=======================================================================//
always @ (posedge clk or negedge rst_n)
begin
	if( rst_n == 0 )
	begin
		dout_verify_r <= 0;
	end
	else
	begin
		if( verify_en != 0 )
		begin
			if( data_vld != 0 )
			begin
				dout_verify_r <= din;
			end
			else
			begin
				dout_verify_r <= 8'b0100_0001;
			end
		end
		else
		begin
			dout_verify_r <= dout_verify_r;
		end
	end
end
//=======================================================================//
assign dout_verify = dout_verify_r;
//=======================================================================//		
endmodule


4、发送模块(uart_txd)

 发送模块的整体结构与接收模块非常相似,同样至少需要两种功能的计数器,计数器的实现方式是一样的,不同的是发送模块的有效判断和数据发送的实现功能:
 发送模块有效标志uart_txd_flag,将fifo有数据,有效标志就为高电平:

		if( txd_vld  == 0 )
		begin
			uart_txd_flag <= 1;
		end
		else
		begin
			uart_txd_flag <= 0;
		end

 发送模块实现发送功能代码,在采集信号有效下,将数据从低位到高位进行串行发送:

		if(txd_sele!=1'b0)
		begin
			case(txd_num)
			    4'd0 :    begin    txd_r <= 1'b0;       end			//The start bit is low
			    4'd1 :    begin    txd_r <= dout[0];    end 
			    4'd2 :    begin    txd_r <= dout[1];    end	
			    4'd3 :    begin    txd_r <= dout[2];    end
			    4'd4 :    begin    txd_r <= dout[3];    end 
			    4'd5 :    begin    txd_r <= dout[4];    end 
			    4'd6 :    begin    txd_r <= dout[5];    end 
			    4'd7 :    begin    txd_r <= dout[6];    end 
			    4'd8 :    begin    txd_r <= dout[7];    end 
			    4'd9 :    begin    txd_r <= 1'b1;       end			//The end bit is high
			    default : begin    txd_r <= 1'b1;       end
			endcase
		end

  发送模块的整体代码

module uart_txd(
                //INPUT    
                input          clk,              //System clock         
                input          rst_n,            //Reset signal is inverted
			   input          txd_vld,          //UART start sending enable
					 
                input [7:0]    dout,        //UART send data
					 
	             //OUTPUT
                output wire    fifo_rden,   //Enable UART to receive data sent by FIFO
                output wire    txd          //UART txd 
);
//=======================================================================//
parameter    CLK = 50_000_000;         //FPGA System clock frequency
parameter    BPS = 9_600;              //Baud rate
localparam   BPS_CNT = CLK / BPS;  	   //
localparam   BPS_CNT_1 = BPS_CNT / 2;	// 
//=======================================================================//
reg           fifo_rden_r;
reg           txd_sele;        //
reg	        uart_txd_flag;   //UART start action identifier
reg           txd_flag;        //1 byte Send flag bit
reg           txd_r;
reg [3:0]     txd_num;         //Send data bit count
reg [31:0]    clk_cnt;		    //Baud rate count
//=======================================================================//
//  uart_txd_flag:UART start action identifier , FIFO starts only when there is data in it
always @ ( posedge clk or negedge rst_n )
begin
	if( rst_n == 1'b0 )
	begin
		uart_txd_flag <=0 ;
	end
	else
	begin
		if( txd_vld == 0 )
		begin
			uart_txd_flag <= 1;
		end
		else
		begin
			uart_txd_flag <= 0;
		end
	end
end
//=======================================================================//
//  txd_flag:Send enable is detected and send flag bit is generated  
always @ ( posedge clk or negedge rst_n )
begin
	if( rst_n == 1'b0 )
	begin
		txd_flag <= 1'b0;
	end
	else
	begin
		if( uart_txd_flag != 0 )
		begin
			txd_flag <= 1'b1;
		end
		else
		begin
			if( txd_num == 4'd10 )
			begin
				txd_flag <= 1'b0;
			end
			else
			begin
				txd_flag <= txd_flag;
			end
		end
	end
end
//=======================================================================//
// clk_cnt: Generate baud rate count according to baud rate calculation   
always @ ( posedge clk or negedge rst_n )
begin
	if( rst_n == 1'b0 )
	begin
		clk_cnt <= 31'd0;
	end
	else
	begin
		 if( txd_flag != 0 )
		 begin
		     if( clk_cnt <= BPS_CNT - 32'd1 )
		     begin
			      clk_cnt <= clk_cnt + 32'd1;
		     end
		     else
		     begin
			      clk_cnt <= 32'd0;
		     end
		 end
	end
end
//=======================================================================//
// txd_sele:  Collect in the middle of valid data    
always @ ( posedge clk or negedge rst_n )
begin 
	if( rst_n == 0 )
	begin
		txd_sele <= 1'b0;
	end
	else
	begin
		if( clk_cnt == BPS_CNT_1 )		//Start signal transmission in the middle part of frequency division counting
		begin
			txd_sele <= 1'b1;			//The clock edge of the transmission signal falls in the middle of the valid data
		end
		else
		begin
			txd_sele <= 1'b0;
		end
	end
end
//=======================================================================//
// txd_num:  Send data bit count   
always @ (posedge clk or negedge rst_n)
begin
	if( rst_n == 0 )
	begin
		txd_num <= 4'd0;
	end
	else
	begin
		if( txd_sele !=0 && txd_flag != 0 )
		begin
			txd_num <= txd_num + 4'd1;
		end
		else
		begin
			if( txd_num == 4'd10 )
			begin
				txd_num <= 4'd0;
			end
			else
			begin
				txd_num <= txd_num;
			end
		end
	end
end
//=======================================================================//
// uart Send data   
always@( posedge clk or negedge rst_n )
begin 
	if( rst_n == 1'b0 )
	begin
		txd_r <= 1'b1;
	end
	else
	begin
		if(txd_sele!=1'b0)
		begin
			case(txd_num)
			    4'd0 :    begin    txd_r <= 1'b0;       end			//The start bit is low
			    4'd1 :    begin    txd_r <= dout[0];    end 
			    4'd2 :    begin    txd_r <= dout[1];    end	
			    4'd3 :    begin    txd_r <= dout[2];    end
			    4'd4 :    begin    txd_r <= dout[3];    end 
			    4'd5 :    begin    txd_r <= dout[4];    end 
			    4'd6 :    begin    txd_r <= dout[5];    end 
			    4'd7 :    begin    txd_r <= dout[6];    end 
			    4'd8 :    begin    txd_r <= dout[7];    end 
			    4'd9 :    begin    txd_r <= 1'b1;       end			//The end bit is high
			    default : begin    txd_r <= 1'b1;       end
			endcase
		end
		else
		begin
			txd_r <= txd_r;
		end 
	end
end 
//=======================================================================//
//  Generate FIFO send enablele  
always @ ( posedge clk or negedge rst_n )
begin
	if( rst_n == 0 )
	begin
		fifo_rden_r <= 0;
	end
	else
	begin
		if(txd_num == 9 )
		begin
			fifo_rden_r <= 0;
		end
		else
		begin
			fifo_rden_r <= 1;
		end
	end
end
//=======================================================================//
assign txd = txd_r;
assign fifo_rden = fifo_rden_r;
//=======================================================================//
endmodule


5、测试文件

 在进行modelsim仿真时,使用的是Quratus ii与Modelsim联合仿真,通过Quratus ii生成测试文件,在相应的地方进行修改,最后将文件添加到Quratus ii内的仿真测试文件,通过Quratus ii逻辑仿真功能启动Modelsim进行仿真。

  测试文件的整体代码

`timescale 1 ns/ 1 ps
module UART_top_vlg_tst();
reg clk;
reg rst_n;
reg rxd;                                              
wire txd;              
UART_top i1 (
	.clk(clk),
	.rst_n(rst_n),
	.rxd(rxd),
	.txd(txd)
);
initial                                                
begin                                                  
	clk = 0;
	rst_n = 0; 
	rxd= 1; //在复位阶段,将激励赋初值
	#200 rst_n = 1; //延时 200ns  后停止复位
	#110000 rxd= 0;
	#110000 rxd= 0;//发送数据 8'ha4 (8'b0011_0000) ASII 码 0
	#110000 rxd= 0;
	#110000 rxd= 0; 
	#110000 rxd= 0; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 0; 
	#110000 rxd= 0; 
	#110000 rxd= 1;//结束位
	
	
	#1300000;
	#110000 rxd= 0;
	#110000 rxd= 1;//发送数据 8'ha4 (8'b0011_0101) ASII 码 2
	#110000 rxd= 0;
	#110000 rxd= 1; 
	#110000 rxd= 0; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 0; 
	#110000 rxd= 0;
	#110000 rxd= 1;//结束位
	
	
	#1300000;
	#110000 rxd= 0;
	#110000 rxd= 1;//发送数据 8'ha4 (8'b0110_0001)  ASII 码 a
	#110000 rxd= 0;
	#110000 rxd= 0; 
	#110000 rxd= 0; 
	#110000 rxd= 0; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 0; 
	#110000 rxd= 1;//结束位
	
	#1300000;
	#110000 rxd= 0;
	#110000 rxd= 1;//发送数据 8'ha4 (8'b0110_1011)  ASII 码 k
	#110000 rxd= 1;
	#110000 rxd= 0; 
	#110000 rxd= 1; 
	#110000 rxd= 0; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 0; 
	#110000 rxd= 1;//结束位
	
	
	#1300000;
	#110000 rxd= 0;
	#110000 rxd= 0;//发送数据 8'ha4 (8'b0111_1110) ASII 码 ~
	#110000 rxd= 1;
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 0;
	#110000 rxd= 1;//结束位
	
 	#1300000;
	#110000 rxd= 0;
	#110000 rxd= 0;//发送数据 8'ha4 (8'b0010_0000) ASII 码 空格
	#110000 rxd= 0;
	#110000 rxd= 0; 
	#110000 rxd= 0; 
	#110000 rxd= 0; 
	#110000 rxd= 1; 
	#110000 rxd= 0; 
	#110000 rxd= 0; 
	#110000 rxd= 1;//结束位
	
	#1300000;
	#110000 rxd= 0;
	#110000 rxd= 1;//发送数据 8'ha4 (8'b0011_1001) ASII 码 9
	#110000 rxd= 0;
	#110000 rxd= 0; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 1; 
	#110000 rxd= 0; 
	#110000 rxd= 0; 
	#110000 rxd= 1;//结束位
	

		
end   
   
   
always                                                                  
begin                                                  
     #10 clk = ~clk;                                      
end                                                    
endmodule

 在测试文件中模拟PC发送0、2、a、k、~空格 、9;



五、仿真及上机调试

1、Modelsin仿真

仿真图

图5.1 Modelsim仿真图
 仿真图中,当接收数据为0、2、空格 、9时,数据正常发送,当接收数据为a、k、~是,数据发送为A;

2、上机调试

&emsp:可通过微软应用商店下载的串口调试助手作为实验的串口通信收发实验的PC端,FPGA的引脚配置根据FPGA开发板的开发手册上的引脚说明进行配置。
上机调试图

图5.2 上机调试的串口调试助手发送接收数据图

六、说明

 1、在uart_rxd和uart_txd之间加一个FIFO是为了提供一个数据缓存的区域,其中基于FIFO具有读写独立,可以将接受模块和发送模块的波特率设置为不同值来进行UART功能的测试。
 2、其中在三个模块都进行了不同程度同步打拍,主要作用:
  (1)为了让一些使能信号更加稳定,避免亚稳态;
  (2)为了延时时钟,等待数据传输或是检验完毕后才进行操作。
 3、可以在再增加一个波特率选者模块,来控制波特的大小选择。
 4、通过在检验模块中加一个中断fifo的写入,中断过程中可以设置检验到错误的位置和个数,限于时间能力,只有概念而没有做出来。

七、参考资料

1、UART和RS485串口通信原理与实践(ZYNQ、Vivado)
2、基于RS422通信的FPGA软件设计第四天
3、uart接口定义详解介绍(基本结构及工作原理)

  • 7
    点赞
  • 62
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值