UART简单实现

1. 各级模块调用关系

以“50MHz 9600bps”为例
在这里插入图片描述

2. 接收模块


// 串行接收模块

`timescale 1ns / 1ps


module  uart_recv (
  output	reg		[7:0]		uart_data,
  output	reg					uart_done,
  
  input		wire				sys_clk,			// 系统时钟,50M
  input		wire				sys_rst_n,			// 系统复位,低有效
  input		wire				uart_rxd			// UART接收端口
);

	// 
	parameter	CLK_FREQ	=	50_000_000;			// 系统时钟频率
	parameter	UART_BPS	=	9600;					// 串口波特率
	parameter	BPS_CNT		=	CLK_FREQ / UART_BPS;	// 为得到指定波特率,需要对系统时钟计数BPS_CNT次
														// 即每接收一个bit数据需要经过多少个系统时钟
	
	//
	reg				uart_rxd_d0;		
	reg				uart_rxd_d1;
	reg		[15:0]	clk_cnt;				// 系统时钟计数器
	reg		[4:0]	rx_cnt;					// 接收数据计数器
	reg				rx_flag;				// 接收过程标志信号
	reg		[7:0]	rxdata;					// 接收数据寄存器
	//
	wire			start_flag;				// 接收数据起始信号
	
	reg		[15:0]	bps_value;
	
	
	// 捕获接受端口下降沿(起始位),得到一个时钟周期的脉冲信号
	// 按键消抖模块也用到了类似的方法检测沿的变化
	assign	start_flag	=	uart_rxd_d1 & (~uart_rxd_d0);
	
	// 对UART接收端口的数据延迟两个时钟周期
	// 当数据经过uart_rxd,传送到uart_rxd_d0,再传送到uart_rxd_d1,经过了两层寄存器,想要捕获到下降沿,就需要分析当前d1寄存器的值
	// 可以认为d1寄存器捕获到了下降沿,一切都以d1寄存器的值为当前需要进行分析处理的数据,起始位、数据位、校验位、结束位
	always @ (posedge sys_clk or negedge sys_rst_n)	begin
		if (!sys_rst_n)	begin
			uart_rxd_d0	<=	1'b0;
			uart_rxd_d1	<=	1'b0;
			bps_value	<=	16'd0;
		end else	begin
			uart_rxd_d0	<=	uart_rxd;
			uart_rxd_d1	<=	uart_rxd_d0;
			bps_value	<=	CLK_FREQ / UART_BPS;
		end
	end
	
	// 当脉冲信号start_flag到达时,进入接收过程
	always @ (posedge sys_clk or negedge sys_rst_n)	begin
		if (!sys_rst_n)	begin
			rx_flag	<=	1'b0;
		end else	begin
			if (start_flag)	begin			// 检测到了起始位
				rx_flag	<=	1'b1;			// 进入接收过程,接收过程标志信号rx_flag拉高
			end else if ( (rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2) )	begin
				rx_flag	<=	1'b0;			// 计数到停止位的数据采样时刻,停止接受过程
			end else	begin
				rx_flag	<=	rx_flag;
			end
		end
	end
	
	// 进入接收过程后,启动系统时钟计数器和接收数据计数器
	always @ (posedge sys_clk or negedge sys_rst_n)	begin
		if (!sys_rst_n)	begin
			clk_cnt	<=	16'd0;
			rx_cnt	<=	4'd0;
		end else	if (rx_flag)	begin		// 处于接收过程
			if (clk_cnt < BPS_CNT - 1)	begin		// 接收一个bit需要BPS_CNT = CLK_FREQ / UART_BPS个系统时钟
				clk_cnt	<=	clk_cnt + 1'b1;
				rx_cnt	<=	rx_cnt;
			end else	begin
				clk_cnt	<=	16'd0;						// 系统时钟计数达一个波特率周期后清零
				rx_cnt	<=	rx_cnt + 1'b1;				// 此时接收数据计数器加1
			end
		end else	begin
			clk_cnt	<=	16'd0;
			rx_cnt	<=	4'd0;
		end
	end
	
	// 根据接收数据计数器来寄存uart接收端口数据
	// 接收1bit需要BPS_CNT个系统时钟,在中间位置BPS_CNT / 2处进行数据采样
	always @ (posedge sys_clk or negedge sys_rst_n)	begin
		if (!sys_rst_n)	begin
			rxdata	<=	8'b0;
		end else if (rx_flag)	begin				// 系统处于接收过程
			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	begin
				rxdata	<=	rxdata;
			end
		end else	begin
			rxdata	<=	8'b0;
		end
	end
	
	// 数据接收完毕后给出标志信号并寄存输出接收到的数据
	// 当寄存完8bit数据后,rx_cnt加1,进入接收结束位的波特率周期,因为结束位固定是1,可以不用等到采样数据,直接完成接收过程
	always @ (posedge sys_clk or negedge sys_rst_n)	begin
		if (!sys_rst_n)	begin
			uart_data	<=	8'b0;
			uart_done	<=	1'b0;
		end else if (rx_cnt == 4'd9)	begin			// 接收数据计数器计数到停止位时
			uart_data	<=	rxdata;								// 寄存输出接收到的8bit数据
			uart_done	<=	1'b1;								// 并将接收完成标志信号位拉高
		end else	begin
			uart_data	<=	8'b0;
			uart_done	<=	1'b0;
		end
	end

endmodule

3. 发送模块

// uart_send发送模块
// 接收模块接收完成uart_done发出高脉冲,连接发送模块的发送使能信号uart_en,所以uart_en默认0,检测到上升沿即可认为一字节数据接收完毕,可以传回


`timescale 1ns / 1ps


module uart_send (
	output	reg					uart_txd,		// UART发送端口
	
	input		wire			sys_clk,		// 系统时钟
	input		wire			sys_rst_n,		// 系统复位,低有效
	input		wire			uart_en,		// 发送使能信号
	input		wire	[7:0]	uart_din		// 带待发送数据
);

	//
	parameter	CLK_FREQ	=	50_000_000;			// 系统时钟频率
	parameter	UART_BPS	=	9600;					// 串口波特率
	parameter	BPS_CNT		=	CLK_FREQ / UART_BPS;	// 为得到指定波特率,需要对系统时钟计数BPS_CNT次
														// 即每接收一个bit数据需要经过多少个系统时钟
										
	//
	reg				uart_en_d0;
	reg				uart_en_d1;
	reg		[15:0]	clk_cnt;				// 系统时钟计数器
	reg		[3:0]	tx_cnt;					// 发送数据计数器
	reg				tx_flag;				// 发送过程标志信号
	reg		[7:0]	tx_data;				// 寄存发送数据
	//
	wire			en_flag;
	
	
	// 捕获uart_en上升沿,得到一个时钟周期的脉冲信号
	assign	en_flag	=	(~uart_en_d1) & uart_en_d0;
	
	// 对发送使能信号uart_en延迟两个时钟周期
	// 与接收延迟相同
	always @ (posedge sys_clk or negedge sys_rst_n)	begin
		if (!sys_rst_n)	begin
			uart_en_d0	<=	1'b0;
			uart_en_d1	<=	1'b1;
		end else	begin
			uart_en_d0	<=	uart_en;
			uart_en_d1	<=	uart_en_d0;
		end
	end
	
	// 当脉冲en_flag到达时,寄存待发送的数据,并进入发送过程
	// en_flag是wire型数据,使能后tx_flag发送过程标志信号被拉高,在发送过程中依据该信号发送数据,直到发送完停止位,发送标志信号被拉低,结束发送
	always @ (posedge sys_clk or negedge sys_rst_n)	begin
		if (!sys_rst_n)	begin
			tx_flag	<=	1'b0;
			tx_data	<=	8'b0;
		end else if (en_flag)	begin		// 检测到发送使能上升沿
			tx_flag	<=	1'b1;					// 进入发送过程,标志位tx_flag拉高
			tx_data	<=	uart_din;				// 寄存待发送的数据
		end else if ( (tx_cnt == 4'd9) && (clk_cnt == BPS_CNT / 2) )	begin	// 计数到停止位的数据采样时刻,停止发送过程
			tx_flag	<=	1'b0;					// 发送过程结束,标志位tx_flag拉低
			tx_data	<=	8'b0;
		end else	begin
			tx_flag	<=	tx_flag;
			tx_data	<=	tx_data;
		end
	end
	
	// 进入发送过程后,启动系统时钟计数器和发送数据计数器
	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)	begin	// 发送一个bit需要BPS_CNT = CLK_FREQ / UART_BPS个系统时钟
				clk_cnt	<=	clk_cnt + 1;
				tx_cnt	<=	tx_cnt;
			end else	begin
				clk_cnt	<=	16'd0;						// 系统时钟计数达一个波特率周期后清零
				tx_cnt	<=	tx_cnt + 1;					// 发送数据计数器加1
			end
		end else	begin						// 发送过程结束
			clk_cnt	<=	16'd0;
			tx_cnt	<=	4'd0;
		end
	end
	
	// 根据发送数据计数器来给uart发送端口赋值
	always @ (posedge sys_clk or negedge sys_rst_n)	begin
		if (!sys_rst_n)	begin
			uart_txd	<=	1'b1;
		end else if (tx_flag)	begin		// 发送标志信号
			case (tx_cnt)							// 依据发送数据计数器确定当前发送的数据
				4'd0:	uart_txd	<=	1'b0;			// 起始位
				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'b1;			// 停止位
				default:	;
			endcase
		end else	begin
			uart_txd	<=	1'b1;					// 空闲时发送端口为高电平
		end
	end
	
endmodule

4. 顶层模块

`timescale 1ns / 1ps


module top_uart (
	output		wire	uart_txd,		// 发送端口
	
	input		wire	sys_clk,
	input		wire	sys_rst_n,
	input		wire	uart_rxd		// 接收端口
);

	parameter	CLK_FREQ	=	50_000_000;
	parameter	UART_BPS	=	9600;
	parameter	CNT_BPS		=	CLK_FREQ / UART_BPS;
	
	wire				uart_en;
	wire	[7:0]	uart_data;
	
	uart_recv	#(							
			.CLK_FREQ	(CLK_FREQ),
			.UART_BPS	(UART_BPS)
			)	
	uart_recv0 (
			.uart_data(uart_data), 
			.uart_done(uart_en),
			
			.sys_clk(sys_clk), 
			.sys_rst_n(sys_rst_n), 
			.uart_rxd(uart_rxd)
			);
			
	uart_send	#(							
			.CLK_FREQ	(CLK_FREQ),
			.UART_BPS	(UART_BPS)
			)	
	uart_send0 (
			.uart_txd(uart_txd),
			
			.sys_clk(sys_clk), 
			.sys_rst_n(sys_rst_n), 
			.uart_en(uart_en), 
			.uart_din(uart_data)
			);

endmodule

5. 测试


`timescale 1ns / 1ps

module sim_uart ();

	wire		uart_txd;		// 发送端口
	
	reg			sys_clk;		// 系统时钟
	reg			sys_rst_n;		// 系统复位,低有效
	reg			uart_rxd;		// 接收端口
	
	
	top_uart	top_uart0 (
			.uart_txd(uart_txd),
			
			.sys_clk(sys_clk),
			.sys_rst_n(sys_rst_n),
			.uart_rxd(uart_rxd)
			);
			
	initial	begin
		sys_clk 	= 	0;
		sys_rst_n	=	0;
		uart_rxd	=	0;
	end
			
	// 产生50MHz的系统时钟
	parameter	clk_period	=	20;
	always #(clk_period / 2)	sys_clk	=	~sys_clk;
	
	initial	begin
		#20
		sys_rst_n = 1;
		uart_rxd = 1;
		#100560
		sys_rst_n = 1;
		uart_rxd = 0;	//起始位:-1-0-
		#100560			//传输11001011 (倒序)
		sys_rst_n = 1;
		uart_rxd = 1;
		#100560
		sys_rst_n = 1;
		uart_rxd = 1;
		#100560
		sys_rst_n = 1;
		uart_rxd = 0;
		#100560
		sys_rst_n = 1;
		uart_rxd = 1;
		#100560
		sys_rst_n = 1;
		uart_rxd = 0;
		#100560
		sys_rst_n = 1;
		uart_rxd = 0;
		#100560
		sys_rst_n = 1;
		uart_rxd = 1;
		#100560
		sys_rst_n = 1;
		uart_rxd = 1;
		#100560			//结束位 -0-1-
		sys_rst_n = 1;
		uart_rxd = 0;
		#100560
		sys_rst_n = 1;
		uart_rxd = 1;
		
		#1100000		//复位
		sys_rst_n = 0;
		uart_rxd = 0;
	end
	
endmodule

按照仿真的输入,0(起始位) 1101_0011
输出的顺序, 0(起始位) 1101_0011 1(停止位)
在这里插入图片描述
在这里插入图片描述

6. 问题分析与总结

6.1. 测试延时#100560计算

	parameter	CLK_FREQ	=	50_000_000;
	parameter	UART_BPS	=	9600;
	parameter	CNT_BPS		=	CLK_FREQ / UART_BPS;

系统时钟频率为50MHz,波特率为9600,CNT_BPS即“50_000_000 / 9600 ≈ 5208”,即每接收1bit数据需要经过5208个clk,然后在这5208个的中间部分,也就是BPS_CNT / 2的位置进行当前数据的数据采样
由于50MHz的时钟周期为20ns,所以控制延时为5208 * 20ns = 100560ns


说明:这里的5208 * 20ns = 104160ns,上面计口算失败,但是问题不大,重新加上波形
数据采样每次都在BPS_CNT / 2位置,也就是BPS_CNT个clk中间位置(这个位置是固定的),在进行rxd_data数据输入的可以允许一定幅度的延时偏差,这种偏差会累积,越往后,每一次的数据采样越来越靠近rxd_data持续范围的偏后部分。
在这里插入图片描述

下面两张图中红色光标的时间差可以说明这个道理,数据更新有快有慢,一定范围内都可以取到正确的数

  • 延时#104160,rxd数据更新适中
    在这里插入图片描述

  • 延时#100560,数据更新过快
    在这里插入图片描述


6.2. 扩展“100MHz 9600bps”

更改各个模块参数定义,修改测试模块100MHz系统时钟的产生
延时计算:
100_000_000 / 9600 ≈ 10417
10417 * 10ns = 104170ns

6.3. 补充

从波形可以看到,输出的顺序和输入一样,都是1101_0011,接收模块采集到数据后从最低位开始寄存,传送到发送模块后,也是从最低位[0]开始发送
在这里插入图片描述

6.4. 参考

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值