Arduino / AVRのコードのクロックサイクルを監視しますか?


11

コードのブロックを監視し、コードがArduinoやAVR atmelプロセッサで実行したプロセッサクロックサイクル数を特定することは可能ですか?または、コード実行の前後に経過したマイクロ秒を監視する必要がありますか?注:「このコードがCPUから必要とするクロックサイクル数」と同じくらい、リアルタイム(経過した実際の秒数など)には関係ありません。

私が思いつくことができる現在の解決策はtime.cからです:

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

lighting.cは以下を追加します。

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

このアカウントにより、通過したマイクロ秒を監視して通過したクロックサイクルを計算し、それをmicrosecondsToClockCycles()に渡すことができます。私の質問は、より良い方法はありますか?

補足:AVRのパフォーマンス監視のための優れたリソースはありますか?lmgtfy.comやさまざまなフォーラムの検索では、タイマーの調査以外に明らかな結果は得られません

ありがとう

回答:


6

最も簡単な方法は、コードがタイミングをとるコードを実行する前にピンをプルアップし、何でも実行した後にローにプルすることです。次に、コードループを作成(またはシングルショットモードでメモリ付きのデジタルオシロスコープを使用)し、スコープを設定してからピンで固定します。パルスの長さは、コードの実行に加えて、ピン状態の変更から1クロックサイクルかかった時間を示します(100%確実ではないので、1サイクルかかると思います)。


ありがとう。はい、これがおそらく最も正確なソリューションであることがわかります。私はまだ、コード内の少なくとも一般的なサイクル使用分析さえも提供するコードにこだわっています。これを使用していくつかのテストツールを作成します。コードとそれに関連するすべてが現在のAtmel CPUで実行されている効率に基づいて、最大許容実行時間などのパラメーターの上限を設定すると便利です使用
cyphunk 2009年

4

「モニター」とはどういう意味ですか?

小さなアセンブリコードのAVRのクロックサイクルをカウントすることは難しくありません。

コードを実行する前にポートを設定し、後でリセットして、ロジックアナライザーまたはオシロスコープで監視してタイミングを取得することもできます。

そして、あなたはあなたが言うように、あなたはまた高速に動くタイマーから時間を読むことができました。


モニターとは、コードによって使用されるサイクル数を決定することを意味します。次のようなものです(注、コードのフォーマットはおそらくコメントエンジンによってフラット化されます):clocks = startCountingAtmegaClocks(); for ... {for ... {digitalRead ...}} Serial.print( "num of usedCycle:"); Serial.print(currentCountingAtmegaClocks()-クロック、DEC);
cyphunk 2009年

しかし、ええ、あなたの反応は私が私の選択肢があると想定したものです。私はアセンブラが手で取るクロックサイクルを計算することができる場合、私は、誰かがおそらくすでにこのプログラム的に行うにはいくつかの素晴らしいコード書かれていることを前提とし、推測
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

最初のループもほとんどのループよりも経過クロックサイクルが短いのはなぜか、他のすべてのループが2つのクロックサイクル間で切り替わるのは理にかなっていると思います。

コード:

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

経過時間は合計実行時間よりも長くなります。1秒が経過すると、クロックは平均で40kから44kに増加します。これは1秒後数ミリ秒で一貫して発生し、経過したクロックは少なくとも次の10秒間は約44kのままです(これ以上テストしていません)。これが、監視が有用または必要な理由です。おそらく、効率の低下は、シリアルの構成またはバグに関係していますか?または、コードがメモリを適切に使用しておらず、パフォーマンスに影響するリークなどがある可能性があります。


それから何年経っても、コードでより正確にクロックを表示できるものが欲しいです(オシロスコープに並ぶ)。私は、16MHZと8MHZの両方でdigitalWrite()に必要なクロックサイクル数を決定しようとしています。16MHZでは8us / 64clkになります。しかし、8MHZでは0us / 0clkになります。
cyphunk 2012

1

ソースに追加されたすべてのコード行はパフォーマンスに影響を与え、適用される最適化を変更する可能性があるためです。変更は、タスクを実行するために最低限必要なものでなければなりません。

「Annotated Assembly File Debugger」というAtmel Studioプラグインを見つけました。http://www.atmel.com/webdoc/aafdebugger/pr01.html実際に生成されたアセンブリ言語をステップ実行しているように見えますが、おそらく退屈で何が起こっているのかを正確に示すでしょう。それでも、各命令にかかるサイクル数をデコードする必要があるかもしれませんが、他のいくつかのポストされたオプションよりもはるかに近くなります。

プロジェクトの出力フォルダーでわからない人のために、LSS拡張子が付いたファイルがあります。このファイルには、すべての元のソースコードがコメントとして含まれており、各行の下には、そのコード行に基づいて生成されたアセンブリ言語があります。LSSファイルの生成をオフにできるので、次の設定を確認してください。

プロジェクトのプロパティ| ツールチェーン| AVR / GNU共通| 出力ファイル

チェックボックス ".lss(lssファイルを生成)


1

組み込みのタイマーの1つを使用できます。ブロックの前にprescaller = 1とTCNT = 0の設定をすべて取得します。次に、ブロックの前の行でタイマーを有効にし、ブロックの後の行でタイマーを無効にします。TCNTは、ブロックが使用したサイクル数を保持するようになり、有効化コードと無効化コードの固定サイクルは差し引かれます。

TNCTは、16ビットタイマーで65535クロックサイクル後にオーバーフローすることに注意してください。オーバーフローフラグを使用して、ランタイムを2倍にすることができます。それでも長く必要な場合は、プリスケーラーを使用できますが、解像度は低くなります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.