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
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
- Schrittmotor (ggfs. mit Getriebe)
- Servos
- 📖 Drehspulmesswerk
- LEDs
