Forum: Mikrocontroller und Digitale Elektronik UART Eingabe ordentlich filtern


von Stefan S. (sschultewolter)


Lesenswert?

Hallo,

ich habe für ein kleines Projekt mit dem Attiny841 eine Serielle 
Schnittstelle eingerichtet, über den ich entsprechende Aktionen 
ausführen kann.

Die Eingaben setzten sich im Terminal wie folgt zusammen.

Beginnend mit einen Buchstaben um zu entscheiden um welchen Befehl es 
sich handelt. Hierbei werden vorerst nur ein paar Kleinbuchstaben 
verwendet.

Nun kann nach diesem Buchstaben entweder nichts erfolgen (dann toggelt 
eine Variable) oder aber einen Zahlenwert von 0 - 255. Das ganze wird 
mit einem CR und/oder NL abgeschlossen. Sollte ein 5. Zeichen 
hereinkommen, gilt dieses ebenfalls als string_complete.

Ich habe mir dazu eine kleine Routine ausgedacht. Ist der Ansatz 
aufwendiger als nötig? Gibt es Verbesserungsvorschläge?
1
void commands_uart(void)
2
{
3
  // Wenn ISR einen String komplett empfangen hat (4 Zeichen, CR oder NL)
4
  if(uart_str_complete)
5
  {
6
    uart_str_complete = 0;
7
    
8
    char buf[5];
9
    
10
    uint8_t size = 0;
11
    uint8_t index = 1;
12
    uint8_t value = 0;  // Wert 0 - 255
13
    
14
    // Erstes Zeichen enthaelt einen Buchstaben
15
    char c = uart_string[0];
16
    if(c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')
17
    {
18
      // Anzahl eingelesener Zeichen ermitteln
19
      while(uart_string[size] != '\0') size++;
20
      
21
      #ifdef _DEBUG
22
      uart_puts_P("\r\nEingelesene Zeichen\t:   ");
23
      uart_puts(itoa(size, buf, 10));
24
      #endif // _DEBUG
25
      
26
      // Zeichen in Zahl umwandeln
27
      while(isdigit(uart_string[index]))
28
      {
29
        value = (value*10) + (uart_string[index] - '0');
30
        index++;
31
      }
32
      
33
      #ifdef _DEBUG
34
      uart_puts_P("\r\nEingelesene Zahl\t: ");
35
      if(size == 1) uart_puts_P("  -");  // Nur einen Buchstaben und keine Zahl eingegeben
36
      else
37
      {
38
        if(value < 10) uart_puts_P("  ");
39
        else if(value < 100) uart_puts_P(" ");
40
        uart_puts(itoa(value, buf, 10));
41
      }
42
      #endif // _DEBUG
43
    }
44
    else return; // Abbruch
45
    
46
    switch(c)
47
    {
48
      case 'a':
49
      if(size == 1) /* toggelt eine Variable */;
50
      else /* value einer Variable zuweisen */;
51
      // ...
52
      break;
53
      
54
      default:
55
      return;
56
    }
57
  }
58
}

von Karl H. (kbuchegg)


Lesenswert?

Vom Prinzip her ist das schon ok.

Anmerkungen
1
      // Anzahl eingelesener Zeichen ermitteln
2
      while(uart_string[size] != '\0') size++;

das ist genau das, was auch die Funktion strlen macht.
1
      size = strlen( uart_string );
den Kommentar kannst du dir dann sparen, denn der steckt implizit in der 
Funktion strlen drinnen, die eigentlich jeder C Programmierer kennen 
sollte und daher auch weiß, was die macht.

Selbiges hier
1
      // Zeichen in Zahl umwandeln
2
      while(isdigit(uart_string[index]))
3
      {
4
        value = (value*10) + (uart_string[index] - '0');
5
        index++;
6
      }
das ist genau dasselbe, was auch die Funktion atoi macht.
1
    value = atoi( &uart_string[1] );
in deinem Fall lässt man die natürlich erst auf den Teilstring los, der 
bei uart_string[1] beginnt.

D.h. unter Verwendung der bereits vorhandenen Standardfunktionen 
schreibt sich das dann als
1
    char c = uart_string[0];
2
    if(c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')
3
    {
4
      size = strlen( uart_string );
5
      value = atoi( &uart_string[1] );
6
7
      ....

Noch ein kleiner Tipp.
Für solche Sachen
1
      uart_puts(itoa(size, buf, 10));
solltest du dir deinen Satz von uart Funktionen ergänzen.

Du hast ein urt_puts in deinem Funktionsvorrat. Nichts und niemand 
hindert dich daran, da noch eine Funktion
1
void uart_puti( int value )
2
{
3
  char buf[7];
4
  uart_puts(itoa(size, buf, 10));
5
}
mit aufzunehmen.
Genauso, wie du dir auch dafür
1
        if(value < 10) uart_puts_P("  ");
2
        else if(value < 100) uart_puts_P(" ");
3
        uart_puts(itoa(value, buf, 10));
gleich mal eine neue Funktion mit in die UART Funktionen mit dazu 
programmieren kannst. Zb.
1
void uart_putb_formatted( uint8_t byte )
2
{
3
  char buf[7];
4
5
  if(value < 10) uart_puts_P("  ");
6
  else if(value < 100) uart_puts_P(" ");
7
8
  uart_puts(itoa(value, buf, 10));
9
}

und in Zukunft hast du dann eben die beiden Funktionen (oder auch noch 
andere) in deinen UART Funktionen vorrätig. Nur weil du bisher UART 
Funktionen hattest bzw. dir irgendwo eine fertigen Satz an Funktionen 
geholt hast, bedeutet das nicht, dass du dir da keine weiteren 
Hilfsfunktionen dazu programmieren darfst, die du in weiterer Folge 
immer wieder brauchen kannst. Lieber diesen Satz an Funktionen 
erweitern, als bei jedem neuen Projekt x-mal die immer gleichen 
Funktionalitäten erneut ausprogrammieren.
Letzten Endes kommt das auch deinem Code zu gute, wenn er aus keinem 
guten Grund so aufgebläht wird, nur weil du dir für eigentlich einfache 
Code-Bausteine keine Funktionen machst, sondern die immer wieder neu 
ausprogrammierst.

von Karl H. (kbuchegg)


Lesenswert?

Dieses Konstrukt
1
    if(c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z')
2
    {
3
...
4
    }
5
    else return; // Abbruch
6
    
7
    switch(c)
8
    {
9
....
finde ich nicht sehr elegant.

Ich bin zwar kein eifriger "Es darf nur einen return geben" Verfechter, 
aber der return steckt mir da ein wenig zu unprominent drinnen.

Wenn du c nur dann auswerten darfst, wenn es sich auch tatsächlich um 
einen Buchstaben gehandelt hat, warum programmierst du das dann nicht 
auch ganz einfach genau so, dass die Auswertung nur dann erfolgt, wenn 
es sich beim ersten Zeichen um einen Buchstaben gehandelt hat. Die 
entsprechende Abfrage steht ja schon da. (und genau so wie es eine 
vordefinierte Funktion isdigit gibt, gibt es auch eine vordefinierte 
Funktion isalpha. rate mal, was die macht)
1
    if( isalpha( c ) ) )
2
    {
3
      size = strlen( uart_string );
4
      value = atoi( &uart_string[1] );
5
6
      switch(c)
7
      {
8
         ....
9
      }
10
    }

dann wird der ganze return da überhaupt nicht mehr gebraucht.

von Stefan S. (sschultewolter)


Lesenswert?

Hallo Karl Heinz,


danke für die Rückmeldungen. Kritik angenommen und wird ausgebesser ;)

Ich hab in meiner eigentlichen uart-lib für den attiny diverse 
Funktionen für die Umwandlung drin gehabt. Vieles lief aber auch über 
sprintf und blähte diesen bedingt auch etwas auf. Hatte es hier mit den 
einfachen Funktionen versucht, die kleinen Ergänzungen sind aber 
sicherlich hilfreich!


Edit/Nachtrag:
Da haste Recht, wobei mir gerade auffällt, das die Abfrage keine Rolle 
spielen sollte, da default in der switch Anweisung keine Variablen dann 
willkürlich verändert ;) Kann somit also komplett raus.

von Karl H. (kbuchegg)


Lesenswert?

Stefan S. schrieb:

> Edit/Nachtrag:
> Da haste Recht, wobei mir gerade auffällt, das die Abfrage keine Rolle
> spielen sollte, da default in der switch Anweisung keine Variablen dann
> willkürlich verändert ;) Kann somit also komplett raus.

Ich bin mir ehrlich gesagt auch nicht sicher, ob ich da einen switch 
case machen würde. Nichts gegen den switch case, aber er zieht den Code 
schon ordentlich in die Länge.

Ich würde mir mal eine andere Variante ansehen
1
static void toggleVar( char variable )
2
{
3
#ifdef DEBUG
4
  uart_puts_P( "Toggling '" );
5
  uart_putc( variable );
6
  uart_puts_P( "'\r\n" );
7
#endif
8
9
  if( variable == 'a' )
10
    wertA = 255 - WertA;
11
12
  else if( variable == 'b' )
13
    wertB = 255 - WertB;
14
}
15
16
static void setVar( char variable, uint8_t value )
17
{
18
#ifdef DEBUG
19
  uart_puts_P( "Setting '" );
20
  uart_putc( variable );
21
  uart_puts( "' to " );
22
  uart_puti( value );
23
  uart_puts_P( "\r\n" );
24
#endif
25
26
  if( variable == 'a' )
27
    WertA = value;
28
29
  else if( variable == 'b' )
30
    WertB = value;
31
}
32
33
void commands_uart(void)
34
{
35
  if( uart_str_complete == 0 )
36
    return;
37
38
  uart_str_complete = 0;
39
        
40
  char c = uart_string[0];
41
  if( isalpha( c ) ) )
42
  {
43
    if( strlen( uart_string ) == 1 )
44
      toggleVar( c );
45
    else
46
      setVar( c, atoi( &uart_string[1] ) );
47
  }
48
}

:-)
Man sieht, dass ich mittlerweile gerne kürzere Funktionen bevorzuge, die 
man mit einem Blick gut überblicken kann. Selbst wenn das heisst, ein 
paar zusätzliche Funktionen einzufügen, die dann Teilaufgaben 
übernehmen. Aber ich gebe zu, da ist auch viel Geschmackssache dabei.
Worauf ich hinaus will: Ich hab in meine jungen Jahren viel gelernt, 
indem ich vorhandenen Code immer wieder in Frage gestellt habe, mich 
zurück gelehnt habe und mich gefragt habe: "wie könnte ich das anders 
schreiben? Diese Stelle gefällt mir nicht, wenn ich das so und so 
schreibe, wo führt mich das hin?"

von Stefan S. (sschultewolter)


Lesenswert?

1
if( isalpha( c ) ) )
 :P Eine Klammer zuviel

Habe jetzt erst einmal den Code für soweit akzeptabel angepasst.
Werde mir deinen Vorschlag dennoch zu Rate ziehen. Die Funktion, ein 
Buchstaben + 1 Byte habe ich sehr häufig in Gebrauch. Das kann ich 
ähnlich wie von dir für meine uart lib nutzen, damit die in anderen 
projekten auch zur Verfügung stehen ;)

Danke

1
void commands_uart(void)
2
{
3
  if(uart_str_complete)
4
  {
5
    uart_str_complete = 0;
6
7
    char c = uart_string[0];
8
    uint8_t size = strlen(uart_string);
9
    uint8_t value = atoi(&uart_string[1]);
10
    
11
    switch(c)
12
    {
13
      case 's':
14
      #if SHOW_CHG
15
      uart_puts_P("\r\n# Start\t\t:");
16
      uart_puti_formatted(run);
17
      uart_puts_P(" -> ");
18
      #endif  // SHOW_CFG
19
      
20
      if(size == 1) run ^= 1; // toggle
21
      else if(value) run = 1;
22
      else run = 0;
23
      
24
      #if SHOW_CHG
25
      uart_puti_formatted(run);
26
      #endif  // SHOW_CHG
27
      break;
28
      
29
      case 'f':
30
      #if SHOW_CHG
31
      uart_puts_P("\r\n# Flug Modus\t: ");
32
      uart_puti_formatted(flight_mode);
33
      uart_puts_P(" -> ");
34
      #endif  // SHOW_CFG
35
      
36
      if(size == 1) flight_mode++;
37
      else flight_mode = value;
38
      if(flight_mode > 8) flight_mode = 0;
39
      
40
      #if SHOW_CHG
41
      uart_puti_formatted(flight_mode);
42
      #endif  // SHOW_CFG
43
      break;
44
      
45
      case 'l':
46
      #if SHOW_CHG
47
      uart_puts_P("\r\n# Led Modus\t:");
48
      uart_puti_formatted(led_mode);
49
      uart_puts_P(" -> ");
50
      #endif  // SHOW_CFG
51
      
52
      if(size == 1) led_mode++;
53
      else led_mode = value;
54
      if(led_mode > 8) led_mode = 0;
55
      
56
      #if SHOW_CHG
57
      uart_puti_formatted(led_mode);
58
      #endif  // SHOW_CFG
59
      break;
60
      
61
      case 'e':
62
      #if SHOW_CHG
63
      uart_puts_P("\r\n# Im Eeprom speichern");
64
      uart_puts_P("\r\n - Flug Modus\t: ");
65
      uart_puti_formatted(flight_mode);
66
      uart_puts_P("\r\n - Led Modus\t: ");
67
      uart_puti_formatted(led_mode);
68
      #endif // SHOW_CHG
69
      break;
70
      
71
      case 'u':
72
      uart_header();
73
      break;
74
75
      default:
76
      uart_puts_P("\r\n# ERROR!");
77
      return;
78
    }
79
  }
80
}

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.