通信协议(一)——UART协议

1、知识点

基础部分参考:UART串口发送模块设计Verilog_发光中请勿扰的博客-CSDN博客_uart设计verilog

 (1)什么是串口(UART)?

        串口作为常用的三大低速总线(UART、SPI、IIC)之一,在设计众多通信接口和调试时占有重要地位。串口(UART)全称通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),主要用于数据间的串行传递,是一种全双工传输模式。它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。

        “异步”两个字即意味着在数据传递的两个模块之间使用的不是同步时钟。实际上在异步串口的传输中是不需要时钟的,而是通过特定的时序来标志传输的开始(起始位--由高到低)和结束(结束位,拉高)。

(2)串口的组成

        UART 通信只有两根信号线,一根是发送数据端口线叫 tx(Transmitter),一根是接收数据端口线叫 rx(Receiver),如图所示,对于 PC 来说它的 tx 要和对于 FPGA来说的 rx 连接,同样 PC 的 rx 要和 FPGA 的 tx 连接,如果是两个 tx 或者两个 rx 连接那数据就不能正常被发送出去和接收到。 

        信号的传输由外部驱动电路实现。电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准有RS232、RS422、RS485等,它们定义了接口不同的电气特性,如RS-232是单端输入输 出,而RS-422/485为差分输入输出等。传输距离较短时(不超过15m),RS232是串行通信最常用的接口标准。RS-232标准的串口最常见的接口类型为DB9,样式如图所示,工业控制领域中用到的工控机一般都配备多个串口,很多老式台式机也都配有串口。但是笔记本电脑以及较新一点 的台式机都没有串口,它们一般通过USB转串口线来实现与外部设备的串口通信。

(3)RS232

 帧结构(10bit):

 空闲状态保持高电平

        UART 在发送或接收过程中的一帧数据由4部分组成,起始位、数据位、奇偶校验位和停止位,如图所示。其中,起始位标志着一帧数据的开始,停止位标志着一帧数据的结束,数据位是一帧数据中的有效数据。

        校验位分为奇校验和偶校验,用于检验数据在传输过程中是否出错。 奇校验时,发送方应使数据位中1的个数与校验位中1的个数之和为奇数;接收方在接收数据时, 对1的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查 1的个数是否为偶数。

 

        UART通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置。数据位可选择为5、6、7、8位,其中8位数据位是最常用的,在实际应用中 一般都选择8位数据位;校验位可选择奇校验、偶校验或者无校验位;停止位可选择1位(默认), 1.5或2位。

        串口通信的速率用波特率表示,它表示每秒传输二进制数据的位数,单位是bps(位 /秒),常用的波特率有9600、19200、38400、57600以及115200等。如波特率9600则代表每秒传输9600bit数据,以串口发送1个字节10bit算(起始位1bit+数据8bit+停止位1bit+NO校验位),则传输1个字节需要的时间是1*10/9600秒。 

2、设计

(1)模块框图

顶层模块:

 子功能模块

串口数据接收模块:

 串口数据发送模块:

整体框图:


( 2)功能实现

① 串口数据接收模块:

需要对rx数据进行打一拍,与系统时钟同步,但仍无法直接传送,因为该信号属于异步信号,会引出亚稳态。

亚稳态问题参考:亚稳态问题_发光中请勿扰的博客-CSDN博客

解决方法:对打拍后的数据再次打两拍

波形图:

波形:

 data输入

 打拍信号

使能信号 

 计数最大值

bit标志位

        该模块支持任意波特率(理论上)的接收,但需要在使用该模块时使用参数将其例化,数据位8位,起始位和停止位各1位,无奇偶校验
        串口的传输是以起始位开始的,而起始位是将数据线拉低 ,所以我们需要捕捉数据线的下降沿,将接收数据线打拍两次,捕捉其下降沿。当捕捉到接收数据线的下降沿,拉高接收标志信号,标志模块进入接收过程;当接收完10个bit后,拉低接收标志信号,标志接收过程结束
        假设波特率为9600,则传输一个bit的时间为1s/9600,一个数据的传输共10bit(数据位8位,起始位和停止位各1位),则共需要1s/960;假设系统时钟为50MHz(参数化以便适应不同的系统频率),则其周期为20ns,那么传输一个bit所需要的系统周期数为(1s/960)/ 20ns ≈ 5208(个)。在接收过程中使用一个计数器计数,计数区间为(0,5208-1),这样的区间一共10个(一个字节需要传输10个bit);此外还需一个计数器对接收的bit数计数(每当上一个计数器计数到5207则表示接收完了一个bit),计数区间(0,9)。
        在接收过程,根据计数器的值(接收bit计数器),在每个bit计数器的中间接收数据,将其移位寄存(在电平中间数据最稳定)
        若接收bit计数器 = 0,则代表是起始位,不需要接收
        若接收bit计数器 = 1,则代表此时接收到数据的最低位LSB(数据的传输总是低位在前,高位在后),将其赋值给寄存数据的最低位;
        ······
        若接收bit计数器 = 8,则代表此时接收到数据的最高位MSB,将其赋值给寄存数据的最高位;
        若接收bit计数器 = 9,则代表是停止位,不需要接收

 代码:

module uart_rx
#(
	parameter uart_bps = 'd9600,//波特率
	parameter clk_fre  = 'd50_000_000//频率
)
(
		input sys_clk   ,
		input sys_rst_n		,
		input rx		,
		output reg [7:0]po_data	,
		output reg po_flag
);

parameter baud_cnt_max = clk_fre/uart_bps; //频率除以波特率

reg rx_reg1										;
reg rx_reg2										;
reg rx_reg3										;
reg start_flag									;
reg work_en										;
reg [15:0]baud_cnt								;
reg bit_flag									;
reg [3:0]bit_cnt								;
reg [7:0]rx_data								;
reg rx_flag										;

//打一拍,同步到系统时钟下
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		rx_reg1 <= 1'b1;
	else 
		rx_reg1 <= rx;

//打两拍,消除亚稳态		
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		rx_reg2 <= 1'b1;
	else 
		rx_reg2 <= rx_reg1 ;

always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		rx_reg3 <= 1'b1;
	else 
		rx_reg3 <= rx_reg2 ;

//开始标志信号		
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		start_flag <= 1'b0;
	else if (rx_reg2 == 1'b0 && rx_reg3 == 1'b1 && work_en == 1'b0)//引入使能信号是为了避免在数据传输时遇到1变为0,从而检验到下降沿
		start_flag <= 1'b1;
	else
		start_flag <= 1'b0;

//使能信号		
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		work_en <= 1'b0;
	else if(start_flag == 1'b1)
		work_en <= 1'b1;
	else if(bit_cnt == 4'd8 && bit_flag == 1'b1)
		work_en <= 1'b0;//bit计数器和bit标志信号
	else
		work_en <= work_en;

//波特率计数器		
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		baud_cnt <= 16'd0;
	else if((baud_cnt == baud_cnt_max - 1 )  || (work_en == 1'b0))
		baud_cnt <= 16'd0;
	else
		baud_cnt <= baud_cnt + 1'b1;

//bit标志信号	
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		bit_flag <= 1'b0;
	else if(baud_cnt == baud_cnt_max / 2 - 1)
		bit_flag <= 1'b1;
	else
		bit_flag <= 1'b0;
//bit计数器
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		bit_cnt <= 4'd0;
	else if(bit_cnt == 4'd8 && bit_flag == 1'b1)
		bit_cnt <= 4'd0;
	else if(bit_flag == 1'b1)
		bit_cnt <= bit_cnt + 1'b1;

//数据拼接	
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		rx_data <= 8'b0;
	else if(bit_cnt >= 4'd1 && bit_cnt <= 4'd8 && bit_flag == 1'b1)
		rx_data <= {rx_reg3,rx_data[7:1]};

always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		rx_flag <= 1'b0;
	else if(bit_cnt == 4'd8 && bit_flag == 1'b1)
		rx_flag <= 1'b1;
	else
		rx_flag <= 1'b0;
//输出并行信号		
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		po_data <= 8'b0;
	else if(rx_flag == 1'b1)
		po_data <= rx_data;
//对rx_flag进行打拍,保持与输出数据同步
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		po_flag <= 1'b0;
	else 
		po_flag <= rx_flag;

endmodule
`timescale 1ns/1ns	//定义时间刻度
//模块、接口定义
module uart_rx_tb();
 
reg 			sys_clk			;			
reg 			sys_rst_n		;
reg				rx				;			
wire	[7:0]	po_data			;
wire            po_flag			;
 
initial 
	begin	
		sys_clk <= 1'b0;	
		sys_rst_n <= 1'b0;
		rx <= 1'b1;
		#20
		sys_rst_n <= 1'b1;
	end
always #10 sys_clk <= ~sys_clk;

initial 
	begin 
		#200
		rx_bit(8'd0);
		rx_bit(8'd1);
		rx_bit(8'd2);
		rx_bit(8'd3);
		rx_bit(8'd4);
		rx_bit(8'd5);
		rx_bit(8'd6);
		rx_bit(8'd7);
	end

//例化发送模块
uart_rx 
#(
	.uart_bps (9600),
	.clk_fre  (50_000_000)
)
uart_rx_inst
(
		.sys_clk(sys_clk)   ,
		.sys_rst_n(sys_rst_n),
		.rx(rx)		,
		.po_data(po_data)	,
		.po_flag(po_flag)
);

task rx_bit (
	input [7:0] data
);
	integer i; //定义一个常量
	//用 for 循环产生一帧数据,for 括号中最后执行的内容只能写 i=i+1
	for(i=0; i<10; i=i+1) begin
		case(i)
		0: rx <= 1'b0;		//起始位
		1: rx <= data[0];		//LSB
		2: rx <= data[1];
		3: rx <= data[2];
		4: rx <= data[3];
		5: rx <= data[4];
		6: rx <= data[5];
		7: rx <= data[6];
		8: rx <= data[7];		//MSB
		9: rx <= 1'b1;		//停止位
		endcase
		#(5208*20); 		//每发送 1 位数据延时
	end		
endtask 							//任务结束
 

 
endmodule 

 ②串口数据接收模块

波形图:

 波形:

模拟产生输入

 输入标志信号

 中间变量

 输出

module uart_tx
#(
	parameter uart_bps = 'd9600,
	parameter clk_fre  = 'd50_000_000
)
(
		input sys_clk			,
		input sys_rst_n				,
		input [7:0]pi_data		,
		input pi_flag			,
		output reg tx				
);

localparam baud_cnt_max = clk_fre/uart_bps;

reg         work_en				;
reg  [15:0] baud_cnt			;
reg  		bit_flag			;
reg  [3:0]	bit_cnt				;

//使能信号
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		work_en <= 1'b0;
	else if(pi_flag == 1'b1)
		work_en <= 1'b1;
	else if(bit_cnt == 4'd09 &&  bit_flag == 1'b1)	
		work_en <= 1'b0;
//波特计数器
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		baud_cnt <= 16'd0;
	else if(work_en == 1'b0 || baud_cnt == baud_cnt_max - 1)
		baud_cnt <= 16'd0;
	else if(work_en == 1'b1)
		baud_cnt <= baud_cnt + 1'b1;
//bit标志位		
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		bit_flag <= 1'b0;
	else if(baud_cnt == 16'd1)
		bit_flag <= 1'b1;
	else
		bit_flag <= 1'b0;
//bit计数器		
always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		bit_cnt <= 4'd0;
	else if(bit_cnt == 4'd09 && bit_flag == 1'b1)
		bit_cnt <= 4'd0;
	else if(work_en == 1'b1 && bit_flag == 1'b1)
		bit_cnt <= bit_cnt + 1'b1;

always @(posedge sys_clk or negedge sys_rst_n)
	if(!sys_rst_n)
		tx <= 1'b1;
	else if(bit_flag == 1'b1)
		case(bit_cnt)
			0:tx <= 1'b0;
			1:tx <= pi_data[0];
			2:tx <= pi_data[1];
			3:tx <= pi_data[2];
			4:tx <= pi_data[3];
			5:tx <= pi_data[4];
			6:tx <= pi_data[5];
			7:tx <= pi_data[6];
			8:tx <= pi_data[7];
			9:tx <= 1'b1;
			default : tx <= 1'b1;
			endcase
			
endmodule	
`timescale 1ns/1ns	//定义时间刻度
//模块、接口定义
module uart_tx_tb();
 
reg 			sys_clk			;			
reg 			sys_rst_n		;			
reg [7:0]		pi_data			;
reg 			pi_flag			;

wire 	 		tx		;

initial 
	begin	
		sys_clk <=1'b0;	
		sys_rst_n <=1'b0;					
		#20 										
		sys_rst_n <=1'b1;
	end
 
always #10 sys_clk = ~sys_clk;

initial 
	begin
		pi_data <= 8'd0;
		pi_flag <= 1'b0;
		#200
		//数据0
		pi_data <= 8'd0;
		pi_flag <= 1'b1;
		#20
		pi_flag <= 1'b0;
		#(5208*10*20)
		//数据1
		pi_data <= 8'd1;
		pi_flag <= 1'b1;
		#20
		pi_flag <= 1'b0;
		#(5208*10*20)
		//数据2
		pi_data <= 8'd2;
		pi_flag <= 1'b1;
		#20
		pi_flag <= 1'b0;
		#(5208*10*20)
		//数据3
		pi_data <= 8'd3;
		pi_flag <= 1'b1;
		#20
		pi_flag <= 1'b0;
		#(5208*10*20)
		//数据4
		pi_data <= 8'd4;
		pi_flag <= 1'b1;
		#20
		pi_flag <= 1'b0;
		#(5208*10*20)
		//数据5
		pi_data <= 8'd5;
		pi_flag <= 1'b1;
		#20
		pi_flag <= 1'b0;
		#(5208*10*20)
		//数据6
		pi_data <= 8'd6;
		pi_flag <= 1'b1;
		#20
		pi_flag <= 1'b0;
		#(5208*10*20)
		//数据7
		pi_data <= 8'd7;
		pi_flag <= 1'b1;
		#20
		pi_flag <= 1'b0;
	end

//例化发送模块

uart_tx
#(
	.uart_bps (9600),
	.clk_fre  (50_000_000)
)
uart_tx_inst
(
	.sys_clk	(sys_clk)		,
	.sys_rst_n	(sys_rst_n)		,
	.pi_data	(pi_data)		,
	.pi_flag	(pi_flag)		,
	.tx			(tx)	
);
 
endmodule 

③顶层模块:

 波形:

rx数据接收(第一个拉高为数据得停止位)

 task验证

数据传递

//顶层
module rs232	
(	
//系统接口
	input 				sys_clk			,			//50M系统时钟
	input 				sys_rst_n		,			//系统复位
//UART	
	input 				rx,			//接收数据线
	output  			tx					//UART发送数据线
);
	
//wire define				
wire	[7:0]	rx_data	;						//接收到的一个BYTE数据
wire			rx_flag	;						//接收有效信号,可用作发送的使能信号
 
//例化发送模块
uart_tx
#(
	.uart_bps (9600),
	.clk_fre  (50_000_000)
)
uart_tx_inst
(
	.sys_clk	(sys_clk)		,
	.sys_rst_n	(sys_rst_n)		,
	.pi_data	(rx_data)		,
	.pi_flag	(rx_flag)		,
	.tx			(tx)	
);
//例化接收模块
uart_rx 
#(
	.uart_bps (9600),
	.clk_fre  (50_000_000)
)
uart_rx_inst
(
		.sys_clk(sys_clk)  	 	,
		.sys_rst_n(sys_rst_n)	,
		.rx(rx)					,
		.po_data(rx_data)		,
		.po_flag(rx_flag)
);
endmodule 
`timescale 1ns/1ns	//定义时间刻度
module rs232_tb();

reg 			sys_clk			;			
reg 			sys_rst_n		;
reg				rx				;
wire			tx				;

initial
	begin
		sys_clk <= 1'b0;	
		sys_rst_n <= 1'b0;
		rx <= 1'b1;
		#20
		sys_rst_n <= 1'b1;
	end
always #10 sys_clk <= ~sys_clk;

initial 
	begin
		#200
		rx_byte();	
	end

task rx_byte();
	integer j; //定义一个常量
	for(j=0; j<8; j = j +1) 
		rx_bit(j);
endtask


task rx_bit (
	input [7:0] data
);
	integer i; //定义一个常量
	//用 for 循环产生一帧数据,for 括号中最后执行的内容只能写 i=i+1
	for(i=0; i<10; i=i+1) begin
		case(i)
		0: rx <= 1'b0;		//起始位
		1: rx <= data[0];		//LSB
		2: rx <= data[1];
		3: rx <= data[2];
		4: rx <= data[3];
		5: rx <= data[4];
		6: rx <= data[5];
		7: rx <= data[6];
		8: rx <= data[7];		//MSB
		9: rx <= 1'b1;		//停止位
		endcase
		#(5208*20); 		//每发送 1 位数据延时
	end		
endtask 							//任务结束
 

rs232	rs232_inst
(	
//系统接口
	.sys_clk(sys_clk)			,			//50M系统时钟
	.sys_rst_n(sys_rst_n)		,			//系统复位
//UART	
	.rx(rx)				,			//接收数据线
	.tx(tx)					//UART发送数据线
);

endmodule
  • 9
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
UART串口通信中,有一些与初始化和控制相关的寄存器。其中包括UARTx_UCR1~4,这些是串口配置寄存器,用于设置串口的参数,如使能、数据位位数、停止位位数等。另外,还有UARTx_USR1~2,这些是串口状态寄存器,可用于判断是否接收到数据、数据是否发送完毕等。此外,还有UARTx_UFCR、UARTx_UBIR、UARTx_UBMR这几个寄存器,它们搭配使用,用于配置波特率。 总结起来,通过这些寄存器的配置,可以控制和管理UART串口通信的初始化和传输过程,实现数据的发送和接收。 UART是一种全双工、异步串行通信方式,通过将并行数据转换成串行数据来传输,同时将接收到的串行数据转换成并行数据来接收,实现了全双工传输和接收。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【裸机开发UART 串口通信(一)—— 寄存器解析](https://blog.csdn.net/challenglistic/article/details/131435415)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [UART串口通信协议](https://blog.csdn.net/STATEABC/article/details/131808646)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

发光中请勿扰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值