Countdown funktioniert nur am Anfang

Hallo Leute

Ich habe mir einen Countdown mit Starttaster, zwei Status-LEDs (grün => Countdown läuft; rot => Countdown läuft nicht) und einer LCD-Anzeige (Restzeit und Fortschrittsbalken) gebaut. Während der Countdown läuft ist der Taster blockiert.

Direkt nach dem Hochladen des Sketches funktioniert der Code noch wunderbar. Ein paar Minuten später startet der Countdown nur noch nach mehrmaligem Betätigen des Tasters. Außerdem stoppt der Countdown nicht mehr bei Null, sondern läuft ins Minus und der Fortschrittsbalken wird unberechenbar.

#include <LiquidCrystal.h>
#include <LcdBarGraph.h>

#define BUTTON 9
#define GREEN_LED 10
#define RED_LED 11

bool button_pressed = false;
int start_time;
int end_time;
int run_time;
int countdown = 10000;

byte LCD_columns_per_line = 16; // Columns on LCD

LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8); // creating LCD instance
LcdBarGraph lbg(&lcd, LCD_columns_per_line, 0, 1);  // (&lcd, LCD_columns_per_line, start X, start Y).


void setup() {
  pinMode(BUTTON, INPUT_PULLUP);  
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  digitalWrite(GREEN_LED, LOW);
  digitalWrite(RED_LED, HIGH);
  
  // initializing the LCD
  lcd.begin(2, LCD_columns_per_line);
  lcd.clear();
  
   delay(100);
}


void loop(){ 

  if (digitalRead(BUTTON) && (!button_pressed)){
    button_pressed = true;
    start_time = millis();
    end_time = start_time + countdown;
    digitalWrite(GREEN_LED, HIGH);
    digitalWrite(RED_LED, LOW);
    lcd.clear();
  }
  
  if (millis() >= end_time){    
    digitalWrite(GREEN_LED, LOW);
    digitalWrite(RED_LED, HIGH);
    button_pressed = false;
    
    lcd.setCursor(0,0);
    lcd.print(float(countdown / 1000));
    lcd.print(" s");
    lcd.setCursor(0,1);
    lcd.print("Bereit");
    lbg.drawValue(0, countdown);
  }
  
  if (button_pressed == true){
    run_time = millis() - start_time;
    lbg.drawValue(run_time, countdown);
    lcd.setCursor(0,0);
    lcd.print((float(countdown - run_time) / 1000));
    lcd.print(" s");
  }
}

Woran liegt es, dass mein Code nur am Anfang funktioniert?

a causa di questo:

int start_time;
int end_time;
int run_time;

Che numeri puoi mettere in una int?

Ciao Uwe

Ich sehe 2 Fehler!

Erstens:

start_time = millis();
end_time = start_time + countdown;

Sowas solltest du dir direkt wieder abgewöhnen.

So handelst du dir einen zweiten Überlauf nach ca 49 Tagen ein.
Den kannst du nicht kompensieren.

Zweitens:
Den anderen fatalen Fehler hat Uwe dir ja schon erklärt!

Die Datentypen habe ich jetzt erweitert:

unsigned long start_time;
unsigned long end_time;
unsigned long run_time;

Damit funktioniert es jetzt auch noch nach mehreren Minuten.

Die folgende Anweisung kann ich auch umstellen.

start_time = millis();
end_time = start_time + countdown;

Aber für die if-Anweisung brauche ich ja Vergleichswerte wie die Start- und Endzeit.

Aber für die if-Anweisung brauche ich ja Vergleichswerte wie die Start- und Endzeit.

Nein, die Endzeit brauchst du nicht.
Die ergibt sich automatisch, wenn sie erreicht wird, und dann ist früh genug.
Denn, die Endzeit würde eine Addition erfordern, und damit nagelst du dir eine Frikadelle ans Knie.

Ich habe jetzt den Code nach dem Beispiel Blink Without Delay umgebaut:

#include <LiquidCrystal.h>
#include <LcdBarGraph.h>

#define BUTTON 9
#define GREEN_LED 10
#define RED_LED 11

bool button_pressed = false;

int countdown = 10000;
unsigned long previous_millis = 0;

byte LCD_columns_per_line = 16; // Columns on LCD

LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8); // creating LCD instance
LcdBarGraph lbg(&lcd, LCD_columns_per_line, 0, 1);  // (&lcd, LCD_columns_per_line, start X, start Y).


void setup() {
  pinMode(BUTTON, INPUT_PULLUP);  
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  digitalWrite(GREEN_LED, LOW);
  digitalWrite(RED_LED, HIGH);
  
  // initializing the LCD
  lcd.begin(2, LCD_columns_per_line);
  lcd.clear();
  
   delay(100);
}


void loop(){
  unsigned long current_millis = millis();
  
  if (digitalRead(BUTTON) && (!button_pressed)){
    button_pressed = true;
    digitalWrite(GREEN_LED, HIGH);
    digitalWrite(RED_LED, LOW);
    // lcd.clear();
  }

  if (current_millis - previous_millis >= countdown){
    previous_millis = current_millis;
    digitalWrite(GREEN_LED, LOW);
    digitalWrite(RED_LED, HIGH);
    button_pressed = false;

    lcd.setCursor(0,0);
    lcd.print(float(countdown / 1000));
    lcd.print(" s");
    lcd.setCursor(0,1);
    lbg.drawValue(0, countdown);
    lcd.print("Bereit");
    
  }

  if (button_pressed == true){
    lbg.drawValue(current_millis - previous_millis, countdown);
    lcd.setCursor(0,0);
    lcd.print((float(countdown - (current_millis - previous_millis)) / 1000));
    lcd.print(" s");
  }
}

Der Countdown hat jetzt die unschöne Angewohnheit, dass der Startzeitpunkt verschieden ist. Je nach Wartezeit zum vorherigen Durchgang startet der Countdown nun bei weniger als 10 Sekunden.

Hi

Wie meinen?
Deine Fehlerbeschreibung sagt mir nicht viel.
Auch sehe ich in Deinen Sketch keine State-Maschine - Du benutzt zwar millis(), Das war's aber auch schon.

Z.B. wird, wenn 'button_pressed' true ist, tausend Mal die Sekunde das LCD aktualisiert - soll Das so?
Viel mehr als 10 Werte die Sekunde kannst Du eh nicht sehen - das Geflimmer der letzten Ziffern kann man dabei genauso gut simulieren und man spart sich 90% Zeit.
DAS wäre z.B. was für 'Blink_without'delay' - nur alle 97ms das Display zu aktualisieren.

Dein Haupt-Problem ist gerade, daß Du 'previous_millis' nicht beim Tastendruck auf millis() setzt - sofern der Countdown immer 'countdown' Millisekunden dauern soll, wäre Das schon Mal ein Schritt in die richtige Richtung - die Start-Bedingung beim Start richtig setzen.

Auch wird Dir der Countdown direkt durchstarten, wenn der Button betätigt bleibt - also einen Countdown von nur wenigen Millisekunden wirst Du so nicht hinbekommen - zumindest nicht als Einzeldurchlauf.

MfG

Ich meinte, dass der Countdown z.B. bei 8 und nicht bei 10 Sekunden gestartet ist, wenn man 2 Sekunden nach dem Ende des vorherigen Countdowns auf den Taster gedrückt hat. Dieses Problem besteht mittlerweile nicht mehr.

Ich habe den Code jetzt entsprechend überarbeitet:

#include <LiquidCrystal.h>
#include <LcdBarGraph.h>

#define BUTTON 9
#define GREEN_LED 10
#define RED_LED 11

bool button_pressed = false;

//unsigned long start_time;
//unsigned long end_time;
//unsigned long run_time;

int countdown = 10000;

unsigned long previous_millis = 0;

byte LCD_columns_per_line = 16; // Columns on LCD

LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8); // creating LCD instance
LcdBarGraph lbg(&lcd, LCD_columns_per_line, 0, 1);  // (&lcd, LCD_columns_per_line, start X, start Y).


void setup() {
  pinMode(BUTTON, INPUT_PULLUP);  
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);
  digitalWrite(GREEN_LED, LOW);
  digitalWrite(RED_LED, HIGH);
  
  // initializing the LCD
  lcd.begin(2, LCD_columns_per_line);
  lcd.clear();
  
   delay(100);
}


void loop(){
  unsigned long current_millis = millis();
  
  if (digitalRead(BUTTON) && (!button_pressed)){
    previous_millis = current_millis;
    button_pressed = true;
    digitalWrite(GREEN_LED, HIGH);
    digitalWrite(RED_LED, LOW);
    lcd.clear();
    
  }

  if (current_millis - previous_millis >= countdown){
    
    digitalWrite(GREEN_LED, LOW);
    digitalWrite(RED_LED, HIGH);
    button_pressed = false;
    
    lcd.setCursor(0,0);
    lcd.print(float(countdown / 1000));
    lcd.print(" s");
    lcd.setCursor(0,1);
    lbg.drawValue(0, countdown);
    lcd.print("Bereit");
    
    delay(500); // Delay gegen doppelten Tastendruck kurz hintereinander.
  }

  if (button_pressed == true){
    lbg.drawValue(current_millis - previous_millis, countdown);
    lcd.setCursor(0,0);
    lcd.print((float(countdown - (current_millis - previous_millis)) / 1000));
    lcd.print(" s");
    
    delay(100); // Delay für 10 Aktualisierungen des LCD pro Sekunde.
  }
}

Was ist der Vorteil einer State-Maschine?

Schlammcatcher:
Was ist der Vorteil einer State-Maschine?

Ein Vorteil ist, dass das ein Strickmuster ist, das dabei helfen kann, mehrere Sachen gleichzeitig zu erledigen. Für einen kurzen Blick genügt vielleicht mein Geschreibsel dazu.

Gruß

Gregor

Für meine 2 Zustände (Countdown läuft, Countdown läuft nicht) kommt mir das Strickmuster State-Maschine ziemlich überdimensioniert und unnötig kompliziert vor.

Bietet eine State-Maschine in meinem Fall tatsächlich einen Vorteil?

Schlammcatcher:
Bietet eine State-Maschine in meinem Fall tatsächlich einen Vorteil?

Dazu müsste ich mich mal in das reinlesen, was „Dein Fall“ denn überhaupt ist. Ich bin neu in diesem Thread.

Gruß

Gregor

PS: Auf die Schnelle: Du hast ja mit deutlich mehr als nur zwei Dingen zu tun. Teil der „State-Machine-Strategie“ ist, jeden Teil als Ding zu betrachten, das Zustände annehmen kann. Also z. B. Taster, LEDs, LC-Display (das D in LCD steht schon für Display - LCD-Display ist bissl viel) usw. Jedes dieser Dinger kannst Du als Klasse formulieren, d. h. eben als Ding, das Zustände annehmen und auf „Anforderung“ Informationen herausrücken kann. Was das Wesen objektorientierter Denke angeht, kann man sich ordentliche Knoten ins Hirn denken. Bist Du denn irgendwie Programmier-Erfahren?

Schlammcatcher:
Für meine 2 Zustände (Countdown läuft, Countdown läuft nicht) kommt mir das Strickmuster State-Maschine ziemlich überdimensioniert und unnötig kompliziert vor.

Bietet eine State-Maschine in meinem Fall tatsächlich einen Vorteil?

Eine gut programmierte State-Maschine läuft in deinem Fall geschmeidiger. Die Delay(100) sorgen dafür, dass dein Countdown eine 1/10tel Sekunde zu lang zählen kann. Auch solltest du die doppelten Tastendrücke anders als mit einer Delay(500) verhindern. Hier bietet sich eine Flankenerkennung an. Wenn die Taste im letzten Durchlauf gedrückt war und jetzt nicht mehr, wurde die Taste losgelassen und der Contdown kann starten. Zum Entprellen finde ich ein Delay(20) bei nicht zeitkritischen Anwendungen OK, aber nicht Delay(500). Eine State-Maschine kann man leichter erweitern. Z.B. mit einem kurzen Tastendruck wird der Countdown angehalten und mit langem abgebrochen.

Nicht zu Letzt kann man bei so einem kleinen Beispiel wunderbar das Programmieren einer State-Maschine üben. Man muss das Prinzip verinnerlichen. In umfangreichen Projekten kommt man ohne sie schnell in Sackgassen, wo man den ganzen Sketch nochmal komplett neu schreiben muss.

Ich habe jetzt den Versuch unternommen eine State Machine zu bauen.

#include <LiquidCrystal.h>
#include <LcdBarGraph.h>

const int BUTTON_PIN = 9;
const int GREEN_LED_PIN = 10;
const int RED_LED_PIN = 11;

const int COUNTDOWN = 3000;

unsigned long previous_millis = 0;

bool button_pressed = false;

enum conditions {countdown_off, countdown_on};
byte condition = countdown_off;

const byte LCD_COLUMNS = 16; // Columns on LCD

LiquidCrystal lcd(2, 3, 4, 5, 6, 7, 8); // creating LCD instance
LcdBarGraph lbg(&lcd, LCD_COLUMNS, 0, 1);  // (&lcd, LCD_columns_per_line, start X, start Y).


void setup()
{
  // put your setup code here, to run once:
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  pinMode(GREEN_LED_PIN, OUTPUT);
  pinMode(RED_LED_PIN, OUTPUT);

  //digitalWrite(GREEN_LED_PIN, LOW);
  //digitalWrite(RED_LED_PIN, HIGH);

  // initializing the LCD
  lcd.begin(2, LCD_COLUMNS);
  lcd.clear();
  
  delay(100);
}

void loop()
{
  // put your main code here, to run repeatedly:  
  unsigned long current_millis = millis();
  
  switch(condition)
  {
    case countdown_off:
       if (current_millis - previous_millis >= COUNTDOWN)
       {
         digitalWrite(GREEN_LED_PIN, LOW);
         digitalWrite(RED_LED_PIN, HIGH);
         button_pressed = false;

         lcd.setCursor(0,0);
         lcd.print(float(COUNTDOWN / 1000));
         lcd.print(" s");
         lcd.setCursor(0,1);
         lbg.drawValue(0, COUNTDOWN);
         lcd.print("Bereit");
       }
       else
       {
         condition = countdown_on;
       }
       break;
       
    case countdown_on:
      if (digitalRead(BUTTON_PIN) && (!button_pressed))
      {
        previous_millis = current_millis;
        button_pressed = true;
        digitalWrite(GREEN_LED_PIN, HIGH);
        digitalWrite(RED_LED_PIN, LOW);
        lcd.clear();       
      }
      else
      {
        condition = countdown_off;
      }
      break;
  }
}

Es läuft eigentlich nur der Compiler fehlerfrei durch. Mit diesem Code reagiert der Arduino nicht mal mehr auf Tastendrücke.