Hallo,
ich habe mir eine riesige 4-Digit 7-Segment Anzeige mittels 8-bit
Schieberegistern zusammengebaut. Diese Anzeige soll 60 Min.
runterzählen. Dies tut sie auch wunderbar.
Das Problem, das ich nun habe, ist dass ich die Uhr gerne jederzeit
anhalten oder reseten können möchte.
Allerdings sind die 4 Digits in 4 for-Schleifen eingewickelt, die
wiederrum in einer if-Bedingung hängen. Dort kommt man scheinbar nicht
so leicht wieder raus.
Der Code sieht ungefähr so aus:
WENN (Knopf gedrückt)
FOR (Zähler 10 Min)
FOR (Zähler 1 Min)
FOR (Zähler 10 Sek)
FOR (Zähler 1 Sek)
Schieberegisterfunktion
delay 500ms
Mittelpunkte an
delay 500ms
Mittelpunkte aus
ENDE
ENDE
ENDE
ENDE
ENDE
Ich würde diesen Ablauf nun gerne jederzeit unterbrechen können. Dazu
muss ich permanent in der Lage sein, einen INPUT abfragen zu können. Das
wäre hier mit einer IF-Bedingung zwar möglich, wenn auch nicht sehr
zuverlässig, da die delays dort ziemlich eingreifen.
Könnte mir eventuell jemand einen Tip geben?
Danke und liebe Grüße
Mike
Oh mann, dass ich da nicht selbst drauf gekommen bin... man kann die
"idle" time der 500ms delays locker in 2 for-schleifen packen, die
jeweils 500ms abzählen - klar !
Danke für den Tipp !
btw, kennt sich hier jemand mit interrupt routinen aus und kann mir
sagen ob die gut oder schlecht sind? gibt ja beim Programmieren immer
fatale Fehler, die man nicht begehen sollte...
Mike schrieb:> btw, kennt sich hier jemand mit interrupt routinen aus und kann mir> sagen ob die gut oder schlecht sind?
Interrupts sind für die Programmierung von µC (bzw. Computern allgemein)
essentiell. Dein "Programm" oben ist das perfekte Beispiel, wie man es
nicht macht. Denn obwohl du den Prozessor zu 100% auslastest, sind
99,9% davon unproduktives Warten in delay(). Und auch die Verwendung von
delay() zum Aufbau eines Timers ist schon mal grundsätzlich falsch.
Denn zu den nominal 1000ms Wartezeit kommt ja noch die Zeit die der µC
wirklich arbeiten muß (z.B. Bits in die Schieberegister schieben). Und
wenn du diese Zeit nicht kennst (oder schlimmer: wenn die nicht konstant
ist) dann kannst du sie auch nicht berücksichtigen und dein Timer läuft
(mehr oder weniger) konstant zu langsam.
Richtig würde man das mit einem Timerinterrupt machen, der mindestens 2
mal (für das Blinken des Doppelpunkts) pro Sekunde auslösen muß. Flexib-
ler wird man, wenn man den Timer etwas öfter auslösen läßt. Vielleicht
10 oder 100 mal pro Sekunde. Und dann zählt man im Interrupt eine
Variable hoch und kann bei den entsprechenden Werten den Doppelpunkt
ausschalten oder auf die nächste Sekunde weiterzählen.
Ob man die Anzeige gleich im Interrupt aktualisiert oder das im Haupt-
programm macht, ist Geschmackssache. Aber selbst bei 100 Interrupts pro
Sekunde hat man mehr als genug Zeit, das im Interrupt zu machen. Die
Tastenabfragelogik kann man dann im Hauptprogramm machen.
Meine Empfehlung wäre, das AVR-GCC-Tutorial durchzuarbeiten,
insbesondere die Kapitel über Interrupts.
Axel S. schrieb:> Interrupts sind für die Programmierung von µC (bzw. Computern allgemein)> essentiell.
Nach so einem Satz hab ich schon lange vergeblich im Internet gesucht.
Ich hatte bis jetzt so oft die Situation, in dem ich eine Prozedur oder
eine Schleife auf "Knopfdruck" beenden hätte müssen. In den Foren steht
dann immer "denk dir eine Abbruchbedingung aus" oder "programmier drum
herum", sogar "goto" Befehle wurden empfohlen.
Axel S. schrieb:> Denn zu den nominal 1000ms Wartezeit kommt ja noch die Zeit die der µC> wirklich arbeiten muß (z.B. Bits in die Schieberegister schieben). Und> wenn du diese Zeit nicht kennst (oder schlimmer: wenn die nicht konstant> ist) dann kannst du sie auch nicht berücksichtigen und dein Timer läuft> (mehr oder weniger) konstant zu langsam.
Ja, die Uhr hinkt auf 60 Min. ca. 25 Sekunden hinterher... Ich hätte
nicht gedacht, dass die Verarbeitung (die sich meines Wissens nach ja im
Nanosekundenbereich abspielt) einen so großen zeitlichen Einfluss hat.
Das Gute ist, dass es bei der Uhr für meinen speziellen Anwendungsfall
nicht auf die Minute ankommt.
Axel S. schrieb:> Meine Empfehlung wäre, das AVR-GCC-Tutorial durchzuarbeiten,> insbesondere die Kapitel über Interrupts.
Ich setze mich sofort dran.
Vielen Dank für die ausführliche Antwort !!!
Mike schrieb:> Axel S. schrieb:>> Interrupts sind für die Programmierung von µC (bzw. Computern allgemein)>> essentiell.>> Nach so einem Satz hab ich schon lange vergeblich im Internet gesucht.> Ich hatte bis jetzt so oft die Situation, in dem ich eine Prozedur oder> eine Schleife auf "Knopfdruck" beenden hätte müssen. In den Foren steht> dann immer "denk dir eine Abbruchbedingung aus" oder "programmier drum> herum", sogar "goto" Befehle wurden empfohlen.
Alles Quatsch.
Sieh dir das einmal an
FAQ: Timer
und überleg, wie du deine Uhr damit programmieren kannst.
Tip. die komplette Uhrenlogik - also das Weiterzählen auf Sekunden,
Minuten, Stunden, kommt in die ISR.
*Edit*: Bei dir natürlich das Runterzählen.
> nicht gedacht, dass die Verarbeitung (die sich meines Wissens nach ja im> Nanosekundenbereich abspielt)
Na ja. Nanosekunden sind jetzt ein bischen zu klein gedacht.
Aber der springende Punkt ist:
zum einen: das alles summiert sich
zum anderen: auch wenn auf deinem Quarz zb 16Mhz drauf steht. Der macht
keine 16000000.000000000 Schwingungen pro Sekunde! Auch ein Quarz hat
Abweichungen. Das besondere bei einem Quarz ist nicht, dass er genau die
aufgedruckte Anzahl Schwingungen macht, sondern dass er
(temperaturkonstanz vorausgesetzt) diese Anzahl nicht verändert. Die
aufgedruckte Anzahl ist ein Fertigungsproblem. Klar könnte man jeden
Quarz darauf hintrimmen - aber du könntest das nicht mehr bezahlen.
Das alles lässt sich dann so zusammenfassen:
dein _delay_ms(1000) wird eine Wartezeit von zirka 1000 Millisekunden
aufweisen. Für den Hausgebrauch um eine LED augenscheinlich 1 Sekunde
lang blinken zu lassen, ist das gut genug. Aber es sind eben nicht
1000.000000000 Millisekunden sondern ein bischen mehr oder weniger. Und
diese Abweichungen summieren sich. Ein Tag hat ca 86000 Sekunden. Ist
die Zeitbestimmung im delay auch nur 0.1 Millisekunden daneben, dann
hast du über den Tag gerechnet dann eben schon einen enormen Fehler.
Axel S. schrieb:> Ob man die Anzeige gleich im Interrupt aktualisiert oder das im Haupt-> programm macht, ist Geschmackssache.Karl H. schrieb:> Tip. die komplette Uhrenlogik - also das Weiterzählen auf Sekunden,> Minuten, Stunden, kommt in die ISR.
Eine Frage hätte ich noch. Ich habe gelesen, dass die ISR so kurz wie
möglich sein und so schnell wie möglich wieder verlassen werden sollen
(ich weiß nicht genau, warum). Wenn ich nun den gesamten
Runterzählalgorithmus in die ISR packe, würde das nicht dagegen
verstoßen?
@ Mike (Gast)
>> Tip. die komplette Uhrenlogik - also das Weiterzählen auf Sekunden,>> Minuten, Stunden, kommt in die ISR.>Eine Frage hätte ich noch. Ich habe gelesen, dass die ISR so kurz wie>möglich sein und so schnell wie möglich wieder verlassen werden sollen>(ich weiß nicht genau, warum).
Damit sie sich im Extremfall nicht überschneiden. Eine TIMER-ISR, welche
all 10ms aufgerufen wird, darf nicht länger als 10ms dauern. Siehe
Interrupt.
> Wenn ich nun den gesamten>Runterzählalgorithmus in die ISR packe, würde das nicht dagegen>verstoßen?
Das passt schon, man muss es nicht übertreiben.
Mike schrieb:> Eine Frage hätte ich noch. Ich habe gelesen, dass die ISR so kurz wie> möglich sein und so schnell wie möglich wieder verlassen werden sollen> (ich weiß nicht genau, warum). Wenn ich nun den gesamten> Runterzählalgorithmus in die ISR packe, würde das nicht dagegen> verstoßen?
So schnell wie möglich. So langsam wie nötig.
Du willst haben, dass die Uhrzeit immer integral (= als eine Einheit)
betrachtet wird. An dieser Stelle ist die komplette Zeit eine einzige
Einheit, die sich "zufällig" in kleinere Einheiten unterteilt.
Und mal Hand aufs Herz. 3 Variablen runterzählen mit ein bischen Logik
dazwischen ist nicht wirklich lang. In Relation zu den fixen 'Kosten'
eines ISR Aufrufs ist das immer noch verschwindend.
...es wurde alles gesagt. Der Timer ist wirklich eine Sache für einen
Interrupt. Wenn Du in C programmierst ist das alles so schnell, das es
völlig unproblematisch ist.
Der AVR läuft ja wenigstens mit 1 MHz, selbst da ist eine Sekunde eine
kleine Ewigkeit.
Solltest Du eine ISR für den Timer verwenden, musst Du aber etwas
aufpassen bei der Routine die die Zeit in die Bits für die
7-Segmentanzeige umwandelt.
Sonst könnte es passieren, das sich während der Bit-Berechnung bzw.
Ausgabe die Zeit ändert, im schlimmsten Fall z.B. gerade auf eine andere
Minute springt, also sowas
- Zeit ist 13:00
- Deine Ausgaberoutine liest die Minuten und fängt and die Bits für die
ersten beiden Segmente zu berechnen
- Die Ausgaberoutine wird unterbrochen, der Timer-Interrupt wird
ausgelöst, Zeit springt auf 12:59
- Deine Routine berechnet die Bits für die Minuten
-> Deine Anzeige wäre 13:59, was natürlich falsch ist
Um sowas zu verhindern gibt es mehrere Möglichkeiten:
- Du kopierst die Werte für Stunden/Minuten in dieser Weise:
StundenCopy = Stunden;
MinutenCopy = Minuten;
if(StundenCopy != Stunden) dann
StundenCopy = Stunden;
MinutenCopy = Minuten;
endif
Dabei wird geprüft ob sich nach dem Kopieren des MInutenwertes etwa die
Stunde geändert hat (durch einen Timer-Interrupt), wenn ja, müssen die
Stunden/Minuten erneut gelesen werden.
- Du sperrst die Interrupts während Deiner Ausgaberoutine
- Du packst auch die Ausgabe mit in den Timer-Interrupt
Markus M. schrieb:> Deine Anzeige wäre 13:59, was natürlich falsch ist
... zumindest für eine Millisekunde oder so steht das dann da so ...
Das kann man getrost ignorieren.
Gruß
Jobst
Markus M. schrieb:> Sonst könnte es passieren, das sich während der Bit-Berechnung bzw.> Ausgabe die Zeit ändert, im schlimmsten Fall z.B. gerade auf eine andere> Minute springt
Sehr unwahrscheinlich. Denn schließlich wird man die Zeit ja nur dann
neu ins Schieberegister schieben, wenn sie sich geändert hat. Man würde
also in der ISR ein Flag (volatile uint8_t) setzen, das dem Hauptpro-
gramm sagt daß sich die Zeit geändert hat und das Display aktualisiert
werden muß. Und dann hat man dazu eine ganze Sekunde Zeit, bis sich die
Zeit das nächste Mal ändert. Der hypothetische AVR mit 1MHz Taktfrequenz
könnte also ca. 1 Million Instruktionen ausführen. Das ist erheblich
mehr als man braucht um 32 Bits rauszuschieben.
OK, wenn man den Doppelpunkt blinken lassen will, dann muß man das
Display zweimal jede Sekunden updaten. Deswegen wäre es u.U. cleverer
den Doppelpunkt direkt mit einem IO-Pin zu steuern. Andererseits gibt es
keinen Bonus für eingesparte Taktzyklen. Das Display zweimal pro Sekunde
upzudaten wird den AVR nicht umbringen.
Wenn man die Software in Funktionen und Prozeduren unterteilt, die
Zähler und die Ausgabe trennt - entsteht erst garnicht so ein
unübersichtlicher Code-Haufen. Dann ergeben sich auch die Möglichkeiten
zum Anhalten und Resetten quasi von selber ...