网络知识 娱乐 FPGA-Verilog实现uart串口异步通信

FPGA-Verilog实现uart串口异步通信

文章目录

  • 前言
  • 一、Uart串口通信
  • 二、串口异步通信实现
    • 1.程序框图
    • 2.波特率设置模块
    • 3.串口发送控制模块
    • 4.串口发送控制模块
  • 三、结果
    • 1、仿真结果
    • 2、板级调试结果
  • 设计文件与仿真文件


前言

(完整代码在文末,包括仿真文件与设计文件,通过仿真与板级验证)本文利用verilog语言实现uart串口异步通信,FPGA接收串口发来的数据,并将接收到的数据通过tx端发送到PC端,在PC端串口打印显示数据
开发板:SF-AT7
软件平台:Vivado 2016.2


一、Uart串口通信

  • uart串口通信是一种异步串行全双工通信方式,tx端用于数据发送,rx端用于数据接收。信号线空闲时为高电平。
  • 由于是异步通信方式,数据发送会包装成数据帧的形式发送,帧格式为:1个起始位(0)、8个数据位(用户数据)、1个奇偶校验位(用于简单的纠错以保证传输的可靠性)、1和2个停止位(1),其中奇偶校验位不是必须的。下图为帧格式结构。
    在这里插入图片描述
  • 那么该如何检测到数据发送呢?可以注意到数据帧格式中第一个bit是低电平,当FPGA的rx端检测到信号线上有下降沿产生时,表示有数据传送过来,根据预先设置好的波特率对数据接收接收,由于数据是串行从低位到高位传输,接收到的数据暂时存储在寄存器中,待接收完1字节的数据,通过串并转换保存接收到的数据。
  • 发送时通过tx信号线按照设置好的波特率将数据发送出去,数据发送仍然要按照数据帧格式发送,即先发送1bit的低电平,再从低位到高位发送数据。

二、串口异步通信实现

1.程序框图

FPGA实时检测uart_rx信号是否有数据,若接收到数据,你把接收到的数据通过uart_tx发送给PC端。
我们知道串口数据传输在设置的波特率下进行,因此需要有专门的波特率产生单元,且发送与接收分别对应一个波特率产生单元,将波特率设置包装成一个模块,分别在发送与接收端例化该模块即可,这是两个独立的硬件资源,属于逻辑复制,而并非资源共享。
整个程序具体实现过程就是有一个顶层模块,在顶层模块中例化波特率设置模块、串口发送与接收处理模块。
在这里插入图片描述

speed_setting		u2_speed_rx(	
							.clk(clk_25m),	//波特率选择模块
							.rst_n(sys_rst_n),
							.bps_start(bps_start1),
							.clk_bps(clk_bps1)
						);

	//UART接收数据处理
my_uart_rx_q			u3_my_uart_rx(		
							.clk(clk_25m),	//接收数据模块
							.rst_n(sys_rst_n),
							.uart_rx(uart_rx),
							.rx_data(rx_data),
							.rx_int(rx_int),
							.clk_bps(clk_bps1),
							.bps_start(bps_start1)
						);
		
//-------------------------------------

	//UART发送信号波特率设置													
speed_setting		u4_speed_tx(	
							.clk(clk_25m),	//波特率选择模块
							.rst_n(sys_rst_n),
							.bps_start(bps_start2),
							.clk_bps(clk_bps2)
						);
						
	//UART发送数据处理
my_uart_tx_q			u5_my_uart_tx(		
							.clk(clk_25m),	//发送数据模块
							.rst_n(sys_rst_n),
							.rx_data(rx_data),
							.rx_int(rx_int),
							.uart_tx(uart_tx),
							.clk_bps(clk_bps2),
							.bps_start(bps_start2)
						);

2.波特率设置模块

  • 前面说过波特率设置本质是一个计数器,常用的波特率如9600、115200等是指1s內传输数据的个数,1/9600就是一个周期所对应的时长。以25MHz为例,一个周期是40ns ,那么计数周期就是:1/9600/40ns*1000000000(注意单位统一),设置一个计数器,当计数次数达到9600bps时,标志位有一个周期的高脉冲信号,该标志位信号用来控制tx端传送数据位的切换或者rx端接收数据位的切换。
  • 根据上面的分析结果,利用verilog语言编程实现,可以用一个always块来实现计数器,另一个always块实现标志位置位。为了保证数据采集更加准备,我们选择在数据传送中间采样,即计数器记到9600一半的时候进行数据采样。
  • 针对接收端,uart端口rx端接收到数据总线的信号。总线上数据都是以字节的形式传输,而uart通信协议中规定数据是串行接收的,因此接收时要进行串并转换,串并转换的速率由波特率决定。接收到信号后波特率设置模块开始计数,定时产生维持一个周期高电平的采样信号。
    代码如下:
module speed_setting(
				input clk,
				input rst_n,
				input bps_start,
				output clk_bps
			);
`define BPS_9600
`define CLK_PERIORD	40	//定义时钟周期为40ns(25MHz)
`define BPS_SET		9600	//定义通信波特率为9600bps(将需要的波特率省去两个零后定义即可)

`define BPS_PARA	(1_000_000_000/`CLK_PERIORD/`BPS_SET)//10_000_000/`CLK_PERIORD/96;		//波特率为9600时的分频计数值
`define BPS_PARA_2	(`BPS_PARA/2)//BPS_PARA/2;	//波特率为9600时的分频计数值的一半,用于数据采样

reg[12:0] cnt;			//分频计数
reg clk_bps_r;			//波特率时钟寄存器
//----------------------------------------------------------
reg[2:0] uart_ctrl;	// uart波特率选择寄存器

//计数器计数
always@(posedge clk or negedge rst_n)begin
	if(!rst_n) cnt <= 13'd0 ;
	else if(bps_start)begin
		if(cnt < BPS_PARA) cnt <= cnt + 13'd1 ;
		else cnt <= 13'd0 ;
	end
	else cnt <= 13'd0 ;
end
//采样信号标志位
always@(posedge clk or negedge rst_n)begin
	if(!rst_n) clk_bps_r <= 1'b0 ;
	else if(cnt == BPS_PARA_2) clk_bps_r <= 1'b1 ;
	else  clk_bps_r <= 1'b0 ;
end
assign clk_bps = clk_bps_r ;
endmodule
	

3.串口发送控制模块

该模块实现对UART接收信号uart_rx进行解码,并实现串并转换,并将数据保存在【rx_data[7:0]】中。具体程序内部实现框图如下:
在这里插入图片描述

  1. 下降沿检测
    通过检测信号【uart_rx】信号下降沿。来判断是否有信号发送过来。边沿检测一般会利用多个锁存器,所存uart_rx的状态,根据前后状态的逻辑运算结果进行判断,如果前一个状态是高电平,最新的状态为低电平,则表示出现了下降沿。
module my_uart_rx(
				clk,rst_n,
				uart_rx,rx_data,rx_int,
				clk_bps,bps_start
			);

input clk;		// 25MHz主时钟
input rst_n;	//低电平复位信号
input uart_rx;	// RS232接收数据信号
input clk_bps;	// clk_bps的高电平为接收或者发送数据位的中间采样点
output bps_start;		//接收到数据后,波特率时钟启动信号置位
output[7:0] rx_data;	//接收数据寄存器,保存直至下一个数据来到 
output rx_int;	//接收数据中断信号,接收到数据期间始终为高电平

//----------------------------------------------------------------
reg uart_rx0,uart_rx1,uart_rx2,uart_rx3;	//接收数据寄存器,滤波用
wire neg_uart_rx;	//表示数据线接收到下降沿

always @ (posedge clk or negedge rst_n) 
	if(!rst_n) begin
		uart_rx0 <= 1'b0;
		uart_rx1 <= 1'b0;
		uart_rx2 <= 1'b0;
		uart_rx3 <= 1'b0;
	end
	else begin
		uart_rx0 <= uart_rx;
		uart_rx1 <= uart_rx0;
		uart_rx2 <= uart_rx1;
		uart_rx3 <= uart_rx2;
	end
assign neg_uart_rx = uart_rx3 & uart_rx2 & ~uart_rx1 & ~uart_rx0;
  1. 波特率控制模块
    该模块因为信号下降沿的到来,使得【bps_start】信号置为1,启动波特率设置模块。

  2. 计数采样模块
    由于数据从低位到高位串行传输,所以要先用一个寄存器暂存数据,且第一位是标识位,不应该将其存储在寄存器中,采用【num】从0开始计数,计数到1时在每个波特率高电平有效的一个周期内从低位到高位保存数据。

always @ (posedge clk or negedge rst_n)
	if(!rst_n) begin
		rx_temp_data <= 8'd0;
		num <= 4'd0;
		rx_data_r <= 8'd0;
	end
	else if(rx_int) begin	//接收数据处理
		if(clk_bps) begin	//读取并保存数据,接收数据为一个起始位,8bit数据,1或2个结束位		
			num <= num+1'b1;
			case (num)
				4'd1: rx_temp_data[0] <= uart_rx;	//锁存第0bit
				4'd2: rx_temp_data[1] <= uart_rx;	//锁存第1bit
				4'd3: rx_temp_data[2] <= uart_rx;	//锁存第2bit
				4'd4: rx_temp_data[3] <= uart_rx;	//锁存第3bit
				4'd5: rx_temp_data[4] <= uart_rx;	//锁存第4bit
				4'd6: rx_temp_data[5] <= uart_rx;	//锁存第5bit
				4'd7: rx_temp_data[6] <= uart_rx;	//锁存第6bit
				4'd8: rx_temp_data[7] <= uart_rx;	//锁存第7bit
				default: ;
			endcase
		end
		else if(num == 4'd9) begin		//我们的标准接收模式下只有1+8+1(2)=11bit的有效数据
			num <= 4'd0;			//接收到STOP位后结束,num清零
			rx_data_r <= rx_temp_data;	//把数据锁存到数据寄存器rx_data中
		end
	end

assign rx_data = rx_data_r;	

4.串口发送控制模块

uart接收端接收数据完成后,通过【uart_tx】端将接收到的数据【rx_data】f发送出去。程序流程框图如下:
在这里插入图片描述
注意:因为要完全按照帧格式发送数据,所以要先发送一个波特率周期的低电平,在从低到高传输数据位。

reg uart_tx_r ;
always@(posedge clk or negedge rst_n)begin
	if(!rst_n)begin
		num <= 4'd0 ;
		uart_tx_r <= 1'b1;
	end
	else if(tx_en)begin
		if(clk_bps)begin
			num <= num + 4'd1 ;
			case(num)
				4'd0 : uart_tx_r <= 1'b0 ;
				4'd1 : uart_tx_r <= tx_data[0] ;
				4'd2 : uart_tx_r <= tx_data[1]  ;
				4'd3 : uart_tx_r <= tx_data[2] ;
				4'd4 : uart_tx_r <= tx_data[3] ;
				4'd5 : uart_tx_r <= tx_data[4] ;
				4'd6 : uart_tx_r <= tx_data[5] ;
				4'd7 : uart_tx_r <= tx_data[6] ;
				4'd8 : uart_tx_r <= tx_data[7] ;
				4'd9 : uart_tx_r <= 1'b1 ;
				default: uart_tx_r <= 1'b1;
			endcase
		end
		else if(num == 4'd10)begin
			num <= 4'd0 ;
			
		end
	end
end
assign uart_tx = uart_tx_r;

三、结果

1、仿真结果

  • 仿真波形可以看到,uart_rx端将信号【8‘haa】接收过来,且是在每个bps周期的中间接收,然后再通过【uart_tx】端发送出去,在每个【clk_bps】有效的一个时钟周期内从低位到高位传输接收到的数据
    在这里插入图片描述

2、板级调试结果

  • 利用串口调试工具,发送8bit数据【aa】,可以看到接收端成功接收到该数据。

在这里插入图片描述

设计文件与仿真文件

https://pan.baidu.com/s/1dnoyo84pEtMlk5fSQdaqSA 提取码:p4q1