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?
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.
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.
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.
Das kann sich durch diese Fragen auch nicht beantworten lassen.
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 |
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...? ;-)
Vergleiche doch einfach mal deinen Code mit etwa: http://www.mikrocontroller.net/articles/AVR-Tutorial:_Uhr Was ist mit SREG beim Interruptaufruf?
> interrupt: > inc zahl > reti du musst doch das Prozessor Register (SREG) sicher und wiederherstellen. inc verändert es.
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.