FPGA学习笔记:UART串口发送模块设计

本文详细介绍了UART串口的基础知识,包括波特率、重要参数以及波特率分频计数值的计算方法。随后,文章着重描述了如何设计一个UART串口发送模块,包括端口说明、波特率设置、代码实现,并在仿真的基础上指出并修正了一个逻辑错误。

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

一、UART基础知识

1.串口通信重要参数

UART串口的重要参数包括波特率、停止位、数据位、奇偶校验位,这些参数通讯双方需保持一致。
波特率:每秒通信数据的比特个数,单位bit/s,一般常见的波特率为2400、4800、9600、19200、38400、57600、115200、256000等,串口通讯时要求通信双方波特率一致。
停止位:在每个字节的数据发送完成之后,发送停止位,表示本次数据传输完成,一般停止位选择:1位(默认)、1.5位 、 2 位,停止位为逻辑高电平
数据位:数据位紧跟在起始位之后,是通信中发送的有效信息。一般数据位可以为5位、7位、8位(默认)。串口通信先发送数据低位,后发送高位
奇偶校验位:校验位用于验证数据的正确性,一般不对数据进行校验,即校验位选择None
除此之外,串口通讯还应具有1位起始位,起始位为逻辑低电平,标志传输一个字符的开始,接收方可用起始位使自己的接收时钟与发送方的数据同步。

2.串口发送波特率分频计数值计算

波特率是指每秒通信数据的比特个数,则每1/bps秒发送1bit数据,则分频计数时间为 1 ∗ 1 0 9 / b p s 1*10^9 /bps 1109/bps ns,系统时钟为50Mhz,得出分频计数值计算公式
波特率分频计数值 = 1 ∗ 1 0 9 / b p s / 20 − 1 波特率分频计数值=1*10^9/bps/20-1 波特率分频计数值=1109/bps/201

二、UART串口发送模块设计

1.模块端口说明

端口名称方向说明
Clkinput系统时钟
Rst_ninput系统复位
send_eninput串口发送使能信号
data[7:0]input8位并行待发送数据
baud_set[2:0]input波特率设置
txoutput串口发送端口
tx_doneoutput串口发送完成标志
uart_stateoutput串口发送状态标志

串口发送模块
模块接收到send_en使能信号时,开始发送数据,tx_state置1。baud_set是数值为0-7的3位二进制数,分别对应波特率2400、4800、9600、19200、38400、57600、115200、256000;当串口发送完成时,tx_done置1,tx_state置0。

2.波特率设置表格

波特率分频计数值 = 1 ∗ 1 0 9 / b p s / 20 − 1 波特率分频计数值=1*10^9/bps/20-1 波特率分频计数值=1109/bps/201
根据波特率分频计数值计算公式计算得出如下表格:

baud_set波特率波特率分频计数值
0240020832
1480010415
296005207
3192002603
4384001301
557600867
6115200433
7384000194

3.代码实现

代码中用了7个always块,分别实现串口发送状态标志uart_state赋值、数据缓存、波特率分频计数最大值cnt_bpsmax赋值、波特率时钟分频计数器、波特率时钟计数器、数据发送、串口发送完成标志tx_done赋值。
(1)串口发送状态标志uart_state赋值
通过对uart_state赋值,可实现将脉冲的send_en信号转化为uart_state的持续信号。当捕捉到send_en为高电平时,uart_state赋值为1;当tx_done为高电平时,串口发送完成,uart_state赋值为0。

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		uart_state <= 1'b0;
	else if(send_en)
		uart_state <= 1'b1;
	else if(tx_done)
		uart_state <= 1'b0;

(2)数据缓存
接收到send_en发送信号时,数据开始发送,为避免在发送过程中由于干扰导致输入的数据变化,在send_en为高电平时,将输入数据data缓存到tx_data中。

	reg [7:0] tx_data;		//数据缓存,防止数据丢失
	always@(posedge Clk or negedge Rst_n)//数据缓存
	if(!Rst_n)
		tx_data <= 8'd0;
	else if(send_en)
		tx_data <= data;
	else
		tx_data <= tx_data;

(3)波特率分频计数最大值cnt_bpsmax赋值
根据上面计算出的波特率设置表格对cnt_bpsmax进行赋值。复位时,cnt_bpsmax为5207,表示默认波特率为9600。

always@(posedge Clk or negedge Rst_n)//波特率选择
	if(!Rst_n)
		cnt_bpsmax <= 16'd5207;//默认波特率9600
	else
		case(baud_set)
			0:cnt_bpsmax <= 16'd20832;	//波特率2400
			1:cnt_bpsmax <= 16'd10415;	//波特率4800
			2:cnt_bpsmax <= 16'd5207;	//波特率9600
			3:cnt_bpsmax <= 16'd2603;	//波特率19200
			4:cnt_bpsmax <= 16'd1301;	//波特率38400
			5:cnt_bpsmax <= 16'd867;	//波特率57600
			6:cnt_bpsmax <= 16'd433;	//波特率115200
			7:cnt_bpsmax <= 16'd194;	//波特率256000
			default:cnt_bpsmax <= 16'd5207;
		endcase

(4)波特率时钟分频计数器
得到波特率分频计数最大值后,编写波特率时钟分频计数器。

always@(posedge Clk or negedge Rst_n)//分频计数
	if(!Rst_n)
		cnt <= 16'd0;
	else  if(uart_state) begin
		if(cnt == cnt_bpsmax)
			cnt <= 16'd0;
		else
			cnt <= cnt + 1'b1;
	end
	else
		cnt <= 16'b0;

(5)波特率时钟计数器
当串口处于发送状态(uart_state=1)时,对波特率时钟bps_cnt进行计数。
当cnt = cnt_bpsmax时对bps_cnt计数,需等待一个波特率时钟才能够开始发送数据,故将cnt == 1设置为bps_cnt的计数条件
串口需发送起始位、8位data、停止位,共10位数据,当uart_state=0时,bps_cnt为0,应避免在bps_cnt=0时发送起始位,则bps_cnt=1时发送起始位,bps_cnt=10时发送停止位,停止位应发送一个波特率时钟的时长,停止位发送完毕时bps_cnt=11,此时可对bps_cnt清零,对tx_done置位。

always@(posedge Clk or negedge Rst_n)//波特率时钟计数
	if(!Rst_n)
		bps_cnt <= 4'd0;
	else if(uart_state) begin
		if(bps_cnt == 4'd11)
			bps_cnt <= 4'd0;
		else if(cnt == 16'b1)
			bps_cnt <= bps_cnt + 1'b1;
		else
			bps_cnt <= bps_cnt;	
	end
	else
		bps_cnt <= 4'd0;

(6)数据发送
串口处于空闲状态时,tx为高电平,故复位和bps_cnt=0时tx为高电平。bps_cnt=1时发送起始位,bps_cnt为2-9时,发送缓存后的tx_data,先发送低位,后发送高位,bps_cnt=10时发送停止位。

always@(posedge Clk or negedge Rst_n)//数据发送
	if(!Rst_n)
		tx <= 1'b1;
	else
		case(bps_cnt)
			0:tx <= 1'b1;
			1:tx <= START_Bit;
			2:tx <= tx_data[0];
			3:tx <= tx_data[1];
			4:tx <= tx_data[2];
			5:tx <= tx_data[3];
			6:tx <= tx_data[4];
			7:tx <= tx_data[5];
			8:tx <= tx_data[6];
			9:tx <= tx_data[7];
			10:tx <= STOP_Bit;
			default:tx <= 1'b1;
		endcase

(7)串口发送完成标志tx_done赋值
停止位发送完毕时bps_cnt=11,对tx_done置位。

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		tx_done <= 1'b0;
	else	if(bps_cnt == 4'd11)
		tx_done <= 1'b1;
	else 
		tx_done <= 1'b0;

模块源代码

//本模块用于实现串口数据发送,系统频率为50Mhz,时钟周期为20ns
module uart_data_tx(   
	input Clk,						//系统时钟
	input Rst_n,					//系统复位
	input send_en,					//串口发送使能信号
	input [7:0] data,				//8位并行待发送数据
	input [2:0] baud_set,		//波特率设置
	
	output reg tx,					//串口发送端口
	output reg tx_done,			//串口发送完成标志
	output reg uart_state		//串口发送状态标志
);
	
	reg [7:0] tx_data;		//数据缓存,防止数据丢失
	reg [15:0] cnt;			//分频计数器
	reg [15:0] cnt_bpsmax;	//分频计数最大值
	reg [3:0] bps_cnt;		//波特率时钟计数器
	
	localparam 
		START_Bit 	= 1'b0,	//起始位
		STOP_Bit 	= 1'b1;	//停止位

	always@(posedge Clk or negedge Rst_n)//数据缓存
	if(!Rst_n)
		tx_data <= 8'd0;
	else if(send_en)
		tx_data <= data;
	else
		tx_data <= tx_data;
	
	always@(posedge Clk or negedge Rst_n)//波特率选择
	if(!Rst_n)
		cnt_bpsmax <= 16'd5207;//默认波特率9600
	else
		case(baud_set)
			0:cnt_bpsmax <= 16'd20832;	//波特率2400
			1:cnt_bpsmax <= 16'd10415;	//波特率4800
			2:cnt_bpsmax <= 16'd5207;	//波特率9600
			3:cnt_bpsmax <= 16'd2603;	//波特率19200
			4:cnt_bpsmax <= 16'd1301;	//波特率38400
			5:cnt_bpsmax <= 16'd867;	//波特率57600
			6:cnt_bpsmax <= 16'd433;	//波特率115200
			7:cnt_bpsmax <= 16'd194;	//波特率256000
			default:cnt_bpsmax <= 16'd5207;
		endcase
	
	always@(posedge Clk or negedge Rst_n)//分频计数
	if(!Rst_n)
		cnt <= 16'd0;
	else  if(uart_state) begin
		if(cnt == cnt_bpsmax)
			cnt <= 16'd0;
		else
			cnt <= cnt + 1'b1;
	end
	else
		cnt <= 16'b0;
		
	always@(posedge Clk or negedge Rst_n)//波特率时钟计数
	if(!Rst_n)
		bps_cnt <= 4'd0;
	else if(uart_state) begin
		if(bps_cnt == 4'd11)
			bps_cnt <= 4'd0;
		else if(cnt == 16'b1)
			bps_cnt <= bps_cnt + 1'b1;
		else
			bps_cnt <= bps_cnt;	
	end
	else
		bps_cnt <= 4'd0;
			
	always@(posedge Clk or negedge Rst_n)//数据发送
	if(!Rst_n)
		tx <= 1'b1;
	else
		case(bps_cnt)
			0:tx <= 1'b1;
			1:tx <= START_Bit;
			2:tx <= tx_data[0];
			3:tx <= tx_data[1];
			4:tx <= tx_data[2];
			5:tx <= tx_data[3];
			6:tx <= tx_data[4];
			7:tx <= tx_data[5];
			8:tx <= tx_data[6];
			9:tx <= tx_data[7];
			10:tx <= STOP_Bit;
			default:tx <= 1'b1;
		endcase

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		tx_done <= 1'b0;
	else	if(bps_cnt == 4'd11)
		tx_done <= 1'b1;
	else 
		tx_done <= 1'b0;

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		uart_state <= 1'b0;
	else if(send_en)
		uart_state <= 1'b1;
	else if(bps_cnt == 4'd11)
		uart_state <= 1'b0;

endmodule

三、仿真

1.仿真代码

在代码中发送两次数据,分别发送8’hab和8’h23

`timescale 1ns/1ns
module uart_data_tx_tb();

	reg Clk,Rst_n,send_en;
	reg [7:0]data;
	reg [2:0]baud_set;
	
	wire tx,tx_done,uart_state;
	
	uart_data_tx uart_data_tx(   
		.Clk(Clk),
		.Rst_n(Rst_n),
		.send_en(send_en),
		.data(data),
		.baud_set(baud_set),
		
		.tx(tx),
		.tx_done(tx_done),
		.uart_state(uart_state)
	);

	initial Clk = 1;
	always #10 Clk = ~Clk;
	
	initial begin
		Rst_n = 1'b0;
		send_en = 1'b0;
		data = 8'h0;
		baud_set = 3'd2;
		#201;//20+1为了和系统时钟错开,便于观察结果
		
		Rst_n = 1'b1;
		#200;
		
		data = 8'hab;
		send_en = 1'b1;
		#20;
		send_en = 1'b0;
		@(posedge tx_done);//等待发送完成
		#50000;
		
		data = 8'h23;
		send_en = 1'b1;
		#20;
		send_en = 1'b0;
		@(posedge tx_done);
		#50000;
		$stop;
	end

endmodule

2.仿真结果及代码更正

在这里插入图片描述
波形整体上没有问题,实现了串口发送。但将波形放大后发现tx_done和uart_state不是同时变化的,当tx_done被拉高时,uart_state依然为高电平,如下图所示:
在这里插入图片描述
这是因为uart_state清零的条件是tx_done=1,当tx_done被拉高时,在下一个时钟周期才会对uart_state清零,因此应该将uart_state的清零条件和tx_done被拉高的条件保持一致,将代码更改如下:

	always@(posedge Clk or negedge Rst_n)
	if(!Rst_n)
		uart_state <= 1'b0;
	else if(send_en)
		uart_state <= 1'b1;
	else if(bps_cnt == 4'd11)
		uart_state <= 1'b0;

更改后再进行仿真,tx_done和uart_state同时变化。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值