FPGA:UART串口接收模块

news/2025/2/26 17:55:09

FPGA:UART串口接收模块

  • UART串口接收
  • 模块分析
  • 模块设计与分析
    • 亚稳态现象进行打拍
    • 边沿检测电路
    • 使能逻辑
    • 波特率分频计数器
    • 位计数器
    • 位接收逻辑
    • 完成标志信号
  • 模块优化
  • 测试仿真
  • 板级验证

声明:本设计中思路和截图均参考 小梅哥FPGA

UART串口接收

串口接收是串口发送的逆操作。在FPGA中,主要指的是别的通信设备通过串口发送数据到FPGA中,再通过FPGA内的指令解析模块对接受到的数据进行解码。例如,在ADC采集中,可以通过串口发送ADC采集时应该选择的通道、采样深度等信息。在串口接收中,主要涉及的参数和串口发送时的参数类似。即波特率、起始位、停止位、校验位等。
在这里,以下列几个要求为例,设计一个UART串口接收模块:
①能够接收 8 8 8位数据位、无校验位、 1 1 1位停止位的UART串口数据
②能够以一定的方式修改波特率(参数重定义)
③每接受完一个数据,就将接受的数据结果显示在开发板的8位LED灯上。

模块分析

在这里插入图片描述
如上图所示,为了准确的获得 B i t 0 ∼ B i t 7 Bit0\sim Bit7 Bit0Bit7的数据,需要考虑以下问题:
(1)在数据变化时,其不是瞬间从逻辑 0 0 0编程逻辑 1 1 1,为了获取准确对应位的数据,我们取每一位的中间位进行采样,即在位时间的中间时刻进行采样,确保数据的准确性。因此需要一个波特率分频计数器
(2)为了检测起始位,还需要一个边沿检测逻辑,这里主要是下降沿逻辑,因为空闲时为 1 1 1,起始为 0 0 0,通过一个D触发器判断前一个时刻和当前时刻的值即可实现。
(3)波特率分频计数器并不是一直工作的,而是检测到发送方发送数据后才开始工作,因此,这里需要设计一个波特率计数器使能逻辑
(4)为了确定发送哪一位,此时还需要设计一个位计数器
(5)因为输入信号(uart_rx)相对于时钟信号而言,是一个异步信号,可能不在时钟的上升沿来临,导致数据无法正确的保存在D触发器中,为了防止这种异步信号导致的“亚稳态现象”,可以引入D触发器进行打拍操作,对于 50 M 50M 50M的晶振,一般选用两级的打拍即可滤除这种现象。
(6)发送逻辑,即在每一位的中点将对应位进行采样。
(7)在完成一次数据发送的时候,需要引入一个发送标志信号,告知其他设备此次传输完成。

模块设计与分析

在这里插入图片描述

亚稳态现象进行打拍

为了防止异步信号的影响,引入两级D触发器进行打拍操作。

reg dff0_uart_rx,dff1_uarx_rx;
// 防止亚稳态出现,进行打拍同步
always @(posedge Clk)
	dff0_uart_rx<=uart_rx;
always @(posedge Clk)
	dff1_uarx_rx<=dff0_uart_rx;

边沿检测电路

因为起始位为0,空闲时一直是逻辑1,所以此时需要设计一个下降沿检测逻辑,主要判断前一个时刻uart_rx1且当前时刻uart_rx0,则说明下降沿来临。

wire nedge_uart_rx;   //下降沿信号
always @(posedge Clk)
	r_uart_rx<=dff1_uarx_rx;
assign nedge_uart_rx = (dff1_uarx_rx==0)&&(r_uart_rx==1);

使能逻辑

通过前面的分析,我们的思路是当检测到下降沿的时候,则认为传输开始,波特率分频计数器开始工作,所以定义一个使能信号(en_baud_cnt),其逻辑如下:复位时置 0 0 0,当检测到下降沿的时候(边沿检测电路中nedge_uart_rx为1),则置 1 1 1,当一次数据传输完成后,即位计数器计满且波特率分频计数器计满(即停止位发送完毕)时再次将该信号置 0 0 0此时还有一种情况,即当检测到下降沿的时候,第一位(起始位)如果为 1 1 1,那这个时候我们将忽略这一次的下降沿,不然使能逻辑置 1 1 1

//波特率计数器使能逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
	en_baud_cnt<=1'b0;
else if(nedge_uart_rx)   //检测到下降沿开始计数
	en_baud_cnt<=1'b1;
else if((Bit_cnt==0)&&(Baud_div_cnt==MCNT_BAUD)&&(dff1_uarx_rx==1'b1))   //起始位为1的情况
	en_baud_cnt<=1'b0;
else if((Bit_cnt==9)&&(Baud_div_cnt==MCNT_BAUD))
	en_baud_cnt<=1'b0;

波特率分频计数器

在前面设置了波特率后,对每一位的传输时间进行计算,设时钟频率为CLOCK_FREQ,波特率为Baud_set,则传输1位数据需要的时钟周期数位:CLOCK_FREQ/Baud_set,而计数器从零开始计数,所以令MCNT_BAUD=CLOCK_FREQ/Baud_set-1

always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
	Baud_div_cnt<=0;
else if(en_baud_cnt)
		begin
			if(Baud_div_cnt==MCNT_BAUD)
				Baud_div_cnt<=30'b0;
			else	
				Baud_div_cnt<=Baud_div_cnt+1'b1;
		end
else
	Baud_div_cnt<=30'b0;

位计数器

当传输数据时,还需要判断当前传输的是哪一位,这时需要一个位计数器,根据协议可知,数据位有 8 8 8位,加上起始位和停止位一共有 10 10 10位数据,所以需要一个4位计数器才可以计数到 10 10 10.其逻辑如下:当波特率分频计数器技术满时,判断其值是否为 9 9 9,如果是,则置 0 0 0,否则自加。这里不需要再额外判断是否使能,因为使用的波特率分频计数器已经判断过。

//位计数器
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
	Bit_cnt<=0;
else if((Bit_cnt==9)&&(Baud_div_cnt==MCNT_BAUD))
	Bit_cnt<=0;
else if(Baud_div_cnt==MCNT_BAUD)
	Bit_cnt<=Bit_cnt+1'b1;
else 
	Bit_cnt<=Bit_cnt;

位接收逻辑

在这里,依旧使用一个case语句,判断位计数器Bit_cnt的值,将其正确的复制给对应的位,为了使得接受的数据是完整的变化,这里定义一个临时的8位寄存器r_Data,接受打拍后的uart数据,即dff1_uart_rx。在每次波特率分频计数器计数到中间时进行采样。代码如下:

// 位接受逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
	r_Data<=8'b0000_0000;
else if(Baud_div_cnt==MCNT_BAUD/2)   //在中间时刻进行采样
	begin
		case(Bit_cnt)
			1:r_Data[0]<=dff1_uarx_rx;
			2:r_Data[1]<=dff1_uarx_rx;
			3:r_Data[2]<=dff1_uarx_rx;
			4:r_Data[3]<=dff1_uarx_rx;
			5:r_Data[4]<=dff1_uarx_rx;
			6:r_Data[5]<=dff1_uarx_rx;
			7:r_Data[6]<=dff1_uarx_rx;
			8:r_Data[7]<=dff1_uarx_rx;
			default:r_Data<=r_Data;
		endcase
	end

完成标志信号

接收完成后,还需要给外界一个信号表示接收完成,即当波特率分频计数器计满且位计数器计数到 9 9 9的时候,表示接受完成。此时将之前存储在临时寄存器r_Data上的值全部复制给Data。

assign w_Rx_done = ((Baud_div_cnt==MCNT_BAUD/2)&&(Bit_cnt==9));
always @(posedge Clk)
	Rx_done<=w_Rx_done;
always@(posedge Clk)
	if(w_Rx_done)
		Data<=r_Data;

模块优化

在上述描述中,还有部分功能需要优化。
首先,在实际应用中,如果FPGA与设备A进行串口通信,由于时钟不同,会带来一定的误差,从而导致数据出现错误。例如:设备A连续发送两个数据给FPGA,8’h00和8’h01,由于极小的误差,FPGA在接受最后一位的时候可能设备A已经发送下一个数据的起始位,从而导致数据之间进行丢失和出错。为了解决这个问题,一般我们在最后一位,即停止位计数到中间,即采样结束后就让设备向外界输出完成标志信号,剩下半个位时间进行容错。在代码上体现就是在每个Bit_cnt==9时,如果波特率分频计数器等于计数参数一半时,就表示结束。
其次,在面对强干扰的情况下,上述在每个波特率分频计数器中间采样可能出错,此时可以使用多次采样求概率的方法进行排除。例如,在一个位时间内,再进行16次采样,去掉前面5次和后面4次的数据,对中间7次数据进行概率统计,如果1出现的次数多,则表示采集的为1,否则为0.这个方法将在下面一个帖子进行阐述。
下面是在第一个问题基础上优化后的代码,整合好如下所示:

`timescale 1ns / 1ps
module uart_byte_rx(
	input Clk,
	input Reset_n,
	input uart_rx,
	output reg [7:0]Data,
	output reg Rx_done
    );
parameter CLOCK_FREQ=50_000_000;   //时钟晶振频率50M
parameter Baud_Set = 9600;         //波特率设置为9600
parameter MCNT_BAUD = CLOCK_FREQ/Baud_Set-1;  // 位计数器计数时间
/*参数定义区*/
reg [29:0] Baud_div_cnt;
reg [3:0] Bit_cnt;
reg en_baud_cnt;   // 计数器使能信号,检测到下降沿开始计数
reg r_uart_rx;
wire nedge_uart_rx;   //下降沿信号
wire w_Rx_done;
reg [7:0]r_Data;
reg dff0_uart_rx,dff1_uarx_rx;
// 防止亚稳态出现,进行打拍同步
always @(posedge Clk)
	dff0_uart_rx<=uart_rx;
always @(posedge Clk)
	dff1_uarx_rx<=dff0_uart_rx;
// 下降沿检测电路
always @(posedge Clk)
	r_uart_rx<=dff1_uarx_rx;
assign nedge_uart_rx = (dff1_uarx_rx==0)&&(r_uart_rx==1);

// 波特率分频计数器
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
	Baud_div_cnt<=0;
else if(en_baud_cnt)
		begin
			if(Baud_div_cnt==MCNT_BAUD)
				Baud_div_cnt<=30'b0;
			else	
				Baud_div_cnt<=Baud_div_cnt+1'b1;
		end
else
	Baud_div_cnt<=30'b0;

//波特率计数器使能逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
	en_baud_cnt<=1'b0;
else if(nedge_uart_rx)   //检测到下降沿开始计数
	en_baud_cnt<=1'b1;
else if((Bit_cnt==0)&&(Baud_div_cnt==MCNT_BAUD/2)&&(dff1_uarx_rx==1'b1))   //起始位为1的情况
	en_baud_cnt<=1'b0;
else if((Bit_cnt==9)&&(Baud_div_cnt==MCNT_BAUD/2))
	en_baud_cnt<=1'b0;

//位计数器
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
	Bit_cnt<=0;
else if((Bit_cnt==9)&&(Baud_div_cnt==MCNT_BAUD/2))
	Bit_cnt<=0;
else if(Baud_div_cnt==MCNT_BAUD)
	Bit_cnt<=Bit_cnt+1'b1;
else 
	Bit_cnt<=Bit_cnt;

// 位接受逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
	r_Data<=8'b0000_0000;
else if(Baud_div_cnt==MCNT_BAUD/2)   //在中间时刻进行采样
	begin
		case(Bit_cnt)
			1:r_Data[0]<=dff1_uarx_rx;
			2:r_Data[1]<=dff1_uarx_rx;
			3:r_Data[2]<=dff1_uarx_rx;
			4:r_Data[3]<=dff1_uarx_rx;
			5:r_Data[4]<=dff1_uarx_rx;
			6:r_Data[5]<=dff1_uarx_rx;
			7:r_Data[6]<=dff1_uarx_rx;
			8:r_Data[7]<=dff1_uarx_rx;
			default:r_Data<=r_Data;
		endcase
	end
// 接受完成标志信号
assign w_Rx_done = ((Baud_div_cnt==MCNT_BAUD/2)&&(Bit_cnt==9));
always @(posedge Clk)
	Rx_done<=w_Rx_done;
always@(posedge Clk)
	if(w_Rx_done)
		Data<=r_Data;
endmodule

测试仿真

编写testbench文件,进行仿真测试,文件如下:

`timescale 1ns / 1ps
module uart_byte_rx_tb();
reg Clk;
reg Reset_n;
reg uart_rx;
wire [7:0] Data;
wire Rx_done;
uart_byte_rx uart_byte_rx(
	.Clk(Clk),
	.Reset_n(Reset_n),
	.uart_rx(uart_rx),
	.Data(Data),
	.Rx_done(Rx_done)
);
initial Clk=1;
always #10 Clk=~Clk;
initial begin
Reset_n=0;
uart_rx=1;
#201;
Reset_n=1;
#200;
// 8'b0101_0101
uart_rx=0;      #(5208*20);   //起始位
uart_rx=1;      #(5208*20);   //Bit0
uart_rx=0;      #(5208*20);   //Bit1
uart_rx=1;      #(5208*20);   //Bit2
uart_rx=0;      #(5208*20);   //Bit3
uart_rx=1;      #(5208*20);   //Bit4
uart_rx=0;      #(5208*20);   //Bit5
uart_rx=1;      #(5208*20);   //Bit6
uart_rx=0;      #(5208*20);   //Bit7
uart_rx=1;      #(5208*20);   //停止位
#(5208*20*10);

// 8'b1010_1010
uart_rx=0;      #(5208*20);   //起始位
uart_rx=0;      #(5208*20);   //Bit0
uart_rx=1;      #(5208*20);   //Bit1
uart_rx=0;      #(5208*20);   //Bit2
uart_rx=1;      #(5208*20);   //Bit3
uart_rx=0;      #(5208*20);   //Bit4
uart_rx=1;      #(5208*20);   //Bit5
uart_rx=0;      #(5208*20);   //Bit6
uart_rx=1;      #(5208*20);   //Bit7
uart_rx=1;      #(5208*20);   //停止位
#(5208*20*10);

// 8'b1111_0000
uart_rx=0;      #(5208*20);   //起始位
uart_rx=0;      #(5208*20);   //Bit0
uart_rx=0;      #(5208*20);   //Bit1
uart_rx=0;      #(5208*20);   //Bit2
uart_rx=0;      #(5208*20);   //Bit3
uart_rx=1;      #(5208*20);   //Bit4
uart_rx=1;      #(5208*20);   //Bit5
uart_rx=1;      #(5208*20);   //Bit6
uart_rx=1;      #(5208*20);   //Bit7
uart_rx=1;      #(5208*20);   //停止位
#(5208*20*10);

// 8'b0000_1111
uart_rx=0;      #(5208*20);   //起始位
uart_rx=1;      #(5208*20);   //Bit0
uart_rx=1;      #(5208*20);   //Bit1
uart_rx=1;      #(5208*20);   //Bit2
uart_rx=1;      #(5208*20);   //Bit3
uart_rx=0;      #(5208*20);   //Bit4
uart_rx=0;      #(5208*20);   //Bit5
uart_rx=0;      #(5208*20);   //Bit6
uart_rx=0;      #(5208*20);   //Bit7
uart_rx=1;      #(5208*20);   //停止位
#(5208*20*10);
$stop;
end
endmodule

依次模拟上位机向FPGA发送四次数据,仿真结果如下所示:
在这里插入图片描述

取其中第一次数据传输进行分析,即8’b0101_0101。
在这里插入图片描述
完成标志信号在最后一位传输到中间时刻(5208-1)/2且位计数器计数到9的时候,完成标志信号拉高,持续一个时钟周期,且此时Data的数据更新为r_Data中的数据。且整体波形和仿真文件中一致。

板级验证

管脚绑定后生成比特流写入开发板。打开串口通信软件,选择对应的波特率,并将接受的8位数据分配给开发板上的8位LED灯。
1、发送FF,8位LED灯全亮
在这里插入图片描述
在这里插入图片描述
2、发送AA(10101010),LED灯交替亮。
在这里插入图片描述
本次实验结束。


http://www.niftyadmin.cn/n/5869020.html

相关文章

在 Windows 下的 Docker 中安装 R语言

以下是在 Windows 系统的 Docker 中安装 R 语言的详细教程&#xff0c;包括 Docker 的安装、配置以及如何在容器中运行 R 语言的步骤。 步骤 1&#xff1a;安装 Docker 下载 Docker Desktop 访问 Docker 官方网站&#xff1a;Docker Desktop: The #1 Containerization Tool for…

2025-02-25 学习记录--C/C++-用C语言实现删除字符串中的子串

用C语言实现删除字符串中的子串 在C语言中&#xff0c;你可以使用strstr函数来查找子串&#xff0c;然后用memmove或strcpy来覆盖或删除找到的子串。 一、举例 &#x1f430; #include <stdio.h> // 包含标准输入输出库&#xff0c;用于使用 printf 函数 #include <s…

CentOS停服后的替代选择:openEuler、Rocky Linux及其他系统的未来展望

CentOS停服后的替代选择&#xff1a;openEuler、Rocky Linux及其他系统的未来展望 引言CentOS停服的背景华为openEuler&#xff1a;面向未来的开源操作系统1. 简介2. 特点3. 发展趋势 Rocky Linux&#xff1a;CentOS的精神继承者1. 简介2. 特点3. 发展趋势 其他可选的替代系统1…

Linux | 进程控制(进程终止与进程等待)

文章目录 Linux | 进程控制 — 进程终止 & 进程等待1、进程终止进程常见退出方法1.1退出码基本概念获取退出码的方式常见退出码约定使用场景 1.2 strerror函数 & errno宏1.3 _exit函数1.4_exit和exit的区别1.4.1 所属头文件与函数原型1.4.2 执行过程差异**结合现象分析…

第八章:虚拟机模块的整合

目录 第一节&#xff1a;代码实现 1-1.前置代码 1-2.成员变量 1-3.构造函数 1-4.交换机接口 1-5.队列接口 1-6.绑定接口 1-7.消息接口 1-8.其他功能接口 第二节&#xff1a;单元测试 下期预告&#xff1a; 虚拟机模块在mqserver目录下实现。 第一节&#xff1a;代码实现 虚…

解决IDEA使用Ctrl + / 注释不规范问题

问题描述&#xff1a; ctrl/ 时&#xff0c;注释缩进和代码规范不一致问题 解决方式 设置->编辑器->代码样式->java->代码生成->注释代码

Android NFC功能开发指南

在 Android 平台上开发 NFC&#xff08;近场通信&#xff09;功能&#xff0c;主要涉及以下几个步骤&#xff1a; 1. 权限声明 首先&#xff0c;在 AndroidManifest.xml 文件中声明 NFC 权限&#xff1a; <uses-permission android:name"android.permission.NFC&quo…

数据分析七大步骤

在工作中&#xff0c;我们可能都遭遇过面对一堆数据&#xff0c;费尽心思进行分析&#xff0c;结果却惨不忍睹&#xff0c;仿佛“一顿操作猛如虎&#xff0c;一看结果0:5”。更糟糕的是&#xff0c;有时我们甚至完全找不到数据分析的头绪。 别急&#xff0c;朋友们&#xff01…