Mein erstes Moped hatte keine Tankanzeige, aber dafür einen genialen Benzinhahn. Auf dem nachfolgenden Bild erkennt man den Schlauch, der einige Zentimeter in den Tank hineinragt. In der Schalterstellung ON/AUF wird das Benzin über diesen Schlauch entnommen, bis zusätzlich Luft gesaugt wird. Der Motor fängt an zu stottern, und der Fahrer weiß, dass er jetzt den Benzinhahn auf Reserve schalten muss. Jetzt erhält er restlichen Tankinhalt und kann noch rund 50 km fahren.
Den gleichen Effekt hat man mit einer LED, die anzeigt, wenn man nur noch eine kleine Restmenge, eben die Reserve, im Tank hat. Auch moderne Autos mit Multimedia-Displays haben diese Warnlampe zusätzlich, häufig begleitet von einem kurzen Piepston, wenn die Lampe angeht.
LED
Für die weiteren Anzeigen wollen wir unsere kleinen Micro Controller benutzen, die uns vielfältige Möglichkeiten eröffnen. Beginnen wir mit mehreren LEDs, die uns den ungefähren Inhalt anzeigen. Mit acht oder zehn farbigen LEDs, z.B. grüne für voll oder fast voll, gelbe für den mittleren Bereich und rote für die Reserve. Das Ampelsystem wird überall auf der Welt verstanden. Man kann dazu einzelne LEDs nehmen, es gibt aber auch kleine Bauteile mit zehn eingebauten LEDs, sogenannte Bargraphs, einfarbig oder mit der eben genannten Farbfolge.
An den vielen Beinchen erkennt man, dass jedes einzelne Segment separat angesteuert wird, also 8 bzw. 10 oder noch mehr Ausgänge am Micro Controller? Das muss nicht sein, schließlich gibt es Integrierte Schaltkreise mit Schieberegistern wie den IC SN74HC595N, den ich im Blog „Wie unser Computer tickt“ vorgestellt hatte. Ich nehme mir die Schaltung aus dem Blog, tausche je zwei gelbe LEDs am Ende gegen rote bzw. grüne LEDs aus und mache mich ans Programmieren.
/* AnalogRead displayed on 8 LEDs with SN74HC595N Reads an analog input on pin 0, prints the result to the Serial Monitor and turn on one of 8 LEDs Attach the center pin of a 10 kOhm potentiometer to pin A0, and the outside pins to +5V and ground. */ // Arduino-Pin connected to SHCP of 74HC595 int shiftPin = 8; // Arduino-Pin connected to STCP of 74HC595 int storePin = 9; // Arduino-Pin connected to DS of 74HC595 int dataPin = 10; // array with LED values int indicator[8] = {0,0,0,0,0,0,0,0}; // counter int i=0; // the setup routine runs once when you press reset: void setup() { // initialize serial communication at 9600 bits per second: Serial.begin(9600); // define Pins 8,9,10 as outputs pinMode(storePin, OUTPUT); pinMode(shiftPin, OUTPUT); pinMode(dataPin, OUTPUT); } // the loop routine runs over and over again forever: void loop() { // read the input on analog pin 0: int sensorValue = analogRead(A0); // print out the value you read: Serial.print("sensorValue = "); Serial.print(sensorValue); int xValue = map(sensorValue,0,1024,0,8); Serial.print(" xValue = "); Serial.print(xValue); for (i = 0; i<8; i++) { indicator[i] = 0; } for (i = 0; i<=xValue; i++) { indicator[i] = 1; } Serial.print(" indicator = "); for (i = 0; i<8; i++) { Serial.print(indicator[i]); } Serial.println(); // storePin to LOW digitalWrite(storePin, LOW); for (i=0; i<8; i++) { // start with all 3 Pins LOW // data transfer (1 bit) at the change from LOW to HIGH (positive-edge triggered) digitalWrite(shiftPin, LOW); // write value of the respective bit to DS pin digitalWrite(dataPin, indicator[i]); // ShiftPin SHCP from LOW to HIGH for data transfer to the sift register digitalWrite(shiftPin, HIGH); } // When all 8 bis are in shift register put the StorePin STCP // from LOW to HIGH, in order to copy all 18 bits // from the shift registers to the storage registers for output digitalWrite(storePin, HIGH); delay(1000); // delay in between reads for stability }
Exemplarisch schließe ich ein 10 kOhm Potentiometer an A0, um damit die Tankanzeige zu simulieren. Der SN74HC595N wird an die Pins 8, 9 und 10 angeschlossen.
Der analoge Eingang liefert (theoretisch) Werte zwischen 0 und 1023. Mit dem in dieser Versuchsanordnung verwendeten Nano erlebe ich zum ersten Mal, dass die höchsten Werte nur knapp über 900 liegen. Kurzerhand schließe ich den Eingang AREF (die Referenzspannung für den Analog-Digital-Konverter) ebenfalls an die Stromschiene an und erhalte Werte bis 1020.
Mit der Funktion map() möchte ich die Messwerte herunterskalieren auf Werte von 0 bis 7.
Erster Versuch: int xValue = map(sensorValue,0,1023,0,7);
Ich bekomme keine 7 angezeigt, selbst wenn ich das Poti voll gegen Vcc aufdrehe, d.h. die siebte LED (Tank voll) würde nicht aufleuchten.
Zweiter Versuch: int xValue = map(sensorValue,0,1024,0,8);
Ich erhöhe die oberen Werte der map()-Funktion um 1 und erhalte genau die Unterteilung, die ich bekommen möchte.
Mit dem Array indicator[8] steuere ich die acht LEDs an. Nachdem zunächst alle Stellen auf 0 gesetzt werden, setze ich in der nächsten Zählschleife alle Werte bis zum Index des eben ermittelten Werts xValue auf 1.
Spaßeshalber habe ich den Code dahingehend erweitert, dass die letzte rote LED (Tank fast leer) kurz blinkt, um Aufmerksamkeit zu erregen. Dazu schließe ich die Kathode der LED an einen Ausgangspin an. Wenn ich den kurz auf HIGH lege, geht die LED kurz aus. Hier der Download des erweiterten Programms.
LCD
Genug mit LED experimentiert, jetzt möchte ich doch ein robustes LCD als Tankanzeige verwenden. Mit dem LCD1602 könnte man in den zwei Zeilen mit je 16 Zeichen sogar zwei Tankanzeigen realisieren. Ich entscheide mich für die digitale Anzeige des Wertes in der oberen Zeile und die „analoge“ Anzeige in der unteren.
Zunächst die Schaltung aufbauen. Das beginnt mit der Entscheidung, ob ich das LCD1602 man viele Pins anschließe oder das LCD1602 mit I2C-Adapter verwende.
Ich zeige in diesem Blog beide Arten, für mich selbst war es eine Wiederauffrischung des verschütteten Wissens. Um die Anzeigen bedienerfreundlich anzusteuern, installiere ich zunächst die Programm-Bibliotheken (libraries) unter dem Pulldown-Menü Werkzeuge/Bibliotheken verwalten … gebe ich im Suchfenster „Liquid“ ein und bekomme mehrere Möglichkeiten angezeigt.
Die erste Bibliothek LiquidCrystal wird in der aktuellen Arduino-IDE als Builtin und „Installiert“ angezeigt. Leider habe ich dafür keine Beispiele gefunden, so dass ich auch die Adafruit LiquidCrystal-Bibliothek installiere, die mehrere Sketch-Beispiele mitliefert.
Durch Herunter-Scollen des Rollbalkens im Blibliotheksverwalter finde ich auch die Bibliothek LiquidCrystal I2C, die ich gleich mit installiere.
Aber zunächst tüftele ich mit dem LCD ohne I2C-Adapter. Die Verkabelung ist etwas aufwendiger, u.a. weil man für den Kontrast des Displays ein Potentiometer benötigt und für die Datenübertragung sechs Daten-Pins am Micro Controller.
Aus dem ursprünglichen Beispiel „Hello World“ übernehme ich die Zeilen mit der Pin-Belegung. Ein weiteres Potentiometer für die Simulation der Tankanzeige schließe ich wieder an den analogen Eingang A0 an.
/* Example sketch level indicator with LCD1602 * LiquidCrystal Library * The circuit: * LCD RS pin to digital pin 12 * LCD Enable pin to digital pin 11 * LCD D4 pin to digital pin 5 * LCD D5 pin to digital pin 4 * LCD D6 pin to digital pin 3 * LCD D7 pin to digital pin 2 * LCD R/W pin to ground * 10K potentiometer (variable resistor): * ends to +5V and ground * wiper to LCD VO pin (pin 3) for contrast adjustment * VDD and A (Anode of backlight) to 5V * VSS, RW and K (Kathode of backlight) to GND */ // include the library code: #include <LiquidCrystal.h> // alternatively: //#include <Adafruit_LiquidCrystal.h> // initialize the library with the numbers of the interface pins LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //(RS, EN, D4, D5, D6, D7) // alternatively: //Adafruit_LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //(RS, EN, D4, D5, D6, D7) void setup() { // initialize serial communication at 9600 bits per second: Serial.begin(9600); // set up the LCD's number of columns and rows: lcd.begin(16, 2); // Print a message to the LCD. lcd.setCursor(0, 0); // (column, line) 0...15, 0...1 lcd.print("Hello, World!"); delay(1000); lcd.setCursor(0, 0); // (column, line) 0...15, 0...1 lcd.print(" "); } void loop() { // read the input on analog pin 0: int sensorValue = analogRead(A0); // print out the value you read: Serial.print("level indicator = "); int levelpercent=map(sensorValue,0,1024,0,101); Serial.print(levelpercent); int xValue = map(sensorValue,0,1024,0,16); String XXX = ""; for (int i=0; i<xValue+1; i++) { XXX = XXX + "X"; } for (int i=xValue+1; i<16; i++) { XXX = XXX + " "; } Serial.print(" xValue = "); Serial.print(xValue); Serial.println(XXX); // set the cursor to column 0, line 1 // (note: line 1 is the second row, since counting begins with 0): lcd.setCursor(0, 0); // print the number of seconds since reset: lcd.print("level = "); lcd.print(levelpercent); lcd.print(" % "); lcd.setCursor(0, 1); lcd.print(XXX); if (xValue==0) { // Turn on the blinking effect: lcd.noDisplay(); delay(300); // Turn off the blinking effect: lcd.display(); delay(1000); } delay(1000); }
Die map-Funktion verwende ich diesmal zweimal, einmal, um den Wert des AD-Wandlers in Prozent umzurechnen und in der oberen Zeile anzuzeigen, ein zweites Mal, um die sechzehn Stellen der unteren Zeile des Displays entsprechend der Tankfüllung mit X zu beschreiben. Und wieder möchte ich Aufmerksamkeit erregen, wenn der Sprit zur Neige geht. Mit den Methoden noDisplay / display schalte ich das Display kurz aus und wieder an, um durch diesen Effekt meine Absicht zu realisieren.
Mit der Methode setCursor wird der Startpunkt der Zeichenausgabe angewählt. Das erste Argument gibt die Stelle in der Zeile an, das zweite die Zeile. Wie so oft beginnt die Zählung bei 0; der Wertebereich liegt damit beim LCD1602 zwischen 0 und 15 bzw. 0 und 1, beim LCD2004 entsprechend zwischen 0 und 19 bzw. 0 und 3.
Nun zum LCD1602 mit I2C-Adapter. Die Schaltung ist ungleich einfacher – nur vier Kabelverbindungen zum Micro Controller: VCC=5V, GND, SDA an A4 und SCL an A5. Das Potentiometer für den Kontrast ist auf dem I2C-Adapter realisiert. Aus Gründen der Sichtbarkeit habe ich den Adapter über ein Mini-Breadboard mit dem LCD1602 verbunden. Normalerweise wird dieser als „backpack“ direkt mit dem Display verlötet.
Auch der Sketch ist sehr schnell angepasst: Zunächst wird die andere Bibliothek inkludiert
// include the library code: #include <LiquidCrystal_I2C.h>
Bei der Initialisierung wird die I2C-Adresse und die Display-Größe angegeben:
// initialize the library with the I2C-address and display details // set the LCD address to 0x27 for a 16 chars and 2 line display (alternatively 20,4) LiquidCrystal_I2C lcd(0x27,16,2);
Und in der Funktion void setup() werden folgende Befehle ergänzt:
// set up the LCD's number of columns and rows: lcd.init(); lcd.backlight();
Hier der gesamte Sketch zum Download sowie ein Bild des Versuchsaufbaus:
OLED
Zuletzt möchte ich eine Tankanzeige realisieren, die einem Zeigerinstrument täuschend ähnlich sieht. Dazu benötige ich ein Mini-OLED, z.B. das 1,3“ I2C-OLED- Display. Auch das hatte ich schon lange nicht mehr benutzt und muss mich wieder schrittweise herantasten. Also schnell das eBook von der Produktseite bei AZ-Delivery herunterladen und nachschlagen, welche Bibliothek ich installieren muss. Im Bibliotheksverwalter u8g2 eingeben, aber diese Bibliothek ist für ganz viele verschiedene Displays. Also muss zusätzlich zum Inkludieren im Beispiel-Sketch eine der vielen Zeilen einkommentiert werden, die für unser Display zutrifft:
U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
Wenn dann z.B. von den Beispiel-Sketches der GraphicsTest funktioniert, können alle anderen auskommentierten Zeilen auch gelöscht werden.
Die Programm-Bibliothek U8g2 von OliKraus ist sehr mächtig. Sie wird nicht nur für sehr verschiedene Displays verwendet, sondern bietet auch eine Vielzahl von graphischen Funktionen.
Einen Überblick verschaffen die beiden folgenden Links:
https://github.com/olikraus/u8g2
https://github.com/olikraus/u8g2/wiki/u8g2reference
Über den ersten Link können Sie das gesamte Paket als zip-Datei herunterladen, aber die Bibliothek ist so weit etabliert, dass sie leichter mit dem Bibliotheksmanager installiert werden kann. Auch dabei werden sehr viele Beispielprogramme mit installiert.
![](https://cdn.shopify.com/s/files/1/1509/1638/files/8U8g2_Beispiele_1024x1024.jpg?v=1647859091)
Für mein Projekt „Tankanzeige“ benötige ich nur einen Halbkreis, um ein Zeigerinstrument zu simulieren, und eine gerade Linie für den Zeiger. Unter dem zweiten Link mit der Referenz werde ich schnell fündig. Ich benötige:
drawCircle und drawLine
Anhand der Bilder bekommt man auch heraus, wie das Koordinatensystem für das Display, in meinem Fall 128 mal 64 Bildpunkte, aussieht. Die Zählung beginnt in der oberen, linken Ecke; von dort zählt man von 0 bis 127 nach rechts in x-Richtung und von 0 bis 63 nach unten in y-Richtung.
Der Mittelpunkt meines Halbkreises sowie der Startpunkt des Zeigers liegen unten in der Mitte, also bei x=63 und y=63. Der Halbkreis ist schnell gezeichnet, mit
u8g2.drawCircle( 63, 63, 63, U8G2_DRAW_UPPER_RIGHT|U8G2_DRAW_UPPER_LEFT);
Die Argumente sind x=63, y=63, Radius=63, und dann werden mit | verknüpft die gewünschten Viertelkreise (UPPER RIGHT | UPPER LEFT) angegeben.
Die Funktion drawLine hat vier Argumente, jeweils die x- und y-Koordinate des Start- und Zielpunktes unseres Zeigers. Den Startpunkt wollten wir ja auf (63,63) legen, erledigt. Aber der Zielpunkt meiner 60 Pixel langen Linie ist abhängig vom „Tankinhalt“, also einer variablen Größe, so dass ich die Werte berechnen muss. Dafür habe ich länger gebraucht als gedacht, aber schließlich fiel mir eine Weisheit aus meiner Marinejugend ein: Wenn der Sailor nicht mehr weiter weiß, macht er eine Maling. Also los:
Die Koordinaten des Zielpunktes sind: zx = 63 + dx und zy = 63 - dy
Dabei sind dx = r * cos α und dy = r * sin α
Woher bekomme ich den Winkel α, der im Übrigen für die Winkelfunktionen im Bogenmaß angegeben werden muss. Ein Schritt nach dem anderen: Der Winkel 0° entspricht 100% Füllung, 90° entspricht 50% und 180° entspricht 0% (leer). Im Sketch verwende ich die Variable p für die prozentuale Füllung. Der Winkel wird berechnet mit α = (100 – p) * 1,8.
Kurze Plausibilitätsprobe: Für p = 100 ist α = 0°, für p = 50 ist α = 50*1,8 = 90° und für p = 0 ist α=180°. Und die Sinusfunktion und damit der Wert dy ist von 0° bis 180° positiv, die Cosinusfunktion und damit der Wert dx wird zwischen 90° und 180° negativ.
Jetzt schnell die Umrechnung vom Gradmaß ins Bogenmaß: Der Vollkreis hat 360° im Gradmaß; das entspricht einem Bogenmaß (Umfang des Einheitskreises) von 2*π (Zahl pi = 3,14…). Damit wird jeder beliebige Winkel α umgerechnet mit *2*π / 360 oder gekürzt *π/180. Und wenn wir schon beim Kürzen sind: 1,8 und 180 lassen sich auch vereinfachen. Die beiden Formeln lauten also:
dx = int(60*cos(3.14*(100-p)/100));
dy = int(60*sin(3.14*(100-p)/100));
Hier zunächst das Bild der Versuchsanordnung:
sowie der Sketch im Ganzen (download):
/* Example for a simple gauge with semicircle and pointer on 1.3" I2C OLED based on OliKraus library U8g2 https://github.com/olikraus/u8g2 https://github.com/olikraus/u8g2/wiki/u8g2reference by Bernd54Albrecht for AZ-Delivery */ #include <Arduino.h> #include <U8g2lib.h> #ifdef U8X8_HAVE_HW_SPI #include <SPI.h> #endif #ifdef U8X8_HAVE_HW_I2C #include <Wire.h> #endif // Initialize the 1.3” I2C OLED // The complete list of displays is available here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp U8G2_SH1106_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); int p,dx,dy,sensorValue; void setup(void) { // initialize serial communication at 9600 bits per second: Serial.begin(9600); u8g2.begin(); } void loop(void) { // read the input on analog pin 0: sensorValue = analogRead(A0); p = map(sensorValue,0,1024,0,101); Serial.print(p); u8g2.firstPage(); do { // example for text // u8g2.setFont(u8g2_font_ncenB14_tr); // u8g2.drawStr(0,15,"Hello World!"); u8g2.drawCircle( 63, 63, 63, U8G2_DRAW_UPPER_RIGHT|U8G2_DRAW_UPPER_LEFT); dx = int(60*cos(3.14*(100-p)/100)); Serial.print(" dx = "); Serial.print(dx); dy = int(60*sin(3.14*(100-p)/100)); Serial.print(" dy = "); Serial.println(dy); u8g2.drawLine(63, 63, 63+dx, 63-dy); } while ( u8g2.nextPage() ); delay(1000); }
Soweit meine Einführung in die exemplarische Anzeige einer Füllmenge. Abschließend möchte ich Ihnen noch eine Übersicht über die Displays geben, die für kleine Projekte in Frage kommen:
Bezeichnung |
Techn. |
Bus |
Sp. |
Zl. |
Diag. |
Pix. X |
Pix. Y |
Höhe |
Grafik |
2x16 LCD blaues Display |
LCD |
Parallel |
16 |
2 |
5,55 |
nein |
|||
2x16 LCD grünes Display |
LCD |
Parallel |
16 |
2 |
5,55 |
Nein |
|||
2x16 LCD blaues Display I2C Bundle |
LCD |
I2C |
16 |
2 |
5,55 |
Nein |
|||
2x16 LCD grünes Display I2C Bundle |
LCD |
I2C |
16 |
2 |
5,55 |
Nein |
|||
4x20 LCD blaues Display |
LCD |
Parallel |
20 |
4 |
4,75 |
Nein |
|||
4x20 LCD grünes Display |
LCD |
Parallel |
20 |
4 |
4,75 |
Nein |
|||
4x20 LCD blaues Display I2C Bundle |
LCD |
I2C |
20 |
4 |
4,75 |
Nein |
|||
4x20 LCD grünes Display I2C Bundle |
LCD |
I2C |
20 |
4 |
4,75 |
Nein |
|||
LCD Display 2x16 with Keypad Shield |
LCD |
I2C |
16 |
2 |
5,55 |
Nein |
|||
LCD Display 84x48 Pixel |
LCD |
SPI |
14 |
6 |
1,7 |
84 |
48 |
4,34 |
Ja |
128x64 LCD blaues Display |
LCD |
Parallel/SPI |
21 |
8 |
3,1 |
128 |
64 |
5,68 |
Ja |
0,91 Zoll I2C OLED Display |
OLED |
I2C |
21 |
4 |
0,91 |
128 |
32 |
2,58 |
Ja |
0,96 Zoll I2C OLED Display |
OLED |
I2C |
21 |
8 |
0,96 |
128 |
64 |
1,76 |
Ja |
1,3 Zoll I2C OLED Display |
OLED |
I2C |
21 |
8 |
1,3 |
128 |
64 |
2,38 |
Ja |
0,66 Zoll OLED Display Shield |
OLED |
I2C |
10 |
6 |
0,66 |
64 |
48 |
1,83 |
Ja |
1,77 Zoll SPI TFT Farbdisplay |
TFT |
SPI |
21 |
20 |
1,77 |
128 |
160 |
1,68 |
Ja |
1,8 Zoll SPI TFT Farbdisplay |
TFT |
SPI |
21 |
20 |
1,8 |
128 |
160 |
1,70 |
ja |
4 comentarios
Bernd Albrecht
@ Aurelien: Without detailed description of the used hardware, it is difficult to find the proper answer. In general: First problem is the kind of display driver to find the best suited library. Very common controllers are SSH1106 or SSD1306. These libraries offer a lot of possibilities. Therefore, best option is to find the library (subfolder Arduino/libraries) and select the display properties, first of all the resolution. Last general comment: Pixel counting starts in the upper left corner.
Aurelien Vanderstraeten
Hello,
I have follow the book to connect the screen to the arduino but it seems that the screen only works on a little part in the top. I have look on the internet to find an answer but i do not find anything. Could you help me please ? For exemple, i will just write “hello” on the screen but it didn’t work.
Thank you
Veit Burmester
Toller Artikel. Sehr gut erklärt. Die Herleitung der Formel perfekt beschrieben und für mich ( über 60 Jahre)
verständlich. Gerade die OLED- Display mit der Zeiger Anzeige macht was her. Trotz mehrerer Versuche die Anzeige rechts des oder links des Zeigers zu füllen ist mir noch nicht gelungen. Dann noch ein Farbwechsel in Abhängigkeit der Zeigerstellung wäre das Optimum.
Wenn jemand dazu eine Idee hat bitte veröffentlichen.
Viele Grüße und Weiter So
Tero Hemiö
Really nice and easy to follow blog. Thanks!