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 ein cmp
/ 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.