In einer Echtzeitanwendung¹ auf einem ARM Cortex M3 (ähnlich wie STM32F101) muss ich ein Stück des Registers eines internen Peripheriegeräts abfragen, bis es Null ist, und zwar in einer möglichst engen Schleife. Ich benutze Bitbanding, um auf das entsprechende Bit zuzugreifen. Der (Arbeits-) C-Code lautet
while (*(volatile uint32_t*)kMyBit != 0);
Dieser Code wird in den ausführbaren RAM auf dem Chip kopiert. Nach einigen manuellen Optimierungen² ist die Abfrageschleife auf Folgendes zurückzuführen, das ich auf 6 Zyklen eingestellt habe:
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 D1FC BNE 0x00600200
Wie kann die Wahlunsicherheit verringert werden? Eine 5-Zyklus-Schleife würde zu meinem Ziel passen: Das gleiche Bit so nahe wie möglich an 15,5 Zyklen abtasten, nachdem es auf Null gegangen ist.
Meine Spezifikation fordert die zuverlässige Erkennung eines niedrigen Impulses von mindestens 6,5 CPU-Taktzyklen; zuverlässig als kurz klassifizieren, wenn es weniger als 12,5 Zyklen dauert; und zuverlässig klassifizieren, solange es länger als 18,5 Zyklen dauert. Die Impulse haben keine definierte Phasenbeziehung zum CPU-Takt, was meine einzige genaue Zeitreferenz ist. Dies erfordert eine Abfrageschleife von höchstens 5 Takten. Eigentlich emuliere ich Code, der auf einer jahrzehntealten 8-Bit-CPU lief, die mit einem 5-Takt-Zyklus abrufen konnte, und was das tat, wurde zur Spezifikation.
Ich habe versucht, die Code-Ausrichtung durch Einfügen von NOP vor der Schleife in den vielen Varianten, die ich ausprobiert habe, auszugleichen, habe jedoch nie eine Änderung festgestellt.
Ich habe versucht, CMP und LDR zu invertieren, erhalte aber immer noch 6 Zyklen:
0x00600200 681A LDR r2,[r3,#0x00]
; we loop here
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 D1FC BNE 0x00600202
Dieser ist 8 Zyklen
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 681A LDR r2,[r3,#0x00]
0x00600204 2A00 CMP r2,#0x00
0x00600206 D1FB BNE 0x00600200
Aber dieser ist 9 Zyklen:
0x00600200 681A LDR r2,[r3,#0x00]
0x00600202 2A00 CMP r2,#0x00
0x00600204 681A LDR r2,[r3,#0x00]
0x00600206 D1FB BNE 0x00600200
¹ Messen, wie lange das Bit niedrig ist, in einem Kontext, in dem kein Interrupt auftritt.
² Der ursprünglich vom Compiler generierte Code verwendete r12 als Zielregister und fügte der Schleife 4 Codebytes hinzu, was 1 Zyklus kostete.
³ Die angegebenen Zahlen werden mit einem vermeintlich zyklengenauen Echtzeit- STIce-Emulator und seiner Emulator-Triggerfunktion beim Lesen an der Adresse des Registers erhalten. Früher habe ich den "States" -Zähler mit einem Haltepunkt in der Schleife ausprobiert, aber das Ergebnis hängt von der Position des Haltepunkts ab. Einzelschritt ist noch schlimmer: Es gibt immer 4 Zyklen für LDR, wenn das mindestens irgendwann bis zu 3 ist.
gcc -Os -mcpu=cortex-m3
?ldr
/cbz reg, end_of_loop
für die inneren abrollen und immer noch eincmp
/bnz
am unteren Rand. Dies würde Ihnen jedoch ein ungleichmäßiges Abfrageintervall geben, z. B. 1 von 8 Umfragen, falls dies von Bedeutung ist.Antworten:
Wenn ich die Frage richtig verstehe, müssen nicht unbedingt die Schleifenzyklen reduziert werden, sondern die Anzahl der Zyklen zwischen aufeinander folgenden Abtastwerten (dh LDR-Anweisungen). Es kann jedoch mehr als einen LDR pro Iteration geben. Sie können so etwas ausprobieren:
Der Abstand zwischen den beiden LDRB-Befehlen variiert, sodass die Abtastwerte nicht gleichmäßig verteilt sind.
Dies kann das Verlassen der Schleife etwas verzögern, aber anhand der Problembeschreibung kann ich nicht sagen, ob es wichtig ist oder nicht.
Ich habe zufällig Zugriff auf das zyklusgenaue M7-Modell, und wenn der Prozess Ihre ursprüngliche Schleife stabilisiert, läuft sie auf M7 in 3 Zyklen pro Iteration (dh LDR alle 3 Zyklen), während die oben vorgeschlagene Schleife in 4 Zyklen ausgeführt wird, aber jetzt gibt es sie zwei LDRs drin (also LDR alle 2 Zyklen). Die Abtastrate ist definitiv verbessert.
@Peter Cordes schlug in einem Kommentar vor , sich mit CBZ als Pause abzuwickeln .
Zugegeben, M3 wird langsamer sein, aber es ist immer noch einen Versuch wert, wenn es die Abtastrate ist, nach der Sie suchen.
Sie können auch überprüfen, ob LDRB anstelle von LDR (wie im obigen Code) etwas ändert, obwohl ich dies nicht erwarte.
UPD: Ich habe eine andere 2-LDR-Schleifenversion, die auf M7 in 3 Zyklen abgeschlossen ist, die Sie aus Interesse ausprobieren können (auch CBZ-Unterbrechungen ermöglichen ein einfaches Ausbalancieren der Pfade nach der Schleife):
quelle
r2
zu dem zu gehenr1
, aber 6 Zyklen von demr1
zu dem zu gehenr2
(aufgrund desb loop
dazwischen liegenden), wenn ich (höchstens) 5 möchte Zyklen zwischen den Probenahmen. Ein leicht zu behebendes Problem besteht darin, dass wirout
nach einer größeren Verzögerung nach dem Abtasten erreichen, wenn der Ausgangr1
Null ist, als wenn err2
Null ist. In anderen Nachrichten wurdeldrb
von einer Bit-Banding-Adresse Probleme verursacht, geändert zuldr
.nop
vorloop:
und 4nop
nachout_fast:
macht es so, dass aldr
nachout_slow:
Proben 10 Zyklen nach der Probe, die zuerst bei Null gesehen wurde, welcher der drei war. Meine Spezifikation (wie in der Frage formuliert) erfordert 13, und das ist trivial anzupassen. Problem 100% gelöst! Vielen Dank sowie an Peter Cordes für seinen Kommentar und B Degan für das erste Kopfgeld.out_fast
könnte vielleicht kompakter als 5nop
s sein, vielleicht nur einen anderenldr
ausführen oder vielleicht einenb
nächsten Befehl ausführen, wenn dies mehr Zyklen als ein NOP auf Ihrer CPU dauert, ohne die Verzweigungsvorhersage (falls vorhanden) zu verschmutzen.Sie können dies versuchen, aber ich bin misstrauisch, dass es die gleichen 6 Zyklen geben wird
quelle