日志采集基础:如何配置调试串口(UART)输出

烧录失败的时候,最怕的就是两眼一抹黑。芯片不跑、灯不亮、调试器连不上——这时候你手里还剩什么?说白了,就剩一根串口线。

我做了十几年固件,踩过最大的坑就是:日志系统没配好,出了问题连个屁都抓不到。所以今天咱们就聊聊,怎么把UART调试串口配得稳稳当当,确保日志一条都不丢。

1. 选对串口引脚,别让硬件坑了你

先别急着写代码。你得确认板子上哪个UART是留给调试的。很多MCU有多个UART外设,但有些引脚可能被复用成其他功能。

我曾经遇到过一块开发板,TX/RX引脚默认被JTAG占用了。我调了一整天波特率,死活没输出。最后拿示波器一量,引脚电平根本没变化——压根就没切到UART模式。

我的习惯是:拿到新板子第一件事,翻原理图或者数据手册,确认以下几点:

  • UART编号:比如USART1、UART4,别搞混
  • 引脚号:TX是哪个GPIO,RX是哪个
  • 复用功能:是否需要配置AFIO或GPIO_AF
  • 电平标准:3.3V还是1.8V?接USB转串口模块时要注意
检查项 常见问题 我的建议
UART编号 用了不支持中断的UART 优先选带FIFO和硬件流控的UART
引脚复用 被其他外设占用 初始化时强制重映射
电平匹配 3.3V接5V烧坏引脚 加电平转换或选兼容模块

2. 波特率:不是越快越好

波特率这东西,很多人觉得设得越高越好。115200、921600,甚至2Mbps往上怼。但你想想看,波特率越高,对时钟精度和线路质量的要求也越高。

我个人的经验是:量产调试用115200,实验室调试用460800。为什么?

  • 115200:几乎所有串口工具都支持,线长3米以内基本稳定
  • 460800:适合短距离、低干扰环境,日志量大的时候能明显减少丢包
  • 921600及以上:除非你用屏蔽线+短距离,否则别碰。我见过太多人设了1Mbps,结果跑几步就乱码

核心原则:波特率的选择要满足 系统时钟误差 < 2%。你可以用公式算一下:

实际波特率 = 系统时钟 / (16 * USARTDIV)
误差 = |(目标波特率 - 实际波特率) / 目标波特率| * 100%

如果误差超过2%,建议换一个能整除的波特率值。

3. 流控:什么时候该用硬件流控?

很多人觉得流控可有可无。我一开始也这么想,直到有一次……

那是个跑RTOS的项目,任务多、日志量大。我用的是115200波特率,没开流控。结果系统跑着跑着就卡死了——日志发送缓冲区满了,但串口还在拼命往外吐,CPU被中断占满,任务调度都停了。

从那以后,我给自己定了个规矩:

  • 日志量小(每秒几十条以内):无流控,用中断发送就够了
  • 日志量大(每秒几百条以上):必须开硬件流控(RTS/CTS)
  • 极端情况(比如DMA+环形缓冲区):硬件流控+软件握手机制双保险
场景 推荐配置 原因
简单调试 无流控,中断发送 实现简单,资源占用少
中等日志量 无流控,DMA发送 减少CPU占用,提高吞吐
高日志量 硬件流控 + DMA 防止缓冲区溢出,保证不丢数据

4. 缓冲区设计:日志不丢的核心

配置好串口只是第一步。真正决定日志会不会丢的,是缓冲区怎么设计。

我见过太多人直接用 printf 往串口怼,结果系统一忙,字符就丢了。正确的做法是:

  1. 用环形缓冲区:生产者(你的代码)往缓冲区写,消费者(UART中断或DMA)从缓冲区读
  2. 缓冲区大小要合理:太小容易溢出,太大浪费内存。我一般按 最大日志速率 × 最大延迟时间 来算
  3. 加超时丢弃机制:如果缓冲区满了,老日志该丢就丢,别让系统卡死

一个小技巧:在环形缓冲区里加一个 overflow_count 计数器。每次丢日志时自增,这样你事后能知道丢了多少条。我习惯把这个值放在系统心跳里定期打印出来。

5. 配置流程总览

说了这么多,咱们用一张图把整个流程串起来。你照着这个顺序做,基本不会出问题。

UART调试串口配置流程 UART调试串口配置流程 1. 确认硬件引脚与复用 2. 选择波特率(误差<2%) 3. 配置流控(RTS/CTS) 4. 设计环形缓冲区 5. 实现中断/DMA发送 💡 每步完成后,用串口助手回环测试,确认数据无误再进入下一步

6. 代码示例:一个靠谱的UART初始化

光说不练假把式。下面是我个人常用的UART初始化代码,你拿去改改就能用。

// 以STM32 HAL库为例,配置UART调试串口
void Debug_UART_Init(void)
{
    UART_HandleTypeDef huart;
    
    // 1. 使能时钟
    __HAL_RCC_USART1_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    
    // 2. 配置GPIO复用
    GPIO_InitTypeDef gpio = {0};
    gpio.Pin = GPIO_PIN_9 | GPIO_PIN_10;  // TX=PA9, RX=PA10
    gpio.Mode = GPIO_MODE_AF_PP;
    gpio.Pull = GPIO_PULLUP;
    gpio.Speed = GPIO_SPEED_FREQ_HIGH;
    gpio.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &gpio);
    
    // 3. 配置UART参数
    huart.Instance = USART1;
    huart.Init.BaudRate = 115200;          // 波特率
    huart.Init.WordLength = UART_WORDLENGTH_8B;
    huart.Init.StopBits = UART_STOPBITS_1;
    huart.Init.Parity = UART_PARITY_NONE;
    huart.Init.Mode = UART_MODE_TX_RX;
    huart.Init.HwFlowCtl = UART_HWCONTROL_NONE;  // 无流控
    huart.Init.OverSampling = UART_OVERSAMPLING_16;
    HAL_UART_Init(&huart);
    
    // 4. 使能UART中断(用于接收)
    HAL_NVIC_SetPriority(USART1_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(USART1_IRQn);
}

注意:上面的代码里 HwFlowCtl 我设成了 NONE。如果你要用硬件流控,记得改成 UART_HWCONTROL_RTS_CTS,并且把RTS/CTS对应的GPIO也配好。

7. 验证:怎么确认日志没丢?

配置完了,怎么知道它到底稳不稳?我的做法是:

  1. 压力测试:写一个循环,每秒打印1000条日志,跑10分钟
  2. 对比计数:每条日志带一个递增序号,收完后检查有没有跳号
  3. 观察缓冲区:把 overflow_count 打印出来,看有没有非零值

如果发现丢日志,别急着改代码。先拿示波器看TX脚的波形——很多时候是硬件问题,不是软件问题。我遇到过好几次,最后发现是杜邦线接触不良。

我曾经花了一整天调一个丢日志的问题,最后发现是USB转串口模块的RX引脚虚焊了。从那以后,我包里常备一个逻辑分析仪,出问题先看波形,不瞎猜。

好了,日志采集的基础就这些。配置好UART,你就有了烧录失败时的第一道防线。下一件事,就是怎么把这些日志高效地存下来——那是后面章节的内容了。