Forum: Mikrocontroller und Digitale Elektronik Große 7Seg-LED-Anzeige mit Timer


von Mike (Gast)


Lesenswert?

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

: Verschoben durch User
von Teo D. (teoderix)


Lesenswert?

Dann bau doch noch ne Schleife die zB. 1/10s zählt und häng da die 
Abfrage rein.

von Mike (Gast)


Lesenswert?

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...

von Teo D. (teoderix)


Lesenswert?

Du scheinst noch mehr Fragen zu haben, als auf diesem Weg beantwortet 
werden könnten.
Schade das Du nicht erwähnt hast welchen Prozessor Du verwendest. Falls 
es ein Pic sein sollte, schau mal hier rein:
http://www.sprut.de/
Für AVR:
https://www.mikrocontroller.net/articles/Absolute_Beginner-AVR_Steckbrettprojekte

Allgemein ist hier das stöbern auch sehr informativ:
https://www.mikrocontroller.net/articles/Hauptseite

von Axel S. (a-za-z0-9)


Lesenswert?

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.

von Mike (Gast)


Lesenswert?

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 !!!

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Mike (Gast)


Lesenswert?

Klasse, danke nochmal für die tollen Antworten !
Ich werde mich nun mal stärker mit diesem Thema befassen.

von Mike (Gast)


Lesenswert?

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?

von Falk B. (falk)


Lesenswert?

@ 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.

von Karl H. (kbuchegg)


Lesenswert?

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.

von Jobst M. (jobstens-de)


Lesenswert?

Moin!

überkam mich gerade so:
1
TimerInterupt_alle_10ms:
2
{  Hundertstel -= Step;
3
  Wenn (Hundertstel < 0)
4
  {  Hundertstel = 99;
5
    Sekunde--;
6
    Wenn (Sekunde < 0)
7
    {  Sekunde = 59;
8
      Minute--;
9
      Wenn (Minute < 0);
10
      {  Step = 0;
11
        Minute = 0;
12
        Sekunde = 0;
13
        Hundertstel = 0;
14
      }
15
    }
16
  }
17
}
18
19
Main:
20
{  Step = 0;
21
  Minute = 0;
22
  Sekunde = 0;
23
  Hundertstel = 0;
24
25
  Timer auf 10ms setzen und starten
26
  IRQs einschalten
27
28
  While true
29
  {  Wenn (Knopf 'Start' gedrückt)
30
    {  Minute = 60;
31
      Sekunde = 0;
32
      Hundertstel = 0;
33
      Step = 1;
34
    }
35
    Wenn (Knopf 'Pause' gedrückt)
36
    {  Step = 0;
37
    }
38
    Wenn (Knopf 'Weiter' gedrückt)
39
    {  Step = 1;
40
    }
41
    Wenn (Knopf 'Reset' gedrückt)
42
    {  Step = 0;
43
      Minute = 0;
44
      Sekunde = 0;
45
      Hundertstel = 0;
46
    }
47
    Schieberegisterfunktion;
48
    Wenn (Hundertstel > 50)
49
    {  Mittelpunkte an;
50
    }
51
    sonst
52
    {  Mittelpunkte aus;
53
    }
54
    // Irgendwelcher anderer Krempel
55
  }
56
}


Gruß

Jobst

von Markus M. (adrock)


Lesenswert?

...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

: Bearbeitet durch User
von Jobst M. (jobstens-de)


Lesenswert?

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

von Axel S. (a-za-z0-9)


Lesenswert?

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.

von Frank E. (Firma: Q3) (qualidat)


Lesenswert?

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 ...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.