7.1 🌀🚧 Uhren bauen

Synopsis: 🔗 Binäruhr mit Zylonenauge

Einfache LCD-Uhr

Erstellen Sie den Programmcode für eine sehr einfache LCD-Uhr, Vorgaben:

  • uint8_t stunden=12,minuten=0,sekunden=0;
  • static HardwareTimer mytimer = HardwareTimer(TIM2);
  • ISR isr_Sek() zählt im Sekundentakt hoch und ruft UP incSek() und UP ausgebenZeit() auf
  • UP incSek() erhöht die Zeit -Variablen
  • UP ausgebenZeit() gibt die Uhrzeit auf der LCD-Anzeige in diesem Format mit printf(..) aus: 12:00:00
#include <Wire.h> // I2C-Library
#include <LiquidCrystal_PCF8574.h> // LCD
LiquidCrystal_PCF8574 lcd (0x27);
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timerauswahl
uint8_t stunden=12,minuten=0,sekunden=0;

void isr_Sek(){ // Isr jede Sekunde
  ...
}

void incSek(){ // erhöht die Zeit
  ...
}

void ausgebenZeit(){  // gibt die Zeit auf dem LCD aus
  lcd.setCursor(0,0); // Cursor an Position Spalte, Zeile setzen
  lcd.printf("%02d:%02d:%02d",stunden,minuten,sekunden); // Zeit ausgeben
}

void setup() {
  Wire.begin();  //Alternatives PinMapping: Wire.begin(SDA, SCL);
  lcd.begin(16, 2); // initialize the lcd
  lcd.clear();
  lcd.setBacklight(255);
  lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
  ...
}

void loop() {}

Hinweis: In der lokalen Formelsammlung wird bei printf %u für unsigned int verwendet.

Lösungsvorschlag
#include <Wire.h> // I2C-Library
#include <LiquidCrystal_PCF8574.h> // LCD
LiquidCrystal_PCF8574 lcd (0x27);
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl
uint8_t stunden=12,minuten=0,sekunden=0;

void isr_Sek(){ // Isr jede Sekunde
  incSek();
  ausgebenZeit();
}

void incSek(){ // erhöht die Zeit
  if(sekunden<59){
    sekunden++;
  } else {
    sekunden=0;
    if(minuten<59){
      minuten++;
    } else {
      minuten=0;
      if (stunden<23){
        stunden++;
      } else {
        stunden=0;
      }
    }
  }
}

void ausgebenZeit(){ // gibt die Zeit auf dem LCD aus
  lcd.setCursor(0, 0);
  lcd.printf("%02d:%02d:%02d",stunden,minuten,sekunden); // Zeit ausgeben
}

void setup() {
  Wire.begin();  //Alternatives PinMapping: Wire.begin(SDA, SCL);
  lcd.begin(16, 2); // initialize the lcd
  lcd.clear();
  lcd.setBacklight(255);
  lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
  mytimer.setOverflow(1000);         // 1000 ms
  mytimer.setPrescaleFactor(32000);  // 1 kHz
  mytimer.attachInterrupt(isr_Sek);  // Sekunde ist rum
  mytimer.resume(); // Timer starten
}

void loop() {}

Uhrzeit mit Tasten einstellen

Die Uhrzeit soll mit den prellenden Tasten PB0 (T_EINSTELLEN), PB3 (T_HOCH) und PB4 (T_RUNTER) eingestellt werden.
Das UP buttonCheck() usw. für eine periodische prellfreie Abfrage der Tasten steht zur Verfügung siehe Code-Vorgabe, Sie können damit mit den Makros T_EINSTELLEN usw. das Drücken einer Taste verarbeiten.

#include <Wire.h> // I2C-Library
#include <LiquidCrystal_PCF8574.h> // LCD
LiquidCrystal_PCF8574 lcd (0x27);
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timeruaswahl
uint8_t stunden=12,minuten=0,sekunden=0;

enum zustandstyp {ANZEIGEN,EINSTELLEN_STD,EINSTELLEN_MIN}; // definiere Aufzählungstyp
enum zustandstyp zustand = ANZEIGEN; // definiere und initialisiere Variable

#define BUTTONREADER GPIOB->IDR & 0b00000011001 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 
void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(10);                      // 10ms Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
}

#define T_EINSTELLEN buttonEnter&(1<<0) 
#define T_HOCH buttonEnter&(1<<3)
#define T_RUNTER buttonEnter&(1<<4)

void isr_Sek(){ // ISR jede Sekunde
 ...
}

void incSek(){ // erhöht die Zeit
  ...
}

void ausgebenZeit(){  // gibt die Zeit auf dem LCD aus
  lcd.setCursor(0,0); // Cursor an Position Spalte, Zeile setzen
  lcd.printf("%02d:%02d:%02d",stunden,minuten,sekunden); // Zeit ausgeben
  ...
}

void setup() {
  pinMode(PB0,INPUT_PULLDOWN);  // Zeit einstellen
  pinMode(PB3,INPUT_PULLDOWN);  // rauf
  pinMode(PB4,INPUT_PULLDOWN);  // runter
  Wire.begin();  //Alternatives PinMapping: Wire.begin(SDA, SCL);
  lcd.begin(16, 2); // initialize the lcd
  lcd.clear();
  lcd.setBacklight(255);
  lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
}

void loop() {
  buttonCheck();
  ...
}

Zustandsdiagramm erstellen

Erstellen Sie ein Zustandsdiagramm mit diesen Zuständen: ANZEIGEN,EINSTELLEN_STD,EINSTELLEN_MIN

  • Drücken von T_EINSTELLEN ändert den Zustand: ANZEIGEN -> EINSTELLEN_STD -> EINSTELLEN_MIN -> ANZEIGEN
  • Im Zustand ANZEIGEN wird durch isr_Sek() die UP incSek() und ausgebenZeit() aufgerufen
  • Beim Verzweigen in den Zustand EINSTELLEN_STD wird sekunde=0 gesetzt und ausgebenZeit() aufgerufen
  • Im Zustand EINSTELLEN_STD wird beim Drücken von T_HOCH die Stunde erhöht, bei 23 wieder auf 0 gesetzt. Danach wird ausgebenZeit() aufgerufen
  • Im Zustand EINSTELLEN_MIN wird beim Drücken von T_HOCH die Minute erhöht, bei 59 wieder auf 0 gesetzt. Danach wird ausgebenZeit() aufgerufen
  • T_RUNTER muss noch nicht berücksichtigt werden.
Lösungsvorschlag
Zustandsdiagramm
Zustandsdiagramm

Code erstellen

Beim Einstellen der Zeit soll in dem jeweiligen Bereich der Cursor blinken. Dies wird z.B. für die Stunden nach der Ausgabe der Zeit erreicht:

lcd.setCursor(1,0); // Cursor an Position Spalte, Zeile setzen
lcd.blink();        // Cursor blinken lassen

Das Cursor-Blinken wird mit lcd.noBlink(); wieder abgestellt. Erstellen Sie den Code für die Uhr.

Lösungsvorschlag
#include <Wire.h> // I2C-Library
#include <LiquidCrystal_PCF8574.h> // LCD
LiquidCrystal_PCF8574 lcd (0x27);
static HardwareTimer mytimer = HardwareTimer(TIM2);  // Timerinstanz sowie Timerauswahl
uint8_t stunden=12,minuten=0,sekunden=0;

enum zustandstyp {ANZEIGEN,EINSTELLEN_STD,EINSTELLEN_MIN}; // definiere Aufzählungstyp
enum zustandstyp zustand = ANZEIGEN; // definiere und initialisiere Variable

#define BUTTONREADER GPIOB->IDR & 0b00000011001 // nur die relevanten Tastenbits
int buttonOld = 0;              // alter Tasten-Zustand 
int buttonEnter,buttonExit;     // gedrueckte und losgelassene Tasten 
void buttonCheck(){             // Tastaturabfrage mit Flankendedektion  
  int buttonTest,tmp; 
  buttonEnter = 0, buttonExit = 0; 
  buttonTest = BUTTONREADER;       // Einlesen
  if (buttonOld != buttonTest){    // hat sich was getan 
    delay(10);                      // 10ms Prellen abwarten 
    tmp = BUTTONREADER;            // noch mal Einlesen
    if (tmp == buttonTest){        // ist es stabil? 
      buttonEnter = (~buttonOld) & buttonTest; // steigende Flanke !alt und neu 
      buttonExit = buttonOld & (~buttonTest);  // fallende Flanke alt und !neu 
      buttonOld = buttonTest; 
    } 
  } 
}

#define T_EINSTELLEN buttonEnter&(1<<0) 
#define T_HOCH buttonEnter&(1<<3)
#define T_RUNTER buttonEnter&(1<<4)

void isr_Sek(){ // ISR jede Sekunde
  incSek();
  ausgebenZeit();
}

void incSek(){ // erhöht die Zeit
  if(sekunden<59){
    sekunden++;
  } else {
    sekunden=0;
    if(minuten<59){
      minuten++;
    } else {
      minuten=0;
      if (stunden<23){
        stunden++;
      } else {
        stunden=0;
      }
    }
  }
}

void ausgebenZeit(){  // gibt die Zeit auf dem LCD aus
  lcd.setCursor(0,0); // Cursor an Position Spalte, Zeile setzen
  lcd.printf("%02d:%02d:%02d",stunden,minuten,sekunden); // Zeit ausgeben
  switch(zustand){
    case EINSTELLEN_STD:
      lcd.setCursor(1,0); // Cursor an Position Spalte, Zeile setzen
      lcd.blink();        // Cursor blinken lassen
      break;
    case EINSTELLEN_MIN:
      lcd.setCursor(4,0);
      lcd.blink();
      break;  
  }
}

void setup() {
  pinMode(PB0,INPUT_PULLDOWN);  // Zeit einstellen
  pinMode(PB3,INPUT_PULLDOWN);  // rauf
  pinMode(PB4,INPUT_PULLDOWN);  // runter
  Wire.begin();  //Alternatives PinMapping: Wire.begin(SDA, SCL);
  lcd.begin(16, 2); // initialize the lcd
  lcd.clear();
  lcd.setBacklight(255);
  lcd.setCursor(0,0); // erstes Zeichen, erste Zeile
  mytimer.setOverflow(1000);         // 1000 ms
  mytimer.setPrescaleFactor(32000);  // 1 kHz
  mytimer.attachInterrupt(isr_Sek);  // Sekunde ist rum
  mytimer.resume(); // Timer starten
}

void loop() {
  buttonCheck();
  switch(zustand){
    case ANZEIGEN:
      if(T_EINSTELLEN){
        mytimer.pause();
        sekunden=0;
        zustand = EINSTELLEN_STD;
        ausgebenZeit();
      }
      break;
    case EINSTELLEN_STD:
      if(T_EINSTELLEN){
        zustand = EINSTELLEN_MIN;
        ausgebenZeit();
      }
      if(T_HOCH){
        if(stunden<23) stunden++;
        else stunden=0;
        ausgebenZeit();
      }
      if(T_RUNTER){
        if(stunden>0) stunden--;
        else stunden=23;
        ausgebenZeit();
      }
      break;
    case EINSTELLEN_MIN:
      if(T_EINSTELLEN){
        zustand = ANZEIGEN;
        mytimer.resume();
        lcd.noBlink();
      }
      if(T_HOCH){
        if(minuten<59) minuten++;
        else minuten=0;
        ausgebenZeit();
      }
      if(T_RUNTER){
        if(minuten>0) minuten--;
        else minuten=59;
        ausgebenZeit();
      }
      break;    
  }
}

Uhrzeit über Serielle Schnittstelle einstellen

Eingabe in Seriellen Monitor: 14:12:40 Ausgabe: Neue Zeit: 14:12:40 und die Zeit ist eingestellt.
NewLine im Seriellen Monitor einstellen sonst gibt es eine Sekunde Verzögerung nach Drücken der Return-Taste.
Funktion in loop() einbauen..

Mit selbst gebauten Parser

void stelleZeitParsen(){             // 1:2:3 oder 01:02:03 parsen
  if (Serial.available()) {          // liegt etwas im Puffer der seriellen Schnittstelle?
    String s;
    uint8_t inStd,inMin,inSek,i=0; // Variablen zum Einlesen
    s = Serial.readStringUntil('\n'); // lese Daten in String ein
    if(i<s.length() && s[i]>='0' && s[i]<='9'){ // Start mit Ziffer
      inStd=s[i]-'0';
      i++;
    } else goto fehler;
    if(i<s.length() && s[i]>='0' && s[i]<='9'){ // Noch eine Ziffer?
      inStd= inStd*10 + s[i]-'0';
      i++;
    }
    if(i<s.length() && s[i]==':'){  // Doppelpunkt muss sein
      i++;
    } else goto fehler;
    if(i<s.length() && s[i]>='0' && s[i]<='9'){ // Weiter mit Ziffer
      inMin=s[i]-'0';
      i++;
    } else goto fehler;
    if(i<s.length() && s[i]>='0' && s[i]<='9'){ // Noch eine Ziffer?
      inMin= inMin*10 + s[i]-'0';
      i++;
    }
    if(i<s.length() && s[i]==':'){  // Doppelpunkt muss sein
      i++;
    } else goto fehler;
    if(i<s.length() && s[i]>='0' && s[i]<='9'){ // Weiter mit Ziffer
      inSek=s[i]-'0';
      i++;
    } else goto fehler;
    if(i<s.length() && s[i]>='0' && s[i]<='9'){ // Noch eine Ziffer?
      inSek= inSek*10 + s[i]-'0';
      i++;
    }                                           // Fertig weitere Eingaben werden ignoriert, führen zu keinem Fehler
    if(inStd>23 || inMin>59 || inSek>59){ // Parameter im Wertebereich?
      Serial.printf("Werte sind nicht ok: %02d:%02d:%02d\n",inStd,inMin,inSek);
      return;
    }  
    stunden=inStd;
    minuten=inMin;
    sekunden=inSek;
    Serial.printf("Neue Zeit: %02d:%02d:%02d\n",stunden,minuten,sekunden);
    return;
fehler:
    Serial.print("Fehler beim Parsen: "); // printf funktioniert mit String nicht
    Serial.println(s);
  }
}

Mit sscanf(..)

Synopsis: 🔗 Arduino sscanf() Function

void stelleZeitScanf(){
  if (Serial.available()) {          // liegt etwas im Puffer der seriellen Schnittstelle?
    char buf[40];                    // Puffer für sscanf
    uint8_t inStd,inMin,inSek,inLen; // Variablen zum Einlesen
    int n = Serial.readBytesUntil('\n', buf, sizeof(buf) - 1); // Puffer einlesen bis \n
    buf[n] = '\0';                   // terminieren mit \0
    inLen = sscanf(buf, "%02d:%02d:%02d", &inStd, &inMin, &inSek); // mit sscanf parsen
    if (inLen != 3) {                // richtige Anzahl Parameter?
      Serial.printf("Anzahl Parameter stimmt nicht: %d '%s'",inLen,buf);
    }
    else if(inStd>23 || inMin>59 || inSek>59){ // Parameter im Wertebereich?
      Serial.printf("Werte sind nicht ok: %s -> %02d:%02d:%02d",buf,inStd,inMin,inSek);
    }
    else {                           // Uhrzeit einstellen
      stunden=inStd;
      minuten=inMin;
      sekunden=inSek;
      Serial.printf("Neue Zeit: %02d:%02d:%02d\n",stunden,minuten,sekunden);
    }
  }
}

ToDo here

  • Seriell einlesen und mit Split-ähnlicher Funktion weiter verarbeiten
  • Datum eingeben können
  • Compilerzeit zum Stellen verwenden
  • WLAN und Time-Server
  • DCF77 als Zeitbasis
  • RTC anschließen
  • Uhrzeit ausgeben auf
    • LEDs
      • BinärCode
      • Gemultiplexte Anzeige
      • SPI-Chip Anzeige
    • Analog mit