一、UART简介
串行通信分为两种方式:同步串行通信和异步串行通信,两者的区别在于通信双方是否采用统一的工作时钟。同步串行通信需要通信双方在同一时钟的控制下,同步传输数据;异步串行通信是指通信双方使用各自的时钟控制数据的发送和接收过程。
通 用 异 步 收 发 传 输 器 (Universal Asynchronous Receiver/Transmitter) , 通 常 称 作UART。UART 是一种通用的数据通信协议,也是异步、串行通信口(串口)的总称,它在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。它包括了 RS232、RS499、RS423、RS422 和 RS485 等接口标准规范和总线标准规范。
单工通信:只需要一根数据线。在任何时刻都只能进行一个方向的通信,即一个固定为发送设备,另一个固定为接收设备通信。
半双工通信:需要两根数据线。可以进行两个方向的传输,但不能同时进行。
全双工通信:需要两根数据线。在同一时刻可以进行两个方向的传输,即两个设备可以同时收发数据。
UART 通信主要由发送数据端口线TX(Transmitter)和接收数据端口线RX(Receiver)组成。在通信时,通信一方的TX与另一方的 RX进行连接,如图一所示(勿错接)。
图一 UART通信连接图
二、UART协议层
协议层主要规定通信逻辑,统一双方的数据收发标准,即数据格式。
UART 串行通信中的数据被组织成称为数据包或帧的块。每个数据包包含1个起始位,5-8个数据位,1个可选的奇偶校验位,1或1.5或2个停止位,数据格式如图二所示:
图二 UART通信数据格式
空闲位:表总线空闲,固定为逻辑“1”状态;
起始位:标志着一帧数据的开始,将总线拉低,固定为1bit的逻辑“0”;
数据位:一帧数据中的有效数据,实际需传输的数据。
校验位:偶校验或奇校验;奇校验时,发送方应使数据位与校验位中 1 的个数之和为奇数;接收方在接收数据时,对 1 的个数进行检查,若不为奇数,则说明数据在传输过程中出了差错。同样,偶校验则检查 1 的个数是否为偶数。
停止位:标志着一帧数据的结束,将总线拉高,固定为1bit的逻辑“1”;
数据传输顺序:先传送数据的低位;
通信速度(波特率):它表示每秒传输二进制数据的位数,单位是 bps(位/秒),常用通信速度有2400、4800、9600、19200、115200 bit/s
UART 通信过程中的数据格式及传输速率是可设置的,为了正确的通信,收发双方应约定并遵循同样的设置,使得通信双方的数据位、校验位、停止位和波特率保持一致。
三、Verilog代码设计
由于针对UART的接口标准定义的电气特性不同,此次主要以RS232为例,设计一个串口环回实验,在此后应用时可直接单独应用UART接收模块和发送模块。串口环回具体功能:由PC机向FPGA发送串口数据,当FPGA接收到数据之后,再将接收到的数据发送至PC。模块总体框图如图三所示。
图三 UART_LOOP总体框图
图四 发送模块波形图设计
图五 接收模块波形图设计
接收模块:
module uart_rx(
input clk,
input rst_n,
input uart_rxd,
output reg uart_rx_done,
output reg [7:0] uart_rx_data
);
//parameter define
parameter CLK_FREQ = 50_000_000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次
reg uart_rxd_d0;//异步时钟打俩拍进行同步处理
reg uart_rxd_d1;
reg uart_rxd_d2;//下降沿提取,提取起始位
wire start_flag;//起始位
reg rx_flag; //接收过程标志信号
reg rx_cnt_flag;
reg [3:0] rx_cnt; //接收数据计数器
reg [15:0] baud_cnt; //波特率计数器,长位宽为了兼容高时钟、低波特率的情况
reg [7:0] rx_data_reg; //接收数据寄存器
//异步信号打拍处理,取沿变打三拍
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
uart_rxd_d0 <= 1'b0;
uart_rxd_d1 <= 1'b0;
uart_rxd_d2 <= 1'b0;
end
else begin
uart_rxd_d0 <= uart_rxd;
uart_rxd_d1 <= uart_rxd_d0;
uart_rxd_d2 <= uart_rxd_d1;
end
end
assign start_flag = (~uart_rxd_d1)& uart_rxd_d2;//取起始位下降沿
//rx_flag接收标志处理
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
rx_flag <= 1'b0;
else if(start_flag == 1'b1)//检测到起始位
rx_flag <= 1'b1;//接收过程中,标志信号rx_flag拉高
else if((rx_cnt == 4'd9) && (rx_cnt_flag == 1'b1))//在停止位一半的时候,即接收过程结束,标志信号rx_flag拉低
rx_flag <= 1'b0;
else
rx_flag <= rx_flag;
end
//baud_cnt计数器
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
baud_cnt <= 16'b0;
else if(rx_flag)begin
if(baud_cnt < BAUD_CNT_MAX - 1'b1)
baud_cnt <= baud_cnt + 1'b1;
else
baud_cnt <= 16'b0;
end
else
baud_cnt <= 16'b0;
end
//rx_cnt_flag产生一个周期高电平
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
rx_cnt_flag <= 1'b0;
else if((rx_flag == 1'b1) && (baud_cnt == BAUD_CNT_MAX / 2 - 1'b1))//在中间时刻拉高,此信号用于数据接收
rx_cnt_flag <= 1'b1;
else
rx_cnt_flag <= 1'b0;
end
//rx_cnt计数器,表示接收位数的累加
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
rx_cnt <= 4'b0;
else if(rx_flag)begin
if(rx_cnt_flag == 1'b1)
rx_cnt <= rx_cnt + 1'b1;
else
rx_cnt <= rx_cnt;
end
else
rx_cnt <= 4'b0;
end
//rx_data_t 存入接收数据
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
rx_data_reg <= 8'b0;
else if(rx_flag)begin
if(rx_cnt_flag == 1'b1)begin
case(rx_cnt)
4'd1:rx_data_reg[0] <= uart_rxd;
4'd2:rx_data_reg[1] <= uart_rxd;
4'd3:rx_data_reg[2] <= uart_rxd;
4'd4:rx_data_reg[3] <= uart_rxd;
4'd5:rx_data_reg[4] <= uart_rxd;
4'd6:rx_data_reg[5] <= uart_rxd;
4'd7:rx_data_reg[6] <= uart_rxd;
4'd8:rx_data_reg[7] <= uart_rxd;
default:rx_data_reg <= rx_data_reg;
endcase
end
else
rx_data_reg <= rx_data_reg;
end
else
rx_data_reg <= 8'b0;
end
//对uart_rx_done输出和uart_rx_data输出
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
uart_rx_done <= 1'b0;
uart_rx_data <= 8'b0;
end
else if((rx_cnt == 4'd9) && (rx_cnt_flag == 1'b1))begin
uart_rx_done <= 1'b1;
uart_rx_data <= rx_data_reg;
end
else begin
uart_rx_done <= 1'b0;
uart_rx_data <= uart_rx_data;
end
end
endmodule
发送模块:
module uart_tx(
input clk ,
input rst_n ,
input uart_tx_en ,
input [7:0] uart_tx_data ,
output reg uart_tx_busy ,
output reg uart_txd
);
parameter CLK_FREQ = 50_000_000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特
reg [3:0] tx_cnt; //接收数据计数器
reg [15:0] baud_cnt; //波特率计数器
reg [7:0] tx_data_reg; //发送数据寄存器
reg tx_cnt_flag; //产生tx_cnt计数的脉冲
//发送忙标志 数据寄存
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
uart_tx_busy <= 1'b0;
tx_data_reg <= 8'b0;
end
else if(uart_tx_en)begin//收到发送使能,开始发送
uart_tx_busy <= 1'b1;
tx_data_reg <= uart_tx_data;
end
else if((tx_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16))//发送的停止位的15/16,拉低
uart_tx_busy <= 1'b0;
else begin
uart_tx_busy <= uart_tx_busy;
tx_data_reg <= tx_data_reg;
end
end
//baud_cnt计数器
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
baud_cnt <= 16'b0;
else if(uart_tx_busy)begin
if(baud_cnt < BAUD_CNT_MAX-1'b1)
baud_cnt <= baud_cnt+1'b1;
else
baud_cnt <= 16'b0;
end
else
baud_cnt <= 16'b0;
end
//tx_cnt_flag产生开始脉冲
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
tx_cnt_flag <= 1'b0;
else if((uart_tx_busy) && (baud_cnt == BAUD_CNT_MAX-1'b1))
tx_cnt_flag <= 1'b1;
else
tx_cnt_flag <= 1'b0;
end
//tx_cnt计数器,表示接收位数的累加
always @(posedge clk or negedge rst_n)begin
if(~rst_n)
tx_cnt <= 4'b0;
else if(uart_tx_busy)begin
if(tx_cnt_flag == 1'b1)
tx_cnt <= tx_cnt + 1'b1;
else
tx_cnt <= tx_cnt;
end
else
tx_cnt <= 4'b0;
end
//根据tx_cnt来给uart发送端口赋值
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
uart_txd <= 1'b1;
else if(uart_tx_busy) begin
case(tx_cnt)
4'd0 : uart_txd <= 1'b0 ; //起始位
4'd1 : uart_txd <= tx_data_reg[0]; //数据位最低位
4'd2 : uart_txd <= tx_data_reg[1];
4'd3 : uart_txd <= tx_data_reg[2];
4'd4 : uart_txd <= tx_data_reg[3];
4'd5 : uart_txd <= tx_data_reg[4];
4'd6 : uart_txd <= tx_data_reg[5];
4'd7 : uart_txd <= tx_data_reg[6];
4'd8 : uart_txd <= tx_data_reg[7]; //数据位最高位
4'd9 : uart_txd <= 1'b1 ; //停止位
default : uart_txd <= 1'b1;
endcase
end
else
uart_txd <= 1'b1; //空闲时发送端口为高电平
end
endmodule
顶层模块:
module uart_232(
input sys_clk,//系统时钟
input sys_rst_n,//复位信号,低电平有效
input uart_rxd,
output uart_txd
);
parameter CLK_FREQ = 100_000_000; //系统时钟频率
parameter UART_BPS = 115200 ; //串口波特率
wire [7:0] uart_rx_data;
wire uart_rx_done;
wire uart_tx_busy;
uart_rx #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
u_uart_rx(
.clk (sys_clk),
.rst_n (sys_rst_n),
.uart_rxd (uart_rxd),
.uart_rx_done (uart_rx_done),
.uart_rx_data (uart_rx_data)
);
uart_tx #(
.CLK_FREQ (CLK_FREQ),
.UART_BPS (UART_BPS)
)
u_uart_tx(
.clk (sys_clk),
.rst_n (sys_rst_n),
.uart_tx_en (uart_rx_done),
.uart_tx_data (uart_rx_data),
.uart_tx_busy (uart_tx_busy),
.uart_txd (uart_txd)
);
endmodule
注:
- 在顶层模块,将波特率和系统时钟调整为所需数值。
- 如需添加奇偶校验码,添加奇偶校验代码,更改rx_flag截止条件,更改rx_cnt的最大值,将rx_data_reg和rx_data更改为合适的位数。
参考:
串口通信协议---UART_uart串口通信_LYY我的卢的博客-CSDN博客
正点原子UART讲解
野火UART讲解
(如有不恰当之处,敬请指教,谢谢。)