Zeitschaltuhr Problem : Uhrzeit auf Display ausgeben mit Menüfunktion

Hallo an alle erstmal,

ich bin einsteiger in der Arduino Programmierung und benötige deshalb Hilfe bei einer kleinen Fehlersuche.Ich habe im Forum nach einiger Sucherei nicht das passende gefunden.Momentan versuche ich eine Zeitschaltuhr zu realisieren mittels Displayshield von Saintsmart und Software.
Zusätzlich soll die Möglichkeit bestehen, die gewünschten Zeiten mittels der auf dem Shield befindlichen Taster über ein kleines Menü einzustellen.Das Einstellen der aktuellen Uhrzeit funktioniert soweit auch nun habe ich ein Problem beim Darstellen der Zeit im Hauptmenü.Ich möchte quasi vom Hauptbildschirm mittels HOCH und RUNTER durch das Menü schalten.

0.Hauptbildschirm (Uhrzeit)
1.Menü (Zeiteinstellung)
2.Menü
3.Menü
4.Menü

Dazu habe ich Switch-Case Anweisungen verwendet und eine While-Schleife die bei drücken der HOCH und RUNTER Taste unterbrechen soll und in das nächste Menü schalten soll.Lange Rede kurzer Sinn
ich kann für die Abbruchbedingung nur eine Taste auswählen und bei Tastendruck bleibt die Zeit stehen und es "Hängt" kurz.

Ziele:
-Menü soll flüssig durchschaltbar sein.
-Es sollen HOCH und RUNTER Taster zum durchschalten verwendet werden können.

Ich danke euch schon mal vorab für eure Hilfe. :slight_smile: vielleicht kann mir ja jemand den Logikfehler erklären.
Und geht bitte nicht so streng mit mir ins Gericht ich programmiere noch nicht all zulange.

CODE:(entschuldigt bitte den Wust, ich hoffe die Kommentare machen es en bissel verständlicher)

//Libraries
#include <Time.h>
#include <TimeLib.h>
#include <LiquidCrystal.h>
#include <sainsmartkeypad.h>
//INIT DISPLAY
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
SainsmartKeypad keypad(0);

// GOLBAL Variablen
char key;
//Variablen zum zählen der Menüseiten
int men1 = 1;
int menuseitegesamt = 4;
int menuseitealt = 0;
int menuseiteakt = 0;
//Variablen zum auslesen der Taster
int keypad_pin = A0;
int keypad_value = 0;
//Aktuelle UHRZEIT
int s = 0;
int sec = 0;
int hrs = 0;
int minutes = 0;
int initialHours = 00;
int initialMins = 00;
int initialSecs = 00;
//Einschaltzeit
//AUschaltzeit
void setup() {
  lcd.begin(16, 2); // put your setup code here, to run once:
  hauptmenu();
  delay(1000);
}

void loop() {
  // put your main code here, to run repeatedly:
  //Auswahl der Untermenüs
  key = ReadKeypad();
  casecount();

  if (key == 'S') {

    switch (menuseiteakt)
    {
      case 0:
        break;

      case 1:
        menu1();
        break;

      case 2:
        //   menu2();
        break;
      case 3:
        //    menu3();
        break;
      case 4:
        //    menu4();
        break;
    }

  }
}
///Funktionen Menü
////Readkeypad
char ReadKeypad() {//Liest die Analog werte der Taster us und gibt Chars zurück
  no button pressed 1023
  select  741
  left    503
  down    326
  up      142
  right   0
  * /


  keypad_value = analogRead(keypad_pin);

  if (keypad_value < 100)
    return 'R';
  else if (keypad_value < 200)
    return 'U';
  else if (keypad_value < 400)
    return 'D';
  else if (keypad_value < 600)
    return 'L';
  else if (keypad_value < 800)
    return 'S';
  else
    return 'N';
}
////Hauptmenu
void hauptmenu()  {//Hier befindet sich der Fehler
  lcd.clear();
  lcd.setCursor(0, 0);

  switch (menuseiteakt)
  {
    case 0:
      //bei drücken von U oder  D  soll direkt nächstes menü aufgerufen werden
      // Oderverknüpfung beider Tasten mit || funktioniert nicht
      key = 'N';//taste sofort auf N setzen klappt nicht
      while (key != 'U') {
        key = ReadKeypad();
        displaytime();
      }
      delay(200);
      lcd.setCursor(0, 1);
      lcd.print(key);
      break;

    case 1:
      lcd.write("1.Menu A");
      lcd.setCursor(0, 1);
      lcd.print(menuseiteakt);
      delay(200);
      break;

    case 2:
      lcd.print("2. Menu B");
      lcd.setCursor(0, 1);
      lcd.print(menuseiteakt);
      delay(200);
      break;
    case 3:
      lcd.print("3. Menu C");
      delay(200);



      break;
    case 4:
      lcd.print("4. Menu D");
      delay(200);



      break;
  }
}
///Waitbutton
void waitbutton() {//Dient zum warten auf loslassen des Tasters

  while (analogRead(keypad_pin) < 800) {}

}
////Casecount
void casecount()  {//Zählt die Menüseiten  für Switch Case


  if (key == 'U') {
    menuseiteakt++;

    if (menuseiteakt > menuseitegesamt) {
      menuseiteakt = 0;
    }
  } else if (key == 'D') {
    menuseiteakt--;
    if (menuseiteakt < 0) {
      menuseiteakt = menuseitegesamt;
    }
  }
  if (menuseiteakt != menuseitealt) {

    menuseitealt = menuseiteakt;
    hauptmenu();
  }


}
////Funktionen Menüaufruf
void menu1() {
  switch (men1) {
    case 1:                                  //Dient zumn einstellen der Stunden
      lcd.clear();
      //lcd.print("in menu1");
      while (ReadKeypad() != 'L') {
        key = ReadKeypad();
        // waitbutton();
        initialHour();

        //lcd.print(key);
        //delay(10);
      }
      menuseiteakt = 0;
      break;
    case 2:                                   // Dient zum enstellen der Minuten ( weiterspringen des Crusors)

      while (ReadKeypad() != 'L') {
        key = ReadKeypad();
        // waitbutton();
        initialMin();

        //lcd.print(key);
        //delay(10);
      }
      menuseiteakt = 0;

      break;
  }
}
/////Funktionen ZEIT
int seconds() {
  s = initialHours * 3600; //Brechnet aus Stunden die sekunden
  s = s + (initialMins * 60); //Berechnet aus Minuten die Sekunden und addiert auf
  s = s + initialSecs;     //Addiert die Sekunden hinzu
  s = s + (millis() / 1000); // alle 1000 ms wird 1 sekunde aufaddiert
  return s;
}
int hours() {
  hrs = seconds();
  hrs = hrs / 3600; // umrechung Sekunden in Stunden
  hrs = hrs % 24; //Modulo zur ausgabe der Stunden
  return hrs;
}
int mins() {
  minutes = seconds();    // siehe hours()
  minutes = minutes / 60;
  minutes = minutes % 60;
  return minutes;
}
int secs() {
  sec = seconds();        //siehe hours()
  sec = sec % 60;
  return sec;
}
char sep() {

  s = millis() / 1000;     //alle 1000 ms bei geraden zahlen ":" printen
  if (s % 2 == 0)
  {
    lcd.print(":");
  }
  else {
    lcd.print(" ");     // bei ungeraden zahlen " " printen
  }
}
void printDigits(byte digits) {
  if (digits < 10)
    lcd.print('0');   // wenn digits < 10 führenden Null einfügen
  lcd.print(digits);//
}
void displaytime() {// gibt Uhrzeit in loop in der Form HH:MM:SS auf display aus 
  key = ReadKeypad();
  lcd.setCursor(0, 0);
  printDigits( hours());
  sep();
  printDigits(mins());
  sep();
  printDigits(secs());

  lcd.setCursor(1, 0);
  lcd.print(hrs);

}
////Funktionen Zeiteinstellung
void initialHour() {//Per Modulo einzelne stellen errechnen und per taster hochzählen 
                    
  int s = millis() / 1000;
  int stunden10 = (initialHours / 10) % 10;
  int stunden1 = initialHours % 10;
  //lcd.clear();

  lcd.setCursor(0, 0);
  lcd.print(stunden10);
  lcd.setCursor(1, 0);
  lcd.print(stunden1);
  lcd.setCursor(2, 0);
  sep();
  lcd.setCursor(3, 0);
  lcd.print("00");
  lcd.setCursor(5, 0);
  sep();
  lcd.print("00");

  if (key == 'U') {
    waitbutton();
    //delay(500);
    initialHours++;

    lcd.setCursor(0, 0);
    lcd.print(stunden10);
    lcd.setCursor(1, 0);
    lcd.print(stunden1);
    lcd.setCursor(2, 0);
    sep();



  }
  if (initialHours > 23) {
    initialHours = 0;
  }
  if (initialHours < 0) {
    initialHours = 23;
  }
  if (key == 'R') {
    men1 = 2;
    menu1();
  }
}
void initialMin() {
  int s = millis() / 1000;
  int min10 = (initialMins / 10) % 10;
  int min1 = initialMins % 10;
  //lcd.clear();

  lcd.setCursor(3, 0);
  lcd.print(min10);
  lcd.setCursor(4, 0);
  lcd.print(min1);
  lcd.setCursor(5, 0);
  sep();


  if (key == 'U') {
    waitbutton();
    //delay(500);
    initialMins++;

    lcd.setCursor(3, 0);
    lcd.print(min10);
    lcd.setCursor(4, 0);
    lcd.print(min1);

  }
  if (initialMins > 59) {
    initialMins = 0;
  }
  if (initialMins < 0) {
    initialMins = 59;
  }

}

Hallo,
ein Menü zu programmieren, ist schon eine Herausforderung.
Daher mein Vorschlag, verwende das folgende Menü:

LCD-Menü

Das ist sehr flexibel und du kannst damit auch eine Schaltuhr aufbauen, habe ich auch damit gemacht.
Und du kannst dich dadurch auf die wesentlichen Dinge konzentrieren.

bananenmilch:
vielleicht kann mir ja jemand den Logikfehler erklären.

Soweit ich das sehe, hast Du drei Hauptprobleme:

  1. Das erste Problem ist, dass Deine Zeitschaltuhr keine "echte Zeit" (real time) zu sehen bekommt.
  2. Das zweite Problem ist, dass Du denkst, ein vollwertiges Menüsystem mit Haupt- und Untermenüs zu programmieren, wäre ein kleines Ding, das es entweder fertig für jeden möglichen Anwendungsfall (Hardware und Logik) bereits geben müßte, oder das in ein paar Zeilen mal eben von jedem Anfänger programmiert werden kann.
  3. Dein drittes Problem scheint zu sein, dass ein Program, das irgendwelche Busy-Waiting-Loops ("Warten auf") abarbeitet, irgendwas sinnvolles machen könnte.

In sinnvollen Mikrocontroller-Programmen gibt es kein "warten auf", sondern immer nur ein "reagieren auf". D.h. das Programm wartet nie, z.B. auf eine Eingabe des Benutzers. Sondern es reagiert:

  • Wenn der Benutzer keine Eingabe gemacht hat ==> mache nichts
  • Wenn der Benutzer eine Taste gedrückt hat ==> Mache dies oder das

Im übrigen sind vollwertige Menüsysteme mit Haupt- und Untermenüs nur schwierig zu realisieren, auch für erfahrene Programmierer, so dass Du Dich am besten eher auf ein einstufiges Einstell-Menü konzentrierst.

Zum Beispeil mit folgender Logik:

  1. Run-Mode /Zeitschaltuhr läuft, zeigt Uhrzeit und Schaltstatus an)
  2. SetClock-Mode, Zeit und Datum der RTC-Uhr sind einstellbar
  3. Timer1-Einstellmode, Ein und Aus-Zeit für Timer1 ist einstellbar
  4. Timer2-Einstellmode, Ein und Aus-Zeit für Timer2 ist einstellbar
  5. Timer3-Einstellmode, Ein und Aus-Zeit für Timer3 ist einstellbar
  6. Timer4-Einstellmode, Ein und Aus-Zeit für Timer4 ist einstellbar
    Mit der 'Select' Taste würdest Du dann zwischen den Betriebsarten umschalten, die übrigen Tasten dienen zur Realisierung von Einstellfunktion und "Sichern und Ende" bzw. "Einstellung abbrechen".

Im Handel als Fertiggerät angeboten wäre das dann wohl eine "elektronische 1-Kanal-Tagesschaltuhr mit 8 Schaltzeiten). Kleinster Schaltabstand üblicherweis 1 Minute. Also mindestens eine Minute zwischen Ein- und Ausschalten.

Ich weiß ja nicht, was das werden soll, es gibt ja verschiedene Schaltuhren:

  • Tagesschaltuhren
  • Tagesschaltuhren mit Wochentags-Schaltblöcken (Schalten abhängig vom Wochentag)
  • Wochenschaltuhren
  • Jahresschaltuhren
  • Mehrkanalschaltuhren mit Programmiermöglichkeiten für mehr als einen Schaltausgang

In jedem Fall benötigt eine Zeitschaltuhr eine "echte Zeit" ("real time"), üblicherweise bekommt ein Mikrocontroller diese von einem RTC-Modul (Realtime-Clock module), z.B. DS3231-Modul. Nur dann hat das Gerät ja nach einem Power-Off gefolgt von einem Power-On wieder automatisch die richtige Uhrzeit: RTC-Module werden üblicherweise mit einer kleinen Knopfzelle batteriegepuffert, so dass diese auch ohne externe Stromversorgung nicht nur die Zeit behalten, sondern auch korrekt weiterzählen, während das angeschlossene Gerät ausgeschaltet ist.

bananenmilch:
Momentan versuche ich eine Zeitschaltuhr zu realisieren mittels Displayshield von Saintsmart und Software.
Zusätzlich soll die Möglichkeit bestehen, die gewünschten Zeiten mittels der auf dem Shield befindlichen Taster über ein kleines Menü einzustellen.Das Einstellen der aktuellen Uhrzeit funktioniert soweit auch nun habe ich ein Problem beim Darstellen der Zeit im Hauptmenü.Ich möchte quasi vom Hauptbildschirm mittels HOCH und RUNTER durch das Menü schalten.

Wie gesagt, vollwertige, mehrstufige Menüs sind selbst für erfahrene Programmierer nicht einfach zu realisieren, so dass Du Dich besser aufeinfache Einstellmenüs konzentrierst, zwischen denen Du umschaltest (Endlicher Zustandsautomat, Finite State Machine), und einstellbar ist immer auch nur das, was gerade auf dem Bildscirm sichtbar ist.

Und diesen Einstellvorgang mußt Du irgendwie entweder "bestätitgen und sichern" oder "abbrechen und nicht sichern" können.

Auch das ist nicht ganz ohne. Und meine bevorzugte Programmlogik für "Sichern" oder "Abbrechen wäre das:
Zum Sichern: Zeige den Text "OK" neben dem einzustellenden Wert /z.Uhrzeit) an. Wenn der User den Einstellwert sichern möchte, bewegt er die Cursor-Schreibmarke auf dem Display so weit nach rechts, dass der Cursor entweder auf 'O' oder auf K' von "OK" steht und drückt dann den 'Select' Taster. Dann wird der eingestellte Wert ins Programm übernommen. Und wenn er vorgenommene Einstellungen verwerfen und den Einstellvorgang abbrechen möchte, dann bewegt er die Cursor-Schreibmarke einfach immer weiter nach rechts bis diese am rechten Rand des Displays verschwindet.

Wie Du siehst, programmiere ich solche Einstellmenüs immer mit aktiviertem Cursor als Schreibmarke, damit der User auch immer auf einen Block sehen kann, welche Stelle in der Uhrzeit oder im Datum gerade verändert werden kann.

Hallo zusammen,

ich kann mich "jurs" nur anschließen und bestätigen, dass solche mehrstufigen Menüs "kein Pappenstiel" sind. In Windows-IDE wie Delphi oder ähnliche ist alles ganz einfach: Ein Klick auf Menüpunkt X führt Funktion X aus. Fertig.

Wenn man sich aber wie damals in vielen DOS-Programmen durch mehrere Ebenen hangeln muß, kann es unter Umständen sehr haarsträubend werden. Auch ich habe nur beim "schnellen Überfliegen" festgestellt, dass einige augenscheinlich unlogische Verknüpfungen vorliegen, die sich gegenseitig behindern und das ganze Konzept undurchsichtig und fehlerträchtig machen.

Ganz nebenbei steht in der Dokumentation zu "LCD Menü" eindeutig, dass man unbedingt auf "delay(x)" verzichten soll - wobei aber im vorliegenden Programm eine Menge davon vorhanden sind. Hier kollidieren also mehrere Dinge.

Ich denke, dass es unbedingt notwendig ist, das gesamte Programm noch einmal komplett neu aufzuziehen gemäß Aussage von "jurs":

  • Wenn der Benutzer keine Eingabe gemacht hat ==> mache nichts
  • Wenn der Benutzer eine Taste gedrückt hat ==> Mache dies oder das

PS:
Erst vor wenigen Tagen habe ich mir eine kleine DigiClock mit einem 16x2 Display geschaffen, die sogar auf einem Tiny84 (mit Quartz) läuft. Hier habe ich auf jegliches delay(x) verzichtet (außer ein paar mS fürs Entprellen) und hangel mich mit nur 2 Tasten flüssig durch alle Einstell-Punkte und habe sogar noch "Zeit und Raum" für andere "Spielchen" im Tiny...

Rudi

Hallo schon mal danke für die Antworten.
Das Menü werde ich dann wohl noch einmal programmieren müssen...später.
Aber ich hatte mir durch die frage eigentlich erhofft eine Lösung speziell für den Fehler zu finden.

Eine andere Frage bezüglich der Zeit... mir ist aufgefallen, dass bei meiner Methode ohne RTC eine Uhr zu realisieren der Speicher überläuft. Ich habe im Forum die Datentypen für den Uno gesucht und auch gefunden. Jedoch funktioniert der unsigned long aus irgend einem Grund nicht. Ich möchte 24h*3600 =86400 in einer Variable abspeichern. Ab 19 Stunden fängt die Zeitausgabe an zu spinnen.
:confused:
Code:

#include <Time.h>
#include <TimeLib.h>
#include <LiquidCrystal.h>
#include <sainsmartkeypad.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
SainsmartKeypad keypad(0);


//Variablen
unsigned long s = 0L;
unsigned long sec = 0L;
unsigned  long hrs = 0L;
unsigned  long minutes = 0L;
int initialHours = 19;//variable to initiate hours
int initialMins = 0;//variable to initiate minutes
int initialSecs = 0;//variable to initiate seconds
uint8_t key;

void setup() {
  // put your setup code here, to run once:
  lcd.begin(16, 2);

  lcd.clear();


}

void loop() {
  // put your main code here, to run repeatedly:
  
     displaytime();
  }
  
 



/// Funktionen Zeit
int seconds()
{
  s = initialHours * 3600; //Brechnet aus Stunden die sekunden
  s = s + (initialMins * 60); //Berechnet aus Minuten die Sekunden und addiert auf
  s = s + initialSecs;     //Addiert die Sekunden hinzu
  s = s + (millis() / 1000); // alle 1000 ms wird 1 sekunde aufaddiert
  return s;
}
int hours()
{
  hrs = seconds();
  hrs = hrs / 3600; // umrechung Sekunden in Stunden
  hrs = hrs % 24; //Modulo zur ausgabe der Stunden
  return hrs;
}
int mins()
{
  minutes = seconds();    // siehe hours()
  minutes = minutes / 60;
  minutes = minutes % 60;
  return minutes;
}
int secs()
{
  sec = seconds();        //siehe hours()
  sec = sec % 60;
  return sec;
}
char sep()
{

  s = millis() / 1000;     //alle 1000 ms bei geraden zahlen ":" printen
  if (s % 2 == 0)
  {
    lcd.print(":");
  }
  else {
    lcd.print(" ");     // bei ungeraden zahlen " " printen
  }
}
void printDigits(byte digits)
{
  if (digits < 10)
    lcd.print('0');   // wenn digits < 10 führenden Null einfügen
  lcd.print(digits);//
}

void displaytime()
{
  lcd.setCursor(0, 0);
  printDigits( hours()%24);
  lcd.setCursor(2, 0);
  sep();
  lcd.setCursor(3, 0);
  printDigits(mins());
  lcd.setCursor(5, 0);
  sep();
  lcd.setCursor(6, 0);
  printDigits(secs());



}

Um noch einmal auf meine Ursprüngliche Frage zurückzukommen.
Das Menü und das Einstellen der Parameter klappt ich benötige lediglich Hilfe um die While Schleife entweder mit der UP oder DOWN Taste zu beenden. While(ReadKeypad()!='U'||'D'){} funktioniert leider nicht.Hat den jemand einen Tipp?
Kann mir jemand sagen wie genau die Methodik heißt, um auf Tastendrücke zu reagieren und nicht zu warten ?(Polling, Interuppt in C?)

@Rudi diese einfache Menüstruktur realisierst du wie?(ebenfalls per Switch CASE oder gibt es da Tricks und Kniffe die ich noch nicht kenne.)

Ich habe mir ebenfalls ein Arduino Lernbuch von Erik Bartman bestellt ,aber bis dahin muss es erstmal so gehen.

Gruß Bananenmilch

Hallo

wie gesagt, es ist nur eine recht einfache DigiClock auf einem Tiny84 und mit 16x2 Display. Das Prinzip dürfte aber im Grunde vergleichbar sein, wie du es haben möchtest. Zuerst wird im Display die Uhrzeit mit Datum und Wochentag angezeigt, beim Betätigen der "Modus"-Taste wird nach "Stellen" umgeschaltet (Variable "modus"). Hierzu habe ich die hauptsächliche loop() recht einfach gehalten und es blinkt sogar eine "BetriebsLED" im Sekundentakt, egal was ich mache:

void loop() 
{  
  // "BetriebsLED" blinkt im (Halb-)Sekundentakt
  if( millis() - last >= 500 )
    {
      last = millis();
      digitalWrite( blueLED, blue = ! blue );      
      // Bei negative Flanke (= je 1 Sek.): Zeit rechnen
      if( ! blue )
        {
          RechneZeit(); // Rennt intern weiter, egal was ich einstelle
          // Zeit zeigen nur wenn Modus == 0
          if( modus == 0 )
            ZeigeZeit();
        }
    }

  // Taste "Modus"?
  if( ! digitalRead( modKEY ) ) 
    ModusWechseln();   

  // Taste "Stellen"? (Nur wenn "Modus 0")
  if( modus > 0 && ! digitalRead( setKEY ) )
    Stellen(); 
}

Beim Betätigen der Modus-Taste schaltet er dann sofort auf das entsprechende "Menü" und fragt ab, was ich stellen möchte. Hier hangel ich mich über Tag, Monat, Jahr... weiter bis Sekunde:

void ModusWechseln()
{
  delay( debounce ); 
  newMod = ! digitalRead( modKEY );
  if( newMod != oldMod )
    {
      oldMod = newMod;
      if( ! newMod )
        {
          // Stellen-Modus wird angezeigt
          modus++;
          lcd.setCursor( 0, 0 );
          lcd.write( zeileS1 );
          lcd.setCursor( 0, 1 );
          lcd.write( zeileS2 );
          lcd.setCursor( 14, 1 );          
          lcd.write( (byte)1 );
          lcd.write( (byte)2 );
          
          // Modus je nach Auswahl
          lcd.setCursor( 9, 0 );
          switch( modus )
          {
            case  1 : StelleWotag();   break; 
            case  2 : StelleTag();     break;
            case  3 : StelleMonat();   break;
            case  4 : StelleJahr();    break;
            case  5 : StelleStunde();  break;
            case  6 : StelleMinute();  break;            
            case  7 : StelleSekunde(); break;            
            default : modus = 0;       break; 
          }
          
          // Am Ende Re-Initialisierung des Displays je nach Modus
          if( modus == 0 ) 
            ZeigeZeit(); 
          else  
            AusgabeStellen(); 
        }
    }
}

"Stellen" bietet dann die Möglichkeit, alle Werte +1 zu zählen... mit den entsprechende Überläufen 59 -> 0, 23 -> 0 oder 31 -> 1 usw.

Mit einem Mega328 hatte ich natürlich mehr Pins für die Bedienung frei und konnte auch -1 zählen oder das Menü mittendrin abbrechen usw. Aber ich wollte es mal auf einem Tiny84 versuchen! Mit Quartz und LCD-Display habe ich aber (abzüglich LED) nur noch 2 Pins übrig. Es funzt jedoch wunderbar ohne "Hänger" oder ähnliches.

Die weiteren Funktionen ("Zeit zeigen" / "Zeit rechnen") sind ähnlich wie in deinem Programm.
Ich hoffe, das hilft ein wenig weiter.
Rudi

bananenmilch:
Jedoch funktioniert der unsigned long aus irgend einem Grund nicht.

Ja klar, wenn Du "unsigned long" als Datentyp überhaupt nicht verwendest, wo Du unsigned long verwenden müßtest, dann funktioniert "unsigned long" natürlich nicht.

Zum Beispiel hast Du bei dieser Funktion als Rückgabewert "int" deklariert:

int seconds() {
  s = initialHours * 3600; //Brechnet aus Stunden die sekunden
  s = s + (initialMins * 60); //Berechnet aus Minuten die Sekunden und addiert auf
  s = s + initialSecs;     //Addiert die Sekunden hinzu
  s = s + (millis() / 1000); // alle 1000 ms wird 1 sekunde aufaddiert
  return s;
}

Eine als "int" deklarierte Funktion kann NIEMALS "unsigned long" zurückliefern, sondern der Rückgabewert ist immer int.

@ Jurs danke :slight_smile: das hatte ich inzwischen gemerkt, jedoch bleibt das gleiche Problem sobald die Stunden einen gewissen wert überschreiten kommt nur noch Schmuh aus der Berechnung raus. Ich verstehe nicht wieso der unsigned long nicht ausreicht um 24 Stunden in Sekunden abzuspeichern. :frowning: :confused:

bananenmilch:
Ich verstehe nicht wieso der unsigned long nicht ausreicht um 24 Stunden in Sekunden abzuspeichern. :frowning: :confused:

Ein Programm macht immer das, was Du programmierst. Und "unsigned long" reicht locker aus, um die Anzahl der Sekunden von mehr als 100 Jahren zusammenzuzählen.

Was Du machst, ist aber int-Rechnung mit Integer-Overflow:

int initialHours = 19;//variable to initiate hours

Also initialHours ist int. und wenn Du sowas machst:

s = initialHours * 3600; //Brechnet aus Stunden die sekunden

dann bastelst Du einen Integer-Überlauf.

Relevant ist das, was Du auf der rechten Seite vom Gleichheitszeichen machst.
initialHours ist int und 3600 ist auch int.

Daher ergibt "initialHours * 3600" eine Rechnung "int mal int mit int-Überlauf".
Dass Du anschließend versuchst, das übergelaufene int-Ergebnis der Rechnung an ein unsigned long zuzuweisen, rettet Dir nichts mehr.

Die Rechenregeln von C sind:

int mal int = Ergebnis int
long mal long = Ergebnis long
unsigned long mal unsigned long = Ergebnis unsigned long

Komplizierter wird es nur bei mixed-Berechnungen, wenn Du Rechnungen mit unterschiedlichen Datentypen machst. Aber bei Berechnungen mit zwei identischen Zahlentypen ist das Ergebnis auch wieder vom selben Zahlentyp.

Wenn Den ergebnis vom Wertebereich her passen soll, müßte die Rechnung eher sein
unsigned long mal int
oder
int mal unsigned long

Ich schlage Dir mal zwei Codezeilen vor, die keinen int-Überlauf verursachen und die an "long" oder auch "unsigned long" problemlos zugewiesen werden können;

s = initialHours * 3600UL;  // hier wäre 3600 ein "unsigned long"
s = (unsigned long) initialHours * 3600;  // hier wäre initialHours zum Rechnen auf ein "unsigned long" gecastet, bevor gerechnet wird

In den beiden Fällen passt es ohne Integer-Überlauf.

Oder mal als gesamte Funktion geschrieben, mit Funktionsrückgabewert "unsigned long":

unsigned long seconds() {
  s = initialHours * 3600UL; //Brechnet aus Stunden die sekunden in "unsigned long"
  s = s + (initialMins * 60); //Berechnet aus Minuten die Sekunden und addiert auf
  s = s + initialSecs;     //Addiert die Sekunden hinzu
  s = s + (millis() / 1000); // alle 1000 ms wird 1 sekunde aufaddiert
  return s;
}

Ein C-Programm macht immer das, was Du tatsächlich programmierst, und nicht das, was Du denkst. Ein Compiler ist kein Hellsehr, sondern arbeitet nach festen Regeln. Und "Hellsehen" gehört NICHT zu den eingebauten Rechenregeln eines Compilers.

:slight_smile: :slight_smile: :slight_smile: :slight_smile: Danke....bisschen Peinlich der Fehler aber der wird mir bestimmt kein zweites mal passieren.

Kann jemand gute Tutorials empfehlen um die Programmierung besser zu lernen ?

Lies mal zB ab hier, da ist ein Beispiel zum Zeit einstellen.

Lies mal ab hier, vielleicht was hilfreiches dabei: Pool-pumpen Steuerung mit TDS und Zeitschaltuhr

Newbie sucht Hilfe... DS3231 RTC mit MEZ/MESZ Umschaltung gesucht

Danke....bisschen Peinlich der Fehler

Ich kenne keinen, dem das so ähnlich noch nicht passiert ist. (Muss ja nicht jeder zugeben)
Und es ist so schmerzhaft, dass es dir nicht wieder passiert. beim nächsten Mal viel früher auffällt.

16bit int sind schon verflixt klein. Aber das ist ja der Spass an den microControllern.
Und c ist eine gemeine Sparche, denn des ist eigentlich klar dass das Ergebnis von int * int einen long Wertebereich braucht.

Hallo .. nach längerer Pause (FH-Prüfungen) widme ich mich jetzt wieder meinem Projekt. Ich habe das Menü bereits so verändert, dass ich nun per Tastendruck die aktuelle Uhrzeit einstellen kann. Nun hat sich jedoch das Problem ergeben, dass wenn ich mit der "Funktion : millis()" arbeite die bereits abgelaufene Zeit zu der eingestellten Zeit automatisch addiert wird. :confused:
Ich habe schon versucht eine zweite Variable mit millis() zu befüllen um um diese dann von der eingestellten Zeit wieder abzuziehen.Das hat auch nicht geklappt. Ich denke ich habe nen Denkfehler in meiner Zeitfunktion auf den ich einfach nicht selber komme. Vermutlich wieder irgendwas triviales.

Ich Poste mal die Code den ich bisher habe. Wenn jemand Zeit und Lust hat mal darüber zu grübeln danke ich bereits für die Hilfe :smiley:

#include <Time.h>
#include <TimeLib.h>
#include <LiquidCrystal.h>
#include <sainsmartkeypad.h>

LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
SainsmartKeypad keypad(0);
////VARS Aktuelle Zeit///
unsigned long hour1;
unsigned long min1;
unsigned long sek1;

//Startzeit
unsigned long hour2;
unsigned long min2;
unsigned long sek2;
//endzeit
unsigned long hour3;
unsigned long min3;
unsigned long sek3;

//Abzug millis()
//unsigned long z=millis();

unsigned long sek=0;
////Vars Menü und Tastenabfrage
char taste;
int modus=1;
int keypad_pin = A0;

void setup() {
  // put your setup code here, to run once:
  lcd.begin(16, 2);
  lcd.clear();
}

void loop() {

  sek = seconds(hour1, min1, sek1);
  
 // sek1 = sek1 + (millis() / 1000);
  
  taste = ReadKeypad();
  waitbutton();
  if (taste == 'U')
  { modus++;
    if (modus > 4) {
      modus = 1;
    }
    lcd.clear();
  }
  if (taste == 'D')
  {
    modus--;
    if (modus < 1) {
      modus = 4;
    }
    lcd.clear();

  }

/////////senden Einschaltsignal
  if(sek2==sek){
    lcd.setCursor(1,1);
    //lcd.clear();
    lcd.print("sende ein");
  }else if (sek3==sek){
    lcd.clear();
    lcd.print("sende aus");
  }

  switch (modus) {

    case 1:
      lcd.setCursor(0, 0);
      displaytime(hours(sek), mins(sek), secs(sek));
      //lcd.setCursor(1,1);
      //lcd.print(hour1);

      break;

    case 2:
    lcd.setCursor(0,0);
    lcd.print("Mom. Uhrzeit");
    if(taste=='S'){
         // min1=initialmin(taste);
            hour1=0;
            min1 =0;
            sek1 =0;
           
            hour1 = initialHour(taste);
       lcd.clear();
    }
     
      break;

    case 3:
      lcd.setCursor(0,0);
    lcd.print("Start Uhrzeit");
    if(taste=='S'){
         // min1=initialmin(taste);
            hour2=0;
            min2=0;
            sek2=0;
        hour2 = initialHour(taste);
       lcd.clear();
    }
      break;

    case 4:
     lcd.setCursor(0,0);
    lcd.print("End Uhrzeit");
    if(taste=='S'){
         // min1=initialmin(taste);
            hour3=0;
            min3=0;
            sek3=0;
        hour3 = initialHour(taste);
       lcd.clear();
    }
      break;

      
  }

}

/////////////////////////////////////////////////////////////Funktionen///////////////////////////////


char ReadKeypad() { //Liest die Analog werte der Taster us und gibt Chars zurück
  /* no button pressed 1023
    select  741
    left    503
    down    326
    up      142
    right   0
  */

  int keypad_value = analogRead(keypad_pin);
  if (keypad_value < 100)
    return 'R';
  else if (keypad_value < 200)
    return 'U';
  else if (keypad_value < 400)
    return 'D';
  else if (keypad_value < 600)
    return 'L';
  else if (keypad_value < 800)
    return 'S';
  else
    return 'N';
}

unsigned long seconds(unsigned long initialHours, unsigned long initialMins, unsigned long initialSecs){ 
  unsigned long s = 0;
  s = initialHours * 3600;    //Brechnet aus Stunden die sekunden
  s = s + (initialMins * 60); //Berechnet aus Minuten die Sekunden und addiert auf
  s = s + initialSecs;        //Addiert die Sekunden hinzu
  s = s + (millis() / 1000);  // alle 1000 ms wird 1 sekunde aufaddiert
  return s;
}

unsigned long hours(unsigned long sekunden){ 
  unsigned long hrs = 0;
  hrs = sekunden;
  hrs = hrs / 3600; // umrechung Sekunden in Stunden
  hrs = hrs % 24; //Modulo zur ausgabe der Stunden
  return hrs;
}

unsigned long mins(unsigned long sekunden){
  unsigned long minutes;
  minutes = sekunden;    // siehe hours()
  minutes = minutes / 60;
  minutes = minutes % 60;
  return minutes;
}

unsigned long secs(unsigned long sekunden){
  unsigned long sec;
  sec = sekunden;        //siehe hours()
  sec = sec % 60;
  return sec;
}

char sep(){
  unsigned long s = millis() / 1000;     //alle 1000 ms bei geraden zahlen ":" printen
  if (s % 2 == 0)
  {
    lcd.print(":");
  }
  else {
    lcd.print(" ");     // bei ungeraden zahlen " " printen
  }
}

void printDigits(byte digits){
  if (digits < 10)
    lcd.print('0');   // wenn digits < 10 führenden Null einfügen
  lcd.print(digits);
}

void displaytime(unsigned long stunden, unsigned long minuten, unsigned long sekunden){
  lcd.setCursor(0, 0);
  printDigits( stunden % 24);
  lcd.setCursor(2, 0);
  sep();
  lcd.setCursor(3, 0);
  printDigits(minuten);
  lcd.setCursor(5, 0);
  sep();
  lcd.setCursor(6, 0);
  printDigits(sekunden);
}

void waitbutton() {//Dient zum warten auf loslassen des Tasters

  while (analogRead(keypad_pin) < 800) {}

}

unsigned long initialHour(char taste) {//Per Modulo einzelne stellen errechnen und per taster hochzählen
  unsigned long initialHours=0;
  unsigned long stunden10=0;
  unsigned long stunden1=0;
   
    while(taste!='L'){
    taste=ReadKeypad();
    
  stunden10 = (initialHours / 10) % 10;
  stunden1 = initialHours % 10;
 
  lcd.setCursor(0, 0);
  lcd.print(stunden10);
  lcd.setCursor(1, 0);
  lcd.print(stunden1);
  lcd.setCursor(2, 0);
  sep();
  lcd.setCursor(3, 0);
  lcd.print("00");
  lcd.setCursor(5, 0);
  sep();
  lcd.print("00");

  
  
  if (taste == 'U') {
  
    waitbutton();
  
    initialHours++;
    


  }
  if (initialHours > 23) {
    initialHours = 0;
  }
  if (initialHours < 0) {
    initialHours = 23;
  }
   if(taste=='R'){
    min1=initialmin(taste);
     
    return initialHours;
    /// zusehen, dass man auf minuten schalten kann 
     modus=1;
     
    
  }

  
  }
  
}
unsigned long initialmin(char taste) {//Per Modulo einzelne stellen errechnen und per taster hochzählen
  unsigned long initialmins=0;
  unsigned long minuten10=0;
  unsigned long minuten1=0;

  waitbutton();
    while(taste!='L'){
    taste=ReadKeypad();
    
  minuten10 = (initialmins / 10) % 10;
  minuten1 = initialmins % 10;
 
  lcd.setCursor(0, 0);
  lcd.print(hour1);
 
  lcd.setCursor(2, 0);
  sep();
  lcd.setCursor(3, 0);
  lcd.print(minuten10);
  lcd.setCursor(4, 0);
  lcd.print(minuten1);
  lcd.setCursor(5, 0);
  sep();
 

  
  
  if (taste == 'U') {
  
    waitbutton();
  
    initialmins++;
    


  }
  if (initialmins> 59) {
    initialmins = 0;
  }
  if (initialmins < 0) {
    initialmins = 59;
  }
  
   if(taste=='R'){
     modus=1;
     //hier sollten die breits vergagenen sekunden abgezogen werden.:(
   
     
  return initialmins;///
 
  }
    
  
  }

}

Ich habe schon versucht eine zweite Variable mit millis() zu befüllen um um diese dann von der eingestellten Zeit wieder abzuziehen.Das hat auch nicht geklappt. Ich denke ich habe nen Denkfehler in meiner Zeitfunktion

Mit millis() alleine wirst du selten glücklich, ausser du nutzt den Reset-Taster ( z.B. für eine Stoppuhr )
Wenn du die aktuelle Uhrzeit einmal stellst und ab da mit millis() weiterarbeiten willst, brauchst du immer die Differenz zu dem da gültigen millis() - Wert. Oder du rechnest dir den theoretischen millis() -Wert um Mitternacht aus und wandelst die Differenz zwischen den aktuellen millis() und diesem Initialwert in Sekunden und diese in Stunden/Minuten/Sekunden um. Oder ... auf jeden fall brauchst du immer einen Vergleichswert für millis() ...

Mich würde einfach mal interessieren, warum du eine Schaltuhr ohne RTC aufbauen möchtest.

Genau wird das nicht! Ok, man lernt daraus, mit der RTC aber auch. :wink:

Naja ... sagen wir mal das ist so gewachsen. :slight_smile: :slight_smile:
Von der RTC hab ich auch schon gehört , die werde ich in meinen nächsten Projekten verwenden.Aber so ungenau kann doch die millis() Funktion nicht sein...Es ist doch auch eine Art Taktgeber auf dem Mikrocontroller verbaut oder ist der echt so " beliebiges schlechtes Adjektiv einfügen "? Ich hab schon versucht den aktuellen millis Wert mit Beendigung der Zeitangabe abzuziehen. Klappt nur irgendwie nicht :frowning:

Eine echte Sekunde dauert ca. 997 .. 1002 "millis". Bei einem Resonator-genauen Arduino.

Am Tag also > 1 Minute Abweichung. Das ist ok für eine Urlaubsschaltung "abends 2 Stunden eine Lampe an" für ein paar Wochen.

Diese Genauigkeit reicht vorerst vollkommen aus. Nur bekomme ich es nicht einfach nicht Implementiert sobald ich die Zeiteinstellung beende und wieder in den Case "Zeitanzeige" switche die bereits vergangenen Sekunden abzuziehen.Ich verzweifel hier noch ... :disappointed_relieved: Das beispiel" delay without delay" habe ich bereits angeschaut, aber das will einfach nicht so wie ich das möchte :smiley: Ich sehe sozusagen den Wald vor lauter Bäumen nicht mehr :cold_sweat: Ich bräuchte sozusagen mal ein 2tes Gehirn was mal den Code durchsieht und mir sagt, wie ich das in der Funktion am besten implementiere.

michael_x:
Eine echte Sekunde dauert ca. 997 .. 1002 "millis". Bei einem Resonator-genauen Arduino.

Am Tag also > 1 Minute Abweichung. Das ist ok für eine Urlaubsschaltung "abends 2 Stunden eine Lampe an" für ein paar Wochen.

Naja...ob das so stimmt, kann ich nicht nachvollziehen, ich bin aber auch kein Mathematiker, der das genau ausrechnen möchte. Ich befürchte es wird deutlich über einer Minute sein.

Der Keramikresonator auf dem Arduino wäre für mich in einer Schaltuhr viel zu ungenau.

Und wenn du auf eine RTC umsteigst, hast du die ganzen aktuellen Probleme nicht mehr.
Da kommen sicher andere. :wink:

Und genau deswegen möchte ich lieber meine eigenen Probleme erstmal Lösen bevor ich mich auf ganz andere Sachen stürze..

Und ich hab es geschafft... :art: Ich war schlicht und einfach zu dumm die Millis()/1000 zu berechnen um auf Sekunden zu kommen um diese dann abzuziehen.
Bezüglich der Ungenauigkeit messe ich einfach mal den Zeitunterschied in 24h und korrigiere diesen dann beim Umschalten der Stunden auf den neuen Tag. Ist zwar nicht schön aber besser als jetzt schon wieder alles von vorne zu Programmieren.