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)

原文发布于微信公众号 - 喔家ArchiSelf(wireless_com)

原文发表时间:2018-07-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏安恒网络空间安全讲武堂

翻译 | python利用shodan搜集信息

文中提及的部分技术、工具可能带有一定的攻击性、仅供安全学习和教学用途,禁止非法使用! 安装 为了开始使用Shodan的Python库,首先要确保你已经收到了AP...

43810
来自专栏BIT泽清

彩票iOS马甲包混淆避规4.3审核详细操作教程

马甲包对于一家公司的产品引流起到至关重要的作用。但是最近这段时间,苹果对于中国区的iOS开发者们要求很是严格,想必各位也吃到了苹果的2.1大礼包,满世界的寻找马...

6425
来自专栏吉浦迅科技

DAY40:阅读Memory Fence Functions

The CUDA programming model assumes a device with a weakly-ordered memory model, ...

774
来自专栏FreeBuf

手工检测Web应用指纹的一些技巧

0x01 Web 应用技术概览 1.1 架构 大多数 web 应用可以粗略划分为三个组件(component)。 1、客户端, 大多数情况下是浏览器。 2、服务...

5476
来自专栏IT派

爬虫大神,又出新招

几乎所有玩爬虫的人,一定会用requests库,这个库的作者是大名鼎鼎的Kenneth Reitz 。牛逼的一塌糊涂,最近我浏览它的网站,发现他又出新招,一个把...

1223
来自专栏SeanCheney的专栏

《Python分布式计算》 第6章 超级计算机群使用Python (Distributed Computing with Python)典型的HPC群任务规划器使用HTCondor运行Python任务

本章,我们学习另一种部署分布式Python应用的的方法。即使用高性能计算机(HPC)群(也叫作超级计算机),它们通常价值数百万美元(或欧元),占地庞大。 真正的...

1.3K10
来自专栏企鹅号快讯

Python爬取网站的一些小技巧

1.最基本的抓站 2.使用代理服务器 这在某些情况下比较有用,比如IP被封了,或者比如IP访问的次数受到限制等等。 3.需要登录的情况 登录的情况比较麻烦我把问...

2595
来自专栏北京马哥教育

爬虫大神,又出新招

1735
来自专栏杨建荣的学习笔记

如果理解Python web开发技术

首先来问一个问题,如何来看待Python web开发技术?如果不知道如何回答,我们换个问题:如何理解Python web的本质,这个我先用了三个程序来说明。 首...

3754
来自专栏owent

对atbus的小数据包的优化

atbus是我按之前的思路写得服务器消息通信中间件,目标是简化服务器通信的流程,能够自动选择最优路线,自动的断线重连和通信通道维护。能够跨平台并且高效。

1002

扫码关注云+社区