Hallo STM32-Fachleute! Ich habe ein Problem, das eigentlich keines sein sollte. Aber vermutlich übersehe ich etwas. Bei einem STM32F405 (Cortex-M4, auf einem S64DIL-405-Board) soll USART2 für eine serielle Kommunikation verwendet werden (nur Tx auf PA2 und Rx auf PA3, keine Handshake-Leitungen). Im folgenden Kode-Beispiel wird der USART initialisiert und die Interrupts behandelt (zu Testzwecken werden nur die Interrupt-Flags zurück gesetzt). Im Hauptprogramm (main) wird nur ein Zeichen ausgegeben (also ein ganz banaler Sachverhalt). Die Zeichenausgabe funktioniert, wenn bei der Initialisierung NICHT der TXE-Interrupt (Interrupt, wenn Sendepuffer leer ist) verwendet wird! Mit USART_IT_RXNE beispielsweise funktioniert es! Ich verstehe nicht, warum das so ist. Wo liegt der Trick? Oder ein Bug im STM32F4xx? Hier das Kode-Beispiel: ---------------------------------------------------------------------- void UsartInit(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; /************************************** * USART2 mit PA2 (Tx) und PA3 (Rx) **************************************/ // USART Taktversorgung RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // alternative Pin-Funktionen für USART GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); // Ports konfigurieren GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; GPIO_Init(GPIOA, &GPIO_InitStructure); // USART konfigurieren USART_InitStructure.USART_BaudRate = 4800; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART2, &USART_InitStructure); USART_Cmd(USART2, ENABLE); // USART2-Interrupt USART_ITConfig(USART2,USART_IT_TXE,ENABLE); // USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); // damit funktioniert es NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0E; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x08; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) { USART_ClearITPendingBit(USART2,USART_IT_RXNE); } if (USART_GetITStatus(USART2, USART_IT_TXE) == SET) { USART_ClearITPendingBit(USART2,USART_IT_TXE); } } int main(void) { UsartInit(); // Initialisierung USART_SendData(USART2,'A'); // Ausgabe eines Zeichens while (1); // Endlosschleife } -------------------------------------------------------------------- Der Hintergedanke bei der Sache: Ein einmal angeworfenes Senden von Daten soll interrupt-gesteuert die weiteren Zeichen senden, bis ein Sendepuffer leer ist. Parallel dazu sollen empfangene Zeichen in der Interrupt-Routine in einen Empfangspuffer kopiert werden (ich benötige später also den RXNE- und den TXE-Interrupt). Zunächst sollte es aber mit TXE funktionieren! Ich hoffe, jemand hat mir den entscheidenden Tip!
Rainer Reusch schrieb: > bis ein > Sendepuffer leer ist. und dann? Was stellst du dir dann vor? Anstatt diese unsägliche ST-Lib zu verwenden und das auch noch zu posten, solltest du lieber direkt im HW-Manual nachlesen, wie und wann genau ein Interrupt zustande kommt. Im Prinzip mußt du deine Sendedaten in deinem UART-Treiber puffern und für den Fall, daß der physische Sender bereits leergelaufen IST, einen passenden Soft-Interrupt auslösen, damit das Interruptgeschehen wieder in Gang kommt. klaro? W.S.
Hi! Ich habe einen F207, aber folgende Routine geht bei mir:
1 | void USART2_IRQHandler(void) { |
2 | // check if the USART2 receive interrupt flag was set
|
3 | if (USART_GetITStatus(USART2, USART_IT_RXNE)) |
4 | {
|
5 | USART_ClearITPendingBit(USART2, USART_IT_RXNE); |
6 | *(UART_Fifo[UART_Fifo_Index[UART2_NO]])=USART_ReceiveData(USART2); |
7 | UART_Fifo_Index[UART2_NO]++; |
8 | if (UART_Fifo_Index[UART2_NO]>=Serial_Fifo_Length[UART2_NO]) { UART_Fifo_Index[UART2_NO]=0; UART_Fifo_Ovfl[UART2_NO]=1; } |
9 | }
|
10 | |
11 | if (USART_GetITStatus(USART2, USART_IT_TXE)) |
12 | {
|
13 | USART_ClearITPendingBit(USART2, USART_IT_TXE); |
14 | USART_SendData(USART2,'g'); |
15 | }
|
16 | }
|
17 | |
18 | void SendCharPolling(USART_TypeDef* UART_X, char c) |
19 | {
|
20 | USART_SendData(UART_X, c); |
21 | /* Loop until the end of transmission */
|
22 | while (USART_GetFlagStatus(UART_X, USART_FLAG_TC) == RESET) {} |
23 | }
|
24 | |
25 | main.. |
26 | ..
|
27 | SendCharPolling(USART2,'h'); |
28 | ..
|
sendet ein 'h' und dann lauter 'g's!
W.S. schrieb: > und dann? Was stellst du dir dann vor? Macht er etwas anderes anwendungsspezifisches. > Anstatt diese unsägliche ST-Lib zu verwenden und das auch noch zu > posten, solltest du lieber direkt im HW-Manual nachlesen, wie und wann > genau ein Interrupt zustande kommt. Zusammenhang? Die ST-Lib ist für den Anfänger durchaus schon ganz gut, der Code ist deutlich weniger kryptisch als direkte Register-Zugriffs-Layer. > Im Prinzip mußt du deine Sendedaten in deinem UART-Treiber puffern Das ist ihm wohl bekannt. > und > für den Fall, daß der physische Sender bereits leergelaufen IST, einen > passenden Soft-Interrupt auslösen Bockmist. Er muss lediglich ein Byte senden, der TXE Interrupt kommt dann von alleine. > damit das Interruptgeschehen wieder in Gang kommt. Warum einen "Soft-"(hä?) Interrupt auslösen um ein Byte zu senden?! Rainer Reusch schrieb: > Die Zeichenausgabe funktioniert, wenn bei der Initialisierung NICHT der > TXE-Interrupt (Interrupt, wenn Sendepuffer leer ist) verwendet wird! Was heißt "funktioniert nicht"? Wird gar nichts gesendet? Crasht der Controller? Hast du da mal step-by-step-debuggt?
Hallo Leute! Zunächst einmal: Als Anfänger auf diesem Gebiet möchte ich mich nicht gerade einstufen. Ich verwende die "unsägliche" ST-Lib gerne als Einstieg in eine Aufgabenstellung. Wenn dann alles läuft, ersetze ich die ST-Lib-Aufrufe größtenteils durch direkte Registeraufrufe. Und so schlecht ist die ST-Lib nun auch wieder nicht! Sie funktioniert und hat einen recht guten selbstdokumentierenden Charakter. Sie ist eben nicht so effizient. Zurück zum Problem. Es geht hier nicht darum, wie ich Zeichen aus einem Puffer über serielle Schnittstelle bekomme. Das kriege ich ohne fremde Hilfe hin. Das aufgeführte Listing soll aufzeigen, wo das Problem liegt und ist deshalb darauf reduziert. Wenn in der Initialisierung die Zeile USART_ITConfig(USART2,USART_IT_RXNE,ENABLE); verwendet wird, wird das 'A' ausgegeben (siehe "main"). Wenn stattdessen USART_IT_TXE verwendet wird, kommt absolut nichts raus (auch mit dem Oszi geprüft). Der Controller geht in while-Schleife, führt den Sendebefehl also scheinbar aus. Dieses Verhalten erscheint mir nicht logisch. Ich interpretiere "USART_IT_TXE" so, dass ein Interrupt ausgelöst wird, wenn das Zeichen im USART-Senderegister (USART2->DR) gesendet ist. Unabhängig davon, was in der Interrupt-Routine getan wird, sollte es beim ersten mal doch auf jeden Fall funktionieren. Oder sehe ich das falsch? Vielleicht noch ergänzend zur Erklärung: Ich muss mit einem "Programmable Gain Amplifier" kommunizieren. Der braucht am Anfang das Zeichen 0x55, das ich als erstes sende. Nachdem dieses Zeichen gesendet ist, soll ein Interrupt ausgelöst werden, der das nächste Zeichen aus einem Puffer holt und ausgibt. Das Synchronisationszeichen bringt die Übertragung im Hintergrund also in Gang. Diese Geschichte kommt aber erst später (wenn das geschilderte Problem gelöst ist). An alle STM32F4xx-Programmierer: Hat jemand vielleicht ein funktionierendes Beispiel für den STM32F4xx, bei dem USART_IT_TXE verwendet wird? Oder vielleicht den entscheidenden Tipp? Es wäre schön, wenn dieser Thread beim Thema bleiben würde. Andere haben sicherlich das gleiche Problem, es vielleicht nur nicht gewagt, es hier zu schildern.
Hi Rainer "USART_IT_TXE" gibt es nicht als Parameter beim Aufruf von "USART_ClearITPendingBit" schau mal in der "stm32f4xx_usart.c" nach vlt ist da dein Fehler Gruss Uwe
Hallo Uwe! Vielen Dank für den Hinweis. Das ist zwar nicht die Lösung, aber der für mich entscheidende Tipp! Das TXE-Flag findet sich im Register USART_SR (Statusregister) und kann nur gelesen werden. Zurück gesetzt wird es durch ein Schreiben in USART_DR. Aus diesem Grund ist der Aufruf von USART_ClearITPendingBit(USART2,USART_IT_TXE) zwar möglich, aber sinnlos. Aufgrund von Uwes Hinweis bin ich wieder zu meiner urspünglichen Lösung mit USART_IT_TC (transmission complete) zurück gekehrt, die, im Nachhinein betrachtet, wegen eines anderen Programmfehlers ursprünglich auch nicht funktionierte. Damit geht es jetzt und das so, wie gewünscht! (an Uwe: Ich war fast schon so weit, das USART-Modul deiner ganz hervorragenden Funktionsbibliothek aus zu probieren!) Natürlich ist die ursprüngliche Frage noch nicht beantwortet. Warum funktioniert die USART-Ausgabe überhaupt nicht mehr, wenn der "TXE-Interrupt" aktiviert wird? Auf das Setzen des TXE-Flags zu reagieren, hätte den (zeitlichen) Vorteil, dass man schon das nächste Zeichen in das Datenregister (USART_DR) schreiben könnte, während die Übertragung noch läuft (das war meine Grundidee). Das Beispiel von Sepp für den STM32F207 belegt, dass es bei dieser Controller-Familie so wohl funktioniert. Also doch ein Bug irgendwo im STM32F4xx-Gedöns?
Rainer Reusch schrieb: > Warum > funktioniert die USART-Ausgabe überhaupt nicht mehr, wenn der > "TXE-Interrupt" aktiviert wird? Weil das Flag in Deinem Interrupt Handler nicht zurück gesetzt wird. In diesem Fall wird der Handler beim Verlassen sofort neu aufgerufen - siehe Tail-Chaining. Das würde man sehen, wenn man in der Haupschleife eine LED blinken lässt. Im o.g. Fall hört sie auf zu blinken, da die Haupschleife nicht mehr ausgeführt wird.
wenn es dir um "performance" geht, warum nimmst du dann nicht den DMA ? Für solche Sachen ist der doch da. UB
@ Jim Meba: Ja, ich glaube in dieser Richtung muss man wohl die Antwort suchen. Ich werde mir das noch genauer anschauen. Die Frage ist dann: Wie wendet man den "TXE-Interrupt" richtig an? @ Uwe: Auf Performance kommt es in meiner Aufgabenstellung garnicht an (zumindest nicht an dieser Stelle). Die Baudrate beträgt gerade mal 4800 Baud und der anzusprechende PGA lässt sich ein paar Millisekunden Zeit mit der Antwort. Hinzu kommt, dass diese Kommunikation nur selten abläuft. Sie soll daher gewissermaßen im Hintergrund (Interrupt-gesteuert) ablaufen, ohne die eigentlichen Abläufe nennenswert zu beeinträchtigen (niedrige Interrupt-Priorität). Gerade für Abläufe im Hintergrund wäre DMA eigentlich ideal. Ich habe anfangs auch mit diesem Gedanken gespielt, es aber gelassen, weil ich mich mit DMA noch nicht eingehend beschäftigt habe. Die Interrupt-Lösung hat auch den Vorteil, dass ich Pufferüberläufe per Software verhindern kann (würde bei DMA aber vermutlich auch gehen). Wenn mich die Muße küsst, werde ich mir diesen Lösungsweg vielleicht mal anschauen...
Rainer Reusch schrieb: > Wie wendet man > den "TXE-Interrupt" richtig an? Was steht denn dazu Im Handbuch/Reference Manual? Normalerweise schreibt man im Interrupt das nächste Zeichen in den UART oder schaltet das TXE-Bit irgendwie ab, wenn es nix mehr zu senden gibt.
Hallo, der TXE Interrupt wird folgendermaßen genutzt:
1 | void USART1_IRQHandler(void) { |
2 | if (USART_GetITStatus(USART1, USART_IT_TXE)) { |
3 | uint8_t get; |
4 | |
5 | switch (readFromBuf(&txBuf, &get)) { |
6 | case NO_ERROR: |
7 | USART_SendData(USART1, get); |
8 | break; |
9 | case BUFFER_EMPTY: |
10 | /* tx buffer empty, disable interrupt */
|
11 | USART_ITConfig(USART1, USART_IT_TXE, DISABLE); |
12 | break; |
13 | }
|
14 | }
|
15 | }
|
16 | |
17 | uint8_t uartPutChar(uint8_t c) { |
18 | writeToBuf(&txBuf, c); |
19 | /* enable interrupt */
|
20 | USART_ITConfig(USART1, USART_IT_TXE, ENABLE); |
21 | |
22 | return 0; |
23 | }
|
Im Interrupt wird geprüft ob was im Buffer steht. Wenn ja, wird gesendet. Wenn nicht, wird der Interrupt disabled. Die Funktion die ich zum Senden von Zeichen benutze, Schreibt das Zeichen in den Buffer und enabled den Interrupt. Kann man auch enpassen und erst einen ganzen String in den Buffer schreiben und dann den Interrupt enablen. Je nach dem wie man es haben will. Das TXE Flag braucht man nicht zurücksetzen.
:
Bearbeitet durch User
Hallo STM32-Fans! Das Beispiel von Michael N. bringt es auf den Punkt! Der TXE-Interrupt darf erst aktiviert werden, nachdem ein Zeichen (das erste) in USARTx->DR geschrieben wurde. Damit wird die Übertragung eines Pufferinhalts angeworfen. In der Interrupt-Behandlung wird dann einfach das nächste Zeichen geschrieben oder der TXE-Interrupt abgeschaltet, wenn es nichts mehr zu senden gibt. Die Übertragung unter Verwendung des TXE-Interrupts dürfte etwas effektiver sein als die Verwendung des TC-Interrupts. Der Interrupt wird bereits ausgelöst, während die eigentliche Übertragung noch läuft (Zeichen wurde vom DR-Register in das Schieberegister übertragen). So ist gewährleistet, dass das nächste Zeichen zur Übertragung schon bereit steht. Darüber hinaus entfällt das Zurücksetzen eines Flags in der Interrupt-Behandlung (das TXE-Flag wird mit den Schreiben des DR-Registers gelöscht). Mein Dank gilt allen, die zur Lösung dieses (zwar kleinen aber ärgerlichen) Problems beigetragen haben.
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.