UART串行通信协议深度解析:通用异步收发传输器的原理与应用

一、UART 简介

  UART广泛应用于微控制器和计算机之间的数据通信,如GPS模块、蓝牙模块、GSM模块等。UART是一种通用串行数据总线,用于异步通信,该总线双向通信,可以实现全双工传输和接收。 在嵌入式设计中,UART用于主机与辅助设备通信UART通常被集成于其他通讯接口的连结上。UART 通道有两条数据线:每个设备上都有一个 RX 引脚和一个 TX 引脚(RX 用于接收,TX 用于发送)每个设备的 RX 引脚都连接到另一个设备的 TX 引脚。
  UART 是通用异步接收方发送方,没有时钟线。 异步通信以一个字符为传输单位:通信中两个字符之间的时间间隔不固定,但同一字符中两个相邻位之间的时间间隔是固定的。此时,需要在两个UART设备上指定相同的传输速率,以及空闲位、起始位、奇偶校验位和结束位,即遵循相同的协议。

二、UART 数据结构

  在UART中,传输模式为数据包形式。连接发送器和接收器的机制包括串行数据包的创建和物理硬件线路的控制。数据包由起始位、数据帧、奇偶校验位和停止位组成。

  • 起始位:由1个逻辑 0 的数据位表示
  • 有效数据:在起始位后紧接着的就是有效数据,有效数据的长度常被约定为 5、 6、 7 或 8 位长
  • 结束位:由 0.5、1、1.5 或 2 个逻辑 1 的数据位表示
  • 校验位:可选,采用奇偶校验方法
  • 波特率:常见的波特率为4800、 9600、 115200,具体实现要结合系统时钟进行计算

    UART传输电气标准
      UART使用电子电路中常使用 TTL 的电平标准,理想状态下使用 5V 表示二进制逻辑 1,使用 0V 表示逻辑 0。
      为了扩展传输距离和应用范围,使用RS232标准,RS232接口通常采用DB9或DB25的形式,以DB9最常见。RS232采用负逻辑,为了增加串口通讯的远距离传输及抗干扰能力,它使用 -3 ~ -15V 表示逻辑 1+3 ~ +15V 表示逻辑 0,传输距离最高可达15m。
  • 2.1 起始位

      当不传输数据时,UART数据传输线通常保持高电压电平。若要开始数据传输,发送UART会将传输线从高电平拉到低电平并保持1个时钟周期。当接收UART检测到高到低电压跃迁时,便开始以波特率对应的频率读取数据帧中的位。

    2.2 数据帧

      数据帧包含所传输的实际数据。如果使用奇偶校验位,数据帧长度可以是5位到8位。如果不使用奇偶校验位,数据帧长度可以是9位。在大多数情况下,数据以最低有效位优先方式发送。

    2.3 奇偶校验

      奇偶性描述数字是偶数还是奇数。通过奇偶校验位,接收UART判断传输期间是否有数据发生改变。电磁辐射、不一致的波特率或长距离数据传输都可能改变数据位。
      接收UART读取数据帧后,将计数值为1的位,检查总数是偶数还是奇数。如果奇偶校验位为0(偶数奇偶校验),则数据帧中的1或逻辑高位总计应为偶数。如果奇偶校验位为1(奇数奇偶校验),则数据帧中的1或逻辑高位总计应为奇数。当奇偶校验位与数据匹配时,UART认为传输未出错。但是,如果奇偶校验位为0,而总和为奇数,或者奇偶校验位为1,而总和为偶数,则UART认为数据帧中的位已改变。

    2.4 停止位

      为了表示数据包结束,发送UART将数据传输线从低电压驱动到高电压并保持1到2位时间。

    三、UART 串口通信FPGA实现

    3.1 UART 接收代码

    module	UART_SRX(Clk,Rst,Rxd,RxRdy,RxErr,Do);
    
    input		Clk,
    input		Rst;
    input		Rxd;
    
    output[7:0]Do;
    output		RxRdy,RxErr;
    
    reg	[13:0]	count;
    reg [2:0]	state;
    reg	[7:0]	Do;
    reg [23:0]	data_sam;
    reg	[1:0]	RX_BIT_COUNT; 
    reg	[2:0]	RX_START_COUNT;
    reg [5:0]	RX_REC_COUNT;
    reg			Clk_SAM;
    reg			RxRdy;
    reg			RxErr;
    reg			Rxd_pr;
    
    parameter	RX_IDLE		= 3'b000, //状态机实现
    			RX_START	= 3'b001,
    			RX_REC		= 3'b010,
    			RX_STOP		= 3'b011,
    			RX_DO		= 3'b100;
    
    //parameter define
    parameter CLK_FREQ = 24576000;					//系统时钟频率
    parameter UART_BPS = 300;						//串口波特率
    localparam BoundCnt = CLK_FREQ/UART_BPS/6-1;	//Fo = Bound*3 = 0.5*Fs/BoundCnt
    
    always@(posedge Clk or posedge Rst)begin
    	if(Rst)begin
    		count <= 14'h0;
    	end
    	else begin
    		if(count == BoundCnt)begin //波特率计数时钟
    			count <= 14'h0;
    			Clk_SAM <= ~Clk_SAM;
    		end
    		else begin
    			count <= count + 14'h1;
    		end
    	end
    end
    
    always @(posedge Clk_SAM or posedge Rst) begin
    	if(Rst)	begin
    		Do <= 8'hff;
    		RxRdy <= 1'b0;
    		RxErr <= 1'b0;
    		RX_START_COUNT <= 2'h0;
    		RX_REC_COUNT <= 5'h0;
    		data_sam[23:0]<= 24'b0;
    		state <= RX_IDLE;
    	end
    	else begin
    		case(state)
    			RX_IDLE:begin //接收初始化
    				RxRdy <= 1'b0;
    				RxErr <= 1'b0;
    				RX_START_COUNT <= 2'h0;
    				RX_REC_COUNT <= 5'h0;
    				if(Rxd == 0)begin
    					state <= RX_START;
    				end
    			end
    			RX_START:begin //接收起始位
    				Rxd_pr <= Rxd;
    				if(!(Rxd && Rxd_pr))begin
    					RX_START_COUNT <= RX_START_COUNT + 2'h1;
    					if(RX_START_COUNT == 1)begin //校验是否开启
    						RX_START_COUNT <= 2'h0;
    						state <= RX_REC;
    					end
    					else begin
    						state <= RX_START;
    					end
    				end
    				else begin
    					state <= RX_IDLE;
    				end
    			end
    			RX_REC:begin //校验起始
    				data_sam[RX_REC_COUNT] <= Rxd;
    				RX_REC_COUNT <= RX_REC_COUNT + 5'h1;
    				if(RX_REC_COUNT == 23) begin
    					state <= RX_STOP;
    				end
    			end
    			RX_STOP:begin
    				Do[0] <= ((data_sam[0]+data_sam[1]+data_sam[2])>1)?1'b1:1'b0;
    				Do[1] <= ((data_sam[3]+data_sam[4]+data_sam[5])>1)?1'b1:1'b0;
    				Do[2] <= ((data_sam[6]+data_sam[7]+data_sam[8])>1)?1'b1:1'b0;
    				Do[3] <= ((data_sam[9]+data_sam[10]+data_sam[11])>1)?1'b1:1'b0;
    				Do[4] <= ((data_sam[12]+data_sam[13]+data_sam[14])>1)?1'b1:1'b0;
    				Do[5] <= ((data_sam[15]+data_sam[16]+data_sam[17])>1)?1'b1:1'b0;
    				Do[6] <= ((data_sam[18]+data_sam[19]+data_sam[20])>1)?1'b1:1'b0;
    				Do[7] <= ((data_sam[21]+data_sam[22]+data_sam[23])>1)?1'b1:1'b0;
    				state <= RX_DO;
    				RxErr <= !Rxd;
    			end
    			RX_DO:begin
    				RxRdy <= 1'b1;
    				state <= RX_IDLE;
    			end
    		endcase
    	end
    end
    endmodule
    

    3.2 UART 发送代码

    module UART_TX(
        input               CLK    , //系统时钟
        input               RST_n  , //系统复位,低有效
        input               TX_EN  , //UART的发送使能
        input     [7:0]     TX_DATA, //UART要发送的数据
        output  reg         TXD    , //UART发送端口
        output  reg         TX_BUSY  //发送忙状态信号
        );
    
    //parameter define
    parameter CLK_FREQ = 50000000;               //系统时钟频率
    parameter UART_BPS = 115200  ;               //串口波特率
    localparam BAUD_CNT_MAX = CLK_FREQ/UART_BPS; //为得到指定波特率,对系统时钟计数BPS_CNT次
    
    //reg define
    reg  [7:0]  tx_data_t;  //发送数据寄存器
    reg  [3:0]  tx_cnt   ;  //发送数据计数器
    reg  [15:0] baud_cnt ;  //波特率计数器
    
    //当TX_EN为高时,寄存输入的并行数据,并拉高BUSY信号
    always @(posedge CLK or negedge RST_n) begin
        if(!RST_n) begin
            tx_data_t <= 8'b0;
            TX_BUSY <= 1'b0;
        end
        //发送使能时,寄存要发送的数据,并拉高BUSY信号
        else if(TX_EN) begin
            tx_data_t <= TX_DATA;
            TX_BUSY <= 1'b1;
        end
        //当计数到停止位结束时,停止发送过程
        else if(tx_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - BAUD_CNT_MAX/16) begin
            tx_data_t <= 8'b0;     //清空发送数据寄存器
            TX_BUSY <= 1'b0;  	   //并拉低BUSY信号
        end
        else begin
            tx_data_t <= tx_data_t;
            TX_BUSY <= TX_BUSY;
        end
    end
    
    //波特率的计数器赋值
    always @(posedge CLK or negedge RST_n) begin
        if(!RST_n) 
            baud_cnt <= 16'd0;
        //当处于发送过程时,波特率计数器(baud_cnt)进行循环计数
        else if(TX_BUSY) begin
            if(baud_cnt < BAUD_CNT_MAX - 1'b1)
                baud_cnt <= baud_cnt + 16'b1;
            else 
                baud_cnt <= 16'd0; //计数达到一个波特率周期后清零
        end    
        else
            baud_cnt <= 16'd0;     //发送过程结束时计数器清零
    end
    
    //tx_cnt进行赋值
    always @(posedge CLK or negedge RST_n) begin
        if(!RST_n) 
            tx_cnt <= 4'd0;
        else if(TX_BUSY) begin             //处于发送过程时tx_cnt才进行计数
            if(baud_cnt == BAUD_CNT_MAX - 1'b1) //当波特率计数器计数到一个波特率周期时
                tx_cnt <= tx_cnt + 1'b1;        //发送数据计数器加1
            else
                tx_cnt <= tx_cnt;
        end
        else
            tx_cnt <= 4'd0;                     //发送过程结束时计数器清零
    end
    
    //根据tx_cnt来给uart发送端口赋值
    always @(posedge CLK or negedge RST_n) begin
        if(!RST_n) 
            TXD <= 1'b1;
        else if(TX_BUSY) begin
            case(tx_cnt) 
                4'd0 : TXD <= 1'b0        ; //起始位
                4'd1 : TXD <= tx_data_t[0]; //数据位最低位
                4'd2 : TXD <= tx_data_t[1];
                4'd3 : TXD <= tx_data_t[2];
                4'd4 : TXD <= tx_data_t[3];
                4'd5 : TXD <= tx_data_t[4];
                4'd6 : TXD <= tx_data_t[5];
                4'd7 : TXD <= tx_data_t[6];
                4'd8 : TXD <= tx_data_t[7]; //数据位最高位
                4'd9 : TXD <= 1'b1        ; //停止位
                default : TXD <= 1'b1;
            endcase
        end
        else
            TXD <= 1'b1;                    //空闲时发送端口为高电平
    end
    endmodule
    

    四、USART – 扩展UART

    USART 通用同步异步收发器(Universal Synchronous Asynchronous Receiver and Transmitter)
    STM32内部的USART控制器学习

      USART是一种扩展了UART功能的通信接口,它不仅支持异步通信(如UART),还支持同步通信模式。USART可以在同一硬件上实现两种不同的通信方式,提供了更大的灵活性,广泛应用于需要稳定和高速数据传输的场合。

    1. USART的主要功能
      异步模式: 与UART功能相同,用于无需时钟信号的点对点通信。
      同步模式: 需要外部时钟信号的同步通信方式,适用于高数据速率和需要精确时序控制的场合。
    2. USART与UART的区别
      同步模式: USART可以工作在同步模式,而UART只能工作在异步模式。
      时钟信号: 在同步模式下,USART需要一个时钟信号进行数据同步,而UART则不需要。
      灵活性: USART提供了更多的配置选项和更高的灵活性,适用于更多种类的通信需求。

    – 异步模式通信过程
    起始位:发送端发送一个低电平信号,通知接收端即将传输数据。
    数据位:发送端按预定的波特率逐位发送数据,接收端根据相同波特率接收数据。
    校验位:可选,用于检测数据传输中的错误。
    停止位:发送端发送一个高电平信号,表示数据传输结束。
    – 同步模式通信过程
    时钟信号:发送端发送时钟信号,接收端根据时钟信号的边缘(上升沿或下降沿)同步接收数据。
    数据位:每个数据位在时钟信号的一个边缘发送或接收。
    帧格式:可以根据应用需求配置起始位、数据位、校验位和停止位。

    作者:伊丽莎白鹅

    物联沃分享整理
    物联沃-IOTWORD物联网 » UART串行通信协议深度解析:通用异步收发传输器的原理与应用

    发表回复