Ich habe versucht, den folgenden C-Code zu kompilieren:
period = TCNT0L;
period |= ((unsigned int)TCNT0H<<8);
Der Assembler-Code, den ich bekomme, ist der folgende:
period = TCNT0L;
d2: 22 b7 in r18, 0x32 ; 50
d4: 30 e0 ldi r19, 0x00 ; 0
d6: 30 93 87 00 sts 0x0087, r19
da: 20 93 86 00 sts 0x0086, r18
period |= ((unsigned int)TCNT0H<<8);
de: 44 b3 in r20, 0x14 ; 20
e0: 94 2f mov r25, r20
e2: 80 e0 ldi r24, 0x00 ; 0
e4: 82 2b or r24, r18
e6: 93 2b or r25, r19
e8: 90 93 87 00 sts 0x0087, r25
ec: 80 93 86 00 sts 0x0086, r24
Also statt 4 Anweisungen werden es 11!
Ich habe versucht, die Optimierungsoptionen O1, O2, O3 und Os auszuwählen. Das Ergebnis ist dasselbe (außer dass diese O3
Option diesen Code überhaupt wegoptimiert).
Ich könnte den Quellcode folgendermaßen schreiben:
period = TCNT0L | ((unsigned int)TCNT0H<<8);
Ich werde kleineren, aber immer noch nicht optimalen Code bekommen:
de: 22 b7 in r18, 0x32 ; 50
e0: 34 b3 in r19, 0x14 ; 20
e2: 93 2f mov r25, r19
e4: 80 e0 ldi r24, 0x00 ; 0
e6: 82 2b or r24, r18
e8: 90 93 87 00 sts 0x0087, r25
ec: 80 93 86 00 sts 0x0086, r24
Ich habe jedoch keine Garantie mehr, dass auf das untere Byte zuerst zugegriffen wird (dies ist eine wesentliche Voraussetzung, um das 16-Bit-Lesen korrekt zu halten). Und dennoch enthält der Code viele zusätzliche unnötige Anweisungen.
Kann ich die Compileroptionen ändern und/oder den Quellcode ändern, um ihn zu verbessern? Ich würde vermeiden, zum Assembler zu gehen.
UPDATE1:
Ich habe den von @caveman vorgeschlagenen Code ausprobiert:
((unsigned char*)(&period))[0] = TCNT0L;
((unsigned char*)(&period))[1] = TCNT0H;
Aber das Ergebnis ist auch nicht sehr gut:
((unsigned char*)(&period))[0] = TCNT0L;
dc: 82 b7 in r24, 0x32 ; 50
de: e6 e8 ldi r30, 0x86 ; 134
e0: f0 e0 ldi r31, 0x00 ; 0
e2: 80 83 st Z, r24
((unsigned char*)(&period))[1] = TCNT0H;
e4: 84 b3 in r24, 0x14 ; 20
e6: 81 83 std Z+1, r24 ; 0x01
Eine Methode besteht darin, direkte Lasten auf die Hälften der Periode zu verwenden. Während dies in C kompliziert aussieht, erzeugt es normalerweise eine sehr enge Assemblierung, dh 2 Ladevorgänge und 2 Speichervorgänge.
((uint8_t*)(&period))[0] = TCNT0L;
((uint8_t*)(&period))[1] = TCNT0H;
Manchmal kann die Verwendung der Array-Mathematik Probleme verursachen, also könnten Sie Folgendes versuchen:
*((uint8_t*)(&period)) = TCNT0L;
*((uint8_t*)(&period) + 1) = TCNT0H;
Dies erzeugt tatsächlich optimalen Code. Sehen Sie sich an, wie 12 Bytes verwendet werden.
((unsigned char*)(&period))[0] = TCNT0L;
dc: 82 b7 in r24, 0x32 ; 50
de: e6 e8 ldi r30, 0x86 ; 134
e0: f0 e0 ldi r31, 0x00 ; 0
e2: 80 83 st Z, r24
((unsigned char*)(&period))[1] = TCNT0H;
e4: 84 b3 in r24, 0x14 ; 20
e6: 81 83 std Z+1, r24 ; 0x01
Wenn Sie dies mit Assembler gemacht haben, scheint es wahrscheinlich besser zu sein, es so zu machen. Es sind auch 12 Bytes, also sind sie gleichwertig.
dc: 82 b7 in r24, 0x32 ; 50
de: 80 93 86 00 sts 0x0086, r24
e2: 84 b3 in r24, 0x14 ; 20
e4: 80 93 87 00 sts 0x0087, r24
Wenn ich "äquivalent" sage, meine ich natürlich die Codegröße. Wenn die Zeit wichtiger ist, dann müssen Sie sich die Zyklen ansehen. In diesem Fall sieht es so aus, als hätte die Assembly-Version 6 Zyklen und die Compiler-Version 8 Zyklen.
In meinem avr-gcc 5.4.0 period = TCNT1;
scheint simple for attiny841 den Code wie folgt auszugeben:
in r24,0x2c
in r25,0x2d
sts 0x0110,r25
sts 0x010f,r24
Es scheint, dass der Compiler bereits weiß, wie auf 16-Bit-Register zugegriffen werden muss, und daher ist der obige Code sicher.
Der avr-Zweig des gcc ist im Allgemeinen nicht sehr gut, selbst bei einfachen Optimierungen wie den Beispielen in der Frage, aber ein Upgrade der Version von avr-gcc hilft oft.
Ein weiteres Problem ist, dass spätere gccs und spätere avr-libcs tatsächlich den Zugriff auf TCNT0 als einzelnes 16-Bit-Register unterstützen könnten - was dem in der Frage verwendeten gcc zu fehlen scheint.
uint32_t
. In diesen Fällen könnte der Compiler manchmal mit 0-Registern davonkommen (verwenden Sie das temporäre Register!), aber schiebt vier davon auf den Stapel und legt sie bei der Rückkehr zurück. Für diese Spezialfälle habe ich ein asm-Makro parat.Wenn Sie bereit sind, einen Stift zu verschwenden, könnten Sie eine 1-Befehl/2-Zyklus-Erfassung des TCNT erhalten, wenn die ISR aufgerufen wird, indem Sie die Output Capture Unit verwenden.
1
.TNCT
Wert aus dem ICR
Register.Wenn Sie mit Zykleneinsparungen hart werden wollen, können Sie diesen Zugriff auf TCNT auf einen einzigen Zyklus in der ISR reduzieren!
Sie können sich die Tatsache zunutze machen, dass das High-Byte des TCNT-Registers gepuffert wird, wenn das Low-Byte gelesen wird.
Wenn Sie also ein Register (z. B. r16) für diese Aufgabe vorbelegt haben ...
register unsigned char tcnt_low_byte asm("r16");
... dann dieses Register mit dem Low-Byte TCNT
der ISR so gefüllt ...
R16 = TCNTL;
... die auf den 1-Zyklus herunterkompiliert werden sollte ...
IN R16,TCNTL
TCNT
...dann könnten Sie später den gesamten Schnappschuss- Wert so im Vordergrund auslesen ....
period = (TCNTH << 8)| R16;
Stellen Sie nur sicher, dass Sie lesen TCNTH
, bevor Sie auf andere 16-Bit-Timer-Register zugreifen, da alle dieses Temp-Temp-Register gemeinsam nutzen.
Die gesamte Arbeit, die in der ISR geleistet wird, ist nur eine einzige, in R16, TCNTL
die 1 Zyklus ist.
Das OP hat nicht angegeben, wie er dem Vordergrundprozess signalisieren würde, dass eine ISR stattgefunden hat, aber wenn er vorab geladen period
und 0
dann nach einer Änderung gesucht hat, ist zusätzliche Arbeit erforderlich ...
0
in das 16-Bit-Register vorladen TEMP
(Sie können dies tun, indem Sie a 0
in ein beliebiges 16-Bit-Register schreiben).0
in R16
.Dann können Sie abfragen, ob die ISR passiert ist mit...
x=TCNTH
if (x || R16) {
period=(x<<8 | R16)
// Process new period capture here...
}
period
(nicht gezeigt)? Antwort aktualisiert für den Fall, wenn sein Mechanismus den Zeitraum auf voreinstellen 0
und dann abfragen sollte.
Golaž
Roman Matwejew
Golaž
Roman Matwejew
PeterJ
0x00FF
Stelle0x0100
, an der Sie vielleicht am Ende lesen0x01FF
.Roman Matwejew
PeterJ
Scott Seidmann