文章目录
本文涉及的所有代码仅用于学习交流,不得用于其他用途
一、UART简介
UART即通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),两个UART设备在进行通信时,发送数据的设备将并行的数据转化为一位一位的串行数据,并按照给定的时钟频率将串行数据依次打到TX通信线上,接收方检测RX上的电平,按照给定的时钟频率读取RX线上电平,并将采样到的串行数据还原成并行数据;

二、UART通信的特点
·异步:设备之间没有统一的时钟线连接
·串行:数据以固定的速率一位一位进行发送
·全双工:UART设备能够同时进行数据接收和发送
·逻辑电平:RS232(3V~25V为高电平 -3V或-25V为低电平)
TTL(5V或3.3V为高电平 0V为低电平)
首先UART是一个异步通信,即通信设备之间是不需要时钟线的,这不同于IIC与SPI等串行通信,UART设备通信双方均在自己的时钟系统下工作;
全双工也很好理解,这里发送线与接收线是独立的分开的,且在每个UART设备的UART模块中,都会设计串口接收模块与发送模块,两者可以同时工作;
UART通信使用的逻辑电平有很多,在单片机系统中长用TTL电平进行UART传输,除此之外还有RS232,RS485,但这些都不是一种协议,而是一种电平逻辑规范,我们从高电平的识别上来看,TTL允许电压波动的范围较小,因此它也是非常易受到干扰的;相比之下RS232的电压范围相对宽些;RS485使用的是差分传输,因此抗干扰能力更强;
综上,三种逻辑电平规范在传输距离上:TTL<RS232<RS485
(本次设计将基于TTL电平)
三、UART传输速率
波特率:描述UART设备传输数据快慢的单位,以每秒位数(bps)表示。
*UART对波特率的要求比较严格,因为其是异步通信,要求通信双方的波特率相差不能超过10%。
UART常用波特率:9600、115200、192000、230400等
假如选择的波特率为9600b/s,则其1s内能够传输9600bit,即9600/8 = 1200字节每秒;
波特率是否越大越好?
在平时的设备通信中,往往会觉得串口传输速率较慢,因此会把波特率提高来加快传输速率,但波特率也不是越大越好,而是越适合越好;
举个简单的例子,在单片机中UART模块后面往往会跟一个FIFO来做数据缓冲,因为单片机读取FIFO的速度有限,如果读FIFO速率小于UART向FIFO的写速率,且FIFO深度有限的情况下,则很容易造成FIFO满,满状态的FIFO不允许数据写入,此时就会造成丢包;
此时有以下几种方法解决,1.增加FIFO深度(参考FIFO文章)2.降低波特率使得读速率大于写速率。3.如果是突发传输,则要根据UART的接收频率、单片机的读出速率来合理计算FIFO深度,使得数据不会溢出(参考FIFO文章);
四、UART数据帧格式
空闲:UART数据线保持高电平。
起始位:传输线TXD由高电平转换为低电平。(必须有)
数据位:5-8位长度数据位,通常从最低位开始发送。(通常为8位)(必须有)
奇偶校验位:判断数据传输过程中是否有数据位发生错误变化。(可以无)
停止位:在数据包发送结束后,发送数据线保持1~2位高电平。(必须有)

UART在空闲时,会保持TX与RX线为高电平;
当一个数据帧进行发送时,首先发送端(TX)会将高电平拉低(发送一个0bit)示意通信的开始;另一接收端在检测到TX线被拉低后,则开始接收数据;
接下来发送端会将8位数据按照从低位到高位的顺序依次将其打到TX线上,接收方则按照对应波特率读取RX线电平;
如果通信双方设置了需要校验,则在8位数据发送结束后,会附加1bit的奇偶校验位;(数据量少的通信中不需要)
在通信需要结束时,发送端会将TX线拉高,接收方检测到这个高电平则结束这次通信;(停止位可以是1,1.5,2个,一般来说是1个)
例:通过UART发送数据0x33(不带校验位,且1个停止位)
对第三节中的字节传输速率做出修正:
如果是不带校验位,且只有一个停止位,则传输一个字节数据需要10bit,据此得到传输字节的真正速率:
9600/10 = 960字节每秒
五、UART模块设计
设计UART模块实际上是设计UART 发送模块与UART 接收模块;
在设计UART模块过程中需重点关注:
波特率如何产生?
串并转换、并串转换如何进行?
起始位、停止位如何检测?
rx信号如何采样?
波特率的产生:
波特率实际上就是一个分频器,其分频系数为fsys_clk/Baunds;
fsys_clk为UART模块所接的时钟源,Baunds则是所要得到的波特率;
例: fsys_clk = 50_000_000 Hz, Baunds = 115200,则分频系数Div0 = fsys_clk/Baunds = 50_000_000 / 115200 = 434
串并转换、并串转换:
并串转换应用于发送模块,其本质是一个移位寄存器,驱动时钟为波特率分频器输出的时钟,输入信号为并行8位数据,输出为串行数据;首先对并行的8位数据进行重建,得到数据帧;
如0x33则重建后的十位数据为:{1‘b1,0x33,1’b0},最低位的1’b0为起始位,最高位的1’b1为停止位;
串并转换应用于接收模块,其本质上也是一个移位寄存器,只不过其输出为8位并行数据,输入为串行数据;
起始位、停止位检测:
起始位的检测可以通过检测高电平到低电平的下降沿来实现;
停止位可以在接收满8位数据后,在第9位数据上进行检测;
rx信号采样:
接收信号首先需要进行同步处理,因为rx信号来自另一个时钟域;
其次同步后的信号需要采样其中间位置,因为此时的电平是最稳定的;

5.1 串行发送模块
综上,发送模块功能可划分为:

并串转换器在检测到有效的Valid信号后将tx_data包装成一帧数据{1‘b1,tx_data,1’b0}
同时发送状态机检测到valid信号后,进入到发送状态,此时波特率分频器开始计数,控制比特计数器增,并串转换模块则将对应比特位的数据串行发送出去。
源码:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Goodwayhouse
// Engineer: Ge Wen Jie
//
// Create Date: 2022/09/11 11:23:52
// Design Name:
// Module Name: uart_dtx
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module uart_dtx#(
parameter integer UART_TX_REF_CLK_FRE = 50_000_000,
parameter integer UART_TX_BAUNDS_RATE = 115200
)(
input wire uart_ref_clk,//uart tx module reference clock
input wire uart_tx_nrst,//uart tx module reset signal
input wire [31:0] uart_baunds_div,//baunds rate division
input wire [7:0] uart_tx_data,//uart tx 8bits data input
input wire uart_tx_data_qvld,//send data valid signal
output wire uart_tx_finish,//uart send data finish flag
output wire uart_tx_busy,//uart tx module busy flag
output wire uart_tx_dout //serial data output
);
//uart tx module baunds rater counter max, start data out, end data out definitions
localparam integer UART_BAUNDS_COUNTER_MAX = UART_TX_REF_CLK_FRE/UART_TX_BAUNDS_RATE;
localparam UART_TX_START_DOUT = 1'b0;
localparam UART_TX_END_DOUT = 1'b1;
//uart tx send state definition
localparam UART_IDLE0 = 3'b000;
// localparam UART_IDLE1 = 3'b001;
localparam UART_TXDATE = 3'b010;
// localparam UART_TXFINISH = 3'b100;
//inner signals definitions
reg tx_out;
reg tx_finish;
reg tx_busy;
reg [3:0] tx_bit_cnt;
reg [31:0] tx_baunds_cnt;
reg [2:0] uart_tx_state;
reg [2:0] uart_tx_next_state;
reg [9:0] uart_tx_buffer;
reg uart_wr_en;
//inner signal connect
assign uart_tx_finish = tx_finish;
assign uart_tx_busy = tx_busy;
assign uart_tx_dout = tx_out;
always@(posedge uart_ref_clk)
begin
if(~uart_tx_nrst)
begin
uart_tx_state <= UART_IDLE0;
end
else begin
uart_tx_state <= uart_tx_next_state;
end
end
always@(*)
begin
case(uart_tx_state)
UART_IDLE0://waiting a valid data input
begin
if(uart_tx_data_qvld)
begin
uart_tx_next_state <= UART_TXDATE;
end
else
begin
uart_tx_next_state <= UART_IDLE0;
end
end
UART_TXDATE://waiting tx module send data finish
begin
if((tx_finish == 1'b1) && (tx_busy == 1'b0))
begin
uart_tx_next_state <= UART_IDLE0;
end
else begin
uart_tx_next_state <= UART_TXDATE;
end
end
default:uart_tx_next_state <= UART_IDLE0;
endcase
end
always@(posedge uart_ref_clk)
begin
if(~uart_tx_nrst)
begin
tx_busy <= 'b0;
tx_finish <= 'b1;
tx_out <= 1'b1;
end
else begin
if((tx_finish == 1'b1)&&(tx_busy == 1'b0)&&(uart_tx_data_qvld))//sample date when tx finish and tx is not busy
begin
uart_tx_buffer <= {
UART_TX_END_DOUT,uart_tx_data,UART_TX_START_DOUT};
tx_finish <= 1'b0;
tx_busy <= 1'b1;
tx_out <= 1'b1;
end
else if((tx_finish == 1'b0)&&(tx_busy == 1'b1)&&(uart_tx_state == UART_TXDATE))
begin
if(uart_wr_en==1'b1)
begin
if(tx_bit_cnt != 4'd10)begin tx_out <= uart_tx_buffer[0];uart_tx_buffer <= uart_tx_buffer >> 1; end
else begin tx_out <= UART_TX_END_DOUT;tx_finish <= 1'b1;tx_busy <= 1'b0; end
end
else begin tx_busy <= tx_busy;tx_finish <= tx_finish;tx_out <= tx_out; end
end
end
end
always@(posedge uart_ref_clk)
begin
if(~uart_tx_nrst)
begin
tx_baunds_cnt <= 'b0;
tx_bit_cnt <= 'b0;
uart_wr_en <= 1'b0;
end
else begin
if(uart_tx_state == UART_IDLE0)
begin
tx_baunds_cnt <= 'b0;
tx_bit_cnt <= 'b0;
uart_wr_en <= 1'b0;
end
else if(uart_tx_state == UART_TXDATE)
begin
if(tx_baunds_cnt != uart_baunds_div - 1)
begin
uart_wr_en <= 1'b0;
tx_bit_cnt <= tx_bit_cnt;
tx_baunds_cnt <= tx_baunds_cnt + 1;
end
else begin
uart_wr_en <= 1'b1;
tx_baunds_cnt <= 'b0;
tx_bit_cnt <= tx_bit_cnt + 1;
end
end
end
end
endmodule
5.2 串行接收模块
串行模块的功能可划分为下图所示;

首先边沿检测电路对rx进行检测,如果检测到下降沿,则开始一次Uart接收,接收状态机检测到边沿检测电路发出的有效信号,则进入接收状态;
在接收状态下,波特率分频器开始工作,这里需要注意,为了能够在rx每比特的中间时刻采用,在第0bit时进行一次f(clk) / Baunds Rate / 2分频,并忽略第一次接收的数据;
比特计数器在检测到接收满8bit后,控制并串模块进行一次高电平停止位的检测,若成功则将并行数据输出,接收状态机回到空闲状态;
源码:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Goodwayhouse
// Engineer: Ge Wen Jie
//
// Create Date: 2022/09/15 16:52:19
// Design Name:
// Module Name: uart_drx
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module uart_drx#(
parameter integer UART_RX_REF_CLK_FRE = 50_000_000,
parameter integer UART_RX_BAUNDS_RATE = 115200
)(
input wire uart_ref_clk,//uart rx module reference clock
input wire uart_rx_nrst,//uart rx module reset signal
input wire [31:0] uart_baunds_div,//baunds rate division
input wire uart_rx_din, //serial data input
output wire uart_rx_data_qvld,//receive data valid signal
output wire uart_rx_finish,//uart send data finish flag
output wire uart_rx_busy,//uart rx module busy flag
output wire [7:0] uart_rx_data //uart rx 8bits data input
);
localparam IDLE0 = 2'b00;
localparam UART_RECV = 2'b01;
localparam integer UART_BAUNDS_COUNTER_MAX = UART_RX_REF_CLK_FRE/UART_RX_BAUNDS_RATE;
localparam UART_TX_START_DOUT = 1'b0;
localparam UART_TX_END_DOUT = 1'b1;
//inner signals definitions
reg rx_finish;
reg rx_busy;
reg [7:0] rx_data;
reg rx_data_qvld;
reg [7:0] rx_data_buffer;//receive data buffer
reg [3:0] bit_cnt;//receive bit counter
reg [1:0] uart_recv_state;
reg [1:0] uart_recv_next_state;
reg [31:0] rx_baunds_cnt;
reg uart_rd_en;
//inner signals connect
assign uart_rx_busy = rx_busy;
assign uart_rx_data = rx_data;
assign uart_rx_finish = rx_finish;
assign uart_rx_data_qvld = rx_data_qvld;
//uart send start check
wire uart_send_start;
reg uart_rx_din_dff;
assign uart_send_start = uart_rx_din_dff & ~uart_rx_din;
always @(posedge uart_ref_clk) begin
if (~uart_rx_nrst) begin
// reset
uart_rx_din_dff <= 1'b0;
end
else
begin
uart_rx_din_dff <= uart_rx_din;
end
end
always@(posedge uart_ref_clk)
begin
if (~uart_rx_nrst) begin
// reset
uart_recv_state <= IDLE0;
end
else
begin
uart_recv_state <= uart_recv_next_state;
end
end
always@(*)
begin
case(uart_recv_state)
IDLE0:begin//check if UART send start
if(uart_send_start == 1'b1) begin
uart_recv_next_state <= UART_RECV;
end
else begin
uart_recv_next_state <= IDLE0;
end
end
UART_RECV:begin //receiver data from master
if((rx_busy == 1'b0) && (rx_finish == 1'b1))begin
uart_recv_next_state <= IDLE0;
end
else begin
uart_recv_next_state <= UART_RECV;
end
end
default:uart_recv_next_state <= IDLE0;
endcase
end
always@(posedge uart_ref_clk)
begin
if(~uart_rx_nrst)begin
rx_data <= 'b0;
rx_data_buffer <= 'b0;
rx_busy <= 1'b0;
rx_finish <= 1'b1;
rx_data_qvld <= 1'b0;
end
else begin
if((rx_busy == 1'b0) && (rx_finish == 1'b1))
begin
rx_data_qvld <= 1'b0;
if(uart_send_start == 1'b1)
begin
rx_data_buffer <= 'b0;rx_busy <= 1'b1; rx_finish <= 1'b0;
end
end
else if((rx_busy == 1'b1) && (rx_finish == 1'b0) && (uart_recv_state == UART_RECV) && (uart_rd_en == 1'b1))
begin
if((bit_cnt != 4'd0)&&(bit_cnt != 4'd10))
begin
rx_data_buffer <= {
uart_rx_din,rx_data_buffer[7:1]};
end
// rx_data_buffer <= {uart_rx_din,rx_data_buffer[7:1]};
else if ((bit_cnt == 4'd10)&&(uart_rx_din == 1'b1)) begin
rx_busy <= 1'b0;
rx_finish <= 1'b1;
rx_data_qvld <= 1'b1;
rx_data <= rx_data_buffer;
rx_data_buffer <= 'b0;
end
end
end
end
always@(posedge uart_ref_clk)
begin
if(~uart_rx_nrst)
begin
rx_baunds_cnt <= 'b0;
bit_cnt <= 'b0;
uart_rd_en <= 1'b0;
end
else begin
if(uart_recv_state

本文介绍UART通信原理及特点,包括异步通信、串行数据传输和全双工操作。详细解析UART数据帧格式、波特率计算及其对传输速率的影响。提供UART发送与接收模块的Verilog实现,以及FIFO缓冲器和跨时钟域处理方案。最后,展示AXI接口封装方法,便于与CPU交互。
最低0.47元/天 解锁文章
904





