15 UART回环

UART 串口简介

常用的通信方式可分为为串行通信(serial communication)和并行通信(parallel communication)两种。并行通信是多比特数据同时通过并行线进行传送(一般以字或字节为单位并行进行传输),这种传输方式用的线多、成本高、速率高、要考虑时延匹配,不宜进行长距离通信,因此并行通信一般用于板载外设的通信。串行通信是数据在一条线上一比特接一比特地按顺序进行传送,这种传输方式线少、成本低、速率低,事宜用于长距离通信,因此串行通信常用于不同设备之间的通信。
串行通信可分为同步串行通信(synchronized serial communication)和异步串行通信(asynchronous serial communication)。同步串行通信需要通信双方在同一时钟的控制下同步传输数据,如SPI、IIC等,而异步串行通信只需要通信双方约定好通信速率、起始标志、结束标志,然后在各自的时钟的控制下进行数据传输,如UART等。
UART 是一种采用异步串行通信方式的通用异步收发传输器(universal asynchronous receivertransmitter),它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。UART 通信需要两根信号线来实现,一根用于串口发送,另外一根负责串口接收。
在这里插入图片描述

UART传输时序

UART 在发送或接收过程中的一帧数据由 4 部分组成,起始位、数据位、奇偶校验位和停止位
在这里插入图片描述
起始位:不传输数据时 UART 数据传输线通常保持高电压电平。若要开始数据传输,发送方会将传输线从高电平拉到低电平并保持 1 个波特率周期。当接收方检测到高电平到低电平的跃迁时,便开始以波特率对应的频率读取数据帧中的位。
数据帧:数据帧包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是 5 位到 8 位,如果不使用奇偶校验位,数据帧长度可以是 9 位。在大多数情况下,数据以最低有效位优先方式发送。
奇偶校验:接收方可以通过奇偶校验位来判断数据传输期间是否发生改变(奇校验就是确保数据位和校验位中 1 的个数为奇数,偶校验就是确保数据位和校验位中 1 的个数为偶数)。
停止位:表示数据包结束,发送方将数据传输线从拉到高并保持 1 到 2 位的时间。
在进行UART通信时双方需要约定好数据位数(可选择 5、6、7、8 位)、奇偶校验(可选择奇校验、偶校验、无校验)、停止位(可选择 1 、1.5 、 2 位)、波特率(每秒传输二进制数据的位数)

UART 硬件

在设置好数据格式及波特率之后,UART 只负责完成数据的串并转换,而信号的传输则由外部驱动电路实现。电信号的传输过程有着不同的电平标准和接口规范,针对异步串行通信的接口标准有TTL、 RS232、RS422、RS485 等,它们定义了接口的电气特性,如 RS-232 是单端输入输出,而 RS-422/485 为差分输入输出等。其中RS232是最常见的标准之一,它一般采用DB9类型的接口,如下图所示
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

硬件设计

在这里插入图片描述
在这里插入图片描述

原理图中的 CH340 就是转换芯片(用于将 TTL 标准的 UART 转换为 USB ),其中 UART1_RXD 连接到 FPGA 芯片的的 RX (E14)管脚,UART1_TXD 连接到 FPGA 芯片的 TX(D17) 管脚,CH340_P/N 分别接到开发板的 TYPE - C 插座 D+/-上。

系统框图

本次实验任务是实现串口数据的回环,以此整个设计包括两个子模块,分别用于串口的发送和接收,其系统框图如下:
在这里插入图片描述

代码编写

串口接收模块设计

串口接收模块的框图如下:
在这里插入图片描述
其时序图如下:
在这里插入图片描述
代码如下:

module uart_rx #(
	parameter CLK_FREQ = 50000000,		//系统时钟
	parameter UART_BPS = 115200			//波特率
)(
	input sys_clk,						//系统时钟
	input sys_rst_n,					//系统复位

	input uart_rxd,						//串口接收线
	output reg data_flag,				//数据有效标志,随数据一起输出,只保持一个时钟周期
	output reg [7:0] rx_data			//串口接收到的数据
);

localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;	//波特率计数器的计数周期

//串口输入延迟一拍,用于捕获上升沿
reg uart_rxd_d0;
//启动标志,在空闲状态检测到开始信号拉高
wire start_flag;
//接收忙标志,接收过程中拉高
reg rx_busy;
//波特率计数器
reg [31:0] baud_count;
//接收计数
reg [3:0] rx_count;
//串口接收移位寄存器
reg [7:0] uart_rx_data;

//延迟一拍,用于捕获下降沿
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		uart_rxd_d0 <= 1'b1;
	else
		uart_rxd_d0 <= uart_rxd;
end

//捕获下降沿,在下降沿时刻且接收空闲则拉高start_flag
assign start_flag = ((uart_rxd_d0 & ~uart_rxd & ~rx_busy)) ? 1 : 0;

//接收忙标志控制
//检测到start_flag时设置为忙,当接收到8bit数据(不包括启动位)且波特率计数器溢出时设置为空闲
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		rx_busy <= 1'b0;
	else if(start_flag == 1'b1)
		rx_busy <= 1'b1;
	else if((rx_count == 4'd8) && (baud_count == (BAUD_CNT_MAX - 1)))
		rx_busy <= 1'b0;
end

//波特率计数器,当接收处于忙状态时进行周期计数
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		baud_count <= 0;
	else if(rx_busy == 1'b1) begin
		if(baud_count < (BAUD_CNT_MAX - 1))
			baud_count <= baud_count + 1;
		else
			baud_count <= 0;
	end
	else
		baud_count <= 0;
end

//接收计数器,当接收忙且波特率计数器溢出时进行递增计数
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		rx_count <= 4'b0;
	else if(rx_busy == 1'b1) begin
		if(baud_count == (BAUD_CNT_MAX - 1))
			rx_count <= rx_count + 4'b1;
	end
	else
		rx_count <= 4'b0;
end

//将数据采集到移位寄存器中
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		uart_rx_data <= 8'b0;
	else if(rx_busy == 1'b1) begin
		if(baud_count == ((BAUD_CNT_MAX - 1) / 2)) begin
			case(rx_count)
				4'd1: uart_rx_data[0] <= uart_rxd_d0;	//bit0
				4'd2: uart_rx_data[1] <= uart_rxd_d0;	//bit1
				4'd3: uart_rx_data[2] <= uart_rxd_d0;	//bit2
				4'd4: uart_rx_data[3] <= uart_rxd_d0;	//bit3
				4'd5: uart_rx_data[4] <= uart_rxd_d0;	//bit4
				4'd6: uart_rx_data[5] <= uart_rxd_d0;	//bit5
				4'd7: uart_rx_data[6] <= uart_rxd_d0;	//bit6
				4'd8: uart_rx_data[7] <= uart_rxd_d0;	//bit7
				default: ;
			endcase
		end
	end
	else
		uart_rx_data <= 8'b0;
end

//数据有效标志输出
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		data_flag <= 1'b0;
	else if((rx_count == 8) && (baud_count == (BAUD_CNT_MAX - 1)))
		data_flag <= 1'b1;
	else
		data_flag <= 1'b0;
end

//数据输出
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		rx_data <= 1'b0;
	else if((rx_count == 8) && (baud_count == (BAUD_CNT_MAX - 1)))
		rx_data <= uart_rx_data;
end

endmodule

串口发送模块设计

串口发送模块框图如下:
在这里插入图片描述
对应的时序图如下:
在这里插入图片描述
程序代码如下:

module uart_tx #(
	parameter CLK_FREQ = 50000000,		//系统时钟
	parameter UART_BPS = 115200			//波特率
)(
	input sys_clk,						//系统时钟
	input sys_rst_n,					//系统复位

	output reg uart_txd,				//串口发送线
	input data_flag,					//数据有效标志,随数据一起输入,只保持一个时钟周期
	input [7:0] tx_data,				//串口需要发送的数据
	output reg tx_busy					//串口发送忙标志
);

localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS;	//波特率计数器的计数周期

//波特率计数器
reg [31:0] baud_count;
//接收计数
reg [3:0] tx_count;
//串口接收移位寄存器
reg [7:0] uart_tx_data;

//切换到忙状态控制
//当数据有效且处于空闲状态则转换到忙状态
//当完成发送后又转换到空闲状态,发送空闲周期时提前BAUD_CNT_MAX/32周期结束,确保回环测试时发送速度大于接收速度
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		tx_busy <= 1'b0;
	else if((data_flag == 1'b1) && (tx_busy == 1'b0))
		tx_busy <= 1'b1;
	else if((tx_count == 10'd9) && (baud_count == (BAUD_CNT_MAX - BAUD_CNT_MAX / 32 - 1)))
		tx_busy <= 1'b0;
end

//锁定需要发送的数据到移位寄存器
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		uart_tx_data <= 8'b0;
	else if((data_flag == 1'b1) && (tx_busy == 1'b0))
		uart_tx_data <= tx_data;
	else if((tx_count == 4'd9) && (baud_count == (BAUD_CNT_MAX - BAUD_CNT_MAX / 32 - 1)))
		uart_tx_data <= 8'b0;
end

//波特率计数器,当发送处于忙状态时进行周期计数
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		baud_count <= 0;
	else if(tx_busy == 1'b1) begin
		if(baud_count < (BAUD_CNT_MAX - 1))
			baud_count <= baud_count + 1;
		else
			baud_count <= 0;
	end
	else
		baud_count <= 0;
end

//发送计数器,当发送忙且波特率计数器溢出时进行递增计数
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		tx_count <= 4'b0;
	else if(tx_busy == 1'b1) begin
		if(baud_count == (BAUD_CNT_MAX - 1))
			tx_count <= tx_count + 4'b1;
	end
	else
		tx_count <= 4'b0;
end

//将移位寄存器中的数据通过txd发送出去
always @(posedge sys_clk) begin
	if(!sys_rst_n)
		uart_txd <= 8'b1;
	else if(tx_busy == 1'b1) begin
		case(tx_count)
			4'd0: uart_txd <= 1'b0;				//起始位
			4'd1: uart_txd <= uart_tx_data[0];	//bit0
			4'd2: uart_txd <= uart_tx_data[1];	//bit1
			4'd3: uart_txd <= uart_tx_data[2];	//bit2
			4'd4: uart_txd <= uart_tx_data[3];	//bit3
			4'd5: uart_txd <= uart_tx_data[4];	//bit4
			4'd6: uart_txd <= uart_tx_data[5];	//bit5
			4'd7: uart_txd <= uart_tx_data[6];	//bit6
			4'd8: uart_txd <= uart_tx_data[7];	//bit7
			4'd9: uart_txd <= 1'b1;				//停止位
			default: uart_txd <= 1'b1;
		endcase
	end
	else
		uart_txd <= 8'b1;
end

endmodule

顶层模块设计

顶层模块主要负责例化串口接收和发送模块,并将接收到的数据给发送模块进行发送,相应的代码如下:

module uart_loopback #(
	parameter CLK_FREQ = 50000000,		//系统时钟
	parameter UART_BPS = 115200			//波特率
)(
	input sys_clk,						//系统时钟
	input sys_rst_n,					//系统复位

	input uart_rxd,					//串口接收线
	output uart_txd					//串口发送线
);

//串口输入延迟一拍
reg uart_rxd_d0;
//串口输入延迟二拍
reg uart_rxd_d1;

//数据有效标志
wire data_flag;
//串口收到的数据
wire [7:0] rx_data;
//串口发送忙标志
wire tx_busy;

//延迟两拍,用于消除亚稳态
always @(posedge sys_clk) begin
	if(!sys_rst_n) begin
		uart_rxd_d0 <= 1'b1;
		uart_rxd_d1 <= 1'b1;
	end
	else begin
		uart_rxd_d0 <= uart_rxd;
		uart_rxd_d1 <= uart_rxd_d0;
	end
end

//例化串口接收模块
uart_rx #(
	.CLK_FREQ(CLK_FREQ),			//系统时钟
	.UART_BPS(UART_BPS)				//波特率
)u_uart_rx_inst0(
	.sys_clk(sys_clk),				//系统时钟
	.sys_rst_n(sys_rst_n),			//系统复位

	.uart_rxd(uart_rxd_d1),			//串口接收线
	.data_flag(data_flag),			//数据有效标志,随数据一起输出,只保持一个时钟周期
	.rx_data(rx_data)				//串口接收到的数据
);

//例化串口发送模块
uart_tx #(
	.CLK_FREQ(CLK_FREQ),			//系统时钟
	.UART_BPS(UART_BPS)				//波特率
)u_uart_tx_inst0(
	.sys_clk(sys_clk),				//系统时钟
	.sys_rst_n(sys_rst_n),			//系统复位

	.uart_txd(uart_txd),			//串口发送线
	.data_flag(data_flag),			//数据有效标志,随数据一起输入,只保持一个时钟周期
	.tx_data(rx_data),				//串口需要发送的数据
	.tx_busy(tx_busy)				//串口发送忙标志
);

endmodule

仿真激励代码设计

仿真激励代码先对顶层模块进行复位,然后通过uart_rxd向顶层模块注入数据,同时还要产生50M的时钟,其代码如下:

`timescale 1ns / 1ps

module tb_uart_loopback();

//parameter define
parameter  CLK_PERIOD = 20;		//时钟周期为20ns

//reg define
reg sys_clk;					//时钟信号
reg sys_rst_n;					//复位信号
reg uart_rxd;					//UART接收端口
wire uart_txd;					//UART发送端口


//发送8'h55(8'b0101_0101)
initial begin
	sys_clk <= 1'b0;
	sys_rst_n <= 1'b0;
	uart_rxd <= 1'b1;
	#200
	sys_rst_n <= 1'b1;
	#1000
	uart_rxd <= 1'b0;	//起始位
	#8680				//延时433个时钟周期
	uart_rxd <= 1'b1;	//D0
	#8680
	uart_rxd <= 1'b0;	//D1
	#8680
	uart_rxd <= 1'b1;	//D2
	#8680
	uart_rxd <= 1'b0;	//D3
	#8680
	uart_rxd <= 1'b1;	//D4
	#8680
	uart_rxd <= 1'b0;	//D5
	#8680
	uart_rxd <= 1'b1;	//D6
	#8680
	uart_rxd <= 1'b0;	//D7 
	#8680
	uart_rxd <= 1'b1;	//停止位
	#8680
	uart_rxd <= 1'b1;	//空闲状态   
end

//50Mhz的时钟,周期则为1/50Mhz=20ns,所以每10ns,电平取反一次
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;

//例化顶层模块
uart_loopback  u_tb_uart_loopback_inst0(
	.sys_clk(sys_clk),
	.sys_rst_n(sys_rst_n),
	.uart_rxd(uart_rxd),
	.uart_txd(uart_txd)
);

endmodule
  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值