Skip to content

2.5 UART 通信

理论知识

物理信号

硬件电路引脚

  • 实现模块点对点通信
  • 在两模块均已供电的情况下,只需 3 条线即可(Rx,Tx,GND)
    • Rx,Receiver,接收端
    • Tx,Transmitter,发送端
    • GND,Ground,接地端:两模块需共地,以区分 Rx & Tx 线上的电平高低
    • 部分具有流控策略的设备还需 RTS & CTS 两根线

接地方式

设备一设备二
RXDTXD
TXDRXD
GNDGND

信号与电平格式

  • 传输 0/1 码元
  • 高电平为 1(3.3V低电平为 0(0V)

比特率与波特率

  • 比特率(bit/s,bps每秒钟传输二进制码元数量
  • 波特率(symbol/s ):每秒钟传输有效信息码元的数量
  • UART 通信中,比特率 = 波特率
  • 十六进制字符编码中,如果一秒传输 1000 个字符,每个字符对应一种唯一的电平信号,那么波特率就是 1000symbol/s;由于一个十六进制字符要用四位二进制编码表示,那么它的比特率就是 4000bit/s

异步通信(Async)

不需等待某一方时钟信号同步后再发送,但通信内容较长时可能会因时钟不统一而错位。

如何解决错位问题呢?

可通过多次采样发现甚至解决错位问题(常见采样次数为 8 和 16)

* 对于 USART(即 UART & SART)通信,这种 SART 具有时钟线同步策略。

收发模式

仅接受、仅发送、收发双向。

链路传输

UART 协议

  • 点到点通信
  • 仅有校验与帧格式
  • 没有地址或 ID 的概念
  • 没有冲突检测、避免和仲裁的说法
  • 没有地址便没有滤波器与掩码

误码校验

Why?

减少硬件偶然错误的信息编码被解析利用

但仍可能存在强干扰化境导致信道电压波动而改变电平

如何实现?

奇偶校验算法

  • 对已发送的一段比特流进行计算,统计 1 的个数决定 1bit 的校验位
    • 偶数个 1 为 0,奇数个 1 为 1,则是偶校验
    • 奇数个 1 为 0,偶数个 1 为 1,则是奇校验
  • 等效于异或操作,对前面的比特流进行异或
    • 异或结果直接作为校验位,是偶校验
    • 异或结果取反作为校验位,是奇校验
  • 01010101,偶校验位为 0,奇校验位为 1

UART 帧数据格式

分段长度 bit备注
起始位1标志帧起始,必须为低电平 0
数据位5~8先发低位后发高位,一般常见的都是 8 位,也就是一个字节。也有 7 位的,相当于发 ASCII 码
校验位1将数据位通过奇偶校验算法得到的值放入该位
停止位1标志帧结束,必须为高电平 1
空闲位%链路上不传输任何信息,必须为高电平 1

通信方式

阻塞式通信

发送一段数据,在没有发送完所有数据之前,一直停留在此发送函数(可设定阻塞时间这个过程中会阻塞别的程序运行;

中断式通信

发送数据的过程在中断中进行,这个中断实际上是发送数据寄存器空的中断。

比如说你要发送 10 个字节的数据,那么这 10 个字节肯定不会一次性发送完,只能是一个一个字节地发送。每发送一个字节,发送数据寄存器就会有一个为空的标志,以提示可以发送下一个字节了,这样在发送数据的过程中 CPU 不会一直在等待,而是用中断来提示需要进行发送的时候再发送。不影响主程序进行。

DMA 中断通信

DMA(Direct Memory Access,直接内存访问)是一种计算机系统中用于数据传输的机制。它允许数据在外设和内存之间直接传输,而不需要 CPU 的介入,从而减轻了 CPU 的负担,提高了数据传输的效率。

举个栗子:

想象一下我们搬家的场景:你要把家里的一些东西从旧房子搬到新房子。在传统的情况下,你可能要亲自搬每一箱东西,把它们从旧房子搬到新房子。这就相当于 CPU 传统地处理数据传输的方式。 现在,有一支搬家队,他们专门负责搬家。你只需要告诉他们从哪里搬,搬到哪里,然后他们就会自己完成这项任务。而你可以利用这段时间去做其他事情,不需要亲自动手。这就有点类似于 DMA 的工作原理。 在计算机中,CPU 通常会处理数据的传输工作,就像你亲自搬家一样。但有了 DMA,就好比有了一支专门负责数据传输的队伍。CPU 只需要告诉 DMA 从哪里搬,搬到哪里,然后就可以去处理其他任务了。DMA 负责在外设和内存之间直接传输数据,而不需要 CPU 一直参与。

简而言之,DMA 就像是一支搬家队伍,负责在不需要 CPU 亲自操劳的情况下完成数据传输任务,从而提高了系统的效率。

该内容摘抄自 CSDNTM32CubeMx+HAL 库:USART 串口收发数据的三种方式

空闲中断

当单片机的 UART 歇下来的时候,会把之前连续接收的内容自动拼装成一段有效的信息存到缓冲区中

DMA 空闲中断

代码示例

阻塞式

  • 配置 UART 通信模块 USART1
  • 设置 USART1 为 asynchronous 模式
    • 设置波特率为 115200
    • 设置数据长度为 8bit
    • 不需要奇偶校验
    • 一位停止位
    • 收发双向通信
C
// 声明变量以存储UART的收发数据
uint8_t RxData[10];
uint8_t TxData[10] = "test";
C
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
HAL_Delay(500);
HAL_UART_Transmit(&huart1, TxData, 3, 1000);
HAL_UART_Receive(&huart1, RxData, 3, 1000);
if (RxData[0] == '1') {
    HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
    RxData[0] = 0;
}

中断式

  • 注意要配置 使能 USART1 global interrupt
  • 主函数负责发送,中断回调函数负责就收
  • 随意收发长数据
C
// 声明变量以存储UART的收发数据
uint8_t RxData[10];
uint8_t TxData[10] = "test";
C
while (1) {
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_5);
    HAL_UART_Transmit_IT(&huart1, TxData, 5);
    HAL_Delay(1000);
}
C
// 定义中断实函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
    uint8_t ReceiveData[10] = "okkk";
    if (huart == &huart1) {
        HAL_UART_Transmit_IT(&huart1, ReceiveData, 5);
        HAL_UART_Receive_IT(&huart1, RxData, 5);
        if (RxData[0] == '1') {
            HAL_GPIO_TogglePin(GPIOE, GPIO_PIN_5);
            RxData[0] = 0;
        }
    }
}

空闲中断

  • 实现 UART 随意发送定长数据,可接收一定长度以内的数据
  • 主函数只负责发送,中断回调函数负责接收

代码在 中断式 的程序基础上修改

  • HAL_UART_Receive_IT(&huart1,ExData,5); 换成 HAL_UARTEx_ReceiveToIdle_IT(&huart1, RxData, 5);
  • 中断回调函数名称改为 void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)

DMA 中断

  • 配置 USART 的 DMA 中断

    • Cube 使能 UART 中断
    • 添加发送 DMA
    • 添加接收 DMA
    • DMA 选项
      • 普通模式:支持定时搬运或代码主动调用搬运
      • 循环模式:一直搬运
      • 搬运时,内存需要自增地址,否则一段话会搬运到同一个地方去造成覆盖
      • FIFO(First In First Out)
        • 当接收信息太多时,DMA 可能搬不过来。FIFO 队列可以先把来不及搬运的内容缓存下来排队,一个一个让 DMA 搬运,但代价是让信息的时效性降低
        • 代价比较:信息丢失 vs 信息时效性差,根据这个来决定是否使用 FIFO 以及 FIFO 的长度
  • 实现 UART 随意收发定长数据

  • 主函数只负责发送,中断回调函数负责接收

代码在 中断式 的基础上进行修改(即 将 IT 换为 DMA)

  • HAL_UART_Transmit_IT(&huart1, TxData, 5); 换为 HAL_UART_ Transmit_DMA(&huart1, TxData, 5);
  • HAL_UART_Receive_IT(&huart1, RxData, 5); 换成 HAL_UART_Receive_DMA(&huart1, RxData, 5);

DMA 空闲中断

  • UART 随意收发定长数据,可接收一定长度内的数据
  • 主函数只负责发送,中断回调函数负责接收

代码在 DMA 中断 的基础上进行修改

  • HAL_UART_Receive_DMA(&huart1, TxData, 5); 换为 HAL_UARTEx_ReceiveToIdle_DMA(&huart1, TxData, 5);
  • 将中断回调函数名改为 HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)

串口查询函数

void HAL_UART_GetState(); //判断UART的接收是否结束,或者发送数据是否忙碌

食用方法:

C
while (HAL_UART_GetState(&huart1) ==
       HAL_UART_STATE_BUSY_TX) // 检测UART1发送结束

我们可以查看一下 void HAL_UART_GetState(); 的定义:

C
HAL_UART_StateTypeDef HAL_UART_GetState(UART_HandleTypeDef *huart) {
    uint32_t temp1 = 0x00U, temp2 = 0x00U;
    temp1 = huart->gState;
    temp2 = huart->RxState;

    return (HAL_UART_StateTypeDef)(temp1 | temp2);
}

让我们深入查看一下 HAL_UART_StateTypeDef 的定义,包含如下状态:

C
HAL_UART_STATE_RESET:UART外设已重置但未初始化。
HAL_UART_STATE_READY:UART外设已初始化并且可以进行数据传输。
HAL_UART_STATE_BUSY:UART外设正在进行数据传输中。
HAL_UART_STATE_BUSY_TX:UART外设正在进行数据发送中。
HAL_UART_STATE_BUSY_RX:UART外设正在进行数据接收中。
HAL_UART_STATE_BUSY_TX_RX:UART外设同时正在进行数据发送和接收中。
HAL_UART_STATE_TIMEOUT:UART外设传输超时。
HAL_UART_STATE_ERROR:UART外设传输错误。

总结

  • 阻塞式
    • HAL_UART_Transmit(&huart1, TxData, 5, 1000);
    • HAL_UART_Receive(&huart1, RxData, 5, 1000);
  • 中断式(使能中断)
    • void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){}
    • HAL_UART_Transmit_IT(&huart1, TxData, 5);
    • HAL_UART_Receive_IT(&huart1, RxData, 5);
  • 空闲中断(使能中断)
    • void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){}
    • HAL_UART_Transmit_IT(&huart1, TxData, 5);
    • HAL_UARTEx_ReceiveToIdle_IT(&huart1, RxData, 5);
  • DMA 中断(配置 DMA 且使能中断)
    • void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){}
    • HAL_UART_Transmit_DMA(&huart1, TxData, 5);
    • HAL_UART_Receive_DMA(&huart1, RxData, 5);
  • DMA 空闲中断(配置 DMA 且使能中断)
    • void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size){}
    • HAL_UART_Transmit_DMA(&huart1, TxData, 5);
    • HAL_UARTEx_ReceiveToIdle_DMA(&huart1, RxData, 5);