4、UART实战:使用STM32或Arduino实现UART回环测试、与PC串口助手通信、中断与DMA方式收发数据

UART(Universal Asynchronous Receiver/Transmitter)作为最经典的串行通信接口,在嵌入式系统中承担着调试信息输出、传感器数据采集、模块间通信等关键角色。本章将围绕STM32与Arduino两大主流平台,从回环测试、PC串口助手通信、中断收发到DMA收发,层层递进地构建一套完整的UART实战能力。通过本章学习,你将掌握UART的硬件连接要点、寄存器配置逻辑、中断与DMA的工程化应用方法,并能够独立排查通信故障。

UART 实战链路图:从回环测试到DMA收发 MCU 核心 STM32F103 / Arduino USART 外设 TX / RX / 波特率 NVIC 中断 RXNE / TXE DMA 控制器 通道映射 / 循环模式 回环测试 回环跳线 TX 短接 RX PC 串口助手 USB-TTL 转换 中断收发 逐字节处理 DMA 收发 内存缓冲区 uint8_t buf[64] 外设寄存器 DR 数据寄存器 传输完成中断 TC / IDLE 波特率一致性 → 电平匹配(TTL/RS-232)→ 共地 → 中断优先级 → DMA 通道映射

4.1 UART回环测试:验证硬件与驱动的基础手段

核心原理:回环测试(Loopback Test)是将UART的TX引脚直接短接到RX引脚,使发送的数据立即被自身接收。这种测试方式能够在不依赖外部设备的情况下,快速验证MCU的UART外设是否正常工作、引脚配置是否正确、波特率设置是否准确。回环测试分为硬件回环(物理跳线)和软件回环(内部寄存器配置)两种方式,工程中通常先做硬件回环以排除PCB布线问题。

操作流程:

  1. 将MCU的TX引脚与RX引脚用杜邦线或跳线帽短接;
  2. 初始化UART外设,设置波特率(如115200)、数据位8、停止位1、无校验;
  3. 编写发送函数,向串口发送一组已知数据(如0x55、0xAA、字符串"UART Loopback Test");
  4. 编写接收函数,读取接收到的数据并与发送数据比对;
  5. 若数据一致,则说明UART收发通路正常;若不一致,则需检查引脚映射、时钟使能或电平转换电路。
Arduino实现示例:
void setup() {
  Serial.begin(115200);  // 初始化串口,波特率115200
  pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
  if (Serial.available() > 0) {      // 检查接收缓冲区
    char c = Serial.read();           // 读取一个字节
    Serial.write(c);                  // 将收到的字节原样发送回去(回环)
    digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN));  // 翻转LED
  }
}

案例拆解:某工程师在调试STM32F103板卡时,发现通过串口助手发送数据无任何响应。首先进行硬件回环测试:将PA9(TX)与PA10(RX)短接,编写简单的回环程序。结果发现发送的数据能够正确接收,说明MCU的UART外设正常。随后检查外部电路,发现USB-TTL转换芯片的TXD/RXD与MCU的TX/RX交叉连接错误,修正后通信恢复正常。此案例说明回环测试是定位UART故障的第一道关卡,能够有效区分MCU内部问题与外部电路问题。

测试类型连接方式验证目标适用场景
硬件回环TX短接RXUART外设、引脚映射、波特率新板卡上电调试
软件回环内部寄存器配置驱动代码逻辑驱动开发阶段
外部回环通过USB-TTL转接电平转换电路、连接线缆整机联调
常见误区:回环测试通过不代表外部通信一定正常。回环测试仅验证了MCU自身的收发通路,若外部设备(如传感器、蓝牙模块)的电平标准不同(TTL vs RS-232),仍需额外的电平转换电路。此外,回环测试无法验证流控信号(RTS/CTS)的正确性。

4.2 与PC串口助手通信:建立调试通道

核心原理:PC串口助手通过USB-TTL转换芯片(如CH340、CP2102、FT232)与MCU的UART接口相连。通信链路为:PC端串口助手 → USB总线 → USB-TTL芯片 → TTL电平信号 → MCU的RX引脚。MCU发送的数据则沿相反方向传输。关键参数包括波特率、数据位、停止位、校验位,两端必须完全一致才能正确解析数据。

通信配置要点:

  • 波特率:常用9600、115200、460800等,需考虑双方时钟误差(一般不超过±2%);
  • 数据格式:8N1(8数据位、无校验、1停止位)是最常用配置;
  • 电平匹配:MCU的UART引脚为TTL电平(0~3.3V或0~5V),PC串口为RS-232电平(±12V),必须经过转换芯片;
  • 共地:MCU与USB-TTL转换器必须共地,否则通信不稳定。
STM32 HAL库初始化代码(STM32CubeMX生成):
UART_HandleTypeDef huart2;

void MX_USART2_UART_Init(void) {
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK) {
    Error_Handler();
  }
}

实战场景:在电机控制项目中,工程师需要通过串口助手实时监控电机转速、电流等参数。采用中断方式接收PC下发的指令(如"SPEED 1500\r\n"),解析后调整PWM占空比;同时以10Hz频率主动向上位机发送状态帧。通过串口助手的数据波形显示功能,可以直观观察参数变化趋势。此场景要求中断接收具备良好的实时性,且发送缓冲区管理要避免数据覆盖。

经验提醒:当使用串口助手发送数据时,注意勾选"发送新行"选项(即自动追加\r\n),否则MCU端需要自行处理帧结束判断。推荐使用帧头+长度+校验的协议格式,避免单纯依赖超时判断。

4.3 中断方式收发数据:提升实时响应能力

核心原理:中断方式收发数据利用UART外设的RXNE(接收数据寄存器非空)和TXE(发送数据寄存器空)中断标志。当接收到一个字节时,硬件自动触发RXNE中断,CPU在中断服务函数中读取数据;当发送寄存器空闲时,触发TXE中断,CPU写入下一个待发送字节。这种方式避免了轮询方式中CPU的忙等待,提高了系统实时性。

中断配置流程:

  1. 使能UART全局中断和RXNE中断;
  2. 配置NVIC中断优先级;
  3. 编写中断服务函数,在函数中读取DR寄存器获取数据;
  4. 对于发送,可采用"发送缓冲区+发送完成中断"的方式,避免阻塞。
STM32 HAL库中断接收示例:
uint8_t rx_buffer[64];
uint8_t rx_index = 0;

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
  if (huart->Instance == USART2) {
    rx_buffer[rx_index++] = rx_data;
    if (rx_index >= 64) rx_index = 0;
    HAL_UART_Receive_IT(&huart2, &rx_data, 1);  // 重新使能接收
  }
}

// 主函数中启动首次接收
HAL_UART_Receive_IT(&huart2, &rx_data, 1);

案例拆解:某智能家居网关项目需要同时处理UART、I2C、SPI等多个外设中断。工程师将UART接收中断优先级设置为中等(NVIC优先级分组2,抢占优先级2),确保不阻塞高优先级的定时器中断。在中断服务函数中仅将数据存入环形缓冲区,主循环中解析处理。实测在115200波特率下,连续接收1000字节无丢失,CPU占用率仅12%。若采用轮询方式,CPU占用率将超过60%,且无法响应其他外设事件。

对比维度轮询方式中断方式DMA方式
CPU占用率高(忙等待)中(每字节中断)低(批量传输)
实时性依赖轮询周期即时响应批量完成后响应
代码复杂度简单中等较高
适用场景低速、简单调试中速、交互式通信高速、大数据量
常见误区:中断服务函数中执行耗时操作(如printf、延时、复杂计算)会导致中断响应延迟,严重时造成数据丢失。正确做法是中断中仅做数据搬运,处理逻辑放在主循环或任务中。此外,多个外设共用中断优先级时,需根据实时性要求合理分配抢占优先级和子优先级。

4.4 DMA方式收发数据:实现高效批量传输

核心原理:DMA(Direct Memory Access)允许外设与内存之间直接传输数据,无需CPU逐字节干预。UART的DMA传输分为两种模式:正常模式(传输指定长度后停止)和循环模式(缓冲区满后自动回绕)。DMA传输完成后触发传输完成中断,CPU只需在中断中处理整块数据。这种方式特别适合大数据量、高波特率的通信场景。

DMA配置关键步骤:

  • 通道映射:STM32中USART1_TX对应DMA1通道4,USART1_RX对应DMA1通道5(不同型号有差异,需查参考手册);
  • 方向配置:发送为内存到外设,接收为外设到内存;
  • 缓冲区管理:定义接收缓冲区数组,设置DMA传输长度;
  • 中断配置:使能DMA传输完成中断和UART空闲中断(用于不定长接收)。
STM32 HAL库DMA接收配置:
#define RX_BUF_SIZE 256
uint8_t dma_rx_buf[RX_BUF_SIZE];

// 启动DMA接收
HAL_UART_Receive_DMA(&huart2, dma_rx_buf, RX_BUF_SIZE);

// 空闲中断回调(需在中断中判断)
void HAL_UART_IdleCpltCallback(UART_HandleTypeDef *huart) {
  if (huart->Instance == USART2) {
    // 计算实际接收长度
    uint16_t len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
    // 处理接收到的数据
    process_data(dma_rx_buf, len);
    // 重新启动DMA接收
    HAL_UART_Receive_DMA(&huart2, dma_rx_buf, RX_BUF_SIZE);
  }
}

实战场景:在无人机飞控系统中,需要以921600波特率从GPS模块接收NMEA协议数据,每帧约80字节,每秒输出10帧。采用DMA循环模式接收,缓冲区大小512字节,配合空闲中断判断帧边界。实测CPU占用率从中断方式的35%降至5%以下,且在高负载飞行控制算法运行时,GPS数据依然稳定接收无丢失。DMA方式还避免了中断频繁进出带来的抖动,提高了数据时间戳的准确性。

经验提醒:DMA接收时务必处理"半传输中断"和"传输完成中断"两个事件,防止缓冲区数据被覆盖。对于不定长数据,推荐使用"空闲中断+DMA"组合方案:DMA负责数据搬运,空闲中断负责帧结束判断。另外,DMA缓冲区大小需根据最大数据帧长度合理设置,过小会导致数据截断,过大会浪费内存。
风险提示:DMA传输过程中,CPU不应同时访问正在被DMA读写的缓冲区,否则会造成数据竞争。建议采用双缓冲机制(Ping-Pong Buffer):DMA写入缓冲区A时,CPU处理缓冲区B;DMA写满A后切换至B,CPU处理A。此机制可有效避免数据冲突,但实现复杂度较高,需根据项目需求权衡。

总结复盘:UART实战的核心在于理解三种收发方式的适用边界。回环测试是硬件调试的第一道工序,能够快速定位故障范围;中断方式适合中低速、交互式通信,兼顾实时性与代码复杂度;DMA方式适合高速、大数据量场景,能够显著降低CPU负载。在实际项目中,往往采用组合策略:中断接收命令帧、DMA传输批量数据、轮询发送调试信息。掌握这些实战技能,将为后续学习CAN、SPI等复杂通信协议打下坚实基础。