Frage deutsch
~~~~~~~~~~~~~~
Wie programmiere ich eine Stoppuhr?
Und wie kann ich dabei das Rücksetzen des System-TIMERs um Mitternacht (Sekundensprung von 86399 auf 0) umgehen?
 

Question English
~~~~~~~~~~~~~~
How can I implement a stop watch?
How can I handle the System Timer rollover at midnight?
 
 
 

Antwort 1
~~~~~~~~~~~~~~~~~
[ von Thomas Antoni, 6.4.2005 - 27.11.2005 ]
.
Für eine eine einfache Stoppuhr, die nur eine Auflösung von ganzen Sekunden zu haben braucht, kann man den Befehl TIME$ verwenden, der die Uhrzeit im Format hh:mm:ss als String zurückliefert (hh=Stunden, mm=Minuten, ss=Sekunden). Wenn sich der Wert von TIME$ geändert hat, muss eine Sekunde vergangen sein, da TIME$ sich nur jede Sekunde ändert.
.
Das folgende Programm zeigt, wie man mit dem TIME$-Befehl eine Stoppuhr programmiert :
 
'******************************************************************
' STOPUHR1.BAS = Einfache Stoppuhr mit Sekunden- und Minutenanzeige
' ============
' Dieses Q(uick)Basic-Programm benutzt die Systemfunktion TIME$ zur
' Realisierung einer Stoppuhr, die die vergangenen Minuten und
' Sekunden anzeigt
'
' (c) jb und Thomas Antoni, 6.4.2005
'******************************************************************
'
'******** Vorbesetzungen und Anzeige der Bedienhinweise ***********
CLS
PRINT " Stoppuhr"
PRINT " ========"
LOCATE 7
PRINT " [S]=Start/Stopp...[R]=Reset...[Esc]=Beenden"
TimeAlt$ = ""
Min% = 0
Sek% = 0
Uhrlaeuft% = 0 'Uhr ist angehalten
'
'******** Tasten abfragen *****************************************
DO
Taste$ = INKEY$
'
SELECT CASE Taste$
CASE "s", "S" 'Start/Stopp betaetigt
IF Uhrlaeuft% = 0 THEN Uhrlaeuft% = 1 ELSE Uhrlaeuft% = 0
CASE "r", "R" 'Ruecksetzen betaetigt
Min% = 0: Sek% = 0 'Stoppuhr auf 0 setzen ...
Uhrlaeuft% = 0 '... und anhalten
CASE CHR$(27) 'Programm beenden bei Esc-Taste
END
END SELECT
'
'******** Zeit weiterzaehlen wenn eine Sec vergangen **************
IF Uhrlaeuft% = 0 THEN
TimeAlt$ = TIME$
ELSE
IF TimeAlt$ <> TIME$ THEN '1 Sek vergangen?
Sek% = Sek% + 1
IF Sek% > 59 THEN Sek% = 0: Min% = Min% + 1
TimeAlt$ = TIME$
END IF
END IF
'
'******** Zeit anzeigen *******************************************
LOCATE 4, 2: PRINT Min%; ":"; Sek%;
LOCATE , 10: PRINT "Minuten"
LOOP
 
Das obige Programm steht im Verzeichnis Progs\ zur Verfügung sowie online unter www.antonis.de/faq/progs/stopuhr1.bas .
 
Scheinbar noch einfacher als mit TIME$ kannst Du eine Stoppuhr mit dem TIMER-Befehl ermitteln, der die seit Mitternacht verstrichenen Sekunden mit einer Auflösung von 56 ms ( = 0,056 sec) als SINGLE-Gleitpunktwert zurückliefert.
 
Mit TIMER kannst Du also im Gegensatz zu TIME$ auch Nachkommastellen der Sekunden darstellen. Eine größere Anzeige-Auflösung als 0,1 sec ist aber wenig sinnvoll, weil die Hunderter-Stelle ja jeweils um den Wert 5 oder 6 springt.
 
Zur Realisierung einer Stoppuhr musst Du Dir einfach beim Starten den momentanen TIMER-Wert als Startwert abspeichern und dann in einer Dauerschleife jeweils die Differenz TIMER - Startwert anzeigen.
 
Das entsprechende Programm würde dann etwa so aussehen:
 
'******************************************************************
' STOPUHR2.BAS = Einfache Stoppuhr mit min, sec u. 1/10 sec Anzeige
' ============
' Dieses Q(uick)Basic-Programm benutzt den System-TIMEr zur
' Realisierung einer Stoppuhr, die die vergangenen Minuten und
' Sekunden sowie die 1/10 Sekunden anzeigt.
' Das Nullsetzen des TIMERS um Mitternacht wird nicht
' beruecksichtigt. Daher zeigt das Programm falsche Zeiten an, wenn
' man es um Mitternacht herum benutzt.
'
' (c) Thomas Antoni, 6.4.2005
'******************************************************************
'
'******** Vorbesetzungen und Anzeige der Bedienhinweise ***********
CLS
PRINT " Stoppuhr"
PRINT " ========"
LOCATE 7
PRINT " [S]=Start/Stopp...[R]=Reset...[Esc]=Beenden"
Sekunden! = 0
StartZeit! = TIMER
Min% = 0
Sek% = 0
Uhrlaeuft% = 0 'Uhr ist angehalten
'
'******** Tasten abfragen *****************************************
DO
Taste$ = INKEY$
'
SELECT CASE Taste$
CASE "s", "S" 'Start/Stopp betaetigt
StartZeit! = TIMER - Sekunden!
IF Uhrlaeuft% = 0 THEN Uhrlaeuft% = 1 ELSE Uhrlaeuft% = 0
CASE "r", "R" 'Ruecksetzen betaetigt
Sekunden! = 0 'Stoppuhr auf 0 setzen ...
Uhrlaeuft% = 0 '... und anhalten
CASE CHR$(27) 'Programm beenden bei Esc-Taste
END
END SELECT
'
'******** Zeit ermitteln und anzeigen *****************************
IF Uhrlaeuft% = 1 THEN
Sekunden! = TIMER - StartZeit!
END IF
LOCATE 4, 2
PRINT USING "#####.#"; Sekunden!; 'Sec mit 1 Nachkommastelle anz.
PRINT " sec"
LOOP
 
Das obige Programm steht im Verzeichnis Progs\ zur Verfügung sowie online unter www.antonis.de/faq/progs/stopuhr2.bas .
 
Der Timer liefert Gleitpunktwerte vom Typ SINGLE zwischen 0.000 und 86400.000 sec (24 x 3600 = 86400; entspricht den in 24 Stunden von 00:00:00h ... 23:59:59h verstrichenen Sekunden). Bei der Realisierung von Wartezeiten, Stoppuhren und Countdown-Timern ist der Rücksprung des TIMERs vom Maximalwert 86400.000 auf 0.000 um Mitternacht zu berücksichtigen (genauer gesagt, von 86399.444 auf Null, denn statt der 86400.000 wird um Mitternacht ja die Null zurückgeliefert). Wenn man das nicht tut, wird eine um Mitternacht herum gestoppte Zeit völlig falsch mit großen negativen Zeitwerten angezeigt.
 
Das obige Programm STOPUHR.BAS hat also 2 Unschönheiten:
1. Des Rücksetzen des System-TIMERs um Mitternacht wird nicht beherrscht, und das Programm zeigt falsche Wertte an, wenn man es um Mitternacht herum benutzt

2. Es werden nur Sekunden, aber keine Minuten und Stunden angezeigt (23:59:59.9 Stunden werden z.B. als 86399.9 Sekunden angezeigt).
 
Diese Mängel sind gar nicht so leicht zu beseitigen. Wie man es trotzdem schafft, zeigt mein folgendes Programm STOPUHR3.BAS. Die Beseitigung der beiden Problempunkte hat mich einige Schweißtropfen gekostet. Dafür ist das aber dann auch schon fast die ideale Stoppuhr.
 
'******************************************************************
' STOPUHR3.BAS = Komfort-Stoppuhr mit min, sec u. 1/10 sec Anzeige
' ============
' Dieses Q(uick)Basic-Programm nutzt den System-TIMER zur
' Realisierung einer Stoppuhr, die die vergangenen Minuten und
' Sekunden sowie die 1/10 Sekunden anzeigt.
' Das Nullsetzen des TIMERS um Mitternacht wird beruecksichtigt,
' so dass das Programm auch um Mitternacht herum richtig
' funktioniert. Dies habe ich getestet, indem ich die PC-Zeit auf
' einen Wert kurz vor Mitternacht gestellt und dann die Stoppuhr
' gestartet habe. Die maximal messbare Zeit betraegt 24h.
'
' (c) Thomas Antoni, 6.4.2005
'******************************************************************
'
'******** Vorbesetzungen und Anzeige der Bedienhinweise ***********
CLS
PRINT " STOPPUHR - (c) Thomas Antoni"
PRINT "==============================="
LOCATE 7
PRINT " [S] = Start/Stopp"
PRINT " [R] = Reset"
PRINT " [Esc]= Beenden"
Sekunden! = 0
StartZeit! = TIMER
Uhrlaeuft% = 0 'Uhr ist angehalten
COLOR 0, 7 'Zeitanzeige schwarz auf hellgrau
'
'******** Tasten abfragen *****************************************
DO
Taste$ = INKEY$
'
SELECT CASE Taste$
CASE "s", "S" 'Start/Stopp betaetigt
StartZeit! = TIMER - Sekunden!
IF Uhrlaeuft% = 0 THEN Uhrlaeuft% = 1 ELSE Uhrlaeuft% = 0
CASE "r", "R" 'Ruecksetzen betaetigt
Sekunden! = 0 'Stoppuhr auf 0 setzen ...
Uhrlaeuft% = 0 '... und anhalten
CASE CHR$(27) 'Programm beenden bei Esc-Taste
END
END SELECT
'
'******** Zeit in Sekunden ermitteln ******************************
IF Uhrlaeuft% = 1 THEN
IF StartZeit! > TIMER THEN 'Ist des Ruecksetzen des TIMERs
'um Mitternacht erfolgt?
Sekunden! = TIMER + 86400 - StartZeit!
'Ruecksetzen kompensieren
ELSE
Sekunden! = TIMER - StartZeit! 'kein Mitternachtssprung
END IF
END IF
'
'******** Stunden:Minuten:Sekunden fuer Anzeige ermitteln *********
'DOUBLE-Gleitpunktformat verwenden, um die Rundungsfehler klein zu
'halten. Die Ganzzahldivision "\" kann wegen Genauigkeitsproblemen
'nicht verwendet werden und ist durch INT ersetzt.
'
Sekunden# = CDBL(Sekunden!) 'Typumwandlung in DOUBLE
Stunden# = INT(Sekunden# / 3600#)
Minuten# = INT((Sekunden# - Stunden# * 3600#) / 60#)
Sekundenanzeige# = Sekunden# - INT(Sekunden# / 60#) * 60#
'
'******** Zeit anzeigen ******************************************
LOCATE 4, 2
PRINT USING "##:"; Stunden#; 'Stunden 2-stellig anzeigen
PRINT USING "##:"; Minuten#; 'Minuten 2-stellig anzeigen
PRINT USING "##.#"; Sekundenanzeige# 'Sekunden 3-stellig anzeigen
LOOP
 
Das obige Programm steht im Verzeichnis Progs\ zur Verfügung sowie online unter www.antonis.de/faq/progs/stopuhr3.bas .
 
Eine grafisch recht ansprechend gestaltete Variante dieses Programms findest Du auf meiner Webseite
www.qbasic.de unter "QBasic -> Download -> Eigene Programme -> Stoppuhr.zip"
 
Wenn eine Genauigkeit von 1 Sekunde ausreicht, kann man eine Stoppuhr auch sehr elegant mit "ON TIMER(1) GOSUB zeitroutine" realisieren. Dieser Befehl bewirkt ein Anspringen der Routine "zeitroutine" zu jeder Sekunde. Ein Überlauf-Problem um Mitternacht, wie es bei der TIMER-Funktion angelegt ist, gibt es bei dieser Methode prinzipbedingt nicht.
 
Mein folgendes Programm STOPUHR4.BAS zeigt, wie das geht:
 
'************************************************************************
' STOPUHR4.BAS = Stoppuhr mit Verwendung des ON TIMER-Befehls
' ============
' Dieses Q(uick)Basic-Programm realisiert eine Stoppuhr mit einer
' Genauigkeit und Aufloesung von 1 Sekunde. Dabei wird der ON TIMER-
' Befehl verwendet, um jede Sekunde ereignisorientiert eine Routine zur
' Uhrbearbeitunge anzuspringen. Der maximale Zaehlbereich betraegt 99
' Sunden.
'
' (c) Thomas Antoni, 26.11.05 - 27.11.05
'************************************************************************
'
'-------------- Bedienhinweise anzeigen ---------------------------------
CLS
PRINT
PRINT " S T O P P U H R"
PRINT " ========================="
PRINT " Start/Stopp...[Leertaste]"
PRINT " Rücksetzen....[Entf]"
PRINT " Beenden.......[Esc]"
COLOR 15, 1 'Uhranzeige Weiss auf Blau
GOSUB Uhranzeige 'Uhr erstmalig anzeigen
'
'------------- Timerbearbeitung aktivieren ------------------------------
ON TIMER(1) GOSUB Uhrbearbeitung 'Jede Sekunde Neuanzeige der Stoppuhr
TIMER ON 'Ereignisorientierte Timer-Bearbeitung aktivieren
'
'------------- Hauptprogramm-Dauerschleife: Tastenbearbeitung -----------
DO 'Hauptprogramm-Dauerschleife
Taste$ = INKEY$
SELECT CASE Taste$
CASE CHR$(32) 'Leertaste => Start/Stop wechseln
Laeuft% = NOT Laeuft%
CASE CHR$(0) + "S" 'Entf-Taste => Uhr ruecksetzen und anhalten
Sekunden% = 0
Minuten% = 0
Stunden% = 0
Laeuft% = 0
CASE CHR$(27) 'Esc-Taste => Programm beenden
END
END SELECT
LOOP
END
'
'------------- Ereignisprozedur "Uhrenbearbeitung" ----------------------
Uhrbearbeitung: 'Stoppuhr weiterzaehlen
IF Laeuft% THEN Sekunden% = Sekunden% + 1
GOSUB Uhranzeige
RETURN
'
'------------- Uhr berechnen und anzeigen -------------------------------
Uhranzeige: 'Minuten und Stunden berechnen u. Uhr anzeigen
IF Sekunden% = 60 THEN
Sekunden% = 0
Minuten% = Minuten% + 1
END IF
IF Minuten% = 60 THEN
Minuten% = 0
Stunden% = Stunden% + 1
END IF
LOCATE 10, 6
PRINT USING " ##:##:## "; Stunden%; Minuten%; Sekunden%
RETURN
 
Das obige Programm steht im Verzeichnis Progs\ zur Verfügung sowie online unter www.antonis.de/faq/progs/stopuhr4.bas .
 
Komischerweise wird die Stoppuhr unter Windows 95 bei diesem Programm nur weitergezählt, wenn das Programmfenster das aktuelle Fenster ist, erkenntlich an der blauen Titelzeile. Bei den anderen Programmen STOPUHR1...3 tritt dieser Effekt nicht auf, und die Uhr läuft auch im Hintegrund weiter. Ich vermute, das liegt an der Verwendung des Timer-Interrupts mit ON TIMER() GOSUB..
 
 

Antwort 2
~~~~~~~~~~~
[ von jb im QB-Forum, 25.9.2005 ]
Das Mitternachts-Problem kann man mit TIME$ lösen, falls eine zeitliche Auflösung von ganzen Sekunden für die Anwendung ausreichend ist. Die Lösung sieht z.B. wiefolgt aus:
 
t$ = TIME$
sekunden = 0
DO
IF t$ <> TIME$ THEN
sekunden = sekunden + 1
t$ = TIME$
END IF
LOOP UNTIL sekunden = 10

 
Hier wird nur geschaut, ob sich TIME$ (die Uhrzeit im hh:mm:ss-Format) ändert, was ja jede Sekunde geschieht.
 
Das ist zwar nicht so genau, aber ob es jetzt 10 1/2 Sekunden sind oder 10 ist ja eigentlich egal.

 
 
Answer 3
~~~~~~~~
' TIMER can be used - it ticks off 1/18.2 of a
'second:
delay = 2
finish = TIMER + delay
DO
LOOP UNTIL TIMER => delay
' This works, but has a fatal midnight flaw: the
'timer is reset to 0 at midnight and it is therefore
'possible that "finish" will never be reached. It's
'easy to test for midnight (there are 86,400 seconds in
'a day).
' However, it is easy not to depend on TIMER to do
'the counting - count it yourself. Something like:
delay = whatever
t! = INT(TIMER)
DO
IF t! <> INT(TIMER) THEN
t! = INT(TIMER)
count = count + 1
PRINT count
END IF
LOOP UNTIL count = delay
' This routine doesn't care what the reading of
'TIMER is, only that it has changed. A roll-over at
'midnight is just as valid a change as an increase of
'one second. If you want 1/10's of a second, you'll
'need to alter accordingly. Since TIMER ticks at 18.2
'times a second, the smallest interval will be
'0.0549450549 sec.

[ The QBasic-MonsterFAQ --- Start Page: www.antonis.de/faq ]