UART
什么是UART?
UART(Universal Asynchronous Receiver/Transmitter)通用异步收发器,是一种串行通信协议。
以上是官方说法,通俗的讲,这是一种传输数据的方式,即一串二进制数是以串行的方式按时间的先后传输的(通常低位在前,高位在后)
基础概念
- 波特率:单位是bps,即每秒传输的比特数
- BLCK:波特率时钟,数据线传输数据需要有自己的节奏,而FPGA时钟频率较高,所以我们需要根据比特率对时钟进行分频,然后作为数据线的时钟BLCK
- 起始位:数据传输的起始标志,通常为0
- 数据位:数据传输的内容,通常为8位
- 奇偶校验位:用于检查传输的数据是否正确,通常为1位
- 停止位:数据传输的结束标志,通常为1
bclk和clk的关系
发送器模块说明
定义:发送器是将并行数据转换为串行数据,将数据一位一位的发送出去,同时发送状态信号,表示数据正在传输。
- 输入信号:
- clk:时钟信号,频率为50MHz,用于分频,作为BLCK时钟
- rst_n:复位信号,低电平有效
- [7:0]data: 需要发送的数据
- tx_en: 发送使能信号,高电平有效
- 输出信号:
- td: 输出的数据
- tx_state: 发送器的状态,高电平表示正在传输数据
发送器verilog代码
//无校验位,时钟信号50mhz, 比特率115200
module uart_tx(
input wire clk,
input wire uart_rst_n,
input wire uart_en,
input wire [7:0] tx_data,
output reg txd,
output reg tx_state //检测状态信号
);
//波特率定义模块
parameter Clk_Fre = 50_000_000; //这里以50MHz为例
parameter BAUD = 115200; //比特率
parameter BAUD_DIV = Clk_Fre / BAUD; //发送一个bit的时钟周期个数
reg uart_en_d0;
reg uart_en_d1; //两级寄存器
wire en_flag; //开始传输标志
assign en_flag = (~uart_en_d1) & (uart_en_d0);
reg [7:0] uart_din; //寄存尚未传输的数据
reg [15:0] clk_cnt;
reg [3:0] tx_cnt;
//为了防止信号的亚稳态,需要两级寄存器,即打两拍,来降低出现亚稳态的频率
// 使能信号状态模块
always@(posedge clk or negedge uart_rst_n)begin
if(!uart_rst_n)begin
uart_en_d0 <= 1'b0;
uart_en_d1 <= 1'b0;
end
else begin
uart_en_d0 <= uart_en;
uart_en_d1 <= uart_en_d0;
end
end
//寄存待发送的数据,开始寄存,防止数据在传输结果中有变化
always@(posedge clk or negedge uart_rst_n)begin
if(!uart_rst_n)begin
uart_din <= 8'b0;
tx_state <= 1'b0;
end
else if(en_flag) begin
uart_din <= tx_data;
tx_state <= 1'b1;
end
else if(tx_cnt == 'd9 && clk_cnt == BAUD_DIV - BAUD_DIV / 16)begin
uart_din <= 0;
tx_state <= 1'b0;
end
else begin
uart_din <= uart_din;
tx_state <= tx_state;
end
end
//计数器模块 tx_cnt, clk_cnt
always @(posedge clk or negedge uart_rst_n)begin
if(!uart_rst_n)begin
tx_cnt <= 0;
end
else if(tx_state)begin
if(clk_cnt == BAUD_DIV - 1)begin //时间到达输出一个bit,bit位加一
tx_cnt <= tx_cnt + 1;
end
else
tx_cnt <= tx_cnt;
end
else tx_cnt <= 0;
end
//bclk产生模块
always @(posedge clk or negedge uart_rst_n)begin
if(!uart_rst_n)begin
clk_cnt <= 0;
end
else if(tx_state)begin
if(clk_cnt == BAUD_DIV - 1) clk_cnt <= 0;
else clk_cnt <= clk_cnt + 1;
end
else clk_cnt <= 0;
end
//数据输出模块
always @(posedge clk or negedge uart_rst_n)begin
if(!uart_rst_n)begin
txd <= 1;
end
else if(tx_state) begin
case(tx_cnt)
0: txd <= 0;
1: txd <= uart_din[0];
2: txd <= uart_din[1];
3: txd <= uart_din[2];
4: txd <= uart_din[3];
5: txd <= uart_din[4];
6: txd <= uart_din[5];
7: txd <= uart_din[6];
8: txd <= uart_din[7];
9: txd <= 1;
default: txd <= 1;
endcase
end
else
txd <= 1;
end
endmodule
测试
module tb_uart_tx;
// 参数定义
parameter Clk_Period = 20; // 时钟周期,单位为ns
parameter BAUD = 115200;
parameter Clk_Fre = 50_000_000;
parameter BAUD_DIV = Clk_Fre / BAUD;
// 信号定义
reg clk;
reg uart_rst_n;
reg uart_en;
reg [7:0] tx_data;
wire txd;
wire tx_state;
// 实例化被测试的模块
uart_tx uut (
.clk(clk),
.uart_rst_n(uart_rst_n),
.uart_en(uart_en),
.tx_data(tx_data),
.txd(txd),
.tx_state(tx_state)
);
// 时钟生成
always begin
#10 clk = ~clk;
end
// 激励模块
initial begin
// 初始化
clk = 0;
uart_rst_n = 0;
uart_en = 0;
tx_data = 8'b0;
// 复位后等待一段时间
#100 uart_rst_n = 1;
// 发送一个字节的数据
uart_en = 1;
tx_data = 8'b11011010; // 任意数据
// 等待一段时间,可以观察输出
#100;
// 关闭使能信号
uart_en = 0;
// 模拟测试结束
#100 $finish;
end
endmodule