协议篇之UART协议
一、写在前面
由于设计需要,需要入门学习一下UART协议。本文主要学习UART协议的数据帧结构,并根据其数据接收和数据发送的原理进行RTL级代码设计。
二、UART协议简介
通用异步收发传输协议(Universal Asynchronous Receiver/Transmitter Protocol,UART)是一种在计算机、嵌入式系统和其他电子设备之间传输数据的通信协议。它使用串行全双工通信来传输数据,发送和接收设备之间的数据传输是异步的,也就是说,每个比特都是按照预定义的时间间隔传输的,而不是在固定的时钟周期内进行传输。
UART协议通常使用RS-232或RS-485等物理层协议来连接发送和接收设备。由于UART协议是一种简单、易于实现的协议,因此它在很多应用中得到广泛应用,例如串口通信、控制台、模块通信等。在工业、汽车、医疗和家庭等领域,UART协议都有着广泛的应用。
三、UART协议数据帧结构
UART协议中传输数据是串行传输的,其数据帧结构通常包含以下几个部分:
- 起始位(Start Bit):一个低电平的比特,用于标识数据传输的开始;
- 数据位(Data Bits):包含要传输的数据的比特数,通常是5、6、7或8个比特。数据可以是数字、字母、符号或二进制数据;
- 奇偶校验位(Parity Bit):可选的比特,用于验证数据的正确性。奇偶校验位的值可以是奇校验或偶校验,它们根据数据位中1的个数确定;
- 停止位(Stop Bit):一个时钟高电平的比特,用于标识数据传输的结束;
值得注意的是,UART数据帧结构的长度和内容可以根据需要进行调整,但它们必须在发送和接收设备之间协商一致,以确保数据的正确传输。
UART完整的数据帧结构如下图所示(这里不包含校验位)。
3.1 UART发送过程
在UART数据发送的过程中,发送设备会先发送一个起始位START,然后发送数据位DATA(数据位的发送是先发送低位,后发送高位),然后发送可选的奇偶校验位PARITY(一般不设置奇偶校验位),最后发送一个停止位STOP。
3.2 UART接收过程
UART数据接收的过程,与UART数据发送的过程是差不多的,接收设备会一直等待发送设备的一个低电平的起始位。当接收到起始位,接收设备将开始接收发送设备发送的数据位,并将多比特的数据存储在缓冲区中。然后如果使用了奇偶校验位,接收设备需要使用接收到的奇偶校验位来校验数据位,验证接收到的数据是否正确。然后等待停止位的到来,结束一帧数据的接收,并将接收到的数据从缓冲区中读出,等待下一帧数据的到来。
3.3 UART传输速率
UART协议的数据传输速率有多种,其中我们常用的波特率有4种:9600bps、19200bps、38400bps和115200bps。
- 9600bps:UART通信中最常见的传输速率之一,适用于大多数低速数据传输应用;
- 19200bps:中等速度的传输速率,适用于一些需要快速传输数据的应用;
- 38400bps:较快的传输速率,适用于需要更快数据传输速度的应用;
- 115200bps:UART通信中更快的传输速率,适用于需要非常快速数据传输速度的应用;
同样的,UART的传输速率必须保证发送设备与接收设备之间一致,否则发送设备无法传输正确的数据到接收设备。
波特率表征串口传输的速率,比如串口波特率为9600bps,那么表示1秒可以传输9600bit,那么,每个比特传输的时间为:
如果我们给系统约束的系统时钟频率为50MHz,则系统时钟周期为20ns,也就是说,每个比特传输的时钟周期个数为:
也就是说,在系统时钟频率为50MHz的情况下,传输每个比特需要保持5208个时钟周期不变。
在这里,需要补充一个重要概念:波特率Baud与比特率Bit Rate是两个不同的概念。波特率与比特率的关系也可换算成:
比特率 = 波特率 * 单个调制状态对应的二进制位数
而在串口传输中,由于采用二进制传输,数值非0即1,所以在串口传输中,波特率与比特率的关系如下:
比特率 = 波特率
四、UART收发模块设计
4.1 UART接收模块设计
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/04/29 16:17:06
// Design Name:
// Module Name: uart_rx
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module uart_rx
#(
parameter BAUD_RATE = 9600 , //波特率
parameter SYS_CLK_FRE = 100_000_000 //系统时钟频率
)
(
input sys_clk , //系统时钟
input sys_rst_n, //复位信号
input uart_rx_i, //串行接收数据
output [7:0] uart_rx_data_o, //接收到的8bit信号
output rx_end //接收结束
);
/**************************参数**************************/
localparam CLK_DIV = SYS_CLK_FRE/BAUD_RATE; //单比特数据传输耗费时钟数量
localparam IDLE = 11'b0000_0000_001; //初始化
localparam START = 11'b0000_0000_010; //起始位
localparam BIT_0 = 11'd0000_0000_100; //第0比特
localparam BIT_1 = 11'b0000_0001_000; //第1比特
localparam BIT_2 = 11'b0000_0010_000; //第2比特
localparam BIT_3 = 11'b0000_0100_000; //第3比特
localparam BIT_4 = 11'b0000_1000_000; //第4比特
localparam BIT_5 = 11'b0001_0000_000; //第5比特
localparam BIT_6 = 11'b0010_0000_000; //第6比特
localparam BIT_7 = 11'b0100_0000_000; //第7比特
localparam STOP = 11'b1000_0000_000; //停止位
/**************************寄存器**************************/
reg r_uart_rx_i_dly1; //串行数据接收打一拍
reg r_uart_rx_i_dly2; //串行数据接收打两拍(消除亚稳态)
reg [$clog2(CLK_DIV)-1:0] clk_div_cnt; //时钟计数器
reg [7:0] rx_data ; //数据寄存
reg [7:0] r_uart_rx_data_o; //接收到的8bit数据
reg rx_en ; //接收使能(高电平期间,处于接收工作状态)
reg r_rx_end; //接收结束
reg [10:0] cuur_state;
reg [10:0] next_state;
/**************************网表型**************************/
wire negedge_check; //下降沿检测信号
/**************************组合逻辑**************************/
assign negedge_check = (~r_uart_rx_i_dly1) && r_uart_rx_i_dly2;
assign uart_rx_data_o = r_uart_rx_data_o;
assign rx_end = r_rx_end;
/**************************时序逻辑**************************/
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n) begin
r_uart_rx_i_dly1 <= 1'b1;
r_uart_rx_i_dly2 <= 1'b1;
end
else begin
r_uart_rx_i_dly1 <= uart_rx_i;
r_uart_rx_i_dly2 <= r_uart_rx_i_dly1;
end
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
rx_en <= 1'b0;
else if(negedge_check)
rx_en <= 1'b1;
else if(rx_end)
rx_en <= 1'b0;
else
rx_en <= rx_en;
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
clk_div_cnt <= 'd0;
else if(rx_en)
if(clk_div_cnt < CLK_DIV)
clk_div_cnt <= clk_div_cnt + 1'd1;
else
clk_div_cnt <= 'd0;
else
clk_div_cnt <= 'd0;
end
/**************************状态机**************************/
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cuur_state <= IDLE;
else
cuur_state <= next_state;
always @(*) begin
case(cuur_state)
IDLE : next_state = (negedge_check) ? START : IDLE;
START: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_0 : START;
BIT_0: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_1 : BIT_0;
BIT_1: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_2 : BIT_1;
BIT_2: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_3 : BIT_2;
BIT_3: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_4 : BIT_3;
BIT_4: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_5 : BIT_4;
BIT_5: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_6 : BIT_5;
BIT_6: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_7 : BIT_6;
BIT_7: next_state = (clk_div_cnt == CLK_DIV-1) ? STOP : BIT_7;
STOP : next_state = (clk_div_cnt == CLK_DIV>>1) ? IDLE : STOP;
default: next_state = IDLE;
endcase
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
rx_data <= 8'd0;
end
else begin
case(cuur_state)
BIT_0: rx_data[0] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[0];
BIT_1: rx_data[1] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[1];
BIT_2: rx_data[2] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[2];
BIT_3: rx_data[3] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[3];
BIT_4: rx_data[4] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[4];
BIT_5: rx_data[5] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[5];
BIT_6: rx_data[6] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[6];
BIT_7: rx_data[7] <= (clk_div_cnt == CLK_DIV>>1) ? uart_rx_i : rx_data[7];
default: rx_data <= rx_data;
endcase
end
end
always @(negedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n) begin
r_uart_rx_data_o <= 'd0;
r_rx_end <= 1'b0;
end
else if(cuur_state == STOP && clk_div_cnt == CLK_DIV>>1) begin
r_uart_rx_data_o <= rx_data;
r_rx_end <= 1'b1;
end
else begin
r_uart_rx_data_o <= r_uart_rx_data_o;
r_rx_end <= 1'b0;
end
endmodule
4.2 UART发送模块设计
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/04/28 11:03:30
// Design Name:
// Module Name: uart_tx
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module uart_tx
#(
parameter BAUD_RATE = 9600 , //波特率
parameter SYS_CLK_FRE = 100_000_000 //系统时钟频率
)
(
input sys_clk , //系统时钟
input sys_rst_n , //复位信号
input tx_start , //发送开始信号
input [7:0] uart_tx_data_i, //需要发送的8bit数据
output uart_tx_o , //串行发送数据
output tx_end //发送结束
);
/**************************参数**************************/
localparam CLK_DIV = SYS_CLK_FRE/BAUD_RATE; //单比特数据传输耗费时钟数量
localparam IDLE = 11'b0000_0000_001; //初始化
localparam START = 11'b0000_0000_010; //起始位
localparam BIT_0 = 11'd0000_0000_100; //第0比特
localparam BIT_1 = 11'b0000_0001_000; //第1比特
localparam BIT_2 = 11'b0000_0010_000; //第2比特
localparam BIT_3 = 11'b0000_0100_000; //第3比特
localparam BIT_4 = 11'b0000_1000_000; //第4比特
localparam BIT_5 = 11'b0001_0000_000; //第5比特
localparam BIT_6 = 11'b0010_0000_000; //第6比特
localparam BIT_7 = 11'b0100_0000_000; //第7比特
localparam STOP = 11'b1000_0000_000; //停止位
/**************************寄存器**************************/
reg [$clog2(CLK_DIV)-1:0] clk_div_cnt ; //时钟计数器
reg [7:0] r_uart_tx_data_i; //发送数据寄存
reg tx_en ; //发送使能(该信号为高电平期间,进行数据的发送)
reg r_uart_tx_o; //串行发送数据
reg r_tx_end ; //发送结束
reg [10:0] cuur_state; //现态
reg [10:0] next_state; //次态
/**************************网表型**************************/
assign uart_tx_o = r_uart_tx_o;
assign tx_end = r_tx_end;
/**************************组合逻辑**************************/
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
clk_div_cnt <= 'd0;
else if(tx_en && clk_div_cnt < CLK_DIV)
clk_div_cnt <= clk_div_cnt + 1'd1;
else
clk_div_cnt <= 'd0;
end
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
r_uart_tx_data_i <= 'd0;
else if(tx_start)
r_uart_tx_data_i <= uart_tx_data_i;
else
r_uart_tx_data_i <= r_uart_tx_data_i;
/**************************状态机**************************/
always @(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cuur_state <= IDLE;
else
cuur_state <= next_state;
always @(*) begin
case(cuur_state)
IDLE : next_state = (tx_start) ? START : IDLE;
START: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_0 : START;
BIT_0: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_1 : BIT_0;
BIT_1: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_2 : BIT_1;
BIT_2: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_3 : BIT_2;
BIT_3: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_4 : BIT_3;
BIT_4: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_5 : BIT_4;
BIT_5: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_6 : BIT_5;
BIT_6: next_state = (clk_div_cnt == CLK_DIV-1) ? BIT_7 : BIT_6;
BIT_7: next_state = (clk_div_cnt == CLK_DIV-1) ? STOP : BIT_7;
STOP : next_state = (clk_div_cnt == CLK_DIV>>1) ? IDLE : STOP;
default: next_state = IDLE;
endcase
end
always @(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
tx_en <= 1'b0;
r_uart_tx_o <= 1'b1;
r_tx_end <= 1'b0;
end
else begin
case(cuur_state)
IDLE :
begin
r_uart_tx_o <= 1'b1;
if(tx_start)
tx_en <= 1'b1;
else
tx_en <= 1'b0;
end
START: r_uart_tx_o <= 1'b0;
BIT_0: r_uart_tx_o <= r_uart_tx_data_i[0];
BIT_1: r_uart_tx_o <= r_uart_tx_data_i[1];
BIT_2: r_uart_tx_o <= r_uart_tx_data_i[2];
BIT_3: r_uart_tx_o <= r_uart_tx_data_i[3];
BIT_4: r_uart_tx_o <= r_uart_tx_data_i[4];
BIT_5: r_uart_tx_o <= r_uart_tx_data_i[5];
BIT_6: r_uart_tx_o <= r_uart_tx_data_i[6];
BIT_7: r_uart_tx_o <= r_uart_tx_data_i[7];
STOP :
begin
r_uart_tx_o <= 1'b1;
if(clk_div_cnt == CLK_DIV>>1)
r_tx_end <= 1'b1;
else
r_tx_end <= 1'b0;
end
default:
begin
tx_en <= 1'b0;
r_uart_tx_o <= 1'b1;
r_tx_end <= 1'b0;
end
endcase
end
end
endmodule
4.3 UART回环顶层模块
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2023/04/30 10:52:44
// Design Name:
// Module Name: uart_loop
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module uart_loop
#(
parameter BAUD_RATE = 9600 , //波特率
parameter SYS_CLK_FRE = 100_000_000 //系统时钟频率
)
(
input sys_clk , //系统时钟
input sys_rst_n , //复位信号
input uart_rx_i , //串行接收数据
output uart_tx_o //串行发送数据
);
/**************************网表型**************************/
wire rx_end ; //接收结束
wire tx_start; //发送开始
wire [7:0] uart_rx_data_o; //接收到的8bit数据
wire [7:0] uart_tx_data_i; //发送的8bit1数据
wire tx_end; //发送结束
/**************************组合逻辑**************************/
assign tx_start = rx_end;
assign uart_tx_data_i = uart_rx_data_o;
/**************************模块例化**************************/
//接收模块
uart_rx
#(
.BAUD_RATE (BAUD_RATE ),
.SYS_CLK_FRE(SYS_CLK_FRE)
)
uart_rx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.uart_rx_i (uart_rx_i ),
.uart_rx_data_o(uart_rx_data_o),
.rx_end (rx_end )
);
//发送模块
uart_tx
#(
.BAUD_RATE (BAUD_RATE ),
.SYS_CLK_FRE(SYS_CLK_FRE)
)
uart_tx_inst
(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.tx_start (tx_start ),
.uart_tx_data_i(uart_tx_data_i),
.uart_tx_o (uart_tx_o ),
.tx_end (tx_end )
);
endmodule
4.4 UART回环上板验证
将比特流文件写入FPGA开发板后,打开串口助手,依次发送11、22给FPGA,FPGA在接收到数据后又发送回PC端,可以看到在串口助手接收窗口中分别接收到了数据11和22。
也可以连续发送多个字节数据,分别发送33、44、55、66、77、88、99、AA、BB、CC、DD、EE、FF给FPGA,在串口助手的接收窗口中看到PC端接收到了FPGA发送的数据33、44、55、66、77、88、99、AA、BB、CC、DD、EE、FF,上板验证通过。
五、写在最后
在本文中,我们学习了UART数据帧结构以及UART协议中一些基本的概念,并使用Verilog实现串口通信的收发操作,并进行上板验证。欢迎评论区友好交流批评指正!!!