11. Dezember 2016

Winterprojekt: Ansteuern der 7-Segment-Anzeige

Mit der nun gelieferten 7-Segment-Anzeige kann das die Programmierung und die Ansteuerung durch den Arduino erfolgen. Zunächst bin ich auf Draht …

Der Anschluss

Was steckt in der 7-Segment-Anzeige?

Die 7-Segment-Anzeig hat gleich eine Überraschung bereit: sie hat eine gemeinsame Anode.

schaltplan-7-segmentDie einzelne LED des vorausgehenden Blogs wurde durch einen aktiven Ausgang (high, 5 Volt) aktiviert. Für die Segment-Anzeige dagegen heisst es umdenken. Nur die Kathoden der einzelnen Dioden sind zugänglich, folglich kann der Arduino sie nur durch ein Low-Pegel, 0 Volt, zum leuchten bringen. Das gilt es anschließend bei der Programmierung zu berücksichtigen.
Anschlussbelegung 7-Segment-AnzeigeDer Schaltplan der Anzeige enthält die Nummern der Pins für das jeweilige Segment. Zudem bezeichnen die Buchstaben “a” bis “f” die jeweiligen Dioden und den Dezimalpunkt. Anhand der Beschreibung aus dem Datenblatt können nun die Verbindungen zum Arduino hergestellt werden. Zwischen jedem Digitalausgang und dem Anschluß habe ich zudem ein Widerstand geschaltet.

Die Verdrahtung

Prinzipiell könnte auch ein einzelner Widerstand an der gemeinsamen Anode genutzt werden. Hierdurch wird allerdings der Strom für alle Dioden begrenzt. Wenn für eine Ziffer 8 alle Segmente leuchten, teilt er sich auf und ist folglich je Diode niedriger als bei weniger leuchten Segmenten. Die Helligkeit könnte dadurch abhängig von der dargestellten Ziffer schwanken. Ich sag mal könnte, weil die Helligkeit durch den Strom nur schlecht steuerbar ist. Beim Ausprobieren habe ich keinen Unterschied sehen können. Durch Materialstreuung könnte es aber dennoch passieren und sähe dann unschön aus.

Die Ansteuerung der sieben Segmente erfordern ebenfalls sieben Digitalausgänge. Ich habe mich dazu entschieden die Ausgänge 13 – Segment “a” bis 7 – Segment “g” zu nutzen. Schließlich besitzen die Digital-Pins 1 und 2 Sonderfunktionen, die ich nicht unnötig blockieren möchte.

Die Software

Das Rahmenprogramm

In der Initialisierung der Ansteuerung definiere ich in einer Schleife die Digitalen Ausgänge und setze sie in einen definierten Zustand.

void setup() {
  for(int i=13; i>6; i--) {
    pinMode(i, OUTPUT);
    digitalWrite(i, HIGH); // Erst mal ausschalten
  }
}

Für den Test soll die 7-Segment-Anzeige die Ziffern 0 bis 9 anzeigen. Jedoch erfolgt nach jeder Ausgabe eine Verzögerung, sonst könnten den Ziffernwechsel wegen der hohen Geschwindigkeit nicht wahrnehmen.

void loop() {
  for(int i=0; i<10; i++) {
    ziffer(i);
    delay(1000);
  }
}

Nachdem die Schleife die Ziffern durchlaufen hat, lässt die übergeordnete Endlosschleife die Ziffern erneut anzeigen. Der spannendere Teil der Ansteuerung liegt darin, wie die Ziffern der 7-Segement-Anzeige auszugeben sind. Dafür ist Prozedur ziffer(int) zuständig.

Ansteuerung der 7-Segment-Anzeige

Ein erster Ansatz

Der erste Gedanke zur Umsetzung ist eine direkte Aufteilung nach anzuzeigender Ziffer

void ziffer(int z) {
  switch(z) {
  case 0:
   ziffer0();
   break;
  case 1:...

und eine zugehörige Aktivieren der Ausgänge. Für die Ziffer 0 auf der 7-Segement-Anzeige sieht das beispielsweise wie folgt aus.

void ziffer0() {
  digitalWrite(A, LOW); 
  digitalWrite(B, LOW);
  digitalWrite(C, LOW);
  digitalWrite(D, LOW);
  digitalWrite(E, LOW);
  digitalWrite(F, LOW);
  digitalWrite(G, HIGH);
}

Das Positive vorweg: es funktioniert. Das geht aber auch anders und besser. Zum einen ist es einfach toll, wenn sich eine Funktion “knackig” ausdrücken lässt. Ein kompakterer Code ist im Folgenden auch besser zu pflegen. Als Nebeneffekt kann aber Speicherersparnis dabei herauskommen. Zur Erinnerung: der Arduino hat 2K freien Speicher und 32K Programmspeicher. Das ist in dieser Baureihe sogar recht viel, denn die kleinen Brüder des verwendeten Chips besitzen nur 16K oder gar nur 8K Programmspeicher. Wenn ein Programm dort hinein passt, wird eine Serie halt auch preiswerter.

Vermeiden von doppelten Code

Was geht also besser? Für jede Ziffer auf der 7-Segment-Anzeige gibt es eine eigene Prozedur, wie die oben dargestellte. Mit Ausnahme der Pegel sieht jedoch jede dieser Prozedur gleich aus. Das ist eine unnötige Wiederholung, die zudem der Faulheit einer Entwicklers widerspricht. Soll nun an der Grundstruktur etwas geändert werden, ist danach jede der anderen Prozeduren anzupassen. Immerhin zehn Prozeduren nach dem gleichen Schema ändern.

Die Alternative heisst ein Feld definieren. Darin stehen nur die Pegel und eine Schleife weist sie jeweiligen Ausgang zu:

const int ZIFFERN[][] = {
          { LOW, LOW, LOW, LOW, LOW, LOW, HIGH },
          { ... 

        }

...

void ziffer(int z) {;
  for(int i=0; i<7; i++) { 
    digitalWrite(13-i, ZIFFERN[z][i]);
  }
}

Meine ursprüngliche Lösung belegt bei mir 1296 Bytes Programm- und 9 Bytes dynamischen Speicher. Die neue dagegen 1128 Bytes Programm- und 149 Bytes dynamischen Speicher. Die “schönere” Lösung ist kleiner benötigt trotzdem viel Platz für das Feld mit den Pegeln, und zwar vom ohnehin kleinen dynamischen Speicher.

Speicheroptimierung

Die Ansteuerung der 7-Segment-Anzeige kann nun noch über eine kompaktere Speicherung der Pegel optimiert werden. Für die Ausgabe ist eigentlich nur interessant, ob er an oder aus geschaltet wird. Als Datentyp habe ich zuvor int gewählt. Dieser Datentyp benötigt deutlich mehr Platz als nur ein Bit. Die sieben Pegel für die 7-Segement-Anzeige finden aber auch als Bit in einem Byte platz. Die Pegel lassen sich dann beispielsweise wir folgt ablegen:

const byte ZIFFERN[] = {
    B0000001, // 0
    B1001111, // 1
    B0010010, // 2
    B0000110, // 3
    B1001100, // 4
    B0100100, // 5
    B0100000, // 6
    B0001111, // 7
    B0000000, // 8
    B0000100, // 9
  };

Für den westlichen Menschen ist eine Kodierung der Segmente von links nach rechts nachvollziehbarer. Entsprechend steht das erste Bit nach dem B für Segment “a”, das zweite für “b”, etc.

Bitte ein Bit

Danach muss die  Prozedur das richtige Bit für den zugehörigen Ausgang setzen. Meine Lösung:

void ziffer(int z) {;
  for(int i=0; i<7; i++) {
    digitalWrite(13-i, (ZIFFERN[z] & 1 << (6-i))==0?LOW:HIGH);
  }
}

Hier spiele ich mit Bit-Operationen. Das ist allerdings nicht auf einen Blick zu lesen. Mit ZIFFERN[z] bekomme ich das Bytes für die gewünschte Ziffer. Die Laufvariable i zählt durch die sieben möglichen Segmente. Für das Segment “c” hat die Variable i den Wert 2. Das ist das dritte Segment, aber die Schleife beginnt mit 0 und nicht mit 1. Mit 1 << (6-i) erhalte ich ein Byte, das nur an der 6-2=4-ten Stelle eine 1 hat. Bei sieben Bits ist das die dritte Stelle von Links, sie soll in den Konstanten die “c”-Stelle darstellen. Die Vorgabe und das Zielsegment verknüpfe ich mit einem logischen “Und”. Das Ergebnis für die jeweiligen Eingangsdaten ist dann

Ziffern-Bit Masken-Bit Ergebnis
0 0 0
0 1 0
1 0 0
1 1 1

Ist also für Segment “c” eine 1 codiert, so sorgt die entstandene Maske B10000 für ein Byte, das ebenfalls eine 1 enthält. Dessen Zahlenwert ist größer als 0. Sonst ist der Wert des Bytes 0, alle Bits auf 0. Diesen Wert prüft nun der ?-Operator und setzt den jeweiligen entsprechend Ausgang auf LOW oder HIGH.

Tipp

Die Programmierung klappt nicht immer auf anhieb. Beispielsweise ist das letzte Bit der Maske auf der Stelle 0, folglich ist der intuitive Ansatz (7-i) für die Maske falsch. Es gibt einen einfachen Weg, um das zu prüfen: einfach Zwischenwerte auf die Serielle Schnittstelle zum Rechner ausgeben lassen. Dazu ist im Setup die Schnittstelle mit Serial.beginn(9600); zu initialisieren. Ausgaben erfolgen dann mit Serial.println(<wert>); Den seriellen Monitor (die Lupe oben links im Fenster) in der IDE starten, die bei der Initialisierung angegebene Geschwindigkeit (9600 Baud) auswählen und schon können wir dem Programm bei der Arbeit zusehen.

Zum Abschluss

Die für die einzelne 7-Segment-Anzeige gefundene Lösung belegt 1000 Bytes Programm- und 19 Bytes dynamischen Speicher. Offensichtlich lohnt es sich Gedanken über seine Programme zu machen. Wenn die Idee dann auch noch funktioniert … Film der zählenden Anzeige

Bisherige Beiträge

1 Comment

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

I accept that my given data and my IP address is sent to a server in the USA only for the purpose of spam prevention through the Akismet program.More information on Akismet and GDPR.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.