Forum: Mikrocontroller und Digitale Elektronik Komisches Verhalten im Timer1 Atmega8


von Karl-alfred R. (karl-alfred_roemer)


Lesenswert?

Hallo Zusammen,

habe ein Stück Code unten angehangen, den ich teilweise aus Tutorials
zusammenkopiert habe und teilweise abgeändert habe, damit er so
halbwegs das tut, was ich möchte.

Die Hauptschleife tut nichts. Der Timer1 soll jede Sekunde einen
Zeitzähler hochzählen. Dann habe ich noch einen Input von meinem
Bewegungsmelder, der je nach dem wo ich den Pin anstecke INT0 oder
INT1 auslöst. Timer und Bewgungsmeldungsaktionen werden beim STK500
auf den LEDs angezeigt.

Die Timerzeit kann ich  über den Compare-Wert ändern.
Bei 3897 ist es ziemlich genau eine Sekunde. Das ist schon mal
seltsam, denn laut AVR-Studio läuft der Atmega8 mit dem
internen Oszi mit 1 MHZ.  Und 100* 3897 ergibt eher 0,389700MHz ???

Noch seltsamer ist die unterstrichene Zeile. Subcount 100.
Es soll nur an jedem 100sten Interupt etwas passieren.
Komischerweise kann ich die Zahl 100 abändern, wie ich will:
Die LED zählen immer genau gleich schnell.  Irgendwie kann das
doch nicht sein oder?

Falls jemand nen Tip hat bedanke ich mich schon mal im Voraus
VG
Karl

.include "m8def.inc"

.def temp = r16
.def Zeitzaehler = r24

.def LEDS  = r20
.def SubCount = r21


.org 0x0000
       rjmp main              ; Reset Handler
.org INT0addr                 ; External Interrupt0 Vector Address
       rjmp int0_handler
.org INT1addr                 ; External Interrupt1 Vector Address
       rjmp int1_handler
.org OC2addr                  ; Output Compare2 Interrupt Vector Address
       reti
.org OVF2addr                 ; Overflow2 Interrupt Vector Address
       reti
.org ICP1addr                 ; Input Capture1 Interrupt Vector Address
       reti
.org OC1Aaddr                 ; Output Compare1A Interrupt Vector 
Address
       rjmp timer1_compare
.org OC1Baddr                 ; Output Compare1B Interrupt Vector 
Address
       reti
.org OVF1addr                 ; Overflow1 Interrupt Vector Address
       reti
.org OVF0addr                 ; Overflow0 Interrupt Vector Address
       reti
.org SPIaddr                  ; SPI Interrupt Vector Address
       reti
.org URXCaddr                 ; USART Receive Complete Interrupt Vector 
Address
       reti
.org UDREaddr                 ; USART Data Register Empty Interrupt 
Vector Address
       reti
.org UTXCaddr                 ; USART Transmit Complete Interrupt Vector 
Address
       reti
.org ADCCaddr                 ; ADC Interrupt Vector Address
       reti
.org ERDYaddr                 ; EEPROM Interrupt Vector Address
       reti
.org ACIaddr                  ; Analog Comparator Interrupt Vector 
Address
       reti
.org TWIaddr                  ; Irq. vector address for Two-Wire 
Interface
       reti


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



      ldi r16, 0xFF                ; lade Arbeitsregister r16 mit der 
Konstanten 0xFF
        out DDRB, r16
    ldi LEDS, 0b11111111
    ldi Zeitzaehler, 0
        ldi temp, 0b00000000 ;(1<<ISC10) | (1<<ISC11) | (1<<ISC00)| 
(1<<ISC01) ;INT0 und INT1 ansteigende Flanke löst Interupt aus
        out MCUCR, temp

        ldi temp, 0b11000000  ; INT0 und INT1 aktivieren
        out GICR, temp


                                          ; Vergleichswert
        ldi     temp, high( 3897 - 1 )   ;bei 58455 und 200 hätte man 
30Sek Intervalle)
        out     OCR1AH, temp
        ldi     temp, low( 3897 - 1 )
        out     OCR1AL, temp
                                    ; CTC Modus einschalten
                                    ; Vorteiler auf 1
        ldi     temp, ( 1 << WGM12 ) | ( 1 << CS10 )
        out     TCCR1B, temp

        ldi     temp, 1 << OCIE1A  ; OCIE1A: Interrupt bei Timer Compare
        out     TIMSK, temp

        clr     SubCount
        sei



loop:
        rjmp    loop



timer1_compare:                     ; Timer 1 Output Compare Handler

        push    temp               ; temp 1 sichern
        in      temp,sreg          ; SREG sichern

        inc     SubCount            ; Wenn dies nicht der 100. Interrupt
        cpi     SubCount, 100       ; ist, dann passiert gar nichts
;----------------------------------------------------------------------- 
----
        brne    end_isr

    adiw Zeitzaehler, 1
    mov temp, Zeitzaehler
    com temp
    out PORTB, temp     ; r16 ins IO-Register PORTB ausgeben

end_isr:
        out     sreg,temp          ; sreg wieder herstellen
        pop     temp
        reti                        ; das wars. Interrupt ist fertig



int0_handler:
         push temp             ; Das SREG in temp sichern. Vorher
         in   temp, SREG       ; muss natürlich temp gesichert werden

         ldi LEDS, 0
         out PORTB, LEDS

         out SREG, temp        ; Die Register SREG und temp wieder
         pop temp              ; herstellen
         reti

int1_handler:
         push temp             ; Das SREG in temp sichern. Vorher
         in   temp, SREG       ; muss natürlich temp gesichert werden

         ldi LEDS, 255
         out PORTB, LEDS

         out SREG, temp        ; Die Register SREG und temp wieder
         pop temp              ; herstellen
         reti

von Gast (Gast)


Lesenswert?

1
timer1_compare:
2
        push    temp               
3
        in      temp,sreg          
4
5
        [temp verändern]
6
7
        out     sreg,temp          
8
        pop     temp
9
        reti

Damit wirst Du keine Freude haben. So geht es richtig:
1
        push    temp               
2
        in      temp,sreg          
3
        push    temp          <-- ! 
4
5
        [temp verändern]
6
7
        pop     temp          <-- !
8
        out     sreg,temp          
9
        pop     temp
10
        reti

Überleg selbst, warum das zusätzliche push-pop-Paar nicht fehlen darf.

Das war ein offensichtlicher Fehler, aber vielleicht gibts auch noch 
weitere in Deinem Programm - ich habe nicht danach gesucht.

von Stefan E. (unixdevil)


Lesenswert?

> timer1_compare:                     ; Timer 1 Output Compare Handler
>
>         push    temp               ; temp 1 sichern
>         in      temp,sreg          ; SREG sichern
>
>         inc     SubCount            ; Wenn dies nicht der 100. Interrupt
>         cpi     SubCount, 100       ; ist, dann passiert gar nichts
> ;----------------------------------------------------------------------- ----
>         brne    end_isr
>
>     adiw Zeitzaehler, 1
>     mov temp, Zeitzaehler ; <--- hier überschreibst Du das sreg
>     com temp
>     out PORTB, temp     ; r16 ins IO-Register PORTB ausgeben


Also entweder wie der Vorposter schrieb, nochmal ein push temp, oder Du 
legst Dir direkt ein Register nur fürs sreg an, so mache es ich immer, 
dann brauchst Du an sowas auch nicht denken.

von Karl-alfred R. (karl-alfred_roemer)


Lesenswert?

Das hat mich im Originalcode auch gewundert.
http://www.mikrocontroller.net/articles/AVR-Tutorial:_Interrupts
(ganz unten)

Habe mir die vermeindliche Unnötigkeit dadurch
erklärt, dass das temp erst mal gesichert wird,
dann sreg in temp hinein kopiert wird.

Hinter dem eigentlichen Programmcode das gleiche,
nur anders herum. Weil temp aber im eigentlichen
Programmcode nicht verändert wird, brauche ich es
auch nicht vorher zu sichern und rücksichern.

Wenn es aber verändert werden würde, wäre der
Statusregisterinhalt natürlich weg. Deshalb
müsste es eigentlich weggesichert werden, wie
du es vorgeschlagen hast.

Das kann aber nicht die Ursache des Problems sein
oder?

von Stefan E. (unixdevil)


Lesenswert?

Ich habe Deinen Code mal angepasst, so funzt es. Erwarte aber beim 
internen Oszillator keine Wunder, der haut schon ganz schön daneben.

Wenn Du alle Sekunde einen Interrupt willst, setze den Vorteiler auf
64, dann hast Du genau eine Sekunde.
1
        ldi     temp, high( ((1000000 / 64) - 1) )
2
        out     OCR1AH, temp
3
        ldi     temp, low( ((1000000 / 64) - 1) )
4
        out     OCR1AL, temp
5
        ; CTC Modus einschalten
6
        ; Vorteiler auf 64, dann hat
7
        ; man alle Sekunde einen Interrupt
8
     
9
        ldi     temp, ( 1 << WGM12 ) | (1<CS11) | ( 1 << CS10 )
10
        out     TCCR1B, temp

Außerdem hast Du Deinen SubCounter nicht zurückgesetzt, konnte also
auch nicht richtig funktionieren.
1
timer1_compare:                    ; Timer 1 Output Compare Handler
2
3
        push    temp               ; temp 1 sichern
4
        in      temp,sreg          ; SREG sichern
5
        push    temp               ; temp sichern, da damit gearbeitet wird
6
7
        inc     SubCount           ; Wenn dies nicht der 100. Interrupt
8
        cpi     SubCount, 100      ; ist, dann passiert gar nichts
9
;---------------------------------------------------------------------------
10
        brne    end_isr
11
                   
12
        clr     SubCount           ;<--- SubCount löschen
13
        adiw    Zeitzaehler, 1
14
        mov     temp, Zeitzaehler
15
        com     temp
16
        out     PORTB, temp        ; r16 ins IO-Register PORTB ausgeben
17
    
18
end_isr:
19
        pop     temp               ; Inhalt von temp wiederherstellen
20
        out     sreg,temp          ; sreg wieder herstellen
21
        pop     temp
22
        reti                       ; das wars. Interrupt ist fertig

Im Original-Code aus dem Tutorial wurde temp auch nicht verändert. Aber 
in Deiner Timer1-Routine nimmst Du temp, überschreibst es und somit geht 
der Inhalt aus dem sreg verloren.

Gruß, Stefan

von Karl-alfred R. (karl-alfred_roemer)


Lesenswert?

Danke Stefan,

das mit dem Clr Subcount bringt eine erhebliche Änderung der Taktung.
Das war wohl der Hauptfehler. Und nun ändert sich auch die Frequenz
wenn ich den Subcountwert ändere. Alles so wie es sein soll.

Nun wundert mich nur, dass es vorher ÜBERHAUPT gelaufen ist. 
stirnrunzel

Das wegsichern des Statusregisters in Temp und das dann auf den
Stack scheint aber irgendwie keine Auswirkungen zu haben. Wenn
ich NUR das korrigiere, läuft mein Programm genauso wie ohne diese
Korrektur.

Dann noch die Taktung:

ldi     temp, high( ((1000000 / 64) - 1) )
        out     OCR1AH, temp
        ldi     temp, low( ((1000000 / 64) - 1) )
        out     OCR1AL, temp
        ; CTC Modus einschalten
        ; Vorteiler auf 64, dann hat
        ; man alle Sekunde einen Interrupt

        ldi     temp, ( 1 << WGM12 ) | (1<CS11) | ( 1 << CS10 )
        out     TCCR1B, temp

Wenn ich diese Werte eingebe, dann dauert das Intervall, das
eigentliche eine Sekunde dauern soll bei mir immer noch ca 1,5
Sekunden.

Rein rechnerisch müsste der Timer 15624 mal pro Sekunde überlaufen.
Durch den Vorteiler 64 wären es dann 244,125 Timerauslösungen pro
Sekunde.

Durch die 100 Überläufe meines Subcountes müsste die Sollsekunde
dann etwa 0,41 Sekunden dauern. Oder rechne ich hier irgendwas
falsch?

von Stefan E. (sternst)


Lesenswert?

Karl-alfred Römer schrieb:

> Das wegsichern des Statusregisters in Temp und das dann auf den
> Stack scheint aber irgendwie keine Auswirkungen zu haben. Wenn
> ich NUR das korrigiere, läuft mein Programm genauso wie ohne diese
> Korrektur.

Logisch, dein "Hauptprogramm" besteht ja auch nur aus:
1
loop:
2
        rjmp    loop

Da brauchst du in den ISRs eigentlich gar keine Register retten (nicht 
mal SREG).

von Gast (Gast)


Lesenswert?

>Nun wundert mich nur, dass es vorher ÜBERHAUPT gelaufen ist.
>stirnrunzel

Das ist doch leicht erklärbar: Auch vorher schon wurde subcount 
regelmäßig Null ("von selbst"), aber erst nach Erreichen des 
Zählerstands 255 (+ 1 = 256 --> 0).

>Das wegsichern des Statusregisters in Temp und das dann auf den
>Stack scheint aber irgendwie keine Auswirkungen zu haben. Wenn
>ich NUR das korrigiere, läuft mein Programm genauso wie ohne diese
>Korrektur.

Möglich, aber dann ist es purer Zufall. In einem komplexen Programm, bei 
denen Interrupts laufenden Code an nicht vorhersehbaren Stellen 
unterbrechen, wird es mit hoher Wahrscheinlichkeit "sehr seltsame" 
Fehler zeitigen.

von Stefan E. (sternst)


Lesenswert?

> Rein rechnerisch müsste der Timer 15624 mal pro Sekunde überlaufen.
> Durch den Vorteiler 64 wären es dann 244,125 Timerauslösungen pro
> Sekunde.

Nein. Mit einem Vorteiler von 64 taktet der Zähler 15625 mal pro 
Sekunde. Mit einem OCR-Wert von 15624 hast du dann einen Interrupt pro 
Sekunde. Warum das bei dir im Augenblick nicht klappt? Weil hier:
1
     ldi     temp, ( 1 << WGM12 ) | (1<CS11) | ( 1 << CS10 )
bei CS11 ein '<' fehlt.

von Karl-alfred R. (karl-alfred_roemer)


Lesenswert?

Jetzt hab ichs kapiert. Habe völlig falsch gerechnet, obwohl
ich dachte, ich hätte es verstanden.

Also der Timer1 zählt immer bis 15624. Das dauert bei 1MHz
Takt dann pro Durchlauf 1/64 Sekunde. Wenn ich nun noch einen
Vorteiler von 64 habe, dann habe ich genau eine Sekunde. Den
Subcounter brauche ich also garnicht!

>>>> ldi     temp, ( 1 << WGM12 ) | (1<CS11) | ( 1 << CS10 )
>>bei CS11 ein '<' fehlt.

Oh, da sieht man mal wieder, dass ich früher nur Turbo-Pascal
programmiert habe. Da hätte der Compiler gemault, wenn mir
so ein blöder Schreibfehler passiert wäre.
Dass der Assembler das aber nicht gemerkt hat?

>> Da brauchst du in den ISRs eigentlich gar keine Register
>> retten (nichtmal SREG).

Hä? Erst geht alles schief, weil ich EIN Register vergessen
habe, und nun brauche ich eigentlich gar keine Register zu
retten?  Wie will man denn dann das Überschreiben durch
Unterprogramme und Interupt-Routinen verhindern?

von Stefan E. (sternst)


Lesenswert?

Karl-alfred Römer schrieb:

>>> Da brauchst du in den ISRs eigentlich gar keine Register
>>> retten (nichtmal SREG).
>
> Hä? Erst geht alles schief, weil ich EIN Register vergessen
> habe, und nun brauche ich eigentlich gar keine Register zu
> retten?  Wie will man denn dann das Überschreiben durch
> Unterprogramme und Interupt-Routinen verhindern?

Das ist natürlich keine allgemeine Aussage, sondern bezieht sich einzig 
auf deinen aktuellen Code. Du hast außerhalb der Interrupts schlicht 
nichts, was überschrieben werden könnte. Nicht mal das SREG spielt 
außerhalb der Interrupts irgendeine Rolle, also muss selbst das nicht 
gerettet werden (bzw. hat das fehlerhafte Retten in deinem Code keine 
Auswirkungen).

von Stefan E. (sternst)


Lesenswert?

Außerdem ist dir vielleicht nicht aufgefallen, dass ich ein anderer 
"Stefan" bin. Es ist also nicht so, dass ich auf einmal meine Meinung zu 
den push/pop geändert hätte. ;-)

von Stefan E. (unixdevil)


Lesenswert?

Ja, und ich bin der andere Stefan. Und das fehlende 1<CS11 war mein 
Fehler, man sollte halt um diese Nachtzeit nichts mehr posten. ;)
Und was das Sichern vom sreg angeht, da halte ich ebenso die Meinung von 
meinem Namensvetter. Im Moment hat es keine Auswirkung, aber sollte 
irgendwann in Deiner Loop noch was dazu kommen, was früher oder später 
sicher der Fall sein wird, dann viel Spaß beim Fehlersuchen.

Gruß, Stefan

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.