#include <RTClib.h> //Bibliothek für das Real Time Clock Modul
#include <Wire.h> //Bibliothek für die Kommunikation mit I2C Devices
#include <LiquidCrystal_I2C.h> //Bibliothek für I2C Liquid Display
#include <IRremote.h> //Bibliothek für IR Fernbedienung und Empfänger
#include <Bounce2.h> // Bibliothek für Tastenentprellung
#include <Servo.h> //Bibliothek für die Ansteuerung der beiden Servomotoren
#define IRReceivePin 17 //A3 -> Pin 17
#define OKButtonPin 6
#define changeButtonPin 7
#define ZeitAnzeigen 1
#define BuzzerPin 14 //A0 -> Pin 14
#define skip 67 //Vorspulen Taste: 67 Alarm Einstellungen Aufrufen
#define rew 68 //Rückspulen Taste: 68 Uhrzeit Einstellungen Aufrufen
#define play 64 //Play/Pause Taste: 64 Als OK Eingabe
#define volp 70 //Vol+: 70 Wert um 1 erhöhen
#define volm 21 //Vol-: 21 Wert um 1 verringern
#define A 2 //Alles ab hier ist für die Ansteuerung der 12 LED
#define B 3
#define C 4
#define D 5
#define PIN_CONFIG 0
#define PIN_STATE 1
DateTime aktuell;
enum Mode {refreshTime, setTime, loadAlarm, setAlarm, exitt, kuckuck}; //enum array für die einzeln Haupt-switchcases
Mode mode = refreshTime; //aktueller Modus des Systems
unsigned long previousMicros = 0; //speichern der Microsekunden um Programmlaufzeit zu berechnen
unsigned long previousMillis = 0;
unsigned long OKmillis = 0; //speichern des Zeitwertes um abgelaufene Zeit abzugleichen
unsigned long changemillis = 0;
unsigned long cursormillis = 0;
unsigned long ServoMinutenOffMillis = 0;
const int ServoMinutenPin[] = {15, 544, 2600}; //A1 -> Pin 15 {Pin, min, max}
const int ServoKuckuckPin[] = {16, 544, 2600}; //A2 -> Pin 16
int ServoMinutenRefresh;
int cursorposition = 0;
int time[] = {1, 2, 3, 4, 5, 6};
int Wecker[] = {7, 0, 0, 8, 0, 0}; //{Stunde, Minute, On/Off, Stunde, Minute, On/Off}
int IRInput = 0;
int matrix[12][2][4] = { //Matrix für die Korrekte Ansteuerung der LEDs
// PIN_CONFIG PIN_STATE
// A B C D A B C D
{{OUTPUT, OUTPUT, INPUT, INPUT}, {HIGH, LOW, LOW, LOW}},
{{OUTPUT, OUTPUT, INPUT, INPUT}, {LOW, HIGH, LOW, LOW}},
{{INPUT, OUTPUT, OUTPUT, INPUT}, {LOW, HIGH, LOW, LOW}},
{{INPUT, OUTPUT, OUTPUT, INPUT}, {LOW, LOW, HIGH, LOW}},
{{OUTPUT, INPUT, OUTPUT, INPUT}, {HIGH, LOW, LOW, LOW}},
{{OUTPUT, INPUT, OUTPUT, INPUT}, {LOW, LOW, HIGH, LOW}},
{{OUTPUT, INPUT, INPUT, OUTPUT}, {HIGH, LOW, LOW, LOW}},
{{OUTPUT, INPUT, INPUT, OUTPUT}, {LOW, LOW, LOW, HIGH}},
{{INPUT, OUTPUT, INPUT, OUTPUT}, {LOW, HIGH, LOW, LOW}},
{{INPUT, OUTPUT, INPUT, OUTPUT}, {LOW, LOW, LOW, HIGH}},
{{INPUT, INPUT, OUTPUT, OUTPUT}, {LOW, LOW, HIGH, LOW}},
{{INPUT, INPUT, OUTPUT, OUTPUT}, {LOW, LOW, LOW, HIGH}}
};
LiquidCrystal_I2C lcd(0x27, 16, 2); //Hier wird festgelegt um welches Display es sich handelt. Die HEX-Adresse 0x27 ist eine Standartadresse für LCD mit einem einfachen I²C-Modul auf der Rückseite. Wenn das I²C Modul Lötstellen zur Veränderung der HEX-Adresse aufweist, ist die Standartadresse "0x3F". In diesem Fall handelt es sich um ein LCD mit 16 Zeichen in 2 Zeilen (16,2). Für ein vierzeiliges I2C-LCD verwendet man den Code "LiquidCrystal_I2C lcd(0x27, 20, 4)"
RTC_DS3231 rtc; // Name des RTC-Moduls (rtc)
Servo ServoMinuten; //Erstellt ein Objekt für den Servo des Minutenzeigers
Servo ServoKuckuck; //Objekt für den Servo des Kuckucks
Bounce OKButton = Bounce(OKButtonPin, 5); // Erstellt Bounce-Objekte für die Taster mit 5 ms Entprellzeit
Bounce ChangeButton = Bounce(changeButtonPin, 5);
void setup(){
lcd.init(); //Im Setup wird der LCD gestartet
lcd.backlight(); //Hintergrundbeleuchtung einschalten (lcd.noBacklight(); schaltet die Beleuchtung aus).
lcd.clear();
rtc.begin(); // RTC-Modul starten
Serial.begin(9600);
IrReceiver.begin(IRReceivePin); //starten des IRReceivers
ServoKuckuck.attach(ServoKuckuckPin);
pinMode(OKButtonPin, INPUT_PULLUP); //In und Output Pins Initialisieren
pinMode(changeButtonPin, INPUT_PULLUP);
pinMode(BuzzerPin, OUTPUT);
test();
}
void loop(){
previousMicros = micros(); //Zeit abspeichern für Programmlaufzeit
aktuell = rtc.now(); // rtc.now() -> aktuelle Zeit holen
OKButton.update(); // Aktualisiert den Zustand des Tasters
if (IrReceiver.decode()) { //abholen des IRInputs und speichern in Variable
IrReceiver.resume();
IRInput = IrReceiver.decodedIRData.command;
}
else{
IRInput = 0;
}
int i;
switch (mode){
case refreshTime:
if((millis() - previousMillis) > 990){ //wenn 990ms vergangen sind wird der Bildschirm refresht
previousMillis = millis();
printTime();
}
i = OKlongshortpress();
if(i == 1 || IRInput == skip) {mode = loadAlarm;}
if(i == 2 || IRInput == rew) {mode = setTime;}
if((Wecker[2] == 1 && Wecker[0] == time[0] && Wecker[1] == time[1]) || (Wecker[5] == 1 && Wecker[3] == time[0] && Wecker[4] == time[1])){ //Wenn eingestellt Weckzeit erreicht wurde
mode = kuckuck;
}
break;
case setTime:
changeTime();
break;
case loadAlarm: //Hier werden die entsprechenden Daten auf das Display geladen
lcd.setCursor(0,0);
lcd.print("Weck1: ");
if(Wecker[0] < 10) {lcd.print("0");}
lcd.print(Wecker[0]);
lcd.print(":");
if(Wecker[1] < 10) {lcd.print("0");}
lcd.print(Wecker[1]);
if(Wecker[2] == 1) {lcd.print(" On ");}
else {lcd.print(" Off");}
lcd.setCursor(0,1);
lcd.print("Weck2: ");
if(Wecker[3] < 10) {lcd.print("0");}
lcd.print(Wecker[3]);
lcd.print(":");
if(Wecker[4] < 10) {lcd.print("0");}
lcd.print(Wecker[4]);
if(Wecker[5] == 1) {lcd.print(" On ");}
else {lcd.print(" Off");}
mode = setAlarm;
break;
case setAlarm:
setWecker();
break;
case kuckuck:
Kuckuck();
break;
case exitt:
if(digitalRead(OKButtonPin == HIGH) && (millis() - OKmillis) > 500){ //Wenn keine Taste gedrückt und 500ms vergangen sind
OKmillis = 0;
mode = refreshTime;
}
lcd.clear();
break;
}
Serial.print(1000000 / (micros() - previousMicros));
Serial.println("Hz");
}
void printTime(){ //Funktion zum Anzeigen der Zeit auf Display als auch auf den LEDs und dem Servo
char Datum[] = "DD.MM.YYYY";
char Zeit[] = "hh:mm:ss";
time[0] = aktuell.hour();
time[1] = aktuell.minute();
time[2] = aktuell.second();
time[3] = aktuell.day();
time[4] = aktuell.month();
time[5] = aktuell.year();
lcd.setCursor(0,0);
lcd.print(String("Zeit: ") + aktuell.toString(Zeit));
lcd.setCursor(0,1);
lcd.print(String("Datum:") + aktuell.toString(Datum));
if(time[0] > 11){ //Zeigen der Stunden durch LEDs
lightOn(time[0] - 12);
}
else{
lightOn(time[0]);
}
if(ServoMinutenRefresh != time[1]){ //Stellen des Minutenzeigers
ServoMinutenRefresh = time[1];
ServoMinuten.attach(ServoMinutenPin[0], ServoMinutenPin[1], ServoMinutenPin[2]);
ServoMinuten.write(180 - 3 * time[1]);
ServoMinutenOffMillis = millis();
}
if((time[1] == 0 && (millis() - ServoMinutenOffMillis) > 3000) || (time[1] != 0 && (millis() - ServoMinutenOffMillis) > 1000)){ //Detachen des Servos damit er nicht durchgehend nachregelt
ServoMinuten.detach();
}
}
int OKlongshortpress(){ //Funktion zum detektieren eines langen oder kurzen Tastendrucks
if(digitalRead(OKButtonPin) == LOW && (millis() - OKmillis) > 1100){ //Wenn Tastendruck bei OK erkannt
OKmillis = millis();
Serial.println("BBBB");
}
if(OKButton.risingEdge() && OKmillis > 0){ //Wenn Tasten Loslassen bei OK erkannt
OKmillis = 0;
Serial.println("CCCC");
return 1;
}
if(digitalRead(OKButtonPin) == LOW && ((millis() - OKmillis) > 1000)){ //Wenn nach Tastendruck 1s vergangen ist
OKmillis = 0;
Serial.println("DDDD");
return 2;
}
return 0;
}
void changeTime(){
switch (cursorposition){ //Hier wird der cursor an die richtige Stelle gesetzt und der Wertebereich an der Stelle auf plausibilität geprüft
case 0:
lcd.setCursor(6, 0);
if(time[cursorposition] > 23) {time[cursorposition] = 0;}
if(time[cursorposition] < 0) {time[cursorposition] = 23;}
break;
case 1:
lcd.setCursor(9, 0);
if(time[cursorposition] > 59) {time[cursorposition] = 0;}
if(time[cursorposition] < 0) {time[cursorposition] = 59;}
break;
case 2:
lcd.setCursor(12, 0);
if(time[cursorposition] > 59) {time[cursorposition] = 0;}
if(time[cursorposition] < 0) {time[cursorposition] = 59;}
break;
case 3:
lcd.setCursor(6, 1);
if(time[cursorposition] > 31) {time[cursorposition] = 1;}
if(time[cursorposition] < 0) {time[cursorposition] = 32;}
break;
case 4:
lcd.setCursor(9, 1);
if(time[cursorposition] > 12) {time[cursorposition] = 1;}
if(time[cursorposition] < 0) {time[cursorposition] = 12;}
break;
case 5:
lcd.setCursor(12, 1);
if(time[cursorposition] > 2099) {time[cursorposition] = 2000;}
if(time[cursorposition] <2000) {time[cursorposition] = 2099;}
break;
}
if((millis() - cursormillis) > 1000){
cursormillis = millis();
if(cursorposition == 5) {lcd.print("____");}
else {lcd.print("__");}
}
if(((millis() - cursormillis) > 400) || OKButton.fallingEdge() || ChangeButton.fallingEdge() || IRInput == play || IRInput == volp || IRInput == volm){ //wenn 400ms um sind oder einer der tasten gedrückt wird
if(time[cursorposition] < 10) {lcd.print("0");}
lcd.print(time[cursorposition]);
}
if(digitalRead(changeButtonPin) == LOW || IRInput == volp || IRInput == volm){ //Wenn Taste gedrückt wird der Timer für Anzeigen von __ Resettet
cursormillis = millis() - 400;
}
if(digitalRead(changeButtonPin) == LOW && (millis() - changemillis) > 200){ //Wenn change taster gedrückt wird, nachstehend auch + und - der Fernbedienung
changemillis = millis();
++time[cursorposition];
}
if(IRInput == volp){ //Wenn vol plus gedrückt wird auf IR Fernbedienung
++time[cursorposition];
}
if(IRInput == volm){ //Wenn vol plus gedrückt wird auf IR Fernbedienung
--time[cursorposition];
}
if(OKButton.fallingEdge() || IRInput == play){
OKmillis = millis();
++cursorposition;
if(cursorposition > 5){
cursorposition = 0;
rtc.adjust(DateTime(time[5], time[4], time[3], time[0], time[1], time[2]));
delay(1000);
mode = exitt;
}
}
}
void setWecker(){
switch (cursorposition){ //Hier wird der cursor an die richtige Stelle gesetzt und der Wertebereich an der Stelle auf plausibilität geprüft
case 0:
lcd.setCursor(7, 0);
if(Wecker[cursorposition] > 23) {Wecker[cursorposition] = 0;}
if(Wecker[cursorposition] < 0) {Wecker[cursorposition] = 23;}
break;
case 1:
lcd.setCursor(10, 0);
if(Wecker[cursorposition] > 59) {Wecker[cursorposition] = 0;}
if(Wecker[cursorposition] < 0) {Wecker[cursorposition] = 59;}
break;
case 2:
lcd.setCursor(13, 0);
if(Wecker[cursorposition] > 1) {Wecker[cursorposition] = 0;}
if(Wecker[cursorposition] < 0) {Wecker[cursorposition] = 1;}
break;
case 3:
lcd.setCursor(7, 1);
if(Wecker[cursorposition] > 23) {Wecker[cursorposition] = 0;}
if(Wecker[cursorposition] < 0) {Wecker[cursorposition] = 23;}
break;
case 4:
lcd.setCursor(10, 1);
if(Wecker[cursorposition] > 59) {Wecker[cursorposition] = 0;}
if(Wecker[cursorposition] < 0) {Wecker[cursorposition] = 59;}
break;
case 5:
lcd.setCursor(13, 1);
if(Wecker[cursorposition] > 1) {Wecker[cursorposition] = 0;}
if(Wecker[cursorposition] < 0) {Wecker[cursorposition] = 1;}
break;
}
if(((millis() - cursormillis) > 1000)){ //Wenn 600ms sekunde abgelaufen ist wird an der Stelle des Cursors __ angezeigt
cursormillis = millis();
if(cursorposition == 2 || cursorposition == 5) {lcd.print("____");}
else {lcd.print("__");}
}
if(((millis() - cursormillis) > 400) || OKButton.fallingEdge() || ChangeButton.fallingEdge() || IRInput == play || IRInput == volp || IRInput == volm){ //Wenn 400ms sekunde abgelaufen ist wird an der Stelle vom Cursor __ durch den Wert ersetzt
if(Wecker[cursorposition] < 10 && cursorposition != 2 && cursorposition != 5) {lcd.print("0");}
if(cursorposition == 2 || cursorposition == 5){
if(Wecker[cursorposition] == 1) {lcd.print("On ");}
else {lcd.print("Off");}
}
else {lcd.print(Wecker[cursorposition]);}
}
if(digitalRead(changeButtonPin) == LOW || IRInput == volp || IRInput == volm){ //Wenn Taste gedrückt wird der Timer für Anzeigen von __ Resettet
cursormillis = millis() - 400;
}
if(digitalRead(changeButtonPin) == LOW && (millis() - changemillis) > 200){ //Wenn change Button gedrückt und 200ms seit letztem drücken verstrichen sind wird Wert an entsprechender Stelle um 1 erhöht
changemillis = millis();
++Wecker[cursorposition];
}
if(IRInput == volp){ //Wenn vol plus gedrückt wird auf IR Fernbedienung
++Wecker[cursorposition];
}
if(IRInput == volm){ //Wenn vol plus gedrückt wird auf IR Fernbedienung
--Wecker[cursorposition];
}
if(OKButton.fallingEdge() || IRInput == play){
OKmillis = millis();
++cursorposition; //Cursorposition um 1 erhöhen
if(cursorposition > 5){
cursorposition = 0;
mode = exitt; //Modus wechseln
}
}
}
void Kuckuck(){
if(Wecker[0] == time[0] && Wecker[1] == time[1]){ //resetten des entsprechenden Weckers
Wecker[2] = 0;
}
if(Wecker[3] == time[0] && Wecker[4] == time[1]){ //resetten des entsprechenden Weckers
Wecker[5] = 0;
}
tone(BuzzerPin, 5000, 1000);
mode = refreshTime; //Modus wieder zurücksetzen
}
void lightOn(int led){ //Funktion zum ansteuern der LEDs
pinMode(A, matrix[led][PIN_CONFIG][0]);
pinMode(B, matrix[led][PIN_CONFIG][1]);
pinMode(C, matrix[led][PIN_CONFIG][2]);
pinMode(D, matrix[led][PIN_CONFIG][3]);
digitalWrite(A, matrix[led][PIN_STATE][0]);
digitalWrite(B, matrix[led][PIN_STATE][1]);
digitalWrite(C, matrix[led][PIN_STATE][2]);
digitalWrite(D, matrix[led][PIN_STATE][3]);
}
void test(){ //Funktionstets der Komponenten
ServoMinuten.attach(ServoMinutenPin[0], ServoMinutenPin[1], ServoMinutenPin[2]);
lcd.clear();
lightOn(0);
ServoMinuten.write(180);
delay(1000);
for (int i = 0; i < 32; i++){
ServoMinuten.write(180 - i * 180/32);
lightOn(round(i * 3 / 8));
if(i < 16){
lcd.setCursor(i, 0);
lcd.print("0");
}
else{
lcd.setCursor(i - 16, 1);
lcd.print("0");
}
delay(50);
}
ServoMinuten.detach();
delay(1000);
lcd.clear();
}