4. 轮询发送性能分析:阻塞时间计算、CPU利用率评估、大数据量发送时的瓶颈

各位同学,今天我们来聊聊轮询发送模式里最核心的问题——性能。说白了,就是你的CPU到底花了多少时间在发数据上,还有多少时间能干别的活。我见过不少工程师,代码能跑通就觉得万事大吉,结果一上大数据量,系统直接卡死。嗯,咱们今天就把这笔账算清楚。

4.1 阻塞时间计算:每一微秒都要心里有数

轮询发送的本质,是CPU在“死等”硬件把数据发出去。这个等待时间,就是阻塞时间。它由两部分组成:数据写入时间和发送完成等待时间。

先看一个典型的轮询发送函数:

void UART_SendByte_Polling(uint8_t data)
{
    // 等待发送缓冲区空
    while (!(UART->SR & UART_SR_TXE));
    // 写入数据
    UART->DR = data;
    // 等待发送完成(可选,取决于是否要等移位寄存器)
    while (!(UART->SR & UART_SR_TC));
}

这里有两个while循环。第一个等TXE标志,第二个等TC标志。每个循环的等待时间,取决于波特率和硬件设计。

我给大家一个计算公式:

单字节阻塞时间 = (1 / 波特率) × 数据位数 × 2

其中“数据位数”包括起始位(1) + 数据位(8) + 校验位(0/1) + 停止位(1/1.5/2)。

举个例子。波特率115200,8N1格式(1起始+8数据+1停止=10位):

  • 单比特时间 = 1 / 115200 ≈ 8.68 μs
  • 单字节时间 = 8.68 μs × 10 = 86.8 μs
  • 两个while循环加起来,大约需要 86.8 μs × 2 ≈ 173.6 μs

你想想看,发一个字节就要花将近174微秒。如果发1KB数据呢?那就是174 μs × 1024 ≈ 178毫秒。这期间CPU什么都干不了。

我的经验: 我在一个STM32F103的项目里,用115200波特率轮询发日志。每次发完数据,系统响应就慢半拍。后来一算,发一条100字节的日志,CPU就卡了17毫秒。对于实时性要求高的控制任务,这简直是灾难。

4.2 CPU利用率评估:你的CPU到底有多忙?

阻塞时间算清楚了,CPU利用率就好办了。公式很简单:

CPU利用率 = (阻塞时间 / 总运行时间) × 100%

假设你的系统每100毫秒发一次数据,每次发256字节:

  • 单字节阻塞时间:173.6 μs(115200, 8N1)
  • 256字节总阻塞时间:173.6 μs × 256 ≈ 44.4 ms
  • CPU利用率:44.4 ms / 100 ms = 44.4%

也就是说,将近一半的CPU时间都花在等UART上。这还没算其他中断和任务。如果系统里还有别的外设要轮询,CPU很快就跑满了。

我整理了一个不同波特率下的对比表,大家可以直观感受一下:

波特率 (bps) 单字节阻塞时间 (μs) 1KB数据阻塞时间 (ms) CPU利用率 (每100ms发1KB)
9600 2083.3 2133.3 2133% (不可能)
38400 520.8 533.3 533% (不可能)
115200 173.6 177.8 177.8% (不可能)
921600 21.7 22.2 22.2%
2000000 10.0 10.2 10.2%

看到没?9600波特率下,发1KB数据就要2秒多。如果每100ms发一次,CPU利用率直接爆表。说白了,低波特率下用轮询发大数据量,就是自寻死路。

注意: 表格里CPU利用率超过100%的情况,意味着你的发送任务根本完不成。系统会不断积压数据,最终导致缓冲区溢出或看门狗复位。我曾经在一个项目里就踩过这个坑,9600波特率下轮询发日志,系统每隔几分钟就重启一次。查了两天才发现是UART堵死了主循环。

4.3 大数据量发送时的瓶颈:到底卡在哪里?

当你要发几百KB甚至几MB的数据时,轮询发送的瓶颈会暴露得淋漓尽致。我总结了三个主要瓶颈点:

  1. CPU被完全占用:整个发送过程中,CPU无法响应其他中断或任务。系统等于“假死”。
  2. 发送缓冲区溢出:如果上层应用还在不断往缓冲区塞数据,而底层发送速度跟不上,缓冲区很快就会满。数据丢失是必然的。
  3. 看门狗复位:很多嵌入式系统都有看门狗。如果主循环被UART阻塞太久,看门狗没来得及喂,系统就复位了。

我画了一张流程图,帮大家理清大数据量发送时的瓶颈链路:

大数据量轮询发送瓶颈链路图 大数据量轮询发送瓶颈链路 应用层发送请求 发送缓冲区 轮询发送(CPU死等) UART硬件发送 瓶颈点分析 ① 缓冲区大小有限 数据积压导致溢出 ② CPU 100% 占用 无法响应其他任务 ③ 看门狗无法喂狗 系统复位重启 ④ 波特率限制 物理层传输速度上限 ⑤ 中断被屏蔽 高优先级事件丢失

从这张图可以看得很清楚。数据从应用层下来,经过缓冲区,再到轮询发送,最后到硬件。每个环节都可能成为瓶颈。我个人习惯,在项目初期就会评估数据量和波特率是否匹配。如果发现轮询模式扛不住,我会果断切换到DMA或中断模式。

核心结论: 轮询发送只适合小数据量(几十字节以内)和低频率(每秒几次)的场景。一旦数据量超过几百字节,或者发送频率超过每秒几十次,就必须考虑其他发送模式。

4.4 实战案例:一个日志系统的性能分析

最后,我分享一个实际案例。之前做一个数据采集器,需要把传感器数据通过UART发到上位机。波特率115200,每100ms采集一次,每次数据包约200字节。

我一开始用轮询发送,结果发现:

  • 每次发送阻塞时间:173.6 μs × 200 ≈ 34.7 ms
  • CPU利用率:34.7 ms / 100 ms = 34.7%
  • 加上传感器采集、数据处理、显示刷新,CPU总利用率超过85%

系统勉强能跑,但偶尔会出现数据丢包。后来我换成DMA发送,CPU利用率直接降到5%以下。问题迎刃而解。

我的建议: 如果你在项目初期不确定用哪种模式,可以先按轮询写。但一定要预留好切换到DMA或中断的接口。别像我早期那样,代码写死了轮询,后期想改都改不动。