Forum: Mikrocontroller und Digitale Elektronik Interrupts vs. Endlosschleife


von mr.chip (Gast)


Lesenswert?

Hallo

Ich bastel derzeit an meinem ersten Roboter, und abgesehen von den - in
anderen Threads besprochenen - Problemen läuft es auch schon ganz gut.
Bisher habe ich lediglich eine minimale Software zum Testen der
einzelnen Komponenten auf dem Controller, möchte aber nun meine
definitive Software entwickeln.

Zum Roboter: Angetrieben wird der Roboter durch 2 Räder auf beiden
Seiten, die je einzeln auf einem gehackten Servo montiert sind. Die
Räder sind gelocht, mit einer Lichtschranke wird die zurückgelegte
Strecke von jedem Rad erfasst. Gesteuert wird das ganze von einem
Atmega 8, der zurzeit auf 4 MHz läuft, programmiert in Assembler. Als
Stromversorgung dient ein 4.8 V Akku aus vier Zellen. Vielleicht kommt
noch eine Kollisionserkennung, ein Display, ein Linienfolger oder eine
kleine Tastatur hinzu.

Nun überlege ich mir, ob die ganze Software in einer grossen Schleife
ablaufen soll, d.h. es wird in jedem Schleifendurchlauf der Wert der
Lichtschranken abgefragt und geschaut, ob die Steuersignale für die
Servos auf high bzw. low gesetzt werden müssen (anhand eines Timers
oder Berechnung der Programm-Laufzeit).

Oder soll ich es mit Interrupts machen? Die Lichtschranken würden dann
bei jedem Loch ein externes Interrupt auslösen. Die Servos würden über
ein Timer-Interrupt gesteuert etc.

Sehe ich es richtig, dass eine Kombination aus Schleife und
Interrupts ein bisschen gefährlich ist?

Gruss

Michael

von Rahul (Gast)


Lesenswert?

Prinzipiell kannst du das gesamte Programm mit Interrupts erlegen, wobei
die länge der Interrupt Service Routinen nicht zu lang sein sollte.
Servos kann man wunderbar per OutputCapture ansteuern.
Die Drehzahl(Periodendauer)/Geschwindigkeit kann man per InputCapture
messen.
Die beiden Sachen und eine Tastatur-Entprellung kann man mit nur einem
Timer erledigen, wobei der Timer bei jedem Überlauf ein Flag setzt, das
in der Hauptschleife abgefragt wird. Jedesmal, wenn das Flag gesetzt
ist, vergleicht man den aktuellen Zustand der Eingänge mit dem
vorhherigen, und reagiert entsprechend (Entprellroutine von Peter
Dnnegger in der Codesammlung).

Gefährlich wird es nur, wenn du dir nicht vorher darüber klar bist, was
so alles in deinem Programm passieren kann...

von Hannes L. (hannes)


Lesenswert?

Ich strikturiere meine AVR-ASM-Programme meist so:

- Reset:
  Initialisierung aller I/Os und genutzter HW-Features
  (wird nur 1 mal durchlaufen und "fällt" in die Hauptschleife)

- Hauptschleife (Mainloop):
  Jobs erledigen, deren Steuerflags gesetzt waren, wenn alle Jobs
  abgearbeitet sind, dann in Sleep-Mode fallen
  (nach jedem Interrupt erfolgt ein neuer Durchlauf, bis alle
  Job-Flags abgearbeitet und gelöscht sind)

- Timer-Interrupt (meist alle 1..20ms):
  Tastenentprellung, diverse Softwaretimer (Uhr, Blinker...) handeln,
  Job-Flags setzen, falls es etwas zu tun gibt, ISR sehr kurz halten
  (wird vom Timer in regelmäßigen Zeitabständen ausgelöst)

- Weitere Timer-Interrupts (OC, ICP, OVF...) diverse zeitabhängige
  Jobs durch setzen der Jobflags "anmelden" (erledigt Mainloop)
  (wird von Timern ausgelöst)

- ADC-Interrupt:
  Messwerte einlesen und sichern, nächste Messquelle einschalten,
  evtl. Jobflag setzen
  (wird vom ADC ausgelöst, wenn er fertig ist)

- andere Interrupts (RX, Ext, Pinchange...):
  Daten sichern, falls erforderlich, Jobflag für Mainloop setzen,
  dadurch ISR sehr kurz halten
  (ausgelöst durch diverse Hardware)

- Jobs:
  Routinen, die bestimmte Aufgaben erledigen, z.B.:
  - ein Zeichen aus dem Ringbuffer an UART senden
  - ein Zeichen aus dem Ringbuffer an das LCD ausgeben
  - eine Berechnung ausführen
  - ...
  (wird von Mainloop aufgerufen, falls Jobflag gesetzt war, löscht
  Jobflag und erledigt Job)


Man richtet sich ein (oder mehrere) Register ein, in denen jedes Bit
für einen Job steht (Jobflags). Erkennt irgendein Programmteil (meist
ein Interrupt), dass ein bestimmter Job ausgeführt werden muss, dann
wird nur das Jobflag gesetzt und evtl. die zur Ausführung
erforderlichen Daten (Timerstand, ADC-Wert, Bitmuster am Port...)
eingelesen und bereitgestellt. Die Mainloop ruft dann den betreffenden
Job auf.

...

von mr.chip (Gast)


Lesenswert?

Hallo

@Hannes: Diese Methode hört sich sehr interessant an! Allerdings ist im
Vorfeld sicher eine präzise Planung des Programms notwendig. Dann dürfte
es aber auch problemlos möglich sein, weiter Komponenten zu integrieren,
was bei einer reinen mainloop und bei einer unsorgfältigen
Interruptprogrammierung schwieriger sein dürfte.

Gruss

Michael

von hans dieter (Gast)


Lesenswert?

also zwei kommilitonen und ich haben einen ähnlichen robo mit einem hc08
gebaut (etwas langsamer bei 4 MHz).
Wir haben da die Rad-Sensoren per input-capture gezählt (du könntest
auch einen flanken-getriggerten interrupt nehmen) und dann noch einer
gewissen zeit (durch einen timer-überlauf bestimmt) die werte
verglichen mit einem soll-wert, dadurch konnten wir die geschwindigkeit
regeln und das ding auch ordentlich gerade aus fahren lassen.
Die motoren hingen über einem motor-treiber an je einem pwm-ausgang.
Das ging eigentlich ganz gut (10cm abweichung auf 15m) besonders wenn
man bedenkt, dass das linke getriebe schon so ausgeklappert war, dass
der robo mit gleicher einstellung aber ohne regelung nicht am anderen
zimmer-ende angekommen, sondern an die seitliche wand gefahren ist.
den rest (tasten abfragen) haben wir dann in einer endlos-schleife
gemacht.
prinzipiell könnte man hier auch einen interrupt nehmen und die taster
über dioden an einen interrupt anschließen und dann wenn es soweit ist,
einfach den zustand abfragen

von Hannes L. (hannes)


Lesenswert?

> Diese Methode hört sich sehr interessant an! Allerdings ist im
> Vorfeld sicher eine präzise Planung des Programms notwendig.

Eben nicht...

Einen Zeittakt (Pulsschlag) braucht man eigentlich in fast jedem
Programm. Also wird erstmal (neben Reset-Sequenz und leerer Mainloop
mit Sleep) ein Timer-Interrupt programmiert.

Dessen Intervall hängt davon ab, was er steuern soll. Für eine Uhr
bietet sich 10ms an, damit kann man auch gut Taster entprellen. Ist
LCD-Ausgabe dabei, nehme ich gern 1ms, setze bei jedem Aufruf das
Jobflag für die Zeichenausgabe, erhöhe bei jedem 10ten Aufruf
(Software-Vorteiler) die Uhr (Hundertstelsekunde) und entprelle
Taster.

Willst du deine Räder (Lichtschranken) abfragen, dann ermittle erstmal
die erwartete Frequenz. Es ist durchaus möglich, das mit Polling zu
erledigen. Ich lese z.B. mit Polling (100kHz) mehrere Kanalimpulse von
RC-Fernsteuerungen ein.

Aber zurück zum Programm-Grundgerüst...
Reset-Sequenz, leere Mainloop mit Sleep, Timer-Interrupt mit fester
(geeigneter) Frequenz... Ein oberes Register für Jobflags reservieren
(heißt bei mir oft "flags", könnte aber auch "jobs" oder "tasks"
heißen).

Dann für jede Aufgabe, die der MC erledigen soll, analysieren, wodurch
der "Auftrag" ausgelöst werden soll (Interrupt, Timeout...) und
Routine schreiben, was gemacht werden muss. Und natürlich ein
zugehöriges Jobflag einrichten (mit Abfrage und bedingtem Sprung in
Mainloop).

Und dabei immer darauf achten, dass nirgends Warteschleifen
erforderlich werden. Das Programm so strukturieren, dass sofort zur
Mainloop gesprungen wird, wenn ein Job jetzt nicht erledigt werden
kann, denn es gibt sicherlich genug Anderes zu tun.

Und so kann man eine Aufgabe nach der anderen hinzufügen...


> Dann dürfte
> es aber auch problemlos möglich sein, weiter Komponenten zu
> integrieren,

Das ist ja auch Zweck der Übung. Und da der Timer den Takt angibt, wird
die meißte Zeit in der Mainloop gepennt. Kommt etwas Arbeit dazu
(weitere Jobs), so bleibt das Timing der bereits funktionierenden
Programmteile erhalten, es reduziert sich lediglich die Sleep-Zeit.

In den ISRs ist natürlich das SREG zu sichern und es sind möglichst
einige Register nur für ISRs zu reservieren, das macht die ISRs etwas
schneller, so dass sie sich nicht gegenseitig behindern.

...

von mr.chip (Gast)


Lesenswert?

Hallo

Ähm...der Begriff ISR sagt mir jetzt nichts. Routinen, welche die
Interrupts behandeln? ;-)

Noch zu den Rädern: Es hat 8 Löcher in jedem Rad für die Lichtschranke,
nur damit man sich ca vorstellen kann, wie gross der Aufwand ist. Die
Räder haben einen Radius von 3 cm.

Gruss

Michael

von Rahul (Gast)


Lesenswert?

_I_nterrupt _S_ervice _R_outine, also der Programmteil, den der
Controller bei Auftreten eines Interrupts anspringt, sofern dieser
Interrupt freiggeben ist.

von Hannes L. (hannes)


Lesenswert?

> Es hat 8 Löcher in jedem Rad ...

Ein geknacktes Servo schafft kaum mehr als 1..2 Umdrehungen pro
Sekunde, das macht maximal 16Hz. Somit kann man die Lichtschranken ohne
Weiteres zusammen mit eventuellen Bedienungstasten und Endschaltern auf
einen Port legen und mittels Entprellung nach Peter Dannegger im
10ms-Takt (100Hz) einlesen. Die Auswertung (Zählung der Wegstrecke...)
kann dann anhand der Tastenflags (tfl bzw. key_press) als Job der
Mainloop erfolgen (auch Tastenflags der Entprellung können als Jobflags
genutzt werden).
Der Haupttimer müsste also in deinem Fall mit 100Hz (10ms, 10000 Takte
bei 1MHz Prozessortakt) laufen, da hast du zwischen den Interrupts alle
Zeit der Welt...

Gleichzeitig sparst du dir durch die Entprellung eine
Hardware-Entprellung. Die Routine entprellt 8 Eingänge gleichzeitig.

...

...

von mr. chip (Gast)


Lesenswert?

Hallo

Ich lasse also den Haupttimer (Welchen am optimalsten?) alle 10ms ein
Interrupt (Da habe ich auch wieder grössere Auswahl - welches würdet
ihr nehmen?) auslösen, dieses setzt ein Job-Flag für die
Tastenauswertung. Wenn dieses Flag gesetzt ist, wird im nächsten
Mainloop-Durchlauf der Tasten-Port eingelesen und je nach deren Zustand
weitere Jobflags gesetzt?

Alle 20 ms löse ich dann noch ein Flag aus, dass die Steuerleitung der
Servos auf high setzt. Dieses muss ich 1-2 ms später wieder runter
nehmen. Da sehe ich jetzt einen Knackpunkt. Ich könnte natürlich
mittels Schleife ein Delay erzeugen, aber dann hängt der Controller 2
ms. Ich könnte aber auch einen zweiten Timer mit seinem Interrupt
benützen. Welchen Timer und welchen dazugehörigen Interrupt würdet ihr
dazu verwenden?

Gruss

Michael

PS: Wegen der Frage, welche Timer und Interrupts ich verwenden soll:
Ich bin nicht etwa zu faul, ins Datenblatt zu schauen, aber mir fehlt
jede Erfahrung, was man mit welchem Timer machen könnte, das ich später
evtl. noch brauche und daher lieber einen anderen 'verbrate'...

von Hannes L. (hannes)


Lesenswert?

Die Tastenentprellung ist so kurz und schnell, die kann in der ISR
ausgeführt werden.
Welchen Timer du nimmst, hängt davon ab, welche anderen Timer-Features
(PWM etc.) du benötigst. Nimm den, auf den du am leichtesten verzichten
kannst.

Schau dir mal das hier an, das erzeugt auch Servoimpulse, ist aber kein
Roboter:
http://www.hanneslux.de/avr/mobau/7ksend/7ksend02.html
Es wird dir aber vermutlich helfen, das von mir vorgeschlagene Konzept
zu verstehen.

Die dort enthaltene Tastenentprellung ist aber nicht komplett, eine
komplette findest du hier:
http://www.hanneslux.de/avr/zuenduhr/index.html
speziell im Quelltext der Zünduhr.

...

von mr. chip (Gast)


Lesenswert?

Hallo nochmals

Zwei Fragen kommen mir gerade noch in den Sinn:

1. Welche Register würdet ihr für die Flags nehmen? Solche aus R0-R15
oder eher diejenigen ab R16?

2. Wie kann ich am schnellsten ein Flag auslesen und entsprechend
Verzweigen? Also mir kommt da nur gerade ein ANDI 0x00000001 und dann
ein BRNE in Frage. Dann verliere ich aber den Inhalt meines Registers
bzw. muss es vorher sichern. Nicht gerade effizient.
Oder ich könnte die Bits jeweils nach rechts schieben und dann das
Carry-Flag abfragen.

Gruss

Michael

von Hartmut Gröger (Gast)


Lesenswert?

Hi

SBRC oder SBRS und danch ein (r)jmp oder (r)call lässt das Register
unbeschädigt.

MfG HG

von crazy horse (Gast)


Lesenswert?

Für Einzelbitoperationen besser die unteren Register nehmen. Nicht, weil
sie dafür besser geeignet sind, sondern weil die erweiterten Funktionen
der oberen Register dafür nicht gebraucht werden.
Entweder arbeitest du mit dem T-flag
bst Rr, b   //lädt Flag nach T und kann dann weiterverzweigt werden
oder direkt mit
sbrc/sbrs Rr, b

von Hannes L. (hannes)


Lesenswert?

1. Ein oberes Register (r16..) wegen Zugriff mit Konstanten.

2. Schau dir die Mainloop meiner Programme (obige Links) an.

Es gibt den Befehl sbrc/sbrs, der fragt exakt ein Bit ab und
überspringt den nächsten Befehl (oder eben nicht).

Beispiel:

mainloop:
 sbrc flags,flag01   ;überspringe, wenn Bit "flag01" 0 ist
 rjmp job01          ;springe zur Jobroutine...
 sbrc flags,flag02   ;überspringe, wenn Bit "flag02" 0 ist
 rjmp job02          ;springe zur Jobroutine...
 sleep               ;geh pennen
 rjmp mainloop       ;nochmal...

flag01:   ;erste Jobroutine
 cbr flags,1<<flag01 ;Jobflag löschen
 ;...                ;mache deine Arbeit
 rjmp mainloop       ;fertig...

Das geht auch mit rcall/ret, rjmp hat aber den Vorteil, dass die
Mainloop solange abgearbeitet wird, bis alle Jobs erledigt sind. Mit
der Reihenfolge in der Mainloop kann man Prioritäten setzen, die oberen
Einträge werden bevorzugt behandelt, die unteren erst, wenn alle
darüberliegenden fertig sind. Manchmal ist es auch sinnvoll, einen oder
mehrere Jobs mit rcall/ret anzuspringen/zurückzuspringen, nämlich dann,
wenn die darunterliegenden Jobs auch eine Chance bekommen sollen. Es
kommt dabei immer auf die Situation an.

...

von Hannes L. (hannes)


Lesenswert?

> Für Einzelbitoperationen besser die unteren Register nehmen.

Hmmm...
SBR/CBR geht aber nur mit oberen Registern... - Oder habe ich jetzt was
verpasst?

Bit- & Bytebruch...
...HanneS...

von crazy horse (Gast)


Lesenswert?

sbr/cbr geht nur mit den oberen Registern, da hast du recht.
Und wenn du mal den OP-code von sbr7ori bzw cbr/andi vergleichst, fällt
dir vielleicht was auf :-).
Die Rede war von sbrc/sbrs!

von Hannes L. (hannes)


Lesenswert?

> OP-code von sbr7ori bzw cbr/andi

Ja, das ist mir klar, Gleiches gilt auch für die Befehle zur
SREG-Manipulation. Man kann einunddasselbe Ding unterschiedlich nennen.
;-)

Die Bits im Register "flags" müssen ja nicht nur ausgewertet werden,
sondern auch gesetzt und gelöscht. Das Setzen geschieht meinst in einer
ISR, da muss es schnell gehen, deshalb bevorzuge ich obere Register für
diesen Zweck. Ansonsten gehe ich mit den oberen Registern auch recht
geizig um.

Gruß...
...HanneS...

von crazy horse (Gast)


Lesenswert?

Nu sei mal nicht zu luxig, äh geizig.
Das kostet dich einen einzigen Takt, unter der Voraussetzung, dass das
sreg sowieso gesichert ist:
set  //T=1
bld bit_adresse

wenns auf den einen Takt ankommt, hast du den falschen MC gewählt :)

von Hannes L. (hannes)


Lesenswert?

Gut, danke für den Rat.

Beim T-Bit bin ich nämlich noch nicht richtig angekommen, nutze ich
bisher sogut wie garnicht, gerade mal in Peters Entprellroutine für
eine Taste...

SREG sichere ich inzwischen grundsätzlich, wenn es erforderlich ist.

Der AVR ist schon der richtige Controller für mich, meine Projekte sind
recht klein und überschaubar. Und es ist nicht kommerziell, sondern nur
zum Hobby.

Gruß...
...HanneS...

von crazy horse (Gast)


Lesenswert?

"SREG sichere ich inzwischen grundsätzlich, wenn es erforderlich
ist."
Der ist gut!
Was bedeutet das eigentlich?? :-)

von Hannes L. (hannes)


Lesenswert?

Das bedeutet, dass ich darauf verzichte, wenn die gesamte Arbeit in den
ISRs gemacht wird (manchmal erlaubt das Timing das) und in der Mainloop
nur gepennt wird. Dann sind der Mainloop die SREG-Flags egal, dann
sichere ich sie auch nicht. Meist kommentiere ich dann aber die Befehle
nur aus.
Beispiel: Software-PWM mit mehreren Kanälen, da geschieht alles (PWM
generieren, ADC auslesen) im Timer-Int.

SREG-Sicherung hatte ich ganz am Anfang "grundsätzlich" vergessen und
erst durch dieses Forum bemerkt. Deshalb das "inzwischen
grundsätzlich".  8-)

...

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.