Forum: Mikrocontroller und Digitale Elektronik mehrere Interrupts beim ATMEGA32


von Wolfram Q. (quehl)


Lesenswert?

kann man Interrupts schachteln, d.h. wenn ich eine A/D Wandlung mache, 
beim Fertig-Interrupt das Ergebnis an die RS232 Schnittstelle zum PC 
senden. Die Programmierung in dieser Form ist übersichtlich, aber 
wahrscheinlich nicht zweckmäßig, sofern das überhaupt geht.

Wenn A/D Interrupt, Timer-Interrupt von mehreren Timern zusammentreffen, 
sehe ich Schwierigkeiten. Timer-Interrupt müßte sofort bearbeitet werden 
und RS232 z.B. müßte die Baudrate eingehalten werden. Wenn 1 Timerint. 
bearbeitet wird, dann kann der andere Timerint. nicht bearbeitet werden 
und es entstehen Fehler.

mfg
Quehl

von Null (Gast)


Lesenswert?

Nein, es entstehen keine Fehler. Die Interrupt routinen muessen nur kurz 
genug sein. Der ADC Interrupt, liest den Wert und startet eine neue 
Wandlung. Ein Timer macht einen Reload und scheibt eine Boolean.

von Hannes L. (hannes)


Lesenswert?

Wolfram Quehl wrote:
> kann man Interrupts schachteln, d.h. wenn ich eine A/D Wandlung mache,
> beim Fertig-Interrupt das Ergebnis an die RS232 Schnittstelle zum PC
> senden. Die Programmierung in dieser Form ist übersichtlich, aber
> wahrscheinlich nicht zweckmäßig, sofern das überhaupt geht.
>
> Wenn A/D Interrupt, Timer-Interrupt von mehreren Timern zusammentreffen,
> sehe ich Schwierigkeiten. Timer-Interrupt müßte sofort bearbeitet werden
> und RS232 z.B. müßte die Baudrate eingehalten werden. Wenn 1 Timerint.
> bearbeitet wird, dann kann der andere Timerint. nicht bearbeitet werden
> und es entstehen Fehler.
>
> mfg
> Quehl

Deshalb macht man die ISRs auch so kurz (und schnell) wie irgendwie 
möglich. Anstatt langwierige Aufgaben zu erledigen, sichert man nur die 
zeitkritischen (also die flüchtigen) Werte, setzt ein Jobflag für die 
Mainloop und kehrt zur Tagesordnung zurück. In der Mainloop werden dann 
die Jobflags geprüft und die zugehörigen Jobs ausgeführt, wobei der Job 
sein Jobflag wieder löscht.

Einen ADC-Complete-Interrupt braucht man eigentlich nur, wenn man den 
Sleepmode ADC-Noise-Reduction nutzt, in dem die Timer abgeschaltet 
werden. Ansonsten kann man den ADC "im Vorbeigehen" in einem zyklisch 
aufgerufenen Job auslesen, ADC-Werte "verfallen" nicht, wenn man sie 
verspätet ausliest.

...

von Willi W. (williwacker)


Lesenswert?

Ich weiß nicht genau, wie das beim mega32 ist, beim mega8/88/16/168 
sieht es so aus:

Beim Einsprung in einen Interrupt wird das Globale Interruptflag 
zurückgenommen und erst beim Verlassen des Interupts wieder gesetzt. Das 
führt dazu, dass der gerade aktive Interrupt durch keinen anderen (wie 
hoch prior auch immer) unterbrochen werden kann. Willst Du den Interrupt 
seinerseits wieder unterbrechbar machen, kannst Du das Interruptflag 
setzen (SEI). Das funktioniert auch! Jedoch ist der Stackbedarf 
entsprechend höher.

Ciao
Willi Wacker

von Johannes M. (johnny-m)


Lesenswert?

> Ich weiß nicht genau, wie das beim mega32 ist, beim mega8/88/16/168
> sieht es so aus:
Das ist bei allen AVRs so...

Nested Interrupts (also unterbrechbare und damit verschachtelte ISRs) 
sollte man nur in Ausnahmefällen und auch dann nur unter Beachtung der 
möglichen Folgen verwenden. Es soll ja Leute geben, die versuchen, einen 
externen Level-Interrupt unterbrechbar zu machen und sich dann wundern, 
wenn es sofort bei Auftreten des besagten Interrupts zum Komplettabsturz 
kommt...

Generell: Interrupt Handler so kurz wie möglich und alles, was nicht 
innerhalb der nächsten Mikrosekunde nach eintreten des 
Interrupt-Ereignisses abgearbeitet werden muss, ins Hauptprogramm 
schmeißen und in der ISR nur ein Jobflag setzen, das dann im 
Hauptprogramm abgefragt wird.

von Knut B. (Firma: TravelRec.) (travelrec) Benutzerseite


Lesenswert?

>ADC-Werte "verfallen" nicht, wenn man sie verspätet ausliest.

Das stimmt nicht ganz, bei freilaufendem ADC mit schneller 
Samplefrequenz werden die alten Werte im ADCH/ADCL-Register schnell mal 
überschrieben (in minimal 13µs). Wenn man die dann nicht abholt, gibt es 
unnachvollziehbare Sprünge im Datenstrom. Bei einzeln ausgelösten 
Wandlungen bleiben die alten Werte bis zum Ende der neuen Wandlung 
gültig, was dann nicht so kritisch ist.

von Hannes L. (hannes)


Lesenswert?

> Das stimmt nicht ganz, ...

Richtig, aber keine Aussage stimmt immer und überall ganz. Es gibt immer 
einen Spezialfall, in dem allgemeine Aussagen nicht mehr ganz stimmen.

Wenn man mit einer (womöglich grenzwertigen) Samplerate eine 
Wechselspannung erfassen (digitalisieren) will, dann bestimmt der ADC 
natürlich das Timing, denn dann will man ja keine Abtastung verlieren.

Aber hier ging es um die Antwort auf eine Anfänger-Verständnisfrage und 
nicht um das letzte Quäntchen Optimierung hart am Ende des Machbaren. 
Deshalb habe ich mich etwas einfach ausgedrückt.

Gruß nach HBS...
...

von Willi W. (williwacker)


Lesenswert?

Johannes M. wrote:
>> Ich weiß nicht genau, wie das beim mega32 ist, beim mega8/88/16/168
>> sieht es so aus:
> Das ist bei allen AVRs so...
>
> Nested Interrupts (also unterbrechbare und damit verschachtelte ISRs)
> sollte man nur in Ausnahmefällen und auch dann nur unter Beachtung der
> möglichen Folgen verwenden. Es soll ja Leute geben, die versuchen, einen
> externen Level-Interrupt unterbrechbar zu machen und sich dann wundern,
> wenn es sofort bei Auftreten des besagten Interrupts zum Komplettabsturz
> kommt...
>
> Generell: Interrupt Handler so kurz wie möglich und alles, was nicht
> innerhalb der nächsten Mikrosekunde nach eintreten des
> Interrupt-Ereignisses abgearbeitet werden muss, ins Hauptprogramm
> schmeißen und in der ISR nur ein Jobflag setzen, das dann im
> Hauptprogramm abgefragt wird.

Stimmt natürlich, ich habe allerdings einen Anwendungsfall, wo es 
wichtig ist, einen festen Takt einzuhalten, und dann läuft der 
entsprechende Timer-Interrupt 5ms (bei einem Takt von 10ms). Das ist 
schon recht gruselig! Damit während dieser Zeit auch andere (externe, 
extrem kurz, von mir!) Interrupts zum Zuge kommen, war der Dreh nötig.

Ich hab das nur geschrieben, weil ich dachte, es interessiert. Ich hatte 
es so nicht erwartet.

Möge es als abschreckendes Beispiel dienen!

Ciao
Willi Wacker

von Hannes L. (hannes)


Lesenswert?

Willi, im Einzelfall kann es sinnvoll sein, in einer ISR andere 
Interrupts zuzulassen. Man sollte sich nur nicht daran gewöhnen, immer 
so zu programmieren.

Dass (D)eine ISR 5ms dauert, sollte allerdings zu Denken geben, auch 
wenn sie nur alle 10ms aufgerufen wird. Ich vermute, da wäre sicher was 
mit Jobflag und Mainloop machbar gewesen.

Aber wenn es funktioniert, ist es ja gut, das nächste Programm wird 
besser. Ich verbessere meine alten Programme, die aus heutiger Sicht 
schlecht programmiert sind, aber zuverlässig arbeiten, auch nicht mehr. 
Die nächsten werden besser - oder auch nicht... ;-)

...

von Wolfram Q. (quehl)


Lesenswert?

die ADC sind ja recht schnell, wenn ich da warte und erst im 
Hauptprogramm irgendwann die Werte zum PC schicke, dann sind die 
vorhergehenden AD Werte schon überschrieben. Ein einfaches Abspeichern 
im RAM geht da so nicht. Man könnte einen Puffer organisieren, aber das 
find ich recht aufwendig, zumal der doch überläuft, weil das Ausgeben an 
die RRS232 sehr viel länger dauert. Ich denke da jetzt doch, im 
Interruptzweig des ADC die Daten zum PC zu schicken, aber da dann nur 
mit der Flagabfrage. Da könnte dann der Timerinterrupt dazwischengehen, 
denke ich. Der AD Interrupt dauert dadurch dann recht lange, aber 
Ausgaben zum PC dauern nun mal.

mfg
Quehl

von Johannes M. (johnny-m)


Lesenswert?

Ich frage mich langsam, wo jetzt wirklich das Problem liegt. Es macht 
sowieso keinen Sinn, mit dem A/D-Wandler schneller zu wandeln, als die 
Daten im Programm verarbeitet oder an den PC gesendet werden können. Die 
Baudrate des UART (siehe OP) hat überhaupt nichts mit irgendwelchen 
Interrupts zu tun. Das UART ist eine komplett autarke Hardware-Einheit, 
die, einmal konfiguriert und mit einem Byte im Sende-Datenregister ganz 
unbeeindruckt vom Rest der Welt vor sich hinrattert. Wenn das UART 
fertig ist, meldet es über sein TXC- oder UDRE-Flag, dass das nächste 
Byte gesendet werden kann. Bei Implementierung einer Flusssteuerung ist 
ein bisschen mehr Interaktion erforderlich, aber das sollte für den µC 
kein Problem sein.

Der ADC ist übrigens überhaupt nicht besonders schnell. Er braucht bei 
optimal eingestellter Taktfrequenz und maximaler Samplingrate (15 kSPS 
bei 200 kHz) immerhin fast 70 µs für eine Wandlung. In dieser Zeit kann 
der µC ne Menge anderer Dinge erledigen (wenn er nicht grad mit einer 
sehr kleinen Taktfrequenz läuft).

von Hannes L. (hannes)


Lesenswert?

Da ich nicht nachvollziehen kann, was Du vorhast, kann ich Dir keinen 
konkreten Rat geben. Aber:

Die Übertragung eines Bytes zum PC dauert nicht irgendwie "lange", 
sondern dauert eine exakt berechenbare Zeit. Ebenso kann man den 
Zeitbedarf des ADC auch exakt berechnen (oder anderweitig ermitteln).

Da Du nun weißt (wissen kannst), wie schnell Du werden darfst, kannst Du 
auch das Programm dazu schreiben.

Gehen wir erstmal davon aus, dass der AVR nichts weiter zu tun hat, als 
einen ADC-Wert in Echtzeit an den PC zu schicken. Dann wäre es sinnvoll:

- Den ADC-Vorteiler so einzustellen, dass der ADC schneller ist als ein
  Byte per UART übertragen werden kann. Single Conversion starten.

- TX mit Innterrupt laufen lassen, der Interrupt tritt auf, wenn
  UART für das nächste zu sendende Byte bereit ist.

Dann im UART-Interrupt:

- ADC auslesen und in UDR legen (UART hat also Nachschub und klappert
  im Hintergrund weiter)
- ADC für nächste Messung starten, wenn diese fertig ist, bleibt der
  ADC stehen. Der Messwert auch, er wird dann im nächsten UART-Interrupt
  ausgelesen.

Für die Mainloop bleibt erstmal nur eine leere Endlos-Schleife oder 
besser der Aufruf von SLEEP übrig, es sei denn, der AVR soll nebenbei 
noch andere Aufgaben erledigen (Zeit dazu hat er massig).

Willst Du mehrere ADC-Quellen einlesen, so geht das genauso, nur dass 
die Quelle jedesmal umgeschaltet wird. Es ist auch möglich, im SRAM eine 
Liste mit allen ADC-Quellen anzulegen, in der der jeweils letzte Wert 
oder der Mittelwert über die letzten Messungen (z.B. über 256 Messungen, 
ist ein sehr einfacher Algorithmus) gehalten werden. Dann wäre es auch 
sinnvoll, den ADC im ADC-Interrupt auszulesen (Mittelwert bilden) und 
ins SRAM zu legen. Wenn die ISRs effizient geschrieben sind, stören sie 
sich nicht gegenseitig.

Es gibt viele verschiedene Möglichkeiten der Realisierung, aber nicht 
jede ist für jeden Einzelfall optimal. Ohne Einzelheiten der 
Aufgabenstellung kommen wir also nicht weiter.

...

von Wolfram Q. (quehl)


Lesenswert?

das Problem ist, daß ich beim AVR noch Anfänger bin und erstmal ein 
möglichst gutes Programm erstellen will, ohne dauernd zu testen, wie ich 
das beim PC sonst mache, weil der Flash nicht ewig hält.
2. Problem ist, daß ich beim PC ein Programm geschrieben hatte, daß 2x 
im Jahr abgestürzt ist, aber keine Folgen hatte. Nach 8 Jahren trafen da 
2 Interrupts unglücklich zusammen, so daß es einen Absturz gab und meine 
angeschlossene heizung für 5000 Euro defekt ging. darum ist mir die 
Interruptverarbeitung und die Zeitabläufe besonders wichtig.

Beim AD hatte ich nur den 200khz Takt im Kopf und dachte 5ms wäre die 
Umsetzung. War kleiner Irrtum von mir, weil ich nicht alles gleich im 
Kopf habe.

Danke, werd das mal neu überdenken.

mfg
Quehl

von Wolfram Q. (quehl)


Lesenswert?

Danke an Hannes u.a.

so genau kann ich das noch nicht sagen, was ich machen will. Das ergibt 
sich aus den Möglichkeiten und den Ergebnissen nach der Programmierung. 
Das kann sich nachher durchaus als unbrauchbar herausstellen. Daher hier 
mal meine Gedanken zu den Aufgaben:

Anlaß war, daß ich mit dem PC nicht alles programmieren konnte, was ich 
brauche. Von der Geschwindigkeit her ist der PC weitaus besser.
Das sind mehr Ein-Ausgänge, Analoge Daten lesen, Timeraufgaben. Das kann 
der PC nicht in ausreichendem Maß.
Es sollen Temperaturen eingelesen werden, wobei ich da verschiedene 
Ausführungen und Schnittstellen testen will.
Für Feuchtemessung kommt der Sensirion in Betracht.
PWM Eingänge habe ich schon mal im anderen Thread geschrieben.
Lichtsensor und Beschleunigungssensor wollte ich auch mal ausprobieren. 
Ob ichs praktisch gebrauchen kann, ist allerdings fraglich.
Uhr, DCF soll auch rein.
Verbrauchsmessung diverser elektr. Geräte.
Ausgaben auf Relais sollten an sich auch rein, muß ich aber mangels Pins 
erst mal zurückstellen. Vielleicht finde ich später noch Pins für ein 
Schieberegister.

Zunächst wollte ich die Aufgaben im AVR so gering wie möglich halten und 
Ergebnisse an den PC senden und dann dort weiterverarbeiten. Es ist aber 
nicht ausgeschlossen, daß ich vielleicht später alles im AVR mache. Das 
wird sich später herausstellen. Aber ich suche deswegen auch jetzt schon 
nach optimalen Möglichkeiten, Zeit und Speicher verballern passiert 
sowieso von alleine. Dazu muß man sich nicht anstrengen. Wenn man aber 
später keine Resourcen mehr zur Verfügung hat und deswegen alles 
umstellen muß, ist das nicht so schön.

Gründe für die Auswahl des ATMEGA 32:

Kaum externe Hardware erforderlich.
PC Programmierung mit Ponyprog möglich.
Viel Speicher und Resourcen.
Übersichtliche Programmierung und Anlehnung an PC Syntax.
Entwicklungsbord für 14,95 verfügbar.
DIP Gehäuse für einfache Lötung auf Leiterplatte.
Programm für DS1820 steht zur Verfügung. (mußte ich nur wegen anderer 
zusätzlicher Hardware abändern)
Nachteilig war, daß die Atmel Entwicklungumgebung nicht mit Win98 läuft 
und der Assembler zunächst nur in einer alten Version zur Verfügung 
stand. Erst später stand die jetzige Assembler Version auf der Atmel 
Seite.

Jetzt geht es mir darum, die Pausenzeiten im Programm sich überlappen zu 
lassen, damit diese minimiert werden. Das, was ich will und worauf es 
ankommt, erfährt man eben erst im Laufe der Zeit und mit Eurer Hilfe.

mfg
Quehl

von Michael U. (Gast)


Lesenswert?

Hallo,

auch das aktuelle AVR-Studio läuft unter Win98SE...

Ein Minimum an Ram ist nötig (bei mir 96MB, mehr kann das alte Thinkpad 
nicht), der Simulator ist nicht nutzbar (Ram...).
Starten dauert mit ca. 10 Files natürlich etwas, wenn alles offen ist, 
kann man aber durchaus damit arbeiten. Assemblieren läuft erträglich 
schnell, PonyProg lauft auch unter Win98 ohne Probleme.

Ist mein Bastelstuben-Rechner, 233MHz P-II mobile.

Zur Lebensdauer des Flash: hatten wir letztens hier im Forum.
War irgendwas mit ca. 1 1/2 Jahre bei täglich 20x neu flashen...

Mir ist daran noch kein AVR gestorben, andererseits liegt mit Sicherheit 
einer Reserve in der Schachtel. ;)

Gruß aus Berlin
Michael


von Hannes L. (hannes)


Lesenswert?

> Jetzt geht es mir darum, die Pausenzeiten im Programm sich überlappen zu
> lassen, damit diese minimiert werden.

Das erfordert nur eine andere Denkweise beim Programmieren.

Pausen dürfen keine Rechenzeit verbraten. Kann ein Job im Moment (noch) 
nicht ausgeführt werden, weil eine Bedingung (Zeitverzögerung einhalten, 
Busy abfragen) noch nicht erfüllt ist, dann ist gefälligst zur Mainloop 
zurückzuspringen, wo vermutlich noch andere Jobs zu erledigen sind.

Ich realisiere mir meist mit einem Timer eine Art "Taktgeber", der mir 
die Jobflags entsprechend setzt. Diese Jobflags werden von der Mainloop 
geprüft und die Jobs ggf. aufgerufen. So wird z.B. alle 20ms die 
Entprellroutine aufgerufen, alle 1..2ms ein Zeichen aus dem 
Bildschirmspeicher ans LCD gesendet, usw...
Wenn ein Job die Voraussetzung für einen anderen geschaffen hat, dann 
setzt er eben dessen Jobflag. Er wird halt bei der nächsten Runde der 
Mainloop mit ausgeführt.
Muss z.B. ein längerer Impuls (definierter Länge) generiert werden, so 
wartet man nicht, bis seine Zeit abgelaufen ist, sondern setzt eine 
Timeout-Variable. Ein Job der Mainloop zählt diese (zyklisch) runter 
(falls sie nicht schon 0 ist) und schaltet beim Erreichen von 0 den 
Ausgang wieder ab. Und in der Zwischenzeit klappert die Mainloop die 
Jobflags ab und sucht sich sinnvollere Arbeit.

Oftmals rufe ich die Jobs auch nicht als Unterprogramm (rcall/call-ret) 
auf, sondern mit rjmp/jmp und mit rjmp mainloop zurück. Damit wird die 
Mainloop nach jedem Job von vorn abgearbeitet, was eine 
Prioritätsreihenfolge ergibt. Sind keine Jobs mehr offen, wird sleep 
aufgerufen. Trifft der nächste (Jobflag setzende) Interrupt ein, ehe 
alle Jobs abgearbeitet wurden, dann wird ggf. der Job mit der geringsten 
Priorität verpennt. Dieser ist dann meist die Ausgabe eines Zeichens zum 
LCD, wenn das mal ausfällt, dann stört das auch nicht weiter.

Und wenn es sein muss, kann dieses System auch mal kurzzeitig 
abgeschaltet werden, um z.B. gelegentlich ein OWI-Dewice "zu Fuß" zu 
bedienen.

Es gibt viele Möglichkeiten...

Meine neueren Programme verbringen die meiste Zeit im Sleep.

...

von Quehl (Gast)


Lesenswert?

danke, ganz interessant

wenn ich das richtig verstehe, darf ein Job nicht einen anderen Job 
aufrufen, sondern nur ein Flag dafür setzen.
Und nun übersetze ich das mal in eine höhere Geschwindigkeit.
Der Timer ist der Takt. Bei jedem Takt wird ein Interruptflag abgefragt, 
ob vielleicht ein anderer Job auszuführen ist. (Das müßte ich mir noch 
genauer ansehen, wie das zu programmieren ist) Ist kein Flag gesetzt, 
geht es in der Mainloop weiter. Ist ein Interruptflag gesetzt, wird die 
Interruptroutine (Job) ausgeführt. Ist der Job zu Ende, geht es mit Reti 
zurück zur Mainloop.
In der Interruptroutine sind dann keine weiteren Jobs auszuführen, 
Interrupt sperren und ggf. das Interruptflag des weiteren Jobs zu 
setzen. Dann wird in der Mainloop irgendwann der dazugehörige Job 
aufgerufen.

Etwas stören tut mich noch die nicht unterbrechbare Jobausführung. Wenn 
diese in der Mainloop ausgeführt wird, habe ich auch die entsprechende 
Verzögerung. Solange etwas nicht zeitkritisch ist, ist das egal. Eine 
Uhr dürfte trotzdem noch funktionieren.
Wie werden definierte Wartezeiten am besten programmiert? Ich habe für 
die Uhr den Timer1 genommen mit oc1 zur Timinganpassung. Für die 
Wartezeit wollte ich den gleichen Timer nehmen, Timer auslesen, 
Wartezeit addieren und in compare2 schreiben. Nun kann die Mainloop 
nicht unmittelbar weiterlaufen, weil die Wartezeit erst ablaufen muß. 
Wenn ich jetzt die Aufgabe überspringe, wird das recht unübersichtlich 
und fehleranfällig. Wenn die Wartezeit abgelaufen ist, woher soll der 
reti dann noch die richtige Rücksprungadresse finden.

Übrigens: Deine Schaltung mit der IR Diode und Fernbedienung fand ich 
interessant, weil ich das Ding gekauft habe und dies bisher als 
unbrauchbar eingestuft habe. Die Relais können keine 230V schalten und 
der IR Teil gibt parallel aus, so daß das in den PC nicht eingelesen 
werden kann. Das IR Teil darf nicht in der Nähe meines Fernsehers 
stehen, weil dieser bei jedem Tastendruck reagiert und keine 
Jumperadresse hat.
Kann man das Programm noch so ergänzen, daß die gedrückte Taste seriell 
an den PC ausgegeben werden kann?

mfg
Quehl

von Hannes L. (hannes)


Lesenswert?

Quehl wrote:
> danke, ganz interessant
>
> wenn ich das richtig verstehe, darf ein Job nicht einen anderen Job
> aufrufen, sondern nur ein Flag dafür setzen.

Von "Dürfen" möchte ich nicht reden, man kann auch anders programmieren. 
Ob's immer sinnvoll ist, entscheidet der Einzelfall.

Ich mache es gern so, dass die Jobs der Mainloop in Punkto 
Ausführungszeit überschaubar bleiben.

> Und nun übersetze ich das mal in eine höhere Geschwindigkeit.
> Der Timer ist der Takt. Bei jedem Takt wird ein Interruptflag abgefragt,
> ob vielleicht ein anderer Job auszuführen ist.

Nicht so hastig. Interrupt-Flags und Jobflags sind zweierlei. Die 
Jobflags sind nur Bits in einem Register (also Boolsche Variablen), das 
selbst deklariert wurde. Sie haben absolut nichts mit den Flags des SREG 
oder diversen Flags der I/O-Units zu tun. Jobflags sind selbstdefinierte 
Bit-Variablen.

> (Das müßte ich mir noch
> genauer ansehen, wie das zu programmieren ist) Ist kein Flag gesetzt,
> geht es in der Mainloop weiter. Ist ein Interruptflag gesetzt, wird die
> Interruptroutine (Job) ausgeführt. Ist der Job zu Ende, geht es mit Reti
> zurück zur Mainloop.

Nein.

Das, was ich "Jobs" nenne, wird von der Mainloop aufgerufen, wenn das 
entsprechende Jobflag gesetzt ist. Der "Job" (also dessen Routine) 
löscht das Jobflag und erledigt den Job. Dann gehts zurück zur Mainloop. 
Dabei gibt es zweierlei Varianten. Aufruf des Jobs als Unterprogramm 
(call, rcall) und Rücksprung mit ret oder Aufruf mit "(r)jmp jobname" 
und Rücksprung mit "(r)jmp mainloop". Letzteres hat den Vorteil, dass 
man die Jobs nach Priorität anordnen kann. Denn nach jedem Job beginnt 
die Mainbloop von vorn und behandelt zuerst die wichtigen, 
zeitkritischen Aufgaben. Es muss allerdings sichergestellt sein, dass 
zwischen den regelmäßig auftretenden Interrupts genügend Zeit für die 
Jobs bleibt. Da es gelegentlich auch unregelmäßig auftretende Interrupts 
(ICP, Ext Int) gibt, besteht dann die Möglichkeit, dass von einer 
solchen ISR ein Jobflag gesetzt wird, ehe alle Jobs erledigt sind. Dann 
ist Priorität hilfreich. Wenn dabei z.B. mal die Ausgabe eines Zeichens 
aus dem Buffer ins LCD verpennt wird, so ist das nicht weiter tragisch.

> In der Interruptroutine sind dann keine weiteren Jobs auszuführen,
> Interrupt sperren und ggf. das Interruptflag des weiteren Jobs zu
> setzen. Dann wird in der Mainloop irgendwann der dazugehörige Job
> aufgerufen.

Das Sperren der Interrupts geschieht automatisch beim Aufruf der ISR. 
Die Freigabe der Interrupts erfolgt durch RETI (das ist der Unterschied 
zwischen RET und RETI, RETI setzt das I-Flag im SREG). Die ISR soll so 
kurz wie möglich sein, also nur die allerwichtigsten Dinge erledigen wie 
Daten sichern. Deshalb wird in der ISR das Jobflag für den Job gesetzt, 
der die darauf folgende Arbeit macht. Somit ist die ISR schnell beendet, 
was andere Interrupts ermöglicht. Das Programm macht also mit der 
Mainloop weiter, findet ein gesetztes Jobflag und arbeitet den Job ab.

Sinnvollerweise schickt die Mainloop den Controller in den Sleep-Mode 
Idle, wenn alle Jobs abgearbeitet sind. Der nächste Interrupt weckt den 
AVR, führt die ISR aus, und landet wieder in der Mainloop, führt diese 
aus, bis alle Jobs abgearbeitet sind und geht wieder pennen.

>
> Etwas stören tut mich noch die nicht unterbrechbare Jobausführung.

Wieso ist die nicht unterbrechbar? Jeder Interrupt kann die Jobs 
unterbrechen und Jobflags setzen. Wenn man darauf achtet, dass die Jobs 
klein genug bleiben, geht es auch schnell genug zur Mainloop zurück, wo 
auf andere Jobflags reagiert werden kann.

> Wenn
> diese in der Mainloop ausgeführt wird, habe ich auch die entsprechende
> Verzögerung. Solange etwas nicht zeitkritisch ist, ist das egal. Eine
> Uhr dürfte trotzdem noch funktionieren.

Eine Uhr funktioniert bestens. Der Timer erzeugt alle 10ms einen 
Interrupt. In dieser ISR wird das Jobflag für die Tastenentprellung 
gesetzt (oder die Entprellung in der ISR erledigt, sind im günstigsten 
Fall 11 Takte), die Hundertstelsekunde hochgezählt und auf Erreichen von 
100 geprüft, bei 100 das Jobflag "Neue_Sekunde" gesetzt und die 
Hundertstelsekunde auf 0 gesetzt. Das Addieren der Sekunden, Minuten, 
Stunden, Tage, Wochen/Monate, Jahre, Jahrhunderte übernimmt dann der Job 
für Neue Sekunde. Da dieser nur einmal pro Sekunde auftritt, aber nie 
verpasst werden darf, sollte er eine hohe Priorität haben, also am 
Anfang der Mainloop stehen.

> Wie werden definierte Wartezeiten am besten programmiert? Ich habe für
> die Uhr den Timer1 genommen mit oc1 zur Timinganpassung. Für die
> Wartezeit wollte ich den gleichen Timer nehmen, Timer auslesen,
> Wartezeit addieren und in compare2 schreiben.

Es kommt darauf an, was alles getimt werden muss. Wenn ein LCD im Spiel 
ist, arbeite ich gern mit einem Buffer (inzwischen als 
"Bildschirmspeicher" organisiert, also für jede Ausgabeposition ein Byte 
im SRAM). Sämtliche Schreibzugriffe aus dem Programm schreiben nur in 
diesen Buffer. Ein Job der Mainloop mit niedrigster Priorität, aber 
häufigem Aufruf (1..2ms) schickt das jeweils nächste Zeichen aus dem 
Buffer an das LCD. Hier geht es also nur darum eine Mindestpause 
zwischen den LCD-Zugriffen einzuhalten.
Also brauche ich einen Timer, der alle 1ms oder 2ms einen Interrupt 
auslöst. In dessen ISR setze ich das Jobflag für LCD_UPDATE und zähle 
eine Variable hoch (oder runter). Diese steuert die langsameren 
Jobsynchronisationen bzw. auch die Uhr.

Z.B. Int alle 1ms,
jedes mal das LCD_Update-Flag setzen,
jedes zehnte mal die Uhr hochzählen und die Tasten entprellen,
wenn erforderlich, dann ein Jobflag für Hundertstelsekunde setzen.

Braucht man eine Wartezeit, so nimmt man statt eines Jobflags eine 
Variable, die in der Mainloop alle Hundertstelsekunde (oder bei Bedarf 
auch alle Sekunde) heruntergezählt wird, falls sie nicht schon auf 0 ist 
und dann, wenn sie zu 0 wird, die entsprechende Aktion auslöst.

Sind mehrere Termine zu verwalten, dann lohnt sich ein Scheduler, siehe 
Codesammlung. War mir bisher aber immer zu komplex und nicht 
erforderlich.

> Nun kann die Mainloop
> nicht unmittelbar weiterlaufen, weil die Wartezeit erst ablaufen muß.

Nein, die Mainloop kümmert sich um die anderen Jobs. Sie lässt halt 
diesen Job aus, der jetzt (in dieser Runde der Mainloop) noch nicht dran 
ist.

> Wenn ich jetzt die Aufgabe überspringe, wird das recht unübersichtlich
> und fehleranfällig.

Überhaupt nicht.

> Wenn die Wartezeit abgelaufen ist, woher soll der
> reti dann noch die richtige Rücksprungadresse finden.

Reti springt aus der ISR zurück. Die (Timer-) ISR synchronisiert zwar 
über die Jobflags die einzelnen Teile der Mainloop, springt aber keine 
Jobs direkt an. Sie wird immer sauber mit RETI verlassen, ehe ein von 
ihr angemeldeter Job begonnen wird.

Ich betone nochmal: Das ist eine Möglichkeit, zu programmieren, 
keinesfalls die einzig mögliche und keinesfalls die einzig richtige. Wer 
es anders macht, der soll es tun. Es ging hier nur darum, zu zeigen, 
dass die befürchteten Probleme sich bei genauer Betrachtung 
relativieren.


>
> Übrigens: Deine Schaltung mit der IR Diode und Fernbedienung fand ich
> interessant,

Falls Du mich meinst, meinst Du vermutlich den Fernbedienungsempfänger 
von Pollin, oder?
Wenn ja, da gibt es inzwischen auch andere Varianten von, nicht nur die 
für die Gartenbahntreffen.

> weil ich das Ding gekauft habe und dies bisher als
> unbrauchbar eingestuft habe. Die Relais können keine 230V schalten und
> der IR Teil gibt parallel aus, so daß das in den PC nicht eingelesen
> werden kann.

Das lässt sich ändern. Dazu müsste ein baudratentauglicher Quarz und ein 
MAX232 nachgerüstet werden, dann kann ich da auch eine serielle Ausgabe 
reinbauen. Die benötigten Pins sind frei und zugänglich. Es ist aber 
vermutlich besser, dafür eine neue Platine zu entwickeln. Sie enthält 
dann nur Tiny2313, Quarz, MAX232, den IR-Empfänger und diverse Rs und 
Cs.

> Das IR Teil darf nicht in der Nähe meines Fernsehers
> stehen, weil dieser bei jedem Tastendruck reagiert und keine
> Jumperadresse hat.

Stimmt, das Original-Programm ist sehr störanfällig.
Da der Quelltext nicht öffentliuch vorliegt, habe ich das Programm 
völlig neu geschrieben. Ich habe dabei darauf geachtet, dass der Empfang 
sehr zuverlässig wird, ein Telegramm wird nur dann akzeptiert, wenn es 
zweimal hintereinander

> Kann man das Programm noch so ergänzen, daß die gedrückte Taste seriell
> an den PC ausgegeben werden kann?

Bei oben beschriebener Hardwareerweiterung ja.

>
> mfg
> Quehl

...

von Wolfram Q. (quehl)


Lesenswert?

@Hannes

Bei Deiner Methode wird die Interruptverarbeitung aber je nach Länge des 
Mainprogramms verzögert und zwar um eine undefinierte Zeit. Gerade bei 
Timerinterrupts kann das aber zu unzulässigen Verzögerungen führen. ggf. 
auch beim A/D Interrupt.

Oben habe ich mich wohl nicht so gut ausgedrückt mit dem 
Schnellerwerden.
Darum noch mal klarer:
Bei jedem Befehl des Prozessors fragt dieser die IntEnable bits ab, 
hardwaremäßig. Das würde bei Dir den gesetzten Softwarebits im 
Hauptprogramm entsprechen. Bei gesetzten intEnable bits verzweigt das 
Programm zur Interruptroutine, hardwaremäßig. Das würde bei Dir 
bedeuten, Du verzweigst zum Jobunterprogramm, softwaremäßig. Am Ende 
kehrt die Interruptroutine zum Mainprogramm zurück. Das bedeutet bei 
Dir, Du kehrst zum Mainprogramm zurück. Du löschst das Jobbit 
softwaremäßig. Die Hardware löscht das Enablebit im Interruptprogramm.

Unterschiede:
Bei Dir kann zu jeder Zeit ein Interrupt akzeptiert werden.
In der Interruptroutine bei mir könnte das Bit freigegeben werden und 
damit wären auch weitere Interrupts akzeptierbar. Bei den 
Rückkehradressen dürfte es keine Probleme geben. Nur für die 
InterruptEnable Bits gibt es keinen Stack.
Nachteil bzw. Vorteil:
Durch die hardwaremäßige Jobabfrage bei jedem Befehl kann der Job 
schneller reagieren. Dadurch können aber mehr Verschachtelungen 
auftreten als bei der softwaremäßigen Abfrage.

Ich hoffe, Du erkennst zumindest die Ähnlichkeit zwischen Deiner Lösung 
und der Hardwarelösung. (meine Lösung will ich da nicht schreiben). 
Entscheidend für die Wahl wird wohl im Einzelfall die Reaktionszeit 
sein.
ggf. auch eine Mischung beider Verfahren, daß das aber eher 
unübersichtlich machen würde.
Hat eigentlich jeder Interrupt ein eigenes InterruptEnabel bit?

Mit der Fernbedienung ist das so, daß die keine Störungen abgibt, 
sondern daß das die gleiche ist, die zu meinem Fernseher paßt, nur in 
einem anderen Gehäuse. Darum schaltet die mit jeder Taste meinen 
Fernseher. Taste 1, 1. Programm u.s.w.
Die RS232 ist kein Problem. Hab ich einen extra Umsetzer gekauft. Quarz 
sollte auch kein Problem sein, weil noch nicht zusammengelötet, muß ich 
sehen, wo ich einen passenden herbekomme. Welcher wird denn benötigt? 
Sicherlich ist der auch von den IR Impulsen und dem Programm abhängig.


von Hannes L. (hannes)


Lesenswert?

Wolfram Quehl wrote:
> @Hannes
>
> Bei Deiner Methode wird die Interruptverarbeitung aber je nach Länge des
> Mainprogramms verzögert und zwar um eine undefinierte Zeit. Gerade bei
> Timerinterrupts kann das aber zu unzulässigen Verzögerungen führen.

Nööö. Der zeitkritische Teil der Interruptverarbeitung erfolgt ja in der 
ISR. Es wäre Unsinn, den nächsten Interrupt-Termin erst in der Mainloop 
zu vereinbaren, das mach ich ja auch in der ISR. Somit ist der nächste 
Interrupt auch dann pünktlich, wenn die restliche Arbeit, die zu diesem 
Termin dran ist, einige hundert Takte verzögert abgearbeitet wird.

> ggf.
> auch beim A/D Interrupt.

Es kommt darauf an, was der messen soll. Das Digitalisieren von NF mit 
hoher Samplerate sollte wohl für den AVR die Ausnahme sein, bei den 
meisten Anwendungen würde es nichtmal stören, wenn eine Abtastung 
verpennt wird. Die nächste Abtastung ist halt aktueller...

>
> Oben habe ich mich wohl nicht so gut ausgedrückt mit dem
> Schnellerwerden.
> Darum noch mal klarer:
> Bei jedem Befehl des Prozessors fragt dieser die IntEnable bits ab,
> hardwaremäßig.

ja, und falls ein (zugelassenes) Interrupt-Ereignis eingetroffen ist, 
dann wird der angefangene Befehl abgearbeitet, der PC auf Stack gelegt 
und zur dem interrupt entsprechenden Adresse verzweigt. Dort steht dann 
der Sprung zur ISR.

> Das würde bei Dir den gesetzten Softwarebits im
> Hauptprogramm entsprechen.

Nööö. Das passiert bei mit trotzdem. Nur beschränkt sich meine ISR auf 
die zeitkritischen Dinge und verlagert den Rest in einen Job der 
Mainloop. Dadurch ist meine ISR schneller fertig und macht Platz für 
andere Interrupts, die damit schneller reagieren können.

> Bei gesetzten intEnable bits verzweigt das
> Programm zur Interruptroutine, hardwaremäßig.

Ja.

> Das würde bei Dir
> bedeuten, Du verzweigst zum Jobunterprogramm, softwaremäßig.

Nööö.

> Am Ende
> kehrt die Interruptroutine zum Mainprogramm zurück. Das bedeutet bei
> Dir, Du kehrst zum Mainprogramm zurück. Du löschst das Jobbit
> softwaremäßig. Die Hardware löscht das Enablebit im Interruptprogramm.

Nööö.
Ich glaube, wir reden völlig aneinander vorbei.

>
> Unterschiede:
> Bei Dir kann zu jeder Zeit ein Interrupt akzeptiert werden.

Nein, während eine ISR läuft, muss der nächste auftretende Interrupt 
warten bis die laufende ISR fertig ist.

> In der Interruptroutine bei mir könnte das Bit freigegeben werden und
> damit wären auch weitere Interrupts akzeptierbar.

Dieses Vorgehen ist sehr gefährlich. Wenn man da die Übersicht verliert 
und derselbe Interrupt erneut auftritt, bevor seine ISR fertig ist, dann 
hat man ganz schnell Stacksalat. Ich habe diese Methode zwar auch schon 
verwendet, aber da gab es nur zwei Interrupts und es war (durch Timing) 
sichergestellt, dass derselbe Interrupt nicht dazwischenfunken kann.

> Bei den
> Rückkehradressen dürfte es keine Probleme geben.

Aber sicher, der Stack ist endlich.

> Nur für die
> InterruptEnable Bits gibt es keinen Stack.

Muss ich das jetzt verstehen?

> Nachteil bzw. Vorteil:
> Durch die hardwaremäßige Jobabfrage bei jedem Befehl kann der Job
> schneller reagieren. Dadurch können aber mehr Verschachtelungen
> auftreten als bei der softwaremäßigen Abfrage.

Ich frage die "Ereignisse" (die Interrupt-Flags) nicht per Software ab. 
Jobflags sind selbstdeklarierte Bool'sche Variablen. Sie haben nix und 
garnix mir den System-Flags der Interruptlogik zu tun.

>
> Ich hoffe, Du erkennst zumindest die Ähnlichkeit zwischen Deiner Lösung
> und der Hardwarelösung. (meine Lösung will ich da nicht schreiben).
> Entscheidend für die Wahl wird wohl im Einzelfall die Reaktionszeit
> sein.

Nö, ich vermute stark, dass wir uns missverstehen.

> ggf. auch eine Mischung beider Verfahren, daß das aber eher
> unübersichtlich machen würde.

Meine Art zu Programmieren ist lediglich eine Optimierung. Ich nutze die 
hardwaremäßige Interruptlogik so, wie es der Hersteller vorgesehen hat. 
Ich mache lediglich die ISRs schneller, indem ich die Teile, die nicht 
zeitkritisch sind, in "Jobs der Mainloop" auslagere und dann erledige, 
wenn Zeit dazu ist.

> Hat eigentlich jeder Interrupt ein eigenes InterruptEnabel bit?

Das sollte ein Blick ins Datenblatt klären können, oder??

>
> Mit der Fernbedienung ist das so, daß die keine Störungen abgibt,
> sondern daß das die gleiche ist, die zu meinem Fernseher paßt, nur in
> einem anderen Gehäuse. Darum schaltet die mit jeder Taste meinen
> Fernseher. Taste 1, 1. Programm u.s.w.

Nunja, es ist ziemlich sinnfrei, zwei Fernbedienungen mit identischer 
Codierung im selben Raum für unterschiedliche Zwecke nutzen zu wollen.

> Die RS232 ist kein Problem. Hab ich einen extra Umsetzer gekauft. Quarz
> sollte auch kein Problem sein, weil noch nicht zusammengelötet, muß ich
> sehen, wo ich einen passenden herbekomme. Welcher wird denn benötigt?
> Sicherlich ist der auch von den IR Impulsen und dem Programm abhängig.

Nein, ist er nicht. Das gesammte Programm muss zwar völlig neu geTIMEt 
werden, weil der Controllertakt ja verändert wird, aber die Frequenz 
wird von der UART-Einheit bestimmt. Der Quarz sollte also 
baudratentauglich sein.

...

von Michael U. (Gast)


Lesenswert?

Hallo,

@Hannes Lux:

ich benutze eigentlich die geliche Version beim Programmaufbau, Du hast 
es allerdings noch weiter perfektioniert und ich werde davon mit 
Sicherheit nächstes Mal was einbauen. :)

Das Problem, welches offenbar gesehen wird, wo es garnicht ist, scheint 
mir oft die Fehleinschätzung zu sein, was eigentlich wann und wie oft 
passieren muß. Wenn man z.B. ein Thermometer mit Anzeige baut, macht es 
wenig Sinn, den ADC 1000x in der Sekunde auslesen zu wollen, weil man 
sinnvoll sowieso nur 2-3 Werte in der Sekunde sinnvoll auf dem Display 
ablesen kann.

Wenn die Uhr die Anzeige um eine 10tel Sekunde zu spät umschaltet, nimmt 
man das nicht wahr.
Wenn die main für einen Durchlauf mehr als 100ms braucht, hat man wohl 
ganz andere Probleme mit dem Konzept und der benutzten CPU.

Bei ADC, Counter-IRQ usw. mache ich es in der ISR oft so, daß ich deren 
Inhalt garnicht durchlauf, wenn das vorige Ergebnis noch nicht 
bearbeitet wurde. Wenn also das Jobflag in der Main noch nicht gelöscht 
wurde, wird die ISR beim Aufruf gelich wieder beendet.
Sonst werden die Werte in feste Register oder Ramplätze geschrieben und 
sind damit in jedem Fall außerhalb gültig.
Eigentliche in simples Semaphoren-Handling, ISR holt die neuen Werte nur 
dann, wenn Jobflag gelöscht und setzt es dann.

Beim kritischen Sachen kann man dann z.B. problemlos ein Error-Flag für 
die Main setzen, wenn der ISR-Aufruf erfolgte, bevor der Job bearbeitet 
wurde.

Ich habe auch schon die ISR-Bearbeitung absichtlich warten lassen, also 
das Jobflag des ADC nicht in Main->ADC bearbeiten gelöscht, wenn der 
Ablauf sowieso sicherstellt, daß die ADC-Werte innerhal eines 
Main-Durchlaufs von z.B. maximal 10ms bearbeitet werden, ich den 
nächsten ADC-Wert aber eigentlich nur alle 10s wieder brauche, dann hat 
das Löschen die Sekundenroutine erledigt.

Das ist eigentlich das zweite Problem, das übersehen wird: der AVR kann 
nur genau in einerm Programmteil zu einer Zeit sein und das bearbeiten. 
Die ISR muß sowieso mit dem vorgefundenen Zustand verlassen werden, nur 
Jobflag und zugehörige Wertänderung werden angefasst.

Man weiß also eigentlich zu jeder Zeit beim Programmieren, was wo ist 
und was in jedem Fall als nächstes passiert. Der AVR würfelt nicht, es 
sei denn, der Programmierer baut sich einen Würfel ein. ;)

Gruß aus Berlin
Michael

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.