Hallo @ Community, ich hätte mal eine Frage zum Thema: "UART und senden via. IRQ mit einem Puffer". Wie ist denn der grobe Ablauf? Ich beschreibe meinen Puffer (z.B. Ringpuffer) und nach dem beschreiben sage ich ihm das er ein Interrupt feuern soll und dort werden dann die Daten aus dem Puffer gelesen und in das Ausgaberegister geschrieben?.
Anders herum. Der UART ruft die ISR auf, wenn er bereit ist, das nächste Byte zu senden. Die ISR schiebt bei jedem Aufrauf ein Byte auf dem Puffer in das TDR Register des UART. Ganz am Anfang muss das erste Byte am Puffer vorbei gesendet werden, indem man es direkt ins TDR Register schreibt. Wenn alle gepufferten Bytes gesendet wurden, hat die letzte ISR nichts mehr zu tun. Es wird nichts mehr ins TDR Register geschrieben. Weiter geht es dann ggf. bei "Ganz am Anfang...".
Stefan ⛄ F. schrieb: > Ganz am Anfang muss das erste Byte am Puffer vorbei gesendet werden, > also ohne auf einen Interrupt zu warten. Gleiches gilt auch, nachdem > alle gepufferten Zeichen gesendet wurden und man später etwas neues > senden will. Du meinst also erstmal die Daten in den Puffer schreiben, dann einmal..
1 | xx->TDR = data |
und in der ISR dann quasie Byte für Byte aus dem Puffer holen?.
Jan H. schrieb: > Du meinst also erstmal die Daten in den Puffer schreiben, > dann einmal..xx->TDR = data > und in der ISR dann quasie Byte für Byte aus dem Puffer holen?. Nein! Das erste Byte wird nicht gepuffert. Das allererste Byte musst du direkt ins TDR Register schreiben. Erst danach löst der UART den ersten Interrupt aus. Der Interrupt Handler und der Puffer ist als nur für das zweite und alle folgenden Bytes zuständig. Bis es nichts mehr zu senden gibt, dann fängst du wieder von vorne an, mit direktem Zugriff auf TDR.
> Nein! > > Das allererste Byte musst du direkt ins TDR Register schreiben. Erst > danach (wenn es gesendet wurde) löst der UART einen Interrupt aus. Der > Interrupt Handler ist als nur für das zweite und alle folgenden Bytes > zuständig. > > Bis es nichts mehr zu senden gibt, dann fängst du wieder von vorne an, > mit direktem Zugriff auf TDR.
1 | /*
|
2 | * BETA
|
3 | */
|
4 | FIFO_ERROR_CODE_t __USARTSend( USART_TypeDef *pUsart, char *pcData ) |
5 | {
|
6 | FIFO_ERROR_CODE_t eError = FIFO_OK; |
7 | |
8 | pUsart->TDR = *pcData++; |
9 | |
10 | for( ; *pcData != '\0'; pcData++ ) |
11 | {
|
12 | eError |= FIFOPush( *pcData ); |
13 | }
|
14 | |
15 | return eError; |
16 | }
|
1 | void USART1_IRQHandler(void) |
2 | {
|
3 | if ( ( USART1->ISR & USART_ISR_RXNE ) == USART_ISR_RXNE ) |
4 | {
|
5 | FIFOPush( USART1->RDR ); |
6 | |
7 | GPIOTogglePin( GPIOC, GPIO_PIN_9 ); |
8 | |
9 | USART1->RQR |= USART_RQR_RXFRQ; // clear flag ( write 1 to clear ) |
10 | }
|
11 | |
12 | |
13 | if ( ( USART1->ISR & USART_ISR_TXE ) == USART_ISR_TXE ) |
14 | {
|
15 | uint8_t uiByte; |
16 | FIFO_ERROR_CODE_t eFIFO = FIFO_OK; |
17 | |
18 | eFIFO = FIFOPull( &uiByte ); |
19 | if ( eFIFO != FIFO_OK ) |
20 | {
|
21 | USART1->CR1 &= ~ (USART_CR1_TXEIE | USART_CR1_TCIE ); |
22 | }else |
23 | {
|
24 | USART1->TDR = uiByte; |
25 | }
|
26 | }
|
27 | }
|
Meinst Du das so?
Jan H. schrieb: > Wie ist denn der grobe Ablauf? Eigentlich ganz einfach (sofern eine Ähnlichkeit besteht mit dem STM32F103): Es gibt zwei Funktionen, die von außen her (also von main.. her) gerufen werden. Das ist zum einen das Lesen eines empfangenen Zeichens und zum anderen das Schreiben eines zu sendenden Zeichens. Die Funktion zum Senden stopft das Zeichen in einen Ringpuffer und gibt dann bei jedem eintrudelnden Zeichen den Sende-Interrupt des UART-Cores frei. Der Sendezweig des UARTs erzeugt immer einen Interrupt, wenn noch ein weiteres Zeichen in den Sender geschoben werden kann. Die Interupt-Routine testet, ob im Ringpuffer ein Zeichen herumliegt und wenn ja, dann stopft sie das Zeichen in den Sender. Wenn nicht, dann schaltet sie den Sende-Interrupt wieder aus. Damit wird das Senden bei jedem Zeichen wieder angestoßen, das dem UART-Treiber zum Senden übergeben wird. Wenn alles gesendet ist, dann wird der Interrupt wieder gesperrt (ist wichtig!) und es passiert nix bis zum nächsten Zeichen. Nochwas: Es sollte noch einige weitere Funktionen geben, und zwar für: - Abfragen, ob ein empfangenes Zeichen vorliegt - Abfragen, ob der Sendepuffer noch ein weitees Zeichen vertragen kann W.S.
Beitrag #6966021 wurde von einem Moderator gelöscht.
Jan, ich muss mich bei dir entschuldigen, habe da zwei Mikrocontroller miteinander verwechselt, so dass eine irreführende Antwort dabei heraus kam. WS hat es korrekt beschrieben. Die ISR wird beim STM32 nicht nur 1x aufgerufen, wenn der USART bereit ist etwas zu senden, sondern sie wird immer wieder aufgerufen, solange er bereit ist. Die Konsequenz daraus ist, dass man den Interrupt abschalten muss, wenn man nichts mehr zu senden hat. Deine ISR ist also insofern stimmig. Beim Senden ist es in der Tat am einfachsten, erst alles in den Puffer zu schreiben (so wie du das vor hattest) und dann die Interrupts einzuschalten. Dann ist ausschließlich die ISR für das Senden der gepufferten Daten zuständig. Du kannst auch das erste Byte direkt (am Puffer vorbei) senden und danach die Interrupts einschalten. Dadurch entfällt ein Interrupt, man spart ein kleines bisschen Zeit. Allerdings wird der Code in __USARTSend() komplexer, vermutlich lohnt sich der Aufwand nicht. Deine __USARTSend() musst du nochmal anpassen. Schau dir die Funktion Usart1Put() auf https://embedds.com/programming-stm32-usart-using-gcc-tools-part-2/ an. Sorry, dass ich Verwirrung gestiftet habe.
Stefan ⛄ F. schrieb: > Jan, ich muss mich bei dir entschuldigen, habe da zwei > Mikrocontroller miteinander verwechselt, so dass eine irreführende > Antwort dabei heraus kam. WS hat es korrekt beschrieben. > Die ISR wird beim STM32 nicht nur 1x aufgerufen, wenn der USART bereit > ist etwas zu senden, sondern sie wird immer wieder aufgerufen, solange > er bereit ist. Die Konsequenz daraus ist, dass man den Interrupt > abschalten muss, wenn man nichts mehr zu senden hat. Deine ISR ist also > insofern stimmig. > Beim Senden ist es in der Tat am einfachsten, erst alles in den Puffer > zu schreiben (so wie du das vor hattest) und dann die Interrupts > einzuschalten. Dann ist ausschließlich die ISR für das Senden der > gepufferten Daten zuständig. > Du kannst auch das erste Byte direkt (am Puffer vorbei) senden und > danach die Interrupts einschalten. Dadurch entfällt ein Interrupt, man > spart ein kleines bisschen Zeit. Allerdings wird der Code in > __USARTSend() komplexer, vermutlich lohnt sich der Aufwand nicht. > Deine __USARTSend() musst du nochmal anpassen. Schau dir die Funktion > Usart1Put() auf > https://embedds.com/programming-stm32-usart-using-gcc-tools-part-2/ an. > Sorry, dass ich Verwirrung gestiftet habe. Alles gut. Bei dem verlinkten Beispiel, packt er ja nur Byte für Byte in den Fifo und aktiviert dann direkt den interrupt… Sollte man nicht erstmal den Fifo ein wenig füllen und dann senden? Was ist effektiver?
Jan H. schrieb: > Sollte man nicht erstmal den Fifo ein wenig füllen und dann senden? Was > ist effektiver? Ich glaub nicht dass das einen wesentlichen Unterschied macht. So ein einfacher Registerzugriff ist ja nicht teuer. Falls du es doch machst berücksichtige den Fall dass die zu sendenden Daten größer sein können, las der Sendepuffer. Nicht dass du dann in einer Warteschleife hängen bleibst weil der Interrupt noch nicht aktiviert ist.
Jan H. schrieb: > Sollte man nicht erstmal den Fifo ein wenig füllen und dann senden? Was > ist effektiver? Nö, denn dein UART-Treiber weiß ja nicht, ob und wann und wieviele Zeichen demnächst bei ihm hereintrudeln werden. Deshalb ist jeder Gedanke daran, erstmal den Fifo etwas zu füllen, bevor man mit der Arbeit beginnt, gründlich falsch. Der Fifo dient letztlich für 2 Dinge: - Entkopplung der ISR-Welt von der maín()-Welt - Puffern von nicht allzu langen Ausgaben, so daß bei kürzeren Ausgaben der µC ohne Wartezeiten weitermachen kann. Wenn man die gesamte Bibel senden will, dann nützt das aber nix. Die ist zu umfänglich, weswegen man dann auf das Senden doch wieder warten muß. W.S.
Ich denke das Problem besteht darin das es nicht die eine optimale Loesung gibt. Es haengt leider immer auch etwas von der Anwendung und den Daten ab. Das zeigt alleine schon die Existenz von Befehlen wie "flush()" Olaf
Olaf schrieb: > Ich denke das Problem besteht darin das es nicht die eine > optimale Loesung gibt. Es haengt leider immer auch etwas > von der Anwendung und den Daten ab. > Das zeigt alleine schon die Existenz von Befehlen wie "flush()" Aus meiner Sicht ist das eher eine Hardwarefrage. Hat man eine CPU, bei der der Kontextwechsel (wie z.B. der Interrupt) nur wenig CPU-Zeit kostet, puffert man nur zwischen Anwendung und ISR und schickt jedes Zeichen einzeln zum UART. So wie es auch W.S. beschrieben hat. Hat man dagegen sowas wie x86 (mit laaaang dauerndem Kontextwechsel) und einen UART mit FIFO, dann spart man sich 9 Kontextwechsel und sendet 10 Bytes auf einmal zum UART-FIFO. Erst dann braucht man flush(), um die letzten <10 im Puffer hängenden Bytes, die die Nachricht vervollständigen, auch noch auf die Reise zu schicken. Und nur dann lohnt es sich, nicht gleich loszubrettern. Just my 2 cents
Olaf schrieb: > Das zeigt alleine schon die Existenz von Befehlen wie "flush()" Ach nein. Sowas wie "flush()" zeigt lediglich an, daß derjenige, der sowas geschrieben hat, mit seinem Latein am Ende war und dies als einen Notbehelf dann geschrieben hat. Ein serieller Kanal wie ein UART benötigt so etwas von Haus aus nicht (weil er auch sendet, wenn der Empfänger abgestöpselt ist), lediglich für den Übergang zwischen seriell und blockorientiert (wie eben beim virtellen COM-Port) kommen da Probleme auf, die eigentlich gelöst sein wollen und nicht mit einem flush() umgangen. W.S.
Klaus S. schrieb: > Hat man dagegen sowas wie x86 (mit laaaang dauerndem Kontextwechsel) und > einen UART mit FIFO, dann spart man sich... Nanana, das Retten von CPU-Registern dauert auch beim 80x86 nicht lang. Und wenn der HW-Fifo im Sendezweig eines UART groß ist, dann kann man den auch mit allen anderen Architekturen nutzen. Aber bei gar vielen Chips hat man als UART-Core nur einen, der ein Senderegister und danach das eigentliche Schieberegister hat. Also ein "Fifo" mit nur 2 Plätzen. Das spart vermutlich einiges an Chipfläche und der Aufwand, beim Anfang der ISR die CPU-Register zu retten, ist dagegen vergleichsweise gering. W.S.
Ich kenne flush() von zahlreichen Bibliotheken mit unterschiedlichen Bedeutungen: a) Bei Schnittstellen, die Daten Paketweise oder in Intervallen senden, dient der Befehl dazu, jetzt sofort zu senden, auch wenn das aktuelle Paket/Intervall noch nicht voll ist. b) Es dient dazu, alle gepufferten Daten jetzt sofort blockierend zu senden. Nach Ausführung des Befehls ist der Sendepuffer leer. c) Er dient dazu, den Inhalt des Puffers zu verwerfen. Kann sowohl den Sende-Puffer als auch (häufiger) den Empfangs-Puffer betreffen. > daß derjenige, der sowas geschrieben hat, mit seinem Latein > am Ende war und dies als einen Notbehelf dann geschrieben hat. Sehe ich nicht so. Du pauschalisierst das zu sehr, bzw. neigst dazu deinen persönlichen Programmierstil anderen aufzudrängen.
W.S. schrieb: > Nanana, das Retten von CPU-Registern dauert auch beim 80x86 nicht lang. Lang ist relativ. Für einen Kernphysiker sind 10 hoch -22 Sekunden lang und 10 hoch -26 Sekunden kurz. Wer bietet weniger? Ich bin kein CPU-Kenner, aber ich weiß, daß sowohl Realtime-Linux als auch QNX nur Kontextwechselzeiten von ca.10 Mikrosekunden auf einem 3GHz-Prozessor hinkriegen, das sind dreißigtausend Takte. Das nenne ich lang und das wird einen handfesten Grund haben, da sitzen ja keine Forumstrolle. Und nur deshalb gibt es mMn UARTs mit FIFO und das schon seit dem 286er. Im Übrigen ging es mir nur darum, die beiden Endsituationen zu skizzieren, unabhängig davon, wie lang nun lang ist. Jeder kann sich mit FIFOs beliebiger Größe dazwischen positionieren. Der x86 war nur der mir grade geläufigste Typ. Keine Ahnung, wie "big iron" das erledigt. IBMs JCL schickt mWn auch immer ganze Zeilen, hat also auch keine Lust, Character einzeln zu übertragen. Und wer unter Linux oder Windows den "canonical mode" des UART-Treibers benutzt (aus welchen Gründen auch immer), der braucht eben auch manchmal flush(), um zum Ziel zu kommen. Wer den "cm" nie benutzt, der braucht auch kein flush() und muß nix drüber wissen, da bin ich mit Dir einig. Es sollte dann aber klar sein, daß es maximal zu Halbwahrheiten reicht. Keine Ahnung, wie ich das freundlicher ausdrücken kann, weil ich Dich und Dein Wissen schätze. Gruß Klaus
Klaus S. schrieb: > Ich bin kein CPU-Kenner, aber ich weiß, daß sowohl Realtime-Linux als > auch QNX nur Kontextwechselzeiten von ca.10 Mikrosekunden auf einem > 3GHz-Prozessor hinkriegen, das sind dreißigtausend Takte. Das sind aber auch ganz andere Randbedingungen. Ein Kontextwechsel bei PC-Betriebssystemen umfaßt nicht nur die paar Register einer CPU in einem µC, sondern auch die Zustände im Gleitkomma-Prozessor, sämtliche taskabhängige Dinge wie Setzen von Rechten, Einrichten der ggf. vorhandenen CPU-Kerne, des Heaps usw. Das ist dramatisch mehr, als das, womit wir es hier zu tun haben. Und bei manchen Dingen wie vor allem bei zusammengesetzten Datenwegen - seriell->paketweise->dateiartig, wie man es z.B. bei Windows hat, wenn man Daten seriell von einem µC per VCP/USB an ein Programm im PC senden will - gibt es an allen Stellen eben Stoßstellen, an denen man abwägen muß. Wer (aus was für Gründen auch immer) per VCP nur voll gefüllte Blöcke übertragen will, der hat auf der Sendeseite große Probleme, denn er kann nicht vorhersagen, ob und wann auf einem asynchronen Inputkanal wie viele Zeichen in Zukunft hereinkommen werden. Da ist man mit seinem Latein am Ende. In so einem Falle gibt es nur 3 Wege: 1. den Anspruch aufgeben, nur volle Blöcke übertragen zu wollen. 2. den Anspruch der Asynchronität des Inputs aufgeben. 3. die Forderung aufstellen, daß das aufrufende Programm sowas wie eine Putzfrau namens flush() hinterherschicken soll. Damit wird das Problem auf die aufrufenden Programme abgeschoben. Da muß sich jeder entscheiden. Wie auch immer. W.S.
W.S. schrieb: > Das ist dramatisch mehr, als das, womit wir es hier zu tun haben. Genau. Den Grund dafür hatte ich ja schon beschrieben. Die folgende Schauergeschichte über einen Vollpfosten gehört für mich zwar eher in die Literaturabteilung. Hat mir aber viel Vergnügen gemacht und ist ein schönes Beispiel dafür, wohin es führt, wenn man allzu platten Annahmen nachgeht. Danke :-)))
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.