LEDs Display

© Eberhard Haug 2003-2024

Optimale Darstellung dieser Website
bei aktiviertem
"Active Scripting"
(LED-Menü-Buttons)

In dieser Rubrik sollen LED-basierende Displays und deren Ansteuerung vorgestellt werden.

Hintergrund

Auch wenn ich bei meinen µC-Anwendungen häufig ein LCD zur Anzeige von Text und Zahlen verwende, sind 7-Segment-LED-Displays für kleine Anzeigen mit wenig Digits eine gute Alternative, insbesondere wenn die Anzeige bei schwachem oder stark schwankendem Umgebungslicht immer gut lesbar und ggf. selbst bei Kälte zuverlässig funktionieren soll.

Andererseits sind LED-Anzeigen deutlich cooler als LCDs, auch wenn sie ziemliche Stromfresser sind.


Der kurze Weg geht hier entlang:


LED&KEY - eine Deluxe-LED-Anzeige (25.07.2017)

Zunächst soll ein käufliches LED-Display vorgestellt werden, das außer acht 7-Segment-Anzeigen zusätzlich 8 extra LEDs und 8 Taster auf einer einzigen Platine von ca. 7,5 cm x 5 cm untergebracht hat und somit ziemlich universell ist.

Zudem ist dieses LED-Display auch noch ausgesprochen preisgünstig.

TM1638_LED&KEY


Alle Funktionen werden von einem einzigen IC übernommen, einem TM1638, der (neben einigen Derivaten) häufig für solche LED-Anzeigen verwendet wird, insbesondere wenn auch noch einige (bis zu 24) Tasten abgefragt werden sollen.

Common Cathode

Allerdings muss man beim Bestellen eines solchen Boards aufpassen, denn es gibt zwei verschiedene Ausführungen, nämlich einmal bestückt mit 7-Segment-LEDs mit gemeinsamer Kathode (einfacher im Aufbau und in der Anwendung) oder auch welche mit gemeinsamer Anode (aufwendiger).

Im folgenden Beitrag geht es ausschließlich um die Variante mit gemeinsamen Kathoden und zwar zunächst nur um die Ansteuerung der LEDs und später auch um die Abfrage der vorhandenen Tasten.

Serielle Schnittstelle

Damit die Daten zum Display kommen (bzw. bei Bedarf die Tasten abgefragt werden können), wird beim TM1638 eine einfache serielle Schnittstelle verwendet, die neben Masse und 5V-Versorgung drei Signale benötigt, nämlich einen Takt, ein Strobe-Signal bzw. Chip-Select und ein Daten-Signal (ggf. bidirektional).

Wenn man das nicht gerade ausführliche Datenblatt (aktuell V1.3 in Englisch) studiert, erkennt man schnell, dass man diese Schnittstelle leicht per SPI-Protokoll bedienen kann, sofern man die folgende SPI-Konfiguration verwendet:

  • SPI-Mode 3 (CPOL = 1, CPHA = 1)
  • LSB zuerst senden (DORD = 1)

Man kann das SPI-Protokoll bei fehlender HW-SPI-Schnittstelle sogar sehr einfach per Software nachbilden, wie wir noch sehen werden. Diese Methode (auch unter dem Begriff Bit-Banging bekannt) ist entsprechend flexibel bezüglich Pin-Belegung des verwendeten µC.

Bei der Soft-SPI darf man für bidirektionalen Betrieb (also auch Tastenabfrage) MOSI und MISO am µC direkt verbinden (nicht so bei der HW-SPI) und an DIO des LED&KEY-Boards anschließen.

Hier ein Screen-Shot, wie die Signale eines Soft-SPI für den TM1638 aussehen können:

Soft-SPI


Die Kanäle 1 und 2 sind die tatsächlichen Spannungen von Takt und Daten (40 cm Flachbandkabel zwischen µC und LED-Display) und der Rest sind die drei beschriebenen Logik-Signale separat per Logic-Pod gemessen und ausgewertet.

Dargestellt sind zwei Kommandos 44h (= Adress-Mode "fixed") und C0h (= Display-Adresse 0) gefolgt von einem Daten-Byte 3Fh (= 7-Segment-Ziffer "0").

Die SPI-Taktfrequenz ergibt sich aus dem CPU-Takt von 9,6 MHz für den von mir für dieses Projekt verwendeten ATmega328P und reichlich Soft-Pausen im Programm. Da liegt also noch einiges mehr drin (der TM1638 würde bis zu 1 MHz SPI-Takt zulassen).

Ohne Software geht gar nichts

Da nicht nur ein paar Bytes seriell übertragen werden, sondern (wegen den vielen Möglichkeiten des TM1638) auch teilweise komplexe Kommandos (und ggf. Daten in beide Richtungen), wäre es keine besonders gute Idee, den TM1638 mit einer selbstgestrickten Schieberegister-Hardware bedienen zu wollen.

Die einzig sinnvolle Ansteuerung des TM1638 ist per µC, der nun mal nicht ohne Software läuft.

nach oben


TM1638-Library (25.7.2017)

Nachfolgend soll deshalb ein umfangreiches TM1638-Softwaretreiber-Paket nebst Demoprogramm für die AVR-µC vorgestellt werden, das auf einem in AVR-Assembler geschriebenen Entwurf von Ralf Jardon[1] basiert und von mir (mit seiner Genehmigung) als sauber strukturiertes s’AVR-Programm umgeschrieben und mit ein paar Extras versehen wurde.

TM1638-Demo (25.7.2017)

Zunächst das Hauptprogramm, das abwechselnd einige grafische Effekte mit den LEDs und LED-Segmenten durchführt und dann einen Binärzähler, einen Hexadezimal-Zähler und einen Dezimalzähler (Vornullen unterdrückt) nebst zugehörigem Lauftext demonstriert.

Die beiden letzten Zähler nützen nicht alle 8 Digits aus, denn sonst würde die Demo bei unveränderten Wartezeiten zu lange dauern.

So ist der Dezimalzähler zwar ein 16-Bit-Zähler, wird aber beim Zählerstand 2.048 per EXITIF XH >= #8 angehalten. Durch Auskommentieren dieses EXITIF zählt er natürlich bis 65.535.

Grundsätzlich lässt sich das Programm dank s’AVR einfach erweitern.

Nachfolgend der s’AVR-Code des Hauptprogramms in einem Scroll-Fenster[2] dargestellt.

Hinweise:

  • Auf Smartphones ist der Text je nach verwendetem Browser u.U. nicht scrollbar, sondern wird dann am Stück dargestellt.
  • Die Listings in den Scroll-Fenstern werden laufend aktualisiert. Deshalb ist evtl. die zugehörige Beschreibung im Beitrag nicht immer aktuell (neuerdings weitere Demos und Abbruch durch Abfrage der Tasten).

Das Format

Die Formatierung im Scroll-Fenster stammt direkt aus Atmel Studio und wird über den Umweg WORD und HTML-Format ohne große Nachbearbeitung für die Website verwendet.

Nun, das schaut zwar auf meiner Website ganz schön aus, ist aber wegen der HTML-Formatierung zur Weiterverwendung nicht besonders gut geeignet, denn selbst bei Einfügen als "nur Text" wird nach jeder Zeile unnötigerweise noch eine Leerzeile eingefügt.

Deshalb stelle ich alle Quellprogramme zusammengezippt im Download-Bereich zur Verfügung, jeweils sowohl als strukturiertes s’AVR-Programm, als auch in compilierter AVR-ASM-Form, womit man zur Not mit einem beliebigen AVR-Assembler arbeiten könnte, auch wenn dann der ganze s’AVR-Luxus strukturierter AVR-Assembler-Programmierung dabei fehlt.

Programmänderungen und Einbindung in Atmel Studio

Unter Atmel Studio wird man zunächst ein Assembler-Projekt anlegen und alle Dateien aus dem ZIP-Paket ins Projekt-Verzeichnis kopieren.

Die *.asm-Dateien sind die Compilate der *.s-Dateien, die von mir für s’AVR geschrieben sind, sprich aus dem Hauptprogramm tm1638cc_EH.s wird durch s’AVR das Assembler-Programm tm1638cc_EH.asm erzeugt, das im Projekt unter "Output Files" mit der Eigenschaft "EntryFile" versehen sein muss (siehe s’AVR-Handbuch).

Da das Projekt aus mehreren Dateien besteht, die per .INCLUDE zusammengelinkt werden, kommt nur s’AVR 2.x in Frage, da nur diese Version das Compilieren mit unterschiedlichen Label-Prefixes zulässt.

Im Beispiel wurde Prefix _Lxxxx (voreingestellt) für das Hauptprogramm tm1638cc_EH.s, Prefix _Ixxxx für das Programm tm1638cc.inc_EH.s und _Dxxxx für das Programm tm1638cc_delay_EH.s verwendet (sonst würde es Adresskollisionen geben).

Unter "Tools" wurden bei Atmel Studio diese Einträge gemacht (siehe auch Handbuch 2.x), so dass das Compilieren sehr einfach klappt:

AVR Tools


In der Datei tm1638cc_EH.def (Definitionen) müssen ggf. der gewünschte AVR und dessen Taktfrequenz und die gewünschten Port-Pins geändert werden.

Die Dateien tm1638cc.mac (Macros) und tm1638cc_font.inc (Zeichensatz) bleiben normalerweise unberührt.

Das Anhängsel "_EH" bedeutet immer, dass ich gegenüber den Originalen von Ralf etwas Wesentliches geändert habe.

Den Zeichensatz habe ich mit einigen Kommentaren versehen und zusätzlich zu "-" noch die ASCII-Zeichen "(", ")" und "." (DP) ergänzt.

Ein extra Dezimalpunkt

Der Dezimalpunkt kann entweder als separates Zeichen in einem ASCII-Text verwendet (siehe letzten Textblock im Demo-Programm) oder dynamisch per Software innerhalb eines anderen Zeichens gesetzt werden (wie z.B. beim Dezimalzähler), indem das MSB (Bit 7) an der gewünschten Display-Stelle zusätzlich im zu druckenden Zeichen gesetzt wird.

Die Routine TM1638_PRINT_CHAR bewertet dieses Bit dann als zusätzlichen Dezimalpunkt im selben Zeichen. Mit einer roten Plexiglasscheibe davor schaut das dann schon ziemlich gut aus:

2.048


Die eigentliche Library

Weil’s so schön ist, hier noch die eigentliche TM1638-Library, sprich die vom Hauptprogramm aufgerufenen Unterprogramme - wiederum als s’AVR-Quellprogramm in einem Scroll-Fenster dargestellt (funktioniert leider nicht bei allen Smartphones, deshalb wird es dort eine etwas größere Angelegenheit):

SPI per Bit-Banging

So einfach schaut z.B. das Senden eines Bytes per SPI-Bit-Banging als s’AVR-Programm aus:

TM1638_SEND:

  push COUNT

  FOR COUNT := #8

    rcall delay1us

    ror TM1638_DATA_BYTE       ; put lowest bit into carry flag

    IF NC

      TM1638_CLK_LOW_DATA_LOW  ; carry is not set -> DATA pin LOW

    ELSE

      TM1638_CLK_LOW_DATA_HIGH ; if carry set  -> DATA pin HIGH

    ENDI

      rcall delay1us

      TM1638_CLK_HIGH          ; CLOCK pin HIGH

  ENDF                         ; next bit

  pop COUNT

  ret


Das ist sauber strukturiert programmiert und entsprechend übersichtlich.

Und so würde die Übersetzung von s’AVR 2.x in der effizienten Betriebsart in "flacher" AVR-Assembler-Sprache ausschauen:

TM1638_SEND:

  push  COUNT

  LDI   COUNT,8

_I1:

  rcall delay1us

  ror   TM1638_DATA_BYTE   ; put lowest bit into carry flag

  BRCS  _I4

  TM1638_CLK_LOW_DATA_LOW  ; carry is not set -> DATA pin LOW

  RJMP  _I6

_I5:

_I4:

  TM1638_CLK_LOW_DATA_HIGH ; if carry set  -> DATA pin HIGH

_I6:

  rcall delay1us

  TM1638_CLK_HIGH          ; CLOCK pin HIGH

  DEC   COUNT

  BRNE  _I1

  pop   COUNT

  ret


Die Befehlszeilen in roter Schrift sind s’AVR-Anweisungen bzw. wurden von s’AVR erzeugt, wobei die ursprünglichen s’AVR-Anweisungen hier beim Assembler-Listing unterdrückt sind.

Die mit diesem kleinen Programm erzeugten elektrischen Signale sind fast so gut wie bei einer echten SPI-Schnittstelle per Hardware, siehe Oszillogramme weiter oben (SPI per Bitbanging) und weiter unten (Hardware-SPI).

Weitere Tweaks

Die s’AVR-Version enthält gegenüber der ursprünglichen AVR-ASM-Version von Ralf bereits einige Verbesserungen und Vereinfachungen.

Nun werden Register auch konsequent im aufgerufenen Unterprogramm gesichert und vor dem Verlassen wieder hergestellt, sofern sie vom Unterprogramm selbst verwendet werden. Ausgenommen davon sind im Normalfall die TM1638-Register, die ja bei jedem Aufruf neu geladen werden.

Zusätzlich könnte man einige der Routinen (sowohl im Hauptprogramm als auch in der Library) gegenüber dem Original noch weiter vereinfachen, indem man z.B. die Register TM1638_SEGM_BYTE und TM1638_GRID_BYTE direkt und nicht über einen AKKU-Umweg bedient (es sind ja alles jeweils AVR-Register im oberen Registerbereich R16 ff).
Bei einigen Erweiterungen und bei meinem MAX7219-Programm ist dies bereits der Fall.

Der Rest

Die restlichen Programmdateien will ich mir an dieser Stelle schenken.
Sie sind - wie gesagt - alle im ZIP-Paket enthalten.

Eigene TM1638-Applikationen

Wenn man selbst ein (s’)AVR-Assembler-Projekt mit der vorgestellten TM1638-Library realisieren möchte, muss man nur das Hauptprogramm (sprich tm1638cc.s bzw. tm1638cc.asm) durch das eigene Programm ersetzen.

Die anderen Dateien können bis auf die Anpassungen an den verwendeten AVR-µC und die Pins für die serielle Schnittstelle so gut wie unverändert bleiben.

Unter Linux

Für Linux-Freunde gibt es hier weitere Details.

nach oben


Tastenabfrage (24.8.2017)

Inzwischen ist auch die Tastenabfrage in der TM1638-Library enthalten (siehe oben im Scroll-Fenster ganz am Ende), und zwar als Polling-Version für die acht (der insgesamt 24 möglichen) Tasten[9] S1 bis S8 des LED&KEY-Boards.

Mit diesen Tasten lassen sich jetzt die einzelnen Demo-Programme auswählen (oben im Scroll-Fenster ebenfalls aktualisiert). Die Zähler-Demos laufen nun alle bis zum jeweiligen Maximum, können aber jederzeit per S8 abgebrochen werden.

Die Tasten nicht per Interrupt

Ursprünglich wurde versucht, die Tasten alle 100ms per Interrupt abzufragen und die gedrückten Tasten in einem Register BUTTONS zu hinterlegen. Damit der Datenaustausch mit dem TM1638 aber nicht kollidiert (während z.B. Anzeigedaten ausgegeben werden), müsste man dann u.a. generell Interrupts in der Routine TM1638_SEND_DATA vorübergehend abschalten.

In der schließlich realisierten Polling-Routine kann es solche Konflikte nicht geben. Auch hier werden die gedrückten Tasten (bei der Library dürfen es bis zu 8 gleichzeitig sein) im Register BUTTONS abgelegt.

Die Zuordnung von S1 bis S8 zu bestimmten Bits im Register BUTTONS ergibt sich aus dem TM1638-Protokoll und diversen Schiebe- und Maskierbefehlen zu 5678_1234 (MSB zu LSB).

Ein nach dem Auslesen der Tasten naheliegender abschließender SWAP-Befehl für die etwas schönere Reihenfolge 1234_5678 (MSB zu LSB) ist nicht wirklich praktisch, wenn man später gedrückte Tasten mit den bereits gedrückten Tasten per ODER im Register BUTTONS verknüpfen möchte (wie es bei der Tastenabfrage tatsächlich gemacht wird).

Deshalb wurde die ursprüngliche Reihenfolge belassen, die eigentlich auch nicht stört, da die korrekte Zuordnung zwischen Bit-Stelle und Taste in der Definitionsdatei festgelegt ist, so dass eine Tastenabfrage unabhängig von der Bit-Anordnung in BUTTONS immer z.B. wie folgt ausschauen kann:

Natürlich müssen die Tasten vorher vom TM1638 abgerufen werden, z.B. mit einer REPEAT/UNTIL-Schleife:

  clr  BUTTONS               ; clear last buttons being pressed

  REPEAT

    rcall TM1638_KEYPAD      ; check the TM1638 buttons

  UNTIL BUTTONS <> #0        ; wait until any button pressed


Wenn sich allerdings die Anzeige ändern kann, während auf eine Tasteneingabe gewartet wird, muss die jeweilige Anzeige zwingend zwischendurch aufgefrischt werden.

Hierzu ein Auszug aus dem Demo-Programm für eine Echtzeituhr, die ja immer die aktuelle Uhrzeit anzeigen soll:

  clr  BUTTONS               ; clear last buttons being pressed

  REPEAT

    rcall TM1638_PRINT_CLOCK ; update clock time

    rcall TM1638_KEYPAD      ; check the TM1638 buttons

  UNTIL BUTTONS <> #0        ; wait until any button pressed


Alternativ könnte man auch auf nur eine ganz bestimmte Taste warten:

  clr  BUTTONS               ; clear last buttons being pressed

  REPEAT

    rcall TM1638_KEYPAD      ; check the TM1638 buttons

  UNTIL BUTTONS, S5          ; wait until button S5 pressed


Das dürfte aber eher die Ausnahme sein, denn bei den anderen beiden Beispielen erhält man immer alle Tasten, die man anschließend ohne weiteres Scannen gezielt auswerten kann, siehe Einzel-Test in der "TEST KEY"-Demo und etwas komplexer in der Echtzeituhr-Demo.

Anmerkung:

Sehr präzise

Ansonsten kann ich nur feststellen, dass das Tastendrücken mit der für den TM1638 implementierten Methode richtig Spaß macht: Die Tasten sprechen ausgesprochen schnell und sehr präzise an.

Inzwischen sind die einzelnen Tastenfunktionen der Demo-Routinen hier zusammengestellt.

Es wird Zeit

Hier zunächst ein Foto der Echtzeituhr-Demo (per SELECT-Taste S7), die für eine aktuelle Uhrzeit während der Tastenabfrage laufend aktualisiert wird (siehe zweites s’AVR-Beispiel oben):

Clock-Time

Im Original sieht man den Hintergrund hinter einer dunkelroten Plexiglasscheibe kaum und die Ziffern sind nicht weiß, sondern satt rot. Es will mir kein Foto gelingen, das die LED-Farbe richtig darstellt ...

Mangels echtem Doppelpunkt bei der LED&KEY-Platine sind es für die Demo ersatzweise eben zwei Trennstriche.

Falls man selbst eine Platine mit dem TM1638 entwirft und nur die Uhrzeit anzeigen möchte, wird man für den üblichen Doppelpunkt beim 3. und 6. Digit je zwei runde Einzel-LEDs an zwei der LED-Segment-Ausgänge a-g des TM1638 anschließen.

Genaue Uhrzeit - per Interrupt und Software

Diese Echtzeituhr läuft natürlich per AVR-Interrupts (im 10-msec-Takt) und somit mit der Genauigkeit des verwendeten Quarzes.

Unter Atmel Studio/Tools muss wegen der zusätzlichen Quellprogrammdatei tm1638cc_timer_EH.s noch ein s’AVR-Eintrag mit dem Label-Prefix _Txxxx (z.B.) ergänzt werden. Unter Linux geschieht dies automatisch.

Grundsätzlich muss man zuerst überprüfen, ob der ausgewählte Vorteiler im 16-bit-Compare-Register des AVR auch einen ganzzahligen Wert erlaubt, was aber bei den gewählten Einstellungen[5] im Demo-Programm eigentlich für alle gängigen Quarzfrequenzen von 1,6 MHz bis 20 MHz der Fall sein sollte. Ansonsten läuft die Uhrzeit u.U. schnell aus dem Ruder ...

Obwohl theoretisch 100% genau, war die Demo-Uhr bei meinem ersten Aufbau mit einem 9,6000-MHz-Quarz ohne Eingriff zunächst nahezu 1 Sekunde pro Tag zu langsam. Das sind für diesen Quarz eigentlich akzeptable -12 ppm, denn ±30 ppm sind vom Hersteller spezifiziert.

Mit der genauen Kenntnis der Abweichung kann man diese bei Bedarf per Software in der Interrupt-Routine entsprechend korrigieren.

Zum Bestimmen der Abweichung setzt man die Sekunden der Demo-Uhr im Einstell-Mode (S3 für die Stunden oder S4 für die Minuten) mit der Sekunden-Rücksetztaste S5 (eine sehr praktische Sache, hab ich mir schon immer bei einer Uhr gewünscht!) z.B. synchron zu einer genauen Armbanduhr oder einer jener Online-Atomuhren[6] mit Sekundenanzeige zurück und ermittelt/schätzt die Abweichung der Sekunden nach genau einem Tag oder bei höheren Ansprüchen nach einem längeren Zeitraum.

Das Rücksetzen sollte möglichst nicht zu einer vollen Stunde geschehen, da es sonst zu einer Überschneidung mit der Korrektur per Software kommen kann.

Dieselbe Prozedur wiederholt man nach der Korrektur solange, bis man mit dem Ergebnis zufrieden ist.

Nach einer Korrektur zu jeder vollen Stunde von 30ms/10ms = 3 Ticks (entspricht 720 msec pro Tag) in der 10-ms-Speicherzelle (im SRAM) konnte ich bei meinem Aufbau und bei Zimmertemperatur nach einem Tag Laufzeit kaum mehr eine Abweichung der Sekunden feststellen.

Ein Langzeitgenauigkeitstest der Demo-Uhr steht aber noch aus.

Hier ein Auszug aus dem s’AVR-Quellprogramm tm1638cc_timer_EH.s:

 IF AKKU > #59       ; check minutes

   ldi AKKU,MSADJ/10 ; adjust 10ms time, see tm1638cc_EH.def

   std y+0,AKKU      ; adjustment depending on individual xtal

   clr AKKU

   std y+2,AKKU      ; reset minutes

   ldd AKKU,y+3      ; read hours

   inc AKKU

   std y+3,AKKU      ; hours := hours + 1


Die Korrektur durch die Interrupt-Routine um MSADJ = 30 ms in der 10-ms-Speicherzelle ist rot markiert. Der Wert für MSADJ wird in der Library-Datei tm1638.def festgelegt.

Für eigene Versuche wird man anfangs MSADJ = 0 (also keine Korrektur) nehmen oder die beiden rot markierten Assembler-Zeilen auskommentieren.

Es ist verblüffend, dass man die kurze Verzögerung von ca. 30 ms kurz vor der Stundenkorrektur mit bloßem Auge beobachten kann, wenn man den Sekundenzeiger der korrekten Uhr mit der Anzeige im LED-Display gleichzeitig betrachtet. Ab der vollen Stunde und einer Sekunde (also nach der kleinen Korrektur) sind aber beide wieder für einige Zeit im Gleichtakt (schalten genau im selben Augenblick um).

Mit der beschriebenen Korrekturmethode mit Vielfachen von 10 ms pro Stunde lässt sich die Uhr immerhin auf maximal ±3,6 Sekunden genau pro 30 Tage einstellen. Das sollte im Normalfall ausreichend sein, da man die Uhr sowieso zweimal im Jahr umstellt (bei der Demo-Uhr ganz einfach per S3 und S1/S2).

Falls die Uhr ohne Korrektur zu schnell läuft, ist natürlich ebenfalls eine Korrektur per Software möglich, wenn auch minimal aufwendiger.

Die Laufzeit ab dem letzten Zurücksetzen der Sekunden der Demo-Uhr lässt sich per SELECT-Taste S6 im "Elapsed Time"-Zähler[7] überprüfen, den man natürlich in der Interrupt-Routine ggf. um denselben Betrag korrigieren muss. Per S8 (oder automatisch nach ca. einer Minute) und anschließend S7 geht es dann wieder zur Demo-Uhr.

Alternativ hätte man die Korrektur auch erst nach 24 Stunden um die gesamte Tagesabweichung in der 10-msec-Speicherzelle korrigieren können - sogar noch deutlich genauer (nämlich 2-stellig), aber halt nur einmal am Tag (bzw. mitten in der Nacht). Dann sollte der "Elapsed Time"-Zähler sinnvollerweise auch Vielfache von ganzen Tagen und nicht die Stunden in 100er-Schritten[7] zählen (wie in der Demo).

nach oben


Zwischenzeit (2.1.2018)

Inzwischen habe ich die Echtzeituhr über einen längeren Zeitraum mit der Korrektur laufen lassen: Die Abweichung beträgt in 30 Tagen weniger als eine Sekunde - genauer geht es bei meinem Aufbau mit der beschriebenen Methode nicht!

Damit man die Abweichung generell besser und genauer direkt mit der TM1638-Platine "messen" kann, habe ich den "Elapsed Time"-Zähler so modifiziert, dass man per S5 Zwischenzeiten ablesen kann.

Hierzu ruft man erst die zugehörige Demo per S6 auf (nachdem die Uhrzeit vorher per S7 etc. eingestellt und die Sekunden zurückgesetzt wurden) und nimmt per S5 bei vollen Atomuhrzeit[6]-Minuten (nicht unbedingt zur vollen Stunde) eine Zwischenzeit. Per wiederholtem S5 werden weitere Zwischenzeiten genommen und angezeigt.

Die Anzeige der Zwischenzeit erfolgt zwangsläufig im Kompaktformat HH.MM.SS.MS, womit die vorhandenen 8 Digits ausreichen.

Für eine besonders genaue Zwischenzeit drückt man S5 dauerhaft und lässt zum gewünschten Augenblick los.

Bei dieser Methode gibt es mit etwas Übung nur in der letzten 10-ms-Stelle geringe Abweichungen, womit sich eine Hochrechnung (und ggf. eine Korrektur) bereits nach einem Tag Laufzeit durchführen lässt.

Per S6 läuft der "Elapsed Time"-Zähler wieder ständig und per S8 verlässt man die Demo zurück zu SELECT.

Feinere Korrektur (2.1.2018)

Für einen Langzeittest habe ich mir schon länger einen Arduino-Uno-Clone zugelegt, der standardmäßig mit einem weniger genauen (und ziemlich kleinen) 16-MHz-Keramikresonator läuft. Allerdings hat sich herausgestellt, dass die Resonatorfrequenz um 200ppm zu niedrig lag bzw. die Uhr um ca. 600ms pro Stunde zu langsam lief.

Diese 600ms könnte man zwar mit der bisherigen Methode pro Stunde korrigieren (maximal 990ms), aber eine so ruckartige "Beschleunigung" der Sekunden zur vollen Stunde würde doch ziemlich irritieren.

Deshalb habe ich nun zusätzlich auch noch eine Korrekturmöglichkeit nach jeder Minute im Programm vorgesehen, nämlich wiederum Vielfache von 10ms, sprich an dieser Stelle maximal bis zu 600ms/h.

Das heißt für 600ms/h wird mit 1x 10ms/min und 0x 10ms/h korrigiert. So kleine Korrekturen fallen kaum auf und bereits nach zwei Stunden sieht man, dass die Echtzeituhr damit schon viel genauer läuft.

Damit die ganze Korrekturprozedur möglichst einfach wird, muss man nur in der Definitions-Datei die per Zwischenzeit ohne Korrektur gemessene Abweichung pro Stunde (MSADJ) in msec angeben und der Assembler (zumindest AVRASM2) setzt die richtigen Werte in der Timer-Interrupt-Routine ein. Hier ein Auszug aus dem Assembler-Listing:

                ;02// IF AKKU > #59 ; check seconds

000511 330b     CPI AKKU,59

000512 f0b8     BRLO _T4

000513 f0b1     BREQ _T4

000514 e001     ldi AKKU,MSMIN/10   ; adjust ms/min

000515 8308     std y+0,AKKU        ; store adjustment

000516 2700     clr AKKU

000517 8309     std y+1,AKKU        ; reset seconds

000518 810a     ldd AKKU,y+2        ; read minutes

000519 9503     inc AKKU            ; min := min + 1

00051a 830a     std y+2,AKKU

                ;03// IF AKKU > #59 ; check minutes

00051b 330b     CPI AKKU,59

00051c f068     BRLO _T7

00051d f061     BREQ _T7

00051e e00a     ldi AKKU,MSHRS/10   ; adjust ms/hr

00051f 8308     std y+0,AKKU        ; store adjustment


Alle Programme und das ZIP der TM1638-Library wurden mit diesen Funktionen aktualisiert (und die Zeitanzeige-Routinen bei dieser Gelegenheit von der Library tm1638cc.inc_EH.s ins Hauptprogramm zu den Demos verschoben)

Wenn das Arduino-Board größeren Temperaturschwankungen ausgesetzt ist, sollte man je nach Genauigkeitsansprüchen den Resonator besser durch einen 16MHz-Quarz ersetzen (das wird bestimmt nicht einfach) oder gleich einen quarzbasierenden RTC verwenden.

Tastenfunktion nach Belieben (24.8.2017)

Acht Tasten scheinen zunächst etwas wenig, wenn man komplexere Funktionen mit der TM1638-Platine verwirklichen möchte. Dem ist aber nicht so, denn mit jedem neuen Programm-Menü-Punkt können die Tasten eine andere Bedeutung haben.

So wird im obigen TM1638-Demo-Programm nach dem Reset zunächst ein Tasten-Test angeboten, mit dem man jede Taste S1 bis S8 einzeln überprüfen kann. Deren Nummer wird dann im Display angezeigt.

Diese Routine wird abgebrochen, indem man S1 und S8 gleichzeitig drückt, wodurch anschließend 7 einzelne Demo-Programme per S1 bis S7 im SELECT-Menü aufgerufen werden können. Mit S8 kommt man wieder zum Tasten-Test zurück.

Ist eines der Demo-Programme am Laufen, können zumindest die Zähler- und Timer-Demos per S8 abgebrochen werden, falls sie nicht nach einer gewissen Zeit selbst eine Ende finden. Lediglich die Echtzeituhr und die abgelaufene Zeit werden aktuell angezeigt (bis sie mit S8 beendet werden).

Feinheiten

Wie oben bereits erwähnt, stehen bei der Demo-Uhr (unter SELECT per S7 ausgewählt) die Tasten S3 und S4 zur Einstellung von Stunden und Minuten zur Verfügung. Dann (und nur dann) kann man mit den Tasten S1 und S2 die Stunden bzw. Minuten rückwärts und vorwärts zählen, wenn man länger drückt etwa im 2-Hz-Rhythmus.

In diesem Zustand (und nur in diesem) lassen sich jederzeit auch die Sekunden zurücksetzen. Per S7 verlässt man den Uhrzeit-Einstell-Mode und vermeidet so ein Verstellen durch versehentliches Drücken von S1, S2 oder S5. Per S8 verlässt man die Demo-Uhr schließlich wieder zurück in den SELECT-Mode.

Solche Feinheiten lassen sich mit einer strukturierten Programmierung dank s’AVR sehr einfach, übersichtlich, pflegeleicht und solide in ihrer Funktion gestalten, siehe s’AVR-Hauptprogramm tm1638cc_EH.s.

Weitere TM1638-Timer ganz nach Wunsch

Das Grundgerüst für weitere Timer steht nun also zur Verfügung, so dass man per s’AVR mit wenig Programmieraufwand auch eine Stoppuhr oder einen Timer für die Küche und andere Zwecke realisieren könnte.

Besonders für die Timer oder für einen (abschaltbaren) Sekunden-Tick der Echtzeituhr wäre bestimmt noch eine akustische Signalisierung wünschenswert, die man dann eben per AVR-µC verwirklicht.

Ansonsten ließen sich z.B. mit den Einzel-LEDs LED1 bis LED7 bei der Echtzeituhr noch die Wochentage anzeigen, die man dann im Einstell-Mode per derzeit freier Taste S6 einstellen könnte.

Die Krönung ist dann Uhrzeit und Kalender per DCF77 (natürlich alles per s’AVR geschrieben) - der Prototyp läuft schon länger ...

nach oben


Belegung der Tasten für die TM1638-Demo-Routinen (13.1.2018)

Obwohl man die Verwendung der einzelnen Tasten sehr einfach aus dem s’AVR-Quellprogramm entnehmen kann, nachstehend alle Tastenfunktionen in zusammengefasster Form.

Erste Demoschleife "TEST KEY":

Zweite Demoschleife "SELECT..":

nach oben


Anschluss per Hardware-SPI (25.8.2017)

Die vorliegende TM1638-Demo nebst TM1638-Library wurde sowohl mit der Option SPI per Bitbanging (BB) als auch per Hardware-SPI getestet.

Die BB-Option ist bezüglich der Pin-Belegung sehr flexibel, spart ein I/O-Pin und kann auch bei AVR-µC ohne HW-SPI verwendet werden.

Bei der HW-SPI ist es nötig, zwei Serien-Widerstände (z.B. 100 Ω) in die MOSI/MISO-Leitungen zu legen, die auf der TM1638-Seite zum gemeinsamen DIO verbunden werden. Ansonsten ist u. U. eine Programmierung im System bei angeschlossenem TM1638 nicht möglich bzw. das TM1638-Display muss zum Programmieren abgeklemmt werden und zwar so, dass MOSI und MISO am AVR nicht direkt miteinander verbunden sind.

Das Senden eines Bytes per HW-SPI ist geringfügig einfacher als per Bitbanging.

Hier zunächst als s’AVR-Quellprogramm:

TM1638_SEND:                   ; start SPI transfer

   push AKKU

   out  SPDR, TM1638_DATA_BYTE ; write Data into SPI engine

   REPEAT

      in AKKU, SPSR

   UNTIL AKKU, SPIF            ; Wait for transmission complete

   pop  AKKU

   ret


Und das wäre die Übersetzung von s’AVR 2.x in der effizienten Betriebsart in "flache" AVR-Assembler-Sprache (s’AVR-Befehle bei der Ausgabe unterdrückt):

TM1638_SEND:                   ; start SPI transfer

   push AKKU

   out SPDR, TM1638_DATA_BYTE  ; write Data into SPI engine

_I8:

   in AKKU, SPSR

   SBRS AKKU,SPIF

   RJMP _I8

   pop AKKU

   ret


In diesem einfachen Fall sind es (abgesehen von der separaten Zeile für das erzeugte Label) gleich viele Befehlszeilen, da die einzige s’AVR-Struktur REPEAT/UNTIL hier sehr effizient durch zwei Assembler-Befehle realisiert wird.

Und hier ein Screen-Shot der Hardware-SPI während alle Tasten[9] dauerhaft per Kommando 0x42 gepollt werden:

HW-SPI

Man sieht, dass die Datenleitung des AVR nach dem Kommando 0x42 (42h) von Ausgang auf Eingang geschaltet wird, wodurch die Leitung kurzzeitig per Pull-Up-Widerstand hochgezogen wird, bis der TM1638 viermal die Daten 0x00 (00h) anlegt, d.h. keine der 24 Tasten ist gedrückt.

Die zugehörige Routine heißt TM1638_KEYPAD und befindet sich in der Library ganz am Ende, siehe Listing im Scroll-Fenster weiter oben.

Die Keypad-Routine gilt sowohl für das HW-SPI als auch für das SPI per Bingbanging, denn der Unterschied steckt in den dort aufgerufenen Routinen TM1638_SEND und TM1638_RCV.

Klare Verhältnisse

Inzwischen enthält die Routine TM1638_CLEAR auch die beiden Initialisierungs-Kommandos, so dass das TM1638-Display ggf. zum ISP-Programmieren oder im laufenden Betrieb zwischendurch abgesteckt werden darf, ohne dass ein Reset des AVR-µC nötig ist.

Allerdings sollte TM1638_CLEAR dann im Hauptprogramm ab und zu aufgerufen werden, so wie es bei der TM1638-Demo der Fall ist.

Falls generell auch evtl. leuchtende LEDs (LED1 bis LED8) gelöscht werden sollen, muss in der REPEAT-Schleife von TM1638_CLEAR die COUNT-Schrittweite von 2 auf 1 geändert werden:

 clr    COUNT

 REPEAT

  mov   TM1638_GRID_BYTE, COUNT ; Grid

  ldi   TM1638_SEGM_BYTE, 0x00  ; Segment

  rcall TM1638_SEND_DATA

  subi  COUNT, -1               ; next address

 UNTIL COUNT > #REG_MAX         ; REG_MAX = 0x0F


Oder man verwendet dann gleich eine FOR-Schleife:

 FOR COUNT := #REG_MAX

  mov   TM1638_GRID_BYTE, COUNT ; Grid

  clr   TM1638_SEGM_BYTE        ; Segment

  rcall TM1638_SEND_DATA

 ENDF


Dann muss man einzelne LEDs aber jedesmal auch wieder gezielt einschalten. Deshalb ist diese Methode vielleicht nicht sehr praktisch.

 

Beim Demo-Programm werden die LEDs nach jedem einzelnen Testprogramm überschrieben und gelöscht.

nach oben


7-Segment-LED-Platine mit MAX7219 (3.8.2017)

Das folgende Projekt soll s’AVR-Software für diese kaskadierbare MAX7219-basierende 8-Digit-7-Segment-Anzeige sein:

MAX7219

Die Platine gibt es für wenig Geld fertig zu kaufen. Sie ist normalerweise etwas teurer als die oben beschriebene mit dem TM1638, obwohl sie weder Tasten noch separate Einzel-LEDs hat.

Das liegt vermutlich am verwendeten LED-Treiber MAX7219, der (wie der TM1638) mit dem geringsten Aufwand 7-Segment-Anzeigen mit gemeinsamer Kathode ansteuert.

Außer dem MAX7219 und den beiden 4-fach-7-Segment-Anzeigen gibt es auf der kleinen Platine nur noch einen Pufferkondensator und einen Widerstand, mit dem der maximale Segmentstrom eingestellt wird (10 kΩ für ca. 40mA LED-Strom).

Diese 40mA sind reichlich viel. Deshalb wird in der Library-Routine MAX7219_CLEAR per Software nur der halbe Segmentstrom von ca. 20mA eingestellt.

Natürlich kann man das bei Bedarf per MAX7219_BRIGHTNESS ändern.

HEX voll dekodiert

Interessant ist vielleicht dieser Hinweis für den Fall eines eigenen Aufbaus:

Mehrere Module kaskadiert

Die abgebildete Platine hat zwei Stiftleisten: Links eine zum Anschluss an den µC und rechts (auf dem Foto nicht sichtbar) eine zweite, um weitere solche Platinen hintereinander zu verschalten (leider nicht lückenlos, sondern nur mit großem Abstand).

Das geschieht mit einem SPI-Ausgang beim MAX7219/AS1107, der mit dem SPI-Eingang des jeweils nächsten Moduls verbunden wird (zusätzlich zu den gemeinsamen Chip-Select- und Takt-Anschlüssen).

Der Software-Aufwand dafür ist per s’AVR kaum größer als bei einem einzigen Modul, denn man muss nur genau so viele 16-bit-Datenpakete hintereinander verschicken, wie man Module hat und zwar beginnend mit dem Modul ganz am Ende der Kaskade.

Falls in einem der kaskadierten Module nichts geändert werden soll, gibt es hierfür einen NO-OP-Befehl (Digit-Adresse 0).

Auf diese Art steht nach dem Senden aller Datenpakete eine lange Datenschlange durch alle kaskadierten MAX7219 bereit, die schließlich mit dem Deaktivieren des allen gemeinsamen Chip-Selects (also der steigenden Flanke) individuell in die einzelnen MAX7219/AS1107 geladen wird.

Die zur Verfügung stehende MAX7219-Library ist zunächst für ein Modul geschrieben, eine Erweiterung auf mehrere Module ist wegen der genannten Eigenschaft aber sehr einfach bzw. Teil des Anwendungsprogramms.

So würde man z.B. in MAX7219.inc die beiden Macro-Befehle MAX7219_STB_LOW am Anfang und MAX7219_STB_HIGH am Ende der Routine MAX7219_SEND_WORD entfernen (auskommentieren) und stattdessen diese beiden Macros am Anfang und Ende des gesamten Datenstrings (bestehend aus lauter MAX7219_SEND_WORD ohne die STB-Macros) einfügen.

Alternativ könnte man den gesamten Daten-String auch im RAM aufbereiten und mit einer neuen Routine MAX7219_SEND_STRING verschicken, die ein modifiziertes MAX7219_SEND_WORD ist.

Ähnlich, aber einiges doch etwas anders

Beim MAX7219 gibt es einige Ähnlichkeiten mit dem TM1638 (wie z.B. die SPI-Schnittstelle), aber mit gravierenden Unterschieden:

Die Library basiert eigentlich auf der s’AVR-Version des TM1368, wobei natürlich die genannten Unterschiede eingeflossen sind.
D.h. der Zeichensatz ist komplett neu und die SPI wird gemäß der abweichenden Spezifikation angesteuert, sowohl per Bit-Banging als auch per Hardware-SPI.

Die Änderungen bei SPI sind dennoch eine relativ einfache Angelegenheit. Dabei wurde auch die Pin-Belegung der Bit-Banging-Version so geändert, dass dasselbe Verbindungskabel zwischen µC und MAX7219-Platine verwendet werden kann wie bei der TM1638-Platine.

Da der MAX7219 die HEX-Zeichen 0xA bis 0xF nicht voll ausdekodiert, werden in der Library alle Ausgaben mit dem Soft-Font ausgeführt.

Die Behandlung des Dezimalpunktes als höchstwertiges Bit ist dieselbe wie bei der TM1638-Library.

Test-Mode

Der MAX7219 hat einen speziellen Test-Mode, mit dem man alle 8x 8 Segmente gleichzeitig mit maximaler Helligkeit anzeigen kann.

Wichtig ist, dass man den Test-Mode für Normalbetrieb wieder deaktiviert, denn sonst ist keine andere Anzeige möglich.

Da ich bei meinen Untersuchungen Situationen festgestellt hatte, dass der MAX7219 sporadisch in den Test-Mode ging (vermutlich wegen der relativ langen Zuleitung bei meinem Testaufbau und der geringen Pufferung der 5V-Versorgung auf der Anzeigeplatine) und in diesem Zustand nicht mehr mit den vorhandenen Library-Routinen ansprechbar war, habe ich nach dem Power-On-Reset eine längere Wartezeit vorgesehen und bei jedem Aufruf von MAX7219_CLEAR wird vorsichtshalber auch der Test-Mode deaktiviert. Seitdem habe ich immer eine zuverlässige Anzeige.

Die Demo

Das Demo-Programm wurde gegenüber TM1638 ziemlich "aufgebohrt".

Der Binärzähler ist weiterhin 8 Bit breit (da bei einem Modul nur 8 Digits zur Anzeige zur Verfügung stehen), aber der Hexadezimalzähler ist nun 16 Bit breit und wird entsprechend mit 4 Digits angezeigt, im Programm aber bei 2.048 angehalten, damit die Demo nicht zu lange dauert.

Der Dezimalzähler geht in einer Binärversion nun ebenfalls über 16 Bit Breite und in einer ASCII-BCD-Version sogar über 5 volle Digit. Der Demo-Stop erfolgt hier bei 2.048 bzw. bei 10.000.

Da durch eine kleine Erweiterung ein ASCII-HEX-Zähler dabei abfällt, wurde ein solcher ebenfalls über 5 Digit realisiert (sprich bis F.FF.FF), der für die Demo aber bei 1.00.00 (Hex) angehalten wird.

Da bei beiden BCD-Zählern[4] die Anzeige ab der niederwertigen Stelle erfolgt (nämlich so, wie gezählt wird), fällt bei der Ausgabe automatisch eine Vornullenunterdrückung mit ab, wenn vor Aufruf der Zähler die gesamte Anzeige im Hauptprogramm per MAX7219_CLEAR gelöscht wird.

Und das ist das zugehörige s’AVR-Programm des ASCII-HEX-Zählers in einem Scroll-Fenster (Auszug aus dem s’AVR-Quellprogramm MAX7219.s) - so aufgeräumt schaut strukturierte AVR-Assembler-Sprache aus:

Aufgrund der selbsterklärenden s’AVR-Anweisungen sind im Vergleich zu einem reinen AVR-Assembler-Programm deutlich weniger Kommentare im s’AVR-Quellprogramm nötig, um dieses auf Anhieb zu verstehen.

Zeichen für Zeichen

Um ein einzelnes ASCII-Zeichen an einer bestimmten Stelle des Displays auszugeben, muss das ASCII-Zeichen generell zunächst in das Register MAX7219_DATA und die gewünschte Stelle (1 = ganz rechts bis 8 = ganz links) in Register MAX7219_ADDR geladen und dann die Routine MAX7219_PRINT_CHAR aufgerufen werden.

Mit Dezimalpunkt

Will man auch noch den Dezimalpunkt im selben Digit anzeigen, muss man vor dem Aufruf zusätzlich das höchstwertige Bit des ASCII-Zeichens z.B. per ori MAX7219_DATA, 0x80 setzen. Das ist schon alles.

Um alle 5 HEX-Digits zählen zu lassen, muss man im dargestellten s’AVR-Programm die Programmzeile EXITIF AKKU5 == #'1' (ziemlich am Ende) auskommentieren.

Der Nachteil bei den beiden ASCII-BCD-Zählern ist der Bedarf an fünf Zählerregistern (AKKU bis AKKU5), die aber wegen der ASCII-Darstellung eine sehr einfache Ausgabe auf der Anzeige erlauben.

Deshalb gibt es in der Library auch keine MAX7219_PRINT-Unterprogramme für die beiden ASCII-BCD/HBCD-Zähler.

Beim recht umfangreichen HEX-BCD-Zähler ist gleich zweimal eine größere Sprungweite per REPEAT.m nötig (siehe oben), da der AVR-Assembler sonst einen Range-Fehler reklamiert.
Ansonsten könnte man MAX7219.s auch mit der weniger eleganten und weniger effektiven "schmerzfreien" s’AVR-Option compilieren.

In einem umfangreicheren Anwendungsprogramm wird man die BCD-Zähler sicher von den Registern ins AVR-RAM[8] auslagern (wohlgemerkt, es handelt sich hier um ein Demo-Programm für eine MAX7219-Library).

Außer den diversen Zählern enthält die Demo die kreisenden Segmente wie beim TM1638 und die Dimm-Funktion, allerdings hier logarithmisch in fünf Stufen. Zusätzlich wird noch das ganze Alphabet als Lauftext angezeigt - viel Spaß und gutes Gelingen damit!

nach oben


[1] Hier ein Link zur ASM-Library: https://github.com/Radulfus/TM1638

In diesen Dateien steht auch die vollständige E-Mail-Adresse von Ralf, die ich (wegen eigener schlechter Erfahrung) bewusst in den Scroll-Fenstern ausgeixt habe.

[2] Inline-Frames sind zwar altmodisch, aber in diesem Fall sehr praktisch, denn bei einem veränderten Quellprogramm muss nur die zugehörige Datei ausgetauscht werden.

[3] Man kann den neuen Zeichensatz mit wenig Aufwand durch Spiegelung der unteren 7 Bit des TM1638-Zeichensatzes erzeugen.

[4] Genau genommen müsste man den HEX-Zähler mit BCH = Binary Coded Hexadecimal statt BCD bezeichnen.

[5] Für 100Hz und mit einem Vorteiler von 64 muss OCR1A (16 Bit breit) mit XTAL/6400-1 geladen werden, d.h. die Quarzfrequenz muss ganzzahlig durch 6.400 teilbar sein.

[6] Z.B. die Atomuhrzeit der PTB (https://uhr.ptb.de/), die einen sehr großen Sekundenzeiger hat, der sehr hilfreich beim Simultan-Vergleich ist.

Die Uhrzeit des eigenen PCs wird (je nach Systemeinstellungen) zwar möglicherweise automatisch über das Netzwerk aktualisiert, schwankt bei mir dennoch manchmal um mehr als zwei Sekunden pro Tag, was für einen Abgleich einer Uhr natürlich völlig ungeeignet ist.
Die PTB-Uhr zeigt diese (schwankende) Abweichung als "lokale Abweichung" an.

[7] Ursprünglich wurde im "Elapsed Time"-Zähler die Zeit seit dem Einschalten gezählt und nicht seit dem letzten Zurücksetzen der Sekunden der Echtzeituhr. Dieser Zähler zählt nun bis zu 900 Stunden = 37,5 Tage, wobei die 100-er Stunden per LED8 (ganz rechts , entspricht 100 Stunden) bis LED1 (ganz links, entspricht 800 Stunden) angezeigt werden, zzgl. zu den bis zu 99:59:59 Stunden im Display.

Falls eine noch größere Laufzeit angezeigt werden soll, könnte man die 100er Stunden auch per LEDs binär codieren, also insgesamt bis zu 25.600 Stunden bzw. fast drei Jahre.

[8] Bei der TM1638-Library wird für die Echtzeituhr und für den Timer der abgelaufenen Zeit im SRAM des AVR gezählt.

[9] Beim vorliegenden TM1638-LED&KEY-Board werden die vorhandenen 8 Tasten am gemeinsamen K3-Anschluss (Pin 3, Leiterbahn geht auf der Bestückungsseite nach S1 und ist auf der Unterseite mit S2 bis S8 verbunden) gescannt.
Deshalb gelten in den vier gelesenen Bytes jeweils nur die Bits B0 und B4, siehe Datenblatt V1.3 Seite 7 unter "Keypad scanning and key combination".