监视时钟周期以获取arduino / AVR上的代码?


11

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

我可以提出的当前解决方案是来自time.c:

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

connection.c添加:

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

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

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

谢谢

Answers:


6

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


谢谢。是的,我可以看到这可能是最准确的解决方案。我仍然不喜欢那些至少可以使我在代码内部进行一般周期使用分析的代码。我将使用它来构建一些测试工具,最好根据当前的Atmel CPU上的代码及其相关内容的运行效率,为参数(例如最大允许运行时间)设置上限。使用
cyphunk

4

“监视”是什么意思?

对于小的汇编代码,为AVR计算时钟周期应该不难。

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

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


监视器,我的意思是确定代码使用的周期数。类似于(注意,代码格式可能会被注释引擎弄平):clocks = startCountingAtmegaClocks(); for ... {for ... {digitalRead ...}} Serial.print(“已使用周期数:”); Serial.print(currentCountingAtmegaClocks()-时钟,DEC);
cyphunk

但是,是的,您的回答就是我认为的选择。我猜想,我假设如果我能计算出汇编程序需要
人工处理

3

这是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

我敢肯定,有一个合理的解释,为什么第一个too循环的循环时钟周期比大多数循环短,为什么所有其他循环在两个时钟周期长度之间切换。

码:

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()的效果。注意,这里比较了两次运行。我仅从各自的日志中收集了彼此接近的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左右(我还没有对其进行进一步测试)。这就是为什么监视有用或需要监视的原因。效率降低可能与配置或串行错误有关?也许代码未正确使用内存,并且存在影响性能的泄漏等。


许多年后,我仍然希望能用代码(与示波器并置)更准确地显示时钟。在我试图确定16MHZ和8MHZ中的digitalWrite()所需的时钟周期数。在16MHZ中,我得到8us / 64clk。但是在8MHZ中,我得到0us / 0clk。
cyphunk 2012年

1

由于添加到源代码中的每一行代码都会对性能产生影响,并且可能会更改所应用的优化。所做的更改应该是执行任务所需的最低要求。

我刚刚找到一个Atmel Studio插件,称为“带注释的程序集文件调试器”。http://www.atmel.com/webdoc/aafdebugger/pr01.html似乎逐步浏览了实际生成的汇编语言,而可能很乏味的将向您确切显示正在发生的事情。您可能仍然需要解码每条指令需要多少个周期,但它会比其他发布的选项更接近。

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

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

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


1

您可以使用内置计时器之一。在块之前获取为prescaller = 1和TCNT = 0设置的所有内容。然后,在块之前的行上启用计时器,并在块之后的行上禁用计时器。现在,TCNT将保留该块所花费的周期数,减去启用和禁用代码的固定周期。

请注意,TNCT将在16位定时器的65535个时钟周期后溢出。您可以使用溢出标志使运行时间加倍。如果仍然需要更长的时间,则可以使用预分频器,但分辨率会降低。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.