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 :)
Du hast eine komplizierte endlosschleife geschrieben. Den Interrupt benutzt man eher wenn man auf etwas reagieren will.
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.