Bekomme Stopuhr nicht zum laufen

Hallo Zusammen,
ich habe mir einen Sketch für einen Timer zusammen gebastetelt. Die Stopuhr hat zwei Timer. Leider bekomme ich keine gestartet und finde den Fehler nicht. Die Uhr soll starten sobald der Taster gedrückt wird. Allerdings passiert dies leider nicht. Das Display läufte, aber die Uhr startet nicht wenn der Taster gedrückt wird. Wo ist mein (Denk-)fehler? :slightly_frowning_face:
Der Code ist noch nicht komplett. Bis jetzt habe ich nur die Funktion zum starten der Uhr eingebaut.
Auf dem seriellen monitor läuft die Uhr sofort

#include <Wire.h> // Wire Bibliothek einbinden
#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)" 

int i = 0;
int TASTER_1 = 2;
int TASTER_2 = 3;
// Anzahl an Millisekunden, die das Programm nach Tastendruck wartet, 
// bevor erneut ein Druck auf einen Taster ausgewertet wird.
const unsigned int BUTTON_DELAY = 300;


// Mehrdimensionales Array für Zeitmessung
// [0][0] = Start 1
// [0][1] = Start 2
// [1][0] = Ende  1
// [1][1] = Ende  2
unsigned long timer[2][2] = {{0, 0}, {0, 0}};
unsigned long buttonPressed[2] = {0, 0};

// TasterStatus sorgt für Start/Neustart
bool TasterStatus = true;

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

pinMode(2, INPUT);
pinMode(3, INPUT);
  delay(500);

}
void loop() {
  TASTER_1=digitalRead(2);
  TASTER_2=digitalRead(3);
  refreshLCD(getTimerDauer(0), getTimerDauer(1));   
  delay(50);
}

  unsigned long getTimerDauer(int index){
  unsigned long timerStart = timer[0][index];                                     // Startzeit für gewählten Timer ermitteln
  unsigned long timerEnde = timer[1][index]==0 ? millis() : timer[1][index];      // Endzeit für gewählten Timer ermitteln
                                                                                  // wird die aktuelle Zeit als Endpunkt verwendet
                                                                                                                                             
  return timerStart==0 ? 0 : timerEnde - timerStart;                              // Wenn Startzeit gesetzt ist, Differenz von Start- und Endzeit zurückgeben. Sonst 0.
}


void startTimer(int index){
  timer[0][index] = millis();                                                     // Startzeit des Timers setzen
}

void stopTimer(int index){
  timer[1][index] = millis();                                                     // Endzeit des Timers setzen
}

void resetTimer(int index){
  timer[0][index] = 0;                                                            // Startpunkt für Timer zurücksetzen
  timer[1][index] = 0;                                                            // Endpunkt für Timer zurücksetzen
}

void refreshLCD(unsigned long time1, unsigned long time2){
  lcd.setCursor(0,0);
  lcd.print("ROT : ");
  printFormattedTime(time1);

  lcd.setCursor(0,1);
  lcd.print("BLAU: ");
  printFormattedTime(time2);
}

void printFormattedTime(unsigned long timeInMs){
  // Zeit in Millisekunden umrechnen in Stunden/Minuten/Sekunden
  unsigned long sekunde = (timeInMs/1000) % 60;                                   
  unsigned long minute  = (timeInMs/60000) % 60;
  unsigned long stunde  = (timeInMs/3600000) % 24;
  Serial.print("Time: ");
  Serial.println(timeInMs);
  Serial.println(sekunde);
  Serial.println(minute);
  Serial.println(stunde);
  char str1[12];
  sprintf(str1, "%02ld:%02ld:%02ld", stunde, minute, sekunde );                    
  lcd.print(str1);
}
void Taster(){
  
  buttonPressed[i] = millis();
  
  if (digitalRead(i) == HIGH 
  && buttonPressed[i] + BUTTON_DELAY < millis()){
  
      Serial.print("Start Timer ");
        startTimer(i);               
    }
    Serial.println(i);
}
boolean isTimerRunning(int index){
  return timer[0][index] > 0 && timer[1][index] == 0;                             // Timer läuft, wenn Startzeit gesetzt, aber die Endzeit noch 0 ist.
}

boolean isTimerStopped(int index){
  return timer[0][index] > 0 && timer[1][index] > 0;                              // Timer wurde gestoppt, wenn Startzeit und Endzeit gesetzt sind.
}

Ich sehe nicht, wo du deine Uhr starten willst.
Das solltest du mal besser kommentieren.

Und irgendwie ist deine Schaltung mit den Tastern falsch.
Die Widerstände gehören einseitig an den Pin.

Hallo,

du verwendest deine Variablen nicht. Du liest zum Bsp. in TASTER_1 und TASTER_2 verwendest diese aber nicht. Genauso mit der Indexvariablen i, die man ohne jede Not niemals global definiert. Standardmäßig gehört die in eine for Schleife.

Dein 2 dimensionales Area ist auch nicht so praktisch. Nimm lieber ein struct und ein Array.
Vorschlag:

struct DatenTimer // alles was für einen Timer benötigt wird
{
  byte taster;
  unsigned long start;
  unsigned long stop;
  bool status;
};

DatenTimer arrayTimer[2] =
{
  {2, 0, 0, false},  // tasterPin, start, stop, status
  {3, 0, 0, false}  
};

void setup() 
{
  for (auto &i : arrayTimer)
  {
    pinMode(i.taster, INPUT);   // oder INPUT_PULLUP
  }
}

void loop()
{
  for (auto &i : arrayTimer)
  {
    if ( digitalRead(i.taster) ) i.start = millis();
  }
}

Desweiteren musst du dich noch um die Tasterentprellung kümmern und der Statusumschaltung ob der Timer läuft oder nicht. Also ob bei Tasterbetätigung der Timer gestoppt oder gestartet werden soll. Ist auf jeden Fall eine interessante Aufgabe. Beim programmieren immer nur an einen Timer denken, nicht an mehrere. Das “mehrere” erledigt das array zusammen mit der for Schleife autoelektrisch.

Anderes Bsp. mit Konstruktor

struct StoppUhr // alles was für einen Timer benötigt wird
{
  const byte taster;
  unsigned long start;
  unsigned long stop;
  bool status;

  // Konstruktor
  StoppUhr (const byte p) :
  // Initialisierungsliste
    taster{p},
    start{0},
    stop{0},
    status{false}
  {}
};

// hat den Vorteil den Pin konstant zu definieren.
StoppUhr arrayStoppUhr[2] =
{
  {2},  // Pin für Taster, der Rest ist im struct/Konstruktor schon initialisiert
  {3}  
};

void setup() 
{
  // for (StoppUhr &i : arrayStoppUhr) ohne 'auto' Keyword
  for (auto &i : arrayStoppUhr)
  {
    pinMode(i.taster, INPUT);
  }
}

void loop()
{
  for (auto &i : arrayStoppUhr)
  {
    if ( digitalRead(i.taster) ) i.start = millis();
  }
}

Nochwas zu den Tastern. Einer geht direkt auf Masse und der andere auf Plus? Soll nicht jede Stoppuhr ihren eigenen Taster haben? Lass den Widerstand weg, verbinde beide Taster nur mit Masse und verwende für pinMode ‘INPUTPULLUP’.
pinMode

1 Like

Danke für Eure Anregungen.
Ja, jeder Timer hat seinen eigenen Taster. Das mit der Masse auf dem einen Taster ist ein Fehler im meiner Zeichnung. Der sollte natürlich auch auf Plus gehen.
Ich werde den Sketch die Tage mal umstricken.

Und die andere Seite der Widerstände muss auf den Pin des Controllers.
So wie du es zeigst, hat der keine Wirkung.

1 Like

Nur um Missverständnisse zu vermeiden. Man hat 3 Möglichkeiten.

2 Likes

Soooooo… ich habe das Ganze Projekt jetzt einmal in die Tonne gekloppt und von Grund auf neu angefangen und das Ganze etwas, für mich, einfacher aufgebaut. Für die Taster habe ich mich an einem Beispiel aus dem Internet orientiert.
Meine Stopuhr läuft nun auch. Ich kann nun auch zwei Timer starten. Nun würde ich das Ganze aber gerne noch so haben, das Timer 1 mit dem roten Button startet, das funktioniert auch, und der blaue Taster den zweiten Timer startet. Wird der rote Taster gedrückt, soll der 2. Timer stoppen, wird der blaue Taster gedrückt, soll Timer 1 stoppen. Wird wieder der rote Taster gedrückt, soll Timer 1 weiterlaufen und Timer 2 stoppen usw…
Und da harpert es bei mir leider wieder. :frowning:
Da ich nur, wie mir geraten wurde :slight_smile: , mit einem Timer gearbeitet habe, weiß ich nun nicht wie ich das bewerkstelligen soll das der blaue Taster auch immer Timer 2 ist und nicht Timer 1, wenn dieser zuerst gedrückt wird.

#include <Wire.h> // Wire Bibliothek einbinden
#include <LiquidCrystal_I2C.h> // Vorher hinzugefügte LiquidCrystal_I2C Bibliothek einbinden
#include <OneButton.h>

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

#define ROTERBUTTON 2
#define BLAUERBUTTON 3
#define STOPBUTTON 4

boolean timerActive[2];   // 2 Timer aktiv true/false
long timerStartTime[2];   // 2 Startzeiten in Millisekunden
long timerTime[2];        // 2 Zählerstände in Millisekunden
byte nextStartTimer;
byte nextStopTimer;

void eingabe()
{
  static byte roterButtonState;
  static byte blauerButtonState;
  static byte stopButtonState;
  byte state;
  state=!digitalRead(ROTERBUTTON);
  if (state==HIGH && roterButtonState==LOW)
  {
    if (timerActive[nextStartTimer]==false)
    {
      timerActive[nextStartTimer]=true;
      timerStartTime[nextStartTimer]=millis();
      nextStartTimer=(nextStartTimer+1)%2;
    }
  }
    roterButtonState=state; 
    
    state=!digitalRead(BLAUERBUTTON);
  if (state==HIGH && blauerButtonState==LOW)
  {
    if (timerActive[nextStartTimer]==false)
    {
      timerActive[nextStartTimer]=true;
      timerStartTime[nextStartTimer]=millis();
      nextStartTimer=(nextStartTimer+1)%2;
    }
  }
  blauerButtonState=state;
  
  state=!digitalRead(STOPBUTTON);
  if (state==HIGH && stopButtonState==LOW)
  {
    if (timerActive[nextStopTimer]==true)
    {
      timerActive[nextStopTimer]=false;
      nextStopTimer=(nextStopTimer+1)%2;
    }
  }

  stopButtonState=state;
//  delay(5);    // Zeit zum Entprellen (entfällt bei "langsamer" loop-Funktion
}

void verarbeitung()
{
  for (int i=0;i<2;i++) // Zeiten für beide Timer aktualisieren
  {
    if (timerActive[i]==true) timerTime[i]=millis()-timerStartTime[i];
  }
}

void ausgabe()
{
  int hour,minute,second;
  char timebuf[12];
  for (int i=0;i<2;i++)
  {

    unsigned long time=timerTime[i];
    
    time=time/1000;
    second=time%60;
    time=time/60;
    minute=time%60;
    hour=time/60;
    sprintf(timebuf,"%02d:%02d:%02d ",hour,minute,second);
    
    lcd.setCursor(0,0);
    lcd.print("ROT : ");
    lcd.setCursor(0,1);
    lcd.print("BLAU: ");
    lcd.setCursor(6,i);
    lcd.print(timebuf);
  }
}


void setup()
{
  pinMode(ROTERBUTTON,INPUT_PULLUP);
  pinMode(BLAUERBUTTON,INPUT_PULLUP);
  pinMode(STOPBUTTON,INPUT_PULLUP);
  lcd.init(); //Im Setup wird der LCD gestartet 
  lcd.backlight(); //Hintergrundbeleuchtung einschalten (lcd.noBacklight(); schaltet die Beleuchtung aus). 

}

void loop()
{
  eingabe();
  verarbeitung();
  ausgabe();
}

Hallo,

geht in die richtige Richtung, nur ohne Struktur kommste in Schwulitäten. Merkste ja. Hab mal was mir in den Sinn kam getippt. Kompiliert fehlerfrei aber ungetestet. Musste noch ergänzen.
Die Umrechnung habe ich nicht überprüft, sollte ja stimmen laut deiner Aussage. Nur die Datentypen habe ich angepasst und den buffer ebenfalls, der kann locker bis zu 17 Zeichen lang werden.

Versuch mal …

// https://forum.arduino.cc/t/bekomme-stopuhr-nicht-zum-laufen/852979/7

struct DataStopUhr // alles was für eine Stoppuhr benötigt wird
{
  byte tasterStart;
  byte tasterStop;
  byte tasterStopAll;
  unsigned long start;
  unsigned long stop;
  unsigned long time;
  bool finish;
};

DataStopUhr arrayStopUhr[] =
{
  {2, 3, 4, 0, 0, 0, false},  // rot, blau, stopAll, zeitStart, zeitStop, zeit, finish
  {3, 2, 4, 0, 0, 0, false},  // blau, rot, stopAll, ...
};


void eingabe (void)
{
  unsigned long loopMillis = millis();
  
  for (auto &i : arrayStopUhr)
  {
    // Uhr wird ggf. gestartet
    if ( !digitalRead(i.tasterStart) )
    {
      i.start = loopMillis;
      i.finish = false;
    }

    // Uhr wird ggf. gestoppt
    if ( !digitalRead(i.tasterStop) || !digitalRead(i.tasterStopAll) )
    {
      i.stop = loopMillis;
      i.finish = true;
    }

    // verrechnen
    if ( i.finish )
    {
      i.time = i.stop - i.start;
    }    
  }
}


void ausgabe (void)
{
  unsigned long hour {0};
  byte minute {0};
  byte second {0};
  char buf[20];     // 10 + 1 + 2 + 1 + 2 + \0
  
  for (auto &i : arrayStopUhr)
  {
    if ( i.finish )
    {
      i.time = i.time/1000;   
      second = i.time%60;
      i.time = i.time/60;
      minute = i.time%60;
      hour = i.time/60;
      snprintf(buf, sizeof(buf), "%02lu:%02d:%02d", hour, minute, second);
      /*  
      lcd.setCursor(0,0);
      lcd.print("ROT : ");
      lcd.setCursor(0,1);
      lcd.print("BLAU: ");
      lcd.setCursor(6,i);
      lcd.print(timebuf);
      */
      i.finish = false;
    }
  }  
}


void setup() 
{
  for (auto &i : arrayStopUhr)
  {
    pinMode(i.tasterStart, INPUT_PULLUP);  
    pinMode(i.tasterStop, INPUT_PULLUP);  
    pinMode(i.tasterStopAll, INPUT_PULLUP);  
  }
}

void loop()
{
  eingabe();
  ausgabe();
}
1 Like

Hallo Doc,

danke für Deine Antwort.
Das mit dem Array für die StopUhr habe ich noch nicht ganz verstanden, glaube ich. Damit gebe ich an welche Pins wie belegt sind und was bei dem jeweiligen Taster in welcher Reihenfolge passiert? Aber wie sage ich nun dem roten button starte Timer1 in Zeile 1 auf dem Display, und dem blauen button startet Timer in Zeile 2, wenn dieser vor dem roten gedrückt wurde? Oder wird dies hier bereits definiert? Quasi:

DataStopUhr arrayStopUhr[] =
{
  {2, 3, 4, 0, 0, 0, false},  // <-- Zeile 1
  {3, 2, 4, 0, 0, 0, false},  // <-- Zeile 2
};

Hallo,

in der Struktur stehen die sogenannten Member Variablen. Alle diese Member hat diese Stoppuhr. Legt man mit dieser Struktur ein Array an gilt die Reihenfolge der Member Definition in der Struktur.

Die ersten 3 Werte sind die Pins der Taster. Die folgenden drei Nullen und false muss man bis C++11 im Array ebenfalls initialisieren.

Nebeninfo:
Ab C++14 kann man die letzten 4 Member in der Struktur defaultmäßig initialisieren und muss im Array nur die speziellen Member initialisieren. Klappt aber nur wenn die Reihenfolge angepasst ist. Mischen ist nicht, dafür braucht es dann einen Konstruktor.

struct DataStopUhr 
{
  byte tasterStart;
  byte tasterStop;
  byte tasterStopAll;
  unsigned long start {0}; // default für alle
  unsigned long stop {0};  // default für alle
  unsigned long time {0};  // default für alle
  bool finish {false};     // default für alle
};

DataStopUhr arrayStopUhr[] =
{
  {2, 3, 4},  // Uhr 0 ... rot, blau, stopAll
  {3, 2, 4},  // Uhr 1 ... blau, rot, stopAll
};

Weiter im Text.
Das man während die Uhr läuft die laufende Zeit sieht ist noch nicht eingebaut. Falls das die Frage war.

Uhr 0 im Array ist die erste Zeile. Uhr 1 im Array ist die zweite Zeile. Ich spreche gern mit Indexnummern, dann ist das immer eindeutig.

Der Starttaster von jeder Uhr wird mit i.tasterStart abgefragt. Das ist der Pin 2 für Uhr 0 und Pin 3 von Uhr 1. Das wiederholt sich mit i.tasterStop für beide Uhren.

Das mit dem tasterStopAll ist noch etwas unglücklich, kann man sehen wie man möchte ob es zur Uhr gehört oder nicht. Erleichtert jedoch die Abfrage und kann in der for Schleife mit erschlagen werden.

Theoretisch solltest du nach stoppen einer Uhr die gemessene Zeit angezeigt bekommen, wenn du das Display mit eingebaut hast. Klappt das soweit?

Ohne Bereichsbasierte for Schleife greift man auf die Member der Uhren mit Array wie folgt zu.
Bsp.

arrayStopUhr[0].tasterStart
arrayStopUhr[0].time
arrayStopUhr[1].tasterStop
arrayStopUhr[1].finish
1 Like

Hallo,

Problem verstanden, die Anzeige sprang zwischen zwischen beiden Uhren. Habe die Logik umgebaut, jetzt sieht man auch die Zeit pro Stoppuhr laufen.
Taster 2 betätigt → Uhr 0 läuft und Uhr 1 stoppt
Taster 3 betätigt → Uhr 1 läuft und Uhr 0 stoppt
Solange wie eine Uhr gestoppt ist, bleibt deren Anzeige bestehen.
Mit dem nächsten Start nullt sich die laufende Zeit automatisch.
Das liegt daran, dass ich keinen Member stop mehr verwende, sondern immer von dem aktuellen millis() den Startzeitpunkt subtrahiere.
Um die Schreiblast auf das LCD zu reduzieren, wird die Anzeige aller Sekunde aktualisiert, sobald sich der Sekundenwert einer Uhr ändert. Damit man der Optik wegen auch den Startwert 00:00:00 sieht, wird der Vergleichswert oldSecond überhöht initialisiert.
Damit das mit der Lcd Ausgabe überhaupt so klappt, verwende ich dafür keine Bereichsbasierte for Schleife. Damit kann man den Index gleich mit für die Lcd Zeile verwenden.
Zum Schluss habe ich die Berechnung der Ausgabe noch geändert. Es wird eine temporäre Variable verwendet. Ansonsten wird immer die aktuelle Zeitnahme überschrieben. Funktioniert solange man den Ablauf nicht ändert. Benötigt man die aktuelle Zeit irgendwann noch für andere Dinge wirds Brühe, weil wurde ja durch 1000 geteilt und überschrieben. Kann jetzt nicht mehr passieren.

Jetzt übergebe ich das in deine Hände. Weitere Änderungen nehme ich nicht vor.
Du musst nur die Lcd Lib anpassen die du verwendest.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.13
  Arduino Nano Every
  26.04.2021
  https://forum.arduino.cc/t/bekomme-stopuhr-nicht-zum-laufen/852979/11
*/

#include <SPI.h>
#include <DogLcdSPI.h>  

DogLcdSPI lcd (5, 6);  // DOGM163, RS, CS

struct DataStopUhr // alles was für eine Stoppuhr benötigt wird
{
  byte tasterStart;
  byte tasterStop;
  byte tasterStopAll;
  unsigned long start;
  unsigned long time;
  bool active;
  byte oldSecond;     // überhöhter Startwert für Ausgabe '0' auf Lcd
};

DataStopUhr arrayUhr[] =
{
  {2, 3, 4, 0, 0, false, 255},  // rot, blau, stopAll, zeitStart, zeit, active, ...
  {3, 2, 4, 0, 0, false, 255},  // blau, rot, stopAll, ...
};

const byte ANZAHLUHREN = sizeof(arrayUhr) / sizeof(arrayUhr[0]);

void setup()
{
  for (auto &i : arrayUhr)
  {
    pinMode(i.tasterStart, INPUT_PULLUP);  
    pinMode(i.tasterStop, INPUT_PULLUP);  
    pinMode(i.tasterStopAll, INPUT_PULLUP);  
  }
  
  SPI.begin();
  lcd.begin(DOG_LCD_M163, 38);    
  lcd.clear();

  lcd.setCursor(0,0);
  lcd.print("ROT :");
  lcd.setCursor(0,1);
  lcd.print("BLAU:");
}

void loop()
{
  eingabe();
  ausgabe();
}

void eingabe (void)
{
  unsigned long loopMillis = millis();
  
  for (auto &i : arrayUhr)
  {
    // Uhr wird ggf. gestartet
    if ( !digitalRead(i.tasterStart) && !i.active )
    {
      i.start = loopMillis;
      i.active = true;
    }

    // Uhr wird gestoppt unter der Bedingung das sie überhaupt läuft,
    // ansonsten gibts unschöne Effekte
    if ( i.active && (!digitalRead(i.tasterStop) || !digitalRead(i.tasterStopAll)) )
    {
      i.active = false;
    }

    // laufende Zeit ermitteln
    if ( i.active )
    {
      i.time = loopMillis - i.start;
    }    
  }
}


void ausgabe (void)
{
  byte hour {0};
  byte minute {0};
  byte second {0};
  char buf[10];     

  // Index vom Uhren Array dient mit zur Zeilenbestimmung auf dem Display.
  for (byte i=0; i<ANZAHLUHREN; i++)
  {
    if ( arrayUhr[i].active )
    {
      unsigned long temp = arrayUhr[i].time/1000;   
      second = temp%60;
      temp   = temp/60;
      minute = temp%60;
      hour   = temp/60;
      snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, minute, second);

      if( arrayUhr[i].oldSecond != second )
      {
        lcd.setCursor(6,i);
        lcd.print(buf); 
        arrayUhr[i].oldSecond = second;
      }     
    }
  }  
}
1 Like

Hallo,

noch ein Bugfix. Wenn für die Displayanzeige active abgefragt wird, dann wird die letzte Aktualisierung nicht ausgeführt und damit eine Sekunde unterschlagen. Habe diese Abfrage ganz entfernt, weil das Display sowieso auf Sekundenänderung reagiert.
Tipp:
Wenn es eine Sportuhr werden soll mit ms oder noch feinerer Auflösung, dann müßte man time vergleichen oder den Vergleich weglassen und die Refreshrate mit millis abhandeln damit sie optisch rund läuft. Soweit meine Überlegungen dazu.

void ausgabe (void)
{
  byte hour {0};
  byte minute {0};
  byte second {0};
  char buf[10] {'\0'};     

  // Index vom Uhren Array dient mit zur Zeilenbestimmung auf dem Display.
  for (byte i=0; i<ANZAHLUHREN; i++)
  {
    unsigned long temp = arrayUhr[i].time/1000;   
    second = temp%60;
    temp   = temp/60;
    minute = temp%60;
    hour   = temp/60;

    // display refreshrate every seconds
    if( arrayUhr[i].oldSecond != second )
    {
      snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, minute, second);
      lcd.setCursor(6,i);
      lcd.print(buf); 
      arrayUhr[i].oldSecond = second;
    }     
  }  
}
1 Like

Hallo Doc,

super so ist es fast perfekt. Einen kleinen Änderungswunsch hätte ich noch. :slight_smile: Dann ist es perfekt.
Ich möchte das wenn ein Timer gestopt wurde und wieder gestartet wird soll dieser nicht neu bei 0 beginnt, sonder die Zeit dort weitergezählt wird wo sie gestoppt wurde. Also quasi eine Pause.
Könntest Du mir hier noch einmal helfen, bitte.
Ich würde das jetzt so machen

    if ( !digitalRead(i.tasterStart) && !i.time > 0 )
    {
      i.start = loopMillis += i.time;
      i.active = true;
    }

klappt aber irgendwie nicht, weil er nicht erkennt das der Wert größer 0 ist.

Hallo,

eigentlich wollte ich mich damit nicht weiter beschäftigen. Etwas solltest du auch eigene Logik reinbringen. Ich meine du musst und solltest den Code auch erstmal verstehen. Außerdem sollte man nicht laufend die Bedingungen ändern wie man lustig ist. Wenn der weiterzählen soll, wann soll er dann genullt werden?

Ein Tipp gebe ich dir.
Du musst unterscheiden können zwischen Neustart und Restart. Bei Restart darf i.start nicht geändert werden sondern nur i.active auf true setzen.
Damit bildet er weiterhin bzw. erneut nur Differenzzeiten zum alten i.start Wert.
Erst wenn man i.start auf den aktuellen millis Wert setzt, wird die Uhr damit genullt. Weil die Differenz dann erstmal 0 ist.
Vielleicht benötigt man noch eine Statusvariable, vielleicht auch nicht.
Und man sollte sich Gedanken machen wie die Uhren nullt.
Wäre mit 3 Tastern dann eigentlich nur so möglich.
blau und rot für Start/Restart und der 3. Taster beide nullen.
Jetzt machste dir mal darüber Gedanken.

1 Like

Du kannst auch seit C++11 Membervariablen mit einer einfachen Zuweisung initialisieren

Hallo,

ja im struct alleine und wenn man kein Array verwendet wo man nur die Objekt spezifischen Member initialisieren möchte und den Rest default haben möchte.

Das hier geht mit C++11 nicht.

struct StoppUhr 
{
  byte pinStart;
  byte pinStop;
  byte pinStopAll;
  unsigned long start {0};
  unsigned long time {0};
  bool active {false};
  byte oldSecond {0};
};

StoppUhr arrayUhr[]
{             // Pins der Taster
  {2, 3, 4},  // rot, blau, stop
  {3, 2, 4},  // blau, rot, stop
};

Im Array fehlt die Initialisierung der restlichen Member. Da hilft nur Konstruktor oder höher wie C++11.

structMember_Array_Init:34:1: error: could not convert '{2, 3, 4}' from '<brace-enclosed initializer list>' to 'StoppUhr'

 };

 ^

structMember_Array_Init:34:1: error: could not convert '{3, 2, 4}' from '<brace-enclosed initializer list>' to 'StoppUhr'
1 Like

Grrr. Ich hatte in der falschen platform.txt nachgeschaut. Und hatte gar nicht mit C++11 kompiliert. :frowning: Das war früher mal einfacher

Das hat übrigens gar nichts mit Arrays zu tun, sondern liegt an der Definition von “aggregate initialization” generell:
https://en.cppreference.com/w/cpp/language/aggregate_initialization

“default initialization” geht seit C++11. Das ist das ich was ich meinte. Aber erst seit C++14 kann man das mit “aggregate initialization” kombinieren. Das hatte ich übersehen

Hallo,

ja hat nichts direkt mit einem Array zu tun. Nur erstelle ich hier die Objekte im Array, deswegen sprach ich von Arrays. Blöde Beschreibung zugegeben. Eine Objekt Initialisierung ohne komplette Parameterangabe geht mit C++11 so nicht. Einigen wir uns auf diesen Wortlaut? :wink:

Also Doc, es ist nicht so das ich hier keinen eigen Hirnschmalz mit einbringen möchte, auch verstehe ich die Funktionen in dem Sketch den Du geschrieben hast; nur ist es natürlich für mich als Anfänger etwas schwieriger eine Lösung zu finden, wenn man diese Art von Sketch, mit diesen Anforderungen noch nie gemacht hat.
Wie dem auch sei ich habe es nun noch mal alle Hirnwindungen beansprucht und bin halbwegs zu dem Step gekommen zu dem ich wollte. Auch ändere ich nicht ständig die Bedingungen, sondern versuche das Ganze step by step aufzubauen und zu erweitern.

Ich habe es nun hinbekommen das ich zwischen den beiden Uhren hin und her springen kann und diese nicht jedesmal nullen. Allerdings habe ich jetzt noch das Problem, das, auch wenn die eine Uhr pausiert, diese dann im Hintergrund weiterläuft und bei der nächsten Aktivierung auf den Wert springt der aktuell wäre, wenn man die Uhr nicht gestoppt hätte.
Hier finde ich auch nach Stunden der Suche leider keine Lösung. Für eine Hilfestellung wäre ich noch einmal sehr dankbar.

#include <SPI.h>
#include <Wire.h> // Wire Bibliothek einbinden
#include <LiquidCrystal_I2C.h> // Vorher hinzugefügte LiquidCrystal_I2C Bibliothek einbinden
#include <OneButton.h>

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


struct DataStopUhr // alles was für eine Stoppuhr benötigt wird
{
  byte tasterStart;
  byte tasterStop;
  byte tasterStopAll;
  unsigned long start;
  unsigned long time;
  bool active;
  bool pause;
  byte oldSecond;     // überhöhter Startwert für Ausgabe '0' auf Lcd
};

DataStopUhr arrayUhr[] =
{
  {2, 3, 4, 0, 0, 0, false, 255},  // rot, blau, stopAll, zeitStart, zeit, active, ...
  {3, 2, 4, 0, 0, 0, false, 255},  // blau, rot, stopAll, ...
};

const byte ANZAHLUHREN = sizeof(arrayUhr) / sizeof(arrayUhr[0]);

void setup()
{
  for (auto &i : arrayUhr)
  {
    pinMode(i.tasterStart, INPUT_PULLUP);
    pinMode(i.tasterStop, INPUT_PULLUP);
    pinMode(i.tasterStopAll, INPUT_PULLUP);
  }

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

  lcd.setCursor(0, 0);
  lcd.print("ROT :");
  lcd.setCursor(0, 1);
  lcd.print("BLAU:");
}

void loop()
{
  eingabe();
  ausgabe();

}


void eingabe (void)
{
  unsigned long loopMillis = millis();
  unsigned long lastMillis = millis();
  for (auto &i : arrayUhr)
  {
    // Uhr wird ggf. gestartet
     if ( !i.pause && !digitalRead(i.tasterStart) && !i.active )
    {
      i.start = loopMillis;
      i.active = true;
    }
    if ( i.pause && !digitalRead(i.tasterStart) && !i.active )
    {
      i.active = true;
    }

    // Uhr wird gestoppt unter der Bedingung das sie überhaupt läuft,
    // ansonsten gibts unschöne Effekte
    if ( i.active && (!digitalRead(i.tasterStop) || !digitalRead(i.tasterStopAll)) )
    {
      i.active = false;
      i.pause = true;
    }
    // laufende Zeit ermitteln
    if ( i.active )
    {
      i.time = loopMillis - i.start;
    }
    if ( i.pause )
    {
      lastMillis = millis();
    }
    if ( i.active > lastMillis )
    {
     i.time == lastMillis;
    }
  }
}


void ausgabe (void)
{
  byte hour {0};
  byte minute {0};
  byte second {0};
  char buf[10] {'\0'};

  // Index vom Uhren Array dient mit zur Zeilenbestimmung auf dem Display.
  for (byte i = 0; i < ANZAHLUHREN; i++)
  {
    unsigned long temp = arrayUhr[i].time / 1000;
    second = temp % 60;
    temp   = temp / 60;
    minute = temp % 60;
    hour   = temp / 60;

    // display refreshrate every seconds
    if ( arrayUhr[i].oldSecond != second )
    {
      snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, minute, second);
      lcd.setCursor(6, i);
      lcd.print(buf);
      arrayUhr[i].oldSecond = second;
    }
  }
}

Hallo,

Das hier funktioniert so nicht.

if ( i.active > lastMillis )
{
  i.time == lastMillis;
}
 	

Hier vergleichst du eine bool Variable mit einer unsigned long Variablen.
Und dann machst du einen Vergleich statt einer Zuweisung.
= … Zuweisung
== … Vergleich

Du hattest deine Bedingungen für die Tasteraktionen geändert.
Deswegen habe ich die Taster umbenannt. Start, Stop und Reset.
Das heißt die Uhr darf für das eventuelle spätere Resuming nur vom Stopptaster gestoppt werden.
Nicht wenn der gemeinsame Resettaster gedrückt wurde. Der Resettaster hat jetzt eine andere Funktion. Der setzt sturr alles zurück.
Also korrigieren wir die Bedingung wo wir uns den aktuellen millis Wert merken wenn die Uhr gestoppt wurde.
Zeile 103.
Gleichzeitig benötigen wir noch eine weitere bool Variable zur Unterscheidung ob es mit Resuming weitergeht oder mit Nullung, wenn die Uhr wieder gestartet werden sollte.
Das wäre die Variable resume.
Das heißt bei Stop setzen wir die Uhr
a) auf nicht aktiv
b) setzen resume auf aktiv
c) merken uns den millis Wert zum Stoppzeitpunkt

Wird der Resettaster gedrückt, Zeile 113, werden pauschal beide bool Variablen active und resume zurück auf “false” gesetzt.
Wird danach die Uhr wieder gestartet wird der Vergleich in Zeile 85 gültig. Also Neustart mit Null.

Wird nicht resetet und die Uhr einfach wieder gestartet wird der Vergleich in Zeile 93 gültig.
Hier berechnen wir einmal die alte gemessene Differenzzeit und ziehen diese vom aktuellen millis Wert ab.
Wir legen damit unsere Startzeit um die Differenz nach hinten.
Als wenn die Uhr nicht jetzt sondern um die Differenz früher gestartet wurden wäre.
Damit das mit jeden Durchlauf nicht erneut berechnet wird, setzen wir resume zurück.
Die Uhr läuft ab jetzt normal vor sich sich. Nur active ist true.

Als Spielerei habe ich noch blinkende Doppelpunkte eingebaut, wenn die Uhr im Resuming Modus wartet.
Damit die optische Darstellung passt, ist noch eine Schönheitskorrektur notwendig, nicht das zufällig die Doppelpunkte weg sind wenn die resetet wird. Würde blöd aussehen. Da mein Programmierstil wert legt das nur das im Ablauf gemacht wird was auch notwendig ist, kam noch eine Variable ‘blink’ hinzu. Membervariable von Stoppuhr. Ansonsten würde das ‘:’ Zeichen ständig auf das Display zusätzlich übertragen, was ich nicht möchte. Es soll bei einer einmaligen Korrektur bleiben - wenn es denn notwendig ist.
Ich muss jetzt zugeben, dass der Code langsam unansehnlich wird. Also viel mehr würde ich in der Form nicht mehr reinpacken wollen.
Viel wichtiger ist aber das du mit dem Code überhaupt klarkommst. Das musst du mir sagen.

Musst nur die Lcd Lib anpassen.

/*
  Doc_Arduino - german Arduino Forum
  IDE 1.8.13
  Arduino Mega2560
  01.05.2021
  https://forum.arduino.cc/t/bekomme-stopuhr-nicht-zum-laufen/852979/20
*/

#include <SPI.h>
#include <DogLcdSPI.h>  

DogLcdSPI lcd (5, 6);  // DOGM163, RS, CS

struct StoppUhr // alles was für eine Stoppuhr benötigt wird
{
  const byte pinStart;
  const byte pinStop;
  const byte pinReset;
  unsigned long timeStart;
  unsigned long timeStop;
  unsigned long time;
  bool active;
  bool resume;
  bool blink;
  byte oldSecond;

  StoppUhr (byte p1, byte p2, byte p3) :
    // Initialisierungsliste;
    pinStart {p1},
    pinStop {p2},
    pinReset {p3},
    timeStart {0},
    timeStop {0},
    time {0},
    active {false},
    resume {false},
    blink{false},     // für ':' Schönheitskorrektur auf dem Display
    oldSecond {255}   // überhöhter Startwert für 0 Ausgabe auf Lcd
  {}
};

StoppUhr arrayUhr[]
{ // Pins der Taster
  {2, 3, 4},  // rot, blau, stopAll
  {3, 2, 4},  // blau, rot, stopAll
};

const byte ANZAHLUHREN = sizeof(arrayUhr) / sizeof(arrayUhr[0]);

void setup()
{
  for (auto &i : arrayUhr)
  {
    pinMode(i.pinStart, INPUT_PULLUP);
    pinMode(i.pinStop, INPUT_PULLUP);
    pinMode(i.pinReset, INPUT_PULLUP);
  }

  SPI.begin();
  lcd.begin(DOG_LCD_M163, 38);    // Kontrast 36
  lcd.clear();

  lcd.setCursor(0, 0); lcd.print("ROT :");
  lcd.setCursor(0, 1); lcd.print("BLAU:");
}

void loop()
{
  eingabe();
  ausgabe();
}

void eingabe (void)
{
  unsigned long loopMillis = millis();

  for (auto &i : arrayUhr)
  {
    // Uhr wird neu gestartet
    if ( !digitalRead(i.pinStart) && !i.active && !i.resume )
    {
      i.timeStart  = loopMillis;
      i.active = true;
    }

    // wenn Resuming aktiv läuft Uhr mit alter Zeit weiter
    // wird einmalig abgearbeitet
    if ( !digitalRead(i.pinStart) && !i.active && i.resume )
    {
      // alte Zeitdifferenz ermitteln und von aktuellen millis abziehen
      i.timeStart  = loopMillis - (i.timeStop - i.timeStart);
      i.active = true;
      i.resume = false;
    }

    // Uhr wird gestoppt unter der Bedingung das sie überhaupt läuft,
    // und fürs spätere weiterlaufen vorbereitet
    if ( i.active && (!digitalRead(i.pinStop)) )
    {
      i.active = false;
      i.resume = true;
      i.timeStop = loopMillis;
    }

    // Uhr wird gestoppt unter der Bedingung das sie überhaupt läuft,
    // ansonsten gibts unschöne Effekte
    // und für Neustart Nullung vorbereitet
    if ( !digitalRead(i.pinReset) )
    {
      i.active = false;
      i.resume = false;
    }

    // laufende Zeit ermitteln
    if ( i.active )
    {
      i.time = loopMillis - i.timeStart;
    }
  }
}


void ausgabe (void)
{
  byte hour {0};
  byte minute {0};
  byte second {0};
  char buf[10] {'\0'};

  // Index vom Uhren Array dient mit zur Zeilenbestimmung auf dem Display.
  for (byte i = 0; i < ANZAHLUHREN; i++)
  {
    unsigned long temp = arrayUhr[i].time / 1000;
    second = temp % 60;
    temp   = temp / 60;
    minute = temp % 60;
    hour   = temp / 60;
    //snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, minute, second);
    
    // display refreshrate every seconds
    if ( arrayUhr[i].oldSecond != second )
    {
      snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hour, minute, second);
      lcd.setCursor(6, i);
      lcd.print(buf);
      arrayUhr[i].oldSecond = second;
    }
    
    // Resume Warteanzeige - optische Spielerei - blinkende Doppelpunkte
    static unsigned long lastMillis {0};
    //static bool blink {false};
    
    if ( arrayUhr[i].resume )
    {
      if (millis() - lastMillis >= 1000)
      {
        arrayUhr[i].blink = !arrayUhr[i].blink;
        if (arrayUhr[i].blink)
        {
          lcd.setCursor(8, i);  lcd.print(' ');
          lcd.setCursor(11,i);  lcd.print(' ');
        }
        else
        {
          lcd.setCursor(8, i);  lcd.print(':');
          lcd.setCursor(11,i);  lcd.print(':');
        }
        lastMillis = millis();
      }
    }

    // Resume Warteanzeige - Schönheitskorrektur
    // falls Uhr gestoppt bzw. resetet wird und ':' zufällig ausgeblendet war,
    // dann wird das Zeichen einmal neu dargestellt.
    if ( !arrayUhr[i].resume && arrayUhr[i].blink)
    {
      lcd.setCursor(8, i);  lcd.print(':');
      lcd.setCursor(11,i);  lcd.print(':');
      arrayUhr[i].blink = false;
    }
  } // Ende for
}
1 Like