FPGA实现UART通信(1)---发送数据

文章详细介绍了UART协议的基本概念,包括起始位、数据位、奇偶校验位和停止位,以及波特率的概念。接着,通过一个Verilog模块展示了如何设计一个UART数据发送器,该模块能根据不同的波特率设置发送数据,并在10ms间隔发送数据。在设计中,针对发送数据的时序和稳定性进行了优化,确保数据在时钟到来时稳定,并通过寄存数据来防止数据在传输过程中的变化。最后,给出了仿真波形和相应代码的修改以满足设计要求。

一、UART协议

1、基本概念
通用异步收发传输器,是一种异步收发传输器,在发送数据通过将并行数据转换成串行数据进行传输,在接收数据时将串行数据转换成并行数据。
串行通信分为同步串行通信和异步串行通信。同步串行通信即需要时钟的参与,通信双方需要在同一时钟的控制下,同步传输数据;异步串行通信则不需要时钟的干预,通信双方使用各种的时钟来控制数据的发送和接收。uart属于异步串行通信,即没有时钟信号来同步或验证从发送器发送并由接收器接收的数据,这就要求发送器和接收器必须事先就时序参数达成一致。
2、UART协议
UART串口协议规定,当总线处于空闲状态时信号线的状态为高电平,表示当前线路上没有数据传输。
起始位:开始进行数据传输时发送方要先发送一个低电平来表示传输字符的开始。
数据位:起始位之后就需要传输数据,数据位可以是5~9位,构成一个字符,一般是8位,先发送最低位后发送最高位。
奇偶校验位:奇偶校验位是用来检验数据在传输过程中是否出错。在奇校验时,发送方应使数据位中1的个数与校验位中1的个数为奇数,接收方在接收数据时,对1的个数进行检测,若1的个数不为奇数个,则说明数据在传输过程中存在差错。偶校验则相反。
停止位:数据结束标志,可以是1位或者2位的高电平。由于数据在传输线上是定时传输的,并且每一个设备有自己的时钟,很可能在通信中两台设备之间出现了小小的不同步,因此停止位不仅仅是表示数据传输的结束,并且提供计算机校正时钟的机会。停止位越多,数据传输越稳定,但是数据传输速度越慢。
在这里插入图片描述
3、波特率
在电子通信领域,波特(Baud)即调制速率,指的是有效数据讯号调制载波的速率,即单位时间内载波调制状态变化的次数。
波特率表示每秒钟传送码元符号的个数(表示一秒钟能传输的最大的bit数),它是对符号传输速率的一种度量,用单位时间内载波调制状态改变的次数来表示,1波特指每秒传输1个字符。
数据传输速率使用波特率来表示,单位bps(bits per second),常见的波特率有9600、19200、38400、57600、115200等。例如将串口波特率设置位115200bps,那么传输一个bit需要的时间是1/115200 ≈ 8.68us ==8680 ns 。这里拿我所使用的板子晶振(时钟频率:50MHz)来说,算出传输1bit需要的时间为8680ns,时钟周期为20ns,8680/20 = 434,转换成相应的时钟频率为434hz.相当于(50*10^6)/115200。因此公式为:时钟频率/波特率。

二、实验设计

实验:urat发送数据在这里插入图片描述在这里插入图片描述

//uart通信发送数据
module  uart01(
				  clk, 
                  rst,
				  baud_set,
				  send_en,
				  data,
				  uart_tx,
				  tx_down);
				  
	input clk;
	input rst;
	input[2:0] baud_set;//9600、19200、38400、57600、115200
	input[7:0] data;
	input send_en;//开关信号,发送数据
	output reg uart_tx;//接收信号
	output reg tx_down;//发完信号
	
	reg[17:0] btl;
	//参数定义
    parameter SCLK = 50_000_000;//系统时钟  50MHZ
	always@(*)
		case(baud_set)
		0:	btl = SCLK/9600;
		1:  btl = SCLK/19200;
		2:	btl = SCLK/38400;
		3:	btl = SCLK/57600;
		4:	btl = SCLK/115200;
		default: btl = SCLK/9600;//默认9600
		endcase
	
	//分频
	reg[17:0] div_cnt;//基本时钟
	always@(posedge clk or negedge rst)
	if(!rst)
		div_cnt <= 0;
	else if(send_en) begin 
		if(div_cnt == btl-1)
		div_cnt <= 0;
	    else
		div_cnt <= div_cnt + 1'b1;
	    end 
	else 
		div_cnt <= 0;
		

	//数据位+起始位+停止位 = 10位数据。但需要11个波特率时钟脉冲,第一个脉冲标记传
	//输的开始,第11个脉冲标记一次传输的结束。
	reg[3:0] bps_cnt;
	//assign bps_clk = (div_cnt == 1);
	always@(posedge clk or negedge rst)
	if(!rst)
		bps_cnt <= 0;
	else  if(send_en) begin 
			if(div_cnt == 1) begin 
			if(bps_cnt == 11)
				bps_cnt <= 0;                                          
			else
				bps_cnt <= bps_cnt + 1'b1;
			end
		end
		else 
			bps_cnt <= 0;		
	
	    
	//发送数据
	always@(posedge clk or negedge rst)
	if(!rst) begin
		uart_tx <= 1;//uart_tx复位时为1,开始传送数据为0。
		tx_down <= 0;
	end
	else case(bps_cnt)
		 1: begin  uart_tx <= 0; tx_down <= 0; end
		 2: uart_tx <= data[0];
		 3: uart_tx <= data[1];
		 4: uart_tx <= data[2];
		 5: uart_tx <= data[3];
		 6: uart_tx <= data[4];
		 7: uart_tx <= data[5];
		 8: uart_tx <= data[6];
		 9: uart_tx <= data[7];
		 10: uart_tx <= 1;
		 11: begin uart_tx <= 1; tx_down <= 1'b1; end
		 default: uart_tx <= uart_tx;
		 endcase
	
endmodule

激励:

		 
//激励
module uart01_tb();

reg clk;
reg rst;
reg[2:0] baud_set;
reg send_en;
reg[7:0] data;
wire uart_tx;
wire tx_down;


	uart01   uartdemo(  
				  .clk(clk), 
                  .rst(rst),
				  .baud_set(baud_set),
				  .send_en(send_en),
				  .data(data),
				  .uart_tx(uart_tx),
				  .tx_down(tx_down)
				  );
				  
initial clk = 1;
always #10 clk = ~clk;
	
initial begin
		rst = 0;
		data = 0;
		send_en = 0;
		baud_set = 4;
		#201;
		rst = 1;
		#100;
		data = 8'h57;
		send_en = 1;
		#20;
		@(posedge tx_down);//等待tx_down发送完
		send_en = 0;
		#200000;
		data = 8'h75;
		send_en = 1;
		#20;
		@(posedge tx_down);
		send_en = 0;
		#2000000000;//2s
		$stop;
    end
	
endmodule

仿真波形如下:
在这里插入图片描述在这里插入图片描述
通过仿真来看,大致完成了设计要求。
若 //assign bps_clk = (div_cnt == 1);添加bps_clk
则仿真波形图为:
在这里插入图片描述

三、串口发送数据任务

设计一个数据发送器,每10ms以115200的波特率发送一个数据,每次发送的数据比前一个数大一

module uart( clk, 
             rst,
		     baud_set,
			 send_en,
		     data,
			 uart_tx,
			 tx_down
			 );
				  
	input clk;
	input rst;
	input[2:0] baud_set;//9600、19200、38400、57600、115200
	input[7:0] data;
	input send_en;//开关信号,发送数据
	output reg uart_tx;//接收信号
	output reg tx_down;//发完信号
	
	reg[17:0] btl;
	//参数定义
    parameter SCLK = 50_000_000;//系统时钟  50MHZ
	always@(*)
		case(baud_set)
		0:	btl = SCLK/9600;
		1:  btl = SCLK/19200;
		2:	btl = SCLK/38400;
		3:	btl = SCLK/57600;
		4:	btl = SCLK/115200;
		default: btl = SCLK/9600;//默认9600
		endcase
	
	//分频
	reg[17:0] div_cnt;//基本时钟
	always@(posedge clk or negedge rst)
	if(!rst)
		div_cnt <= 0;
	else if(send_en) begin 
		if(div_cnt == btl-1)
		div_cnt <= 0;
	    else
		div_cnt <= div_cnt + 1'b1;
	    end 
	else 
		div_cnt <= 0;
		

	//数据位+起始位+停止位 = 10位数据。但需要11个波特率时钟脉冲,第一个脉冲标记传
	//输的开始,第11个脉冲标记一次传输的结束。
	reg[3:0] bps_cnt;
	assign bps_clk = (div_cnt == 1);
	always@(posedge clk or negedge rst)
	if(!rst)
		bps_cnt <= 0;
	else  if(send_en) begin 
			if(bps_clk) begin 
			if(bps_cnt == 11)
				bps_cnt <= 0;                                          
			else
				bps_cnt <= bps_cnt + 1'b1;
			end
		end
		else 
			bps_cnt <= 0;		
	
	    
	//发送数据
	always@(posedge clk or negedge rst)
	if(!rst) begin
		uart_tx <= 1;//uart_tx复位时为1,开始传送数据为0。
		tx_down <= 0;
	end
	else case(bps_cnt)
		 0: tx_down <= 0;
		 1: uart_tx <= 0; 
		 2: uart_tx <= data[0];
		 3: uart_tx <= data[1];
		 4: uart_tx <= data[2];
		 5: uart_tx <= data[3];
		 6: uart_tx <= data[4];
		 7: uart_tx <= data[5];
		 8: uart_tx <= data[6];
		 9: uart_tx <= data[7];
		 10: uart_tx <= 1;
		 11: begin uart_tx <= 1; tx_down <= 1'b1; end
		 default: uart_tx <= uart_tx;
		 endcase
endmodule

module uartDemo( clk,
			     rst,
			     uart_tx
);

input clk;
input rst;
output uart_tx;


reg[7:0] data;
reg send_en;

//例化模块
uart demo01(
			.clk(clk),
			.rst(rst),
			.baud_set(3'd4),
			.send_en(send_en),
			.data(data),
			.uart_tx(uart_tx),
			.tx_down(tx_down)
			);

			
//10ms分频 =10^7ns = 500000 *20ns
parameter CNT = 500_000;
reg[18:0] cnt1;
always@(posedge clk or negedge rst)
	if(!rst)
	cnt1 <= 0;
	else if(cnt1 == CNT-1)
			cnt1 <= 0;
		 else
			cnt1 <= cnt1 + 1'b1;

//产生send_en信号	 

always@(posedge clk or negedge rst)
	if(!rst)
	send_en <= 0;
	else if(cnt1 == 1)
		 send_en <= 1'b1;
	else if(tx_down)
		 send_en <= 0;
		 
		 
//data变化

always@(posedge clk or negedge rst)
	if(!rst)
		data <= 0;
	else if(tx_down)//发完就+1
		data <= data+1'b1;	
		
endmodule

激励:

module uartDemo( clk,
			     rst,
			     uart_tx
);

input clk;
input rst;
output uart_tx;


reg[7:0] data;
reg send_en;

//例化模块
uart demo01(
			.clk(clk),
			.rst(rst),
			.baud_set(3'd4),
			.send_en(send_en),
			.data(data),
			.uart_tx(uart_tx),
			.tx_down(tx_down)
			);

			
//10ms分频 =10^7ns = 500000 *20ns
parameter CNT = 500_000;
reg[18:0] cnt1;
always@(posedge clk or negedge rst)
	if(!rst)
	cnt1 <= 0;
	else if(cnt1 == CNT-1)
			cnt1 <= 0;
		 else
			cnt1 <= cnt1 + 1'b1;

//产生send_en信号	 

always@(posedge clk or negedge rst)
	if(!rst)
	send_en <= 0;
	else if(cnt1 == 1)
		 send_en <= 1'b1;
	else if(tx_down)
		 send_en <= 0;
		 
		 
//data变化

always@(posedge clk or negedge rst)
	if(!rst)
		data <= 0;
	else if(tx_down)//发完就+1
		data <= data+1'b1;	
		
endmodule

仿真波形如下:
在这里插入图片描述
可以看出来波形是不太对的,计数器01和02持续的时间太短了,那么为什么会出现这样的问题呢?
data自增是因为tx_down为高电平1,由仿真图看出tx_down=1维持了三个周期,原意是维持一个周期即可,因此需要修改tx_down=1的条件。根据仿真图看出,在(bps_clk == 1) && (bps_cnt == 10)时,下一时刻就是tx_down=1。
在这里插入图片描述
修改代码:


module uart( clk, 
             rst,
		     baud_set,
			 send_en,
		     data,
			 uart_tx,
			 tx_down
			 );
				  
	input clk;
	input rst;
	input[2:0] baud_set;//9600、19200、38400、57600、115200
	input[7:0] data;
	input send_en;//开关信号,发送数据
	output reg uart_tx;//接收信号
	output reg tx_down;//发完信号
	
	reg[17:0] btl;
	//参数定义
    parameter SCLK = 50_000_000;//系统时钟  50MHZ
	always@(*)
		case(baud_set)
		0:	btl = SCLK/9600;
		1:  btl = SCLK/19200;
		2:	btl = SCLK/38400;
		3:	btl = SCLK/57600;
		4:	btl = SCLK/115200;
		default: btl = SCLK/9600;//默认9600
		endcase
	
	//分频
	reg[17:0] div_cnt;//基本时钟
	always@(posedge clk or negedge rst)
	if(!rst)
		div_cnt <= 0;
	else if(send_en) begin 
		if(div_cnt == btl-1)
		div_cnt <= 0;
	    else
		div_cnt <= div_cnt + 1'b1;
	    end 
	else 
		div_cnt <= 0;
		

	//数据位+起始位+停止位 = 10位数据。但需要11个波特率时钟脉冲,第一个脉冲标记传
	//输的开始,第11个脉冲标记一次传输的结束。
	reg[3:0] bps_cnt;
	assign bps_clk = (div_cnt == 1);
	always@(posedge clk or negedge rst)
	if(!rst)
		bps_cnt <= 0;
	else  if(send_en) begin 
			if(bps_clk) begin 
			if(bps_cnt == 11)
				bps_cnt <= 0;                                          
			else
				bps_cnt <= bps_cnt + 1'b1;
			end
		end
		else 
			bps_cnt <= 0;		
	
	    
	//发送数据
	always@(posedge clk or negedge rst)
	if(!rst) begin
		uart_tx <= 1;//uart_tx复位时为1,开始传送数据为0。
		tx_down <= 0;
	end
	else case(bps_cnt)
		 1: uart_tx <= 0; 
		 2: uart_tx <= data[0];
		 3: uart_tx <= data[1];
		 4: uart_tx <= data[2];
		 5: uart_tx <= data[3];
		 6: uart_tx <= data[4];
		 7: uart_tx <= data[5];
		 8: uart_tx <= data[6];
		 9: uart_tx <= data[7];
		 10: uart_tx <= 1;
		 11:  uart_tx <= 1; 
		 default: uart_tx <= uart_tx;
		 endcase
	//将tx_down信号单独拿出来
	always@(posedge clk or negedge rst)
    if(!rst) 
        tx_down <= 0;
    else if((bps_clk == 1)  && (bps_cnt == 10))//使tx_down只保持一个周期
        tx_down <= 1;
    else
        tx_down <= 0;
	
endmodule

仿真波形如下:基本完成了设计要求。
在这里插入图片描述
但是为了更加完善我们的代码,还需要进行一些修改。
(1)由于串口是一个异步的收发器,因此为了保证发送的数据在时钟到来的时候是稳定的,这里也需要对输入数据进行寄存。

//寄存数据,防止数据被修改
    reg [7:0]r_Data;
    always@(posedge clk)
    if(Send_Go)
        r_Data <= data;
    else
        r_Data <= r_Data;  

完整的代码为:

module uart( clk, 
             rst,
		     baud_set,
			 Send_Go,
		     data,
			 uart_tx,
			 tx_down
			 );
				  
	input clk;
	input rst;
	input[2:0] baud_set;//9600、19200、38400、57600、115200
	input[7:0] data;
	input Send_Go;//控制数据发送
	output reg uart_tx;//接收信号
	output reg tx_down;//发完信号
	
	reg[17:0] btl;
	//参数定义
    parameter SCLK = 50_000_000;//系统时钟  50MHZ
	always@(*)
		case(baud_set)
		0:	btl = SCLK/9600;
		1:  btl = SCLK/19200;
		2:	btl = SCLK/38400;
		3:	btl = SCLK/57600;
		4:	btl = SCLK/115200;
		default: btl = SCLK/9600;//默认9600
		endcase
		
	reg send_en;
    always@(posedge clk or negedge rst)
    if(!rst)       
        send_en <= 0;
    else if(Send_Go)//数据发送
        send_en <= 1;
    else if(tx_down)//停止发送
        send_en <= 0;
	
	//寄存数据,防止数据被修改
    reg [7:0]r_Data;
    always@(posedge clk)
    if(Send_Go)
        r_Data <= data;
    else
        r_Data <= r_Data;  
	
	
	//分频
	reg[17:0] div_cnt;//基本时钟
	always@(posedge clk or negedge rst)
	if(!rst)
		div_cnt <= 0;
	else if(send_en) begin 
		if(div_cnt == btl-1)
		div_cnt <= 0;
	    else
		div_cnt <= div_cnt + 1'b1;
	    end 
	else 
		div_cnt <= 0;
		

	//数据位+起始位+停止位 = 10位数据。但需要11个波特率时钟脉冲,第一个脉冲标记传
	//输的开始,第11个脉冲标记一次传输的结束。
	reg[3:0] bps_cnt;
	assign bps_clk = (div_cnt == 1);
	always@(posedge clk or negedge rst)
	if(!rst)
		bps_cnt <= 0;
	else  if(send_en) begin 
			if(bps_clk) begin 
			if(bps_cnt == 11)
				bps_cnt <= 0;                                          
			else
				bps_cnt <= bps_cnt + 1'b1;
			end
		end
		else 
			bps_cnt <= 0;		
	
	    
	//发送数据
	always@(posedge clk or negedge rst)
	if(!rst) begin
		uart_tx <= 1;//uart_tx复位时为1,开始传送数据为0。
		tx_down <= 0;
	end
	else case(bps_cnt)
		 1: uart_tx <= 0; 
		 2: uart_tx <= r_Data[0];
		 3: uart_tx <= r_Data[1];
		 4: uart_tx <= r_Data[2];
		 5: uart_tx <= r_Data[3];
		 6: uart_tx <= r_Data[4];
		 7: uart_tx <= r_Data[5];
		 8: uart_tx <= r_Data[6];
		 9: uart_tx <= r_Data[7];
		 10: uart_tx <= 1;
		 11:  uart_tx <= 1; 
		 default: uart_tx <= uart_tx;
		 endcase
	//将tx_down信号单独拿出来
	always@(posedge clk or negedge rst)
    if(!rst) 
        tx_down <= 0;
    else if((bps_clk == 1)  && (bps_cnt == 10))//使tx_down只保持一个周期
        tx_down <= 1;
    else
        tx_down <= 0;
	
endmodule

module uartDemo( clk,
			     rst,
			     uart_tx
);

input clk;
input rst;
output uart_tx;


reg[7:0] data;
reg Send_Go;

//例化模块
uart demo01(
			.clk(clk),
			.rst(rst),
			.baud_set(3'd4),
			.Send_Go(Send_Go),
			.data(data),
			.uart_tx(uart_tx),
			.tx_down(tx_down)
			);

			
//10ms分频 =10^7ns = 500000 *20ns
parameter CNT = 500_000;
reg[18:0] cnt1;
always@(posedge clk or negedge rst)
	if(!rst)
	cnt1 <= 0;
	else if(cnt1 == CNT-1)
			cnt1 <= 0;
		 else
			cnt1 <= cnt1 + 1'b1;

//产生send_en信号	 

always@(posedge clk or negedge rst)
    if(!rst)       
        Send_Go <= 0;
    else if(cnt1 == 1)
        Send_Go <= 1;//一个脉冲信号
    else
        Send_Go <= 0;
		 

//data变化
always@(posedge clk or negedge rst)
	if(!rst)
		data <= 0;
	else if(tx_down)//发完就+1
		data <= data+1'b1;	
		
endmodule

module uartDemo_tb();

reg clk;
reg rst;
wire uart_tx;


uartDemo  shiyan01(
			.clk(clk),
			.rst(rst),
			.uart_tx(uart_tx)
);

				  
initial clk = 1;
always #10 clk = ~clk;
	
initial begin
		rst = 0;
		#201;
		rst = 1;
		#5000000;//50ms
    end
	
endmodule

仿真波形节选如下:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值