Forum: Compiler & IDEs Grundsatzfrage zu mehreren Interrupts im Programm


von Uboot- S. (uboot-stocki)


Lesenswert?

Hi,

ich plane im Moment ein GCC-Programm und treffe dabei auf folgendes 
Problem:

Mittels eines Timer-Interrupts wird eine Uhr "angetrieben". Ich habe ca. 
20 Interrupts pro Sekunde. Dies impliziert auch, dass kein Interrupt 
ausfallen darf, sonst geht die Uhr nach.

Wenn ich nun eine BIDIREKTIONALE serielle Kommunikation aufbauen muss, 
benötige ich ja den uart-interrupt.

Wie macht man das? Wie kann man Kommandos über die serielle 
Schnittstelle in das Programm eingeben während die Uhr läuft? Es tritt 
ja sicher der Fall ein, dass ein Interrupt den anderen "verhindert". 
Damit würde aber die Uhr nach-gehen oder es würde uart-Zeichen verloren 
gehen?

Wer kann den Knoten lösen ?

Gibt es irgendwo ein Beispielprogramm ?

Gruß

Andreas

von Peter D. (peda)


Lesenswert?

Andreas St. wrote:

> Es tritt
> ja sicher der Fall ein, dass ein Interrupt den anderen "verhindert".

Woher weißt Du das?

Nicht vermuten, sondern ausrechnen!

Bei 20MHz kommt ein 20Hz Timerinterupt alle 1.000.000 Zyklen.

Wenn also beide Interrupts zusammen länger als 1.000.000 Zyklen dauern, 
kannst Du einen verlieren.

Wenn Du allerdings derart riesenlange Interrupts bastelst, ist irgendwas 
grundsätzlich falsch.

Interrupts sollten möglichst nicht länger als 100..1000 Zyklen sein.


Peter

von AVRFan (Gast)


Lesenswert?

>Es tritt
>ja sicher der Fall ein, dass ein Interrupt den anderen "verhindert".

Das passiert nicht, wenn die Regel eingehalten wird, dass alle 
Interrupts und alle Critical Sections in Deinem Programm grundsätzlich 
so kurz wie möglich zu halten sind.

Tritt eine Interruptbedingung "B" auf, während gerade ein anderer 
Interrupt "A" bearbeitet wird oder sich das Programm zufällig in einer 
Critical Section aufhält, ist das kein Problem: Das zu "B" gehörige 
Interrupt-Flag wird automatisch von der Hardware gesetzt, und der 
"B"-Interrupthandler angesprungen, sobald das möglich ist (sobald das 
I-Bit im Statusregister wieder gesetzt wird, z. B. durch das "reti" am 
Ende des "A"-Interrupthandelers).  In einem solchen Fall spricht man von 
"pending interrupt" - der Interrupt steht an und wartet, bis er 
gehandelt werden kann.

Würde Dein Progamm zu lange in einer Interrupt-Behandlung oder in einer 
Critical Setion verweilen, könnten andere Interrupts "verloren gehen". 
Ein schon gesetztes Pending-Flag würde dann nochmals gesetzt, aber der 
Handler nur einmal abgearbeitet.

von Uboot- S. (uboot-stocki)


Lesenswert?

Hi,

> Das zu "B" gehörige
> Interrupt-Flag wird automatisch von der Hardware gesetzt, und der
> "B"-Interrupthandler angesprungen, sobald das möglich ist (sobald das
> I-Bit im Statusregister wieder gesetzt wird, z. B. durch das "reti" am
> Ende des "A"-Interrupthandelers).  In einem solchen Fall spricht man von
> "pending interrupt" - der Interrupt steht an und wartet, bis er
> gehandelt werden kann.

OK - das löst das Problem wenn ich beim Start der Interupt-Routine A das 
I-Bit lösche und am Ende wieder setze.

Wenn während der Bearbeitungszeit von Routine A die Bedingung für B 
eintrat (z.B. Ein Zeichen an den Controller gesandt werden soll) Beginnt 
sofort die Bearbeitung der Routine B (UART).

Bitte verifiziert folgende Überlegung:

Bei 9600 Baud und kein Stop, Paritiy oder sonstwas BIT dauert ein BIT 
60s/(9600*8)= 781us. Ich muss also sicherstellen, dass meine 
Interrupt-Routine A innerhalb dieser Zeit fertig wird und ich in der 
Routine B mit dem Lesen des BITs begonnen habe sonst ist es weg und 
damit kann das Zeichen nicht erkannt werden.

Stimmt diese ÜBerlegung ?

Gruß

Andreas

von Roland P. (pram)


Lesenswert?

Ein UART-Interrupt wird erst ausgeführt wenn das Zeichen komplett 
empfangen ist. Das sind je nach start/stopbit Konfiguration ca. 10 bit.

Gruß
Roland

von Uboot- S. (uboot-stocki)


Lesenswert?

Roland Praml wrote:
> Ein UART-Interrupt wird erst ausgeführt wenn das Zeichen komplett
> empfangen ist. Das sind je nach start/stopbit Konfiguration ca. 10 bit.
>
OK? - Wie lange steht das Zeichen dann zum Lesen zur Verfügung ? Bis zum 
nächsten Interrupt ?

Gruß

Andreas

von Karl H. (kbuchegg)


Lesenswert?

Andreas St. wrote:
> Roland Praml wrote:
>> Ein UART-Interrupt wird erst ausgeführt wenn das Zeichen komplett
>> empfangen ist. Das sind je nach start/stopbit Konfiguration ca. 10 bit.
>>
> OK? - Wie lange steht das Zeichen dann zum Lesen zur Verfügung ? Bis zum
> nächsten Interrupt ?

Bis das nächste Byte komplett empfangen wurde.
AVR haben einen 1 Byte breiten Buffer für den UART Empfang.

von Karl H. (kbuchegg)


Lesenswert?

> OK - das löst das Problem wenn ich beim Start der Interupt-Routine A das
> I-Bit lösche und am Ende wieder setze.

Du brauchst dich darum nicht kümmern.
Der AVR handahbt das Interrupt sperren und freigeben selbsttätig.
Beim Eintritt in eine Interrupt Routine werden Interrupts gesperrt
und beim abschliessenden return werden sie wieder freigegeben.

von Karl H. (kbuchegg)


Lesenswert?

Andreas St. wrote:
> Bei 9600 Baud und kein Stop, Paritiy oder sonstwas BIT dauert ein BIT
> 60s/(9600*8)= 781us.

Wo kommen die 60 her?
1 Baud = 1 Zeichen pro 1 Sekunde

von Uboot- S. (uboot-stocki)


Lesenswert?

Upps,

Karl heinz Buchegger wrote:
>
> Wo kommen die 60 her?
> 1 Baud = 1 Zeichen pro 1 Sekunde

Du hast recht! Daher kam auch mein Einheitenproblem .... So ists 
richtig:

1/ (9600 * 8 [bit/s]) = 13us

Nach der Erklärung mit dem Puffer bleibt mir aber 1 / 9600 = 104us Zeit? 
Stimmt das jetzt ?

Gruß und nochmals Danke !

Andreas

von Andreas K. (a-k)


Lesenswert?

Karl heinz Buchegger wrote:

> Bis das nächste Byte komplett empfangen wurde.
> AVR haben einen 1 Byte breiten Buffer für den UART Empfang.

Alle halbwegs aktuellen AVRs (ab ca. Mega8/16) besitzen zusätzlich zum 
Schieberegister 2 Empfangspuffer. Das bedeutet, dass erst dann Daten 
verloren gehen, wenn insgesamt 3 Bytes vollständig empfangen werden ohne 
dass eines davon abgeholt wird.

Man muss sich schon recht bös anstellen, um bei moderaten Bitraten dabei 
Daten zu verlieren. Denn bei 9600bps müssen dazu die Interrupts für ca. 
3ms blockiert werden.

von Andreas K. (a-k)


Lesenswert?

Andreas St. wrote:

> Nach der Erklärung mit dem Puffer bleibt mir aber 1 / 9600 = 104us Zeit?
> Stimmt das jetzt ?

Nein. Dich interessieren hier Bytes, nicht Bits. Ein Byte wird seriell 
mit Start- und Stopbit in 10 Bits codiert, d.h. im Empfangspuffer der 
UART können die Bytes nicht schneller als alle 10*104µs=1040µs 
eintrudeln.

von AVRFan (Gast)


Lesenswert?

>OK - das löst das Problem wenn ich beim Start der Interupt-Routine A das
>I-Bit lösche und am Ende wieder setze.

Gelöscht wird das I-Bit im SREG automatisch durch die Interrupt-Logik 
des Controllers, in dem Moment, wo ein Interrupt gehandelt wird.  Das 
Setzen des I-Bits obliegt jedoch dem Programmierer, üblicherweise 
geschieht es mit "reti" am Ende des Interupthandlers.

>Wenn während der Bearbeitungszeit von Routine A die Bedingung für B
>eintrat (z.B. Ein Zeichen an den Controller gesandt werden soll) Beginnt
>sofort die Bearbeitung der Routine B (UART).

Nein, eben nicht sofort, sondern erst, wenn das Programm aus Routine A 
ins Hauptprogramm zurückgekehrt ist.  Ist dann wegen des "reti" am Ende 
der Routine A das I-Bit wieder gesetzt, wird im Hauptprogramm noch genau 
eine Instruktion abgearbeitet und DANN der über das entsprechende Flag 
"vorgemerkte" Interrupt B gehandelt (sollten mehrere Interrupts 
anstehen, gehts nach der Interruptpriorität).

>Bei 9600 Baud und kein Stop,

"kein Stop" gibts nicht.  Üblich sind 1 Startbit, 8 Datenbits, 1 
Stopbit.  Macht zusammen 10 übertragene Bits pro Nutzbyte.

>Paritiy oder sonstwas BIT dauert ein BIT 60s/(9600*8)= 781us.

Was rechnest Du für nen Murks?

9600 Baud = 9600 Bit/s = 960 * 10 Bit/s = 960 Nutzbytes/s

Die Übertragung eines Nutzbytes dauert somit 1/960 s = 1.041666 ms

>Ich muss also sicherstellen, dass meine
>Interrupt-Routine A innerhalb dieser Zeit fertig wird und ich in der
>Routine B mit dem Lesen des BITs begonnen habe sonst ist es weg und
>damit kann das Zeichen nicht erkannt werden.

Von der UART-Empfangseinheit werden stets Bytes gelesen, nicht Bits. 
Für das Auslesen jedes eingetroffenen Bytes stehen Dir tatsächlich "nur" 
1.041666 ms zur Verfügung.  Das ist aber kein Problem, denn ein AVR kann 
bei 8 MHz Taktfrequenz während dieser Zeitspanne ca. 5000 Instruktionen 
abarbeiten, und das reicht hundertmal aus, um das Byte an der richtigen 
Stelle im UART-Empfangspuffer (= Speicherbereich im SRAM) abzuspeichern, 
und die Interruptroutine danach wieder zu verlassen (mehr ist ja nicht 
nötig).  Die Prüfung, ob der Puffer voll ist plus Ingangsetzen der 
entsprechenden Aktionen erfolgt natürlich im Hauptprogramm.

von Karl H. (kbuchegg)


Lesenswert?

Andreas Kaiser wrote:
> Alle halbwegs aktuellen AVRs (ab ca. Mega8/16) besitzen zusätzlich zum
> Schieberegister 2 Empfangspuffer. Das bedeutet, dass erst dann Daten
> verloren gehen, wenn insgesamt 3 Bytes vollständig empfangen werden ohne
> dass eines davon abgeholt wird.

Ah. Das wuste ich noch nicht.
Um ehrlich zu sein: Ich hab mich auch noch nie darum gekümmert.
In der Zeit, die 1 Zeichen bei 9600 Baud zur Übertragung braucht,
kann man zwischendurch noch sooooo viel erledigen :-)

von Karl H. (kbuchegg)


Lesenswert?

AVRFan wrote:
>>OK - das löst das Problem wenn ich beim Start der Interupt-Routine A das
>>I-Bit lösche und am Ende wieder setze.
>
> Gelöscht wird das I-Bit im SREG automatisch durch die Interrupt-Logik
> des Controllers, in dem Moment, wo ein Interrupt gehandelt wird.  Das
> Setzen des I-Bits obliegt jedoch dem Programmierer, üblicherweise
> geschieht es mit "reti" am Ende des Interupthandlers.

Er programmiert in C.
Da braucht er sich auch um das reti am Ende des Interrupt
Handlers nicht kümmern.

von Uboot- S. (uboot-stocki)


Lesenswert?

Hi,

>>Wenn während der Bearbeitungszeit von Routine A die Bedingung für B
>>eintrat (z.B. Ein Zeichen an den Controller gesandt werden soll) Beginnt
>>sofort die Bearbeitung der Routine B (UART).
>
> Nein, eben nicht sofort, sondern erst, wenn das Programm aus Routine A
> ins Hauptprogramm zurückgekehrt ist.  Ist dann wegen des "reti" am Ende
> der Routine A das I-Bit wieder gesetzt, wird im Hauptprogramm noch genau
> eine Instruktion abgearbeitet und DANN der über das entsprechende Flag
> "vorgemerkte" Interrupt B gehandelt (sollten mehrere Interrupts
> anstehen, gehts nach der Interruptpriorität).

Ja, ich habs verstanden. Konnte heute Morgen noch nicht klar gradeaus 
schreiben...

> Was rechnest Du für nen Murks?
>
> 9600 Baud = 9600 Bit/s = 960 * 10 Bit/s = 960 Nutzbytes/s
>
> Die Übertragung eines Nutzbytes dauert somit 1/960 s = 1.041666 ms
>

Auch das mit Start- und Stop-BIT ist klar. Keine Ahnung wie ich auf 8 
BIT kam. 10 BIT sind richtig.


> ... und das reicht hundertmal aus, um das Byte an der richtigen
> Stelle im UART-Empfangspuffer (= Speicherbereich im SRAM) abzuspeichern,
> und die Interruptroutine danach wieder zu verlassen (mehr ist ja nicht
> nötig).  Die Prüfung, ob der Puffer voll ist plus Ingangsetzen der
> entsprechenden Aktionen erfolgt natürlich im Hauptprogramm.

Äh?

Ich will ja mit dem Daten, die ich von der Schnittstelle empfange was 
anfangen. Nehmen wir mal an, ich möchte eine 8-BIT Zahl einlesen:

Mir ist klar, dass ich die einzelnen Ziffern (maximal 3) im 
Anwendungsprogramm zwischenspeichern muss, da ich ja nachher eine Zahl 
zwischen 0 und 255 erzeugen möchte - die höchstwertige Stelle kommt ja 
zuerst.

Was hat das aber alles mit "um das Byte an der richtigen
Stelle im UART-Empfangspuffer (= Speicherbereich im SRAM) abzuspeichern" 
zu zun? Oder meinst Du mit "UART-Empfangspuffer" z.B. mein obiges 
Beispiel?

Gruß

Andreas

von Karl H. (kbuchegg)


Lesenswert?

Andreas St. wrote:

>> ... und das reicht hundertmal aus, um das Byte an der richtigen
>> Stelle im UART-Empfangspuffer (= Speicherbereich im SRAM) abzuspeichern,
>> und die Interruptroutine danach wieder zu verlassen (mehr ist ja nicht
>> nötig).  Die Prüfung, ob der Puffer voll ist plus Ingangsetzen der
>> entsprechenden Aktionen erfolgt natürlich im Hauptprogramm.
>
> Äh?
>
> Ich will ja mit dem Daten, die ich von der Schnittstelle empfange was
> anfangen.

Schon.
Aber das machst du normalerweise nicht im Interrupt.
Im Interrupt wird üblicherweise das Zeichen in einem Puffer 
zwischengespeichert.
Mehr nicht.
Nicht ganz: Im Interrupt wird auch noch geprüft, ob ein bestimmtes
Zeichen angekommen ist, welches das Ende dieser einen Übertragung
signalisiert. Dann wird von der ISR eine globale Variable auf
1 gesetzt an der das Hauptprogramm erkennen kann, dass in diesem
Zwischenpuffer eine komplette Eingabe vorliegt und ausgwertet
werden muss.


> Nehmen wir mal an, ich möchte eine 8-BIT Zahl einlesen:
>
> Mir ist klar, dass ich die einzelnen Ziffern (maximal 3) im
> Anwendungsprogramm zwischenspeichern muss, da ich ja nachher eine Zahl
> zwischen 0 und 255 erzeugen möchte - die höchstwertige Stelle kommt ja
> zuerst.

OK. Wenn es nur um diese Aufgabenstellung geht. Die ist einfach
genug, dass man sie auch in der ISR machen könnte.

In dem Fall braucht man dann auch nicht zwischenspeichern.

Bei jeder ISR wird gerechnet:

  Neue Zahl = Alte Zahl * 10 + neue Ziffer

Aber diesr Fall ist eher die Ausnahme. Meist hat man ja irgendwelche
Steuerkommandos, die per UART übertragen werden. In dem Fall
ist es das einfachste zunächst einfach mal alle Zeichen, bis
zum Ende Zeichen, in einen Puffer zwischenzuspeichern und erst
dann wenn die Übertragung fertig ist, auszuwerten (aber nicht in
der ISR auswerten. Die ist dafür nicht zuständig!)

so ungefähr:
1
...
2
3
uint8_t LineCnt;
4
char    InLine[80];
5
volatile uint8_t LineReceived; 
6
7
ISR( RX ... )
8
{
9
  char c = UDR;
10
11
  if( c == '\n' ) {
12
    LineReceived = 1;
13
    InLine[LineCnt] = '\0';
14
    LineCnt = 0;
15
  }
16
  else {
17
    InLine[LineCnt++] = c;
18
  }
19
}
20
21
22
int main()
23
{
24
  char RecLine[80];
25
26
   ....
27
28
  LineCnt = 0;
29
  LineReceived = 0;
30
31
  sei();
32
33
  while( 1 ) {
34
35
    if( LineReceived ) {
36
      // mach was mit dem empfangenen Text, zb umkopieren
37
      // damit im Hintergrund wieder ein neuer Text empfangen
38
      // werden kann
39
      cli();
40
      strcpy( RecLine, InLine );
41
      LineReceived = 0;
42
      sei();
43
44
      // und jetzt kann ausgewertet werden, was der Benutzer
45
      // schon wieder will
46
47
      if( strcmp( RecLine, "Help" ) == 0 ) {
48
        // es war das help Kommando
49
50
      else if( strcmp( RecLine, "CLR" ) == 0 ) {
51
        // es war das clear Kommando
52
      }
53
54
      ...
55
  
56
    }
57
  }
58
}

WIe gesagt: So ungefähr. Wie man das dann genau umsetzt, hängt auch
von der konkreten Aufgabenstellung ab.

von Falk B. (falk)


Lesenswert?

@  Karl heinz Buchegger (kbuchegg)

>WIe gesagt: So ungefähr. Wie man das dann genau umsetzt, hängt auch
>von der konkreten Aufgabenstellung ab.

Ja, Scheisse. Die Frage kommt nun zum 1001 mal, und du hast sie bestimmt 
999 mal so ausführlich beantwortet? Wäre es nicht höchste Eisenbahn, das 
mal in einen ORDENTLICHEN Wikiartikel mit getestetem, vollständigem 
Beispiel zu packen? Oder willst das noch 10000 mal zu 99% erklären (was 
dann wieder endlose Fragen auslöt und wieder 1001 Erklärung).

MFG
Falk

von Karl H. (kbuchegg)


Lesenswert?

@Falk
Hmm. Irgendwie hast du schon recht. :-)

von AVRFan (Gast)


Lesenswert?

>Ich will ja mit dem Daten, die ich von der Schnittstelle empfange was
>anfangen.

Logisch.

>Nehmen wir mal an, ich möchte eine 8-BIT Zahl einlesen:

>Mir ist klar, dass ich die einzelnen Ziffern (maximal 3) im
>Anwendungsprogramm zwischenspeichern muss, da ich ja nachher eine Zahl
>zwischen 0 und 255 erzeugen möchte - die höchstwertige Stelle kommt ja
>zuerst.

Um eben dieses Zwischenspeichern geht es.  Du definierst Dir irgendeinen 
zusammenhängenden Bereich im SRAM als "UART-Empfangspuffer" und stapelst 
die eintreffenden UART-Bytes darin auf (je ein Byte in jedem Interrupt), 
bis der "Block" komplett ist.  Das ist die minimalst mögliche Aktion, 
bei der gewährleistet ist, dass kein eintreffendes Byte verloren geht. 
Mehr muss im Interrupthandler nicht geschehen und wegen dem Grundsatz 
"Alle Interrupthandler so kurz wie möglich halten" sollte man auch 
nicht sehr viel mehr im Interrupthandler veranstalten.  Die Aktion, die 
bei "Block vollständig empfangen" abhängig vom Inhalt des Blocks 
ausgeführt werden soll, wird erst im Hauptprogramm synchron mit den 
restlichen Vorgängen (Tasks) Deiner Software in Gang gesetzt. Solche - 
teilweise rechenaufwendigen - Aktionen können z. B. sein die Umwandlung 
einer Zahl in einen String für die Anzeige auf einem Display, das 
Formatieren einer CF-Karte, das Einschalten eines Motors, das Auslesen 
eines Sensors, das Beschreiben eines EEPROM-Bytes etc.

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.