Experimente mit dem Super Starterkit: 7-Segment Anzeige, Schieberegister, Multiplexer -  Teil 3

In diesem Beitrag geht es um die Darstellung von Ziffern mit 7-Segment-Anzeigen. Zur Ansteuerung wird ein Mikrocontroller verwendet. Es werden alle Segmente direkt, oder über ein Schieberegister angesteuert. Für die Ansteuerung einer Anzeige mit vier Ziffern wird die Multiplextechnik eingesetzt.

Benötigte Hardware

Anzahl

Bauteil

Anmerkung

1

Super Starter Kit

 

1

9V-Blockbatterie oder 9V-Netzteil

optional

 

7-Segment-Anzeige 5161AS

Eine 7-Segment Anzeige besteht aus acht Leuchtdioden. Eine für jedes Segment und eine für einen Dezimalpunkt. Um die Anzahl der Anschlüsse klein zu halten, werden alle Kathoden oder Anoden zusammengefasst und an einem Pin herausgeführt. Die Anzeige im Starterkit hat eine gemeinsame Kathode. Die folgenden Abbildungen zeigen die Anzeige mit der Bezeichnung der Segmente und der Pinbelegung, sowie die Innenschaltung.

 

Anschluss am Mikrocontroller und Testprogramm

Am einfachsten ist es, die Anode jeder Leuchtdiode mit einem Ausgang des Mikrocontrollers zu verbinden. Auch hier ist es, wie bei der einfachen Leuchtdiode, notwendig, einen Widerstand zur Strombegrenzung vorzuschalten. Es können, wie im Teil 2 gezeigt, Widerstände mit 1 kΩ verwendet werden. Ich habe 4.7 kΩ gewählt, da in einem späteren Beispiel auch die Kathode über einen Ausgang des Mikrocontrollers angesteuert werden soll. Wenn alle Dioden leuchten, ergibt sich an der gemeinsamen Kathode ein Gesamtstrom von 3 V / 4700 Ω * 8 = 5.1 mA. Diesen Strom kann ein Ausgang des Mikrocontrollers problemlos verarbeiten.  Die Anzeige ist mit 4.7 kΩ kaum dunkler, als mit 1 kΩ.

Da im Programm der Inhalt eines Bytes in einzelne Bits zerlegt werden muss, kommen logische Operatoren zum Einsatz, die auf die einzelnen Bits eines Bytes verknüpfen. Folgende Operatoren stehen zur Verfügung:
Logische UND-Verknüpfung c = a & b
a    10011011
b    01010101
c    00010001
Im Ergebnis sind alle Bits gesetzt, die in a und b gesetzt sind

Logische ODER-Verknüpfung c = a | b
a    10011011
b    01010101
c    11010111
Im Ergebnis sind alle Bits gesetzt, die in a oder b gesetzt sind

Logische EXCLUSIV ODER-Verknüpfung c = a ^ b
a    10011011
b    01010101
c    11001110
Im Ergebnis sind alle Bits gesetzt, die in a und b verschieden sind

Verschieben nach rechts c = a >> 2
a    10011010
c    00100110
Im Ergebnis werden alle Bits um die, im zweiten Operanden angegebene Anzahl von Bits, nach rechts verschoben. Die rechten Bits gehen verloren. Von links werden Nullen nachgeschoben.

Verschieben nach links c = a << 2
a    10011010
c    01101000
Im Ergebnis werden alle Bits um die, im zweiten Operanden angegebene Anzahl von Bits, nach links verschoben. Die linken Bits gehen verloren. Von rechts werden Nullen nachgeschoben.
Alle logischen Operatoren funktionieren nicht nur für Bytes, sondern auch für 16-Bit und 32-Bit Integer.

Das Programm beginnt wieder mit der Definition der verwendeten Pins.

#define PINA 2

Es reicht den Pin für das Segment a zu definieren, da die weiteren Pins der Reihe nach genutzt werden. Damit Ziffern dargestellt werden können, wird eine Tabelle benötigt, aus der die Information entnommen werden kann, welche Segmente für eine bestimmte Zahl leuchten müssen.
Jedem Segment wird ein Bit eines Bytes zugewiesen. Also Segment a = Bit 0, Segment b = Bit 1 u.s.w.

 

byte segmente[17] = {
//  hgfedcba 
  0B00111111, //0
  0B00000110, //1
  0B01011011, //2
  0B01001111, //3
  0B01100110, //4
  0B01101101, //5
  0B01111101, //6
  0B00000111, //7
  0B01111111, //8
  0B01101111, //9
  0B01110111, //A
  0B01111100, //b
  0B00111001, //C
  0B01011110, //d
  0B01111001, //E
  0B01110001, //F
  0B01000000, //-
};

Es folgt eine globale Variable, die festhält, ob der Dezimalpunkt leuchten soll oder nicht.

boolean pkt;

Für die Anzeige einer Ziffer soll diesmal eine eigene Funktion verwendet werden. Der Vorteil einer eigenen Funktion liegt in der Kapselung einer bestimmten Aufgabe. Die Deklaration beginnt mit dem Datentyp, den die Funktion nach ihrer Ausführung zurückgibt, oder void, wenn nichts zurückgegeben wird. Es folgt der Name der Funktion und in Klammern eine Auflistung von Variablen, die als Parameter an die Funktion übergeben werden. Der Programm-Code für die Funktion folgt danach in geschlungenen Klammern.

void print7(byte zahl, boolean punkt) {
  byte bits;
  if (zahl < 17) {
    bits =  segmente[zahl];
    for (byte i = 0; i < 7; i++) {
      digitalWrite(PINA+i, bits & 1);
      bits = bits >> 1;
    }
    digitalWrite(PINA + 7, punkt);
  }
}

Die Funktion heißt print7, sie gibt keine Daten zurück. Als Parameter erhält sie die auszugebende Zahl zahl und die boolesche Variable punkt, die steuert, ob der Punkt angezeigt wird, oder nicht. In der Funktion wird eine lokale Variable bits definiert, die als Zwischenspeicher für die Ausgabe des Bitmusters verwendet wird. Die Bedingung if (zahl < 17) stellt sicher, dass die Zahl in der Tabelle der Bitmuster vorkommt. Die Tabelle enthält 17 Bitmuster mit den Indizes 0 bis 16. Mit dem Parameter zahl als Index wird das entsprechende Bitmuster aus der Tabelle in die Variable bits geladen. Nun müssen der Reihe nach die Segmente entsprechend dem Bitmuster ein- oder ausgeschaltet werden. Zu diesem Zweck wird das Kommando for verwendet, das eine Iterationsschleife definiert. Nach dem Schlüsselwort folgt in Klammern die Schleifenbedingung. Diese besteht aus drei Teilen, die durch Strichpunkt getrennt werden. Im ersten Teil wird der Schleifenvariablen der Anfangswert zugewiesen. Der zweite Teil enthält eine Bedingung, die für einen weiteren Durchlauf erfüllt sein muss. Der dritte Teil gibt an, wie sich der Wert der Schleifenvariablen nach jedem Durchlauf ändert. Danach folgt in geschlungenen Klammern der Programmcode, der innerhalb der Schleife ausgeführt werden soll. Mit dem Kommando for (byte i = 0; i < 7; i++) erhält die Schleifenvariable i vom Typ byte die Werte 0 bis 6 und wird nach jedem Durchlauf um eins erhöht. Mit digitalWrite(PINA+i, bits & 1); wird ein Segment ein- oder ausgeschaltet. Die Nummer des Pins wird aus der Nummer des ersten Pins plus der Schleifenvariablen gebildet. Um den Zustand für das entsprechende Segment zu erhalten, wird das niederwertigste Bit des Bitmusters mit der logischen UND-Verknüpfung bits & 1 maskiert. Danach wird das Bitmuster mit dem Kommando bits = bits >> 1; um ein Bit nach rechts geschoben. Dadurch kommt das zweite Bit an die niederwertigste Stelle. Dies wird so lange wiederholt bis sieben der acht Bits des Bitmusters verarbeitet wurde und damit die Schleife beendet wird. Eine gesonderte Behandlung erfolgt für den Punkt. Hierzu wird einfach der Parameter punkt auf den entsprechenden Pin ausgegeben.

void setup() {
  for (byte i = 0; i < 8; i++) pinMode(PINA + i, OUTPUT);
  pkt = false;
}

In der setup() Funktion werden über eine Schleife alle acht Pins für die Segmente als Ausgang definiert. Die Globale Variable pkt wird auf false gesetzt.

void loop() {
  for (byte num = 0; num < 17; num++) {
    print7(num,pkt);
    delay (500);
  }
  pkt = ! pkt;
}

In der loop() Funktion wird mit einer weiteren Schleife die Funktion print7() mit den Zahlen 0 bis 16 aufgerufen, wodurch die jeweilige Zahl angezeigt wird. Zwischen den Zahlen wird eine Pause von einer halben Sekunde eingebaut, damit die Zahl auch abgelesen werden kann. Nachdem einmal alle Zahlen angezeigt wurden, wird der Zustand der Variablen pkt umgekehrt, sodass abwechselnd der Dezimalpunkt an oder aus ist.

Schieberegister 74HC595

Ein Schieberegister besteht aus mehreren Speicherzellen, bei denen jeweils der Ausgang der einen Speicherzelle mit dem Eingang der nächsten verbunden ist. Mit jedem Taktimpuls an der Taktleitung wird die Information vom Eingang an den Ausgang übertragen. Die Daten werden also mit jedem Takt um einen Schritt weitergeschoben.

Der Baustein 74HC595 enthält ein achtstufiges Schieberegister. Zusätzlich besitzt er auch noch ein acht Bit Speicherregister, damit können die Daten festgehalten werden, während neue Daten ins Schieberegister geschoben werden. Die Ausgänge des Speicherregisters sind an Pins herausgeführt. Diese Ausgänge können über den Eingang OE „Output Enable“ ein- oder ausgeschaltet werden. Low an diesem Eingang gibt die Ausgänge frei, High setzt sie auf Hochohmig, sodass mehrere solcher Bausteine parallelgeschaltet werden könnten. Low am Eingang SRCLR „Shift Register Clear“ setzt den Inhalt des Schieberegisters auf null. Der Eingang SRClk „Shift Register Clock“ ist für den Takt zum Schieben. Der Eingang RClk „Register Clock“ ist für den Takt zur Übernahme der Schieberegisterdaten in das Speicherregister. SER ist der Eingang für die seriellen Daten. QH‘ ist der Ausgang für serielle Daten, für den Fall, dass mehrere solcher Bausteine hintereinandergeschaltet werden sollen.

Ansteuerung der Anzeige über ein Schieberegister 74HC595

Durch den Einsatz des Schieberegisters kann die Anzahl der notwendigen Mikrocontroller-Ausgänge von acht auf drei reduziert werden. Die Segmente der 7-Segmentanzeige werden mit den Ausgängen des Schieberegisters verbunden. Der Mikrocontroller stellt an einem Ausgang die seriellen Daten zur Verfügung. Auf einem zweiten Ausgang erzeugt er den Schiebetakt und am dritten den Takt zur Übernahme ins Speicherregister.

Im Programm muss für die neue Schaltung nur die Funktion print7() geändert werden. Am Beginn werden wieder die verwendeten Pins definiert.

#define SER 2
#define RCLK 3
#define SRCLK 4

Diesmal die Pins für die seriellen Daten, für den Registertakt und für den Schieberegistertakt. Es folgt die Tabelle mit den Bitmustern und die globale Variable für den Punkt.

byte segmente[17] = {
//  hgfedcba 
  0B00111111, //0
  0B00000110, //1
  0B01011011, //2
  0B01001111, //3
  0B01100110, //4
  0B01101101, //5
  0B01111101, //6
  0B00000111, //7
  0B01111111, //8
  0B01101111, //9
  0B01110111, //A
  0B01111100, //b
  0B00111001, //C
  0B01011110, //d
  0B01111001, //E
  0B01110001, //F
  0B01000000, //-
};

boolean pkt;

Die Funktion print7() muss komplett geändert werden. Es werden nicht mehr die Segmente direkt angesteuert. Die Bitmuster werden seriell an das Schieberegister übergeben. Dazu muss zuerst der Pin für die seriellen Daten mit dem entsprechenden Wert gesetzt und dann auf der Taktleitung ein Impuls erzeugt werden. Wenn alle acht Bits des Bitmusters in das Schieberegister übertragen wurden, wird ein Impuls auf der Registertaktleitung erzeugt, um die Daten aus dem Schieberegister in das Speicherregister zu übernehmen. Da der OE-Eingang fix auf Low ist, wird das Bitmuster auf der 7-Segement-Anzeige angezeigt. Diesmal muss das Bitmuster nach links verschoben werden, da das höchstwertigste Bit des Bitmusters als erstes in das Schieberegister geschoben werden muss.

void print7(byte zahl, boolean punkt) {
  byte bits;
  if (zahl < 17) {
    bits =  segmente[zahl];
    if (punkt) bits = bits | 0B10000000;
    for (byte i = 0; i < 8; i++) {
      digitalWrite(SER, bits & 128);
      digitalWrite(SRCLK, 1);
      digitalWrite(SRCLK, 0);
      bits = bits << 1;
    }
    digitalWrite(RCLK, 1);
    digitalWrite(RCLK, 0);
  }
}

Am Beginn wird eine lokale Variable für das Bitmuster angelegt. Es folgt eine Sicherheitsüberprüfung, damit die Zahl sicher kleiner als 17 ist. Dann wird das Bitmuster für die übergebene Zahl aus der Tabelle in der lokalen Variablen gespeichert. Wenn der Parameter punkt wahr ist, wird das höchstwertigste Bit mit einer logischen ODER-Verknüpfung bits = bits | 0B10000000;

gesetzt. Das Bitmuster kann nun an das Schieberegister gesendet werden. Das geschieht in einer Schleife für i = 0 bis 7. Das höchstwertigste Bit des Bitmusters wird mit einer logischen UND-Verknüpfung bits & 128 maskiert und auf den Pin für die seriellen Daten geschrieben. Der Taktimpuls für das Schieberegister wird erzeugt indem zuerst High und gleich danach Low auf den Ausgang geschrieben wird. Zuletzt wird der Inhalt von bits um eine Stelle nach links verschoben, damit das nächste Bit an die höchstwertigste Stelle kommt. Nachdem so alle acht Bits gesendet wurden, wird am Anschluss für den Registertakt ebenfalls ein Impuls erzeugt.

void setup() {
  pinMode(SER, OUTPUT);
  pinMode(RCLK, OUTPUT);
  pinMode(SRCLK, OUTPUT);
  digitalWrite(RCLK, 0);
  digitalWrite(SRCLK, 0);
  pkt = false;
}

In der setup() Funktion werden die benutzten Pins als Ausgang definiert. Außerdem werden beide Taktleitungen auf Low und die Variable pkt auf False gesetzt.

void loop() {
  for (byte num = 0; num < 17; num++) {
    print7(num,pkt);
    delay (1000);
  }
  delay(2000);
  pkt = ! pkt;
}

Die loop() Funktion bleibt gleich wie bei der direkten Ansteuerung.

 

4-stellige 7-Segment Anzeige 5461AS

Diese Anzeige besteht aus vier einfachen 7-Segment-Anzeigen. Um die Anschlusszahl klein zu halten, werden die Anoden der gleichen Segmente parallelgeschaltet. Die Kathoden werden je Ziffer zusammengefasst und auf einen Pin herausgeführt. Nur jene Ziffer, deren Kathode auf Low ist, leuchtet. Man muss also der Reihe nach immer eine Ziffer kurz anzeigen. Durch die Trägheit der Augen sind dann alle vier Ziffern sichtbar.

Ansteuerung

Es wird wieder die Ansteuerung über das Schieberegister verwendet. Zusätzlich werden noch vier Ausgänge benötigt, mit denen die Kathoden jener Ziffer auf Low gesetzt wird, die gerade angezeigt wird. Da im Extremfall alle Segmente einer Ziffer leuchten, darf der Gesamtstrom aller acht Leuchtdioden den Ausgang des Mikrocontrollers nicht überlasten. Mit den gewählten Vorwiderständen mit einem Wert von 4,7 kΩ gibt es da keine Probleme.

Pin 2 ist für die seriellen Daten, Pin 3 für den Registertakt und Pin 4 für den Schiebetakt. Pin 5 steuert die Kathoden der Ziffer ganz links. Pin 6, 7 und 8 steuern, der Reihe nach, die weiteren Ziffern.

Das Testprogramm soll im Bereich von -999 bis +999 hinauf- und hinunterzählen und den Zählerstand anzeigen.

Zuerst werden wieder alle benutzten Pins definiert. Die vier Pins für die Kathodenansteuerung kommen dazu.

#define SER 2
#define RCLK 3
#define SRCLK 4
#define DIG1 5
#define DIG2 6
#define DIG3 7
#define DIG4 8

In der Tabelle mit den Bitmustern gibt es ein Muster mehr, nämlich alle auf 0 entspricht einer leeren Anzeige.

byte segmente[18] = {
//  hgfedcba 
  0B00111111, //0
  0B00000110, //1
  0B01011011, //2
  0B01001111, //3
  0B01100110, //4
  0B01101101, //5
  0B01111101, //6
  0B00000111, //7
  0B01111111, //8
  0B01101111, //9
  0B01110111, //A
  0B01111100, //b
  0B00111001, //C
  0B01011110, //d
  0B01111001, //E
  0B01110001, //F
  0B01000000, //-
  0B00000000, //leer
};

Diesmal gibt es drei globale Variablen. Und zwar last eine 32-Bit Integer-Zahl ohne Vorzeichen als Zeitspeicher, zahl, eine Integer Zahl mit Vorzeichen und auf, eine boolesche Variable zur Unterscheidung zwischen Aufwärts- und Abwärtszählen.

uint32_t last;
int zahl;
boolean auf;

Die Funktion print7() ist identisch mit dem vorangegangenen Beispiel.

void print7(byte zahl, boolean punkt) {
  byte bits;
  if (zahl < 18) {
    bits =  segmente[zahl];
    if (punkt) bits = bits | 0B10000000;
    for (byte i = 0; i < 8; i++) {
      digitalWrite(SER, bits & 128);
      digitalWrite(SRCLK, 1);
      digitalWrite(SRCLK, 0);
      bits = bits << 1;
    }
    digitalWrite(RCLK, 1);
    digitalWrite(RCLK, 0);
  }
}

Es folgt eine neue Funktion, die eine dreistellige Zahl mit Vorzeichen anzeigen kann.

void displayNum(int Nummer) {
  int z;
  if ((Nummer > -1000) && (Nummer < 1000)) {
    if (Nummer < 0) {
      print7(16,false);
      Nummer = Nummer * -1;
    } else {
      print7(17,false);
    }
    digitalWrite(DIG1,0);
    delay(2);
    digitalWrite(DIG1,1);
    z = Nummer/100;
    print7(z,false);
    digitalWrite(DIG2,0);
    delay(2);
    digitalWrite(DIG2,1);
    z = ( Nummer - z * 100) /10;
    print7(z,false);
    digitalWrite(DIG3,0);
    delay(2);
    digitalWrite(DIG3,1);
    z = Nummer % 10;
    print7(z,false);
    digitalWrite(DIG4,0);
    delay(2);
    digitalWrite(DIG4,1);
  }
}

Am Beginn wird eine lokale Variable z als Hilfsspeicher definiert. Es folgt die Sicherheitsabfrage, damit die übergebene Zahl im zulässigen Bereich zwischen -999 und 999 liegt. Die erste Anzeige soll das Vorzeichen anzeigen. Ist die übergebene Zahl kleiner als 0, wird das Bitmuster mit Index 16, also das Minuszeichen ausgegeben und die Zahl mit -1 multipliziert, um eine positive Zahl zu erhalten. Ist die Zahl positiv wird das Bitmuster mit Index 17 ausgegeben, es leuchten also keine Segmente. Mit der Befehlsfolge

    digitalWrite(DIG1,0);
    delay(2);
    digitalWrite(DIG1,1);

werden die Kathoden der ersten 7-Segment-Anzeige für 2 ms auf Low gesetzt, sodass die Segmente deren Anoden auf High sind, leuchten.
Nun wird die Zahl durch 100 dividiert. In der Variablen z stehen dann die Hunderter. Die Variable z wird an das Schieberegister gesendet und dann die zweite Ziffer 2 ms lang eingeschaltet. Von der übergebenen Zahl werden die Hunderter abgezogen und der Rest durch 10 dividiert. In der Variablen z stehen nun die Zehner, die ausgegeben und angezeigt werden. Zuletzt werden mit dem Modulo Operator % die Einer ermittelt. Auch diese werden ausgegeben und angezeigt.

void setup() {
  pinMode(SER, OUTPUT);
  pinMode(RCLK, OUTPUT);
  pinMode(SRCLK, OUTPUT);
  pinMode(DIG1, OUTPUT);
  pinMode(DIG2, OUTPUT);
  pinMode(DIG3, OUTPUT);
  pinMode(DIG4, OUTPUT);
  digitalWrite(RCLK, 0);
  digitalWrite(SRCLK, 0);
  digitalWrite(DIG1,1);
  digitalWrite(DIG2,1);
  digitalWrite(DIG3,1);
  digitalWrite(DIG4,1);
  zahl = -99;
  auf = true;
  last = millis();
}

In der setup() Funktion werden alle verwendeten Pins als Ausgänge definiert. Die Taktleitungen werden auf Low und die Ausgänge für die Kathoden auf High gesetzt. Die Variable zahl wird auf einen beliebigen Anfangswert gesetzt. Auch die Variable auf für die Zählrichtung kann beliebig gesetzt werden. Mit der Zuweisung last = millis(); wird der aktuelle Wert des internen Zählers in der Variablen last gespeichert.

 void loop() {
  displayNum(zahl);
  if ((millis() - last) > 100)  {
    last = millis();
    if (auf) {
      zahl++;
      if (zahl > 999) {
        zahl = 998;
        auf = false;
      }
    } else {
      zahl--;
      if (zahl < -999) {
        zahl = -998;
        auf = true;
      }
    }
  }
}

In der loop() Funktion wird displayNum() aufgerufen, um den aktuellen Zählerstand anzuzeigen. Der Zähler sollte alle 0.1 s geändert werden. Damit der aktuelle Zählerstand kontinuierlich angezeigt wird, kann die delay() Funktion hier nicht verwendet werden. Man kann aber den internen Zähler nutzen, um herauszufinden, wann der Zähler verändert werden soll. Das ist dann der Fall, wenn der interne Zähler millis() um mehr als 100 größer, als der in last gespeicherte Zählerstand ist. Ist diese Bedingung erfüllt, wird der aktuelle Stand des Internen Zählers für das nächste Intervall in last gespeichert. Dann wird, je nach Zustand der Variablen auf, der Zählerstand erhöht oder vermindert. Wird einer der Endwerte überschritten, wird die Zählrichtung umgedreht.

Viel Spaß bei den Experimenten mit den 7-Segment-Anzeigen.

 Beitrag als PDF

 

DisplaysFür arduinoProjekte für anfänger

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert