Forum: Mikrocontroller und Digitale Elektronik Stringvergleich bei UART


von TUC (Gast)


Lesenswert?

Hallo,

ich versuche bei der Kommunikation mit einem ESP8266 den empfangenen 
String auszulesen und daraufhin etwas auszulösen. Mit Problem liegt in 
der C - Programmierung des AVR:

1
 // ...Weitere Includes...
2
#include "uart.h"
3
4
char delimiter[] = ":";
5
char *token;
6
7
char rec;
8
  
9
char rec_conv[30];    // char-array fuer empfangene Zeichen
10
11
int i;
12
13
14
int main(void)
15
{
16
17
  for (i=0;i<30;i++){
18
19
    rec_conv[i]='\0';
20
21
  }
22
  
23
  DDRB = 0xFF;
24
25
  init_uart();      // Funktion: UART initialisieren
26
  
27
  sei();
28
  
29
  _delay_ms(1000);
30
    
31
  uart_puts("AT+CIPMUX=1");
32
  uart_puts("\r\n");
33
  _delay_ms(500);
34
  
35
  uart_puts("AT+CIPSERVER=1,8001");
36
  uart_puts("\r\n");
37
  
38
  while(1){
39
    
40
      while (uart_data_received()){  // Wenn Daten empfangen wurden
41
42
        rec = uart_getchar();  // Daten nach rec speichern
43
44
        sprintf(rec_conv,"%s%c",rec_conv,rec);  // Alte und neue Zeichen nach rec_conv
45
    
46
      }    
47
      
48
      // String teilen
49
      token = strtok(rec_conv, delimiter);
50
51
      while(token != NULL) {
52
        if (strcmp(token,"test")==0){
53
          PORTB = 0xFF;}
54
        else {
55
          PORTB = 0x01;
56
        }
57
        token = strtok(NULL, delimiter);
58
      }
59
        
60
      for (i=0;i<30;i++){  
61
62
        rec_conv[i]='\0';
63
64
      }
65
      
66
    }  
67
    
68
}

Wenn ich mit HTerm einen String sende und ich lasse die Variable 
"rec_conv" vor der Stringteilung ausgeben, erscheint exakt ein Echo. 
Soweit so gut.
Wenn ich jedoch den String am Doppelpunkt teile und anschließend 
vergleiche passiert leider nichts. Beispiel:

Senden -> 123:test
Der PortB schaltet jedoch nicht ein. Kann mir jemand helfen ?

Grüße
TUC

von Karl H. (kbuchegg)


Lesenswert?

TUC schrieb:

>       while (uart_data_received()){  // Wenn Daten empfangen wurden
>
>         rec = uart_getchar();  // Daten nach rec speichern
>
>         sprintf(rec_conv,"%s%c",rec_conv,rec);  // Alte und neue Zeichen
> nach rec_conv

Äh. Da bin ich jetzt überfragt, ob du das überhaupt darfst. Bei sprintf 
eine Quelle angeben und gleichzeitig diese Quelle als Buffer zu 
benutzen. Sei es wies sei: sprintf ist da mit Kanonen auf Spatzen. Du 
möchtest einen strcat benutzen. Dazu musst du aus rec einen String 
machen, was weiter kein grosses Problem ist.
1
char rec[2] = " ";
2
3
4
        ...
5
        rec[0] = uart_getchar();
6
        strcat( rec_conv, rec );
7
      }

aber das ist nicht dein eigentliches Problem. Dein eigentliches Problem 
besteht darin, dass du hier
1
       while (uart_data_received()){  // Wenn Daten empfangen wurden
2
         ...
3
       }
4
5
       // Annahme: der String wurde fertig übertragen

viel zu optimistisch bist.

Nur weil momentan kein Zeichen in der UART vorliegt, heisst das noch 
lange nicht, dass 30 Zeichen übertragen wurden!
UART Übertragung ist relativ langsam. Während sich da 1 Byte über die 
Leitung quält hat dein µC ein paar hundert mal festgestellt, dass zur 
Zeit kein Zeichen in der UART zur Abholung bereit steht. Du agierst wie 
jemand der 30 Sendungen von Amazon erwartet und früh morgens 30 mal im 
Postkasten nachsieht, ob was da ist und nach dem 30 mal nachsehen die 
'Übertragung der Ware' als beendet erklärt.

Das funktioniert so nicht.
Der Sender muss dir mit einem Zeichen mitteilen "Das wars jetzt. Jetzt 
ist die Übertragung fertig. Jetzt kannst du mit der Auswertung 
loslegen". Oft nimmt man zb das Zeilenendezeichen '\n' für diesen Zweck 
her. D.h. deine Schleife läuft so lange, so lange nicht dieses 
Zeilenende gesichtet wurde.
1
   rec = ' ';    // hier kann rec wieder ein einfacher char sein
2
                 // du wirst gleich sehen warum
3
   i = 0;
4
5
   do {
6
     if( uart_data_received() ) {
7
       rec = uart_getchar();
8
       rec_conv[i] = rec;
9
       i++;
10
     }
11
   } while( rec != '\n' );
12
13
   rec_conv[i] = '\0';
14
15
16
   // Jetzt hast du einen gültigen String.

Jetzt hast du hier einen gültigen String. Der Sender hat dir ja 
mitgeteilt, wann der String zu Ende ist.
Was man noch einbauen müsste. Die Absicherung, das der Sender nicht zu 
viel schickt, so dass das Array über läuft.

: Bearbeitet durch User
von Max H. (hartl192)


Lesenswert?

TUC schrieb:
> Der PortB schaltet jedoch nicht ein.
1
(token != NULL)
ist also immer falsch. Versuch mal den Empfangsbuffer vor dem
1
while(token != NULL)
auszugeben um zu testen ob deine Einlesroutine überhaupt richtig 
funktioniert.

von Karl H. (kbuchegg)


Lesenswert?

TUC schrieb:

> Wenn ich mit HTerm einen String sende

händisch getippt?

> und ich lasse die Variable
> "rec_conv" vor der Stringteilung ausgeben, erscheint exakt ein Echo.

Das kann nicht sein.
Da musst du dich irgendwo vertan haben.
Du magst ein korrektes Echo bekommen haben, in dem du die einzelnen 
Zeichen gleich nach dem Empfang zurück geschickt hast. Aber der String 
war nie und nimmer korrekt. So schnell kannst wiederrum du gar nicht 
tippen, dass du mit einer halbwegs moderaten Baudrate mithalten kannst 
und der µC nicht aus deiner while( received ) Schleife rausgefallen 
wäre.

von TUC (Gast)


Lesenswert?

Es funktioniert jetzt, vielen Dank !!

1
  while(1){
2
    i = 0;
3
       do {
4
         if( uart_data_received() ) {
5
           rec = uart_getchar();
6
           rec_conv[i] = rec;
7
           i++;
8
         }
9
       } while( rec != '\r' );
10
11
       rec_conv[i] = '\0';
12
       
13
       uart_puts(rec_conv);
14
      
15
      // String teilen
16
      token = strtok(rec_conv, delimiter);
17
18
      while(token != NULL) {
19
        if (strcmp(token,"test\r")==0){
20
          PORTB = 0xFF;}
21
        else {
22
          PORTB = 0x01;
23
        }
24
        token = strtok(NULL, delimiter);
25
      }
26
        
27
      for (i=0;i<30;i++){  
28
29
        rec_conv[i]='\0';
30
31
      }
32
      
33
    }

Sende ich die Asciifolge "123:test" schaltet PortB komplett ein. Sende 
ich hingegen nur "123test", ist nur B0 eingeschaltet.

LG TUC

von Max H. (hartl192)


Lesenswert?

Karl Heinz schrieb:
> So schnell kannst wiederrum du gar nicht
> tippen, dass du mit einer halbwegs moderaten Baudrate mithalten kannst
Bei Hterm kann man auch den gesamten Text schreiben und erst wenn man 
mit Enter bestätigt wird alles hintereinander gesendet. Wenn der µC aber 
schon fertig ist mit dem sprintf bevor das nächste Zeichen da ist 
verlässt er aber trotzdem die while().

von Karl H. (kbuchegg)


Lesenswert?

Max H. schrieb:
> Karl Heinz schrieb:
>> So schnell kannst wiederrum du gar nicht
>> tippen, dass du mit einer halbwegs moderaten Baudrate mithalten kannst
> Bei Hterm kann man auch den gesamten Text schreiben und erst wenn man
> mit Enter bestätigt wird alles hintereinander gesendet.


Ja, da hast du recht.

Das ist aber trotzdem nicht die Erklärung für das was er ursprünglich 
gesehen hat (mit seiner Beschreibung, dass das Echo stimmen würde).

Das korrekte Echo stammt nicht daher, dass der eine String korrekt 
empfangen wurde, sondern daher, dass er viele Strings mit wahlweise 0 
oder 1 Zeichen zusammengebaut hat (manchmal mögen eventuell sogar 2 
Zeichen direkt hintereinander aus der UART gepurzelt sein). Und wenn man 
die echot, dann sieht es am Terminal so aus, als ob es nur 1 String 
wäre, in dem alle Zeichen drinn sind.

-> Es ist oft eine gute Idee, sich in Ausgaben zum Terminal ein paar 
Sonderzeichen einzubauen. Dann kann man besser beurteilen, wo am 
Terminal eine einzelne Ausgabe anfängt und wo sie endet. Hätte er im 
Originalcode sein Echo so gemacht:
1
  while(1){
2
    
3
      while (uart_data_received()){  // Wenn Daten empfangen wurden
4
5
        rec = uart_getchar();  // Daten nach rec speichern
6
7
        sprintf(rec_conv,"%s%c",rec_conv,rec);  // Alte und neue Zeichen nach rec_conv
8
    
9
      }    
10
11
      uart_putc( '#' );     // <--- der entscheidende Punkt
12
      uart_puts(rec_conv);
13
      uart_putc( '#' );
14
....

dann hätte er unmissverständlich gesehen, was da los ist. Der µC hätte 
ihm das Terminal mit '#' geflutet, während er tippt :-) Das wiederrum 
hätte ihn auf die Spur gebracht, dass die Schleifensteuerung über 
data_received keine gute Idee ist.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

TUC schrieb:
> Es funktioniert jetzt, vielen Dank !!

Vorsicht:

Du solltest hier
1
  while(1){
2
    i = 0;
3
       do {
4
         if( uart_data_received() ) {
5
           rec = uart_getchar();
6
           rec_conv[i] = rec;
7
           i++;
8
         }
9
       } while( rec != '\r' );
sicher stellen, dass rec nicht '\r' ist, wenn es das erste mal in die 
Schleife reingeht. Denn niemand sagt, dass da sofort wieder ein Zeichen 
aus der UART raus kommt. D.h. rec kann immer noch den Wert haben, den es 
aus dem 'Durchgang' vorher hatte. Und der war - Trommelwirbel - '\r'

Was natürlich sofort dazu führen würde, dass die Schleife gleich wieder 
abbricht.

wie gesagt:

derartige Ausgaben sind gut:
>        uart_puts(rec_conv);

Aber noch besser sind sie, wenn du sicherstellst, dass du sie auf keinen 
Fall übersehen kannst. Selbst dann nicht, wenn rec_conv ein leerer 
String ist
1
        uart_puts( "-> #" );
2
        uart_puts(rec_conv);
3
        uart_puts( "#\r" );

jetzt kannst du das auf keinen Fall mehr übersehen. Genausowenig wie du 
übersehen kannst, wenn am Anfang des Strings (oder am Ende) zusätzliche 
Leerezeichen drinnen stehen. Eine Ausgabe von
1
-> # 123.test
2
#
zeigt dir das unmissverständlich. Genauso wie dir der Zeilenumbruch im 
Terminal vor dem 2-ten # unmissverständlich anzeigt, dass da wohl ein 
'\r' im String sein muss, denn du sonst nicht sehen würdest.

von TUC (Gast)


Lesenswert?

Ja das ergibt Sinn, danke für den Tipp!

Deinen ersten Einwand kann ich aber noch nicht ganz nachvollziehen:
rec bekommt doch bei jedem Durchlauf (wenn etwas neues angekommen ist) 
erst einmal einen neuen Wert zugewiesen !?

Z.B.: Ich übertrage irgendetwas und das letzte Zeichen ist \r, dann ist 
rec='\r' und die Schleife bricht ab. Wenn nun beim nächsten mal 
festgestellt wird, dass etwas Neues angekommen ist, dann wird rec doch 
ersteinmal ein neuer Wert zugewiesen (wenn ich "abcd\r" übertrage z.B. 
'a') und die Schleife würde wieder durchlaufen !?
Vllt. habe ich das Problem auch einfach noch nicht erkannt.

LG TUC

von Karl H. (kbuchegg)


Lesenswert?

TUC schrieb:
> Ja das ergibt Sinn, danke für den Tipp!
>
> Deinen ersten Einwand kann ich aber noch nicht ganz nachvollziehen:
> rec bekommt doch bei jedem Durchlauf (wenn etwas neues angekommen ist)
                                        *******************************
> erst einmal einen neuen Wert zugewiesen !?

Der ***** ist der springende Punkt.
Was, wenn noch nichts neues angekommen ist?

> Vllt. habe ich das Problem auch einfach noch nicht erkannt.
>

Pimp dir die Ausgabe wie vorgeschlagen und du wirst es sehen.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz schrieb:

> Pimp dir die Ausgabe wie vorgeschlagen und du wirst es sehen.

Dein Programm versucht dann ständig erneut den (jetzt leeren) String in 
rec_conv auszuwerten. Erst wenn dann wieder das erste neue Zeichen 
eintrudelt, bleibt es in der while Schleife und wartet auf das Ende der 
neuen Übertragung.

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.