Forum: Mikrocontroller und Digitale Elektronik Problem mit Interrupt RET und RETI


von M. M. (mrmcchicken)


Lesenswert?

Hallo miteinander,
ich habe eine kurze frage zu Interrupts.
Mit dem 16 Bit Timer des ATmega8 möchte ich gerne ein Zahl hoch zählen.
Der Interrupt funktioniert soweit auch. Allerdings habe ich das Problem, 
dass der Interrupt nur ein einziges mal ausgeführt wird und dann nichts 
mehr.

Kann ich innerhalb eines Interupthandlers bestimmte Funktionen mir rcall 
aufrufen und dann mit RET wider zurück springen? Wenn der Interrupt in 
einer Endlosschleife ausgelöst wurde springe ich dann mit RETI zurück in 
diese Schleife oder zurück zur Interrupt Adresse?

von (prx) A. K. (prx)


Lesenswert?

Konrad M. schrieb:
> Kann ich innerhalb eines Interupthandlers bestimmte Funktionen mir rcall
> aufrufen und dann mit RET wider zurück springen?

Ja.

> Wenn der Interrupt in
> einer Endlosschleife ausgelöst wurde springe ich dann mit RETI zurück in
> diese Schleife oder zurück zur Interrupt Adresse?

RETI springt exakt dorthin zurück, wo untergebrochen wurde. Interrupts 
halten also den Programmfluss im Hauptprogramm nur temporär auf, ändern 
ihn aber nicht.

von Falk S. (falkschilling)


Lesenswert?

Konrad M. schrieb:
> Kann ich innerhalb eines Interupthandlers bestimmte Funktionen mir rcall
> aufrufen und dann mit RET wider zurück springen?

Kannst du, aber ob du das solltest, müsstest du dich selbst 
hinterfragen. Um ein halbwegs gescheites Timing hinzukriegen sollten 
deine ISRs auch von einer überschaubaren Komplexität sein.

> Wenn der Interrupt in
> einer Endlosschleife ausgelöst wurde springe ich dann mit RETI zurück in
> diese Schleife oder zurück zur Interrupt Adresse?

Vergegenwärtige Dir doch mal, was die eigentliche Aufgabe von RET ist 
(was passiert da auf dem Stack). Da die Interruptbehandlung eine 
Unterbrechung des normalen Programmablaufs ist, erfordert diese einen 
speziellen Befehl um wieder in den normalen Programmablauf 
zurückzuspringen - und das macht RETI.

von M. M. (mrmcchicken)


Lesenswert?

Danke soweit für die Hilfe.
Das Timing ist nicht ganz so kritisch. Das größere Problem was ich habe, 
ist, dass der Interrupt nur einmalig ausgeführt wird obwohl ich ein RETI 
am ende des Interupthandlers habe und momentan auch keine weiteren 
Adressen im Interrupt anspringe.

von (prx) A. K. (prx)


Lesenswert?

Das kann sich durch diese Fragen auch nicht beantworten lassen.

von M. M. (mrmcchicken)


Lesenswert?

Eventuell kann mir von euch jemand sagen was das faul im Programm ist.
Das soll jetzt ein erster versuch werden ein LCD anzusteuern. Ich würde 
im Grunde nur ganz gerne  eine Zahl hoch zählen und ausgeben. Leider 
wird der Interrupt nur einmalig ausgelöst. Mir ist bewusst, dass 
pause_lang kürzer ist als pause_weniger_lang, dass sollte bei diesem 
Problem aber nichts ändern.

1
/*
2
 * PD5 = E (enable)
3
 * PD4 = RS (register select | Befehle / Daten)
4
 * PD0 = DB4
5
 * PD1 = DB5
6
 * PD2 = DB6
7
 * PD3 = DB7
8
 *
9
 */ 
10
11
12
.org 0x000
13
rjmp main
14
15
.org 0x007
16
rjmp interrupt
17
18
19
20
21
main:
22
 
23
;------------------------------- Variablen
24
.def temp = r16
25
.def temp1 = r18
26
.def temp3 = r3
27
.def temp4 = r17
28
.def temp5 = r4
29
.def lcd1 = r20
30
.def lcd2 = r21
31
32
.def zahl = r22
33
34
;------------------------------- Stackpointer
35
ldi temp, LOW(RAMEND)
36
out SPL, temp
37
ldi temp, HIGH(RAMEND)
38
out SPH, temp
39
40
;------------------------------- PORTD Ausgang
41
ldi temp, 0xff
42
out DDRD, temp
43
out DDRB, temp
44
out PORTB, temp
45
ldi zahl, 0x30
46
47
;------------------------------- Timer 1
48
ldi temp, (1<<OCIE1B)                                      ; Interupt bei clear timer on compare match
49
out TIMSK, temp
50
ldi temp, (1<<WGM12) | (1<<CS12) | (0<<CS11) | (1<<CS10)   ; CTC Modus einschalten, so wie vorteiler setzen
51
out TCCR1B, temp
52
53
ldi temp, HIGH(1000)                                       ; CTC wert
54
out OCR1BH, temp
55
ldi temp, LOW(1000)
56
out OCR1BL, temp
57
58
59
60
;------------------------------- Vor initialisierung Pause machen
61
rcall pause_lang
62
63
64
lcd_init:                     ; LCD Initialisierung
65
ldi lcd1, 0b00000011          ; muss 3mal hintereinander gesendet
66
out PORTD, lcd1               ; werden zur Initialisierung
67
rcall lcd_enable              ; 1
68
rcall pause_lang 
69
rcall lcd_enable              ; 2
70
rcall pause_lang
71
rcall lcd_enable              ; 3
72
rcall pause_lang 
73
74
ldi lcd1, 0b00000010          ; 4bit-Modus einstellen
75
out PORTD, lcd1
76
rcall lcd_enable
77
rcall pause_lang
78
79
ldi lcd1, 0b00101000          ; 4Bit / 2 Zeilen / 5x8
80
rcall lcd_command
81
ldi lcd1, 0b00001111          ; Display ein / Cursor ein /  Blinken
82
rcall lcd_command
83
ldi lcd1, 0b00000100          ; inkrement / kein Scrollen
84
rcall lcd_command
85
ldi lcd1, 0b00000010          ; Cursor home
86
rcall lcd_command
87
ldi lcd1, 0b00000001          ; LCD clear
88
rcall lcd_command
89
sei
90
rjmp loop
91
92
93
94
lcd_enable:
95
sbi PORTD, 5                  ; Enable high
96
nop
97
nop
98
nop
99
cbi PORTD, 5                  ; Enable low
100
ret
101
102
;-------------------------------- Ausgabe des Bytes als Befehl
103
lcd_command:                  
104
mov lcd2, lcd1                ; Kopie für Übertragung des zweiten Nibbles
105
swap lcd1                     ; Vertauschen (erst Bit 4 - 7 Ausgeben!) --> LCD Datenleitungen DB4 bis DB7 angeschlossen
106
                              ; ARV Datenleitungen PD0 bis PD3 angeschlossen.
107
andi lcd1, 0b00001111         ; Rest auf 0 setzen (RS = 0 also befehl)
108
out PORTD, lcd1               ; Ausgabe Bit 4 - 7
109
rcall lcd_enable
110
andi lcd2, 0b00001111         ; Das gleich mit den Bits 0 - 3 wiederholen
111
out PORTD, lcd2               ; Kein vertauschen nötig. Nibble bereits an richtiger stelle
112
rcall lcd_enable
113
rcall pause_lang
114
ret
115
116
;-------------------------------- Ausgabe eines Bytes als Befehl
117
lcd_data:
118
mov lcd2, lcd1               ; Kopie für Übertragung des zweiten Nibbles
119
swap lcd1                    ; Vertauschen (erst Bit 4 - 7 Ausgeben!) --> LCD Datenleitungen DB4 bis DB7 angeschlossen
120
                             ; ARV Datenleitungen PD0 bis PD3 angeschlossen.
121
andi lcd1, 0b00001111        ; Rest auf 0 setzen
122
sbr lcd1, 1<<4               ; RS = 1, also Daten!!!
123
out PORTD, lcd1              ; Ausgabe Bit 4 - 7
124
rcall lcd_enable
125
andi lcd2, 0b00001111        ; Das gleich mit den Bits 0 - 3 wiederholen
126
sbr lcd2, 1<<4               ; RS = 1, also Daten!!!
127
out PORTD, lcd2              ; Kein vertauschen nötig. Nibble bereits an richtiger stelle
128
rcall lcd_enable
129
rcall pause_lang
130
ret
131
132
133
134
135
136
loop:
137
138
ldi lcd1, 0x01
139
rcall lcd_command
140
mov lcd1, zahl
141
rcall lcd_data
142
rcall pause_weniger_lang
143
144
rjmp loop
145
146
147
148
149
interrupt:
150
inc zahl
151
reti
152
153
154
155
156
157
158
pause_lang: 
159
ldi temp4, 0x00        
160
ldi temp, 0x05
161
mov temp5, temp
162
pause_l_w:
163
ldi temp, 0x00
164
pause_2:
165
inc temp
166
cpi temp, 0xFF
167
breq pause_3
168
rjmp pause_2
169
pause_3:
170
inc temp4
171
cp temp4, temp5
172
breq pause_ende
173
rjmp pause_l_w
174
pause_ende:
175
ldi temp4, 0x00
176
ret
177
178
pause_weniger_lang: 
179
ldi temp, 0xFF
180
mov temp5, temp
181
pause_wl_w:
182
ldi temp, 0x00
183
pause_wl_2:
184
inc temp
185
cpi temp, 0xFF
186
breq pause_wl_3
187
rjmp pause_wl_2
188
pause_wl_3:
189
inc temp4
190
cp temp4, temp5
191
breq pause_wl_ende
192
rjmp pause_wl_w
193
pause_wl_ende:
194
ldi temp4, 0x00
195
ret

von Markus W. (Firma: guloshop.de) (m-w)


Lesenswert?

Konrad M. schrieb:
> Allerdings habe ich das Problem,
> dass der Interrupt nur ein einziges mal ausgeführt wird und dann nichts
> mehr.

SP wurde richtig initialisiert? Besteht das Hauptprogramm wirklich nur 
aus einer Endlosschleife? Falls nicht, könnte es sein, dass die ISR 
Auswirkungen auf den Lauf des Hauptprogramms hat -- alle doppelt 
genutzten Register müssen gesichert werden.

Vielleicht liegt der Fehler aber auch in Zeile 42...? ;-)

von Falk S. (falkschilling)


Lesenswert?

Vergleiche doch einfach mal deinen Code mit etwa:

http://www.mikrocontroller.net/articles/AVR-Tutorial:_Uhr

Was ist mit SREG beim Interruptaufruf?

von Peter II (Gast)


Lesenswert?

> interrupt:
> inc zahl
> reti

du musst doch das Prozessor Register (SREG) sicher und wiederherstellen. 
inc verändert es.

von M. M. (mrmcchicken)


Lesenswert?

Habe den Fehler beseitigen können.
Ich fürchte da war wohl irgend eine Schwarze Magie am werk.

Habe den Abschnitt Timer1 in folgendes umgeändert:
1
;------------------------------- Timer 1
2
ldi temp, (1<<OCIE1A)                                      ; Interupt bei clear timer on compare match
3
out TIMSK, temp
4
ldi temp, (1<<WGM12) | (1<<CS12) | (0<<CS11) | (1<<CS10)   ; CTC Modus einschalten, so wie vorteiler setzen
5
out TCCR1B, temp
6
7
sei
8
9
ldi temp, HIGH(1000)                                       ; CTC wert
10
out OCR1AH, temp
11
ldi temp, LOW(1000)
12
out OCR1AL, temp

Also ich benutze nun den CTC Kanal A. Wieso das nun einen Unterschied 
macht weiß ich auch nicht. Kann mir das evtl. jemand erklären?
Natürlich habe ich die Interruptadresse auch geändert.

von Stefan E. (sternst)


Lesenswert?

Konrad M. schrieb:
> Also ich benutze nun den CTC Kanal A. Wieso das nun einen Unterschied
> macht weiß ich auch nicht. Kann mir das evtl. jemand erklären?

Es gibt kein "CTC Kanal B". Wenn du den CTC-Modus wählst, und OCR1A auf 
0 lässt (wie in deinem ersten Code), dann zählt der Timer nur 
0-0-0-0-0-... und erreicht nie die 1000 um den OCR1B-Interrupt 
auszulösen.

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.