目录
写在前面
UART协议在之前的一篇博客中有介绍,直达链接如下:
这里只是做简单的介绍,重点在 Verilog 实现部分。
UART 工作原理
将要传输数据的UART从数据总线接收数据。数据总线用于通过另一个设备(如CPU,内存或微控制器)将数据发送到UART。数据以并行形式从数据总线传输到传输UART。在发送UART从数据总线获取并行数据后,它会添加一个起始位、一个奇偶校验位和一个停止位,从而创建数据包。接下来,数据包在Tx引脚上逐位串行输出。接收UART在其Rx引脚上逐位读取数据包。然后,接收的UART将数据转换回并行形式,并删除起始位、奇偶校验位和停止位。最后,接收UART将数据包并行传输到接收端的数据总线:
UART传输的数据被组织成数据包。每个数据包包含 1 个起始位、5 到 9 个数据位(取决于 UART)、一个可选的奇偶校验位以及 1 个或 2 个停止位:
UART 接收部分
UART RX 模块图
UART 接受模块的作用是将接受到的串行数据转换成并行数据。
由于我做的实验工程是将 UART 和 RS485 共同使用,所以需要一个信号 dir 控制RS485的传输方向,如果不用 RS485 的话可以将此信号忽略。
UART 的接受模块分为六个信号,三个输入信号:时钟信号clk、复位信号(低有效)rst_n、接受串行数据信号;三个输出信号:并行数据po_data、输出并行数据的同步标志信号pi_flag、控制方向信号 dir。
UART RX 时序图
时序图清晰的描述了数据是如何变化的。
Verilog 实现 UART RX 模块
`timescale 1ns / 1ps
//
// Company:
// Engineer: Linest-5
// Create Date: 2022/04/14
// Design Name:
// Module Name: UART_RX
// Project Name:
// Target Devices:
// Tool Versions:
// Description: UART 接收模块,将串行的数据转成并行的数据接收
// Dependencies:
// Revision:
// Additional Comments:
//
//
module UART_RX #(
parameter BAUD_RATE = 'd9600, //波特率
parameter CLK_FREQ = 'd250000000, //时钟周期
parameter BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE
)
(
input wire clk, //接收到的数据
input wire rst_n, //复位信号,低电平有效
input wire rx, //输入的串行数据
output reg [7:0] po_data, //输出的并行数据
output reg po_flag //并行数据输出的同步标志信号
);
reg rx_reg1; //打三拍,稳定数据
reg rx_reg2;
reg rx_reg3;
reg start_flag; //数据稳定信号
reg work_en; //开始提取数据有效信号
reg bit_flag; //数据提取标志信号
reg [3:0] bit_cnt; //数据位计数信号
reg [12:0] baud_cnt; //比特计数信号,一个数据需要多少个时钟周期
reg [7:0] rx_data; //串转并数据
reg rx_flag; //串转并完成信号
//消除亚稳态
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
rx_reg1 <= 'd1;
end
else begin
rx_reg1 <= rx;
end
end
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
rx_reg2 <= 'd1;
end
else begin
rx_reg2 <= rx_reg1;
end
end
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
rx_reg3 <= 'd1;
end
else begin
rx_reg3 <= rx_reg2;
end
end
//下降沿检测
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
start_flag <= 'd0;
end
else if ((rx_reg2 == 0) && (rx_reg3 == 1) && (work_en == 'd0)) begin
start_flag <= 'd1;
end
else begin
start_flag <= 'd0;
end
end
//开始数据计数信号
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
work_en <= 'd0;
end
else if (start_flag == 1) begin
work_en <= 'd1;
end
else if ((bit_flag == 1) && (bit_cnt == 'd8)) begin
work_en <= 'd0;
end
end
//波特计数信号,用时钟频率除以波特率即每个数据需要的时钟周期数
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
baud_cnt <= 'd0;
end
else if ((baud_cnt == BAUD_CNT_MAX-1) || (work_en == 'd0)) begin
baud_cnt <= 'd0;
end
else begin
baud_cnt <= baud_cnt + 'd1;
end
end
//数据中间提取最稳定
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
bit_flag <= 'd0;
end
else if (baud_cnt == BAUD_CNT_MAX/2-1) begin
bit_flag <= 'd1;
end
else begin
bit_flag <= 'd0;
end
end
//数据位计数信号,只去有效的数据位,起始位和结束位不要
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
bit_cnt <= 'd0;
end
else if ((bit_flag == 'd1) && (bit_cnt == 'd8)) begin
bit_cnt <= 'd0;
end
else if (bit_flag == 'd1) begin
bit_cnt <= bit_cnt + 'd1;
end
end
//将提取的数据串转并
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
rx_data <= 'd0;
end
else if ((bit_flag == 'd1) && (bit_cnt >= 'd1) && (bit_cnt <= 'd8)) begin
rx_data <= {rx_reg3,rx_data[7:1]};
end
end
//完成最后一个数据的拼接之后, 就把此信号拉高
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
rx_flag <= 'd0;
end
else if ((bit_flag == 'd1) && (bit_cnt == 'd8)) begin
rx_flag <= 'd1;
end
else begin
rx_flag <= 'd0;
end
end
//并行数据输出
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
po_data <= 'd0;
end
else if (rx_flag == 'd1) begin
po_data <= rx_data;
end
else begin
po_data <= po_data;
end
end
//并行数据输出标志信号
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
po_flag <= 'd0;
end
else begin
po_flag <= rx_flag;
end
end
endmodule
UART 发送部分
UART TX 模块图
输入信号:时钟信号clk、复位信号rst_n、输入并行数据、输入并行数据的同步标志信号
输出信号:发送串行数据tx、数据发送完成标志信号tx_done、方向控制信号dir、发哦是那个端空闲标志信号tx_ready
UART TX 时序图
Verilog 实现 UART TX 模块
`timescale 1ns / 1ps
//
// Company:
// Engineer: Linest-5
// Create Date: 2022/04/15
// Design Name:
// Module Name: UART_TX
// Project Name:
// Target Devices:
// Tool Versions:
// Description: UART 发送模块,将接收到的并行数据转成串行数据并发送
// Dependencies:
// Revision:
// Additional Comments:
//
//
module UART_TX(
input clk, //系统时钟
input rst_n, //系统复位,低电平有效
input valid, //数据有效信号
input [7:0] pi_data, //输入的并行数据
output tx, //并转串的输出数据
output tx_done, //并转串的输出数据发送完成标志信号
output reg dir=1, //控制max3485的dir使能信号
output reg ready, //tx模块可以接收数据信号
output reg ena //使能信号
);
reg [7:0] pi_data_reg; //输入数据寄存
reg work_en; //开始提取数据有效信号
reg [17:0] baud_cnt; //波特计数信号,每个数据需要的波特数
reg [3:0] bit_cnt; //数据位计数信号
reg bit_flag; //数据提取标志信号
reg tx_reg; //发送数据寄存
reg tx_done_reg; //发送数据完成标志信号
//参数定义
parameter CLK_FREQ = 'd50000000;
parameter BAUD_RATE = 'd9600;
parameter BAUD_CNT_MAX = CLK_FREQ/BAUD_RATE;
//检测输入数据的到来,并将数据寄存
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
pi_data_reg <= 'd0;
end
else if (valid == 'd1) begin
pi_data_reg <= pi_data ;
end
else begin
pi_data_reg <= pi_data_reg;
end
end
//工作使能,在数据标志信号为高时拉高,在发送数据完成信号为高时拉低
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
work_en <= 'd0;
end
else if (valid == 'd1) begin
work_en <= 'd1;
end
else if (tx_done == 'd1) begin
work_en <= 'd0;
end
end
//根据不同的波特率,对每个数据需要波特数进行计数
always @(posedge clk or negedge rst_n) begin
if(rst_n == 'd0)begin
baud_cnt <= 'd0;
end
else if(work_en == 'd1 && (baud_cnt == BAUD_CNT_MAX - 'd1))begin
baud_cnt <= 'd0 ;
end
else if (work_en == 'd1) begin
baud_cnt <= baud_cnt + 'd1 ;
end
else begin
baud_cnt <= 'd0;
end
end
//比特标志信号拉高,在每一个波特计数为1时拉高,相当于对数据的提取标志信号
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
bit_flag <= 'd0;
end
else if (baud_cnt == 'd1) begin
bit_flag <= 'd1;
end
else begin
bit_flag <= 'd0;
end
end
//比特计数信号,在对每一个数据为进行计数
always @(posedge clk or negedge rst_n) begin
if(rst_n == 'd0)begin
bit_cnt <= 'd0;
end
else if ((work_en == 'd1) && (bit_flag == 'd1) && (bit_cnt == 'd11)) begin
bit_cnt <= 'd0;
end
else if ((work_en == 'd1) && (bit_flag == 'd1)) begin
bit_cnt <= bit_cnt + 'd1;
end
else if (work_en == 'd0) begin
bit_cnt <= 'd0;
end
end
//对并行数据映射到串行数据
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
tx_reg <= 'd0;
end
else begin
case (bit_cnt)
4'd1: tx_reg <= 'd0;
4'd2: tx_reg <= pi_data_reg[0];
4'd3: tx_reg <= pi_data_reg[1];
4'd4: tx_reg <= pi_data_reg[2];
4'd5: tx_reg <= pi_data_reg[3];
4'd6: tx_reg <= pi_data_reg[4];
4'd7: tx_reg <= pi_data_reg[5];
4'd8: tx_reg <= pi_data_reg[6];
4'd9: tx_reg <= pi_data_reg[7];
4'd10:tx_reg <= 'd1;
4'd11:tx_reg <= 'd1;
default :tx_reg <= 'd1;
endcase
end
end
//发送数据完成标志信号
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
tx_done_reg <= 'd0;
end
else if ((bit_flag == 'd1) && (bit_cnt == 'd10)) begin
tx_done_reg <= 'd1;
end
else begin
tx_done_reg <= 'd0;
end
end
//在tx模块发送数据完成时或者空闲状态时拉高,拉高表示可以接收上游传来的数据,在数据并转串时拉低
always @(posedge clk or negedge rst_n) begin
if (rst_n == 'd0) begin
ready <= 'd1;
end
else if (tx_done == 'd1) begin
ready <= 'd1;
end
else if (valid == 'd1) begin
ready <= 'd0;
end
end
//Bram的使能信号
always @(posedge clk or negedge rst_n) begin
if (rst_n) begin
ena <= 'd0;
end
else begin
ena <= tx_done;
end
end
assign tx = tx_reg;
assign tx_done = tx_done_reg;
endmodule
总结
UART 的时序并不复杂,最重要的是理解比特率的概念,以及在计数当中如何设定,这两个模块在实际的工程中可以正常跑通。