在嵌入式开发中,开发者经常面临以下挑战:
- 优化效果难以量化:修改大量代码后,无法直观判断性能是否提升
- 实时性要求苛刻:控制算法需要精确评估是否满足1kHz、4kHz等高频率周期
1. 嵌入式时间测量的常见陷阱
在PC端测量代码执行时间相对简单,可使用std::chrono、gettimeofday或各种性能分析工具。但在MCU/SoC环境中,复杂度显著增加:
- 操作系统支持有限:许多裸机或轻量级RTOS环境缺乏成熟的时间API,需要直接操作寄存器
- 资源约束严格:主频仅几十到几百MHz,带宽有限,额外的
printf输出都可能改变原有时序
- 动态频率调整:芯片的变频、低功耗模式和时钟管理会导致"周期数-时间"转换关系非线性
常见错误做法包括:
- 使用毫秒级系统节拍测量微秒级代码:例如用1ms的SysTick测量10us函数,结果永远显示为0
- 过度依赖
printf输出:printf本身执行时间可能远超被测代码
2. 主流测量方案对比

- CPU周期计数器(如DWT_CYCCNT):精度最高,侵入性最小
- 片上定时器/SysTick:工程中最常用的平衡方案
- GPIO翻转+示波器/逻辑分析仪:最直观的波形观测方式
- RTOS运行时间统计/Trace:系统级优化的有效工具
2.1 CPU周期计数器(最高精度方案)
以Cortex-M架构为例,内核内置调试用的周期计数器(DWT_CYCCNT),使能后每个CPU周期自动递增1。
测量流程:
- 开始测量:读取
DWT_CYCCNT作为起始值start
- 结束测量:再次读取作为结束值
end
- 计算周期差:
delta = end - start
- 转换时间:
Δt = delta / f_cpu
Cortex-M4的DWT_CYCCNT初始化
以下为STM32平台的典型初始化代码(关键部分精简):

实际测量代码段示例:

从工程角度看,若芯片支持DWT/CYCCNT,这应作为首选方案。
2.2 片上定时器计时(工程平衡方案)
当芯片未提供DWT类计数器,或需要与CPU频率解耦的时间基准时,可采用通用定时器或SysTick。
配置要点:
- 设置32位(或16位)定时器为自由运行模式(Auto-reload设为最大值)
- 选择合适分频,使递增频率已知(如1MHz对应1tick=1us)
- 测量时读取定时器计数器值(CNT),计算差值后乘以tick时间
STM32 TIM2配置示例
假设目标计数频率1MHz(精度1µs):
- APB1定时器时钟:84MHz
- 预分频系数PSC = 84 - 1 = 83
- 自动重装载ARR = 0xFFFFFFFF(32位计数器)
代码实现:

测量过程:

此方案适用于几十微秒至秒级的代码块测量。
2.3 GPIO翻转+示波器(最直观方案)
这种方法虽然简单,但工程实践中非常有效且误差较小。
操作步骤:
- 代码段开始前将指定GPIO置高
- 代码段结束后将GPIO拉低
- 使用示波器或逻辑分析仪测量高电平脉宽即为执行时间

代码示例:

调试阶段建议使用GPIO+示波器组合,确认性能后可通过宏定义关闭相关代码,避免影响最终固件。
2.4 RTOS运行时间统计(系统级分析)
FreeRTOS提供了任务级运行时间统计功能,可查看每个任务的CPU时间占用情况。
启用步骤(简化):
- 在FreeRTOSConfig.h中启用统计功能
- 提供两个钩子函数用于时间基准管理
- 创建监控任务定期输出统计信息
FreeRTOSConfig.h相关配置:

钩子函数实现:

定时器初始化:

注意:vConfigureTimerForRunTimeStats()由FreeRTOS在调度器启动前自动调用,无需手动触发;ulGetRunTimeCounterValue()在内核任务切换时被调用,用于累计任务运行时间。
创建监控任务周期性输出(示例间隔1s):

运行时间统计原理:
- FreeRTOS通过钩子函数获取递增计数值
- 每次任务切换时计算时间差并累加到对应任务
vTaskGetRunTimeStats()将累计值转换为百分比格式
典型输出示例:
Task Run Time Percentage
------------------------------------
ctrlTask 350000 35%
commTask 250000 25%
logTask 150000 15%
idle 250000 25%
这种统计能快速识别:
- CPU占用最高的任务
- 任务CPU占比的异常波动(可能指示bug或负载变化)
3. 方案选择指南
从精度、侵入性、实现复杂度和系统级支持四个维度总结:

- 高精度需求(控制、DSP):DWT+GPIO(调试阶段)
- 通用逻辑性能评估:定时器计数+简单统计
- 系统架构优化:任务调度分析+RTOS运行时间统计
4. 实用测量宏定义
项目中可使用轻量级宏定义方便插桩测量:
#if !defined(NDEBUG) || defined(ENABLE_PROFILING)
/* Debug模式或手动启用性能分析时激活 */
#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 cycles\r\n", (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");
}
调试阶段启用测量,性能确认后可通过宏定义完全禁用,不影响生产代码。
5. 总结
嵌入式环境下的代码执行时间测量比PC环境复杂,简单的printf和毫秒级节拍基本不可靠。
核心方法包括CPU周期计数器、片上定时器、GPIO+示波器和RTOS统计,覆盖从函数级到系统级的多层次时间观测。
