Forum: Mikrocontroller und Digitale Elektronik Kann sich ein und derselbe Interrupt unterbrechen?


von Interrupt (Gast)


Lesenswert?

Hi Leute, ich probiere gerade mit dem STM32 und seinen Timern & 
Interrupteinstellungen herum.

Da ist mir aufgefallen, dass wenn die Interrupt Routine länger braucht, 
als die Zeit in der der Timerinterrupt auslöst (gemessen durch toggeln 
eines Ports), so verlängert sich logischerweise die Zeit zum nächsten 
Interrupt.

Die Interrupts des selben Timers können sich also nicht gegenseitig 
unterbrechen? Vermutlich ist dann immer die Interrupt Flag gesetzt und 
er hängt ständig im Interrupt fest, korrekt? Wie kann ich das z.B. im 
Debugger nachvollziehen?

Ich frage überhaupt, weil ich in der Interruptroutine eine Funktion 
aufrufe (habe gerade gelesen, dass man das gar nicht machen sollte, 
werde die Funktion also noch irgendwie auslagern), die die UART 
Blockweise 1000 Byte ausgeben lässt. Diese 1000 Byte werden in der Timer 
ISR gefüllt (Zahlen 1-1000 @ uint16_t). Die Sendefunktion wird 
aufgerufen, sobald 1000 Byte voll sind (liegen alle in einem Array).
Meine Uart hat eine Baudrate von 115200 Baud/s, wodurch sie für die 
1000Byte ca. 78,125us/9Bit*1000Bit=78,125ms benötigt. In diesen 78,125ms 
wird aber der Puffer sehr häufig wieder gefüllt (die Timer Frequenz 
liegt bei eingestellten 100ns, die gemessene Dauer einer ISR (nur füllen 
des Arrays) beträgt etwa 5us.

Die 1000 Bytes werden sehr schnell gefüllt, bei 5us pro Timer ISR: 
1000*5us=5ms. Also ist der Puffer nach 5ms voll. Dann wird die Senden 
Routine aufgerufen und es wird in einen zweiten Puffer geschrieben. 
Dieser ist ja wiederrum nach 5ms gefüllt und wird der Senden Routine 
übergeben. Dann kommt wieder der erste Puffer und so weiter.

Obwohl die Sendenfunktion alleine auf Grund der Baudrate viel länger 
benötigt (und eigentlich wird die ja auch immer wieder von der Timer ISR 
unterbrochen?!?!), kommen die korreten Zahlen hinten raus.

Kann mir das jemand erklären?

Meine Überlegungen:
Die Sendefunktion, als quasi Teil der Timer ISR kann nicht von der 
eigenen ISR unterbrochen werden und gibt tatsächlich den kompletten 
gerade übergebenen Puffer aus. Erst, wenn diese Sendefunktion zu Ende 
ist, kommt der Return from Interrupt und dann wird der Puffer neu 
gefüllt.

Wenn das so ist - blockiert ja die ISR quasi ständig die CPU. Wenn jetzt 
meine einzige Aufgabe mit den beiden Funktionen erfüllt wäre, könnte ich 
dann das Konstrukt so lassen?

Gehe ich Recht in der Annahme, dass dies der Grund ist, die ISR nur 
möglichst kurz zu gestalten? :D

Wenn ja, wie sähe eine elegantere Lösung aus? In der Timer ISR wird nur 
der Wert in den Puffer geschrieben. Aber wo rufe ich dann die senden 
Funktion auf? Ich könnte ein Bit setzen, sobald der Puffer voll ist und 
das z.B. in der Main ständig pollen, aber wo genau ist dann der große 
Vorteil eines Interruptes?



Ich merke gerade, das Thema ist etwas abgerutscht, das tut mir Leid. Im 
Grunde handelt es sich wohl um zwei Fragen. Einmal: Kann sich ein und 
der selbe Interrupt unterbrechen (ich gehe mittlerweile nicht davon aus, 
da ja sonst nicht die korrekten Zahlen heraus kämen)?
Und: Wo macht man den USART SendenFunktion Aufruf?


Vielen Dank fürs Lesen, wenn was unklar ist, schreibt :)

von Hans M. (hansilein)


Lesenswert?

Du hast eine komplizierte endlosschleife geschrieben.
Den Interrupt benutzt man eher wenn man auf etwas reagieren will.

von Sebastian (Gast)


Lesenswert?

Einige Ideen:

Die ISR tut irgendetwas mit dem Puffer. Im Hauptprogramm stubst du nun 
das Senden an. Es kann (wird) ja nun vorkommen, dass während des 
Sendens, die ISR den noch nicht gesendeten Teil des Puffers wieder 
überschreibt.
D.h. Deine Sende-Funktion sollte zuerst ein Flag setzen, dass sie gerade 
sendet. DIeses Flag wertest Du in der ISR aus und
a) änderst Deien Variablen nicht
b) hast 2 Puffer, wenn Puffer 1 gesendet wird, wird in Pudder 2 
geschrieben und umgekehrt


Beispiel a)
1
volatile int buffer[ELEMENTS]
2
volatile int flag;
3
4
ISR()
5
{
6
  if (!flag) 
7
  { buffer[1] = PINA; buffer[2] = PINB; }
8
} 
9
10
void sende()
11
{
12
  flag = 1;
13
  // hier senden
14
  flag = 0;
15
}
16
17
void main()
18
{ while (1) sende(); }

oder Variante b)

1
volatile int buffer1[ELEMENTS]
2
volatile int buffer2[ELEMENTS]
3
volatile int flag;
4
5
ISR()
6
{
7
  if (!flag) 
8
  { buffer1[1] = PINA; buffer1[2] = PINB; }
9
  else
10
  { buffer2[1] = PINA; buffer2[2] = PINB; }
11
  
12
} 
13
14
void sende()
15
{
16
  if (flag) { flag=0; do_senden(buffer2);  } 
17
  else { flag=1; do_senden(buffer1); }
18
}
19
20
void main()
21
{ while (1) sende(); }

Eins noch: Wenn Du die Daten schneller "generierst" (Abfragst...) als Du 
sie übertragen kannst, musst Du einige Daten logischerweise 
verwerfen/überschreiben.

von W.S. (Gast)


Lesenswert?

Interrupt schrieb:
> die die UART
> Blockweise 1000 Byte ausgeben lässt.

Mir wird schlecht bei so einem Programmierstil.

Kannst du denn nicht erstmal dein System analysieren und dir eine 
vernünftige Strategie ausdenken, bevor du in die Tasten haust?

Interrupts sind bei normalen uC dazu da, auf Ereignisse schnell zu 
reagieren. Aber nicht, um dort Zeit zu vertrödeln. Wenn du schoon 
einen UART benutzt, dann spendiere dem Teil einen Sende-Ringpuffer 
ausreichender Größe und fahre den UART separat im Interruptbetrieb. Das 
heißt, das Programm zum Senden befaßt sich überhaupt nicht mit dem UART, 
sondern füllt nur den Ringpuffer. Aber obwohl sowas ne Menge abfedern 
kann, ist es ausgesprochener Mumpitz, sowas in einer Interrupt-Routine 
aufzurufen. Setze lieber ein Flag und überlasse das zeitaufwendige 
Senden der Grundschleife im Programm.

Nochwas: Programmieren heißt nicht, in irgendeiner Programmiersprache 
irgendwas hinschreiben zu können, sondern DENKEN_ _KÖNNEN, ein Problem 
analysieren können und eine Strategie zum stabilen Lösen dieses Problems 
entwerfen zu können.

W.S.

von Falk B. (falk)


Lesenswert?

Lese er die Wahrheit über Interrupts.

von Captain S. (captainsubtext)


Lesenswert?

Auch wenn es nur ein Testprogramm von DIe ist, um mit Timer und Uart zu 
spielen:

Die Aufgabe schreit eigentlich förmlich nach DMA.
Damit kannst du deine Daten senden ohne dass die CPU einen Finger krumm 
machen muss. :-)

Erst einmal in aller Ruhe den Puffer füllen, dann den DMA-Controller 
drauf loslassen. Der kümmert sich dann selbst darum, dass dein Puffer 
über die USART geschickt wird.


Aber arbeite dich lieber erst einmal in die Timer und Interrupts ein. Um 
dein Programm ohne DMA ein wenig aufzupeppen würde ich folgendes tun:

Erstelle dir einen Ringpuffer und merke dir die aktuelle Schreib- und 
Lese/Sendeposition. Dann arbeitest du mit zwei Interrupts:
1
Timer-Interrupt: (löst alle x Mikrosekunden aus)
2
  Zeichen an die aktuelle Schreibposition schreiben
3
  Schreibposition ein Feld weiterschieben*
4
  Prüfen, ob Schreibposition gleich der Lese/Sendeposition ist.**
5
  Prüfen, ob zwischen Schreib- und Leseposition >=1000 Zeichen liegen. Ja? Es darf gesendet werden. Dafür den USART-Interrupt aktivieren
6
7
USART-Interrupt: (löst aus, wenn ein Zeichen in den USART-Sendepuffer geschrieben werden kann)
8
  Zeichen von Leseposition holen und in den USART-Sendepuffer schreiben.
9
  Leseposition um ein Feld weiterschieben
10
  Mitzählen, wie viele Zeichen bisher gesendet wurden. 1000? Dann USART-Interrupt deaktivieren und Anzahl der gesendeten Zeichen auf 0 zurücksetzen.
11
12
Hauptschleife:
13
  Tue irgendetwas Anderes

Wenn du es so in etwa umsetzt wird deine Hauptschleife nur genau dann 
unterbrochen, wenn es wirklich sinnvoll ist. Du vertrödelst keine 
unnötigen Wartezeiten damit, auf einen freien Platz für dein nächstes 
Zeichen zu warten. Die USART meldet sich von alleine, wenn die wieder 
ein Zeichen aufnehemn kann.


*Wrap-around beachten!
**Wenn ja, hast du deine Senderoutine überholt und überschreibst Daten, 
die noch nicht gesendet wurden. Fehlerbehandlung einleiten.

Viel Erfolg!

von Interrupt (Gast)


Lesenswert?

Sebastian schrieb:
> Einige Ideen:
>
> Die ISR tut irgendetwas mit dem Puffer. Im Hauptprogramm stubst du nun
> das Senden an. Es kann (wird) ja nun vorkommen, dass während des
> Sendens, die ISR den noch nicht gesendeten Teil des Puffers wieder
> überschreibt.
> D.h. Deine Sende-Funktion sollte zuerst ein Flag setzen, dass sie gerade
> sendet. DIeses Flag wertest Du in der ISR aus und
> a) änderst Deien Variablen nicht
> b) hast 2 Puffer, wenn Puffer 1 gesendet wird, wird in Pudder 2
> geschrieben und umgekehrt

Hey, danke für den Input. Die zwei Puffer habe ich ja bereits eingebaut. 
Werde die Senden Routine in die Main verlagern.

W.S. schrieb:
> Mir wird schlecht bei so einem Programmierstil.

Das tut mir natürlich Leid.

W.S. schrieb:
> Kannst du denn nicht erstmal dein System analysieren und dir eine
> vernünftige Strategie ausdenken, bevor du in die Tasten haust?

Ich spiele mit einem STM32 herum und möchte was dazu lernen, soll ich 
mir erst einen Plan machen, wie ich vorzugehen habe?

W.S. schrieb:
> Interrupts sind bei normalen uC dazu da, auf Ereignisse schnell zu
> reagieren. Aber nicht, um dort Zeit zu vertrödeln. Wenn du schoon
> einen UART benutzt, dann spendiere dem Teil einen Sende-Ringpuffer
> ausreichender Größe und fahre den UART separat im Interruptbetrieb. Das
> heißt, das Programm zum Senden befaßt sich überhaupt nicht mit dem UART,
> sondern füllt nur den Ringpuffer. Aber obwohl sowas ne Menge abfedern
> kann, ist es ausgesprochener Mumpitz, sowas in einer Interrupt-Routine
> aufzurufen. Setze lieber ein Flag und überlasse das zeitaufwendige
> Senden der Grundschleife im Programm.

Okay mache ich.

W.S. schrieb:
> Nochwas: Programmieren heißt nicht, in irgendeiner Programmiersprache
> irgendwas hinschreiben zu können, sondern DENKEN_ _KÖNNEN, ein Problem
> analysieren können und eine Strategie zum stabilen Lösen dieses Problems
> entwerfen zu können.

Wenn ein wirklich konkretes Problem kommt werde ich das berücksichtigen.

Falk Brunner schrieb:
> Lese er die Wahrheit über Interrupts.

Ok.

Captain Subtext schrieb:
> Auch wenn es nur ein Testprogramm von DIe ist, um mit Timer und Uart zu
> spielen:
>
> Die Aufgabe schreit eigentlich förmlich nach DMA.
> Damit kannst du deine Daten senden ohne dass die CPU einen Finger krumm
> machen muss. :-)
>
> Erst einmal in aller Ruhe den Puffer füllen, dann den DMA-Controller
> drauf loslassen. Der kümmert sich dann selbst darum, dass dein Puffer
> über die USART geschickt wird.

Wird das nächste sein, das ich mir angucke dann =)

Danke für dein Input, werde mir Gedanken machen.

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.