通用异步接收器/发送器 (UART) 是一种硬件功能,可使用广泛采用的异步串行通信接口(例如 RS232、RS422 和 RS485)处理通信(即定时要求和数据帧)。 UART提供了一种广泛采用且廉价的方法来实现不同设备之间的全双工或半双工数据交换。

基础知识

每个 UART 控制器均可独立配置参数,如波特率、数据位长度、位顺序、停止位数量、奇偶校验位等。所有常规 UART 控制器均与各个制造商的支持 UART 的设备兼容 。

在UART通信中,两个UART直接相互通信。 发送UART将来自CPU等控制设备的并行数据转换为串行形式,将其串行发送到接收UART,然后接收UART将串行数据转换回并行数据以供接收设备使用。 只需两根线即可在两个 UART 之间传输数据。 数据从发送 UART 的 Tx 引脚流向接收 UART 的 Rx 引脚:

UART

UART 异步传输数据,这意味着没有时钟信号来同步发送 UART 的位输出和接收 UART 的位采样。 发送 UART 向正在传输的数据包添加起始位和停止位,而不是时钟信号。 这些位定义数据包的开始和结束,以便接收 UART 知道何时开始读取这些位。

当接收 UART 检测到起始位时,它开始以称为波特率的特定频率读取传入位。 波特率是数据传输速度的度量,以每秒位数 (bps) 表示。 两个 UART 必须以大致相同的波特率运行。 在位时序相差太远之前,发送和接收 UART 之间的波特率只能相差约 10%。

两个 UART 还必须配置为发送和接收相同的数据包结构。

工作原理

Introduction-to-UART-Data-Transmission-Diagram

将要发送数据的UART从数据总线接收数据。 数据总线用于由 CPU、内存或微控制器等其他设备将数据发送到 UART。 数据以并行形式从数据总线传输到发送UART。 发送UART从数据总线获取并行数据后,添加起始位、奇偶校验位和停止位,创建数据包。 接下来,数据包在 Tx 引脚上逐位串行输出。 接收 UART 在其 Rx 引脚上逐位读取数据包。 然后,接收 UART 将数据转换回并行形式,并删除起始位、奇偶校验位和停止位。 最后,接收UART将数据包并行传输到接收端的数据总线:

框图

UART 框图由两个组件组成,即发送器和接收器,如下所示。 发送器部分包括三个块,即发送保持寄存器、移位寄存器和控制逻辑。 同样,接收器部分包括接收保持寄存器、移位寄存器和控制逻辑。 这两个部分通常由波特率发生器提供。 该发生器用于在发送器部分和接收器部分必须发送或接收数据时生成速度。

发送器中的保持寄存器包含要发送的数据字节。 发送器和接收器中的移位寄存器将位向右或向左移动,直到发送或接收一个字节的数据。 读(或)写控制逻辑用于告知何时读或写。

发送器和接收器之间的波特率发生器生成范围为 110 bps 至 230400 bps 的速度。 通常,微控制器的波特率为 9600 至 115200。

UART-Block-Diagram

帧结构

UART 传输的数据被组织成数据包。 每个数据包包含 1 个起始位、5 至 9 个数据位(取决于 UART)、一个可选奇偶校验位以及 1 或 2 个停止位:

UART-Communication

空闲状态:高电平,表示当前线路上无数据传送

起始位:UART 数据传输线在不传输数据时通常保持在高电压电平。 为了开始数据传输,发送 UART 将传输线从高电平拉至低电平,持续一个时钟周期。 当接收UART检测到高电压到低电压的转变时,它开始以波特率的频率读取数据帧中的位。

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

奇偶校验位: 奇偶校验描述了数字的偶数或奇数。 奇偶校验位是接收 UART 判断数据在传输过程中是否发生变化的一种方式。 位可能因电磁辐射、不匹配的波特率或长距离数据传输而改变。 接收UART读取数据帧后,统计值为1的位数,并检查总数是偶数还是奇数。 如果奇偶校验位为 0(偶校验),则数据帧中的 1 位总数应为偶数。 如果奇偶校验位为 1(奇奇偶校验),则数据帧中的 1 位总计应为奇数。 当奇偶校验位与数据匹配时,UART 就知道传输没有错误。 但如果奇偶校验位为0,且总数为奇数; 或者奇偶校验位为 1,并且总数为偶数,UART 知道数据帧中的位已更改。

停止位:停止位位于数据包的末尾。 通常,该位为 2 位长,但经常仅使用一位。 为了停止广播,UART 保持数据线处于高电压。

波特率

线路中信号调制的频率,单位是bps或b/s(位每秒)。一个固定频率的时钟信号不断振荡,每一个时钟周期,发送一位数据信号。

band_rate1

UART通信双方要求具有相同的波特率。但是,由于UART是异步通信,即没有一根时钟线连接通信双方,各自按照自己内部的时钟调制出一个理论上相等的波特率,但是由于硬件本身不可避免的误差,实际上的波特率往往不可能严格等于理论值,但是要求双方波特率的误差不能超过10%,否则会导致接收方读取到的是乱码数据。

硬件流控

流控,即流量控制。

任何通信协议的双方,都会分配有存储空间有限的缓冲区,用来接收对方发送的数据。一旦对方发送数据过快,而己方处理速度较慢,就可能出现缓冲区满无法处理、甚至丢数据的严重情况。

此时流量控制则显得尤为重要,接收方无法接收更多数据时,通知发送方暂停数据发送,当可以接收数据后,再通知发送发继续发送数据。

uart_control

硬件流控引脚的UART接口连接图,相对于前面,多了RTS和CTS两个引脚,此二者UART硬件接口的常规功能引脚。

RTS:输出功能,连接对方的CTS,当己方RTS拉高时,则通知对方UART暂停发送数据,当RTS恢复低电平时,通知对方继续发送数据 。

CTS:输入功能,连接对方的RTS,当己方CTS检测到高电平时,则暂停发送数据,当己方CTS检测到低电平时,则继续发送数据。

FIFO

FIFO(First-In, First-Out)是一种基本的数据结构,其核心思想是:先进入的数据先出去。FIFO可以在硬件和软件中实现,且有同步和异步之分(本节仅介绍同步FIFO)。

Data_Queue.svg

软件FIFO:
  • 定义:在软件中使用编程数据结构(如数组、链表)实现的FIFO。
  • 应用:常见于操作系统中的任务调度、网络中的数据包处理或任何需要队列的场合。
  • 操作:主要操作包括入队(添加到队列尾部)和出队(从队列头部移除)。
  • 优点:灵活,可以轻松地调整大小或实现优先级排队等高级功能。
  • 缺点:由于它是在软件中实现的,因此可能不如硬件FIFO那样快。

实现:

软件FIFO最熟悉的就是环形buffer→ ringbuffer。

环形buffer相比与线性buffer,我们不用频繁的分配内存,内存反复使用也使得我们能用更少的内存块做更多的事,并且对内存的管理更加方便更加安全。一般应用在我们频繁的对数据buffer进行读写的时候。

ringbuff

环形缓冲区并不是指物理意义上的一个首尾相连成“环”的缓冲区,而是逻辑意义上的一个环,因为内存空间是线性结构,所以实际上环形缓冲区仍是一段有长度的内存空间,是一个先进先出功能的缓冲区,具备实现通信进程对该缓冲区的互斥访问功能。

实现原理:

环形缓冲区的长度是固定的,在使用该缓冲区时,不需要将所有的数据清除,只需要调整指向该缓冲区的head、write pointer和tail指针位置即可。write pointer指针最先指向head指针位置(环形缓冲区开头位置),数据从write pointer指针处开始存储,每存储一个数据,write pointer指针位置向后移动一个长度 ,随着数据的添加,write pointer指针随移动数据长度大小个位置。当write pointer指向tail尾部指针,write pointer重新指向head指针位置(折行处理),并且覆盖原先位置数据内容直到数据存储完毕。环形缓冲区的好处是可以减少内存分配继而减少系统的开销,减少内存碎片数量,有利于程序长期稳定的运行。

UART-Communication

一般构建一个环形缓冲区需要一段连续的内存空间以及4个指针:
head指针:指向内存空间中的首地址;
tail指针:指向内存空间的尾地址;
read pointer:指向内存空间存储数据的起始位置(读指针);
write pointer:指向内存空间存储数据的结尾位置(写指针)。

当申请完内存以及指针定义完毕后,环形缓冲区说明及使用如下:

  • 1.该段内存空间的长度是Len = tail-head;
  • 2.read pointer是读数据的起始位置,当读取完N数据之后要移动N个单位长度的偏移,当有addlen长度的数据要存入到环形缓冲区,若addlen + write pointer > tail时,write pointer将存入len1 = tail - write pointer个数据长度,然后write pointer回到head位置,将剩下的len2 = addlen - len1个数据从head开始存储并覆盖到原来的数据内容。
  • 3.write pointer是写数据的起始位置,当存入N个数据之后要移动N个单位长度的偏移,read pointer是读数据的起始位置,当读取N个数据之后要移动N个单位长度的偏移。当要addlen长度的数据要从环形缓冲区读取,若addlen + read pointer > tail时,read pointer 将读取len1 = tail - read pointer 个数据长度,然后read pointer 回到head位置,将剩下的len2 = addlen - len1个数据从head开始读取完毕。
硬件FIFO
  • 定义:在硬件中,特别是在数字电路中实现的FIFO。
  • 应用:常见于通信接口(如UART、SPI等)中,用于数据缓冲。
  • 实现:通常使用寄存器阵列或双口RAM实现,具有读指针和写指针。
  • 优点:速度快,可以与其他硬件模块并行工作,提供高效的数据流。
  • 缺点:大小固定,不如软件FIFO灵活。

目前QuecPython系列模组均使用硬件FIFO,用于模块间的数据缓冲、跨异步传输数据等。 是由芯片设计者使用硬件电路实现的数据缓存。

同步FIFO
  • 定义:同步FIFO的读和写操作都是在相同的时钟下进行的。
  • 工作原理:使用一个共同的时钟信号来控制数据的读写。当有新的数据可写入时,写指针移动;当有数据可读时,读指针移动。
  • 应用:常见于同一时钟域中的数据流缓冲。

synchronous-fifo

写入操作: FIFO可以根据w_en信号在时钟的每个位置存储/写入wr_data,直到数据满。 写入指针在FIFO存储器中的每个数据写入时都会递增。

读取操作: 根据rd_en信号在时钟的每个位置从FIFO取出或读取数据,直到数据为空。从FIFO存储器读取的每个数据时,读取指针都会递增。

FIFO内部通过对写请求、读请求计数产生读、写指针,读写指针即为memory的读、写地址。写指针指向下一个要写入的地址,读指针指向下一个要读取的地址,写请求使写指针递增,读请求使读指针递增。

FIFO模块输出empty和full信号指示其状态,fifo_full表示FIFO内空间已满不能再写入数据,fifo_empty表示FIFO内没有可供读取的下一个有效数据。

同步FIFO空满信号的产生:

复位时FIFO读、写指针都归零。此时fifo_empty拉高,只能写不能读,一旦有数据写入,会将fifo_empty拉低,允许读取数据。当fifo的写指针指向fifo_depth-1时,此时进行一个写操作会使写指针归零(此时无读操作),拉高fifo_full。

在读写指针相等时,FIFO要么空要么满,所以需要对这两种情况进行区分。

  1. FIFO满信号产生

FIFO满状态是由写操作触发的:“在写操作使读、写指针在下个时钟保持相等时,FIFO满”,更为通俗的解释是“写操作让写指针追上了读指针,即写指针套了读指针一圈(跑步的角度)

  1. FIFO空信号产生

FIFO空状态是由读操作触发的:“在读操作使读、写指针在下个时钟保持相等时,FIFO空”,更为通俗的解释是“读操作让读指针追上了写指针

异步FIFO
  • 定义:异步FIFO的读和写操作在不同的时钟域中进行。
  • 工作原理:使用两个独立的时钟,一个用于写操作,另一个用于读操作。这需要特殊的设计,以确保跨时钟域的数据完整性和同步。
  • 应用:用于两个有不同操作频率或来自不同源的时钟域之间的数据传输。

DMA

DMA(Direct Memory Access)是一种技术,允许外部设备(如硬盘、音频接口、网络适配器等)直接与系统内存进行数据交换,而无需中央处理单元(CPU)的干预。这种机制可以显著提高数据传输效率,因为它允许设备在不占用CPU时间的情况下进行数据传输。

特点:

DMA和cpu之前并不是一个并行关系,因为主存只有一个,cpu和DMA无法同时访问主存,只能通过交替访问的方式,访问主存,DMA之所以效率高速度快是因为省去现场保护和现场恢复

DMA传输本身并不会中断程序,但它会占用系统资源:比如IO或RAM。这样一旦CPU需要访问相同的IO或RAM时,就需要长时间等待,直到DMA传输完毕、释放资源。从软件角度来看,这和中断程序非常相似,但其内核截然不同:因为CPU一直在工作,从未有过任务切换,只是偶尔暂停,所以无需现场保护。

此外,如果CPU拥有一定容量的cache,而DMA传输的颗粒度又恰到好处,那么即使访问同一块RAM,软件也是感觉不到程序停止的.

工作过程:

  1. 请求与授权
    • 当一个外部设备(如硬盘控制器)需要与内存交换数据时,它会发出一个DMA请求(DMA Request, 也称为 DRQ)给DMA控制器。
    • DMA控制器在收到请求后,会等待合适的时机来执行数据传输,即当系统总线处于空闲状态时。
    • 一旦准备好,DMA控制器向CPU发出一个DMA授权信号(DMA Grant, 也称为 DACK)。这通常会导致CPU在当前指令执行完毕后暂停,并释放总线控制权。
  2. 传输数据
    • DMA控制器接管总线并开始数据传输。根据设置,它可以是单个字节的传输,或一整块数据的传输。
    • DMA控制器更新源和目的地址,以及还需传输的字节计数。
    • 在数据传输过程中,CPU是不活跃的,或者说被“隔离”了,不参与数据传输过程。
  3. 传输完成
    • 一旦DMA控制器完成了所有的数据传输,它会取消DMA授权信号。
    • DMA控制器会向CPU发送一个中断信号(如果已经设置了中断),告知数据传输已完成。这样,CPU可以恢复其操作,可能是处理数据,或执行其他任务。
  4. 中断服务
    • 如果启用了DMA完成的中断,CPU在数据传输完成后会接收到一个中断请求。
    • CPU随后会调用相应的中断服务程序来处理传输后的任务,例如数据后处理、错误检查或其他相关任务。
  5. 重置与准备下一次DMA
    • DMA控制器重置其状态,并准备响应下一个DMA请求。

特点与应用

特点:

  1. 简单性: UART通信的硬件和编程复杂度相对较低。UART本身通常是在微控制器或其他处理器的硬件中实现的,所以在硬件层面上不需要额外的芯片。
  2. 无需时钟同步: 因为UART是异步的,所以发送和接收设备不需要共享一个时钟信号。这降低了硬件设计的复杂性。
  3. 灵活性: UART通信可以调整数据位长度、停止位数量和奇偶校验等参数,以适应不同的通信需求。
  4. 可靠性: 尽管UART通信没有错误修复功能,但它的奇偶校验能提供一定程度的错误检测。
  5. 限制: UART的通信速度(波特率)有一定的限制,而且通信速度越高,数据错误的可能性也越大。此外,UART的通信距离通常也有限制,对于很长的距离,可能需要使用RS-422或RS-485等差分信号标准。

应用:

  1. 嵌入式系统: UART常用于微控制器和其他低级硬件设备的通信,例如传感器、记忆卡、GPS模块等。

  2. 串行通信: UART可以用于RS-232、RS-422和RS-485接口的串行通信,用于连接如打印机、调制解调器、显示器等设备。

  3. 电脑硬件: 在早期的电脑硬件中,UART用于鼠标和键盘等外设的接口。

  4. 电信设备: 在无线通信和电信设备中,UART用于与SIM卡和其他设备通信。

  5. 蓝牙模块: 在蓝牙模块中,UART用于与主设备进行通信。

  6. IOT设备: 在物联网设备中,UART用于低速的设备间通信。

数据流

基于QuecPython EC600U模组介绍UART数据流。

发送数据的数据流:

send_data

当软件FIFO中无数据超过500ms时,结束数据传输。

TX通信:发送数据->数据发送完毕发送事件->触发中断->触发回调函数发送事件到对应的线程处理->对应线程触发用户自定义回调函数。

其触发中断类型只有一种:TX_COMPLETE。

接收数据的数据流:

recv_data

RX通信有两个中断,dma中断(数据接收到64byte),超时中断(接收数据字节数少于64而不产生dma中断的情况,时长40ms)。

其触发中断的类型有两种:RX_ARRIVED 和 RX_OVERFLOW。

当软件FIFO满时,再接收数据会造成数据溢出,出现丢数据问题。溢出的同时会上报RX_OVERFLOW事件。故串口接收数据需要及时处理,否则出现数据丢失,数据接收不全问题,当大量数据传输时,可考虑硬件流控。

RX通信:接收数据满64字节或超时->触发中断->触发回调函数发送事件到对应的线程处理->对应的线程触发用户定义回调函数。

应用示例

主要介绍如何使用 UART 具体的应用示例:

基础收发

QuecPython提供了简化的方法来在通信模组上使用 Python 进行 UART 通信。对于实时应用或需要高效处理 UART 信息的场景,使用回调函数(基于中断)进行 UART 读取是一种非常有效的方法。

实验前需了解UART QuecPython接口,请参考machine.UART

实验步骤:

  1. 初始化UART

    from machine import UART
    # 初始化 UART1
    uart1 = UART(UART.UART1, 115200, 8, 0, 1, 0)
    
  2. 写入UART

    发送数据到 UART 是非常简单的:

    uart1.write('Hello UART1')
    
  3. 使用回调数据读取数据

    在 UART 上使用回调函数,通常涉及到设置一个 IRQ(中断请求)来监听 UART 事件,如数据接收。当这些事件触发时,相关的回调函数会被执行。

    你需要确保回调函数尽可能短,以减少对其他系统任务的干扰。同时数据来之后,应立即读取,防止底层软件FIFO溢出导致数据丢失

    def uart_callback(arg):
        _queue.put(para[2])
    
    # 设置中断
    uart1.set_callback(uart_callback)
    

示例:

import _thread
import utime
from machine import UART
from queue import Queue

class Example_uart(object):
    def __init__(self, no=UART.UART2, bate=115200, data_bits=8, parity=0, stop_bits=1, flow_control=0):
        self.uart = UART(no, bate, data_bits, parity, stop_bits, flow_control)
        self._queue = Queue(5)
        _thread.start_new_thread(self.handler_thread, ())
        self.uart.set_callback(self.callback)



    def callback(self, para):
        print("call para:{}".format(para))
        if(0 == para[0]):
            self._queue.put(para[2])

    def uartWrite(self, msg):
        print("write msg:{}".format(msg))
        self.uart.write(msg)

    def uartRead(self, len):
        msg = self.uart.read(len)
        utf8_msg = msg.decode()
        print("UartRead msg: {}".format(utf8_msg))
        return utf8_msg

    def uartWrite_test(self):
        for i in range(10):
            write_msg = "Hello count={}".format(i)
            self.uartWrite(write_msg)
            utime.sleep(1)

    def handler_thread(self):
        while True:
            recv_len = self._queue.get()
            self.uartRead(recv_len)

if __name__ == "__main__":
    uart_test = Example_uart()
    uart_test.uartWrite_test() 

常见问题和故障

UART是一种非常简单和直接的通信协议,但在实际应用中也可能遇到一些问题。以下是一些常见的问题以及可能的故障排查步骤:

1. 数据接收不正确或无法接收数据

这可能是由于几种原因引起的,包括:波特率设置错误、硬件连接问题、中断处理程序问题、缓冲区溢出等。故障排查步骤可能包括:

  • 检查发送端和接收端的波特率设置是否一致。如果它们的波特率不匹配,可能会导致接收的数据出现错误。
  • 检查硬件连接,确保TX和RX线正确连接,并且地线(GND)也要正确连接。
  • 如果使用了中断处理程序,确保它能正确地处理接收到的数据,并且不会错过任何数据。
  • 检查接收缓冲区。如果缓冲区太小或处理速度太慢,可能会导致数据丢失。可以考虑增大缓冲区或优化处理程序。

2. 数据发送不正确或无法发送数据

这可能是由于几种原因引起的,包括:硬件连接问题、发送缓冲区问题、发送程序问题等。故障排查步骤可能包括:

  • 检查硬件连接,确保TX线正确连接,并且地线(GND)也要正确连接。
  • 检查发送缓冲区和发送程序。确保发送的数据被正确地放入缓冲区,并且发送程序能正确地从缓冲区取出数据并发送出去。

3. 通信距离问题

UART通信的距离有限,如果通信距离过长,可能会导致信号衰减,从而影响数据的接收。如果需要在更长的距离进行通信,可能需要使用RS-422或RS-485等差分信号标准。

4. 干扰问题

在有电磁干扰的环境中,UART通信可能会受到影响。如果有可能的话,尝试减少电磁干扰,使用屏蔽线来减少干扰或者采用电容滤波以降低噪声和电磁干扰。如果干扰过大,可能需要使用差分信号标准或光电隔离。

在进行故障排查时,一种常见的方法是使用逻辑分析器或示波器来观察UART的信号,这可以帮助确定问题的来源。在软件层面,也可以使用调试工具来观察和分析程序的运行情况。

Logo

2万人民币佣金等你来拿,中德社区发起者X.Lab,联合德国优秀企业对接开发项目,领取项目得佣金!!!

更多推荐