(一)FPGA之串口通信(UART)

(一)FPGA之串口通信(UART)

回到梦开始的地方,如今回过头来看串口协议,确实清晰了很多,但是奈何好记性不如烂笔头,我还是要重新记录一下学习的知识点,方便查找和学习。
波特率(Band Rate):
串口协议中很重要的一点就是波特率,波特率的概念是每秒钟传送码元的个数,就是一秒钟传输了几个二进制的个数,他的单位是Bit/s和bps两种。常见的串口速度有115200bps 9600bps等等,串口(RS232)的最大传输速率是 115200bps,表示一秒钟传输了115200个二进制 。
波特率和字节的关系
1GB=1024MB
1MB=1024KB
1KB=1024B(字节)

我们需要串口接收的数据数每秒512字节,串口的波特率是115200位/秒

波特率115200=115200(位/秒)

如果没有校验位,就应该除以10,得到的是每秒字节数:波特率115200=115200(位/秒)=11520(字节/秒)

再除以1024,就是每秒KB数:波特率115200=115200(位/秒)=11.25(KB/秒)也就是满足每秒可以接收512字节。

**在Verilog代码中,我们只需要理解计算这两个值就可以完成串口代码的梳理,**假设我们FPGA使用的是50MHZ的系统时钟 波特率使用的是9600bps 传输一个bit需要的时钟周期个数是50_000_000/9600个个数,得到个数之后再用这个个数乘以周期的时间便是传输1bit需要的时间50_000_000/9600*20便是1bit需要的时间。

Uart通信协议

1.串口通信的信号线只需要两条线就可以完成,TX和RX TX发送端 RX为接收端。
2.起始位,数据线从高变低,低有效为0,数据传输开始。
3.数据位,起始位传输之后便是数据位开始,一般为8位,传输时低位(LSB)在前,高位(MSB)在后。
4.校验位,校验位可以认为是一个特殊的数据位,通常使用的是奇偶校验,使用串口协议时通常取消奇偶校验位。
5.停止位,停止位高有效为1,他表示这一个个字节传输结束。
6.位时间,起始位、数据位、校验位的位宽度是一致的,停止位有0.5位、1位、1.5位格式,一般为1位。
7.空闲位,持续的高电平。
7.帧:从起始位开始到停止位结束的时间间隔称之为一帧。

接受模块

首先约定波特率为9600(可变),无校验位
将数据线打两拍,防止亚稳态并捕获其的下降沿信号(起始位0)
检测到下降沿后拉高数据传输标志,意味着数据开始接收;并在传输第九个数据(终止位1)的正中(数据比较稳定)将数据传输标志拉低,意味着数据接收完成
对时钟进行计数,每一个周期(在设定波特率条件下传输一位所需要的时间)对数据线进行采样并将数据寄存,同时记录传输个数(记录当前共接收了多少个数据)
接收到第九个数据(终止位1)后,拉高接收完成标志位
————————————————

module uart_rx(
	input 			sys_clk,			//50M系统时钟
	input 			sys_rst_n,			//系统复位
	input 			uart_rxd,			//接收数据线
	output reg 		uart_rx_done,		//数据接收完成标志
	output reg [7:0]uart_rx_data		//接收到的数据
);
//常量化波特率,可更改
parameter	BPS=9600;					//波特率9600bps,可更改
parameter	SYS_CLK_FRE=50_000_000;		//50M系统时钟
localparam	BPS_CNT=SYS_CLK_FRE/BPS;	//传输一位数据所需要的时钟个数
 
reg 			uart_rx_d0;		//寄存1拍
reg 			uart_rx_d1;		//寄存2拍
reg [15:0]		clk_cnt;				//时钟计数器
reg [3:0]		rx_cnt;					//接收计数器
reg 			rx_flag;				//接收标志位
reg [7:0]		uart_rx_data_reg;		//数据寄存
	
wire 			neg_uart_rx_data;		//数据的下降沿
 
assign	neg_uart_rx_data=uart_rx_d1 & (~uart_rx_d0);  //捕获数据线的下降沿,用来标志数据传输开始
//将数据线打两拍,作用1:同步不同时钟域信号,防止亚稳态;作用2:用以捕获下降沿
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		uart_rx_d0<=1'b0;
		uart_rx_d1<=1'b0;
	end
	else begin
		uart_rx_d0<=uart_rxd;
		uart_rx_d1<=uart_rx_d0;
	end		
end
//捕获到数据下降沿(起始位0)后,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		rx_flag<=1'b0;
	else begin 
		if(neg_uart_rx_data)
			rx_flag<=1'b1;
		else if((rx_cnt==4'd9)&&(clk_cnt==BPS_CNT/2))//在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
			rx_flag<=1'b0;
		else 
			rx_flag<=rx_flag;			
	end
end
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
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-1'b1)begin
			clk_cnt<=clk_cnt+1'b1;
			rx_cnt<=rx_cnt;
		end
		else begin
			clk_cnt<=16'd0;
			rx_cnt<=rx_cnt+1'b1;
		end
	end
	else begin
		rx_cnt<=4'd0;
		clk_cnt<=16'd0;
	end		
end
//在每个数据的传输过程正中(数据比较稳定)将数据线上的数据赋值给数据寄存器
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		uart_rx_data_reg<=8'd0;
	else if(rx_flag)
		if(clk_cnt==BPS_CNT/2) begin
			case(rx_cnt)			
				4'd1:uart_rx_data_reg[0]<=uart_rxd;
				4'd2:uart_rx_data_reg[1]<=uart_rxd;
				4'd3:uart_rx_data_reg[2]<=uart_rxd;
				4'd4:uart_rx_data_reg[3]<=uart_rxd;
				4'd5:uart_rx_data_reg[4]<=uart_rxd;
				4'd6:uart_rx_data_reg[5]<=uart_rxd;
				4'd7:uart_rx_data_reg[6]<=uart_rxd;
				4'd8:uart_rx_data_reg[7]<=uart_rxd;
				default:;
			endcase
		end
		else
			uart_rx_data_reg<=uart_rx_data_reg;
	else
		uart_rx_data_reg<=8'd0;
end	
//当数据传输到终止位时,拉高传输完成标志位,并将数据输出
always@(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		uart_rx_done<=1'b0;
		uart_rx_data<=8'd0;
	end	
	else if(rx_cnt==4'd9)begin
		uart_rx_done<=1'b1;
		uart_rx_data<=uart_rx_data_reg;
	end		
	else begin
		uart_rx_done<=1'b0;
		uart_rx_data<=8'd0;
	end
end
endmodule
————————————————

接收端仿真代码

`timescale 1ns/1ns	//定义时间刻度
//模块、接口定义
module uart_rx_tb();
reg sys_clk;			
reg sys_rst_n;			
reg uart_rxd;
 
wire uart_rx_done;		
wire uart_rx_data;
//例化被测试的接收模块
uart_rx #(
	.BPS			(9600),		//波特率9600
	.SYS_CLK_FRE	(50_000_000)//时钟频率50M	
)
u_uart_rx(
	.sys_clk		(sys_clk),			
	.sys_rst_n		(sys_rst_n),			
	.uart_rxd		(uart_rxd),			
	.uart_rx_done	(uart_rx_done),		
	.uart_rx_data	(uart_rx_data)	
);
 
localparam	CNT=50_000_000/9600*20;	//计算出传输每个时钟所需要的时间
initial begin	//传输8位数据	8'b01010101
	//初始时刻定义
		sys_clk	<=1'b0;	
		sys_rst_n<=1'b0;		
		uart_rxd<=1'b1;
	#20 //系统开始工作
		sys_rst_n<=1'b1;
	#(CNT/2)	
		uart_rxd<=1'b0;//开始传输起始位
	#CNT		
		uart_rxd<=1'b1;//传输最低位,第1位
	#CNT		
		uart_rxd<=1'b0;//传输第2位
	#CNT		
		uart_rxd<=1'b1;//传输第3位
	#CNT		
		uart_rxd<=1'b0;	//传输第4位
	#CNT		
		uart_rxd<=1'b1;//传输第5位
	#CNT		
		uart_rxd<=1'b0;//传输第6位
	#CNT		
		uart_rxd<=1'b1;//传输第7位
	#CNT		
		uart_rxd<=1'b0;	//传输最高位,第8位
	#CNT		
		uart_rxd<=1'b1;	//传输终止位
end
 
always begin
	#10	sys_clk=~sys_clk;	//时钟20ns,50M
end
 
endmodule 
————————————————

发送端代码

module uart_tx(
	input 			sys_clk,	//50M系统时钟
	input 			sys_rst_n,	//系统复位
	input	[7:0] 	uart_data,	//发送的8位置数据
	input			uart_tx_en,	//发送使能信号
	output reg 		uart_txd	//串口发送数据线
 
);
 
parameter 	SYS_CLK_FRE=50_000_000;    //50M系统时钟 
parameter 	BPS=9_600;                 //波特率9600bps,可更改
localparam	BPS_CNT=SYS_CLK_FRE/BPS;   //传输一位数据所需要的时钟个数
 
reg	uart_tx_en_d0;			//寄存1拍
reg uart_tx_en_d1;			//寄存2拍
reg tx_flag;				//发送标志位
reg [7:0]  uart_data_reg;	//发送数据寄存器
reg [15:0] clk_cnt;			//时钟计数器
reg [3:0]  tx_cnt;			//发送个数计数器
 
wire pos_uart_en_txd;		//使能信号的上升沿
//捕捉使能端的上升沿信号,用来标志输出开始传输
assign pos_uart_en_txd= uart_tx_en_d0 && (~uart_tx_en_d1);
 
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		uart_tx_en_d0<=1'b0;
		uart_tx_en_d1<=1'b0;		
	end
	else begin
		uart_tx_en_d0<=uart_tx_en;
		uart_tx_en_d1<=uart_tx_en_d0;
	end	
end
//捕获到使能端的上升沿信号,拉高传输开始标志位,并在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)begin
		tx_flag<=1'b0;
		uart_data_reg<=8'd0;
	end
	else if(pos_uart_en_txd)begin
		uart_data_reg<=uart_data;
		tx_flag<=1'b1;
	end
	else if((tx_cnt==4'd9) && (clk_cnt==BPS_CNT/2))begin//在第9个数据(终止位)的传输过程正中(数据比较稳定)再将传输开始标志位拉低,标志传输结束
		tx_flag<=1'b0;
		uart_data_reg<=8'd0;
	end
	else begin
		uart_data_reg<=uart_data_reg;
		tx_flag<=tx_flag;	
	end
end
//时钟每计数一个BPS_CNT(传输一位数据所需要的时钟个数),即将数据计数器加1,并清零时钟计数器
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
			clk_cnt<=clk_cnt+1'b1;
			tx_cnt <=tx_cnt;
		end
		else begin
			clk_cnt<=16'd0;
			tx_cnt <=tx_cnt+1'b1;
		end
	end
	else begin
		clk_cnt<=16'd0;
		tx_cnt<=4'd0;
	end
end
//在每个数据的传输过程正中(数据比较稳定)将数据寄存器的数据赋值给数据线
always @(posedge sys_clk or negedge sys_rst_n)begin
	if(!sys_rst_n)
		uart_txd<=1'b1;
	else if(tx_flag)
		case(tx_cnt)
			4'd0:	uart_txd<=1'b0;
			4'd1:	uart_txd<=uart_data_reg[0];
			4'd2:	uart_txd<=uart_data_reg[1];
			4'd3:	uart_txd<=uart_data_reg[2];
			4'd4:	uart_txd<=uart_data_reg[3];
			4'd5:	uart_txd<=uart_data_reg[4];
			4'd6:	uart_txd<=uart_data_reg[5];
			4'd7:	uart_txd<=uart_data_reg[6];
			4'd8:	uart_txd<=uart_data_reg[7];
			4'd9:	uart_txd<=1'b1;
			default:;
		endcase
	else 	
		uart_txd<=1'b1;
end
endmodule

————————————————
发送端仿真代码

`timescale 1ns/1ns	//定义时间刻度
//模块、接口定义
module uart_tx_tb();
reg 		sys_clk;			
reg 		sys_rst_n;			
reg [7:0]	uart_data;
reg 		uart_tx_en;
		
wire 	 	uart_txd;
//例化被测试的接收模块
uart_tx #(
	.BPS			(9600),		//波特率9600
	.SYS_CLK_FRE	(50_000_000)//时钟频率50M	
)
u_uart_tx(
	.sys_clk		(sys_clk),			
	.sys_rst_n		(sys_rst_n),			
	.uart_data		(uart_data),			
	.uart_tx_en		(uart_tx_en),		
	.uart_txd		(uart_txd)	
);
 
localparam	CNT=50_000_000/9600*20;	//计算出传输每个时钟所需要的时间
initial begin	//传输8位数据	8'b01010101
	//初始时刻定义
		sys_clk		<=1'b0;	
		sys_rst_n	<=1'b0;		
		uart_tx_en	<=1'b0;
		uart_data	<=8'b01010101;//发送数据 01010101	
	#20 //系统开始工作
		sys_rst_n   <=1'b1;
	#(CNT/2)
		uart_tx_en	<=1'b1;	
	#20
		uart_tx_en	<=1'b0;		
end
 
always begin
	#10	sys_clk=~sys_clk;	//时钟20ns,50M
end
 
endmodule 

————————————————

Uart顶层模块

module uart_top(
	input 	sys_clk,	//系统时钟
	input 	sys_rst_n,	//系统复位
 
	input 	uart_rxd,	//接收端口
	output 	uart_txd	//发送端口
 
);
parameter	UART_BPS=9600;			//波特率
parameter	CLK_FREQ=50_000_000;	//系统频率50M	
 
wire uart_en_w;
wire [7:0] uart_data_w; 
 
//例化发送模块
uart_tx#(
	.BPS		    (UART_BPS),
	.SYS_CLK_FRE	(CLK_FREQ))
u_uart_tx(
	.sys_clk		(sys_clk),
	.sys_rst_n	    (sys_rst_n),
	.uart_tx_en		(uart_en_w),
	.uart_data	    (uart_data_w),	
	.uart_txd	    (uart_txd)
);
//例化接收模块
uart_rx #(
	.BPS				(UART_BPS),
	.SYS_CLK_FRE		(CLK_FREQ))
u_uart_rx(
	.sys_clk			(sys_clk),
	.sys_rst_n		    (sys_rst_n),
	
	.uart_rxd		    (uart_rxd),	
	.uart_rx_done	    (uart_en_w),
	.uart_rx_data	    (uart_data_w)
);
 
endmodule

————————————————
顶层模块仿真代码

`timescale 1ns/1ns	//定义时间刻度
//模块、接口定义
module uart_top_tb();
reg 		sys_clk;			
reg 		sys_rst_n;			
reg 		uart_rxd;
		
wire 	 	uart_txd;
 
//例化被测试的接收模块
uart_top #(
	.UART_BPS		(9600),		//波特率9600
	.CLK_FREQ		(50_000_000)//时钟频率50M	
)
u_uart_top(
	.sys_clk		(sys_clk),			
	.sys_rst_n		(sys_rst_n),			
	.uart_rxd		(uart_rxd),			
	.uart_txd		(uart_txd)	
);
 
localparam	CNT=50_000_000/9600*20;	//计算出传输每个时钟所需要的时间
initial begin	//传输8位数据	8'b01010101
//初始时刻定义
		sys_clk	<=1'b0;	
		sys_rst_n<=1'b0;		
		uart_rxd<=1'b1;
	#20 //系统开始工作
		sys_rst_n<=1'b1;
	#(CNT/2)	
		uart_rxd<=1'b0;//开始传输起始位
	#CNT		
		uart_rxd<=1'b1;//传输最低位,第1位
	#CNT		
		uart_rxd<=1'b0;//传输第2位
	#CNT		
		uart_rxd<=1'b1;//传输第3位
	#CNT		
		uart_rxd<=1'b0;	//传输第4位
	#CNT		
		uart_rxd<=1'b1;//传输第5位
	#CNT		
		uart_rxd<=1'b0;//传输第6位
	#CNT		
		uart_rxd<=1'b1;//传输第7位
	#CNT		
		uart_rxd<=1'b0;	//传输最高位,第8位
	#CNT		
		uart_rxd<=1'b1;	//传输终止位
end
 
always begin
	#10	sys_clk=~sys_clk;	//时钟20ns,50M
end
 
endmodule
————————————————

将8位或者多位数据拆分为一位一位的发送出去的过程称为并转串。将一位一位接收的数据合并为8位或者多位数据的过程称为串转并。
对于串行通信设备来说,发送方都是在执行并转串,接收方都是在执行串转并。
UART设备为串行通信设备。

全双工通信、半双工通信和单工通信
全双工通信是指在同一时刻信息可以进行双向传输。例如:打电话,说的同时也能听。

半双工通信是指在同一时刻信息只能单向传输,双方都可以进行发送和接收,但是不能够同时发送和接收。例如:对讲机通信。

单工通信是指在通信过程中,只能够设备A发送,设备B接收。例如:听收音机。

SANXIN – B01的开发板上的UART接口设备可以做到半双工通信。

UART的通信电平标准
两个设备之间能够互相通信的基础条件为电平标准相同。UART的接口标准有很多,有RS232、RS485等等。

台式PC上一般会有一个DB9,接口标准为RS232。

此接口在各个工业板上也有很多。随着技术的发展,PC上的DB9的接口逐渐被淘汰,换成了USB接口。

我们的开发板上选择使用USB接口,方便大家学习,便于和PC进行通信。

FPGA芯片是无法(较为复杂)发出对应的电平标准,如:RS485、RS232、USB接口电平等。在大多数板卡设计时,都会在FPGA外围添加电平转换器,将FPGA的电平标准转换为通信的电平标准。

我们的开发板上采用USB <->UART(LVCOMS/LVTTL)的电平转换芯片CP2102。所以开发板上的供电端口不仅仅可以供电,还可以进行通信。

物联沃分享整理
物联沃-IOTWORD物联网 » (一)FPGA之串口通信(UART)

发表评论