Forum: Mikrocontroller und Digitale Elektronik STM32F405, USART2: Mit TXE-Interrupt funktioniert es nicht


von Rainer R. (Firma: Reusch Elektronik) (reusch)


Lesenswert?

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!

von W.S. (Gast)


Lesenswert?

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.

von Sepp (Gast)


Lesenswert?

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!

von Dr. Sommer (Gast)


Lesenswert?

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?

von Rainer R. (Firma: Reusch Elektronik) (reusch)


Lesenswert?

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.

von Uwe B. (derexponent)


Lesenswert?

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

von Rainer R. (Firma: Reusch Elektronik) (reusch)


Lesenswert?

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?

von Jim M. (turboj)


Lesenswert?

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.

von Uwe B. (derexponent)


Lesenswert?

wenn es dir um "performance" geht, warum nimmst du dann nicht den DMA ?
Für solche Sachen ist der doch da.

UB

von Rainer R. (Firma: Reusch Elektronik) (reusch)


Lesenswert?

@ 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...

von Jim M. (turboj)


Lesenswert?

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.

von Michael N. (betonmicha)


Lesenswert?

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
von Rainer R. (Firma: Reusch Elektronik) (reusch)


Lesenswert?

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
Noch kein Account? Hier anmelden.