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.
... 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:
- h0 gibt die NullPosition für die Stunde an. Wer Mitternacht oben und Mittag unten haben möchte, gibt hier einfach h0=12 an.
- das Gleiche gilt für m0. Wer will, kann die Minuten auch unten anfangen lassen zu zählen.
- in den Codezeilen mit pix.Color(5,0,0) kann die Intensität und Farbe geändert werden. Wer es also heller braucht, sollte hier herumschrauben.
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();
}
}
}