Interaction Design WikiElectrical Engineering

Bit-Shifting

BitShifting ist eine Methode, bei welcher einzelne Bits nacheinander über einen Pin des Arduino “geshiftet” – d.h. herausgeschrieben – werden. Diese Funktion kann genutzt werden um z.B. mehrere LEDs mit nur wenigen Pins anzusteuern. Dies kann Sinn machen, wenn man z.B. eine Segmentanzeige mit vielen Stellen steuern möchte.

Hintergrund

Um die Funktionsweise des BitShifting zu verstehen hilft es, sich die grundlegenden Operationen des Microkontrollers vorzustellen. Ein Microkontroller – wie jede andere CPU auch – arbeitet nur auf der Basis von 0 (AUS) und 1 (EIN). Diese werden so verarbeitet, dass logische Systeme damit konstruiert werden können. Die Basis dafür bilden die sog. bitweisen Operanden. Ein Byte besteht grundsätzlich aus acht Bits. Man kann sich den Zusammenhang zwischen Bits und Bytes also folgendermassen vorstellen…

BILD

D.h. alle Werte der Bits in einem Byte zusammengenommen ergeben den Wert des Bytes. Dabei gilt folgende Logik …

0000 0000 0

0000 0001 1
0000 0010 2
0000 0011 3
0000 0100 4
0000 0101 5
...
1111 1111 255 

Durch alle Kombinationen lassen sich so mit jedem Byte 256 Werte (von 0 bis 255) darstellen, je nachdem, welches Bit “aktiv” und welches “nicht aktiv” ist. Im folgenden Tutorial wird genau dies mit dem “Beispiel ShiftOut 01″ visualisiert.

Der Wert des Bytes lässt sich also entweder durch die Summe der Werte der Bits bestimmen oder man setzt direkt den Wert des Bytes, welches wiederum die Werte der Bits verändert. In C/C++ haben wir durch einige definierte Funktionen Zugriff auf diese Werte – durch die sog. bitweisen Operanden. Möchte man also z.B. das dritte Bit in einem Byte verändern gibt es folgende drei Möglichkeiten…

byte test = B00100000; // Direkte Zuweisung des Bit Wertes
byte test = 40// Zuweisung über den Wert des Bytes
byte test = bitWrite(test, 3, HIGH); // Zuweisung des Wertes über die Funktion bitWrite() 

Wenn wir uns nun vorstellen, dass jedes Bit eine LED darstellt, welche je nach Wert des Bits aus EIN od. AUS gesetzt wird, dann können wir mit Hilfe eines Bytes acht unterschiedliche LEDs ansteuern. Es geht also lediglich darum, wie wir ein Byte versenden und dann wieder so decodieren können um damit eine LED zu schalten. Dafür nutzen wir ein ShiftRegister des Types 75HC595. Dieses erlaubt es uns über einen Daten PIN (DATA_PIN) das gesamte Byte zu senden und je nach dessen Wert bis zu acht Ausgänge zu schalten.

Die Funktion ShitOut(DATA_PIN, CLOCK_PIN, bitOrder, Wert) übernimmt dabei das Timing und das Herausschreiben des kompletten Bytes. DATA_PIN ist dabei der PIN, über welchen die Daten versandt werden. CLOCK_PIN ist für das Timing. bitOrder beschreibt ob wir das Byte von Links nach Rechts (MSBFIRST – most significant bit first) oder von Rechts nach Links (LSBFIRST – last significant bit first) lesen möchten. Wert kennt dann nur noch zwei Zustände HIGH od. LOW.

BILD

Beispiel

Ein gutes Tutorial zur Verwendung eines Shift Registers ist hier dokumentiert. Folgende Komponenten werden für das Tutorial benötigt:

1x Arduino
1x Shift Register 74HC595 (Datenblatt)
1x Display Kingsbright (Datenblatt)
2x Breadboard Mini

Das Display besteht eigentlich aus 8 einzelnen LEDs, welche in der uns vertrauten Form angeordnet sind. Auf der Rückseite des Diplays befinden sich die entsprechenden Anschlüsse. Im Datenblatt des Diplays finden wir die Information, wie diese Anschlüsse belegt werden müssen.

Um das Shift Register ansprechen zu können, müssen wir uns zunächst das Timing des Registers ansehen. Hierzu gibt es im Datenblatt der 74HC595 ein sog. Timing Diagramm.

Mit Hilfe dieses Diagramms lässt sich erkennen, dass wir zunächst den LATCH_PIN auf LOW setzen müssen. Danach können die Daten an das Register geshiftet werden. Zum Abschluss wird der LATCH_PIN wieder auf HIGH gesetzt um die Ausgänge des Registers zu setzen und die LEDs zum leuchten zu bringen.

Beispiel ShiftOut  Expand source
#define LATCH_PIN 8  //Pin zu ST_CP vom 74HC595
#define CLOCK_PIN 12 //Pin zu SH_CP vom 74HC595
#define DATA_PIN 11  //Pin zu DS vom 74HC595
 
void setup() 
{
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);
}
 
void loop() 
{
  for (int numberToDisplay = 0; numberToDisplay < 256; numberToDisplay++) 
  {
    digitalWrite(LATCH_PIN, LOW);  // LATCH_PIN auf LOW = Beginn der Daten
 
    shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, numberToDisplay); // Daten shiften
 
    digitalWrite(LATCH_PIN, HIGH);  // LATCH_PIN auf HIGH = LEDs leuchten
 
    delay(500);
  }
}

Bei dem ersten Beispiel lässt sich erkennen, wie ein Byte auf Bit Ebene aufgelöst wird. Um wirklich jeden einzelnen Pin des Registers ansprechen zu können hilft folgendes Beispiel.

Beispiel BitWrite  Expand source
#define LATCH_PIN 8  //Pin zu ST_CP vom 74HC595
#define CLOCK_PIN 12 //Pin zu SH_CP vom 74HC595
#define DATA_PIN 11  //Pin zu DS vom 74HC595
 
void setup() 
{
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);
}
 
void loop() 
{
  for(int i=0; i<8; i++)
  {
    registerWrite(i, LOW); // Entsprechende LED anmachen
    delay(1000);
  }
}
 
void registerWrite(int _whichPin, int _whichState) 
{
 
  byte bitsToSend = 255; // Dieses Byte hat acht Bits also: 11111111
 
  digitalWrite(LATCH_PIN, LOW);
 
  bitWrite(bitsToSend, _whichPin, _whichState); // Hier wird das entsprechende Bit im Byte gesetzt (z.B. 00100000)
 
  shiftOut(DATA_PIN, CLOCK_PIN, MSBFIRST, bitsToSend);
 
  digitalWrite(LATCH_PIN, HIGH);
 
}

Wollen wir alle Ziffern korrekt anzeigen, so müssen wir uns eine kleine Tabelle erstellen, welche uns die LEDs zeigt, die für die jeweilige Ziffer eingeschaltet sein müssen. Daraus ergibt sich folgende Visualisierung.

Aufgaben

1. Stellt alle Ziffern von 0-9 auf dem 7-Segment Display dar
2. Vereinfacht euren Code, so dass ihr nur noch ein Array habt, in welchem die Ziffern und entsprechenden LEDs hinterlegt sind. 
3. Ergänzt den Code, dass ihr zusätzlich zu den Ziffern auch Buchstaben anzeigen könnt. 

Lösung Aufgabe 1  Expand source
#define LATCH_PIN 8  //Pin zu ST_CP vom 74HC595
#define CLOCK_PIN 12 //Pin zu SH_CP vom 74HC595
#define DATA_PIN 11  //Pin zu DS vom 74HC595
  
void setup()
{
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);
}
  
void loop()
{
  registerWrite(B00000011); //oder 0x03
  delay(1000);
  registerWrite(B10011111); //oder 0x9f
  delay(1000);
  registerWrite(B00100101); //oder 0x25
  delay(1000);
  registerWrite(B00001101); //oder 0x0d
  delay(1000);
  registerWrite(B10011001); //oder 0x99
  delay(1000);
  registerWrite(B01001001); //oder 0x45
  delay(1000);
  registerWrite(B01000001); //oder 0x41
  delay(1000);
  registerWrite(B00011111); //oder 0x1f
  delay(1000);
  registerWrite(B00000001); //oder 0x01
  delay(1000);
  registerWrite(B00011001); //oder 0x19
  delay(1000);
}
void registerWrite(byte bitsToSend)
{
  digitalWrite(LATCH_PIN, LOW);
  
  shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, bitsToSend);
  
  digitalWrite(LATCH_PIN, HIGH);
}
Lösung Aufgabe 2  Expand source
#define LATCH_PIN 8  //Pin zu ST_CP vom 74HC595
#define CLOCK_PIN 12 //Pin zu SH_CP vom 74HC595
#define DATA_PIN 11  //Pin zu DS vom 74HC595
byte number[] =
{
  B00000011, //0
  B10011111, //1
  B00100101, //2
  B00001101, //3
  B10011001, //4
  B01001001, //5
  B01000001, //6
  B00011111, //7
  B00000001, //8
  B00011001, //9
};
unsigned long lastTime = 0;
int delayOfDisplay = 1000;
int index = 0;
void setup()
{
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);
}
  
void loop()
{
  if(millis()-lastTime > delayOfDisplay)
  {
    registerWrite(number[index]);
    lastTime = millis();
    if(index < 9)
    {
      index++;
    }
    else
    {
      index = 0;
    }
  }
}
void registerWrite(byte bitsToSend)
{
  digitalWrite(LATCH_PIN, LOW);
  
  shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, bitsToSend);
  
  digitalWrite(LATCH_PIN, HIGH);
}
Lösung Aufgabe 3  Expand source
//Selber herausfinden ;-)