UART协议
目录
- 一、概述
- 二、帧格式
- 起始位
- 数据位
- 奇偶校验位
- 停止位
- 三、数据传输过程
- 四、串行通信接口
- RS232
- RS422
- RS485
- 五、UART环回程序设计
参考:正点原子FPGA开发指南
一、概述
UART(通用异步收发器)是一种异步、全双工的串行通信总线,在发送数据时将并行数据转换成串行数据来传输,在接收数据时将接收到的串行数据转换成并行数据。
UART以1个字符为传输单位,两字符之间间隔不固定,但同一字符两个相邻位之间间隔固定。
UART通信主要有TX、RX、GND3根线,收发端TX、RX交叉相连。
二、帧格式
UART传输的1帧数据由起始位、数据位、奇偶校验位、停止位组成。
UART通信的速率用波特率表示,波特率单位是bps,表示每秒传输的二进制位数。
起始位
UART传输线空闲状态时保持高电平,若要开始传输,需将传输线拉低并维持1个时钟周期。
当接收端检测到传输线的下降沿(起始位)时,开始以波特率对应的速率读取数据帧中各位。
数据位
数据位可选择5~8位,一般常用8位数据位,优先发送数据的低位。
奇偶校验位
检验传输数据中1的个数总和是奇数/偶数来判断数据传输是否出错,可选择奇校验、偶校验、无校验。
奇(偶)校验时,发送方应使数据位中 1 的个数与校验位中 1 的个数之和为奇(偶)数。接收放对接收数据中1的个数进行检查,若不为奇(偶)数,则说明在传输过程中出错。
停止位
UART将数据传输线拉高,并维持1~2个时钟周期。
三、数据传输过程
- UART发送模块从数据总线并行接收数据
2.UART发送模块将起始位、奇偶校验位、停止位添加到数据帧。
3.以串行方式将整个数据包传输到UART接收模块,UART接收模块以预配置的波特率对数据进行采样。
4.UART接收模块解包获取数据。
5.UART接收模块将串行数据转化为并行数据,并传输到接收端数据总线上。
四、串行通信接口
RS232
RS232采用负逻辑电平、单端传输方式,可实现全双工通信,常用接口类型是DB9,一般只用到TXD、RXD、GND。
RS232抗干扰能力差、通信距离短、数据传输速率低,而且仅支持一对一通信,无法实现多个设备互联。
RS422
RS422采用差分传输方式,采用4根线实现全双工通信,2根用于发送、2根用于接收。
RS422最大传输速率可达10Mbps,允许在1条总线上连接10个接收器,实现了单个设备发送、多个设备接收的功能。
RS485
RS485同样采用差分传输,但RS485只有两根信号线,发送和接收共用,只能实现半双工通信,允许多个发送器连接到同一条总线上,各设备通过使能信号控制发送和接收过程。
五、UART环回程序设计
//UART发送模块
module uart_send(input sys_clk, //系统时钟input sys_rst_n, //系统复位,低电平有效input uart_en, //发送使能信号input [7:0] uart_din, //待发送数据output uart_tx_busy, //发送忙状态标志 output reg uart_txd //UART发送端口);//parameter define
parameter CLK_FREQ = 50000000; //系统时钟频率
parameter UART_BPS = 9600; //串口波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次//reg define
reg uart_en_d0;
reg uart_en_d1;
reg [15:0] clk_cnt; //系统时钟计数器
reg [ 3:0] tx_cnt; //发送数据计数器
reg tx_flag; //发送过程标志信号
reg [ 7:0] tx_data; //寄存发送数据//wire define
wire en_flag;//*****************************************************
//** main code
//*****************************************************
//在串口发送过程中给出忙状态标志
assign uart_tx_busy = tx_flag;//捕获uart_en上升沿,得到一个时钟周期的脉冲信号
assign en_flag = (~uart_en_d1) & uart_en_d0;//对发送使能信号uart_en延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) beginuart_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//当脉冲信号en_flag到达时,寄存待发送的数据,并进入发送过程
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin tx_flag <= 1'b0;tx_data <= 8'd0;end else if (en_flag) begin //检测到发送使能上升沿 tx_flag <= 1'b1; //进入发送过程,标志位tx_flag拉高tx_data <= uart_din; //寄存待发送的数据end//计数到停止位结束时,停止发送过程else if ((tx_cnt == 4'd9) && (clk_cnt == BPS_CNT -(BPS_CNT/16))) begin tx_flag <= 1'b0; //发送过程结束,标志位tx_flag拉低tx_data <= 8'd0;endelse begintx_flag <= tx_flag;tx_data <= tx_data;end
end//进入发送过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) clk_cnt <= 16'd0; else if (tx_flag) begin //处于发送过程if (clk_cnt < BPS_CNT - 1)clk_cnt <= clk_cnt + 1'b1;elseclk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零endelse clk_cnt <= 16'd0; //发送过程结束
end//进入发送过程后,启动发送数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) tx_cnt <= 4'd0;else if (tx_flag) begin //处于发送过程if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期tx_cnt <= tx_cnt + 1'b1; //此时发送数据计数器加1elsetx_cnt <= tx_cnt; endelse tx_cnt <= 4'd0; //发送过程结束
end//根据发送数据计数器来给uart发送端口赋值
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) uart_txd <= 1'b1; else if (tx_flag)case(tx_cnt)4'd0: uart_txd <= 1'b0; //起始位 4'd1: uart_txd <= tx_data[0]; //数据位最低位4'd2: uart_txd <= tx_data[1];4'd3: uart_txd <= tx_data[2];4'd4: uart_txd <= tx_data[3];4'd5: uart_txd <= tx_data[4];4'd6: uart_txd <= tx_data[5];4'd7: uart_txd <= tx_data[6];4'd8: uart_txd <= tx_data[7]; //数据位最高位4'd9: uart_txd <= 1'b1; //停止位default: ;endcaseelse uart_txd <= 1'b1; //空闲时发送端口为高电平
endendmodule
//UART接收模块
module uart_recv(input sys_clk, //系统时钟input sys_rst_n, //系统复位,低电平有效input uart_rxd, //UART接收端口output reg uart_done, //接收一帧数据完成标志output reg [7:0] uart_data //接收的数据);//parameter define
parameter CLK_FREQ = 50000000; //系统时钟频率
parameter UART_BPS = 9600; //串口波特率
localparam BPS_CNT = CLK_FREQ/UART_BPS; //为得到指定波特率,//需要对系统时钟计数BPS_CNT次
//reg define
reg uart_rxd_d0;
reg uart_rxd_d1;
reg [15:0] clk_cnt; //系统时钟计数器
reg [ 3:0] rx_cnt; //接收数据计数器
reg rx_flag; //接收过程标志信号
reg [ 7:0] rxdata; //接收数据寄存器//wire define
wire start_flag;//*****************************************************
//** main code
//*****************************************************
//捕获接收端口下降沿(起始位),得到一个时钟周期的脉冲信号
assign start_flag = uart_rxd_d1 & (~uart_rxd_d0); //对UART接收端口的数据延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begin uart_rxd_d0 <= 1'b0;uart_rxd_d1 <= 1'b0; endelse beginuart_rxd_d0 <= uart_rxd; uart_rxd_d1 <= uart_rxd_d0;end
end//当脉冲信号start_flag到达时,进入接收过程
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) rx_flag <= 1'b0;else beginif(start_flag) //检测到起始位rx_flag <= 1'b1; //进入接收过程,标志位rx_flag拉高//计数到停止位中间时,停止接收过程else if((rx_cnt == 4'd9) && (clk_cnt == BPS_CNT/2))rx_flag <= 1'b0; //接收过程结束,标志位rx_flag拉低elserx_flag <= rx_flag;end
end//进入接收过程后,启动系统时钟计数器
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) clk_cnt <= 16'd0; else if ( rx_flag ) begin //处于接收过程if (clk_cnt < BPS_CNT - 1)clk_cnt <= clk_cnt + 1'b1;elseclk_cnt <= 16'd0; //对系统时钟计数达一个波特率周期后清零endelse clk_cnt <= 16'd0; //接收过程结束,计数器清零
end//进入接收过程后,启动接收数据计数器
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) rx_cnt <= 4'd0;else if ( rx_flag ) begin //处于接收过程if (clk_cnt == BPS_CNT - 1) //对系统时钟计数达一个波特率周期rx_cnt <= rx_cnt + 1'b1; //此时接收数据计数器加1elserx_cnt <= rx_cnt; endelserx_cnt <= 4'd0; //接收过程结束,计数器清零
end//根据接收数据计数器来寄存uart接收端口数据
always @(posedge sys_clk or negedge sys_rst_n) begin if ( !sys_rst_n) rxdata <= 8'd0; else if(rx_flag) //系统处于接收过程if (clk_cnt == BPS_CNT/2) begin //判断系统时钟计数器计数到数据位中间case ( rx_cnt )4'd1 : rxdata[0] <= uart_rxd_d1; //寄存数据位最低位4'd2 : rxdata[1] <= uart_rxd_d1;4'd3 : rxdata[2] <= uart_rxd_d1;4'd4 : rxdata[3] <= uart_rxd_d1;4'd5 : rxdata[4] <= uart_rxd_d1;4'd6 : rxdata[5] <= uart_rxd_d1;4'd7 : rxdata[6] <= uart_rxd_d1;4'd8 : rxdata[7] <= uart_rxd_d1; //寄存数据位最高位default:; endcaseendelse rxdata <= rxdata;elserxdata <= 8'd0;
end//数据接收完毕后给出标志信号并寄存输出接收到的数据
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) beginuart_data <= 8'd0; uart_done <= 1'b0;endelse if(rx_cnt == 4'd9) begin //接收数据计数器计数到停止位时 uart_data <= rxdata; //寄存输出接收到的数据uart_done <= 1'b1; //并将接收完成标志位拉高endelse beginuart_data <= 8'd0; uart_done <= 1'b0; end
endendmodule
//UART环回模块
module uart_loop(input sys_clk, //系统时钟input sys_rst_n, //系统复位,低电平有效input recv_done, //接收一帧数据完成标志input [7:0] recv_data, //接收的数据input tx_busy, //发送忙状态标志 output reg send_en, //发送使能信号output reg [7:0] send_data //待发送数据);//reg define
reg recv_done_d0;
reg recv_done_d1;
reg tx_ready;//wire define
wire recv_done_flag;//*****************************************************
//** main code
//*****************************************************//捕获recv_done上升沿,得到一个时钟周期的脉冲信号
assign recv_done_flag = (~recv_done_d1) & recv_done_d0;//对发送使能信号recv_done延迟两个时钟周期
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) beginrecv_done_d0 <= 1'b0; recv_done_d1 <= 1'b0;end else begin recv_done_d0 <= recv_done; recv_done_d1 <= recv_done_d0; end
end//判断接收完成信号,并在串口发送模块空闲时给出发送使能信号
always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begintx_ready <= 1'b0; send_en <= 1'b0;send_data <= 8'd0;end else begin if(recv_done_flag)begin //检测串口接收到数据tx_ready <= 1'b1; //准备启动发送过程send_en <= 1'b0;send_data <= recv_data; //寄存串口接收的数据endelse if(tx_ready && (~tx_busy)) begin //检测串口发送模块空闲tx_ready <= 1'b0; //准备过程结束send_en <= 1'b1; //拉高发送使能信号endend
endendmodule