实用:嵌入式执行时间测量常用方法

一、常用方法

常用方法:

1.1 CPU 周期计数器

CPU 周期计数器是内核自带的调试模块,每一个 CPU 周期自增 1,具有精度最高(纳秒级)、侵入性最小的优势,是函数级测量的首选方案。

工作原理

以 Cortex-M 系列内核为例,通过使能 DWT(Data Watchpoint and Trace)模块的 CYCCNT 计数器,记录代码执行前后的计数值,计算差值后除以 CPU 频率,即可得到执行时间。

例:

复制代码
#include "core_cm4.h"

// 初始化周期计数器
static inline void cycle_counter_init(void) {
    // 1. 使能DWT模块
    if (!(CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk)) {
        CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    }
    DWT->CYCCNT = 0; // 2. 清零计数器
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk; // 3. 开启计数
}

// 获取当前周期计数值
static inline uint32_t cycle_counter_get(void) {
    return DWT->CYCCNT;
}

// 测量示例
void measure_function(void) {
    cycle_counter_init(); // 初始化(可全局执行一次)
    uint32_t start = cycle_counter_get();
    
    // 被测代码块
    foo();
    bar();
    
    uint32_t end = cycle_counter_get();
    uint32_t delta_cycles = end - start; // 无符号溢出自动处理
    float delta_us = (float)delta_cycles / (SystemCoreClock / 1000000); // 换算为微秒
    printf("[PROF] 执行时间:%lu 周期 / %.2f us\r\n", delta_cycles, delta_us);
}
适用场景
  • 微秒级甚至纳秒级的高精度测量;
  • 函数、中断服务程序等小粒度代码块;
  • 无 RTOS 的裸机环境或轻量 RTOS 环境。

1.2 片上定时器 / SysTick:工程折中方案

当芯片不支持 DWT 模块,或需要与 CPU 频率解耦的时间基准时,片上通用定时器是最常用的选择。通过配置定时器为自由运行模式,以固定频率计数,适合中粒度时间测量。

工作原理

配置定时器的预分频系数和自动重装载值,使其以已知频率(如 1MHz,即 1tick=1us)递增,测量前后读取计数器值,计算差值即为时间。

例:
复制代码
#include "stm32f4xx_hal.h"

static TIM_HandleTypeDef htim2;

// 初始化定时器(1MHz计数,1tick=1us)
void timer_profiling_init(void) {
    __HAL_RCC_TIM2_CLK_ENABLE();
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 83; // APB1时钟84MHz,84MHz/(83+1)=1MHz
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 0xFFFFFFFF; // 32位计数,减少溢出
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
    HAL_TIM_Base_Init(&htim2);
    HAL_TIM_Base_Start(&htim2); // 自由运行模式启动
}

// 获取当前计数值
static inline uint32_t timer_profiling_get_ticks(void) {
    return __HAL_TIM_GET_COUNTER(&htim2);
}

// 测量示例
void measure_task(void) {
    timer_profiling_init(); // 全局初始化一次
    uint32_t start = timer_profiling_get_ticks();
    
    // 被测代码块
    task_run();
    
    uint32_t end = timer_profiling_get_ticks();
    // 处理计数器溢出
    uint32_t delta_ticks = (end >= start) ? (end - start) : (0xFFFFFFFFu - start + 1u + end);
    float delta_us = (float)delta_ticks; // 1tick=1us
    printf("[PROF] 任务执行时间:%.2f us\r\n", delta_us);
}
适用场景
  • 几十微秒到秒级的中粒度代码块;
  • 需与 CPU 频率解耦的测量场景;
  • 不支持 DWT 模块的芯片。

1.3 GPIO 翻转 + 示波器:最直观的可视化方案

这种方法原理简单但实用性极强,通过 GPIO 电平翻转标记测量起止,用示波器或逻辑分析仪测量脉宽,可避免软件测量的侵入性影响,误差极小。

工作原理
  • 测量开始前:将指定 GPIO 引脚拉高;
  • 测量结束后:将该 GPIO 引脚拉低;
  • 示波器测量高电平脉宽,即为代码执行时间。
例:
复制代码
#include "stm32f4xx_hal.h"

// 定义测量GPIO(需提前初始化为输出模式)
#define PROF_GPIO_Port GPIOA
#define PROF_Pin GPIO_PIN_0

// 测量宏定义
#define MEASURE_BEGIN() HAL_GPIO_WritePin(PROF_GPIO_Port, PROF_Pin, GPIO_PIN_SET)
#define MEASURE_END() HAL_GPIO_WritePin(PROF_GPIO_Port, PROF_Pin, GPIO_PIN_RESET)

// 测量示例
void process_data_measure(void) {
    MEASURE_BEGIN();
    
    // 被测代码块
    process_sensor_data();
    update_control_output();
    
    MEASURE_END();
}
适用场景
  • 调试阶段的直观验证;
  • 需排除软件测量干扰的场景;
  • 多信号时序对齐测量(如同步观察多个代码块执行时间)。

1.4 RTOS 运行时间统计:系统级瓶颈分析

在 RTOS 环境中,可通过系统自带的运行时间统计功能,获取各任务的 CPU 占用率,适合系统级性能优化和瓶颈定位。

工作原理(以 FreeRTOS 为例)
  • 启用统计功能后,FreeRTOS 通过高精度定时器维护每个任务的运行时间计数器;
  • 任务切换时,内核计算当前任务的运行时长并累加;
  • 通过 API 函数可获取各任务的运行时间占比,生成统计报表。

例:

复制代码
#define configGENERATE_RUN_TIME_STATS 1 // 启用运行时间统计
#define configUSE_STATS_FORMATTING_FUNCTIONS 1 // 启用格式化输出函数
extern void vConfigureTimerForRunTimeStats(void); // 定时器初始化钩子
extern unsigned long ulGetRunTimeCounterValue(void); // 计数值获取钩子
  1. 实现钩子函数与定时器初始化:

    // 定时器初始化(复用片上定时器方案)
    void vConfigureTimerForRunTimeStats(void) {
    timer_profiling_init(); // 1MHz计数,1tick=1us
    }

    // 获取当前计数值
    unsigned long ulGetRunTimeCounterValue(void) {
    return (unsigned long)timer_profiling_get_ticks();
    }

  2. 创建统计打印任务:

    #define RUNTIME_STATS_BUF_LEN 256
    void vTaskPrintStats(void *pvParameters) {
    char buf[RUNTIME_STATS_BUF_LEN];
    for (;;) {
    vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒打印一次
    memset(buf, 0, RUNTIME_STATS_BUF_LEN);
    vTaskGetRunTimeStats(buf); // 生成统计报表
    printf("===== 任务运行时间统计 =====\r\n%s\r\n", buf);
    }
    }

  3. 典型输出结果:

    ===== 任务运行时间统计 =====
    Task Run Time Percentage

    ctrlTask 350000 35%
    commTask 250000 25%
    logTask 150000 15%
    idle 250000 25%

适用场景
  • 多任务 RTOS 系统的全局性能分析;
  • 定位 CPU 占用率过高的任务;
  • 系统架构优化与负载均衡调整。

二、方法选型指南

测量需求 推荐方法 优势 局限性
函数 / 中断级、微秒级精度 CPU 周期计数器(DWT) 精度最高、侵入性最小 依赖内核支持、需 CPU 频率稳定
任务级、10μs~ 秒级 片上定时器 / SysTick 实现简单、与 CPU 频率解耦 精度中等、需处理计数器溢出
调试阶段、直观验证 GPIO 翻转 + 示波器 无软件干扰、可视化强 需硬件工具、不适用于生产环境
系统级、任务占比分析 RTOS 运行时间统计 全局视角、定位系统瓶颈 仅支持 RTOS、无法测量单个函数
相关推荐
chao18984420 分钟前
STM32 HAL库驱动AT24C02 EEPROM例程
stm32·单片机·嵌入式硬件
憧憬成为java架构高手的小白31 分钟前
docker学习笔记(基于b站多个视频学习)【未完结】
笔记·学习
RainCity2 小时前
Java Swing 自定义组件库分享(七)
java·笔记·后端
東隅已逝,桑榆非晚2 小时前
字符函数和字符串函数
c语言·笔记
Upsy-Daisy3 小时前
AI Agent 项目学习笔记(七):RAG 高级扩展——过滤检索、PgVector 与云知识库
人工智能·笔记·学习
智者知已应修善业4 小时前
【51单片机LED闪烁10次数码管显示0-9】2023-12-14
c++·经验分享·笔记·算法·51单片机
智者知已应修善业4 小时前
【51单片机2按键控制1个敞亮LED灯闪烁和熄灭】2023-11-3
c++·经验分享·笔记·算法·51单片机
拾知_H5 小时前
STM32/Delay延时函数编程思路
stm32·单片机·时钟·延时
w2018006 小时前
二年级下册语文看图写话作文:蛋壳的奇妙之旅
笔记
daanpdf6 小时前
初三中考英语作文模板万能句型及范文大全电子版
笔记