【FPGA】——UART串口通信

本文详细介绍了UART串口通信的工作原理,包括异步串行通信、帧结构和波特率。通过Verilog代码展示了如何在FPGA中实现UART的接收和发送子模块,包括信号检测、数据采样和并行到串行的转换。同时,给出了接收和发送的完整时序图,验证了设计的正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UART串口简介

  串行通信分为两种方式:同步串行通信和异步串行通信。同步串行通信要求通信双方使用同一时钟,异步则没有这个要求。UART是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receiver-transmitter),它在发送数据时将接收到的串行数据转成并行数据。

通信时序

在这里插入图片描述
  如上图所示,一帧数据由起始位(低电平),数据位(5/6/7/8),校验位(奇/偶),停止位(高电平1/1.5/2)构成,数据传输的速率用波特率(每秒传输的比特数bps)来表示。
  在以往的单片机开发中,串口往往作为一种外设集成进单片机中,我们只需要通过配置寄存器来确定通信的参数即可。当然,也可以通过软件模拟通信协议。而这里,我们通过FPGA来实现纯数字电路上的串口通信,这里的任务即上位机通过串口发送数据给FPGA,然后FPGA实现回传。

顶层模块设计

在这里插入图片描述
  上图为顶层模块的设计框图,系统时钟和复位信号不必多说,顶层模块有一个串口的接收信号(传给串口接收子模块)和一个串口的发送信号(传给串口发送子模块)。
  在顶层模块中,例化了串口串口接收子模块和串口发送子模块,分别用来处理串口接收到的数据和串口需要发送的数据。

//uart串口收发程序,顶层模块
module uart_top (
	input 	sys_clk,
	input 	sys_rst_n,
	input		uart_rxd,	//FPGA串口接收端
	output	uart_txd		//FPGA串口发送端
);

parameter CLK_FREQ = 50000000;
parameter UART_BPS = 115200;

//模块之间的交互一定要用线网变量
wire [7:0]	uart_data;
wire			uart_done;

//例化串口接收子模块
uart_recv #(
	.CLK_FREQ		(CLK_FREQ),
	.UART_BPS		(UART_BPS)
)u_uart_recv(
	.sys_clk			(sys_clk),
	.sys_rst_n		(sys_rst_n),
	.uart_rxd		(uart_rxd),		//接收端口的信号
	.uart_done		(uart_done),	//接收完成
	.uart_data		(uart_data)		//接收的数据,并行
);

//例化串口发送子模块
uart_send #(
	.CLK_FREQ		(CLK_FREQ),
	.UART_BPS		(UART_BPS)
)u_uart_send(
	.sys_clk			(sys_clk),
	.sys_rst_n		(sys_rst_n),
	
	.uart_en			(uart_done),	//发送使能信号
	.uart_data		(uart_data),	//需要发送的串口数据,并行
	.uart_txd		(uart_txd),		//串口发送端
);

endmodule
串口接收子模块设计
  • 时钟信号、复位信号、串口接收信号均从顶层模块引入
  • 通过信号线上的下降沿确定起始位,开始接收(下降沿通过两个变量的阻塞赋值实现)
  • 开始接收后,需要对时钟数进行计数,到达规定时钟数进入下一个bit的接收,此时bit数也要+1
  • 每一次在时钟数的中间在信号线上进行采样,注意这里的时钟均指一个bit内的时钟
  • 采用一个寄存器专门存放接收到的数据,即串转并
  • 接收到第9个比特(停止位)时跳出,认为接受完成,并输出接收完成信号
//串口接收子模块
module uart_recv (
	input 					sys_clk,
	input 					sys_rst_n,
	input					uart_rxd,		//接收端口的信号
	output	reg				uart_done,		//接收完成
	output	reg[7:0]		uart_data		//接收的数据,并行
);

parameter 	CLK_FREQ = 50000000;			//时钟频率,50M
parameter	UART_BPS = 115200;				//波特率,bit/s
parameter	BPS_CNT = CLK_FREQ/UART_BPS;	//每个bit传输需要的时钟数

reg				uart_rxd_d0;
reg				uart_rxd_d1;
reg	[15:0]		clk_cnt;		//时钟数,最大到BPS_CNT
reg	[3:0]		rx_cnt;			//接收数据bit的个数,最大到9
reg	[7:0]		rxdata;			//接收的数据寄存器
reg				rx_flag;		//接收数据的标志位


wire				start_flag;	//起始位开始的标志位

//起始位触发(捕获下降沿),d1延后d0一个周期,所以当d0为0,d1为1,意味着下降沿
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0);

//d1和d0都是数据线上信号,保证d1延后d0一个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n) begin	//复位时数据信号清0
		uart_rxd_d0 <= 1'b0;
		uart_rxd_d1 <= 1'b0;
	end
	else begin
		uart_rxd_d0 <= uart_rxd;
		uart_rxd_d1 <= uart_rxd_d0;
	end
		
end

//确立信号接收标志位
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n)
	rx_flag <= 1'd0;
	else begin
		if (start_flag)
			rx_flag <= 1'd1;
		else if ((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))	//判断停止位,在第9个bit的中间时钟处
			rx_flag <= 1'd0;
		else
			rx_flag <= rx_flag;
	end
end

//对时钟进行计数(最大到BPS_CNT),对接收的bit数进行计数,
//根据这两个数判断停止位
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n) begin				//复位时计数都清零
		rx_cnt <= 4'd0;
		clk_cnt <= 16'd0;
	end
	else if (rx_flag) begin				//开始数据传输
		if (clk_cnt < BPS_CNT)
			clk_cnt <= clk_cnt + 1'b1;
		else begin						//经过了一个bit的时钟数
			clk_cnt <= 1'b0;			//时钟数清零
			rx_cnt <= rx_cnt + 1'b1;	//bit数加1,1bit即从第一个数据位开始,包含了起始位
		end
	end	
	else begin
		clk_cnt <= 16'd0;
		rx_cnt <= 4'd0;
	end
end

//寄存接收数据
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n)
		rxdata <= 8'd0;
	else if (rx_flag) 
		if (clk_cnt == BPS_CNT/2) begin	//在一个比特的时钟中间处对数据进行接收
			case (rx_cnt)						//寄存接收数据
				4'd1:	rxdata[0] <= uart_rxd_d1;
				4'd2:	rxdata[1] <= uart_rxd_d1;
				4'd3:	rxdata[2] <= uart_rxd_d1;
				4'd4:	rxdata[3] <= uart_rxd_d1;
				4'd5:	rxdata[4] <= uart_rxd_d1;
				4'd6:	rxdata[5] <= uart_rxd_d1;
				4'd7:	rxdata[6] <= uart_rxd_d1;
				4'd8:	rxdata[7] <= uart_rxd_d1;
				default:	;
			endcase
		end
		else
		rxdata <= rxdata;
	else
		rxdata <= 8'd0;
end

//判断接收数据是否完成以及把数据赋给输出信号
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n) begin
		uart_done <= 1'd0;
		uart_data <= 8'd0;
	end
	else if (rx_cnt == 4'd9) begin
		uart_data <= rxdata;
		uart_done <= 1'd1;
	end
	else begin
		uart_data <= 8'd0;
		uart_done <= 1'd0;
	end
end

endmodule
串口发送子模块设计
  • 时钟信号、复位信号来自顶层模块,需要发送的数据(并行)以及发送使能信号来自串口接收子模块
  • 找到发送使能信号的上升沿,确立起始位,开始发送
  • 发送的时候同样需要对时钟进行计数,达到规定时钟数后进入下一个bit,当然也需要对bit进行计数
  • 发送时根据当前bit数确定发送数据的哪一位,然后对应拉高或者拉低Tx信号线,这里需要注意,起始位和停止位都需要发送
  • 发送完9个比特后拉高信号线
//串口发送子模块
module uart_send(
	input 			sys_clk,
	input 			sys_rst_n,
	
	input				uart_en,			//发送使能信号
	input	 [7:0]	uart_data,		//需要发送的串口数据,并行
	output reg		uart_txd			//串口发送端
);

parameter 	CLK_FREQ = 50000000;				//时钟频率,50M
parameter	UART_BPS = 115200;				//波特率,bit/s
parameter	BPS_CNT = CLK_FREQ/UART_BPS;	//每个bit传输需要的时钟数

reg				uart_en_d0;			
reg				uart_en_d1;
reg	[15:0]	clk_cnt;				//时钟计数寄存器
reg	[3:0]		tx_cnt;				//发送bit计数寄存器
reg				tx_flag;				//发送标志位
reg	[7:0]		tx_data;				//寄存发送数据

wire				en_flag;				//发送使能位

//找到发送使能信号的上升沿,将发送使能位置位
assign en_flag = uart_en_d0 & (~uart_en_d1);

//确定d0和d1,d1滞后d0一个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n) begin
		uart_en_d0 <= 1'd0;
		uart_en_d1 <= 1'd0;
	end
	else begin
		uart_en_d0 <= uart_en;
		uart_en_d1 <= uart_en_d0;
	end
end

//确定发送标志位
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n)
		tx_flag <= 1'd0;
	else if (en_flag) begin
		tx_flag <= 1'd1;
		tx_data <= uart_data;
	end
	else begin
		if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))	begin//已经发送了9比特数据,则发送结束,标志位清零
			tx_flag <= 1'd0;
			tx_data <= 8'd0;
		end
		else begin
			tx_flag <= tx_flag;
			tx_data <= tx_data;	
		end
	end
end

//对时钟和发送bit进行计数
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n) begin
		clk_cnt <= 16'd0;
		tx_cnt <= 4'd0;
	end
	else if (tx_flag) begin
		if (clk_cnt < BPS_CNT - 1'd1)
			clk_cnt <= clk_cnt + 1'd1;
		else begin
			clk_cnt <= 16'd0;
			tx_cnt <= tx_cnt + 1'd1;
		end
	end
end

//并转串,将数据放到串口发送信号线上
always @(posedge sys_clk or negedge sys_rst_n) begin
	if (~sys_rst_n) 
		uart_txd <= 1'd1;
	else if (tx_flag) 
		case (tx_cnt)
			4'd0:	uart_txd <= 1'd0;
			4'd1:	uart_txd <= tx_data[0];
			4'd2:	uart_txd <= tx_data[1];
			4'd3:	uart_txd <= tx_data[2];
			4'd4:	uart_txd <= tx_data[3];
			4'd5:	uart_txd <= tx_data[4];
			4'd6:	uart_txd <= tx_data[5];
			4'd7:	uart_txd <= tx_data[6];
			4'd8:	uart_txd <= tx_data[7];
			4'd9:	uart_txd <= 1'd1;
			default:;
		endcase
	else
		uart_txd <= 1'd1;
end

endmodule
结果

  直接放抓到的波形图:
在这里插入图片描述
  时序图很漂亮,接收子模块通过下降沿触发后开始接收数据,接收完后发出完成信号,发送子模块根据这个完成信号的上升沿,发送数据。
  在写FPGA的程序时,跟以往的编程差别非常之大,Verilog的并行执行要求写程序时拥有更加严谨的逻辑。现在还处于FPGA的入门阶段,只能隐隐约约地从代码背后感受到一点数字电路的轮廓。
  时隔一年多再次回到了硬件编程,需求也今非昔比了,现在的目标是将通信算法移植到硬件平台上,任重而道远,慢慢积累吧!

Ref:
《正点原子新起点FPGA教程》

### FPGA UART 串口通信实现方案教程 #### 1. FPGA 中的 UART 基本概念 UART 是一种通用异步收发传输器,常用于低速设备间的通信。在 FPGA 设计中,UART 的主要功能是将数据从串行形式转换为并行形式(称为 **串转并**),或将数据从并行形式转换为串行形式(称为 **并转串**)。这种机制使得 FPGA 能够与其他外部设备进行高效的数据交换[^1]。 #### 2. 接收端的设计原理 当 PC 或其他设备通过串口向 FPGA 发送数据时,FPGA 使用单比特宽度的 `rx` 引脚逐位接收数据。具体来说,PC 将一个字节(通常为 8 比特)按时间顺序一位一位地发送给 FPGA,其中最低有效位先被发送。随后,FPGA 内部逻辑会对接收到的每一位进行缓存,并最终将其组合成完整的 8 比特数据流[^2]。 #### 3. 发送端的工作流程 对于发送操作,FPGA 需要完成相反的过程——即把内部存储的并行数据转化为串行信号并通过 `tx` 引脚输出。这一过程同样遵循标准的 UART 协议框架,包括起始位、停止位以及可选的校验位设置。为了确保数据同步性和准确性,还需要精确控制波特率与时钟频率的关系[^4]。 #### 4. 关键参数配置 - **波特率**: 定义了每秒传输多少个符号单位 (baud),常见的有9600, 115200等。 - **帧格式**: 包括起始位(一般固定为1), 数据长度(通常是7或8 bits), 可能存在的奇偶校验位 和 结束位数量(一般是1或者2)[^3]. #### 5. 工程实例说明 实际项目开发过程中可以参考已有的开源资源来加速学习进程。例如某公开资料提供了完整的串口程序包,涵盖了接收单元、发射单元及其顶层连接结构还有相应的测试验证文档[见引用]. 利用这些材料可以帮助初学者快速掌握如何构建自己的解决方案. ```verilog // Verilog 示例代码片段展示简单的UART RX模块 module uart_rx ( input wire clk, input wire reset_n, input wire rx_in, output reg [7:0] data_out, output reg valid_data ); always @(posedge clk or negedge reset_n) begin if (!reset_n) begin // Reset logic here... end else begin // Implementation details omitted for brevity. end end endmodule ``` 上述代码仅作为一个简化版的例子用来演示基本架构思路;真实环境中可能需要考虑更多细节比如错误检测处理等功能扩展。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值