在许多实时应用程序中,CPU 可以在不到 5% 的代码中花费 95%(或更多)的时间。电机控制、发动机控制、无线通信和许多其他对时间敏感的应用就是这种情况。这些嵌入式系统通常是用 C 语言编写的,并且开发人员经常被迫手动优化代码,可能会恢复为汇编语言,以满足紧迫的期限。测量部分代码的实际执行时间可以帮助您找到代码中的热点。本文将展示如何轻松测量和显示实时基于 Cortex-M 的 MCU 上的代码执行时间。
测量代码的执行时间
有很多方法可以测量代码执行时间。作为一名嵌入式工程师,我经常使用一个或多个数字输出和示波器。您只需在执行要监视的代码之前将其中一个输出设置为高电平,然后再将输出设置为低电平。当然,在您执行此操作之前还有相当多的设置工作:找到一个或多个空闲输出,确保它们易于探测,将端口配置为输出,编写代码,编译,设置范围等等。 收到信号后,您可能需要对其进行一段时间的监控以查看最小值和最大值。数字存储示波器使这个过程更容易,但还有其他方法比这更容易。
测量执行时间的另一种方法是使用具有跟踪功能的调试探针。您只需运行代码、查看跟踪、计算增量时间(通常是手动)并将 CPU 周期转换为微秒。不幸的是,跟踪为您提供了一个执行实例,您可能需要进一步查看跟踪捕获以找到最坏情况下的执行时间。这可能是一个乏味的过程。
Cortex-M 周期计数器
大多数基于 Cortex-M 的处理器上的 CoreSight 调试端口都包含一个 32 位自由运行计数器,用于计算 CPU 时钟周期。该计数器是调试监视和跟踪 (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) { // 看看
DWTDEMCR是否可用 ARM = 1 《《 24; // 设置位 24
ARM_CM_DWT_CYCCNT = 0;
ARM_CM_DWT_CTRL |= 1 《《 0; // 设置位 0
}
使用 DWT 循环计数器测量代码执行时间
您可以通过读取该段之前和之后的循环计数器的值来测量和计算代码段的执行时间,如下所示。当然,这意味着您必须检测您的代码,但您会得到一个非常准确的值。
uint32_t 开始;
uint32_t 停止;
uint32_t 增量;
开始 = ARM_CM_DWT_CYCCNT;
// 测量
停止的代码 = ARM_CM_DWT_CYCCNT;
delta = 停止 - 开始;
因为我们使用的是无符号数学,所以 delta 表示测量代码的实际执行时间(以 CPU 时钟周期为单位),即即使 stop 小于 start。
当然,在测量开始和停止读数之间括起来的代码的执行时间时可能会发生中断,因此每次执行此序列时很可能会有不同的值。在这种情况下,您可能希望在测量期间禁用中断以删除该伪影,如下所示,但要了解禁用中断是暂时的,并且仅包含在测量中。话虽如此,包含中断的工件可能会很有用,因为它们会影响代码的截止日期。
禁用中断;
开始 = ARM_CM_DWT_CYCCNT;
// 测量
停止的代码 = ARM_CM_DWT_CYCCNT;
启用中断;
delta = 停止 - 开始;
如果被测量的代码包含条件语句、循环或任何可能导致变化的东西,那么获得的值可能不代表最坏情况下的执行时间。要纠正这个问题,您可以简单地添加一个峰值检测器,如下所示。当然,在进行任何测量之前,需要声明 max 并将其初始化为最小值(即 0)。
开始 = ARM_CM_DWT_CYCCNT;
// 测量
停止的代码 = ARM_CM_DWT_CYCCNT;
delta = 停止 - 开始;
if (max 《 delta) {
max = delta;
}
同样,了解最短执行时间也可能很有趣且有用。在进行任何测量之前,只需声明 min 并将其初始化为最大可能值(即 0xFFFFFFFF)。这是新代码:
开始 = ARM_CM_DWT_CYCCNT;
// 测量
停止的代码 = ARM_CM_DWT_CYCCNT;
delta = 停止 - 开始;
if (max 《 delta) {
max = delta;
}
if (min 》 delta) {
min = delta;
}
执行时间还取决于 CPU 是否配备高速缓存,就像某些 Cortex-M4 处理器和 Cortex-M7 一样。如果您的系统使用指令或数据缓存,则同一段代码的多次测量可能会不一致。您可能会考虑禁用缓存以测量最坏的情况。
为了显示这些值,大多数调试器允许您实时显示这些变量值。如果是这种情况,则需要在全局范围内声明显示的变量以保留其值并允许实时监控。此外,不幸的是,这些值代表 CPU 时钟周期,并且大多数调试器都不够复杂,无法缩放变量以用于显示目的。假设 CPU 时钟速度为 16 MHz,显示 70.19 微秒比显示 1123 个周期要方便得多。实际上有一种更好的方法来显示实时变量,它还提供了缩放值的能力,因此您可以以更易读的形式查看它们。我将很快解释如何做到这一点。
经过时间模块
您当然可以将代码片段添加到您的应用程序中,或者您可以使用我编写的一个简单模块(包含在本文中)。与 elapsed_time.h 模块一起出现在下方的“elapsed_time.c”模块仅包含 4 个函数。
要使用:
只需#include
在使用 elapsed_time.c 中定义的其他函数之前调用 elapsed_time_init()。
通过设置 ELAPSED_TIME_MAX_SECTIONS 定义经过时间测量结构的最大数量。这对应于您要使用停止/启动代码包装的不同代码片段的数量。
调用 elapsed_time_start() 并将您要监视的代码片段的索引传递给它(即 0 。. ELAPSED_TIME_MAX_SECTIONS-1)。
调用 elapsed_time_stop() 并将您在 elapsed_time_start() 调用中使用的索引传递给它。
如果您的调试器允许您实时监控变量(即在目标运行时),您可以显示 elapsed_time_tbl[] 并查看您使用的相应索引的 ELAPSED_TIME 结构。
重复执行第 4 步到第 6 步,并让您的代码处于最坏和最好的情况下,以便 ELAPSED_TIME 结构的 .min 和 .max 字段很好地表示您正在测量的代码片段的执行时间。
您会注意到(请参阅 elapsed_time.c)我在测量期间没有禁用中断,因为可能涉及 ISR,您可能想知道这如何影响感知的执行时间。
void main (void)
{
// 一些代码
elapsed_time_init(); // 初始化模块
// 一些代码
}
void MyCode (void)
{
// 这里的一些代码
elapsed_time_start(0); // 开始测量代码片段 #0
// 正在测量的代码
elapsed_time_stop(0); // 停止和
// 一些其他代码
}
当然,最小和最大执行时间取决于您进行测量的频率以及代码是否分别受制于其最佳和最差条件。
elapsed_time_tbl[] 中的字段可以使用 Silicon Labs 的 Micrium uC/Probe 显示。事实上,uC/Probe 可以显示每个字段并缩放每个值,以便可以将 CPU 时钟周期转换为微秒,这更加友好。与大多数 Cortex-M MCU 内置的 CoreSight 调试端口连接时,uC/Probe 不需要对您的代码进行任何检测。
附带说明一下,不需要显示起始字段,因为它仅用于记录测量开始时 DWT 循环计数器的值。但是,开始字段可用于显示活动。换句话说,当您看到此值发生变化时,您就会知道正在进行测量。
使用 uC/Probe 的示例显示
我将 elapsed_time.c 模块与 uC/Probe 结合使用,并测量了四个代码片段的执行时间。
图 1 显示了使用 IAR 的 LiveWatch(左)和 uC/Probe 的 Tree View 控件(右)的原始形式的值。请注意,屏幕截图是在不同时间拍摄的。elapsed_time_tbl[] 是一个数组,用于存储不同代码片段的测量值。
图 1,IAR 和 uC/Probe 的树形视图控件。
您还可以将最小/最大/当前值分配给仪表和数字指示器,如图 2 所示。在这里,这些值以微秒为单位显示,因为我应用了 0.0125 的缩放因子,CPU 以 80 MHz 运行。我还决定只显示最大执行时间。左侧的按钮用于重置统计信息,从而强制重新计算最小值和最大值。
【图2 | 使用 uC/Probe 的仪表之一显示最大执行时间。]
uC/Probe 非常强大的功能之一是能够与 Microsoft 的 Excel 交互,从而在电子表格上显示值(实时),如图 3 所示。
【图3 | 使用 Excel 显示实时数据。]
概括
作为嵌入式开发人员,我们有很多工具可以用来测试和验证我们的设计。我已经演示了使用 Cortex-M 处理器的众多功能之一是多么容易,即 DWT 循环计数器。
Micrium 的 uC/Probe 提供了许多功能,允许您使用仪表、仪表、数字指示器、Excel 界面或图形/绘图来监控应用程序中的许多变量。凭借其内置的示波器功能,一旦满足触发条件,您还可以捕获多达七个附加变量的值。
随意使用或改进 elapsed_time.* 模块。不要犹豫,向我发送反馈。我考虑添加的另一个功能是在最大执行时间超过阈值时调用的回调函数。如果您想在这种情况发生时立即收到通知(打开 LED、发出警报等),这可能很有用。事实上,您甚至可以设置一个断点,以防您想查看是什么条件导致超过阈值。
审核编辑:郭婷
-
处理器
+关注
关注
68文章
19286浏览量
229809 -
led
+关注
关注
242文章
23277浏览量
660844 -
计数器
+关注
关注
32文章
2256浏览量
94561
发布评论请先 登录
相关推荐
评论