Amateurfunk verbindet die Welt

Jonglieren mit Bits

Erstellt: DL6GL, 05.12.2013, letzte Änderung 27.01.2022

Zumeist werden Bytes, Integer usw. als Zahlen zum Zählen und Rechnen hantiert, wobei z.B. Bytes die Dezimalzahlen 0 bis 255 aufgrund der Länge von 8 Bits darstellen können. Bisweilen ist aber das Bitmuster interessanter, also die jeweilige Position von Nullen und Einsen. Gezählt wird die Position von rechts nach links ab Position 0. Ein gesetztes Bit 0 (least significant bit, LSB) in einem Byte ist dann "00000001", ein gesetztes Bit 7 (most significant bit, MSB) ist "10000000".

Immer wenn an einem AVR-Port z.B. Taster abzufragen oder einzelne LEDs ein- oder auszuknipsen sind, ist es praktisch, dem AVR-Port ein Byte und jedem Taster bzw. jeder LED ein Bit innerhalb dieses Bytes zuzuordnen. Denn die Ports von 8 Bit-AVR haben in der Regel 8 Pins. Bei kleinen AVR sind es bisweilen weniger, da entsprechende IC-Beinchen fehlen.


1 Bits und Bytes an AVR-Ports

1.1 Port-Pins als Input (lesen von Eingangsinformationen high/low)

Das Konfigurieren von Tastern in BASCOM, z.B. an Port D, erfolgt so (es geht auch mit "Config")

DDRD = &B00000000                                 'PortD = input
PORTD = &B11111111 'Activate Pullup
Key_port Alias PIND...............................'Alias for input port D, must be "PIN"

"DDRD" bedeutet Data Direction Register von Port D. Ein Wert 0 für eines der 8 Register 0...7 macht den entsprechenden Pin 0...7 zu einem Eingang, ein Wert 1 zu einem Ausgang. Das kann bunt gemischt werden. Hier ist der gesamte Port D als Eingang konfiguriert.

"PORTD" definiert das Data Register von Port D. Da dieser mit "DDRD = &B00000000" als Eingang konfiguriert wurde, wird mit einer 1 ein interner Pullup-Widerstand nach +Vcc aktiviert. Der jeweilige Pin ist also über den Pullup-Widerstand an +Vcc hochgelegt. Sind im DDRx, hier x=D, nur einige Pins mit 0 als Eingang konfiguriert, werden auch nur für diese mit einer 1 in PORTx die Pullups aktiviert. Grundsätzlich sollten nicht benutzte Pins als Eingang mit Pullup konfiguriert werden. Offene AVR-Eingänge sind hochohmig, können also Störsignale aus der Umgebung aufnehmen, die ein Programm ggf. undefiniert zum Stolpern oder Abstürzen bringen.

Leichter lesbar, insbesondere bei gemischten Bit-Zuordnungen, lässt sich das Byte in der Bit-Schreibweise mit einem Underscore "_" als zwei Halbbytes schreiben, etwa
DDRD = &B00011101 ist identisch mit DDRD = &B0001_1101.

Der Zustand von Input-Anschlüssen wird mit "PIN" abgefragt, hier also mit "PIND" für den Input-Port D in der Alias-Zuweisung.

Die Taster schalten nach GND, also gedrückter Taster = low, sonst high über den internen Pullup.

Einzelne Taster werden über die Bit-Position in Key_port angesprochen, z.B.
Das Bit "Key_port.0" ist der Taster an Port D.0
Das Bit "Key_port.7" ist der Taster an Port D.7.

Den einzelnen Tastern kann man auch Namen zuordnen, z.B.

Const Key_up = 0                                  'Up-key at Port D.0
Const Key_down = 1 'Down-key at Port D.1

Die Key-port-Bits bekommen dann sprechende Namen, z.B.
Das Bit "Key_port. Key_up" ist der Up-Taster an Port D.0 (statt "Key_port.0")


1.2 Port-Pins als Output (Setzen von Ausgangsinformationen high/low)

Soll der AVR Ausgangssignale - high = Vcc oder low = GND - erzeugen, etwa für LED, wird der Port als Output konfiguriert, z.B. Port C:

DDRC = &B11111111                                 'PortC = output
LED_port Alias PORTC..............................'Alias for LED port C, must be "PORT"

Das Data Direction Register DDRC konfiguriert hier mit den Werten 1 Port C als Ausgang.
Mit einem Befehl, etwa "PORTC.2 = 1" bzw. mit dem Alias "LED_port.2 = 1", wird der Pin 2 an Port C auf +Vcc (high) geschaltet, mit "... =0" entsprechend auf GND (low).

Der Zustand von Output-Anschlüssen wird mit "PORT" gesetzt, hier also mit "PORTC" für den Output-Port C in der Alias-Zuweisung.

In den o.a. Zuweisungen "DDRx = ..." wurde die binäre Schreibweise gewählt. So ist die bitweise Zuordnung erkennbar. "Wahre Könner"  verschleiern das mit Hex- oder Dezimalzahlen, z.B. wäre dann Binär "11111111" dezimal "255" oder hex "FF".


1.3 Zuweisen von sprechenden Bezeichnungen mit Alias

Der Vorteil der Alias-Zuweisung auf die Ports ist, dass im Programm nur noch mit dem besser lesbaren Aliasnamen gearbeitet wird und nur in einer Zeile im Erklärungsteil die Portzuordnung erfolgt. "Alias" ist eine Anweisung für den Compiler, der die sprechende Bezeichnung, etwa "LED_port", beim Compilieren der Zuweisung "LED_port Alias PORTC" folgend wieder auf auf die tatsächlichen AVR-Anschlüsse "PORTC" setzt. Das geht mit einem ganzen Port wie auch mit einzelnen Anschluss-Pins, etwa "LED_SWR Alias PORTC.3". Eine feine Sache.


2 Bit-Arithmetik

Es ist nicht immer elegant, einzelne Bits in den Taster- oder LED-Ports anzusprechen, sondern das gesamte Port-Byte zu betrachten. BASCOM wie z.B. auch C bieten vier logische Verknüpfungen.


2.1 Logisches AND (Konjunktion)

Wirkungsweise: Ergebnis = 1, wenn alle Inputs 1, sonst = 0.

X Y X AND Y      
0 0 0   Beispiel für ein Byte
0 1 0   X 01001110
1 0 0   Y 11100010
1 1 1   X AND Y 01000010


2.2 Logisches OR (Disjunktion, einschließendes Oder)

Wirkungsweise: Ergebnis = 1, wenn irgendein Input 1 (entweder oder-oder beide), sonst = 0.

X Y X OR Y      
0 0 0   Beispiel für ein Byte
0 1 1   X 01001110
1 0 1   Y 11100010
1 1 1   X OR Y 11101110


2.3 Logisches XOR (Exklusives Oder)

Wirkungsweise: Ergebnis = 1, wenn Inputs unterschiedlich sind (entweder oder-nicht beide), sonst = 0.

X Y X XOR Y      
0 0 0   Beispiel für ein Byte
0 1 1   X 01001110
1 0 1   Y 11100010
1 1 0   X XOR Y 10101100


2.4 Logisches NOT (Komplement)

Wirkungsweise: Kehrt den Wert um 0⇒1, 1⇒0.

X NOT X   Beispiel für ein Byte
0 1   X 01001110
1 0   NOT X 10110001


3 Bit-Maskierung

Mit den logischen Operationen 2.1 bis 2.3 (AND, OR, XOR) lassen sich nun interessante Maskierungen anstellen, die mit einem einzigen Statement einzelne Bits manipulieren. Ein Paradebeispiel hierzu ist die Tastenentprellung nach der Dannegger-Methode, siehe "Taster mit BASCOM" auf dieser Website.


3.1 Zurücksetzen (Reset) von Bits mit AND

Bestimmte Bits können auf 0 gesetzt werden mit einem logischen AND.

  • X AND 1 = X (unverändert), X AND 0 = 0
  • Ein AND mit einer "0" in der Maske setzt das zu maskierende Bits auf "0" (Reset, grün), wenn es "1" war
  • Ein AND mit einer "1" in der Maske verändert das zu maskierende Bit nicht.
0 1 0 0 1 0 1 0 Zu maskierendes Byte
AND  
1 1 1 1 0 0 0 0 Maske gelb: Reset Bits 0...3
=  
0 1 0 0 0 0 0 0 Ergebnis grün: Reset


3.2 Abfragen von Bits mit AND

Wenn wir nun genau ein Bit in der Maske auf "1" setzen, alle anderen auf "0",

  • ergibt sich für das mit "1" maskierte Bit auch "1", wenn es gesetzt war, sonst 0,
  • werden alle anderen Bits auf "0" gesetzt (Reset).
0 1 0 0 1 0 1 0 Zu maskierendes Byte
AND  
0 0 1 0 0 0 0 0 Maske gelb: Check Bit 5
=  
0 0 0 0 0 0 0 0 Ergebnis grün: Bit war nicht gesetzt

 

0 1 1 0 1 0 1 0 Zu maskierendes Byte
AND  
0 0 1 0 0 0 0 0 Maske gelb: Check Bit 5
=  
0 0 1 0 0 0 0 0 Ergebnis grün: Bit war gesetzt

Mit der "AND"-Funktion können einzelne Bits oder Bitfolgen von AVR-Registern mit entsprechenden Masken abgefragt werden, etwa in den Datenblättern zu den Registern dokumentierte Statuscodes.


3.3 Setzen (Set) von Bits mit OR

Bestimmte Bits können auf 1 gesetzt werden mit einem logischen OR

  • X OR 1 = 1, X OR 0 = X (unverändert)
  • Ein OR mit einer "1" in der Maske ergibt immer "1" (Set, grün)
  • Ein OR mit einer "0" in der Maske verändert das zu maskierende Bits nicht.
0 1 0 0 1 0 1 0 Zu maskierendes Byte
OR  
0 0 0 0 1 1 1 1 Maske gelb: Setze Bits 0...3
=  
1 1 1 0 1 1 1 1 Ergebnis grün: Bits gesetzt


3.4 Invertieren (Toggle) von Bits mit XOR

Mit dem exklusiven Oder XOR (entweder oder, nicht beide) können Bits von 0 nach 1 und von 1 nach 0 umgedreht werden. Mit NOT werden alle Bits umgedreht (invertiert), hier erfolgt es gezielt für die in der Maske vorgegebenen Bits.

  • 1 XOR 1 = 0 XOR 0 = 0
  • 1 XOR 0 = 0 XOR 1 = 1 (entweder oder, nur eins von beiden)
  • Ein XOR mit der Maske 1 ergibt 0, wenn das zu maskierende Bit 1 war
  • Ein XOR mit der Maske 1 ergibt 1, wenn das zu maskierende Bit 0 war
0 1 0 0 1 0 1 0 Zu maskierendes Byte
XOR  
0 0 0 0 1 1 1 1 Maske gelb: Invertiere Bits 0...3
=  
0 1 0 0 0 1 0 1 Ergebnis grün: Bits 0...3 invertiert


4  Halbbytes (Nibbles)

Bisweilen kann es vorkommen, dass Informationen innerhalb eines Bytes aufgeteilt werden sollen, etwa bei Ansteuerung von (8 Bit breiten) Ports oder von LCD oder auch zum Packen von zwei unabhängigen Werten innerhalb eines Bytes. Den Datentyp "Nibble" (=Halbbyte) gibt es aber in BASCOM nicht. Mit dem o.a. logischen And und dem Verschieben von Bits geht es dennoch.
Dazu brauchen wir zwei Hilfsbytes.

Dim bytData As Byte                                 'Data byte
Dim bytTmp0 As Byte 'Temp byte
Dim bytTmp1 As Byte 'Temp byte

bytData = 234 '=&B1110_1010

'Get Hi nibble of bytData
bytTmp0 = bytData And &B1111_0000 'Mask out Hi nibble
' Result: bytTmp0 = &B1110_0000
Shift bytTmp0, Right, 4 'Shift Hi nibble 4 bits to Lo nibble
' 'Result: bytTmp0 = &B0000_1110
'Get Lo nibble of bytData
bytTmp1 = bytData And &B0000_1111 'Mask out Lo nibble
' Result: bytTmp1 = &B0000_1010
'Recombine nibbles to bytData
Shift bytTmp0, Left, 4 'Shift Lo nibble 4 bits to Hi nibble
' Result: bytTmp0 = &B1110_0000
bytData = bytTmp0 + bytTmp1 'Result: bytData = &B1110_1010

Hier wurde wieder eine optische Zerlegung der Bit-Darstellung in zwei Nibbles mit dem Underscore "_" vorgenommen - alleine für das menschliche Auge, für den Compiler ohne Bedeutung .

Wollten wir zwei Datenwerte in ein Byte packen, ginge der Vorgang umgekehrt, also Setzen der zwei Hilfsbytes, dann Shift eines der (Halb)Bytes in das High Nibble und schließlich Addition der zwei Bytes (Nibbles). Dabei ist zu beachten, dass ein 4 Bit breites Halbbyte nur Zahlen von 0...24 - 1 = 15 dezimal hantieren kann. Mit den Werten im obigen Beispiel würden wir "1110" = 14 dezimal mit "1010" = 10 dezimal zu "1110_1010" = 234 dezimal packen.


5  Der Variablentyp Bit

Bisher haben wir einzelne Bits in Variablen wie Bytes betrachtet. BASCOM kennt aber auch den Variablentyp Bit. Ihm kann man die Werte 0 oder 1 zuordnen, also etwa die Entscheidung "Ja" oder "Nein" oder "ON" oder "OFF". Wenn es nur um solche Zustände geht und es gilt Speicherplatz zu sparen, sind wir damit goldrichtig. Denn BASCOM speichert Bit-Variablen tatsächlich als Bits. Im eigentlich Byte-organisierten SRAM-Speicher werden die mit Bit deklarierten Variablen nacheinander in einer Byte-Zelle abgelegt. Ein SRAM Byte mit 8 einzelnen Bit-Variablen belegt, dann weiter in einem anderen SRAM-Byte. Nachzusehen im .rpt-File des Compilers.

Ich habe mir allerdings angewöhnt, statt der zufälligen Verteilung von Bits in noch freie Byte-Speicherzellen durch den Compiler, jeweils in einem Funktionsblock zu verwendende Bits gezielt in einem Kontroll-Byte anzuordnen und die Namen mit einem Alias zu vergeben. Das geht nicht nur wie oben in Abschnitt 1 beschrieben mit AVR-Ports,  etwa so,  hier Bits 0...4 von Byte bytEncCtrl:

'Variables Encoder ------------------------------------------------------------
Dim bytEncCtrl As Byte 'Contains encoder control bits
bitLC_changed Alias bytEncCtrl.0 '=1: L/C or Hi/Lo pass changed
bitC_reset Alias bytEncCtrl.1 'C manually resetted
bitL_reset Alias bytEncCtrl.2 'L manually resetted
bitHL_pass Alias bytEncCtrl.3 'High pass(1), low pass(0)
bitHL_old Alias bytEncCtrl.4 'High/Low pass before change

Das nachfolgende Programm hantiert dann die mit Alias zugeordneten Bit-Variablen, etwa "bitHL_old".

Aber nicht nur zum Speicher sparen, etwa mit ATtiny-Controllern, sind Bit-Variablen praktisch. Mit einem simplen "Toggle" lässt sich ein Bit "umdrehen", also invertieren. Aus 1 wird 0, aus 0 wird 1 oder aus "On" wird "Off". Das geht mit anderen Variablen wie Byte, Word usw. so nicht. Die kann BASCOM zwar auch toggeln, dabei werden aber alle Bits invertiert.
Ein Byte mit dem Wert 1 (dez) = &B0000_0001 ist getoggelt &B1111_1110 = 254 (dez),
ein Byte mit dem Wert 0 (dez) = &B0000_0000 ist getoggelt &B1111_1111 = 255 (dez).

Einschränkungen gibt es allerdings auch, z.B.: Das Ergebnis einer Function kann kein Bit sein, etwa "Declare Function Test(byVal bytInput As Byte) As Bit" geht nicht. Das Ergebnis muss mindestens "Byte" sein.