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 :)
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.
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.
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!
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.