24-Stunden-Uhr mit Neopixel-Ring und 8fach-7Segment-Anzeige

Heute will ich eine 24-Stunden-Uhr bauen, die sowohl über einen Neopixel-Ring mit 24 LEDs die Zeit analog, aber mit 24 anstatt 12 Stunden; als auch über eine MAX7219-8fach-7Segment-LED-Anzeige das Datum und die Uhrzeit digital anzeigt.

Es ist sozusagen die Verschmelzung der Projekte MAX7219 Treiber zur Ansteuerung von 8x8 LED-Matrix und 8fach-7Segment-Display verwenden, Echtzeituhr mit RTC auf 8fach-7Segment-Display anzeigen und WS2812-Neopixel-Ring mit 24 LEDs ansteuern, die ich zuletzt hier vorgestellt habe. Unter den beiden Links gibt es alles zu lesen und lernen, um auch diese Projekt nachbauen zu können.

Das Ganze soll ich ein 3D-gedrucktes Gehäuse wandern und deshalb benutze ich hier einen Arduino Nano (328P) statt eines Arduino UNOs wie sonst. Denn der Nano ist mit 45 x 18mm einfach schön klein und damit kann auch das Gehäuse kleiner sein.


Dieser bietet im Prinzip die selben Möglichkeiten wie ein großer Uno und verfügt ebenfalls über einen ATmega 328P. Nur ist alles ein bisschen kleiner und enger auf der Platine zusammengerückt. Auf der Unterseite befinden sich noch Spannungswandler und ein CH340G Chip für die serielle Kommunikation, so dass man mit der Arduino IDE Sketche auch auf den Nano hochladen kann, ich musste hier allerdings "Bootloader (alt)" auswählen, damit es funktionierte. Die Stromversorgung erfolgt bei meinem Model über Mini USB.

Statt - wie vorgesehen - die Headerleisten durch die Löcher zu stecken und anzulöten, um den Nano dann in ein Breadboard zu stecken, habe ich direkt die Leitungen der Jumper-Wire angelötet und unten mit der Printzange abgeknipst.




Der Nano und alle anderen Bauteile bekamen einen kleinen 3D-gedruckten (orangen) Bumper verpasst, die ich immer drucke und anbringe, damit mir die Pins auf den Unterseiten der Module nicht den Schreibtisch verkratzen. Danach habe ich alles über Dupont-Kabel verbunden und diese an den Modulen mit ein bisschen Tesa-Film fixiert.

Nach einem letzten Test, ob alles funktioniert, waren die Module bereit, in ein selbstgedrucktes Gehäuse eingesetzt zu werden.

























Das Gehäuse ist so designt, dass jedes Modul inkl. Bumper seinen Platz bekommt und so tief versenkt wird, dann die Anzeigekomponenten oben herausschauen, und zwar soviel, wie der Deckel hoch ist, damit alles schön abschließt.



Nachdem der 3D-Druck der Basis - natürlich in weiß, der ca. 7 Stunden in Anspruch nahm und über Nacht lief, fertig war...
... wurden die einzelnen Komponenten in die dafür vorgesehenen Fächer eingesetzt. Alles passte auf Anhieb wunderbar, nur beim Ring musste ich ein klein bisschen nachfeilen, weil er doch ein klein wenig zu eng geraten war. Nano und 7Segment-Anzeige habe ich noch mit einem Klecks Heißkleber fixiert. RTC und Ring saßen so genügend fest. In der Mitte habe ich eine horizontale Aussparung als Kabelkanal vorgesehen.


Während der 3D-Drucker mit dem Deckel beschäftigt war, habe ich noch ein Zifferblatt zum leichteren Ablesen entworfen, gedruckt und aufgeklebt.

Wie man sieht ja ich mich für das Model "Sonnenverlauf" für die Stunden (blau) entschieden. 0 Uhr, also Mitternacht ist ganz unten und 12 Uhr mittags ganz oben, ganz wie der Stand der Sonne.
Bei den Minuten (grün) ist der Nullpunkt wie gewohnt oben.


Der Deckel war auch bald fertig und ließ sich gut anbringen. Nun sind die Innereien verdeckt und nichts lenkt mehr von der Uhrzeitanzeige ab. Wie man hier sieht ist, ist es nach 15 Uhr. Die Intensität der LED bei 5 Minuten gibt an, wie weit der 2,5 Minuten Abschnitt, für die jede LED steht, schon fortgeschritten ist. Je heller, desto "voller" ist der Abschnitt. Hier ist die gelbe LED schon so hel wie die blaue und damit dürfte es schon fast 7,5 Minuten nach 15 Uhr sein.


Ist dieser Zeitpunkt erreicht, springt die LED eins weiter und beginnt dunkel leuchtend von neuem ihren Zyklus. Dabei wird sie hier die blaue Stunde überdecken. Fehlt also der "Stundenzeiger", dann befindet er sich unter dem "Minutenzeiger". Das selbe gilt für die 0, 6, 12 und 18 Uhr Markierungen in dunklerem Rot, die werden genauso "überschrieben".

























Auch, und gerade im Dunkeln macht die Uhr einen tollen Eindruck. Die blaue Stunden-LED leuchtet mit 50 von 255 Einheiten, also weniger als 20% der möglichen Leuchtstärke einer Einzel-LED. Auch die gelbe Minute leuchtet mit nur 2 mal 25 von 255. Die roten LEDs nur mit 5 von 255. Die LEDs voll aufzudrehen wäre blendend hell und kaum auszuhalten.

Rechts unten in der Uhr befindet sich der Arduino Nano, dessen Power-LED ständig leuchtet. Außerdem lass ich die LED an Pin 13 im Sekundentakt blinken. Das ergibt noch einmal einen coolen Effekt durch das leicht durchscheinende, weiße Gehäuse und ähnelt einem schlagendes Herz.

Natürlich gibt es auch diesmal wieder ein kleines Demonstrations-Video, in dem ich noch das Eine oder Andere erkläre:



Sourcecode

//////////////////////////////////////////////////////// // (C) 2019 by Oliver Kuhlemann // // Bei Verwendung freue ich mich über Namensnennung, // // Quellenangabe und Verlinkung // // Quelle: http://cool-web.de/arduino/ // //////////////////////////////////////////////////////// #include "LedControl.h" // Lib für 8fach-7Segment-Anzeige mit MAX7219 #include <Wire.h> // Lib für I2C Kommunikation mit RTC #include "RTClib.h" // RTC-Lib by JeeLabs http://news.jeelabs.org/code/ #include <Adafruit_NeoPixel.h> // Lib für Neopixel-Ring #define PinTaster 11 #define PinNeopixel 4 #define AnzNeopixel 24 RTC_DS1307 RTC; LedControl lc=LedControl(7,5,6,1); // Anzeige initialisieren // 7 = DIN // 5 = CLK // 6 = CS / LOAD // 1 = eine Anzeige / 1 Max7291 Adafruit_NeoPixel pix = Adafruit_NeoPixel(AnzNeopixel, PinNeopixel, NEO_GRB + NEO_KHZ800); int mode=1; // Anzeigemodus 1-3 void setup() { // This is for Trinket 5V 16MHz, you can remove these three lines if you are not using a Trinket #if defined (__AVR_ATtiny85__) if (F_CPU == 16000000) clock_prescale_set(clock_div_1); #endif // End of trinket special code pinMode(PinTaster, INPUT_PULLUP); pinMode(13, OUTPUT); // interne LED pix.begin(); // Neopixel-Ring initialisieren lc.shutdown(0,false); // MAX7219 aufwecken (schläft zu Beginn) lc.setIntensity(0,8); // Helligkeit setzen (0 bis 15) lc.clearDisplay(0); // Display leeren Wire.begin(); RTC.begin(); // RTC-Objekt initialisieren //Serial.begin(115200); //Serial.println (__DATE__); //Serial.println (__TIME__); if (! RTC.isrunning()) { // RT-Clock läuft nicht, initialisieren mit Kompilierungsdatum / Zeit RTC.adjust(DateTime(__DATE__, __TIME__)); } } void segText(String strg) { // schreibt String bis 8 Zeichen auf Segmentanzeige, Punkte werden eingeschoben unsigned int siz=strg.length(); char c; int p; int i; p=7; i=0; while (p>=0) { // nächster Buchstabe ein Punkt, Komma, Doppelpunkt? if (i < siz-1) { c=strg[i+1]; if (c=='.' || c==',' || c=='.' || c==':' || c==';') { lc.setChar(0,p,strg[i],true); siz++; i+=2; p--; continue; } } if (i < siz) { lc.setChar(0,p,strg[i],false); } else { lc.setChar(0,p,' ',false); } i++; p--; } } long waitTaster(long maxWait) { // gibt zurück, wieviele msecs der Taster gedrückt gehalten war long msecs=0; while (digitalRead(PinTaster) == HIGH) { delay (1); msecs++; if (maxWait != 0 && msecs > maxWait) return 0; } // wielange gehalten ? msecs=0; while (digitalRead(PinTaster) == LOW) { delay (1); msecs++; } return msecs; } void incMode() { mode++; if (mode > 3) mode =1; } void showPixTime(int h, int m, int s){ // Zeigt Stunde (h) und Minute (m) auf 24er-Neopixel-Ring int h0=0; // position der Stunde 0 (unten auf 12, wenn Modell Sonne: Mitternacht ganz unten und Mittag ganz oben) // (oben auf 0, wenn Modell bei 0 beginnt der Tag: Mitternacht ganz oben und Mittag ganz unten) int m0=12; // dto. nur für Minutenzeiger // Lötstelle unten markiert Pixel 0 for (int i=0; i<24; i++) pix.setPixelColor(i, pix.Color(0,0,0)); // Uhr-Markierungen auf 0,6,12,18 - in rot und nicht stark leuchtend, sind ja immer an pix.setPixelColor(0, pix.Color(5,0,0)); pix.setPixelColor(6, pix.Color(5,0,0)); pix.setPixelColor(12, pix.Color(5,0,0)); pix.setPixelColor(18, pix.Color(5,0,0)); // Stunde in blau int ph = (h0+h)%24; pix.setPixelColor(ph, pix.Color(0,0,50)); // Minute in grün/gelb, also mischung aus rot und grün, je nach Position im 2.5 Minuten Raum (300 Einheiten) // R und G auf jeweils 25 sind 50 Abstufungen, danach wird es zu grell (meine Meinung, Anpassung leicht möglich) // Position Minute float mf = ((m*60.+s)/60.); int pm = m0+floor((mf/60.*24.)); if (pm >23) pm -= 24; // Farbe/Intensität Minute: je heller der Minuten-Pixel, desto weiter das 2,5 Minuten-Segment fortgeschritten float x= (mf/2.5 - floor(mf/2.5)) *50; // auf 50 Abstufungen abbilden int mr=floor(x/2); int mg=floor(x/2+.5); pix.setPixelColor(pm, pix.Color(mr+1,mg+1,0)); pix.show(); } void loop() { DateTime now; char msg[16]; char c; while (1) { DateTime now = RTC.now(); if (mode == 1) { // Datum/Zeit 30.01.16.52 sprintf (msg, "%02d.%02d.%2d.%02d", now.day(), now.month(), now.hour(), now.minute()); c=now.minute()%10+48; } else if (mode == 2) { // Datum voll 30.01.2019 sprintf (msg, "%02d.%02d.%04d", now.day(), now.month(), now.year()); c=now.year()%10+48; } else if (mode == 3) { // Zeit voll 16.52.34 sprintf (msg, "%02d.%02d.%02d ", now.hour(), now.minute(), now.second()); c=' '; } showPixTime(now.hour(), now.minute(), now.second()); segText (msg); digitalWrite(13,HIGH); if (waitTaster(500) > 0) { incMode(); continue; } digitalWrite(13,LOW); lc.setChar(0,0,c,true); if (waitTaster(500) > 0) { incMode(); continue; } } //end while 1 } //end loop() Den Taster habe ich jetzt zwar nicht verbaut, die Abschnitte im Code aber dringelassen, was ja nicht weiter stört. Vielleicht kommen später ja doch noch Taster, z. B. für die Zeitumstellung dazu.

Interessant und neu gegenüber den bereites bekannten Projekten ist die Funktion showPixTime, die die übergebene Uhrzeit auf dem Neopixel-Ring anzeigt. Diese ist leicht anzupassen: In der Hauptschleife Loop() steht dann gar nicht mehr viel: Es wird die Zeit aus der Echtzeituhr gelesen, auf die 7Segment-Anzeige und den Neopixel-Ring ausgegeben und die LED auf der Arduino-Nano-Platine im Halbsekundentakt an und ausgeschaltet.

Auf dem Arduino sind noch ein paar Pins frei. Wer möchte, kann die Uhr erweitern, z. B. mit einem Fotowiderstand zur automatischen Helligkeit oder Tastern, um die Uhrzeit umstellen zu können - die unsägliche Sommerzeitumstellung ist ja leider immer noch nicht abgeschafft.

Nachtrag 2019-04-03: Die RTC-Zeit "rennt" mir weg

Die Uhr hängt jetzt 2 Monate (genau gesagt 59 Tage) an der Wand. Mir war es schon aufgefallen: die Uhr geht vor. Das hat sich in den zwei Monaten auf 16 Minuten akkumuliert. Macht 16.27 Sekunden Abweichung pro Tag.

Da kenne ich so manche mechanische Automatikuhr, die genauer geht als das Teil. Aber irgendwo musste ja ein Haken sein bei dem Preis.

Doch so ein großes Problem ist das auch wieder nicht. Schließlich haben wir einen vollwertigen Mikrocontroller in der Uhr. Nichts einfacher, als jeden Tag um Mitternacht die Zeit für die RTC-Uhr um 16 Sekunden zurück zu stellen. Müssen wir uns nur noch den Tag der letzten Umstellung merken, und nur umstellen, falls noch nicht geschehen, damit wir nicht in einer Schleifenfalle landen.

// meine Uhr geht 16.27 Sek. pro Tag zu schnell, also 1x pro Tag um 16 Sek. zurücksetzen if (now.day() != tagLetzteUmstellung) { if (now.hour() == 0 && now.minute() == 0) { if (now.second() > 16) { // 16 Sek. weniger speichern RTC.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), now.minute(), now.second()-16)); tagLetzteUmstellung = now.day(); } } }