Forum: Mikrocontroller und Digitale Elektronik Uart String empfangen Interrupt


von Marco (Gast)


Lesenswert?

Hallo zusamen,

ich habe eine Frage zum UART eine NXP Controllers; KEA128.

Den UART habe ich soweit am Laufen. Ich kann Zeichen senden und 
empfangen, ich kann Strings senden.
Beim Empfangen von Strings habe ich allerdings ein Problem.

Der Controller empfängt einen String, vom PC aus gesendet, sendet diesen 
dann auch wieder an den PC, aber
bleibt dann immer an einer Stelle hängen; in der Funktion "rx_char".

Hier an der Stelle "while(!(p->S1&UART_S1_RDRF_MASK));".
Ich kann dann weitere Strings an den Controller senden und er sendet 
diese als Echo zurück an den PC, aber der Controller bleibt eben immer
an der oben genennten Stelle endlos hängen.

Kann mir da vielleicht jemand auf die Sprünge helfen?


Folgend mal die entsprechenden Funktionen:
1
/**************************/
2
char rx_char(Ptr)
3
{
4
    char rec;
5
6
    while (!(p->S1&UART_S1_RDRF_MASK));                      
7
    rec = p->D;                      
8
    return rec;                        
9
}
10
11
/**************************/
12
void tx_char(Ptr p, uint8_t send)
13
{
14
  while (!(p->S1&UART_S1_TDRE_MASK));      
15
  p->D = (uint8_t)send;              
16
}
17
18
/**************************/
19
void tx_string(Ptr p, char data_str[])
20
{
21
    int i = 0;
22
    while(data_str[i] != '\0')          
23
    {
24
        tx_char(p, data_str[i]);
25
        i++;
26
    }
27
}
28
29
/**************************/
30
void rx_echo_string(Ptr p)
31
{
32
  char rec[30];
33
  uint8_t j = 0;
34
  char n_char;
35
36
  while((n_char = rx_char(p)) != '\0')
37
  {
38
    rec[j++] = n_char;
39
    tx_char(p, n_char);
40
  }
41
42
  rec[j] = '\0' ;
43
  tx_string(p, rec);
44
}
45
46
/**************************/
47
void UART2_IRQHandler( void )
48
{
49
  (void)UART2_S1;    
50
  rx_echo_string( UART2 );
51
}



Gruß
Marco

von Tom (Gast)


Lesenswert?

Marco schrieb:
> Hier an der Stelle "while(!(p->S1&UART_S1_RDRF_MASK));".
> Kann mir da vielleicht jemand auf die Sprünge helfen?

Er wartet in der while Schleife auf den Empfang eines Zeichens.

von Mario M. (thelonging)


Lesenswert?

Es ist allgemein eine schlechte Idee in einer Interrupt-Routine auf 
etwas zu warten. Übergib die empfangenen Daten an das Hauptprogramm und 
sende dort das Echo.

von Tom (Gast)


Lesenswert?

Marco schrieb:
> void UART2_IRQHandler( void )

Das ist anscheinend eine ISR, die läuft parallel zu der while-Schleife.
Er sendet echo-Strings, hängt ansonsten aber in der while-Schleife fest

von Rex Gildo (Gast)


Lesenswert?

Ich würde in der Empfangs ISR lediglich einen Fifo befüllen und
im Hauptprogramm abfragen, ob etwas im Fifo ist. Wenn ja aus dem
Fifo auslesen und echo.
Der Fifo hat den Vorteil, dass er nicht mit der ISR synchronisiert 
werden
muss, solange es nur einen Leser und nur einen Schreiber gibt.

von Marco (Gast)


Lesenswert?

Hallo zusammen,

vielen Dank für die Antworten.

Ich werde dann wohl mal versuchen das Ganze so umzubauen, dass die Daten 
in der ISR nur ausgelesen werden und der Rest in der Hauptschleife 
passiert.

Ich frage mich nur, warum der scheinbar selbst nach dem Empfang des 
letzten Zeichens noch einmal in die ISR springt. Eigentlich sollte diese 
und somit der Funktionsaufruf  rx_echo_string( UART2 ); doch nur kommen, 
wenn ein Zeichen empfangen wurde. Da aber der komplette String zurück an 
den PC gesendet und keine weiteren Zeichen an den Controller gesendet 
wurden, dürfte der doch eigentlich gar nicht mehr da landen und dann 
natürlich auf ein Zeichen warten, weil ja auch keines gesendet wurde.

Gruß
Marco

von M.K. B. (mkbit)


Lesenswert?

Marco schrieb:
> Da aber der komplette String zurück an den PC gesendet und keine
> weiteren Zeichen an den Controller gesendet wurden

Bist du dir da sicher? Wird in deinem Fall das Zeichen '\0' wirklich an 
den Controller geschickt?

von Marco (Gast)


Lesenswert?

Hallo,

ich habe mir mal die Zeichen direkt nach dem Empfang ausgeben lassen.
Das \0 wird an den Controller gesendet.
Erst nach dem Empfang wird der String ja wieder zurück an den PC 
gesendet. Also sollte \0 erkannt werden.

Gruß
Marco

von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

Bei meinen Programmen ist der USART_RX Handler so aufgebaut, daß er die 
empfangenen Zeichen in einen Puffer schreibt und auf eine 
String-Ende-Markierung (z.B. CR/LF oder NUL) ein Flag setzt, daß ein 
vollständiger String empfangen wurde. Gleichzeitig wird auf einen 
zweiten Puffer gewechselt, damit dort der nächste empfangene String 
abgelegt werden kann, während sich das Hauptprogramm mit dem ersten 
beschäftigt.

Das aktuelle Projekt (wobei dieser Teil schon so gut wie fertig ist), 
ist ein Terminal für eine Alarmanlage. Da werkelt ein ATMega88, der 
kümmert sich um das Tastenfeld und die LCD-Ausgabe. Dafür hat der 5x24 
Zeichen Puffer (worst case 2x20 Zeichen fürs LCD mit Steuerzeichen und 
drei Kurzkommandos).

von Marco (Gast)


Lesenswert?

Hallo,

ein Flag zu setzen und dann daraufhin in der Hauptschleife den 
empfangenen String auszuwerten hatte ich mir hier nach den ganzen 
Antworten hier auch schon mal skizziert.
Das mit dem zweiten Buffer, damit der erste in Ruhe ausgewertet werden 
kann und neue Daten nicht verloren gehen hatte ich noch nicht dran 
gedacht. Werde ich aber aifnehmen. Macht Sinn.
Vielen Dank!

Stellst du evtl. eine Teilfunktion mal zur Verfügung?
Ich weiß, selber von grundauf zu machen ist immer besser ;)

Vielen Dank!

Gruß
Marco

von Ben B. (Firma: Funkenflug Industries) (stromkraft)


Lesenswert?

Würde Dir leider nicht viel nutzen, alles in AVR-Assembler.

Aber so schwer ist es nicht.

Die ISR merkt sich einfach welcher Puffer gerade der aktuelle ist und an 
welche Stelle im Puffer zuletzt geschrieben wurde. Wird ein Zeichen 
empfangen, wird es in den Puffer geschrieben und geprüft ob es eine 
Ende-Markierung ist. Wenn ja, wird das "fertig-Flag" für diesen Puffer 
gesetzt, der aktuelle Puffer wird auf den nächsten (0..4) gesetzt und 
die gemerkte Schreibposition auf Null zurückgesetzt. Dazu noch ein 
kleiner Schutz, daß der Puffer nicht überlaufen wird wenn mal mehr 
Zeichen ankommen als eine Zeile haben darf (wenn der Puffer voll ist 
werden alle Zeichen bis zur nächsten Ende-Markierung verworfen), mehr 
macht die ISR nicht.

Das Hauptprogramm fragt fortlaufend alle 5 Puffer ab und führt quasi die 
Befehle aus, die in den als fertig markierten Puffern drinstehen. Wenn 
ein Befehl ausgeführt wurde, wird das fertig-Flag wieder gelöscht und 
der Spaß kann von vorne beginnen.

Probleme (Datenverlust) gibt es erst wenn das Hauptprogramm es nicht 
schafft, den ersten Befehl abzuarbeiten bis die restlichen 4 Puffer 
gefüllt sind. In dem Fall würde der gerade ausgeführte Befehl 
überschrieben werden und beide Routinen (die ISR und das Hauptprogramm) 
kommen sich mit den Flags in die Quere. Wenn man das verhindern will, 
muß die ISR noch prüfen, ob der neu zu verwendete Puffer als frei 
markiert ist. In dem Fall würde der neu ankommende Befehl ins Leere 
laufen weil die ISR ihn nirgendwo speichern kann. Vielleicht ändere ich 
das bei mir nochmal, weils die elegantere Variante ist.

von W.S. (Gast)


Lesenswert?

Marco schrieb:
> Den UART habe ich soweit am Laufen. Ich kann Zeichen senden und
> empfangen, ich kann Strings senden.
> Beim Empfangen von Strings habe ich allerdings ein Problem.

Du bist völlig unsystematisch an die Sache herangegangen. Und dein 
geposteter Code danach aus, nämlich wirr.
Beispiel:
void tx_char(Ptr p, uint8_t send)
Das ist Mumpitz, denn ein zu sendendes Zeichen ist ein char und keine 
wie auch immer geartete Integer-Zahl!

Zuerst mal ne Klarstellung: Ein UART kennt keine Strings und auch der 
Transport von Zeichen auf einer seriellen Strippe kennt keine Strings.

Folglich mußt du dir einen UART-Treiber schreiben, der Einzelzeichen 
entgegennimmt und auch Einzelzeichen liefert und der alle Zeichen 
zwischenpuffert, um die eigentliche Hardware-Schnittstelle von seinem 
Software-Interface zu entkoppeln. Das Software-Interface sollte etwa so 
ähnlich aussehen:
1
extern char  V24Char_Out  (char c); /* Zeichen senden */
2
extern int   V24numTxFree (void);  /* Anzahl freier Plätze im Sendepuffer */
3
extern bool  V24RxAvail   (void);  /* ob Zeichen empfangen wurden */
4
extern char  V24GetChar   (void);  /* empfangene Zeichen abholen */
5
extern void  V24TxDone    (void);  /* warten bis Sendepuffer geleert ist */
Und auf diesem Interface dürfen dann alle anderen Funktionen aufbauen, 
also Senden von Strings oder Ausgabe von Zahlen im Klartext und so 
weiter.

Der Interrupt dient IMMER nur zum Bedienen der Hardware und NICHT zu 
irgendwelchen programminternen Dingen wie z.B. der Ende-Erkennung von 
Strings und dergleichen.

So. Es sind 2 Ringpuffer nötig, dazu jeweils zwei Indizes pro Puffer 
(einer zum Befüllen, der andere zum Entnehmen). V24Char_Out(..) befüllt 
den Sendepuffer und veranlaßt die Freigabe des Interrupts für 
UART-Sendepuffer-Leer. Diese Funktion benutzt NUR den Befüll-Index und 
greift auf den Entnehmen-Index nur LESEND zu. Der Sende-Teil des 
Interrupt-Handlers benutzt NUR den Entnehmen-Index und greift auf den 
Befüllen-Index nur LESEND zu, um herauszukriegen, wann der Puffer leer 
ist.

Die Empfangsseite geht sinngemäß.

So herum macht man das.

W.S.

von Markus S. (acepilot)


Lesenswert?

Marco schrieb:
> Das mit dem zweiten Buffer, damit der erste in Ruhe ausgewertet werden
> kann und neue Daten nicht verloren gehen hatte ich noch nicht dran
> gedacht. Werde ich aber aifnehmen. Macht Sinn.
> Vielen Dank!

Dein Controller hat ja mit bis zu 16KByte RAM genug speicher. Es gäbe 
auch die Möglichkeit das Senden und Empfangen von Daten komplett in eine 
ISR mit einem größeren Ringpuffer zu erledigen.

Damit kannst du dir ganz einfach in deiner Hauptschleife die Empfangenen 
Daten einsammeln und verarbeiten.

Habe dir mal ein Beispiel rauskopiert aus einer Schnitstelle die mit 
einem ESP kommuniziert. Der Code unten läuft so auf einem STM32F103, 
hatte den Code damals selber aus einem Beispiel kopiert ...

Header File:
1
#define ESP_TX_BUFFER_SIZE    256
2
#define ESP_RX_BUFFER_SIZE    256
3
4
#if ESP_TX_BUFFER_SIZE < 2
5
#error ESP_TX_BUFFER_SIZE is too small, It must be larger then 1.
6
#elif ((ESP_TX_BUFFER_SIZE & (ESP_TX_BUFFER_SIZE-1)) != 0 )
7
#error ESP_TX_BUFFER_SIZE must be a power of 2
8
#endif
9
10
#if ESP_RX_BUFFER_SIZE < 2
11
#error ESP_RX_BUFFER_SIZE is too small, It must be larger then 1.
12
#elif ((ESP_RX_BUFFER_SIZE & (ESP_RX_BUFFER_SIZE-1)) != 0 )
13
#error ESP_RX_BUFFER_SIZE must be a power of 2
14
#endif
15
16
int SendESPChar (int c);
17
int GetESPKey (void);

Code File:
1
struct esp_tx_buf_st {
2
  unsigned int in;                                // Next In Index
3
  unsigned int out;                               // Next Out Index
4
  char buf [ESP_TX_BUFFER_SIZE];                  // Buffer
5
};
6
7
struct esp_rx_buf_st {
8
  unsigned int in;                                // Next In Index
9
  unsigned int out;                               // Next Out Index
10
  char buf [ESP_RX_BUFFER_SIZE];                  // Buffer
11
};
12
13
static struct esp_rx_buf_st esp_rbuf = { 0, 0, };
14
#define SIO_ESP_RBUFLEN ((unsigned short)(esp_rbuf.in - esp_rbuf.out))
15
static struct esp_tx_buf_st esp_tbuf = { 0, 0, };
16
#define SIO_ESP_TBUFLEN ((unsigned short)(esp_tbuf.in - esp_tbuf.out))
17
18
volatile uint8_t ESP_tx_restart = 1;
19
20
// USART3 Handles the complete ESP Communication
21
void USART3_IRQHandler(void)
22
{
23
  struct esp_rx_buf_st *p_rx;
24
  struct esp_tx_buf_st *p_tx;
25
26
  if (USART_GetITStatus(USART3, USART_IT_ERR) != RESET)
27
  {
28
    ESP_tx_restart = 0;
29
  }
30
31
    if (USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)            // read interrupt
32
    {
33
      p_rx = &esp_rbuf;
34
35
       if (((p_rx->in - p_rx->out) & ~(ESP_RX_BUFFER_SIZE-1)) == 0 )
36
    {
37
         p_rx->buf[p_rx->in & (ESP_RX_BUFFER_SIZE-1)] = USART_ReceiveData(USART3);
38
         p_rx->in++;
39
    }
40
41
       if (SIO_ESP_RBUFLEN >= (ESP_RX_BUFFER_SIZE-1))
42
       {
43
         USART_ITConfig(USART3, USART_IT_RXNE, DISABLE);            // Verhindert einen Pufferüberlauf
44
       }
45
    }
46
47
    if (USART_GetITStatus(USART3, USART_IT_TXE) != RESET)            // read interrupt
48
    {
49
      p_tx = &esp_tbuf;
50
51
      if (p_tx->in != p_tx->out)
52
      {
53
        USART_SendData(USART3,p_tx->buf [p_tx->out & (ESP_TX_BUFFER_SIZE-1)]);
54
        p_tx->out++;
55
        ESP_tx_restart = 0;
56
      }
57
      else
58
      {
59
        ESP_tx_restart = 1;
60
        USART_ITConfig(USART3, USART_IT_TXE, DISABLE);
61
      }
62
63
       USART_ClearITPendingBit(USART3, USART_IT_TXE);
64
    }
65
}
66
67
int SendESPChar (int c)
68
{
69
  struct esp_tx_buf_st *p = &esp_tbuf;
70
                          // If the buffer is full, return an error value
71
  if (SIO_ESP_TBUFLEN >= ESP_TX_BUFFER_SIZE)
72
  return (-1);
73
74
  p->buf [p->in & (ESP_TX_BUFFER_SIZE - 1)] = c;           // Add data to the transmit buffer.
75
  p->in++;
76
77
  if (ESP_tx_restart) {                               // If transmit interrupt is disabled, enable it
78
    ESP_tx_restart = 0;
79
  USART_ITConfig(USART3, USART_IT_TXE, ENABLE);            // enable TX interrupt
80
  }
81
82
  return (0);
83
}
84
85
int GetESPKey (void)
86
{
87
88
  struct esp_rx_buf_st *p = &esp_rbuf;
89
90
  if (SIO_ESP_RBUFLEN == 0)
91
  {
92
    USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
93
    return (-1);
94
  }
95
  else if (SIO_ESP_RBUFLEN < (ESP_RX_BUFFER_SIZE-2))
96
  {
97
  USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);
98
  }
99
100
  return (p->buf [(p->out++) & (ESP_RX_BUFFER_SIZE - 1)]);
101
}

: Bearbeitet durch User
von Purzel H. (hacky)


Lesenswert?

Bei Strings ueber ein UART sollte man sich von der Idee der 
nullterminierten Strings verabschieden. Und statt dessen die Laenge des 
Strings zuerst senden. Das hat den Vorteil, dass man abzaehlen kann.

von W.S. (Gast)


Lesenswert?

Jetzt ist G. schrieb:
> Das hat den Vorteil...

Das geht den eigentlichen Handler überhaupt nichts an. Der nimmt Zeichen 
an und sendet sie. Ob da welche zu irgend einem String oder zu sonstwas 
gehören, ist ihm schnurz.

W.S.

von Stefan F. (Gast)


Lesenswert?

Marco schrieb:
> bleibt dann immer an einer Stelle hängen; in der Funktion "rx_char".
> Ich kann dann weitere Strings an den Controller senden und er sendet
> diese als Echo zurück an den PC

Wie meinst du das, er sendet weiterhin Echos an den PC zurück während er 
hängt?

von Marco (Gast)


Lesenswert?

Hallo,

Stefanus F. schrieb:
> Wie meinst du das, er sendet weiterhin Echos an den PC zurück während er
> hängt?

Damit ist gemeint, dass nicht der Controller an sich hängen bleibt. Wenn 
er in diesem Zustand verharrt oder eben in der While Schleife hängen 
bleibt, kann man weiterhin Nachrichten vom PC an den Controller senden, 
welche auch empfangen und wie gewünscht als Echo an den PC zurück 
gesendet werden. Nur die Funktionen der Hauptschleife werden nicht mehr 
durchlaufen.

Gruß
Marco

von Stefan F. (Gast)


Lesenswert?

Marco schrieb:
> Nur die Funktionen der Hauptschleife werden nicht mehr durchlaufen.

> Hier an der Stelle "while(!(p->S1&UART_S1_RDRF_MASK));".
> Ich kann dann weitere Strings an den Controller senden und er sendet
> diese als Echo zurück an den PC, aber der Controller bleibt eben immer
> an der oben genennten Stelle endlos hängen.

Diese Zeile befindet sich jedoch in der Funktion tx_char() welche von 
der Empfangs-Interruptroutine aufgerufen wird, um das Echo zu senden.

Also kann die Funktion tx_char() nicht grundsätzlich falsch sein.

Was mir allerdings aufgefallen ist:

Der Receive-Interrupt wird vermutlich für jedes empfangene Zeichen 
einzeln aufgerufen. Du empfängst darin aber nicht nur ein einzelnes 
Zeichen, sondern wartest auf einen kompletten String! Während dieser 
Warteschleife wird die Hauptschleife nicht ausgeführt (ist ja kein 
Dual-Core Prozessor).

Ich denke, dort liegt irgendwo der Hund begraben.

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.