Forum: Mikrocontroller und Digitale Elektronik UART echo bzw. loopback (STM32)


von Karl Heinz (Gast)


Lesenswert?

Hallo,

ich hab mal nach einer Funktion gesucht um einen USART loopback zu 
erzeugen. Also die Daten die man sendet so wieder zurueck zu erhalten.

Hier die Loesung fuer alle die auch danach gesucht haben:
1
while(1)
2
{
3
  /* Wait until a byte is received */
4
  while(USART_GetFlagStatus(USART2, USART_FLAG_RXNE) == RESET);
5
    
6
  RXBUFF[j] = USART_ReceiveData(USART2);
7
 
8
  if (USART_GetFlagStatus(USART2, USART_FLAG_TXE) != RESET)
9
    USART_SendData(USART2, RXBUFF[j]);
10
 
11
  j++;
12
  if (j >= 248)
13
    j = 0;
14
15
    if (userabort() == false)
16
    break;
17
}
18
19
bool userabort()
20
{
21
  bool status=true;// im normalfall true  = kein abbruch
22
  
23
  //Wenn 9 empfangen wird abbrechen!
24
  ReceivedData = USART_ReceiveData(USART2);
25
   if (ReceivedData == 9)
26
    status=false; //wenn false dann abbruch der schleife  
27
     
28
  return status;
29
30
}

von Dr. Sommer (Gast)


Lesenswert?

Und jetzt bitte noch mit Interrupt und Sleep mode, sodass der Core nicht 
jede Menge Rechenleistung/Energie aufs Nichtstun verschwendet.

von pompete (Gast)


Lesenswert?

Dr. Sommer schrieb:
> Und jetzt bitte noch mit Interrupt und Sleep mode, sodass der Core nicht
> jede Menge Rechenleistung/Energie aufs Nichtstun verschwendet.

gg

von Ralf (Gast)


Lesenswert?

@Dr.Sommer:
Och komm, das ist nicht dein Ernst, oder? Er meinte es gut und wollte 
ein bisschen was zur Community beitragen.
Als Basis, auf der man aufbauen kann reicht sein Beispiel doch, oder? 
Zeig mir ein Code-Beispiel, welches du eins zu eins übernehmen konntest. 
Ich wette in 99,99% der Fälle nimmst du Änderungen vor, um anzupassen - 
und sei es nur die Code-Kommentierung.

Ralf

von Ersi (cell85)


Lesenswert?

Hey,

ihr koennt das ja gerne mal mit Interrupt machen.
Waere dran interessiert!

Gruss
Sven

von Dr. Sommer (Gast)


Lesenswert?

Ralf schrieb:
> Ich wette in 99,99% der Fälle nimmst du Änderungen vor, um anzupassen -
> und sei es nur die Code-Kommentierung.

Sven S. schrieb:
> ihr koennt das ja gerne mal mit Interrupt machen.
> Waere dran interessiert!

Offenbar scheint diese Änderung doch nicht ganz trivial zu sein. 
Aufgrund des hohen-Coretakts macht die UART-Nutzung auf einem STM32 
eigentlich nur mit Interrupts Sinn und wäre daher im Anfangsbeispiel 
angebracht. Außerdem fehlt hier die ganze Initialisierung, und die ist 
aufgrund der Komplexität der STM32 durchaus interessant. So in etwa:
1
const int bufSize = 255;
2
uint8_t buffer [bufSize], bufFill, txPtr;
3
4
void myUsartInit () {
5
  // Clock des USART einschalten:
6
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
7
  RCC_APB2PeriphClockLPModeCmd(RCC_APB2Periph_USART1, ENABLE);
8
  RCC_AHB1PeriphClockCmd(RCC_AHB1ENR_GPIOBEN, ENABLE);
9
10
  // USART Pins einschalten
11
  GPIO_InitTypeDef GPIO_InitStructure;
12
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
13
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
14
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
15
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
16
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
17
  GPIO_Init(GPIOB, &GPIO_InitStructure);
18
  GPIO_PinAFConfig (GPIOB, GPIO_PinSource6, GPIO_AF_USART1);
19
20
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
21
  GPIO_Init(GPIOB, &GPIO_InitStructure);
22
  GPIO_PinAFConfig (GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
23
24
  // USART konfigurieren & einschalten
25
  USART_InitTypeDef uInit;
26
  USART_StructInit (&uInit);
27
  uInit.USART_BaudRate = 57600;
28
  uInit.USART_WordLength = USART_WordLength_8b;
29
  uInit.USART_StopBits = USART_StopBits_1;
30
  uInit.USART_Parity = USART_Parity_No;
31
  uInit.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
32
  uInit.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
33
  USART_Init (USART1, &uInit);
34
35
  USART_ClockInitTypeDef ucInit;
36
  USART_ClockStructInit (&ucInit);
37
  ucInit.USART_Clock = USART_Clock_Disable;
38
  USART_ClockInit (USART1, &ucInit);
39
40
  USART_Cmd (USART1, ENABLE);
41
42
  // Interrupt channel einschalten
43
  NVIC_InitTypeDef NVIC_InitStructure;
44
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
45
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
46
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
47
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
48
  NVIC_Init(&NVIC_InitStructure);
49
50
  // Interrupts einschalten
51
  USART_ITConfig (USART1, USART_IT_TXE, ENABLE); // TX register Empty - wir können was senden
52
  USART_ITConfig (USART1, USART_IT_RXNE, ENABLE); // RX register Not Empty - es wurde was empfangen
53
  
54
  bufFill = 0; txPtr = 0;
55
}
56
57
void USART1_IRQHandler (void) {
58
  // Können wir was senden?
59
  if (USART_GetFlagStatus (USART1, USART_FLAG_TXE) == SET) {
60
    // Gibts was zu senden?
61
    if (txPtr < bufFill) {
62
      // absenden
63
      USART_SendData (USART1, buffer [txPtr]);
64
      txPtr++;
65
    } else {
66
      // Wenn nicht - Interrupt abschalten, wir wollen nicht in Endlosschleife in diesem Interrupt landen
67
      USART_ITConfig (USART1, USART_IT_TXE, DISABLE);
68
    }
69
  }
70
  // Haben wir was empfangen?
71
  if (USART_GetFlagStatus (USART1, USART_FLAG_RXNE) == SET) {
72
    // Auslesen
73
    uint8_t rec = USART_ReceiveData (USART1);
74
    // Noch Platz im Puffer?
75
    if(bufFill < bufSize) {
76
      // Speichern
77
      buffer [bufFill] = rec;
78
      bufFill++;
79
      
80
      // TXE-Interrupt ein
81
      USART_ITConfig (USART1, USART_IT_TXE, ENABLE);
82
      // Können wir das direkt absenden?
83
      if(USART_GetFlagStatus (USART1, USART_FLAG_TXE) == SET) {
84
        // Absenden
85
        USART_SendData (USART1, buffer [txPtr]);
86
        txPtr++;
87
      } // Falls nicht, wird demnächst ein TXE-Interrupt eintreffen und wir können dann senden
88
    }
89
  }
90
}

In diesem Fall ist das etwas komplizierter als nötig - bei einem 
einfachen loopback müsste beim Empfang eines Bytes das TX-Register immer 
leer sein und man direkt senden können. Zu Demonstrationszwecken habe 
ich aber die Abfrage des "TX Register Empty" Flags eingebaut, d.h. es 
wird sichergestellt dass das TX-Register tatsächlich leer ist.

So, und jetzt darf wer anders das ganze mit DMA machen...

von Ersi (cell85)


Lesenswert?

Funktioniert der Code Dr. Sommer? (selbst getestet ?)

1)
Wie ich das jetzt verstehe, werden deine Empfangenen Daten in buffer[] 
reingeschrieben oder?
2)
Wie koennte ich denn jetzt was ich in buffer[] empfangen habe in meine 
Variablen schreiben, wenn ich mal annehme das mir die gegenstelle 
mehrere bytes geschickt hat.  also bspw. 5 Ints.

Ich will ja die Daten aus dem Buffer weiterverarbeiten.


3)
Aber ist ein Interrupt Empfang wirklich noetig wenn man bspw. nur in 
bestimmten faellen was empfangen moechte und nicht staendig?

Zum Beispiel gibts neben der Hauptroutine einen Empfangsmodus den der 
nutzer ab und an aktivieren kann.

In diesem Fall ist doch der Interrupt nicht noetig, da der 
Empfangsamodus nach getaner Arbeit verlassen wird.

Aber klar, du hast recht. Falls der loopback in der Hauptroutine laeuft 
ist es sehr sinnvoll!

gruss
Sven

von Dr. Sommer (Gast)


Lesenswert?

Sven S. schrieb:
> Funktioniert der Code Dr. Sommer? (selbst getestet ?)
Teilweise getestet, das wichtigste funktioniert auf jeden Fall, glaub 
aber nicht dass was falsch ist.
> 1)
> Wie ich das jetzt verstehe, werden deine Empfangenen Daten in buffer[]
> reingeschrieben oder?
Ja.
> 2)
> Wie koennte ich denn jetzt was ich in buffer[] empfangen habe in meine
> Variablen schreiben, wenn ich mal annehme das mir die gegenstelle
> mehrere bytes geschickt hat.  also bspw. 5 Ints.
buffer[] ist eine variable, du kannst die daten direkt da heraus 
verwenden. wenn du sie als int behandeln willst, musst du das nur dem 
compiler sagen:
uint32_t myInt = *((uint32_t*) &buffer);
Dies kopiert die ersten 4 bytes des "buffer" in eine int-Variable.

> 3)
> Aber ist ein Interrupt Empfang wirklich noetig wenn man bspw. nur in
> bestimmten faellen was empfangen moechte und nicht staendig?
Nicht zwangsweise nötig, aber äußerst sinnvoll - deine busy-wait-Methode 
mit while() verbraucht viel Energie (und der Controller wird warm), da 
der ganze Core aktiv ist, während man bei der Verwendung eines 
Interrupts die Hauptschleife mit __WFE(); schlafen legen kann, und der 
Core nur dann etwas tut, wenn tatsächlich etwas angekommen ist. Der 
Energieverbrauch ist dann nur noch ein kleiner Bruchteil (ich habs 
nachgemessen).


> Zum Beispiel gibts neben der Hauptroutine einen Empfangsmodus den der
> nutzer ab und an aktivieren kann.
>
> In diesem Fall ist doch der Interrupt nicht noetig, da der
> Empfangsamodus nach getaner Arbeit verlassen wird.

Nicht unbedingt noetig, aber sinnvoller. Außerden kannst du so noch 
gleichzeitig auf andere Events warten - zB auf einen Interrupt bei 
Tastendruck o.ä.

Mit DMA wär das ganze wohl noch effizienter, aber da hab ich grad keinen 
Code zur hand.

von Alex (Gast)


Lesenswert?

nochmal eine dummer Frage:

den Rx Interrupt verstehe ich ja...also wurde etwas empfangen, wird es 
an entsprechene Stelle in buffer geschrieben bis bufFil überläuft. Denn 
gehts wieder von vorne los. Aber das Senden wie läuft das genau ab?

von Thomas W. (diddl)


Lesenswert?

Ganz einfach:

Der RX Interrupt kann immer laufen. Kommt ein Zeichen speichert es die 
Interrupt Routine in den RX Ringbuffer.

Der TX Interrupt läuft, solange etwas im TX Ringbuffer ist. Mit dem 
letzten Zeichen im Buffer schaltet es automatisch den Interrupt aus.

Dann braucht es noch ein paar Funktionen um die Ringbuffer zu bedienen. 
Zeichen ausgeben legt ein Zeichen in den Ringbuffer und startet 
gegebenfalls den TX Interrupt. Eine Funktion guckt ob etwas im RX 
Ringbuffer ist. Eine dritte Funktion holt ein oder mehrere Zeichen aus 
dem Ringbuffer.

von Alex (Gast)


Lesenswert?

also die isr wird immer aufgerufen, wenn etwas empfangen wurde (RXNE) 
und wenn nichts mehr zu senden ist (TXE).

Wenn ich jetzt was senden möchte schreibe ich das in den buffer etwas 
rein oder auch nicht und setze, denn den Bufferfil entsprechend hoch 
also wen ich 1 zeichen senden will schreibe ich:
buffer[0]=0x2;
bufFil++;
 die isr wird denn aufgerufen weil ja TXE ist, er sendet denn mein 
zeichen, beim nächsten isr aufruf ist txPtr denn gleich buFil und der 
TXE interrupt wird diabled. der RXNE ist aber aber dieses mal gesetzt 
und es wird wider etwas in den buffer geschrieben bufFil wird um 1 
erhöht und der RXNE interrupt wird wieder enabled es kann wieder was 
gesendet werden.

von Thomas W. (diddl)


Lesenswert?

RX Isr wird aufgerufen, wenn ein Byte empfangen wurde.

TX Isr wird aufgerufen, wenn das Register bereit ist, ein neues Byte 
aufzunehmen. Es gibt zwei Register, das Schieberegister (das sendet) und 
ein TX Register. Sobald das Schieberegister das letzte Bit gesendet hat, 
übernimmt es das TX Register.

So kann man das TX register befüllen, noch während gesendet wird. Man 
hat dadurch eine durchgehende Sendung ohne Unterbrechung. Man hat 
relativ lange Zeit das TX register zu befüllen (abhängig von der 
transfer geschwindigkeit), so lange wie das Senden eines Bytes benötigt.

Die Ringbuffer muss man in Software implementieren.

von Ralf L. (Gast)


Lesenswert?

Hallo Dr. Sommer

Dank dir für den Loopback, er hat mir bei der Einarbeitung sehr 
geholfen! Nachdem die Examples in der Coocox Ide recht mager sind (oder 
miserabel strukturiert) würde ich diesen Code gerne dort posten und die 
Arbeit so für andere vereinfachen. Ist das möglich?

Hat schon jemand mit den Examples (USART) direkt von ST gearbeitet? 
Gerade beim USART scheint es mir so als wäre hier die Send Fkt. komplett 
unter den Tisch gefallen. Aber wie gesagt ich bin noch in der 
Einarbeitungsphase, vielleicht habe ich diese auch übersehen ;)

Grüße

Ralf L.

von Peter S. (psavr)


Angehängte Dateien:

Lesenswert?

Anbei noch eine Version:
- Integriert ins syscalls.c für printf() und scanf()
- Inklusive Backspace Unterstützung

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.