在 arduino/AVR 上监控代码的时钟周期?

电器工程 Arduino avr 爱特梅尔 计时器
2022-01-22 06:51:31

是否可以监控代码块并确定代码在 Arduino 和/或 AVR atmel 处理器上占用的处理器时钟周期数?或者,我是否应该监视代码运行前后经过的微秒?注意:我并不关心实时(例如,经过了多少实际秒数),就像“这段代码需要 CPU 多少时钟周期”一样

我能想出的当前解决方案来自 t​​ime.c:

#define clockCyclesPerMicrosecond() ( F_CPU / 1000000L )
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )

接线.c 补充说:

#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )

通过这个帐户,我可以通过监视经过的微秒来计算经过的时钟周期,然后将其传递给 microsecondsToClockCycles()。我的问题是,有没有更好的方法?

旁注:是否有用于 AVR 性能监控的良好资源。lmgtfy.com 和各种论坛搜索没有提供任何明显的结果,除了探索计时器

谢谢

4个回答

最简单的方法是让您的代码在执行您想要计时的代码之前拉高一些引脚,并在完成任何操作后将其拉低。然后进行代码循环(或在单次模式下使用带内存的数字示波器),然后只使用示波器然后固定。脉冲的长度告诉你从改变引脚状态到执行一段代码加上一个时钟周期需要多长时间(我认为需要一个周期,不是 100% 确定)。

“监控”是什么意思?

为一小段汇编代码计算 AVR 的时钟周期应该不难。

您还可以在代码执行之前设置端口并在之后将其重置,并使用逻辑分析仪或示波器对其进行监控以获取时序。

正如您所说,您还可以从快速运行的计时器中读取时间。

这是 Arduino 使用 clockCyclesPerMicrosecond() 函数计算已通过的时钟的示例。此代码将等待 4 秒,然后打印自程序启动以来经过的时间。左边的 3 个值是总时间(微秒、毫秒、总时钟周期),最右边的 3 个是经过的时间:

输出:

clocks for 1us:16
runtime us, ms, ck :: elapsed tme us, ms ck
4003236 4002	64051776	::	4003236	4002	64051760
8006668 8006	128106688	::	4003432	4004	64054912
12010508    12010	192168128	::	4003840	4004	64061440
16014348    16014	256229568	::	4003840	4004	64061440
20018188    20018	320291008	::	4003840	4004	64061440
24022028    24022	384352448	::	4003840	4004	64061440
28026892    28026	448430272	::	4004864	4004	64077824
32030732    32030	512491712	::	4003840	4004	64061440
36034572    36034	576553152	::	4003840	4004	64061440
40038412    40038	640614592	::	4003840	4004	64061440
44042252    44042	704676032	::	4003840	4004	64061440
48046092    48046	768737472	::	4003840	4004	64061440
52050956    52050	832815296	::	4004864	4004	64077824

我确信有一个合理的解释,为什么第一个太循环的经过时钟周期比大多数循环短,以及为什么所有其他循环在两个时钟周期长度之间切换。

代码:

unsigned long us, ms, ck;
unsigned long _us, _ms, _ck;
unsigned long __us, __ms, __ck;
void setup() {
        Serial.begin(9600);
}
boolean firstloop=1;
void loop() { 
        delay(4000);

        if (firstloop) {
                Serial.print("clocks for 1us:");
                ck=microsecondsToClockCycles(1);
                Serial.println(ck,DEC);
                firstloop--;
                Serial.println("runtime us, ms, ck :: elapsed tme us, ms ck");
        }

        _us=us;
        _ms=ms;
        _ck=ck;

        us=micros(); // us since program start
        ms=millis();
        //ms=us/1000;
        ck=microsecondsToClockCycles(us);
        Serial.print(us,DEC);
        Serial.print("\t");
        Serial.print(ms,DEC);
        Serial.print("\t");
        Serial.print(ck,DEC);     
        Serial.print("\t::\t");

        __us = us - _us;
        __ms = ms - _ms;
        __ck = ck - _ck;
        Serial.print(__us,DEC);
        Serial.print("\t");
        Serial.print(__ms,DEC);
        Serial.print("\t");
        Serial.println(__ck,DEC);     

}

旁注:如果您删除 4 秒延迟,您将开始更清楚地看到 Serial.print() 的效果。请注意,这里比较了 2 次运行。我只从各自的日志中包含了 4 个彼此靠近的样本。

运行 1:

5000604 5000	80009664	::	2516	2	40256
6001424 6001	96022784	::	2520	3	40320
7002184 7002	112034944	::	2600	3	41600
8001292 8001	128020672	::	2600	3	41600

运行 2:

5002460 5002	80039360	::	2524	3	40384
6000728 6000	96011648	::	2520	2	40320
7001452 7001	112023232	::	2600	3	41600
8000552 8000	128008832	::	2604	3	41664

经过的时间随着总运行时间的增加而增加。一秒钟后,时钟平均从 40k 增加到 44k。这在 1 秒后持续发生几毫秒,并且经过的时钟至少在接下来的 10 秒内保持在 44k 左右(我没有进一步测试)。这就是监控有用或需要的原因。也许效率下降与串行配置或错误有关?或者代码可能没有正确使用内存并且存在影响性能的泄漏等。

因为添加到源代码的每一行代码都会对性能产生影响,并且可能会更改应用的优化。更改应该是执行任务所需的最低限度。

我刚刚找到了一个名为“Annotated Assembly File Debugger”的 Atmel Studio 插件。http://www.atmel.com/webdoc/aafdebugger/pr01.html似乎单步执行实际生成的汇编语言,而可能很乏味将向您展示到底发生了什么。您可能仍然需要解码每条指令需要多少个周期,但它会比其他一些发布的选项更接近。

对于那些不知道在您的项目的输出文件夹中有一个带有 LSS 扩展名的文件的人。该文件包含所有作为注释的原始源代码,每行下方是基于该代码行生成的汇编语言。可以关闭生成 LSS 文件,因此请检查以下设置。

项目属性 | 工具链 | AVR/GNU 通用 | 输出文件

复选框“.lss(生成 lss 文件)