扫码加入

  • 正文
  • 相关推荐
申请入驻 产业图谱

嵌入式中代码执行时间测量的几种方法!

2025/11/18
1888
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

嵌入式开发中,大概率遇到过这些问题:

这个优化到底值不值:改了一堆代码,肉眼看不出有没有快。

控制算法一拍用了多少时间:能扛得住 1 kHz、4 kHz 的控制周期吗?

这篇文章我们来梳理下:在嵌入式环境下,靠谱地测一段代码的执行时间的方法

1. 嵌入式里“测时间”容易踩坑

在 PC 上测时间很简单:std::chronogettimeofday、各种 profiler,甚至 Chrome DevTools,都能给你一个数字。

MCU/SoC 上,事情立刻复杂几个量级:

操作系统薄甚至没有

很多裸机/轻量 RTOS 环境没有成熟 API,你只能直接碰寄存器

资源受限
频率才几十到几百 MHz,带宽有限,任何额外 printf 都可能改变原本的时序。

频率、功耗动态变化
有些芯片会变频、进低功耗、关时钟,导致“周期数 → 时间”的关系不再简单线性。

常见错误姿势:

用毫秒级系统滴答去测微秒级代码:比如用 1ms 的 SysTick tick 测一个 10us 的函数,结果永远是 0。

到处 printf 打时间printf 本身可能比你要测的代码还慢好几倍。

2. 几种主流手段

CPU 周期计数器(如 DWT_CYCCNT):精度最高,侵入最小。

片上定时器 / SysTick:工程中最常用的折中方案。

GPIO 翻转 + 示波器 / 逻辑分析仪:最直观的波形方式。

RTOS Trace / 运行时间统计:做系统级优化时很好用。

2.1 用 CPU 周期计数器(精度王)

以 Cortex-M 为例,内核里有一个调试用的周期计数器DWT_CYCCNT),从使能那一刻开始,每个 CPU cycle 自增 1。

开始测量:读一次 DWT_CYCCNT,记为 start

结束测量:读一次 DWT_CYCCNT,记为 end

周期差delta = end - start

时间Δt = delta / fcpu

Cortex-M4 上开启 DWT_CYCCNT

以下是一个典型的初始化过程(以 STM32 为例),代码压缩到最关键的几行:

实际测量某段代码:

从工程视角看,如果芯片支持 DWT/CYCCNT,这应该是首选方案

2.2. 用片上定时器计时(工程折中)

当芯片没有暴露类似 DWT 的计数器,或者你想用一个“跟 CPU 频率解耦”的时间基准时,就需要用通用定时器或 SysTick

配置一个 32 位(或 16 位)定时器为 自由运行模式(Auto-reload 设置为最大值)。选择合适的分频,让它以某个已知频率递增,比如 1 MHz(1 tick = 1 us)。测量时读定时器计数器值(CNT),前后做差,乘以 tick 时间就是时间差。

以 STM32 TIM2 为例的配置思路

假设计数频率目标是 1 MHz(精度 1 µs):

APB1 定时器时钟:假设为 84 MHz;预分频系数 PSC = 84 - 1 = 83;自动重装载 ARR = 0xFFFFFFFF(32 位计数器)。

代码实现如:

测量时:

几十微秒~秒级的代码块,用这种定时器方案。

2.3 GPIO 翻转 + 示波器/逻辑分析仪(最直观)

这个方法的思路特别“土”,但在工程里非常管用,而且误差小。

代码块开始前,把某个 GPIO 拉高;代码块结束后,把 GPIO 拉低;用示波器或逻辑分析仪测量这段高电平脉宽,就是执行时间。

代码示例:

调试阶段能用 GPIO + 示波器就用,做完确认再把这些宏关掉(宏空实现),避免污染最终固件

2.4 RTOS 运行时间统计 / Trace(系统级)

FreeRTOS 提供了一个功能:每个任务都维护一个运行时间计数器,最终可以看到“每个任务占用了多少 CPU 时间”。

开启方式(简化):

在 FreeRTOSConfig.h 打开统计功能

    1. ——告诉 FreeRTOS “我要统计运行时间”:

configGENERATE_RUN_TIME_STATS 1:启用运行时间统计(依赖你提供计数器)。

configUSE_STATS_FORMATTING_FUNCTIONS 1:启用 vTaskGetRunTimeStats() 这个格式化输出函数。

提供 2 个钩子函数——FreeRTOS 会回调它们来获取“时间刻度”:

vConfigureTimerForRunTimeStats():初始化“高精度计数器”。

ulGetRunTimeCounterValue():返回当前“运行时间计数值”(一个不断递增的无符号数)。

在某个任务里周期性打印统计信息——方便观察每个任务的 CPU 占比。

示例:

FreeRTOSConfig.h 中相关配置:

钩子函数的实现:

定时器实现:

注意:vConfigureTimerForRunTimeStats() 会在调度器启动前由 FreeRTOS 调用一次,你不需要手动调用ulGetRunTimeCounterValue() 则会被内核在每次任务切换时调用,用来给对应任务累加“运行时间”。

随后我们创建一个“监控任务”,每隔 1s 打一份统计:

这几个函数背后的逻辑可以这样理解:

FreeRTOS 不会自己搞定“时间”,而是一直向你要一个“递增计数值”;每次任务切换时,它记下“上一个任务最后离开时的计数值”,和上一次进入时的值做差,累加到这个任务名下;

vTaskGetRunTimeStats()

     会把每个任务的累计计数换算成百分比(相对于总计数),生成一张“任务运行时间分布表”。

所以,你看到的 Run Time 列本质上就是“这个任务在你的计数器上累计占了多少刻度”,刻度单位由 ulGetRunTimeCounterValue() 决定(本例中是 1us)。

这也是为什么我们用 1 MHz 的定时器:既好算,又有足够分辨率。

你会得到类似这样的输出(示意):

Task          Run Time    Percentage
------------------------------------
ctrlTask      350000      35%
commTask      250000      25%
logTask       150000      15%
idle          250000      25%

这能很快告诉你:

    谁是真正的 CPU 大户;哪个任务突然 CPU 占比飙升(可能出现 bug 或负载增加)。

3. 如何选方法?

我们按“精度 / 侵入性 / 实现复杂度 / 是否系统级”几个维度简单总结。

精度敏感(控制、DSP):DWT + GPIO(调试);

通用逻辑性能评估:定时器计数 + 简单统计;

任务调度/架构优化:RTOS 运行时间统计/Trace。

4. 测量时间的宏

实际项目里,可以使用一套非常轻量的宏,方便随手插测量点:

#if !defined(NDEBUG) || defined(ENABLE_PROFILING)
    /* Debug 模式,或者手动定义 ENABLE_PROFILING 时启用 */
    #define PROF_INIT()        cycle_counter_init()
    #define PROF_START(var)    uint32_t var = cycle_counter_get()
    #define PROF_END(var, label) 
        do { 
            uint32_t _end = cycle_counter_get(); 
            uint32_t _delta = _end - (var); 
            printf("[PROF] %s: %lu cyclesrn", (label), (unsigned long)_delta); 
        } while (0)
#else
    #define PROF_INIT()
    #define PROF_START(var)
    #define PROF_END(var, label)  do { (void)(var); (void)(label); } while (0)
#endif

 

cycle_counter_init与cycle_counter_get的实现就是上面的几种方法。

使用时:

void control_step(void)
{
    PROF_START(t0);

    update_observer();
    update_pid();
    update_pwm();

    PROF_END(t0, "control_step");
}

调试阶段开着,确认性能 OK 后,可以把 PROF_END 宏改成空实现,完全不影响生产固件。

5. 总结

嵌入式里测一段代码的执行时间,比 PC 环境复杂得多,简单的 printf 和毫秒级 tick 基本不靠谱。

核心手段就是用 CPU 周期计数器、片上定时器、GPIO + 示波器、RTOS 统计,从“函数级 → 任务级 → 系统级”多个层次观测时间。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!