Wie kann ich eine lokale Variable an das rXX-Register von AVR binden?

Ich brauche eine programmierbare Pause mit möglichst hoher Präzision. Um dies zu erreichen, habe ich den folgenden GCC-Code:

void delay(unsigned char d){
  volatile unsigned char i=d;
  while(i>0) i--;
}

Was kompiliert wird zu:

1cc:    89 81           ldd r24, Y+1    ; 0x01
1ce:    81 50           subi    r24, 0x01   ; 1
1d0:    89 83           std Y+1, r24    ; 0x01
1d2:    89 81           ldd r24, Y+1    ; 0x01
1d4:    81 11           cpse    r24, r1
1d6:    fa cf           rjmp    .-12        ; 0x1cc <__vector_1+0x2c>

(Ich zeige nur den Loop-Core-Code). Dies führt dazu, dass die Genauigkeit 7 Zyklen beträgt, was nicht sehr akzeptabel ist. Ich sehe jedoch, dass der Compiler seine Arbeit nicht so schnell wie möglich gemacht hat: Wenn die iVariable r24-Register wäre, würde ich 3 Operationen sparen und der Code wäre fast doppelt so schnell.

Wie kann ich also dem Compiler mitteilen, dass ich diese Variable in einem Register haben möchte?

PS. Ich würde überlegen, mit programmierbarer Nummer auf nop's zu pausieren. Aber ich kann mir nicht vorstellen, wie dies erreicht werden kann. AVR hat keine Anweisungen zum Verzweigen zur berechneten Adresse. Soweit ich weiß, ist der Stack in AVR nicht direkt zugänglich (wenn ich den erforderlichen Wert auf den Stack schieben und die retAnweisung ausführen könnte, um zur benötigten Programmadresse zu springen - es ist auch eine knifflige Aufgabe, aber es wäre zumindest beträchtlich).

UPDATE Nachdem ich volatilezu registerKeyword gewechselt habe (wie in einer der Antworten beschrieben) habe ich folgenden Code:

 14e:   81 50           subi    r24, 0x01   ; 1
 150:   f1 f7           brne    .-4         ; 0x14e <__vector_6+0x1c>

Also habe ich den Zyklus von 7 auf 2 Zyklen reduziert. Was viel besser ist, als ich erwarten konnte.

Warum hast du es geschafft volatile? Das zwingt den Compiler so ziemlich dazu, einen Speicherort zu verwenden und bei jeder Schleifeniteration darauf zuzugreifen. Versuchen Sie es registerstattdessen.
@DaveTweed das hat geholfen! Der Zyklus wird nur 2 Befehle lang. Früher habe ich volatileden Compiler daran gehindert, den Code "herauszuoptimieren", schien nichts zu tun. Würden Sie eine Antwort schreiben, die ich abstimmen und akzeptieren könnte?
Volatile ist normalerweise kein gutes Schlüsselwort, um einen Compiler daran zu hindern, Code so zu optimieren. Viele Compiler verfügen über #pragma-Direktiven, um die Optimierung für kleine Codeabschnitte zu deaktivieren.

Antworten (2)

Deklarieren Sie Ihre Variable nicht als volatile. Das zwingt den Compiler so ziemlich dazu, einen Speicherort zu verwenden und bei jeder Schleifeniteration darauf zuzugreifen. Verwenden Sie registerstattdessen.

Funktioniert nicht als Verzögerung, da der Compiler die Schleife vollständig wegoptimiert.
@TurboJ: Das OP sagt, du liegst falsch. Außerdem können Sie den verwendeten Optimierungsgrad steuern und bei einigen Compilern für einzelne Funktionen variieren. Die insgesamt bessere Lösung ist jedoch die Verwendung von Hardware-Timern für präzises Timing.

Der herkömmliche Weg, um das zu erreichen, was Sie wollen, besteht darin, die Verzögerungs-Subroutine in der nativen Assemblersprache von Hand zu codieren. Fast alle Entwicklungs-Tool-Sets, die C-Compiler sind, haben die Fähigkeit, Assembler-Sprachmodule in den Build aufzunehmen.

Ein gängiger Trick erfahrener Programmierer besteht darin, zunächst die Verzögerungsroutine in C-Code zu erstellen, so wie Sie es getan haben. Wenn Sie sich dann den kompilierten Maschinencode ansehen, erhalten Sie eine Vorlage, wie Sie dieselbe Routine in Assembler-Code schreiben können, und können dann die kritischen Abschnitte optimieren. Der Vorteil dieses Ansatzes besteht darin, dass der Compiler Ihnen auf angenehme Weise "zeigt, wie" der Einstieg und der Ausstieg in die/aus der Unterroutine so eingerichtet werden, dass sie mit dem Aufruf aus dem Haupt-C-Code kompatibel sind.

Ich wäre mir der Tatsache bewusst, dass mein Assembler-Code falsch wird, wenn der Compiler seine Arbeit aus irgendeinem Grund auf andere Weise erledigt, z. B.: Hinzufügen einer weiteren lokalen Variablen, Ändern von Optimierungsmethoden oder etwas anderem.
@RomanMatveev - Das ist in der Tat wahr. Ändern Sie ein Programm und erwarten Sie Änderungen im Verhalten. Es kommt jedoch die gleiche Überlegung zum Tragen, den gesamten Verzögerungscode auch im C-Code zu belassen. Das Ändern des Codedesigns könnte ändern, wie der Compiler die Registernutzung in der Subroutine sieht. Ändern Sie auch Compiler-Versionen, Optimierungsstufen oder sogar die Portierung auf einen anderen Compiler, und das erwartete Verhalten der C-Code-Verzögerungsroutine wird sich höchstwahrscheinlich von dem ändern, was Sie heute gesehen haben. Übergeben Sie die Routine an Assembler, und der Code ändert sich nicht, es sei denn, Sie entscheiden sich dafür, ihn zu ändern.
Beachten Sie, dass bestimmte Toolsets Standardprozesse zum Übergeben von Parametern an Unterroutinen und zum Zurücksenden von Rückgabewerten definieren. Es ist unwahrscheinlich, dass sich die Methoden dazu mit Compilerversionen oder Optimierungsstufen ändern, denn wenn sie sich ändern, wird der Kundenstamm wirklich verärgert, wenn ihre Assemblerroutinen versagen oder vorkompilierte Bibliotheken nicht richtig funktionieren, wenn sie mit einer aktuellen Kompilierung verknüpft werden.