MCU上的代码执行时间

在许多实时应用程序中,二八原则并不生效,CPU 可以花费95%(或更多)的时间在不到5% 的代码上。电动机控制、引擎控制、无线通信以及其他许多对时间敏感的应用程序都是如此。这些嵌入式系统通常是用c编写的,而且开发人员常常被迫对代码进行手工优化,可能会回到汇编语言,以满足性能的需求。测量代码部分的实际执行时间可以帮助找到代码中的热点。本文将说明如何可以方便地测量和显示在基于Cortex-M MCU的实时执行时间。

测量代码的执行时间

测量代码执行时间的方法有很多。作为一个嵌入式工程师,经常使用一个或多个数字输出和一个示波器。需要在执行要监视的代码之前设置一个高的输出,然后将输出降低。当然,在做这些之前有相当多的设置工作: 找到一个或多个自由输出,确保它们可以轻松访问,将端口配置为输出,编写代码,编译,设置范围等等。一旦有了一个信号,你可能需要对它进行一段时间的监视,以便看到最小值和最大值。 数字存储示波器使这个过程更容易,但是还有其他更简单的方法。

另一种测量执行时间的方法是使用可跟踪调试接口。只需要运行代码,查看跟踪,计算 delta时间(通常是手动的) ,并将CPU周期转换为微秒。不幸的是,这个跟踪给了一个执行的实例,可能不得不在追踪捕获中进一步查找最坏情况下的执行时间。这是一个乏味的过程。

Cortex-M 周期计数器

在大多数Cortex-M的处理器中调试端口包含一个32位的自由运行计数器,它可以计算 CPU 的时钟周期。计数器是 Debug 观察和跟踪(DWT)模块的一部分,可以很容易地用于测量代码的执行时间。下面的代码是启用和初始化这个特性非常有用。

#define  ARM_CM_DEMCR      (*(uint32_t *)0xE000EDFC) #define  ARM_CM_DWT_CTRL   (*(uint32_t *)0xE0001000) #define  ARM_CM_DWT_CYCCNT (*(uint32_t *)0xE0001004) if (ARM_CM_DWT_CTRL != 0) {        // See if DWT is available    ARM_CM_DEMCR      |= 1 << 24;  // Set bit 24    ARM_CM_DWT_CYCCNT  = 0;    ARM_CM_DWT_CTRL   |= 1 << 0;   // Set bit 0 }

使用DWT周期计数器来测量代码执行时间

可以通过在目标代码之前和之后读取周期计数器的值来测量和计算代码段的执行时间,如下所示。 当然,这意味着必须设置代码,但能够得到一个非常准确的值。

uint32_t  start; uint32_t  stop; uint32_t  delta; start = ARM_CM_DWT_CYCCNT; // Code to measure stop  = ARM_CM_DWT_CYCCNT; delta = stop – start;

因为使用的是无符号运算,delta表示所测量代码的实际执行时间(CPU 时钟周期)。

在测量开始和停止读数之间的代码执行时间时,可能会发生中断,所以每次执行这个序列很可能会有不同的值。在这种情况下,可能希望在测量过程中禁用中断,但是要清楚禁用中断是暂时的,只用于测量。尽管如此,也许应该把中断的任务包括进来,因为它们会影响到代码的最后执行时间。

Disable Interrupts; start = ARM_CM_DWT_CYCCNT; // Code to measure stop  = ARM_CM_DWT_CYCCNT; Enable Interrupts; delta = stop – start;

如果所测代码包含条件语句、循环或任何可能导致变化的东西,那么获得的值可能不代表最坏情况下的执行时间。为了纠正这个问题,需要添加一个峰值检测器,如下图所示。当然,在进行任何测量之前,需要将 max 声明并初始化为最小值(即0)。

start = ARM_CM_DWT_CYCCNT; // Code to measure stop  = ARM_CM_DWT_CYCCNT; delta = stop – start; if (max < delta) {    max = delta; }

同样,知道最短执行时间也是有趣且有用的 在进行任何测量之前,只需要声明和初始化最大可能值(即0xFFFFFFFF)。下面是新的代码: ``` tart = ARMCMDWT_CYCCNT;

// Code to measure

stop = ARMCMDWT_CYCCNT;

delta = stop – start;

if (max < delta) {

max = delta;

}

if (min > delta) {

min = delta;

} ``` 就像 Cortex-M4处理器和 Cortex-M7那样,执行时间还取决于CPU是否配备了缓存。如果系统中使用了指令或数据缓存,对同一段代码的多重测量可能不一致。这时,可以考虑禁用缓存以测量最坏的情况。

大多数调试器允许显示这些变量值。如果是这样,则需要在全局范围内声明显示变量,以保留它们的值并允许实时监控。不幸的是,这些值代表的是CPU时钟周期,而且大多数调试器还不够成熟,无法为了显示目的而对变量进行缩放。假设一个16兆赫的CPU时钟速度,显示70.19微秒比显示1123个周期要方便得多。实际上还有一种更好的方法来显示这些变量,这也提供了规模化能力,可以以一种更加可读的形式看待它们。

经过的时间模块

当然,可以将代码片段嵌入到应用程序中,但还可以可以使用一个简单的模块。 elapsedtime.c与elapsedtime.h,它仅由4个函数组成。

方法如下:

  1. 按照惯例,#include

  2. 在使用elapsedtime.c 中的其他函数之前,调用 elapsedtime_init()

  3. 通过设置"ELAPSEDTIMEMAX_SECTIONS"来定义时间测量结构的最大数目。这与用 stop/start代码包装的不同代码段相对应

  4. 调用elapsedtimestart()并传递要监视的代码片段的索引(即0 到ELAPSEDTIMEMAX_SECTIONS-1)

  5. 调用elapsedtimestop()并传递在运行时启动时所使用的相同索引

  6. 如果调试器允许监视变量(即当目标正在运行时) ,则可以显示elapsedtimetbl[],并查看对应索引的运行时间结构

  7. 重复执行步骤4到6,并将代码置于最坏和最好的情况下,以便ELAPSED_TIME数据结构中的Min 和max 字段可以很好地表示所测量代码片段的执行时间

需要注意的是, 没有在测量过程中禁用中断,因为ISR可能会涉及到,也需要了解这会如何影响感知的执行时间。

void  main (void) {    // Some code    elapsed_time_init();         // Iitialize the module    // Some code } void  MyCode (void) {    // Some code here    elapsed_time_start(0);    // Start measurement of code snippet #0    // Code being measured    elapsed_time_stop(0);     // Stop and    // Some other code }

当然,最小和最大的执行时间取决于测量的频率,以及代码是否分别受到最佳和最差条件的限制。

另外,没有必要显示起始字段,因为它只用于在测量开始时记录DWT周期计数器的值,然而,启动字段可以用来显示出来。换句话说,当看到这个值变化时,就会知道测量正在发生。

使用 uc / probe 的示例显示

使用了elapsed_time.c 和 uc/probe,来测量一下代码片段的执行时间。

图1| IAR 和 uc/probe 的树视图

图1显示了使用IAR的LiveWatch (左)和 uc / probe 的树视图(右)。截图是在不同的时间拍摄的,是一个存储不同代码片段的测量值的数组。

可以将min/max/current分配给计量表和数字指示器,如图2所示。CPU 运行在80mhz,这些值以微秒显示,应用了0.0125的缩放因子。左侧的按钮用于重置统计数据,从而迫使重新计算最小值和最大值。

图2 | 使用uc/probe 的仪表显示最大执行时间

Uc/probe 的一个强大特性是能够与微软的 Excel 对接,从而在电子表格中显示这些值(实时) ,如图3所示。

图3 | 使用 Excel 显示实时数据

小结

作为嵌入式开发人员,有许多工具可以用来测试和验证设计。对于代码执行时间,可以很容易地使用 Cortex-M 处理器众多特性中的一个,即DWT周期计数器。

uc/probe 提供了很多功能,允许使用计量表、仪表盘、数字指示器、 Excel界面或图表来监控应用程序中的许多变量。通过内置的示波器功能,一旦触发条件满足,还可以捕获多达7个额外变量值。

附录代码

elapsed_time.c

#include  <stdint.h> #include  <elapsed_time.h> /* ******************************************************************************** *                           CORTEX-M - DWT TIMER ******************************************************************************** */ #define  ARM_CM_DEMCR      (*(uint32_t *)0xE000EDFC) #define  ARM_CM_DWT_CTRL   (*(uint32_t *)0xE0001000) #define  ARM_CM_DWT_CYCCNT (*(uint32_t *)0xE0001004) /* ******************************************************************************** *                             Data Structure ******************************************************************************** */ typedef  struct  elapsed_time {    uint32_t  start;    uint32_t  current;    uint32_t  max;    uint32_t  min; } ELAPSED_TIME; /* ******************************************************************************** *                      STORAGE FOR ELAPSED TIME MEASUREMENTS ******************************************************************************** */ static  ELAPSED_TIME  elapsed_time_tbl[ELAPSED_TIME_MAX_SECTIONS]; /* ******************************************************************************** *                              MODULE INITIALIZATION * * Note(s): Must be called before any of the other functions in this module ******************************************************************************** */ void  elapsed_time_init (void)         {    uint32_t  i;            if (ARM_CM_DWT_CTRL != 0) {                  // See if DWT is available        ARM_CM_DEMCR      |= 1 << 24;            // Set bit 24        ARM_CM_DWT_CYCCNT  = 0;                        ARM_CM_DWT_CTRL   |= 1 << 0;             // Set bit 0    }    for (i = 0; i < ELAPSED_TIME_MAX_SECTIONS; i++) {        elapsed_time_clr(i);    } } /* ******************************************************************************** *                  START THE MEASUREMENT OF A CODE SECTION ******************************************************************************** */ void  elapsed_time_start (uint32_t  i)   {    elapsed_time_tbl[i].start = ARM_CM_DWT_CYCCNT; } /* ******************************************************************************** *           STOP THE MEASUREMENT OF A CODE SECTION AND COMPUTE STATS ******************************************************************************** */ void  elapsed_time_stop (uint32_t  i)   {    uint32_t       stop;    ELAPSED_TIME  *p_tbl;        stop           = ARM_CM_DWT_CYCCNT;      p_tbl          = &elapsed_time_tbl[i];    p_tbl->current = stop - p_tbl->start;    if (p_tbl->max < p_tbl->current) {        p_tbl->max = p_tbl->current;    }    if (p_tbl->min > p_tbl->current) {        p_tbl->min = p_tbl->current;    } } /* ******************************************************************************** *                      CLEAR THE MEASUREMENTS STATS ******************************************************************************** */ void  elapsed_time_clr (uint32_t  i)         {    ELAPSED_TIME  *p_tbl;            p_tbl          = &elapsed_time_tbl[i];    p_tbl->start   = 0;    p_tbl->current = 0;    p_tbl->min     = 0xFFFFFFFF;    p_tbl->max     = 0; }

elapsed_time.h

/* ******************************************************************************** *                       MODULE TO MEASURE EXECUTION TIME ******************************************************************************** */ /* ******************************************************************************** *                MAXIMUM NUMBER OF ELAPSED TIME MEASUREMENT SECTIONS ******************************************************************************** */ #define  ELAPSED_TIME_MAX_SECTIONS  10 /* ******************************************************************************** *                             FUNCTION PROTOTYPES ******************************************************************************** */ void  elapsed_time_clr   (uint32_t  i);      // Clear measured values void  elapsed_time_init  (void);             // Module initialization void  elapsed_time_start (uint32_t  i);      // Start measurement void  elapsed_time_stop  (uint32_t  i);      // Stop  measurement

参考文献

https://www.micrium.com/ucprobe/about/

https://www.iar.com/iar-embedded-workbench/

https://www.arm.com/products/processors/cortex-m

(本文编译自 http://www.embedded-computing.com/hardware/measuring-code-execution-time-on-arm-cortex-m-mcus)

(0)

相关推荐

  • 17张图带你秒杀synchronized关键字

    来自公众号:一只自动编码机 引子 小艾和小牛在路上相遇,小艾一脸沮丧. 小牛:小艾小艾,发生甚么事了? 小艾:别提了,昨天有个面试官问了我好几个关于 synchronized 关键字的问题,没答上来. ...

  • 【精品博文】老莫出题:数字集成电路与系统设计的寒假作业

    现在已经进入寒假阶段了,各位学生朋友们应该都已经回家或者在回家的路上了.也有少量的同学们还可能留在学校准备一些类似于"美国数学建模大赛"之类的竞赛事宜.总的来说放假是好事,辛苦了一 ...

  • 测试程序执行时间

    ​ 采用 Python 内置 time 模块来测试代码运行时间,即分别在被测代码的开 始和结束处调用time.perf_counter()函数得到系统时间,两次结果的差值即为代码执行时间. impor ...

  • 在 GitHub 上提交代码必备指南!

    [CSDN 编者按]你是否经常参与开源项目?如果在 GitHub 上参与开源项目的 Pull Request,你知道正确的做法是什么吗?别担心,本文为你准备了详细的指南,希望对你有一定裨益. 作者 | ...

  • jenkins学习12-github上提交代码后构建job

    前言 当我们有代码提交到代码仓库时,我们希望能自动触发构建任务,这个需求可以用jenkins的"构建触发器"来实现. 一般自己公司有本地的代码参考如gitlab,我这里以githu ...

  • win7系统显卡驱动装不上,代码43,怎么办?

    大家好我是大明今天就"win7系统显卡驱动装不上提示代码43"的故障给大家做一分享,导致显卡驱动问题的原因有很多 有可能是驱动版本不对.驱动程序损坏.操作系统问题.也有可能是显卡的 ...

  • (2条消息) IDEA中使用git拉取gitee上的代码并运行

    拉取项目 第1步 获取gitee上面的要拉取的项目url 第2步 file -> new -> Project from Version Control -> Git 第3步 接下来 ...

  • 咿呀?!火车上的代码、符号原来是这个意思

    坐过那么多次火车 车上各种代码.符号 到底什么意思? 比如 ↓↓↓ 想不想知道其中的 "奥秘"? 和小编一起开始今天的 普速客运列车车号科普吧 客运列车车号由3部分组成 依次是车种 ...

  • 教您看懂燃气表上的代码

    教您看懂燃气表 生活中洗澡做饭样样离不开燃气,在使用过程会看到燃气表上显示的各种提示代码,很多用户不知道这些代码的意思.今天,小编来给大家普及下物联表液晶屏显示的含义和一些常见的报故障代码! 其中最常 ...

  • 从网页上直接复制代码的因果推断书籍出现了, 学会主流方法成效极快

    邮箱:econometrics666@126.com 所有计量经济圈方法论丛的do文件, 微观数据库和各种软件都放在社群里.欢迎到计量经济圈社群交流访问. 之前推荐过1.我是如何从一个诗人变成一个计量 ...

  • 软件开发c++运行出错,我是敲的C++primer上的代码

    出错信息: Sales_data.obj : error LNK2019: 无法解析的外部符号 "class std::basic_istream > & __cdecl re ...

  • 上“低代码”半年,30名程序员被裁,CTO离职

    一位读者小M给我讲述了发生在他们公司的真实故事,为了避免不必要的麻烦,隐去一些敏感信息,我将整个事件的经过整理出来: 小M是广州某制造企业的技术负责人,下面带了50个技术人员,负责该公司OA.CRM. ...