Mehrstellige 7-Segment-Anzeigen direkt per Schieberegister zum Anzeigen von Daten zu nutzen, bedeutet auf dem Breadboard einen ziemlichen Kabelsalat. Praktischerweise gibt es fertige Module (oft als digital tube bezeichnet) mit vier- oder achtstelligen Anzeigen und den Chips TM1637 oder MAX7219, die mit vier Anschlüssen auskommen. Die vierstelligen TM1637-Module können meist in der Mitte einen Doppelpunkt darstellen und sind primär für Zeitanzeigen gedacht, man kann sie aber auch z.B. als Temperaturanzeige mit Minuszeichen und Gradsymbol nutzen. Es gibt auch Module mit Dezimalpunkten nach den einzelnen Ziffern und anderen Farben als dem standardmäßigen LED-Rot.
Bei deutschen Elektronikversendern ist ein einfaches TM1637-Modul ab Preisen zwischen 1 und 2 € zu bekommen, die achtstelligen Anzeigen mit MAX7219-Chip sind etwas teurer. [1]Je nach Händler können die Preise auch deutlich höher liegen – ein Preisvergleich kann sich lohnen. Bestellungen, Service, auch Reklamationen ohne Sprachbarriere, dazu der hiesige Standort … Continue reading
Das TM1637-Modul
Das Modul hat vier Anschlüsse, die auf der Rückseite beschriftet sind. Von oben nach unten sind das:
- CLK – Taktung der Register
- DIO – Daten
- VCC – Versorgungsspannung (3,3 – 5,25 V)
- GND – Masse
CLK und DIO werden jeweils mit einem beliebigen digitalen Output-Pin verbunden (ich nutze hier GPIO 33 für CLK und GPIO 32 für DIO).
Bibliothek TM1637
Für TM1637-Module gibt es diverse Bibliotheken zum Einbinden in die Arduino-IDE. Ich habe mich für die Bibliothek TM1637 entschieden (Kurzvorstellung im Arduino Playground), weil man damit neben Ziffern und Hexadezimalwerten auch recht einfach andere Zeichen darstellen kann. Die Bibliothek ist am einfachsten über die Bibliotheksverwaltung der Arduino-IDE zu installieren – man findet sie unter dem Namen »TM1637 by Avishay Orpaz«.
Will man sie lieber manuell installieren, geht das wie folgt. (Beide Installationswege werden ausführlich im Beitrag zur Bibliotheksverwaltung der Arduino-IDE erläutert.)
- Download des ZIP-Archivs von Github
- Entpacken der ZIP-Datei im
libraries
-Ordner der Arduino-IDE und Umbenennen des entpackten Ordners inTM1637
Die Bibliothek stellt u.a. folgende Funktionen bereit, deren jeweilige Parameter in der Datei TM1637Display.h
dokumentiert sind:
setBrightness
– stellt die Helligkeit in 8 Stufen (0-7) ein und schaltet die Anzeige an oder ausshowNumberDec
– Anzeige einer Dezimalzahl mit oder ohne führende NullenshowNumberDecEx
– Anzeige einer Dezimalzahl mit Dezimalpunkten oder DoppelpunktshowNumberHexEx
– Anzeige einer Hexadezimalzahl mit Dezimalpunkten oder DoppelpunktsetSegments
– zeigt beliebige Zeichen an, indem einzelne Segmente des Displays gesetzt werden
Anzeige der Zeit eines NTP-Zeitservers
Der ESP32 besitzt (wie andere Mikrocontroller auch) keine Batterie (Akku) zur Erhaltung der internen Zeit – beim Einschalten starten die Zeitfunktionen mit dem Wert 0 Sekunden, was dem 1. Januar 1970 0:00 Uhr entspricht. Um z.B. für die Protokollierung von Daten die tatsächliche Zeit zu ermitteln, kann der ESP32 einen NTP-Zeitserver im Internet abfragen – hierzu müssen (mindestens vorübergehend) die WLAN-Funktionen aktiviert werden. Das Programm zeigt dann im Wechsel auf dem Display Uhrzeit (mit Doppelpunkt) und Datum (Tag und Monat) an.
Als Zeitserver wird häufig die Adresse pool.ntp.org
oder de.pool.ntp.org
verwendet. Wer seinen WLAN-Router als Zeitserver für das lokale Netz konfiguriert hat, sollte den eintragen; bei mir ist es die lokale Adresse fritz.box
. [2]Eine Anleitung zur Einrichtung der FRITZ!Box als lokaler NTP-Server findet man z.B. beim Hersteller AVM. Sinnvolle Einträge für Zeitserver im deutschsprachigen Raum sind
de.pool.ntp.org
(Zeitserver aus dem deutschen NTP-Pool)at.pool.ntp.org
(Zeitserver aus dem östereichischen NTP-Pool)ch.pool.ntp.org
(Zeitserver in der Schweiz)ptbtime1.ptb.de
(Zeitserver der Physikalisch-Technischen Bundesanstalt PTB in Braunschweig; ebensoptbtime2
undptbtime3
)europe.pool.ntp.org
(europäische Zeitserver)pool.ntp.org
(eine Adresse aus dem weltweiten Pool von NTP-Servern)
Allgemeine Informationen zu Zeitservern und dem verwendeten Protokoll NTP (network time protocol) liefern u.a. Wikipedia und zeitserver.de.
Im unten folgenden Programm orientiert sich der Teil zum Setzen der Zeit am Beispielprogramm SimpleTime aus der Arduino-IDE (Menü Datei → Beispiele → ESP32 → Time → SimpleTime
). [3]Per Web-Suche findet man auch andere (meiner Meinung nach umständlichere) Beispiele zur Kommunikation mit NTP-Servern, bei denen erst noch eine NTP-Client-Bibliothek installiert werden muss. Ein … Continue reading
Zeitzonenautomatik
Das Setzen der Zeitzone vereinfacht die Abfrage des NTP-Servers per configTime()
. Standardmäßig erwartet die Funktion als ersten Parameter die Zeitdifferenz der eigenen Zeitzone zu UTC (koordinierte Weltzeit; entspricht der Zeitzone GMT) in Sekunden – für die hiesige Zeitzone CET = UTC + 1 Stunde also den Wert 3600. Zweiter Parameter ist eine weitere Zeitdifferenz – je nach Normal- oder Sommerzeit 0 oder 3600. (CET steht für Central European Time = MEZ = Mitteleuropäische Zeit.)
Verwendet man die hier gezeigte automatische Anpassung der Zeitzone mit der Funktion setenv()
, lässt man die beiden ersten Parameter von configTime()
bei 0 und spart sich die Rechnerei. Für die deutschsprachigen Länder lautet die korrekte Zeitzonenangabe wie folgt:[4]Eine Liste der Zeitzonen inkl. Angaben zur Sommerzeit findet sich auf github.com/nayarsystems/posix_tz_db/blob/master/zones.csv. Den Hinweis darauf habe ich auf fipsok.de gefunden.
#define TIMEZONE "CET-1CEST,M3.5.0/02,M10.5.0/03" [...] configTime(0, 0, ntp_server); // vor Trennung des WLANs einmal getLocalTime() // aufrufen; sonst wird die Zeit nicht übernommen getLocalTime(&tdata); setenv("TZ", TIMEZONE, 1); // Zeitzone einstellen
Oder etwas kürzer mit configTzTime()
statt configTime()
und setenv()
:
#define TIMEZONE "CET-1CEST,M3.5.0/02,M10.5.0/03" [...] configTzTime(TIMEZONE, ntp_server); // vor Trennung des WLANs einmal getLocalTime() // aufrufen; sonst wird die Zeit nicht übernommen getLocalTime(&tdata);
Anmerkung zu configTime()
Mir ist es nicht gelungen, die Zeit vom NTP-Server zu übernehmen, ohne vor dem Abschalten der WLAN-Funktionalität einmal die Funktion getLocalTime()
aufzurufen. Es ist egal, ob man – wie hier – einen nicht ausgewerteten Aufruf der Funktion macht oder wie im Arduino-Beispielprogramm die Daten z.B. in den seriellen Monitor ausgibt. Wenn man configTime()
ohne anschließendes getLocalTime()
aufruft, wird die Zeit nicht gesetzt, sondern startet bei Null (01.01.1970 0:00:00 Uhr). Das hat mich einige Zeit zur Fehlersuche gekostet; den Grund konnte ich nicht finden und ich würde mich freuen, wenn es jemand weiß und mir per Kommentar mitteilt.
Das hier gezeigte Programm nutzt aus Bequemlichkeit führende Nullen zur Darstellung von Zeit und Datum. Will man Leerzeichen verwenden (um z.B. in der Datumsdarstellung die einstelligen Monate nur mit einer Ziffer anzuzeigen), muss man die Zeichen des Displays einzeln setzen. Wie das geht, zeigt das zweite Programm weiter unten.
WLAN-Name (SSID; service set identifier) und Passwort müssen zur Anmeldung im eigenen WLAN angepasst werden. Auch der Name des Zeitservers muss ggf. statt des lokalen Routers (im Beispiel: fritz.box
) auf einen Server im Internet gesetzt werden.
/* Zeit von einem NTP-Server holen und auf dem TM1637-LED-Modul anzeigen
*
* 2019-07-17 Heiko (unsinnsbasis.de)
*/
#include <WiFi.h>
#include "time.h"
#include <TM1637Display.h>
const char* ssid = "Deine_WLAN_SSID";
const char* password = "Dein_WLAN_Passwort";
const char* ntp_server = "fritz.box"; // oder (de/at/ch).pool.ntp.org
// lokale Zeitzone definieren
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
#define TIMEZONE "CET-1CEST,M3.5.0/02,M10.5.0/03"
#define CLK_PIN 33
#define DIO_PIN 32
TM1637Display display(CLK_PIN, DIO_PIN); // Datenstruktur für Display
void setup() {
struct tm tdata;
Serial.begin(115200);
// mit dem WLAN verbinden; nach Ermitteln der Zeit kann die
// Verbindung wieder getrennt werden
Serial.printf("Verbindung herstellen mit %s ", ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" Verbunden!");
// Zeit vom NTP-Server holen
configTime(0, 0, ntp_server);
// vor Trennung des WLANs einmal getLocalTime()
// aufrufen; sonst wird die Zeit nicht übernommen
getLocalTime(&tdata);
setenv("TZ", TIMEZONE, 1); // Zeitzone einstellen
// WLAN trennen
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
// Display auf mittl. Helligkeit einstellen
display.setBrightness(3);
}
void loop() {
struct tm tdata;
if (!getLocalTime(&tdata)) {
Serial.println("Fehler beim Ermitteln der Zeit");
delay (10000);
} else {
// Zeit als Zahl HHMM mit Doppelpunkt in der Mitte und führenden Nullen
// (sonst werden Zeiten in der ersten Stunde als xx statt 00:xx angezeigt)
display.showNumberDecEx((tdata.tm_hour)*100 + tdata.tm_min, 0b01000000, true);
delay(2000);
// Datum als Zahl TTMM mit führender Null
display.showNumberDec(tdata.tm_mday*100 + tdata.tm_mon+1, true);
delay(1000);
}
}
Buchstaben und andere Zeichen als Laufschrift

Neben Zahlen kann man mit einer 7-Segment-LED auch eine ganze Menge Buchstaben und ein paar Sonderzeichen anzeigen. Hierzu muss man die einzelnen Segmente passend setzen. Die Position der Segmente und Berechnung der Zeichenwerte hatte ich ausführlich im Beitrag zu den 7-Segment-Anzeigen erläutert. Hier nur ein kurzes Beispiel: um ein kleines „o” anzuzeigen, müssen die Segmente C, D, E und G angeschaltet sein; als Zeichencode ergibt sich 0b01011100 = 22 + 23 + 24 + 26 = 4 + 8 + 16 + 64 = 92 = 0x5C.
Im Programm ist für jedes darstellbare Zeichen der entsprechende Hexadezimalwert kodiert. Da die TM1637-Bibliothek passende Konstanten bereitstellt (SEG_A
– SEG_G
), könnte man die Werte auch durch Oder-Verknüpfung der Segment-Werte darstellen, z.B.:
code_o_lower = SEG_C | SEG_D | SEG_E | SEG_G;
Das folgende Programm ergänzt die TM1637-Bibliothek um Funktionen zur Zeichendarstellung: [5]Sauberer wäre es natürlich, die Klasse display
um Methoden zu erweitern, die diesen Funktionen entsprechen. Vielleicht später einmal… ;-)
encodeChar()
liefert zu einem Zeichen den 7-Segment-Code zurück. Nicht darstellbare Zeichen erzeugen ein Leerzeichen.encodeString()
übersetzt eine Zeichenkette in 7-Segment-Codes. Parameter sind:- (Zeiger auf den) Text
- ein Zeiger auf ein Array, das die codierten Zeichen aufnimmt
- Textlänge; Default: Displaygröße (4 Zeichen)
scrollText()
zeigt den übergebenen Text als Laufschrift an. Es werden immer so viele Zeichen dargestellt, wie auf das Display passen. Nach einer einstellbaren Pause wandert die Anzeige eine Zeichenposition weiter. Texte mit kürzerer Länge als die Displaygröße werden mit Leerzeichen aufgefüllt. Parameter:- (Zeiger auf den) Text
- Pause in Millisekunden, bis die Anzeige ein Zeichen vorrückt; Default: 300 ms
Das vierstellige Display ist etwas schmal für Laufschrift – das Programm sollte sich aber leicht an die achtstelligen MAX7219-Displays anpassen lassen, wenn die Bibliothek eine ähnliche Methode wie setSegments()
als Unterbau zur Verfügung stellt.
/* Text auf TM1637 als Laufschrift anzeigen
*
* 2019-07-20 Heiko (unsinnsbasis.de)
*/
#include <TM1637Display.h>
#define DISPLAY_SIZE 4 // vereinfacht Portierung auf größere Displays
#define CLK_PIN 33
#define DIO_PIN 32
TM1637Display display(CLK_PIN, DIO_PIN); // Datenstruktur für Display
// Codes für alle auf einer 7-Segment-Anzeige darstellbaren Zeichen
const uint8_t charToSegment[] =
// G F E D C B A
// HEX digits 0-9, A-F
{ 0x3f, // 0 1 1 1 1 1 1 - 0 (and uppercase o)
0x06, // 0 0 0 0 1 1 0 - 1 (and uppercase i, lowercase l)
0x5b, // 1 0 1 1 0 1 1 - 2
0x4f, // 1 0 0 1 1 1 1 - 3
0x66, // 1 1 0 0 1 1 0 - 4
0x6d, // 1 1 0 1 1 0 1 - 5 (and uppercase s)
0x7d, // 1 1 1 1 1 0 1 - 6
0x07, // 0 0 0 0 1 1 1 - 7
0x7f, // 1 1 1 1 1 1 1 - 8 (and uppercase b)
0x6f, // 1 1 0 1 1 1 1 - 9
0x77, // 1 1 1 0 1 1 1 - A
0x7c, // 1 1 1 1 1 0 0 - b
0x39, // 0 1 1 1 0 0 1 - C
0x5e, // 1 0 1 1 1 1 0 - d
0x79, // 1 1 1 1 0 0 1 - E
0x71, // 1 1 1 0 0 0 1 - F
// additional characters
// for words like: Lo, Hi, PUSH (PU5H), PULL, UP...
0x7f, // 1 1 1 1 1 1 1 - B (and digit 8)
0x58, // 1 0 1 1 0 0 0 - c
0x76, // 1 1 1 0 1 1 0 - H
0x74, // 1 1 1 0 1 0 0 - h
0x06, // 0 0 0 0 1 1 0 - I (and digit 1, lowercase l)
0x04, // 0 0 0 0 1 0 0 - i
0x0e, // 0 0 0 1 1 1 0 - J
0x38, // 0 1 1 1 0 0 0 - L
0x06, // 0 0 0 0 1 1 0 - l (and digit 1, uppercase I)
0x54, // 1 0 1 0 1 0 0 - n
0x3f, // 0 1 1 1 1 1 1 - O (and digit 0)
0x5c, // 1 0 1 1 1 0 0 - o
0x73, // 1 1 1 0 0 1 1 - P
0x67, // 1 1 0 0 1 1 1 - q
0x50, // 1 0 1 0 0 0 0 - r
0x6d, // 1 1 0 1 1 0 1 - S (and digit 5)
0x78, // 1 1 1 1 0 0 0 - t
0x3e, // 0 1 1 1 1 1 0 - U
0x1c, // 0 0 1 1 1 0 0 - u
0x6e, // 1 1 0 1 1 1 0 - y
0x40, // 1 0 0 0 0 0 0 - minus
0x08, // 0 0 0 1 0 0 0 - underscore
0x00 // 0 0 0 0 0 0 0 - space
};
// alle darstellbaren Zeichen (in gleicher Reihenfolge wie oben)
const char *sevenSegChars = "0123456789AbCdEFBcHhIiJLlnOoPqrStUuy-_ ";
// Das Gradsymbol (°) muss extra codiert werden, weil es nicht Standard-ASCII
// ist, sondern UTF-codiert zwei Ausgabezeichen erzeugen würde
const uint8_t codeDegree = 0x63; // 1 1 0 0 0 1 1
const uint8_t codeMinus = 0x40; // 1 0 0 0 0 0 0
const uint8_t codeUnderscore = 0x08; // 0 0 0 1 0 0 0
const uint8_t codeBlank = 0x00; // 0 0 0 0 0 0 0
// encodeChar liefert zu einem Zeichen den 7-Segment-Code zurück.
// Nicht darstellbare Zeichen erzeugen ein Leerzeichen.
//
uint8_t encodeChar(char ch) {
char *pos;
pos = strchr(sevenSegChars, ch);
if (pos == NULL) {
return codeBlank;
} else {
return charToSegment[pos - sevenSegChars];
}
}
// Eine Zeichenkette wird in 7-Segment-Codes übersetzt.
// Parameter:
// - Text
// - Referenz auf das Ziel zum Speichern der codierten Zeichen
// - Textlänge (Default: Displaygröße (4))
//
void encodeString(char *src, uint8_t *tgt, int len=DISPLAY_SIZE) {
int i;
for (i=0; i<len && i<strlen(src); i++) {
tgt[i] = encodeChar(src[i]);
}
}
// scrollText erzeugt eine Laufschrift des Textes. Es werden immer so
// viele Zeichen dargestellt wie auf das Display passen. Nach einer
// einstellbaren Pause wandert die Anzeige eine Position weiter.
// Texte mit kürzerer Länge als die Displaygröße werden mit Leerzeichen
// aufgefüllt.
// Parameter:
// - Text
// - Pause in Millisekunden bis zum Anzeigen des nächsten Zeichens
//
void scrollText(char *text, int pause=300) {
uint8_t codes[DISPLAY_SIZE];
int i;
if (strlen(text) < DISPLAY_SIZE) {
encodeString(text, codes, strlen(text));
// überschüssige Segmente mit Leerzeichen auffüllen
for (i=strlen(text); i<DISPLAY_SIZE; i++) {
codes[i] = codeBlank;
}
display.setSegments(codes, DISPLAY_SIZE);
delay(pause);
} else {
for (i=0; i<strlen(text)-DISPLAY_SIZE+1; i++) {
encodeString(text+i, codes, DISPLAY_SIZE);
display.setSegments(codes, DISPLAY_SIZE);
delay(pause);
}
}
}
void setup() {
// Display auf mittl. Helligkeit einstellen
display.setBrightness(3);
}
void loop() {
uint8_t segments[DISPLAY_SIZE];
scrollText("thiS codE iS Funny");
display.clear();
delay(1000);
// Text mit ein paar nicht darstellbaren Zeichen
scrollText("THIS CODE RunS FAStEr but hAS BLANKS", 200);
display.clear();
delay(1000);
scrollText("thiS runS too FASt to rEAd", 80);
display.clear();
delay(1000);
// kurzer Text
scrollText("Hi", 1000);
display.clear();
delay(1000);
scrollText("0123456789AbCdEFBcHhIiJLlnOoPqrStUuy");
display.clear();
delay(1000);
// einzelne Segmente mit Zeichen belegen
segments[0] = codeDegree;
segments[1] = codeMinus;
segments[2] = codeBlank;
segments[3] = codeUnderscore;
display.setSegments(segments);
delay(1000);
display.clear();
delay(1000);
}
Material und Datenblatt
Die Material-Liste ist kurz. Neben dem ESP32 habe ich verwendet:
- 1 TM1637-Modul
- 1 kleines Breadboard
- 4 Dupont- oder Jumperkabel mit Male/Female-Anschlüssen (Stecker und Buchse)
(wenn man das Modul ohne Breadboard direkt mit den ESP32-Pins verbindet, benötigt man Female/Female-Kabel)
TM1637 Datenblatt
Fußnoten
1↑ | Je nach Händler können die Preise auch deutlich höher liegen – ein Preisvergleich kann sich lohnen. Bestellungen, Service, auch Reklamationen ohne Sprachbarriere, dazu der hiesige Standort mit zusätzlichen Arbeitsplätzen sowie Steuern und Zoll, rechtliche Auflagen wie RoHS und Richtlinien zur Entsorgung von Elektroschrott, Gewährleistung usw. erklären die höheren Preise im Vergleich zu einer Direktbestellung in China über die großen Handelsplattformen. Angeboten werden häufig die gleichen Artikel aus China, für die man dort aufgrund der subventionierten, lächerlich kleinen Versandkosten ohne Mindestbestellwert auch in kleinen Stückzahlen teilweise nur halb so viel bezahlt wie hier, oft noch weniger. TM1637-Module findet man bspw. schon für ca. 50 Cent plus Versandkosten – dafür muss man mehrwöchige Lieferzeiten einplanen; Reklamationen sind aufwendig, Rücksendungen lohnen eher nicht. Auch Dokumentationen wie Datenblätter muss man selbst suchen; von „vernünftigen” Rechnungen mit Angabe der Mehrwertsteuer ganz zu schweigen. Außerdem wird auf größere Bestellungen aus dem Nicht-EU-Ausland ab 22 € Einfuhrumsatzsteuer fällig und man muss sie beim Zoll abholen – zumindest theoretisch. In der Praxis wird das oft umgangen, indem ein geringerer Warenwert auf der Sendung angegeben wird (und eine Rechnung liegt ja nicht bei). Trotzdem sind die kleineren Preise bei privaten Direktbestellungen natürlich verlockend. Ich will hier für keine der Optionen Werbung machen, sondern nur ein paar Hinweise zur Ursache der Preisunterschiede geben. |
---|---|
2↑ | Eine Anleitung zur Einrichtung der FRITZ!Box als lokaler NTP-Server findet man z.B. beim Hersteller AVM. |
3↑ | Per Web-Suche findet man auch andere (meiner Meinung nach umständlichere) Beispiele zur Kommunikation mit NTP-Servern, bei denen erst noch eine NTP-Client-Bibliothek installiert werden muss. Ein solches Beispiel hat Random Nerd Tutorials. |
4↑ | Eine Liste der Zeitzonen inkl. Angaben zur Sommerzeit findet sich auf github.com/nayarsystems/posix_tz_db/blob/master/zones.csv. Den Hinweis darauf habe ich auf fipsok.de gefunden. |
5↑ | Sauberer wäre es natürlich, die Klasse display um Methoden zu erweitern, die diesen Funktionen entsprechen. Vielleicht später einmal… ;-) |
Themenverwandte Beiträge
I2C ist ein serieller Datenbus, über den ein Mikrocontroller mit nur zwei Steuerleitungen mehrere ve...
Die monochromen SSD1306-OLED-Displays sind eine relativ günstige und stromsparende Möglichkeit, Mikr...
ESP32 und Arduino Nano unterscheiden sich in der Hardware erheblich. Insbesondere was die Kommunikat...
Der TEMT6000 ist ein Lichtsensor, der seine Messwerte analog ausgibt. Er besteht im wesentlichen aus...