Binärumrechner mit Arduino und I2C-LCD-Display - [Teil 2]

Teil 2

Hallo liebe Bastler,

im zweiten Teil unseres Binärumrechnerprojektes zeige ich Ihnen, wie wir nun statt 'Hello World' unsere Dezimal-Binär-Hexadezimaltabelle auf dem Display anzeigen. Es folgt dabei dann die Erklärung, wie wir mithilfe von Port- und Pinmanipulation mit wenig Aufwand mehrere Pins auslesen und mit einem Rutsch Binärumrechnungen durchführen. Die Umrechnungstabelle aus Teil 1, Abschnitt Binärumrechnung wollen wir teilweise so übernehmen und auf dem Display darstellen. Los geht's.

Quellcode

Beginnen wir mit einem leeren Sketch und fügen die ersten Zeilen ein. 

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define MAX_COL 20
#define MAX_ROW 4
#define MAX_BITS 8

Quelltext 1: Includes und Defines

Zu Beginn inkludieren wir die Wire-Bibliothek für die I2C-Schnittstelle. Außerdem die 
zuvor installierte liquid_crystal_I2C-Bibliothek. Als Nächstes definieren wir uns Konstanten, die wir als Endwerte für Schleifen nutzen werden. Unser Display hat eine Maximalanzahl von 20 Spalten (COL für column) und 4 Zeilen (ROW).

Außerdem hat unser Datenwort später eine Größe von 8 Bit. Als Nächstes übernehmen wir folgende Zeilen aus dem
CustomChar-Beispiel. Damit können wir einheitlich die Funktion printByte() verwenden. Das ist ziemlich praktisch.

#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args) write(args);
#else
#define printByte(args) print(args,BYTE);
#endif

Quelltext 2: Definition von printByte()

Nun erstellen wir uns die Arrays aus Bytes, die unsere eigenen Symbole darstellen.

byte on[] = {
B11111,
B11111,
B11111,
B11111,
B10001,
B10001,
B10001,
B11111
};
byte off[] = {
B11111,
B10001,
B10001,
B10001,
B11111,
B11111,
B11111,
B11111
};
byte led[] = {
B00100,
B01110,
B11111,
B11111,
B01110,
B00100,
B00000,
B00000
};
byte pointed[] = {
B00100,
B00000,
B00100,
B00000,
B00100,
B00000,
B00100,
B00000
};

Quelltext 3: Custom Character

Die ersten beiden Symbole (byte on und byte off, s.o.) stellen die zwei Schalterstellungen eines Dipschalters dar. Das dritte Symbol repräsentiert eine eingeschaltete LED. (Ursprünglich wollte ich für die Ausgabe tatsächlich LEDs verwenden. Auf dem 20x4-Display kann man allerdings alles zusammen unterbringen und es bleibt übersichtlich. Außerdem spart man sich acht LEDs und Widerstände.)

Abbildung 4 zeigt die Darstellung wie auf dem Display. Vergleicht man es mit dem Quellcode für byte led oben, sieht man, dass die binäre Darstellung des Arrays einfacher zu lesen ist, als die hexadezimale.

 Abbildung 4: Custom Char

Das vierte Array wird eine gestrichelte Linie. Damit trennen wir später die beiden Halbbytes für die Darstellung in hexadezimal. Im Quellcode folgt als nächstes ein Array, in das die Spaltennummern des Displays eingetragen werden, in denen etwas dargestellt werden soll. Warum wir das machen, erkläre ich mit dem Quellcode der Funktion, die wir für die Ausgabe aus dem Display schreiben.

int ledDisplayCols[MAX_BITS] = {19, 17, 15, 13, 11, 9, 7, 5};
LiquidCrystal_I2C lcd(0x27,MAX_COL, MAX_ROW); 
byte zahl_dez_new = 1;
byte zahl_dez_old = 0;

Quelltext 4: Definition von weiteren globalen Variablen

Es folgt anschließend die Definition eines LCD-Objektes. Dem Konstruktor wird die I2C-Adresse und die Displaygröße übergeben. Die Adresse wird entweder vom Hersteller angegeben oder man nutzt einen I2C-Scanner, der als Sketch im Internet zu finden ist. Für die Displaygröße nutzen wir unsere definierten Konstanten vom Anfang des Quellcodes.

Die beiden folgenden Variablen werden unsere Dezimalzahlen sein. Es sind zwei, da unser Programm später in einer Schleife läuft. Wir wollen nur dann die Werte aus den digitalen Eingängen für unsere Berechnung übernehmen, wenn sich einer der Schalter verändert hat. Wir definieren die erste Zahl mit 1, damit sich beide zu Beginn des Programms unterscheiden und damit die Eingangspins beim ersten Durchlauf übernommen werden.

Es folgt nun die setup()-Funktion. Sie beinhaltet die Initialisierungen des Displays und der Eingangspins.

void setup() {
// Display initialisieren
lcd.init();
lcd.backlight();

// eigene Zeichen in das Display laden
lcd.createChar(0, off);
lcd.createChar(1, on);
lcd.createChar(2, led);
lcd.createChar(3, pointed);
// Port mit den Pins fuer die Binaerschalter initialisieren
DDRD = 0;
PORTD = 255;

// zum ersten Feld des Displays springen
lcd.home();
}

Quelltext 5: Die setup()-Funktion

Die Kommentare im Quelltext erklären die Aufgaben der einzelnen Codeabschnitte. Die Funktion createChar() wird benutzt, um eigene Symbole in den Speicher des Displays zu übertragen. Der erste Parameter ist einer der 8 Speicherplätze (0 bis 7). Der zweite Parameter ist der jeweilige Name unserer Arrays, die wir zuvor erstellt haben.

Beim nächsten Schritt handelt es sich um die sogenannte
Portmanipulation. Normalerweise werden die Eingangspins mit pinMode(n, INPUT_PULLUP) initialisiert. Da wir acht Eingänge nutzen, müssten wir acht Zeilen Code schreiben. Nun kann man das auch mit einer Schleife realisieren. Wir lösen das eleganter. Man kann drei Ports mit jeweils 8 Pins des Prozessors auf dem Arduino Uno nutzen. Port B, Port C und Port D.

Dabei ist der zuletzt genannte Port D der einzige, von dem alle 8 Pins zur Verfügung stehen.Die Pinnummern auf Port D sind 0 bis 7. Die Steuerung der Ports geschieht über drei Register mit den Bezeichnungen DDRx, PORTx und PINx. Für unseren Port D wären das also die Register DDRD, PORTD und PIND. DDR steht für
Data Direction Register.

Damit bestimmen wir, ob es sich bei dem gewünschten Pin um einen Aus- oder Eingang handelt. Das PORTD-Register ist ein Ausgangsregister und legt fest, ob der Zustand des jeweiligen Pins low oder high ist. Der Port PIND ist das Eingangsregister. Es zeigt die Zustände der Pins an, die im DDRD-Register als Eingang definiert wurden.

Um nun die 8 Pins an Port D als Eingänge zu initialisieren, müssen die Werte im Register DDRD auf 0 gesetzt werden. Pins, die als Ausgänge definiert werden sollen, müssten auf 1 gesetzt werden. Da wir alle Pins als Eingänge definieren möchten, schreiben wir über das Makro DDRD eine 0 = 0000 0000(BIN)  in das Register.

Als Nächstes möchten wir die Pull-up-Widerstände für alle Pins aktivieren. Definiert man einen Pin als Eingang mit dem PIND-Register, wird das PORTD-Register genutzt, um die Pull-ups für die jeweiligen Pins im PIND-Register einzuschalten. Dafür setzt man die Zustandswerte in PORTD auf 1. Da wir alle
Pins gleichzeitig auf 1 setzen möchten, schreiben wir eine 255 = 1111 1111(BIN) in das Register.

Wir wollen für alle 8 Pins die Pull-ups aktivieren. Wir brauchen also für jedes Bit in dem Register den Zustand 1. Auch hier speichern wir im Register 1111 1111(BIN) =  255(DEZ). 

Tipp: Da wir Pull-up-Widerstände nutzen und damit das Potenzial 'hochziehen', ist der Normalzustand (aus) also 1. Für unsere Zwecke ist das nicht ausschlaggebend. Wir werden im Quellcode auch nicht konkret die Zustände abfragen, sondern nur alte und neue Zustände vergleichen. 

Kommen wir nun zur loop()-Funktion. Diese durchläuft der Arduino endlos. Wir können die CPU ein wenig entlasten, in dem wir nur dann die Bildschirmausgabe verändern, wenn sich einer der Pins verändert hat. Ansonsten bleibt die einzige Aufgabe nur, den Zustand der Eingangspins neu abzufragen und mit dem alten Zustand zu vergleichen.

void loop() {
zahl_dez_new = PIND;
if (zahl_dez_new != zahl_dez_old) {
output(zahl_dez_new);
zahl_dez_old = zahl_dez_new;
}
}

Quelltext 6: Die loop()-Funktion

Wir können die Zustände des PIND-Registers (also der Eingangspins) als Dezimalzahl einlesen und in eine Variable schreiben. Das ist sehr komfortabel, da wir damit direkt weiterarbeiten können, ohne jedes einzelne Bit anzufassen. Hat sich diese Zahl nun verändert, rufen wir eine Ausgabefunktion auf und übergeben ihr die aktuelle Zahl. Wir könnten diesen Parameter auch weglassen und globale Variablen verwenden.

Für eine eventuelle Wiederverwendbarkeit der Funktion nutze ich aber hier den Übergabewert und arbeite damit weiter. Anschließend schreiben wir noch den neuen Wert in die alte Variable und überschreiben den ursprünglichen alten Wert. Hier erkennt man auch, warum die erste der beiden Variablen zu Beginn mit 1 definiert wurde. Damit unterscheidet sie sich im ersten Durchlauf bereits von der zweiten Variablen und die Ausgabe wird auf jeden Fall durchgeführt.

Es folgt als nächstes unsere Funktion für die Ausgabe. Ich habe sie output() genannt. Sie gibt nichts zurück und bekommt als Übergabe den Wert aus dem PIND-Register.

void output(byte input) {
int col = MAX_COL;
int arraycounter = 0;
int bitwert = 1;
byte high_mask = B11110000;
byte low_mask = B00001111;
byte low = 0;
byte high = 0;

Quelltext 7: Die ouput()-Funktion

Wir erzeugen uns lokale Variablen. Wir brauchen die Anzahl der Spalten, die wir später herunterzählen. Einen Zähler, mit dem wir über das Array mit den aktiven Spalten iterieren. Außerdem wollen wir die Wertigkeit anzeigen. Wir nennen es hier Bitwert.

Dieser 
beginnt bei 1. Außerdem wollen wir die Dezimalzahl auch wieder in binär umrechnen und einzelne Bits auslesen. Allerdings wollen wir nicht jedes einzelne Bit bearbeiten, sondern am besten gleich alle auf einmal. Dafür nutzen wir die Bitmanipulation. Wir erzeugen uns sogenannte Bitmasken. Für die leichtere Lesbarkeit habe ich sie hier in Binärform definiert. 

low = input & low_mask;
high = input & high_mask;
high = high >> 4;

Quelltext 8: Bitmanipulation

Aus dem Abschnitt 'Binärumrechnung' wissen wir, dass wir die 8 Bit in zwei Nibbles zu je 4 Bit aufteilen müssen, um eine hexadezimale Zahl zu erhalten. Wir erreichen das mit logischen Rechenoperationen. Dafür legen wir beide Binärwerte übereinander.

Bitmaske low 0 0 0 0 1 1 1 1
Register Bsp. 0 1 1 0 1 1 0 1


Beide werden dann einzeln mit einem logischen UND verrechnet. Das bedeutet, dass als Ergebnis immer nur 1 erscheint, wenn beide Operanden auch 1 sind. Alles andere wird 0.

Bitmaske low 0 0 0 0 1 1 1 1
Register Bsp. 0 1 1 0 1 1 0 1
Logisches UND 0 0 0 0 1 1 0 1


Man sieht, dass wir nur die Zustände der rechten Seite übrig behalten. Diese schreiben wir in die neue Variable low. Wir brauchen allerdings noch den linken Teil, das Highbyte. Dafür nutzen wir die andere Bitmaske.

Bitmaske high 1 1 1 1 0 0 0 0
Register Bsp. 0 1 1 0 1 1 0 1
Logisches UND 0 1 1 0 0 0 0 0


In diesem Fall erhalten wir nur den linken Teil aus dem Binärcode des Registers. Diesen schreiben wir in die Variable high. Allerdings fehlt hier noch ein Schritt. Denn im Moment haben wir nur den rechten Teil gelöscht. Wir brauchen den linken Teil auf der rechten Seite, da wir immer von rechts nach links mit dem Auszählen beginnen. Dafür nutzen wir die Shiftoperation 'Rechtsshift' (»).

Wir schieben alle Zustände in der Variable um vier Stellen nach rechts. Damit wandern alle vier Werte der linken auf die rechte Seite. Von außen werden immer Nullen nachgezogen.

Bitmaske high 1 1 1 1 0 0 0 0
Register Bsp. 0 1 1 0 1 1 0 1
Logisches UND 0 1 1 0 0 0 0 0
Rechtsshift 0 0 0 0 0 1 1 0


So haben wir mit drei Zeilen Quellcode den 8-Bit-Wert in zwei 4-Bit-Werte aufgeteilt. Nun folgt die eigentliche Ausgabe auf dem Bildschirm. Die Felder müssen mit der LiquidCrystalBibliothek einzeln beschrieben werden. Das können wir mit Schleifen vereinfachen.

for (; col >= 0 ; col--) {
for(; arraycounter < MAX_BITS; arraycounter++) {
if (col == ledDisplayCols[arraycounter]) {
lcd.setCursor(col, 0);
lcd.print(bitwert);
bitwert = bitwert *2;
if (bitwert > 8) {
bitwert = 1;
}
if (bitRead(input, arraycounter)) {
lcd.setCursor(col, 1);
lcd.printByte(2);
lcd.setCursor(col, 2);
lcd.printByte(1);
}
else {
lcd.setCursor(col, 1);
lcd.print(" ");
lcd.setCursor(col, 2);
lcd.printByte(0);
}
}
}
arraycounter = 0;
}

Quelltext 9: Die ouput()-Funktion - Ausgabeschleife

Die äußere Schleife iteriert über die Spalten des Displays. Den Startwert haben wir bereits in der Definition der Variablen gesetzt. Daher fehlt er hier im Schleifenkopf. Wir zählen die Spalten abwärts und somit von rechts nach links, da wir die Bits aus den Variablen ebenfalls in dieser Richtung auslesen.

Die innere Schleife iteriert über das Array mit den Spaltenzahlen, die wir beschreiben möchten. Entspricht der Spaltenzähler einem Wert aus dem Array, wird an dieser Stelle in der ersten Zeile die Wertigkeit ausgegeben. Mit jedem Durchgang wird dieser Wert verdoppelt, da sich auch die Wertigkeit in jeder Spalte verdoppelt. Wir geben hier die Wertigkeiten der Nibbles aus. Also müssen wir den Wert wieder zurücksetzen, wenn er größer als 8 wird. Entfernen wir diese If-Anweisung, werden die Wertigkeiten weitergezählt und bis 128 ausgegeben.

In den nächsten beiden Zeilen nutzen wir die Funktion bitRead(), um aus der Eingangsvariable die einzelnen Bits auszulesen. Der zweite Parameter ist die Position des Bits. Dafür nutzen wir den gleichen Zähler wie für das Array. 

Somit wird in jeder aktivierten Spalte in der zweiten Zeile unser Kreis ausgegeben und in der dritten Zeile unser Dipschalter. Die If-Anweisung kümmert sich darum, ob der Zustand 1 oder 0 ist. Für 1 wird der Kreis und Dipschalter 'an' angezeigt. Ansonsten kein Kreis und Dipschalter 'aus'. Der Kreis dient dazu, leichter zu erkennen, ob ein Schalter an oder aus ist.

Die Funktion
printByte() bekommt die Zahlen der Speicherplätze, die wir im Setup mit unseren Zeichen beschrieben haben. Nach dem Durchlauf einer Zeile wird noch der Arrayzähler auf 0 gesetzt, damit der nächste Durchlauf wieder bei 0 beginnen kann. Damit wir nun noch die Dezimal- und die Hexadezimalzahl ausgeben können, springen wir die Positionen auf dem Display direkt an.

lcd.setCursor(0, 0);
lcd.print("DEZ:");
lcd.setCursor(0, 1);

if(input <10) lcd.print(" ");
if(input <100) lcd.print(" ");
if(input <1000) lcd.print(" ");
lcd.setCursor(0, 1);
lcd.print(input);

Quelltext 10: Die ouput()-Funktion - Ausgabe der Dezimalzahl

Wir springen das obere linke Feld an und schreiben den Text direkt als Zeichenkette auf das Display. Als Nächstes springen wir eine Zeile weiter und geben den Eingangswert aus. Hierbei müssen wir beachten, dass die Zahl ein-, zwei- oder dreistellig sein kann. Ist die alte Zahl dreistellig und die neue Zahl einstellig, bleiben die anderen beiden Stellen der alten Zahl stehen. Also überprüfen wir die Anzahl der Stellen und löschen die Felder gegebenenfalls.

Der Aufmerksame Beobachter wird festgestellt haben, dass wir den binären Code, den wir über die Schalter eingegeben haben, gar nicht selbst in das Dezimalsystem umgerechnet haben. Das könnten wir tun. Da wir aber (wie zuvor beschrieben) das Register direkt als Dezimalzahl auslesen können, übernimmt die CPU bzw. die Software den Schritt für uns. Genauso verfahren wir mit der Umrechnung in hexadezimal.

lcd.setCursor(0, 3);
lcd.print("HEX:");
lcd.setCursor(8, 3);
lcd.print(high, HEX);
lcd.setCursor(16, 3);
lcd.print(low, HEX);

Quelltext 11: Die ouput()-Funktion - Ausgabe der Hexadezimalzahl

Wir springen an die entsprechenden Stellen auf dem Display und geben die beiden Werte für low und high aus. Dabei können wir den zweiten Parameter der print()-Funktion nutzen. Dort geben wir an, mit welchem Zahlensystem wir den Wert ausgeben möchten. Als letzten Schritt zeichnen wir noch eine gestrichelte Linie, um die beiden Halbbytes (oder Nibbles) optisch zu trennen. 

for (int i = 0; i < 4; i++) {
lcd.setCursor(12, i);
lcd.printByte(3);
}

Quelltext 12: Die output()-Funktion - Ausgabe der gestrichelten Linie

Wir setzen den Teil auch mit einer Schleife um. Die Ausgabe auf dem Display sieht dann wie folgt aus. Die rot markierten Bereiche weisen auf das jeweilige Zahlensystem hin:

Abbildung 5: Displayausgabe 

Sie können nun die Schalter nutzen und 8-Bit-Binärcodes als Dezimal- und Hexadezimalzahlen anzeigen lassen. Für den umgekehrten Weg könnten wir einen weiteren Schalter anschließen und zwischen beiden Rechenrichtungen umschalten. Wir bräuchten dann eine Eingabe der Dezimal- oder Hexadezimalzahlen. Das könnte man mit einem Dreh-Encoder umsetzen, oder mit einem Tastenfeld. Euer Kreativität sind keine Grenzen gesetzt.

Es folgt der komplette Quellcode inklusive Kommentaren: 

/*  Binaerkonverter
 *  von Andreas Wolter
 *  fuer AZ-Delivery.de
 *  
 *  Funktion:
 *  wandelt ueber Schalter eingegebene Binaerwerte in dezimale und hexadezimale Zahlen um
 *  
 *  Verwendete Hardware:
 *    - Arduino Uno R3
 *    - Liquid Crystal Display
 *	  -	Liquid Crystal I2C-Adapter
 *    - acht separate Schalter oder Dipschalterleiste mit mindestens acht Dips
 *    - Anschlusskabel
 *  
 *  Verwendete Bibliotheken:
 *    - wire
 *    - liquid_crystal_i2c
 *  
 *  Beispielquelle aus der Liquid Crystal Bibliothek: CustomChars
 *  
 *  Zeichentabelle des Displays:  https://i.stack.imgur.com/1TKZH.gif 
 *  Zeichengenerator online:      https://maxpromer.github.io/LCD-Character-Creator/
 *  
 *  Pinout:
 *  
 *  Arduino Uno   |   I2C Adapter am Display      
 *  ----------------------------------------
 *      GND       |         GND      
 *      5V        |         VCC      
 *      A4        |         SDA      
 *      A5        |         SCL
 *  
 *  Arduino Uno   |   Taster fuer jedes Bit von rechts nach links
 *  -------------------------------------------------------------
 *      D0        |         0
 *      D1        |         1
 *      D2        |         2
 *      D3        |         3
 *      D4        |         4
 *      D5        |         5
 *      D6        |         6
 *      D7        |         7
 *      GND       |         GND 0 - 7
 *      
 * Hinweis:
 *  Wenn das Programm auf den Mikrocontroller geladen werden soll,
 *  sollten die Pins 0 (RX) und 1 (TX) nicht verbunden sein.
 *  Es koennten Fehler beim Upload auftreten. 
 */

#include <Wire.h>                                     // Fuer I2C
#include <LiquidCrystal_I2C.h>                        // Fuer die Ansteuerung des Displays via I2C
#define MAX_COL 20                                    // Maximalanzahl der Spalten des Displays
#define MAX_ROW 4                                     // Maximalanzahl der Zeilen des Displays
#define MAX_BITS 8                                    // Wortbreite der Zahl


  // Aus dem Liquid Crystal Beispiel CustomChars
  // statt write() und print() kann dann printByte() verwendet werden
#if defined(ARDUINO) && ARDUINO >= 100
#define printByte(args)  write(args);
#else
#define printByte(args)  print(args,BYTE);
#endif

  // Definition der eigenen Zeichen fuer das Display
  // Schalter an
byte on[] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B10001,
  B10001,
  B10001,
  B11111
};

  // Schalter aus
byte off[] = {
  B11111,
  B10001,
  B10001,
  B10001,
  B11111,
  B11111,
  B11111,
  B11111
};

  // Kreis aehnlich einer LED
byte led[] = {
  B00100,
  B01110,
  B11111,
  B11111,
  B01110,
  B00100,
  B00000,
  B00000
};

  // gepunktete senkrechte Linie
byte pointed[] = {
  B00100,
  B00000,
  B00100,
  B00000,
  B00100,
  B00000,
  B00100,
  B00000
};

  // Festlegen, in welchen Spalten des Displays die Wertigkeiten, LEDs und Dipschalter angezeigt werden sollen.
  // Als Array mit den Werten fuer die Spalten von rechts nach links, da Bits ebenfalls von rechts nach links ausgelesen werden.
  // Das erleichtert spaeter die Verwendung von Schleifen.
int ledDisplayCols[MAX_BITS] = {19, 17, 15, 13, 11, 9, 7, 5};

  // Display-Objekt:
  // Die liquid_crystal_i2c Bibliothek muss installiert sein und
  // das Display per I2C-Adapter an den Arduino angeschlossen sein
  // Die Adresse (hier 0x27) unterscheidet sich eventuell abhaengig
  // vom Hersteller des Displays.
LiquidCrystal_I2C lcd(0x27,MAX_COL, MAX_ROW);

  // Input als Dezimalzahl
  // Alt und neu, damit auf Veraenderungen reagiert werden kann.
byte zahl_dez_new = 1;
byte zahl_dez_old = 0;

/* Funktion, die die Umrechnung auf dem Display ausgibt
 * Rueckgabe: nichts
 * Uebergabe: Eingabe aus den Binaerschaltern
 */
void output(byte input) {
  int col = MAX_COL;
  int arraycounter = 0;
  int bitwert = 1;                                      // Startwert der Dezimal-Wertigkeit der einzelnen Bits (1, 2, 4, 8, ...)
  byte high_mask = B11110000;                           // Bitmasken fuer die Aufteilung von 8 Bit in 2 x 4 Nibbles
  byte low_mask = B00001111;
  byte low = 0;
  byte high = 0; 
  low = input & low_mask;                               // die unteren 4 Bit (von rechts nach links 0 - 3) auslesen und als neues Byte speichern
  high = input & high_mask;                             // die oberen 4 Bit (von rechts nach links 4 - 7) auslesen und als neues Byte speichern
  high = high >> 4;                                     // die oberen 4 Bit an die unteren Bitstellen schieben

    // Die Spalten des Displays von rechts nach links fuellen
  for (; col >= 0 ; col--) {
      // Ueber das Array mit den Spaltenzahlen iterieren
    for(; arraycounter < MAX_BITS; arraycounter++) {
        // wenn in der Spalte etwas angezeigt werden soll
      if (col == ledDisplayCols[arraycounter]) {
        lcd.setCursor(col, 0);
        lcd.print(bitwert);                             // Ausgabe der Dezimal-Wertigkeit des aktuellen Bits
        bitwert = bitwert *2;                           // Die Wertigkeit steigt mit jedem Bit um das Doppelte
        if (bitwert > 8) {                              // Die Wertigkeiten werden nicht von 1 bis 128 angezeigt, sondern pro Nibble (Halbbyte) von 1 bis 8
          bitwert = 1;
        }
        
        if (bitRead(input, arraycounter)) {             // aus der Dezimalzahl werden die einzelnen Bits extrahiert
          lcd.setCursor(col, 1);                      
          lcd.printByte(2);                             // ...und eine LED (Kreis) angezeigt, wenn das Bit = 1 ist
          lcd.setCursor(col, 2);                      
          lcd.printByte(1);                             // ...und ein "Dipschalter an" angezeigt, wenn das Bit = 1 ist
        }
        else {
          lcd.setCursor(col, 1);
          lcd.print(" ");                               // ...und nichts angezeigt, wenn das Bit = 0 ist
          lcd.setCursor(col, 2);
          lcd.printByte(0);                             // ...und ein "Dipschalter aus" angezeigt, wenn das Bit = 1 ist
        }                                               // printByte(n) gibt die eigenen Zeichen aus. 8 Speicherplaetze stehen dafuer 
      }                                                 // zur Verfuegung (0 bis 7)
    }
    arraycounter = 0;
  }

    // Ausgabe der Dezimalzahl
  lcd.setCursor(0, 0);
  lcd.print("DEZ:");
  lcd.setCursor(0, 1);
    // wenn die neue Zahl weniger Stellen hat, werden damit die alten Stellen geloescht
  if(input <10) lcd.print(" ");                         // ein leerzeichen wenn Zahl kleiner 2-stellig
  if(input <100) lcd.print(" ");                        // ein leerzeichen wenn Zahl kleiner 3-stellig
  if(input <1000) lcd.print(" ");                       // ein leerzeichen wenn Zahl kleiner 3-stellig
  lcd.setCursor(0, 1);
  lcd.print(input);
  
    // Ausgabe der Hexadezimalzahl
  lcd.setCursor(0, 3);
  lcd.print("HEX:");
  lcd.setCursor(8, 3);
  lcd.print(high, HEX);
  lcd.setCursor(16, 3);
  lcd.print(low, HEX);

    // Ausgabe der gestrichelten Trennlinie zwischen den Halbbytes
  for (int i = 0; i < 4; i++) {
    lcd.setCursor(12, i);
    lcd.printByte(3);   
  }
}

void setup() {
    // Display initialisieren
  lcd.init(); 
  lcd.backlight();

    // eigene Zeichen in das Display laden
  lcd.createChar(0, off);
  lcd.createChar(1, on);
  lcd.createChar(2, led);
  lcd.createChar(3, pointed);

    // Port mit den Pins fuer die Binaerschalter initialisieren
  DDRD = 0;                                             // PortD (D0 - D7) als Input
  PORTD = 255;                                          // PortD (D0 - D7) Input Pullup on

    // zum ersten Feld des Displays springen
  lcd.home();
  
}

void loop() {
  zahl_dez_new = PIND;                                  // Pins auslesen
  if (zahl_dez_new != zahl_dez_old) {                   // Wenn sich ein Pin des Ports veraendert hat
      output(zahl_dez_new);                             // ...wird die Aenderung auf dem Display ausgegeben
      zahl_dez_old = zahl_dez_new;
  }
} 

Quelltext 13: Kompletter Quellcode

Viel Spaß beim Basteln!

Ihr Andreas Wolter
für AZ-Delivery

1 Kommentar

Rainer Unverhau

Rainer Unverhau

Hallo lieber Andreas Wolter,

das funktioniert super, ein tolles Programm um meinem Neffen die Binärzahlen anschaulich zu erklären.

ein kleiner Verbesserungsvorschlag:

diese beiden Zeilen mit // sollte man weglassen:

if(input <10) lcd.print(" "); // ein leerzeichen wenn Zahl kleiner 2-stellig if(input <100) lcd.print(" "); // ein leerzeichen wenn Zahl kleiner 3-stellig // if(input <1000) lcd.print(" "); // ein leerzeichen wenn Zahl kleiner 3-stellig // lcd.setCursor(0, 1); lcd.print(input);

bei einer 3-stelligen Zahl braucht man vorne kein Leerzeichen
und wenn man den Cursor vor der Ausgabe wieder ganz nach links stellt, werden die vorher eingefügten Leerzeichen bei 2 und einstelligen Zahlen wieder mit der Zahl überschrieben.

dann hat man plötzlich Zahlen wie 637 (Beim Wechsel von 127 auf 63) auf dem Display und wundert sich, wo die herkommt.

Ansonsten super, den Trick mit den Ports kannte ich noch nicht das hätte ich schon oft mal gebraucht, da spart man sich viel Initialisierungs- und Ausleseprogrammierung.

vielen Dank und Grüße

Rainer Unverhau

Einen Kommentar hinterlassen

Alle Kommentare werden vor der Veröffentlichung moderiert