找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

140

积分

0

好友

18

主题
发表于 前天 02:19 | 查看: 5| 回复: 0

在嵌入式开发中,开发者经常面临以下挑战:

  • 优化效果难以量化:修改大量代码后,无法直观判断性能是否提升
  • 实时性要求苛刻:控制算法需要精确评估是否满足1kHz、4kHz等高频率周期

1. 嵌入式时间测量的常见陷阱

在PC端测量代码执行时间相对简单,可使用std::chronogettimeofday或各种性能分析工具。但在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时间占用情况。

启用步骤(简化):

  1. 在FreeRTOSConfig.h中启用统计功能
  2. 提供两个钩子函数用于时间基准管理
  3. 创建监控任务定期输出统计信息

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_initcycle_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统计,覆盖从函数级到系统级的多层次时间观测。

图片

您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区(YunPan.Plus) ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-1 17:05 , Processed in 0.074512 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 CloudStack.

快速回复 返回顶部 返回列表