Taktzyklen auf Code auf Arduino / AVR überwachen?

11

Ist es möglich, einen Codeblock zu überwachen und die Anzahl der Prozessortaktzyklen zu bestimmen, die der Code auf einem Arduino- und / oder AVR-Atmel-Prozessor ausgeführt hat? oder sollte ich lieber Mikrosekunden überwachen, die vor und nach dem Ausführen des Codes vergangen sind? Hinweis: Es geht mir nicht um Echtzeit (wie in, wie viele echte Sekunden vergangen sind), sondern um "Wie viele Taktzyklen benötigt dieser Code von der CPU?"

Die aktuelle Lösung, die ich finden kann, ist von time.c:

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

Wiring.c fügt hinzu:

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

Mit diesem Konto konnte ich Taktzyklen berechnen, die durch Überwachen der verstrichenen Mikrosekunden übergeben wurden, und diese dann an microsecondsToClockCycles () übergeben. Meine Frage ist, gibt es einen besseren Weg?

Nebenbemerkung: Gibt es gute Ressourcen für die Leistungsüberwachung des AVR? lmgtfy.com und verschiedene Forensuchen liefern keine offensichtlichen Ergebnisse, außer der Untersuchung von Timern

Vielen Dank

Cyphunk
quelle

Antworten:

6

Die einfachste Methode besteht darin, Ihren Code dazu zu bringen, einen Pin nach oben zu ziehen, bevor er den Code ausführt, den Sie zeitlich festlegen möchten, und ihn nach Abschluss des Vorgangs nach unten zu ziehen. Machen Sie dann die Codeschleife (oder verwenden Sie ein digitales Oszilloskop mit Speicher im Single-Shot-Modus) und legen Sie einfach das Zielfernrohr und dann den Pin fest. Die Länge des Impulses gibt an, wie lange es gedauert hat, den Code plus einen Taktzyklus nach dem Ändern des Pin-Zustands auszuführen (ich denke, es dauert einen Zyklus, nicht 100% sicher).

Kanake
quelle
Vielen Dank. Ja, ich kann sehen, dass dies wahrscheinlich die genaueste Lösung ist. Ich schmeiße immer noch nach Code, der mir zumindest eine allgemeine Analyse der Zyklusnutzung im Code ermöglichen würde. Ich werde dies verwenden, um einige Testtools zu erstellen, und es wäre schön, meine Obergrenzen für Parameter wie die maximal zulässige Laufzeit festzulegen, basierend darauf, wie effizient der Code + alles, was damit zusammenhängt, auf der aktuellen Atmel-CPU in ausgeführt wird Verwenden Sie
Cyphunk
4

Was meinst du mit "Monitor"?

Es sollte nicht schwierig sein, Taktzyklen für AVR für kleine Teile des Assembler-Codes zu zählen.

Sie können auch einen Port festlegen, bevor der Code ausgeführt wird, und ihn anschließend zurücksetzen und diesen mit einem Logikanalysator oder einem Oszilloskop überwachen, um das Timing zu ermitteln.

Und Sie können die Zeit auch von einem schnell laufenden Timer ablesen, wie Sie sagen.

Sternenblau
quelle
Mit Monitor meine ich die Anzahl der vom Code verwendeten Zyklen bestimmen. so etwas wie (Hinweis, die Formatierung des Codes wird wahrscheinlich durch die Kommentar-Engine abgeflacht): clocks = startCountingAtmegaClocks (); für ... {für ... {digitalRead ...}} Serial.print ("Anzahl der verwendeten Zyklen:"); Serial.print (currentCountingAtmegaClocks () - Uhren, DEC);
Cyphunk
Aber ja, Ihre Antwort ist das, was ich angenommen habe. Ich denke, wenn ich die Taktzyklen berechnen kann, die der Assembler von Hand nehmen würde, hat vielleicht jemand bereits einen netten Code geschrieben, um dies programmgesteuert zu tun
Cyphunk
3

Dies ist ein Beispiel für Arduino, das die Funktion clockCyclesPerMicrosecond () verwendet, um die übergebenen Uhren zu berechnen. Dieser Code wartet 4 Sekunden und gibt dann die seit dem Programmstart verstrichene Zeit aus. Die linken 3 Werte sind die Gesamtzeit (Mikrosekunden, Millisekunden, Gesamttaktzyklen) und die am weitesten rechts stehenden 3 sind verstrichene Zeiten:

Ausgabe:

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

Ich bin sicher, es gibt eine vernünftige Erklärung dafür, warum die ersten zu-Schleifen kürzere Taktzyklen hatten als die meisten und warum alle anderen Schleifen zwischen zwei Längen von Taktzyklen umschalten.

Code:

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);     

}

Nebenbemerkung: Wenn Sie die Verzögerung von 4 Sekunden entfernen, werden Sie die Auswirkungen von Serial.print () viel deutlicher sehen. Beachten Sie, dass hier 2 Läufe verglichen werden. Ich habe nur 4 nahe beieinander liegende Proben aus ihren jeweiligen Protokollen aufgenommen.

Lauf 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

Lauf 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

Die verstrichene Zeit erhöht sich über die Gesamtlaufzeit. Nach Ablauf einer Sekunde steigen die Uhren im Durchschnitt von 40.000 auf 44.000. Dies geschieht konstant einige Millisekunden nach 1 Sekunde und die verstrichenen Uhren bleiben mindestens die nächsten 10 Sekunden bei 44.000 (ich habe es nicht weiter getestet). Aus diesem Grund ist die Überwachung nützlich oder erforderlich. Vielleicht hat die verminderte Effizienz mit Konfiguration oder Fehlern in der Serie zu tun? Oder der Code verwendet den Speicher möglicherweise nicht richtig und weist ein Leck auf, das sich auf die Leistung usw. auswirkt.

Cyphunk
quelle
Viele Jahre später möchte ich immer noch etwas, das die Uhren mit Code genauer anzeigt (wie bei einem Oszilloskop). Ich versuche, die Anzahl der Taktzyklen zu bestimmen, die für ein digitalWrite () sowohl in 16 MHz als auch in 8 MHz erforderlich sind. In 16 MHz bekomme ich 8us / 64clk. Aber in 8 MHz bekomme ich 0us / 0clk.
Cyphunk
1

Da jede Ihrer Quelle hinzugefügte Codezeile sich auf die Leistung auswirkt und die angewendeten Optimierungen ändern kann. Die Änderungen sollten das Minimum sein, das zur Ausführung der Aufgabe erforderlich ist.

Ich habe gerade ein Atmel Studio-Plugin namens "Annotated Assembly File Debugger" gefunden. http://www.atmel.com/webdoc/aafdebugger/pr01.html Es scheint, als würde man durch die tatsächlich generierte Assemblersprache gehen, während wahrscheinlich mühsam Ihnen genau gezeigt wird, was passiert. Möglicherweise müssen Sie noch dekodieren, wie viele Zyklen für jede Anweisung erforderlich sind, aber es würde viel näher kommen als einige der anderen veröffentlichten Optionen.

Für diejenigen, die nicht wissen, im Ausgabeordner Ihres Projekts ist eine Datei mit einer LSS-Erweiterung. Diese Datei enthält Ihren gesamten ursprünglichen Quellcode als Kommentare. Unter jeder Zeile befindet sich die Assemblersprache, die basierend auf dieser Codezeile generiert wurde. Das Generieren der LSS-Datei kann deaktiviert werden. Überprüfen Sie daher die folgende Einstellung.

Projekteigenschaften | Toolchain | AVR / GNU Common | Ausgabedateien

Kontrollkästchen ".lss (lss-Datei generieren)

James
quelle
1

Sie können einen der eingebauten Timer verwenden. Richten Sie vor dem Block alles für prescaller = 1 und TCNT = 0 ein. Aktivieren Sie dann den Timer in der Zeile vor dem Block und deaktivieren Sie ihn in der Zeile nach dem Block. Der TCNT enthält nun die Anzahl der Zyklen, die der Block benötigt hat, abzüglich der festen Zyklen für den Aktivierungs- und Deaktivierungscode.

Beachten Sie, dass die TNCT nach 65535 Taktzyklen auf einem 16-Bit-Timer überläuft. Mit dem Überlauf-Flag können Sie die Laufzeit verdoppeln. Wenn Sie noch länger brauchen, können Sie einen Prescaler verwenden, erhalten jedoch eine geringere Auflösung.

Bigjosh
quelle