Arduino Stoppuhr

Servus,
Ich möchte mit meinen Arduino uno eine Einfache Stoppuhr bauen welche die Zeit dann auf einen Display in min : sekunden : millisekunden darstellt allerdings weiß ich nicht recht wie ich das Programmieren soll hab schon bisschen rumprobiert mit dem befehl (millis) aber komme leider nicht weiter. Hat jemand eine Idee für mich wie ich das Ganze einfach umsetzen kann ohne runden Zähler oder viel Schnick Schnack nur zwei Taster als Start und Stop

Im englischen Teil des Forum müssen die Beiträge und Diskussionen in englischer Sprache verfasst werden.
Deswegen wurde diese Diskussion in den deutschen Teil des Forums verschoben

Wie man dieses Forum benutzt - bitte lesen


Hast du überhaupt versucht, nach „arduino stopwatch ms“ zu googlen?

erste Antwort: Stopwatch Using Arduino - Hackster.io

Ausgabe auf welchem Display?!?
Nur zwei Taster Start und Stop ... wodurch wirst du die Zeit zurücksetzen auf 0?
Was hast du bisher (poste deinen Code hier in Code Tags) und woran scheitert es aktuell?

Hallo,

nur eine Frage an den TO zur Selbstbeantwortung. Wie bildest du im echten Leben mit Armbanduhr o.ä. alltägliche Differenzzeiten? Wenn du das beantworten kannst, kannst du millis() anwenden.

Hi,
das ist nicht schwer, ich kann dir helfen. Du brauchst folgende Hardware:

  • Arduino Nano
  • LCD-Display (z. B. 16x2 oder 20x4)
  • Taster (mindestens 3 Stück)
  • Widerstände (für die Taster)
  • Jumperkabel

Ich würde es an Deiner Stelle so programmieren, dass Taster 1 zum Starten ist, Taster 2 zum Stoppen und Taster 3 zur Nullung/Counter zurücksetzen. Allerdings kann man es auch über einen Taster lösen, dass eben die Tastenanschläge gemerkt werden. Wie du magst.

Damit kann man Zwischenzeiten zum Ansehen einfrieren und nach erneutem Start weiterlaufen lassen. Ob die Zeit während des Stopps weiterlaufen soll oder nicht, musst du noch entscheiden.

Einfacher ist es natürlich, wenn bei Taster 1 mit 00:00:00 gestartet wird und bei Taster 2 gestoppt wird.

Richtig, aber erstaunlich aufwendig, denn mechanische Taster prellen, und selbst wenn man den Taster 1 (Start) loslässt, kann nochmal ein kurzer Wechsel losgelassen-gedrückt-losgelassen auftreten, der den Start dann zu spät erfasst.

Versuch zum Einstieg mal das. Ich weiss deine genauen Wünsche der Stoppuhr nicht. Vielleicht soll ja noch bei einer bestimmten Zeit ein Befehl mit einem HC12 Modul auf 433 Mhz versendet werden, das in 500m Entfernung eine Blitzleuchte und eine Sirene auslösen soll...

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C-Adresse und Displaygröße anpassen

const int startButtonPin = 2;  // Taster 1 (Start)
const int stopButtonPin = 3;   // Taster 2 (Stop)
const int resetButtonPin = 4;  // Taster 3 (Reset)

volatile boolean isRunning = false;
volatile unsigned long startTime = 0;
volatile unsigned long elapsedTime = 0;

void setup() {
  lcd.begin(16, 2);
  lcd.print("Stoppuhr");

  pinMode(startButtonPin, INPUT_PULLUP);
  pinMode(stopButtonPin, INPUT_PULLUP);
  pinMode(resetButtonPin, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(startButtonPin), startButtonPressed, FALLING);
  attachInterrupt(digitalPinToInterrupt(stopButtonPin), stopButtonPressed, FALLING);
  attachInterrupt(digitalPinToInterrupt(resetButtonPin), resetButtonPressed, FALLING);
}

void loop() {
  if (isRunning) {
    elapsedTime = millis() - startTime;
  }

  displayElapsedTime();
  delay(100);
}

void displayElapsedTime() {
  unsigned long milliseconds = elapsedTime % 1000;
  unsigned long seconds = (elapsedTime / 1000) % 60;
  unsigned long minutes = (elapsedTime / 60000) % 60;

  lcd.setCursor(0, 1);
  lcd.print("Time: ");
  lcd.print(minutes);
  lcd.print(":");
  if (seconds < 10) {
    lcd.print("0");
  }
  lcd.print(seconds);
  lcd.print(":");
  if (milliseconds < 100) {
    lcd.print("0");
  }
  if (milliseconds < 10) {
    lcd.print("0");
  }
  lcd.print(milliseconds);
}

void startButtonPressed() {
  if (!isRunning) {
    startTime = millis();
    isRunning = true;
  }
}

void stopButtonPressed() {
  if (isRunning) {
    elapsedTime = millis() - startTime;
    isRunning = false;
  }
}

void resetButtonPressed() {
  isRunning = false;
  elapsedTime = 0;
}

Wenn es um so kurze Zeiten geht, dann sollte man sich von mechanischen Tasten lösen und z.B. Konstruktionen aus Hall-IC und Magnet benutzen. Die prellen nicht.
Hier kommt es aber wieder auf den unbekannten Einsatz der Stoppuhr an.
Wenn die zu stoppende Zeit immer über der Prellzeit liegt, könnte man auch auf die 1. Flanke triggern und für ca. 10 ms die Eingabe sperren.

Gruß Tommy

1 Like

Ob man den ersten oder letzten Flankenwechsel des Prellens nimmt ( 2..3 ms ) macht keinen wesentlichen Unterschied bei einer handbetätigten Stoppuhr. Wenn eine halbe Sekunde nach dem Drücken, beim Loslassen des Start-Tasters, der Start nochmal erfolgt, das merkt man dann aber schon.

Ja, selbstverständlich! Hier ist der aktualisierte Code, der eine Prellzeit für die Tastereingaben berücksichtigt und auf die 1. Flanke triggert:

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C-Adresse und Displaygröße anpassen

const int startButtonPin = 2;  // Taster 1 (Start)
const int stopButtonPin = 3;   // Taster 2 (Stop)
const int resetButtonPin = 4;  // Taster 3 (Reset)

volatile boolean isRunning = false;
volatile unsigned long startTime = 0;
volatile unsigned long elapsedTime = 0;

volatile boolean startButtonPressedFlag = false;
volatile boolean stopButtonPressedFlag = false;
volatile boolean resetButtonPressedFlag = false;

void setup() {
  lcd.begin(16, 2);
  lcd.print("Stoppuhr");

  pinMode(startButtonPin, INPUT_PULLUP);
  pinMode(stopButtonPin, INPUT_PULLUP);
  pinMode(resetButtonPin, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(startButtonPin), startButtonInterrupt, FALLING);
  attachInterrupt(digitalPinToInterrupt(stopButtonPin), stopButtonInterrupt, FALLING);
  attachInterrupt(digitalPinToInterrupt(resetButtonPin), resetButtonInterrupt, FALLING);
}

void loop() {
  if (isRunning) {
    elapsedTime = millis() - startTime;
  }

  if (startButtonPressedFlag) {
    startButtonPressed();
    startButtonPressedFlag = false;
  }

  if (stopButtonPressedFlag) {
    stopButtonPressed();
    stopButtonPressedFlag = false;
  }

  if (resetButtonPressedFlag) {
    resetButtonPressed();
    resetButtonPressedFlag = false;
  }

  displayElapsedTime();
  delay(100);
}

void displayElapsedTime() {
  unsigned long milliseconds = elapsedTime % 1000;
  unsigned long seconds = (elapsedTime / 1000) % 60;
  unsigned long minutes = (elapsedTime / 60000) % 60;

  lcd.setCursor(0, 1);
  lcd.print("Time: ");
  lcd.print(minutes);
  lcd.print(":");
  if (seconds < 10) {
    lcd.print("0");
  }
  lcd.print(seconds);
  lcd.print(":");
  if (milliseconds < 100) {
    lcd.print("0");
  }
  if (milliseconds < 10) {
    lcd.print("0");
  }
  lcd.print(milliseconds);
}

void startButtonInterrupt() {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 10) {
    startButtonPressedFlag = true;
    lastInterruptTime = interruptTime;
  }
}

void stopButtonInterrupt() {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 10) {
    stopButtonPressedFlag = true;
    lastInterruptTime = interruptTime;
  }
}

void resetButtonInterrupt() {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  if (interruptTime - lastInterruptTime > 10) {
    resetButtonPressedFlag = true;
    lastInterruptTime = interruptTime;
  }
}

void startButtonPressed() {
  if (!isRunning) {
    startTime = millis();
    isRunning = true;
  }
}

void stopButtonPressed() {
  if (isRunning) {
    elapsedTime = millis() - startTime;
    isRunning = false;
  }
}

void resetButtonPressed() {
  isRunning = false;
  elapsedTime = 0;
}

Und hier mit Kommentierung:

#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 16, 2); // I2C-Adresse und Displaygröße anpassen

const int startButtonPin = 2;  // Taster 1 (Start)
const int stopButtonPin = 3;   // Taster 2 (Stop)
const int resetButtonPin = 4;  // Taster 3 (Reset)

volatile boolean isRunning = false;
volatile unsigned long startTime = 0;
volatile unsigned long elapsedTime = 0;

volatile boolean startButtonPressedFlag = false;
volatile boolean stopButtonPressedFlag = false;
volatile boolean resetButtonPressedFlag = false;

void setup() {
  lcd.begin(16, 2);
  lcd.print("Stoppuhr");

  pinMode(startButtonPin, INPUT_PULLUP);
  pinMode(stopButtonPin, INPUT_PULLUP);
  pinMode(resetButtonPin, INPUT_PULLUP);

  // Interrupts für Tasterereignisse konfigurieren
  attachInterrupt(digitalPinToInterrupt(startButtonPin), startButtonInterrupt, FALLING);
  attachInterrupt(digitalPinToInterrupt(stopButtonPin), stopButtonInterrupt, FALLING);
  attachInterrupt(digitalPinToInterrupt(resetButtonPin), resetButtonInterrupt, FALLING);
}

void loop() {
  if (isRunning) {
    elapsedTime = millis() - startTime;
  }

  // Tastereignisse verarbeiten, falls Flag gesetzt
  if (startButtonPressedFlag) {
    startButtonPressed();
    startButtonPressedFlag = false;
  }

  if (stopButtonPressedFlag) {
    stopButtonPressed();
    stopButtonPressedFlag = false;
  }

  if (resetButtonPressedFlag) {
    resetButtonPressed();
    resetButtonPressedFlag = false;
  }

  displayElapsedTime();
  delay(100);
}

void displayElapsedTime() {
  unsigned long milliseconds = elapsedTime % 1000;
  unsigned long seconds = (elapsedTime / 1000) % 60;
  unsigned long minutes = (elapsedTime / 60000) % 60;

  lcd.setCursor(0, 1);
  lcd.print("Time: ");
  lcd.print(minutes);
  lcd.print(":");
  if (seconds < 10) {
    lcd.print("0");
  }
  lcd.print(seconds);
  lcd.print(":");
  if (milliseconds < 100) {
    lcd.print("0");
  }
  if (milliseconds < 10) {
    lcd.print("0");
  }
  lcd.print(milliseconds);
}

// Interrupt-Handler für Starttaste
void startButtonInterrupt() {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  // Prellzeit von 10 ms überprüfen
  if (interruptTime - lastInterruptTime > 10) {
    startButtonPressedFlag = true;
    lastInterruptTime = interruptTime;
  }
}

// Interrupt-Handler für Stoptaste
void stopButtonInterrupt() {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  // Prellzeit von 10 ms überprüfen
  if (interruptTime - lastInterruptTime > 10) {
    stopButtonPressedFlag = true;
    lastInterruptTime = interruptTime;
  }
}

// Interrupt-Handler für Zurücksetzen-Taste
void resetButtonInterrupt() {
  static unsigned long lastInterruptTime = 0;
  unsigned long interruptTime = millis();

  // Prellzeit von 10 ms überprüfen
  if (interruptTime - lastInterruptTime > 10) {
    resetButtonPressedFlag = true;
    lastInterruptTime = interruptTime;
  }
}

void startButtonPressed() {
  if (!isRunning) {
    startTime = millis();
    isRunning = true;
  }
}

void stopButtonPressed() {
  if (isRunning) {
    elapsedTime = millis() - startTime;
    isRunning = false;
  }
}

void resetButtonPressed() {
  isRunning = false;
  elapsedTime = 0;
}

????

Die Funktion attachInterrupt() ermöglicht es, eine Funktion (in diesem Fall resetButtonInterrupt() ) als Interrupt-Handler für einen bestimmten Pin festzulegen. Der erste Parameter digitalPinToInterrupt(resetButtonPin) wandelt den Pin (z.B. resetButtonPin ) in den entsprechenden Interrupt-Index um. Das ist notwendig, da nicht alle Pins des Arduino für Interrupts geeignet sind. Der zweite Parameter resetButtonInterrupt gibt die Funktion an, die als Interrupt-Handler aufgerufen werden soll. Der dritte Parameter FALLING legt fest, dass der Interrupt bei einer fallenden Flanke (vom HIGH- zum LOW-Zustand) ausgelöst werden soll.

Im spezifischen Fall wird der Interrupt für den Pin, der mit resetButtonPin verbunden ist, konfiguriert. Wenn der Taster an diesem Pin gedrückt wird und der Zustand des Pins von HIGH auf LOW wechselt (fallende Flanke), wird die Funktion resetButtonInterrupt() aufgerufen.

Du musst hier nicht die Funktion attachInterrupt() erklären. Die ist bekannt.
Die Frage zielt auf deren Verwendung durch Dich.

Wieso braucht es nach Deiner Meinung für Buttons überhaupt Interrupts?
Allgemein wird das nur als schlechte Programmierung angesehen, weil Buttons (und der Mensch davor) langsam genug sind, um das normal im loop() zu bearbeiten.

Gruß Tommy

2 Likes

Dann kann man es auch normal im loop() bearbeiten. Ist doch alles kein Beinbruch... Grüße

Ach?

... nutzt diese Funktion nicht viel.
Macht nur dem menschlichen Leser klar, dass man dran gedacht hat, dass Interrupt-Nummer und Pin-Nummer was verschiedenes sind.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.