Bei der AVR-Programmierung werden Registerbits immer durch Verschieben von a 1
nach links an die entsprechende Bitposition gesetzt - und durch ein Einsenkomplement derselben gelöscht.
Beispiel: Für einen ATtiny85 könnte ich PORTB, b 4 wie folgt einstellen :
PORTB |= (1<<PB4);
oder löschen Sie es so:
PORTB &= ~(1<<PB4);
Meine Frage ist: Warum wird es so gemacht? Der einfachste Code ist ein Durcheinander von Bitverschiebungen. Warum werden Bits als Bitpositionen anstelle von Masken definiert?
Der E / A-Header für den ATtiny85 enthält beispielsweise Folgendes:
#define PORTB _SFR_IO8(0x18)
#define PB5 5
#define PB4 4
#define PB3 3
#define PB2 2
#define PB1 1
#define PB0 0
Für mich wäre es viel logischer, die Bits stattdessen als Masken zu definieren (wie folgt):
#define PORTB _SFR_IO8(0x18)
#define PB5 0x20
#define PB4 0x10
#define PB3 0x08
#define PB2 0x04
#define PB1 0x02
#define PB0 0x01
Also könnten wir so etwas machen:
// as bitmasks
PORTB |= PB5 | PB3 | PB0;
PORTB &= ~PB5 & ~PB3 & ~PB0;
um die Bits b 5 , b 3 und b 0 ein- bzw. auszuschalten. Im Gegensatz zu:
// as bit-fields
PORTB |= (1<<PB5) | (1<<PB3) | (1<<PB0);
PORTB &= ~(1<<PB5) & ~(1<<PB3) & ~(1<<PB0);
Der bitmask Code liest viel deutlicher: gesetzte Bits PB5
, PB3
und PB0
. Darüber hinaus scheint es Operationen zu sparen, da die Bits nicht mehr verschoben werden müssen.
Ich dachte, vielleicht wurde dies auf diese Weise getan, um die Allgemeinheit zu bewahren und den Portierungscode von einem n- Bit-AVR auf ein m- Bit (Beispiel 8-Bit auf 32-Bit) zu ermöglichen. Dies scheint jedoch nicht der Fall zu sein, da es #include <avr/io.h>
sich um Definitionsdateien handelt, die für den Ziel-Mikrocontroller spezifisch sind. Selbst das Ändern von Zielen von einem 8-Bit-ATtiny zu einem 8-Bit-Atmega (bei dem sich die Bitdefinitionen beispielsweise syntaktisch von PBx
zu ändern PORTBx
) erfordert Codeänderungen.
quelle
_BV(b)
anstelle von(1<<b)
macht die Dinge unnötig chaotisch. Ich definiere normalerweise Bit-Mnemonik mit_BV()
z#define ACK _BV(1)
.Antworten:
Nein überhaupt nicht. Die Verschiebungen erfolgen nur im C-Quellcode, nicht im kompilierten Maschinencode. Alle Beispiele, die Sie gezeigt haben, können und werden vom Compiler zur Kompilierungszeit aufgelöst , da es sich um einfache konstante Ausdrücke handelt.
(1<<PB4)
ist nur eine Möglichkeit, "Bit PB4" zu sagen.Für den menschlichen Programmierer ist es auch sinnvoll, die Bits nach ihrem Index (z. B. 5) und nicht nach ihrer Bitmaske (z. B. 32) zu benennen, da auf diese Weise fortlaufende Zahlen 0..7 verwendet werden können, um die Bits zu identifizieren, anstatt die umständliche Potenz von zwei (1, 2, 4, 8, .. 128).
Und es gibt noch einen anderen Grund (möglicherweise den Hauptgrund):
Die C-Header-Dateien können nicht nur für C-Code verwendet werden, sondern auch für Assembler-Quellcode (oder Assembler-Code, der im C-Quellcode enthalten ist). Im AVR-Assembler-Code möchten Sie definitiv nicht nur Bitmasken verwenden (die durch Bitverschiebung aus Indizes erstellt werden können). Für einige Assembler-Anweisungen zur AVR-Bitmanipulation (z. B. SBI, CBI, BST, BLD) müssen Sie Bitindizes als unmittelbaren Operator in ihrem Befehls-Op-Code verwenden.
Nur wenn Sie SFR-Bits anhand von Indizes identifizieren(nicht durch Bitmaske) Sie können solche Bezeichner direkt als unmittelbaren Operanden der Assembler-Anweisungen verwenden. Andernfalls mussten Sie zwei Definitionen für jedes SFR-Bit haben: eine Definition seines Bitindex (der z. B. als Operand in den oben genannten Assembler-Anweisungen zur Bitmanipulation verwendet werden kann) und eine Definition seiner Bitmaske (die nur für Anweisungen verwendet werden kann, bei denen das gesamte Byte verwendet wird manipuliert wird).
quelle
Vielleicht ist die Bitverschiebung nicht der einzige Anwendungsfall für die
PB*
Definitionen. Vielleicht gibt es andere Anwendungsfälle, in denen diePB*
Definitionen direkt und nicht als Verschiebungsbeträge verwendet werden. Wenn ja, dann würde das DRY- Prinzip Sie dazu veranlassen, einen Satz von Definitionen zu implementieren, der für beide Anwendungsfälle (wie diesePB*
Definitionen) verwendet werden kann, anstatt zwei verschiedene Sätze von Definitionen, die sich wiederholende Informationen enthalten.Zum Beispiel habe ich eine Anwendung geschrieben, die Messungen von bis zu 8 ADC-Kanälen durchführen kann. Es verfügt über eine Schnittstelle zum Starten einer neuen Messung, in der Sie mehrere Kanäle über ein 8-Bit-Feld angeben können, ein Bit für jeden Kanal. (Die Messungen werden parallel durchgeführt, wenn mehrere Kanäle angegeben sind.) Dann verfügt es über eine andere Schnittstelle, die die Messergebnisse für einen einzelnen Kanal zurückgibt. Eine Schnittstelle verwendet also die Kanalnummer als Verschiebung in ein Bitfeld und die andere Schnittstelle verwendet die Kanalnummer direkt. Ich habe eine einzelne Aufzählung definiert, um beide Anwendungsfälle abzudecken.
quelle