Projektvorstellung: Die Hangover Clock

Hallo zusammen, ich würde gerne ein weiteres Projekt vorstellen.

Einer meiner Freunde hat nicht sonderlich viel Glück mit der Liebe, bei ihm muss ich deshalb immer an ein bestimmtes Bild denken.

Jetzt habe ich mich mal ans Werk gemacht und ihm eine solche Uhr als Einzugsgeschenk für seine neue Wohnung gebaut.

Diese Uhr ist ein toller Gag und Aufhänger für Gespräche, anbei findet ihr alles was ihr braucht um solch eine Uhr nachzubauen.

Materialliste:

Wemos D1 mini v3.0 (oder anderes ESP8266 / ESP32 Board)
TM1637 0,56" 7-Segment Anzeige (Grün)
DS3231 ZS-042 Modul
(Plexiglasscheibe)
(Autotönungsfolie)

Wenn ihr euch für eine andere Farbe beim 7-Segment Display entscheidet, dann solltet ihr eine Plexiglasscheibe mit Autotönungsfolie folieren, die Displays lassen sich sonst schon ab geringer Entfernung nicht mehr angenehm ablesen. Das sieht man auch wunderbar auf dem Foto, dieses habe ich mal bewusst ohne eine getönte Scheibe für euch aufgenommen.

Die Verkabelung:
TM1637:
CLK <-> GPIO 16 (Wemos D1 mini v3.0: D0)
DIO <-> GPIO 14 (Wemos D1 mini v3.0: D5)
GND <-> GND
5V <-> 5V

DS3231 (Unbedingt die Ladeschaltung deaktivieren, hierzu muss ein Widerstand entfernt werden!):
SCL <-> SCL Pin eures Boards (Wemos D1 mini v3.0: D1)
SDA <-> SDA Pin eures Boards (Wemos D1 mini v3.0: D2)
VCC <-> 3V3
GND <-> GND

Zum Sketch: Dieser ist sofort lauffähig auf ESP8266 Boards, bei ESP32 Boards muss allerdings die WiFi Bibliothek in #include <WiFi.h> geändert werden. Zusätzlich muss die komplette Funktion bool getLocalTime ausgeklammert werden.

/* Quellenangaben: 
 * DS3231 RTC - Zeit per NTP setzen und aus der RTC auslesen, 2019-07-21 Heiko (https://web.archive.org/web/20220804035404/https://unsinnsbasis.de/ds3231-rtc/)
 * Funktion calculateoverhang: https://github.com/programminghoch10/HangoverClock/blob/master/app/src/main/java/com/JJ/hangoverclock/ClockGenerator.java
 * Restlicher Sketch: https://forum.arduino.cc/t/projektvorstellung-die-hangover-clock/1211889
 */
 
#include <Wire.h>
#include "RTClib.h"
#include <ESP8266WiFi.h>
#include "time.h"
#include <sys/time.h>

#include <TM1637Display.h>
#define CLK_PIN 16
#define DIO_PIN 14
TM1637Display display(CLK_PIN, DIO_PIN);  // Datenstruktur für Display
 
RTC_DS3231 rtc;
struct tm tdata;  // lokale Zeit
 
const char* ssid = "Uhrzeit";
const char* password = "00000000";
const char* ntp_server = "de.pool.ntp.org"; // oder (de/at/ch).pool.ntp.org // fritz.box
 
// 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"
 
char days_of_week[7][11] = {"Sonntag", "Montag", "Dienstag", "Mittwoch",
        "Donnerstag", "Freitag", "Samstag"};

int twelvehoursnumber = 24;
int minuteoverhang = 40;
int houroverhang = 0;

bool firstloop = true;

const uint8_t conn[] = {
  SEG_D | SEG_E | SEG_G,           // c
  SEG_C | SEG_D | SEG_E | SEG_G,   // o
  SEG_C | SEG_E | SEG_G,           // n
  SEG_C | SEG_E | SEG_G            // n
};

bool getLocalTime(struct tm * info, uint32_t ms = 5000)
{
    uint32_t start = millis();
    time_t now;
    while((millis()-start) <= ms) {
        time(&now);
        localtime_r(&now, info);
        if(info->tm_year > (2016 - 1900)){
            return true;
        }
        delay(10);
    }
    return false;
}
 
void setup () {
  DateTime now;
  timeval tv;
  int rc;
 
  Serial.begin(115200);

  display.setBrightness(3);
  delay(500);
 
  if (! rtc.begin()) {
    Serial.println("Keine Echtzeituhr gefunden!");
    while (1);
  }
 
  if (rtc.lostPower()) {
    Serial.println("RTC wird neu gestellt");
 
    // 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) {
      display.setSegments(conn);
      delay(500);
      Serial.print(".");
    }
    Serial.println(" Verbunden!");
    delay(1000);
 
    // Zeit vom NTP-Server holen
    configTime(0, 0, ntp_server);
    // vor Trennung des WLANs einmal getLocalTime() aufrufen
    getLocalTime(&tdata);
    setenv("TZ", TIMEZONE, 1);  // Zeitzone einstellen
 
    // WLAN trennen
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
 
    // RTC anhand der Systemzeit stellen
    rtc.adjust(time(NULL));
  } else {
    // (System-)Zeit aus der RTC übernehmen
    now = rtc.now();
    tv.tv_sec = now.unixtime();
    tv.tv_usec = 0;  // keine Mikrosekunden setzen
    rc = settimeofday(&tv, NULL);  // NULL = keine Zeitzoneninfo an dieser Stelle ...
    setenv("TZ", TIMEZONE, 1);  // ... sondern hier: Zeitzone einstellen
    Serial.printf("Returncode beim Setzen der Systemzeit nach der RTC: %d\n\n", rc);
  }
}
 
void loop () {
    DateTime now = rtc.now();
    if (firstloop == true) {
    Serial.printf("RTC Unix-Zeit - Datum TT.MM.JJJJ und Zeit: %s %02d.%02d.%d %02d:%02d:%02d\n",
        days_of_week[now.dayOfTheWeek()],
        now.day(), now.month(), now.year(), 
        now.hour(), now.minute(), now.second());
    Serial.printf("RTC Unix-Zeit seit 1.1.1970 - %d Sekunden bzw. %d Tage\n", 
        now.unixtime(), now.unixtime() / 86400L);
    }
    getLocalTime(&tdata);
    if (firstloop == true) {
    Serial.printf("Systemzeit: time() = %d\n", time(NULL));
    Serial.printf("Lokale Uhrzeit (aus Systemzeit): %02d:%02d:%02d\n", 
        tdata.tm_hour, tdata.tm_min, tdata.tm_sec);
    Serial.printf("RTC-Temperatur: %2.1f °C\n\n",
        rtc.getTemperature());
    //delay(10000);
    }
    calculateoverhang(tdata.tm_hour, tdata.tm_min);
    delay(1000);
}

void calculateoverhang(int h, int m) {
  if (minuteoverhang % 60 > m) m += 60;
  m += (minuteoverhang / 60) * 60;
  h -= (m / 60) % twelvehoursnumber;
  if (houroverhang % twelvehoursnumber > h) h += twelvehoursnumber;
  h += (houroverhang / twelvehoursnumber) * twelvehoursnumber;

  if (firstloop == true) {
  Serial.println();
  Serial.print(h);
  Serial.print(":");
  Serial.println(m);
  firstloop = false;
  }

display.showNumberDecEx((h)*100 + m, 0b01000000, true);
}

Beim ersten Start solltet ihr "conn" auf dem Display sehen, das bedeutet, dass keine Uhrzeit auf dem DS3231 Modul gespeichert ist. Aktiviert also einen Hotspot mit eurem Handy mit der SSID "Uhrzeit" und acht nullen "00000000" als Passwort. Das Board zieht sich dann die Uhrzeit von einem Zeitserver.

Die Uhrzeit geht dann anschließend immer bis XX:99 und schlägt dann auf XX:40 um. Dabei kann man die Uhrzeit aber immer ablesen, 15:98 ist dementsprechend 16:38.

Wenn jemand die Uhr nachgebaut hat, poste doch gerne ein Foto in den Thread!

PS: Verpasst eure Termine nicht. :wink:

1 Like

Hi
Ich finde die Idee/Uhr ganz nett

Habe jetzt endlich die Zeit gefunden/erobert sie nachzubilden.

Verwendet habe ich mangels Alternativen ein Suchtipp: "Arduino Multi Function Shield" auf einem Arduino UNO Klon.
Auf die Schnelle aus meiner Wühlkiste Code eingesammelt und zu etwas zusammen gedengelt, was keine Fehler wirft.
Natürlich auch von dir den "Hang Over Code" geklaut.

Fehlt noch was?
Klar, ganz viel.

  1. Uhr stellen
  2. Sommerzeit
  3. Größe (will 5cm hohe 7 Segment Dinger)

Hat es etwas besonderes?
Klar!

  1. Hang Over
  2. den Multiplexer in einer ISR

Hier der Code (3 Tabs in der IDE):
SevenSegment.cpp (1,4 KB)
SevenSegment.h (283 Bytes)
HangOverUhr.ino (877 Bytes)
Die INTERVAL. h findet sich als Lib hier im Forum irgendwo.

Einen Schönheitswettbewerb wird der Code nicht gewinnen, aber manchmal heiligt ja auch der Zweck die Mittel.
Gerne hätte ich auch einen Doppelpunkt in der Anzeige, aber bei dem Shield muss ich mich mit so einem blinkenden Dezimalpunkt begnügen.

1 Like

Hey Combie, freut mich und danke fürs Teilen des Sketches.

Ich werde mir wenn die Teile da sind noch eine Uhr mit einem LCD1602 Display bauen, das macht sich auf dem Schreibtisch sicherlich ganz hübsch und ich kann zusätzlich noch das Datum mit anzeigen.

Über sowas habe ich auch nachgedacht.
Mein Resultat: Wenn dann ein 2004 LCD, mit einem Big Font

Wobei mir allerdings die LED Dinger besser gefallen, die haben so einen netten 70er Jahre Flair.

So ein Vacuum Fluorescent Display wäre auch was feines, wenn es dir um Flair geht.

Leider ohne Controller
https://a.aliexpress.com/_EzIbGyp

Gibt's aber auch in größer direkt mit Controller, mit Versand aber etwas happig
https://a.aliexpress.com/_EJlgLNf

Ja, hat auch was....

Wobei größere Displays dieser Art in Richtung "nicht erhältlich" bis "mir zu teuer" für solche Spielchen gehen.

Getestet, und für OK befunden.
Der auf dem Foto nur schwach erkennbare Doppelpunkt blinkt ordentlich im Sekundentakt.

2 Likes

So, mein Display ist nun auch angekommen, finde das sieht echt super aus!

Zur Abwechslung habe ich es mal mit einem ESP32 angesteuert, natürlich absolut Overkill dafür, aber dann hat man die Version auch mal hier. :wink:

Auf einem ESP8266 muss man nur die Klammern von bool getLocalTime wieder rausnehmen und die WiFi Bibliothek ändern. Display und Zeitmodul sind an SDA und SCL. Das Display an 5V und das Zeitmodul an 3.3V

/* Quellenangaben:
   DS3231 RTC - Zeit per NTP setzen und aus der RTC auslesen, 2019-07-21 Heiko (https://web.archive.org/web/20220804035404/https://unsinnsbasis.de/ds3231-rtc/)
   Display anbinden: https://hartmut-waller.info/arduinoblog/zeit-anzeigen-rtc-modul/
   Funktion calculateoverhang: https://github.com/programminghoch10/HangoverClock/blob/master/app/src/main/java/com/JJ/hangoverclock/ClockGenerator.java
   Restlicher Sketch: https://forum.arduino.cc/t/projektvorstellung-die-hangover-clock/1211889
*/

#include <Wire.h>
#include "RTClib.h"
#include <WiFi.h>
#include "time.h"
#include <sys/time.h>

#include <LiquidCrystal_I2C.h> // Vorher hinzugefügte LiquidCrystal_I2C Bibliothek einbinden
LiquidCrystal_I2C lcd(0x27, 16, 2); //Hier wird festgelegt um was für einen Display es sich handelt. In diesem Fall eines mit 16 Zeichen in 2 Zeilen und der HEX-Adresse 0x27. Für ein vierzeiliges I2C-LCD verwendet man den Code "LiquidCrystal_I2C lcd(0x27, 20, 4)"

RTC_DS3231 rtc;
struct tm tdata;  // lokale Zeit

const char* ssid = "Uhrzeit";
const char* password = "00000000";
const char* ntp_server = "de.pool.ntp.org"; // oder (de/at/ch).pool.ntp.org // fritz.box

// 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"

char days_of_week[7][11] = {"Sonntag", "Montag", "Dienstag", "Mittwoch",
                            "Donnerstag", "Freitag", "Samstag"
                           };

int twelvehoursnumber = 24;
int minuteoverhang = 40;
int houroverhang = 0;

bool firstloop = true;

int lastDay = 0;
int lastHour = -1;
int lastMinute = -1;
int lastSecond = -1;

/*bool getLocalTime(struct tm * info, uint32_t ms = 5000)
  {
    uint32_t start = millis();
    time_t now;
    while((millis()-start) <= ms) {
        time(&now);
        localtime_r(&now, info);
        if(info->tm_year > (2016 - 1900)){
            return true;
        }
        delay(10);
    }
    return false;
  }*/

void setup () {
  DateTime now;
  timeval tv;
  int rc;

  Serial.begin(115200);

  lcd.init(); //Im Setup wird der LCD gestartet
  lcd.backlight(); //Hintergrundbeleuchtung einschalten (lcd.noBacklight(); schaltet die Beleuchtung aus).

  delay(500);

  if (! rtc.begin()) {
    Serial.println("Keine Echtzeituhr gefunden!");
    while (1);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC wird neu gestellt");

    // 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) {
      lcd.setCursor(0, 0);//Hier wird die Position des ersten Zeichens festgelegt. In diesem Fall bedeutet (0,0) das erste Zeichen in der ersten Zeile.
      lcd.print("Zeit verloren");
      lcd.setCursor(0, 1);// In diesem Fall bedeutet (0,1) das erste Zeichen in der zweiten Zeile.
      lcd.print("Erwarte Hotspot");
      delay(500);
      Serial.print(".");
    }
    Serial.println(" Verbunden!");
    delay(1000);

    // Zeit vom NTP-Server holen
    configTime(0, 0, ntp_server);
    // vor Trennung des WLANs einmal getLocalTime() aufrufen
    getLocalTime(&tdata);
    setenv("TZ", TIMEZONE, 1);  // Zeitzone einstellen

    // WLAN trennen
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);

    // RTC anhand der Systemzeit stellen
    rtc.adjust(time(NULL));

    // LCD leeren
    lcd.clear();
  } else {
    // (System-)Zeit aus der RTC übernehmen
    now = rtc.now();
    tv.tv_sec = now.unixtime();
    tv.tv_usec = 0;  // keine Mikrosekunden setzen
    rc = settimeofday(&tv, NULL);  // NULL = keine Zeitzoneninfo an dieser Stelle ...
    setenv("TZ", TIMEZONE, 1);  // ... sondern hier: Zeitzone einstellen
    Serial.printf("Returncode beim Setzen der Systemzeit nach der RTC: %d\n\n", rc);
  }
}

void loop () {
  DateTime now = rtc.now();
  if (firstloop == true) {
    Serial.printf("RTC Unix-Zeit - Datum TT.MM.JJJJ und Zeit: %s %02d.%02d.%d %02d:%02d:%02d\n",
                  days_of_week[now.dayOfTheWeek()],
                  now.day(), now.month(), now.year(),
                  now.hour(), now.minute(), now.second());
    Serial.printf("RTC Unix-Zeit seit 1.1.1970 - %d Sekunden bzw. %d Tage\n",
                  now.unixtime(), now.unixtime() / 86400L);
  }
  getLocalTime(&tdata);
  if (firstloop == true) {
    Serial.printf("Systemzeit: time() = %d\n", time(NULL));
    Serial.printf("Lokale Uhrzeit (aus Systemzeit): %02d:%02d:%02d\n",
                  tdata.tm_hour, tdata.tm_min, tdata.tm_sec);
    Serial.printf("RTC-Temperatur: %2.1f °C\n\n",
                  rtc.getTemperature());
    //delay(10000);
  }
  calculateoverhang(tdata.tm_hour, tdata.tm_min);
  delay(1000);
}

void calculateoverhang(int h, int m) {
  if (minuteoverhang % 60 > m) m += 60;
  m += (minuteoverhang / 60) * 60;
  h -= (m / 60) % twelvehoursnumber;
  if (houroverhang % twelvehoursnumber > h) h += twelvehoursnumber;
  h += (houroverhang / twelvehoursnumber) * twelvehoursnumber;

  if (firstloop == true) {
    Serial.println();
    Serial.print(h);
    Serial.print(":");
    Serial.println(m);
    firstloop = false;
  }

  DateTime now = rtc.now();

  lcd.setCursor(0, 0);

  // Wochentag abrufen (0 = Sonntag, 1 = Montag, ..., 6 = Samstag)
  int weekday = now.dayOfTheWeek();

  // Wochentagskürzel (So., Mo., usw.)
  const char* weekdayAbbreviations[] = {"So.", "Mo.", "Di.", "Mi.", "Do.", "Fr.", "Sa."};

  // Tag im Monat
  int dayOfMonth = now.day();

  // Monatsnamen
  const char* monthNames[] = {"Jan.", "Feb.", "M\xE1r.", "Apr.", "Mai", "Jun.", "Jul.", "Aug.", "Sep.", "Okt.", "Nov.", "Dez."};

  lcd.print(weekdayAbbreviations[weekday]);
  lcd.print(" ");
  lcd.print(dayOfMonth);
  lcd.print(" ");
  lcd.print(monthNames[now.month() - 1]);
  lcd.print(" ");
  lcd.print(now.year(), DEC);

  lcd.setCursor(0, 1); // Zum zweiten Zeilenanfang des LCD wechseln

  if (h != lastHour || m != lastMinute || now.second() != lastSecond) {
    lcd.print(h);
    lcd.print(":");
    if (m < 10) {
      lcd.print("0");
    }
    lcd.print(m);
    lcd.print(":");
    if (now.second() < 10) {
      lcd.print("0");
    }
    lcd.print(now.second(), DEC);
    lcd.print(" "); // Leerzeichen nach Sekunden

    // angelsächsische Schreibweise der Temperatur
    // . durch , ersetzen
    String Temperatur = String(rtc.getTemperature(), 1); // Nur eine Nachkommastelle
    Temperatur.replace(".", ",");

    if (rtc.getTemperature() >= 0 && rtc.getTemperature() < 10) {
      lcd.print("  ");  // Zwei weitere Leerzeichen für einstellige positive Temperaturen
    } else if (rtc.getTemperature() >= 10) {
      lcd.print(' ');  // Ein weiteres Leerzeichen für zweistellige positive Temperaturen
    } else if (rtc.getTemperature() >= -9.9 && rtc.getTemperature() < 0) {
      lcd.print(' ');  // Ein weiteres Leerzeichen für einstellige negative Temperaturen
    }
    lcd.print(Temperatur);
    lcd.print((char)223);
    lcd.print("C ");

    lastHour = h;
    lastMinute = m;
    lastSecond = now.second();
  }
}

Gibt es diese Big Font Mimik irgendwo zum abkupfern?

Mit dem Display auf 5V machts nach kurzer Zeit den ESP kaputt der wird SDA znd SCL mit 5V befeuert hol dir ein Level Shifter. So was zB.

Danke für den Hinweis, die Uhr läuft ohne Level Shifter schon seit ca. zwei Jahren 24/7.

Ja!

1 Like

Oh .. vielen Dank. Ich bin bei meiner Suche immer bei bigfont01_i2c oder bigfont02_ic oder ähnlichem gelandet. Ich habe aber auch immer nur nach Fonts gesucht.

Naja ... Danke nochmal :slightly_smiling_face:

Ich hätte hierzu noch eine Frage an die Community. Mein Code enthält einen Fehler den ich nicht nachvollziehen kann. Beim Monatswechsel stimmt die erste Zeile nicht mehr, bis ich den Microcontroller neu starte. Das Jahr passt dann einfach nicht mehr. Also am Ende der ersten Zeile steht dann nicht 2024 sondern 22024 oder 20244.

Gerade jetzt wieder mit Wechsel 31 Jun. zum 1 Jul. passiert.

Dan zeig doch mall den letzten Stand.
Man muss schon Platz lassen für Tag grösser als 10 sonst schiebst sich alles hin und her auf dem Display.

für die Serial Ausgaben verwendest du die formatierte Ausgabe mit printf.
am LCD machst du händisch die Vornullen für die Uhrzeit.
Beim Datum machst du es nicht.
Daher würde ich das Datum auch formatieren. Ob manuel oder printf ist Geschmackssache.

Ich habe leider deine Beitrag nur heute zum ersten mal gesehen habe aber genau die gleiche VFD Anzeige gekauft und ein Controller zusammen geworfen.

image

Alles ist hier dokumentiert ESP32-C3 Supermini clock with VFD display . Ich habe zwei gebaut und die Bauteile für noch zwei (nächste Lockdown Projekt).

1 Like

Ah, ja das wird es sein, macht dann auch Sinn dass es immer passiert wenn sich die Zahl von 31 auf 1 verkürzt, dann bleibt die 4 vom Jahr ganz rechts kleben.