Forum: Mikrocontroller und Digitale Elektronik Timer. Er ist schneller als gewünscht.


von Jochen (Gast)


Lesenswert?

Hallo,

ich habe einen Atmel Mega 8 mit 3,6864 MHZ.
Ich möchte einen timer erstellen, der genau 1 mal in der Sekunde 
aktiviert wird.

Wie ich den Tutorial
http://www.mikrocontroller.net/articles/AVR-Tutorial:_Timer

habe ich überlegt:

3,6864 Mhz bedeutet in der Sek. werden 3686400 Taktzyklen generiert. 
Durch eine nVorteiler von 64 wird der timer 57600 mal in der Sekunde 
erhöht.

Es werden in der Sekunde also 57600/256 = 225 overflows generiert.

Ich habe also überlegt, nur jedes 225 te zu werten und dann immer die 
Aktion, hier Led umschalten durchzuführen.
Jedoch blinken die Leds wesentlich schneller als einmal in der Sekunde. 
Was könnte das sein ? Hier der Quellcode


.include "m8def.inc"

.def temp = r16
.def leds = r17

.org 0x0000
        rjmp    main                  ; Reset Handler
.org OVF0addr
        rjmp    timer0_overflow       ; Timer Overflow Handler

main:
        ldi     temp, LOW(RAMEND)     ; Stackpointer initialisieren
        out     SPL, temp
        ldi     temp, HIGH(RAMEND)
        out     SPH, temp

        ldi     temp, 0xFF            ; Port B auf Ausgang
        out     DDRB, temp

        ldi     leds, 0xFF

        ldi     temp, 0b00000011      ; CS00 setzen: Teiler 1
        out     TCCR0, temp
 ldi r20,0b11100001
        ldi     temp, 0b00000001      ; TOIE0: Interrupt bei Timer 
Overflow
        out     TIMSK, temp

        sei

loop:   rjmp    loop

timer0_overflow:                      ; Timer 0 Overflow Handler
        go:
    inc r20
    brne end


    out     PORTB, leds
    com     leds
        ldi r20,0b11100001
    end:
    reti

von bleifrei (Gast)


Lesenswert?

Jedes mal wenn ein overflov von R20 ist, wird R20 auf 0xE1 gesetzt. Du 
solltest R20 auf 256-225 setzen.

von crazy horse (Gast)


Lesenswert?

inc r20
    brne end


    out     PORTB, leds
    com     leds
        ldi r20,0b11100001

entweder
dec r20

oder
ldi r20, -225

Du zählst nur von 225 bis 0, dementsprechend nur 30 Timer-overflows.

von Jochen (Gast)


Lesenswert?

Danke. Wenn ich es auf 256 minus 225 stelle funktionert es.
Jetzt habe ich aber ein Verständnusproblem.

Ich möchte doch 225 und nicht 31 Durchläufe machen.

und m.E. macht

timer0_overflow:                      ; Timer 0 Overflow Handler
        go:
    inc r20
    brne end


    out     PORTB, leds
    com     leds
        ldi r20,0b11100001
    end:
    reti

225 Durchläufe, ich verstehe das so:

Wenn ein overflow ist, dann wird zu timer0_overflow: gesprungen.
Dann wird r20 um eins kleiner. Ist es nicht identisch mit null, wird auf 
den nächsten timer overflow gewartet und es waren noch keine 225 
Durchläufe.

Ist es Null, dann wird "brne end" übersprungen und die Led gewechselt

von crazy horse (Gast)


Lesenswert?

incrementieren heisst erhöhen, also +1.
Dementsprechend decrementieren -1.

von Sebastian (Gast)


Lesenswert?

Der Hund liegt in der ISR (Interrupt Service Routine) des Timers 
begraben.

timer0_overflow:                      ; Timer 0 Overflow Handler
        go:

das go: kannst du dir sparen. Du hast ja schon timer0_overflow als 
Einsprungmarke. Stören tut es aber auch nicht.
Was du aber unbedingt machen solltest, ist das sreg auf den Stack zu 
schieben. Und nicht vergessen, es vor Beenden der ISR wieder abzuholen.

    inc r20

Bevor du das Register verwendest solltest du dessen Inhalt löschen. 
Meines Wissens nach ist der nach dem Neustart nämlich zufällig. Demnach 
ist auch die Dauer der "ersten Sekunde" die du abzählst zufällig

    brne end

Vor brne musst du immer sagen, was du mit was verglichen hast. Also zum 
beispiel   "cpi r20, 225" würde den Inhalt von r20 mit 225 vergleichen 
und je nach Wert von r20 bestimmte Bits im status-Register (sreg) 
setzten. brne schaut dann lediglich nach, welche Bits gesetzt sind, und 
springt dann entweder zur angegebenen Adresse, oder eben nicht.

    out     PORTB, leds
    com     leds

Das sollte soweit passen

        ldi r20,0b11100001

Hier wirds dann etwas arg seltsam. Wenn ich das richtig verstanden habe, 
willst du doch mit r20 zählen, wie oft die ISR schon durchlafen wurde. 
Wenn das Programm diesen Punkt hier erreicht, dann ist r20 ja genau 225 
(wenn du das Prog so änderst wie ich es vorgeschlagen habe). Wenn du 
hier also nochmal 225 reinschreibst ändert sich der Wert von r20 nicht. 
Beim Nächsten Aufruf der ISR wird r20 wieder um eins erhöht. Erreicht 
r20 den Wert 255 folgt als nächstes 0. Danach wird weiter aufaddiert 
bist 225. Dann werden die LEDs erst wieder geschaltet. Insgesamt hast du 
also 256 ISR-Aufrufe zwischen zweimal schalten.
Besser ist es, wenn du "clr r20" schreibst. Dann wird der Inhalt von r20 
hier gelöscht. Es wird also wieder von vorne mit Zählen angefangen. Bis 
eben wieder 225 erreicht ist und die LEDs geschalten werden.

    end:

Wie schon erwähnt: das sreg wieder zurückspielen.

    reti




Übrigens wird deine LED im 2-Sekunden-Takt blinken: AN-eine 
Sekunde-AUS-eine Sekunde-AN-...

Ansonsten solltest du dir vielleicht noch das Tutorial hier auf der 
Seite durchlesen (link ist links oben). Das dürfte bei klarheit 
schaffen.

Gruß, Sebastian

von Jochen (Gast)


Lesenswert?

Hi Sebastian,

Danke für deine Anregungen. Ist die erste Sekunde wirklich Zufall? Ich 
habe ja in der main Schleife, die ganz am Anfang durchlaufen wird auch 
ldi r22.... geschrieben, genau das war meine Absicht, dass es eben beim 
ersten Durchlauf auch einen Wert hat.

"Hier wirds dann etwas arg seltsam."

ich habe an dieser Stelle r22 wieder den vollen Wert gegeben, für die 
weitere Runde. Es geht ja dann alle wieder von vorne los.

von Jochen (Gast)


Lesenswert?

crazy horse, danke das habe ich verwechselt, ja..

von Sebastian (Gast)


Lesenswert?

Oh, sorry. Habe übersehen, dass r20 initialisisert wird. Das ist dann in 
Ordnung.

So richtig funktionieren kann es aber halt nicht. An sich hast du zwei 
möglichkeiten.
1. die von dir angedachte:
Man lädt 255 in r20, zieht jedesmal eins ab. wenn Null erreicht ist, 
schaltet man die LEDs und lädt wieder 255 nach r20.

Dann muss man aber aus "inc r20" "dec r20" machen. inc erhöht um ein, 
dec verringert um eins.

und halt die Sache mit dem "brne". Das muss wissen, was verglichen 
wurde. Dafür schaut es ins sreg. Im sreg steht aber halt nichts über 
deinen Vergleich von r20 mit 0, weil du ihn ja nicht ausgeführt hast.


Die andere Möglichkeit wäre eben, dass man "0" nach r20 lädt und r20 
immer um eins erhöht und dann eben mit 255 vergleicht

Schau einfach mal ins Tutorial. Dann werden auch die Hintergründe 
klarer.

Sebastian

von Jochen (Gast)


Lesenswert?

Ja inc und dec habe ich verwechselt.
Mit dieser Korrektur funktioniert es Bei brne habe ich nichts 
korrigiert, es klappt trotzdem.

von crazy horse (Gast)


Lesenswert?

"im sreg steht aber halt nichts über
deinen Vergleich von r20 mit 0, weil du ihn ja nicht ausgeführt hast."

Noch mal nachdenken!

von Sebastian (Gast)


Lesenswert?

Oh, richtig. inc setzt das zero-flag. Bleibt nur noch die Frage, ob das 
dem Programmierer auch bewusst war ;-)

Sebastian

von Jochen (Gast)


Lesenswert?

ja, das hab ich mir so gedacht.
Danke, dass ihr mir geholfen habt ich habe vor eine Uhr zu machen.

von Karl H. (kbuchegg)


Lesenswert?

Jochen wrote:
> ja, das hab ich mir so gedacht.
> Danke, dass ihr mir geholfen habt ich habe vor eine Uhr zu machen.

Dann solltest du dir den Overflow Interrupt gleich wieder
aus dem Kopf schlagen. Im angegebenen Tutoriums Artikel steht
auch warum das so nichts wird. Und es steht auch drinnen, wie
man es stattdessen mit dem CTC Modus des Timers machen kann.


von Jochen (Gast)


Lesenswert?

Da steht beim overflow gibt es keien ganzen Zahlen, deswegen entstehen 
Ungenauigkeiten. Mit meinem Takt geht es aber exakt auf, siehe Rechnung 
ganz oben.

von Hannes L. (hannes)


Lesenswert?

@Karl heinz:

Im Prinzip hast Du recht. Aber hier ist der Sonderfall, dass Jochen ohne 
Timer-Reload arbeitet, da er den vollen Zählumfang von 256 bei Vorteiler 
64 nutzt. Die Reload-bedingten Differenzen treten also nicht auf. Auch 
der Interrupt-Abstand von 4,44 ms ist groß genug, um keinen Interrupt zu 
verpassen. Und wie es aussieht, hat er den nachfolgenden Software-Timer 
inzwischen auch in den Griff bekommen. Und die ISR-Frequenz von 225 Hz 
(4,44 ms) ist auch noch geeignet, die Anzeige zu multiplexen und die 
Taster effizient zu entprellen, letzteres notfalls nur zu jedem zweiten 
oder vierten Aufruf. Wenn er keine Zehntel oder Hundertstel Anzeigen 
will, dann ist das keine schlechte Wahl, ich habe (hier) schon 
schlechtere Vorgehensweise bei Timer-Berechnungen gesehen.

Also alles im grünen Bereich, weiter so, Jochen...

Die Anzeigen (siehe anderen Thread) kannst Du direkt am AVR verwenden. 
Die 7 Segmente über Widerstände 330 Ohm an die Portpins, die 4 
Digit-Anoden über Transistoren an Vcc, angesteuert über 4 weitere 
Portpins. Dabei gehen sowohl PNP-Transistoren mit Basiswiderstand in 
Emitterschaltung als auch NPN-Transistoren in Kollektorschaltung (als 
Emitterfolger).

...

von Simon K. (simon) Benutzerseite


Lesenswert?

Sebastian wrote:
> Oh, richtig. inc setzt das zero-flag. Bleibt nur noch die Frage, ob das
> dem Programmierer auch bewusst war ;-)
>
> Sebastian

Jau, dec übrigens auch :D

Btw: Endlich mal jemand der nicht versucht eine Uhr per Delay-Schleife 
zu bauen *räusper.

Jochen, ich würd dir empfehlen statt der Binärwerte bei zB
1
ldi temp, 0b00000011      ; CS00 setzen: Teiler 1
eine andere Schreibweise anzugewöhnen. Und zwar die, mit dem 
Schiebeoperator.

zB:
1
ldi temp, (3<<CS00) ;CS00 und CS01 setzen
oder
1
ldi temp, (1<<CS00)|(1<<CS01) ;CS00 und CS01 setzen
1
x << y
 verschiebt den wert "x" um "y" binäre Stellen nach links, falls du es 
noch nicht weißt.

Ist so jedenfalls viel lesbarer und fehler-unanfälliger (Also finde ich 
jedenfalls).

von Jochen (Gast)


Lesenswert?

Vielen vielen Dank für die Tipps. Ich werde mir diese Schreibweise in 
Zukunft angewöhnen.

von Hannes L. (hannes)


Lesenswert?

Jochen wrote:
> Ich werde mir diese Schreibweise in
> Zukunft angewöhnen.

Alles zu seiner Zeit...
Nimm am besten die Schreibweise, die für die jeweilige Zeile die beste 
Aussagekraft hat.
Es ist sinnfrei, einen Schleifenzähler oder Timer-Vergleichswert binär, 
oktal, hex oder als ASCII-Zeichen anzugeben, wenn die stinknormale 
dezimale Angabe bedeutend besser lesbar ist.
Einstellungen an Steuerregistern der internen Pripherie sollte man dann 
bei den Bitnamen nennen, wenn jedes Bit eine andere Bedeutung hat, den 
Vorteiler eines Timers fasse ich dabei gern zu einer Dezimalzahl 
zusammen, die als 'Nummer' für die Vorteiler-Liste aufgefasst werden 
kann.

Es gibt nunmal mehrere (gleichberechtigte) Darstellungsformen für 
Konstanten. Man sollte daher nicht die wählen, die am coolsten aussieht, 
sondern die, die den Zweck am besten widerspiegelt. Für oft benötigte 
konstante Werte lohnt sich auch das Verwenden von Alias-Namen 
(sprechende Namen). Sie werden im Kopf des Quelltextes (mit .equ) 
vereinbart und können dann überall verwendet werden. Aber diese 
Feinheiten kommen so nach und nach.

...

von Jochen (Gast)


Lesenswert?

Ja die Schreibweise mit den Pfeilen mag ich auch nich so ;)
Nagut dann werde ich jetzt mal an der Uhr weitermachen. Um mit meinen 
Bauteilen auszukommen hab ich mir jetzt übrigens überlegt 4x Multiplexer 
zu nehmen.  die können ja mit 4 Eingangskombinationen angesteuert werden 
und dann hab ich das in etwa so vor:

00 wird an Pins vom MC ausgegeben:
In dieser Zeit wird die erste Stelle der Sekunden Anzeige beschrieben

01 wird an Pins vom MC ausgegeben:
Die zweite Anzeige der Sekundenanzeige wird beschrieben

10 wird vom MC ausgegeben:
Die erste Anzeige der Stunden wird ausgegeben

11 wird ausgegeben:
Die zweite Anzeige der Stunden wird ausgegeben


und wenn das ganz schnell immer wieder von vorne durchlaufen wird hoffe 
ich, dass die Uhrzeit angezeigt wird...

von Hannes L. (hannes)


Lesenswert?

Jochen wrote:
> Ja die Schreibweise mit den Pfeilen mag ich auch nich so ;)

Ich finde die Tipperei auch nicht ergötzend, aber in bestimmten Fällen 
muss es zwecks Lesbarkeit einfach sein.

> Nagut dann werde ich jetzt mal an der Uhr weitermachen. Um mit meinen
> Bauteilen auszukommen hab ich mir jetzt übrigens überlegt 4x Multiplexer
> zu nehmen.  die können ja mit 4 Eingangskombinationen angesteuert werden
> und dann hab ich das in etwa so vor:

In Anbetracht der Tatsache, dass da für Dich Vieles noch Neuland ist, 
würde ich davon abraten.

>
> 00 wird an Pins vom MC ausgegeben:
> In dieser Zeit wird die erste Stelle der Sekunden Anzeige beschrieben
>
> 01 wird an Pins vom MC ausgegeben:
> Die zweite Anzeige der Sekundenanzeige wird beschrieben
>
> 10 wird vom MC ausgegeben:
> Die erste Anzeige der Stunden wird ausgegeben
>
> 11 wird ausgegeben:
> Die zweite Anzeige der Stunden wird ausgegeben
>
>
> und wenn das ganz schnell immer wieder von vorne durchlaufen wird hoffe
> ich, dass die Uhrzeit angezeigt wird...

Überleg' doch mal, was willst Du und was hast Du.

Du hast:
- 'nen Mega8 mit
  - 8 Pins an Port D (bei Verzicht auf UART)
  - 6 Pins an Port B (nur 6 wegen Quarz)
  - 6 Pins an Port C (die gehen auch digital)

Du willst:
- 'ne Uhr mit 4 Ziffernanzeigen auf 7-Segment-LED-Basis

Die braucht:
- 7 (8) Portpins für die Segmente (Kathoden), sind die innerhalb eines
  Ports, dann wird die Programmierung einfacher
- 4 Portpins für die Digit-Treiber, also für die Transistoren, die immer
  nur eine der 4 Anoden an H schalten
- ein paar entprellte Taster zum Stellen der Uhr, möglichst auf einem
  Port

Es bietet sich an:
- PortD: (7) Segmente der Anzeige über Widerstände 330 Ohm
- PortB: (4) Transistoren für Ziffern, (2) frei
- PortC: einige Taster

Du brauchst also:
- ein Stück Lochraster-Platine
- Mega8
- Keramik-Kondensatoren 100nF (Abblocken der Betriebsspannung)
- Quarz mit 2 Keramik-Kondensatoren 22pF
- 7 (8) Segmentwiderstände 330 Ohm
- 4 NPN-Transistoren, z.B. BC337
- 4 7-Segment-Anzeigen
- 3 oder 4 Taster (je nach Stellalgorithmus)
- eine stabilisierte Stromversorgung 5V / 200mA

Mehr fällt mir jetzt nicht ein, aber ich habe sicher was vergessen...

...

von Karl H. (kbuchegg)


Lesenswert?

Jochen wrote:
> Da steht beim overflow gibt es keien ganzen Zahlen, deswegen entstehen
> Ungenauigkeiten. Mit meinem Takt geht es aber exakt auf, siehe Rechnung
> ganz oben.

Und denkst, der Quarz schwingt wirklich auf der Frequenz
die aufgedruckt ist, ...
Auch Quarze haben (kleine) Abweichungen. Gerade bei Uhren
wirkt sich das aber aus, wenn nach 1 Woche 1 Sekunde fehlt.

Aber du hast recht: Lass es erst mal mit dem Overflow
und sieh zu, dass du den Multiplex noch hinkriegst.
Die Timersteuerung der Uhr selbst kann man später immer
noch ändern.

von Hannes L. (hannes)


Lesenswert?

Wenn die Uhr dann falsch geht (dazu muss sie aber erstmal überhaupt 
"gehen", bis dahin ist noch ein weiter Weg), kann man immernoch auf "die 
genaue Sekunde" in der Codesammlung verweisen...

;-)

...

von Jochen (Gast)


Lesenswert?

Hallo,

ich habe mir jetzt diverse Bauteile bestellt und werde mal mein Bestes 
geben wenn sie angekommen sind.
Was meint ihr denn wie ungenau wird die Uhr werden so wie ich sie 
gestern aufgeführt habe?

Grüße
Jochen

von Philipp B. (philipp_burch)


Lesenswert?

Da kann man nix meinen, mit viiiieeel Glück geht die genauer als 'ne 
Atomuhr der PTB, mit etwas Pech wird die Ungenauigkeit schon nach einer 
Woche (Oder weniger) spürbar.

von Simon K. (simon) Benutzerseite


Lesenswert?

Hannes Lux wrote:
> ...
> Es gibt nunmal mehrere (gleichberechtigte) Darstellungsformen für
> Konstanten. Man sollte daher nicht die wählen, die am coolsten aussieht,
> sondern die, die den Zweck am besten widerspiegelt.
> ...

Äh, schon klar, dass man nicht plötzlich alle Konstanten mit Bitnamen 
und Schiebeoperator angeben soll. Zum Beispiel bei eben diesem 
Prescaler, um den es hier geht. Der sollte, wie Hannes schon gesagt hat 
am besten dezimal angegeben werden.

Aber: Gerade wenn man ein Stück Code in ein Forum stellt, wird man ein 
höheres "Publikum" erreichen, wenn man diese Schreibweise wählt um ein 
solches "Einstellungsregister" zu befüllen. Und nicht etwa binär oder 
gar dezimal. So kann man wirklich sicher gehen, dass wenigstens das 
funktioniert.

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.