mikrocontroller.net

Forum: Compiler & IDEs Befehle aus String-Zeichenketten extrahieren


Autor: Armin S. (hendrix-84)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo :)

Ich entwickle gerade die Firmware fuer ein Messgeraet, was in der 
Fertigungsmesstechnik eingesetzt werden soll. An das Geraet sollen 
Kommandos von einem Anwender-PC über die RS 232 Schnittstelle gesendet 
werden.

Als µC-Basis dient mir im Moment noch ein ATMega32 (noch BreadBoard 
Entwicklungsstatus). Auf der fertigen Platine wird es dann ein ATMega128 
sein.

Ueber die Implementierung des UARTS auf dem AVR hab ich mir schon 
Gedanken gemacht. Ich hab die UART-Library von

http://homepage.hispeed.ch/peterfleury/avr-software.html

fuer meine Umgebung genommen und entsprechend angepasst. Jetzt stehe ich 
an dem Punkt wo es darum geht einen String, der einen Befehl vom 
Terminal-Programm zum Gerät representiert, auszuwerten und die 
Befehlsparameter fehlerfrei abzufangen.

Da ich ein Zeitgesteuertes Betriebssystem nutze 
(http://www.le.ac.uk/engineering/mjp9/pttes.html) habe ich eine 
UART-Handler-Funktion konstruiert, die zyklisch aufgerufen wird und 
immer den Ring-Buffer auswertet, sofern was neues drinne steht. Wenn ein 
Endzeichen ('\n') gefunden wird gilt der String als komplett uebertragen 
und die "Befehlsauswertung" beginnt. An dieser Stelle bin ich mir nicht 
sicher, ob ich wirklich eine vernünftige Lösung gefunden habe.

Hier mal mein Quellcode für die Befehlsauswertung:
/* Wird nur betreten wenn ein String uebertragen wurde */
void uart_rx_handler(void)
{
  /* Schnipp */
  rx_string[0] = 'm'; 
  rx_string[1] = 'v';
  rx_string[2] = ',';
  rx_string[3] = '1';
  rx_string[4] = '3';
  rx_string[5] = '6';
  rx_string[6] = ',';
  rx_string[7] = '2';
  rx_string[8] = '6';
  rx_string[9] = '4';
  rx_string[10] = ',';
  rx_string[11] = '5';
  rx_string[12] = '3';
  rx_string[13] = '9';
  rx_string[14] = '\n';

  if(Flag_string_complete == true)
  {    
    uint8_t tmp_index = 0;
    uint8_t tmp_rx_index = 3;

    char tmp_string[5];
    uint16_t tmp_parameter[3];
    
    switch(rx_string[0])
    {
      case 'm':
    
     switch(rx_string[1])
     {
       case 'v': /* mv-Befehl wurde uebertragen */

         if(rx_string[2] == ',')
         {
           while(rx_string[tmp_rx_index] != ',')
           {
             tmp_string[tmp_index] = rx_string[tmp_rx_index];
             tmp_index++;
             tmp_rx_index++;
           }
   
           tmp_string[tmp_index] = '\0';
           tmp_parameter[0] = atoi(tmp_string); /* Schritte */
            
           tmp_rx_index++;
           tmp_index = 0;

           while(rx_string[tmp_rx_index] != ',')
           {
             tmp_string[tmp_index] = rx_string[tmp_rx_index];
             tmp_index++;
             tmp_rx_index++;
           }
         
           tmp_string[tmp_index] = '\0';
           tmp_parameter[1] = atoi(tmp_string); /* Geschwindigkeit */

           tmp_rx_index++;
           tmp_index = 0;

           while(rx_string[tmp_rx_index] != '\n')
           {
             tmp_string[tmp_index] = rx_string[tmp_rx_index];  
             tmp_index++;
             tmp_rx_index++;
           }
   
           tmp_string[tmp_index] = '\0';
           tmp_parameter[2] = atoi(tmp_string); /* Beschleunigung */
          
           tmp_rx_index = 3;
           tmp_index = 0;

           /* Befehl ausfuehren */
           rampe_starten(tmp_parameter[0], tmp_parameter[1], tmp_parameter[2]);
           Flag_string_complete = false; /* es kann neuer String uebertragen werden */ 
          }

          else
          {
            /* Syntax-Fehlercode ausgeben */
          }
        break;
      }  
    break;
  }  
}

Das ist natuerlich nur ein Ausschnitt aus der gesamten 
uart_rx_handler-Funktion. Auch die explizite Zuweisung des Inhhalts des 
char-arrays "rx_string" ist "symbolisch" zu verstehen. So kann der 
String aussehen, wenn er aus dem Ring-Buffer ausgelesen wurde. Außerdem 
wird im Moment auch nur dieser eine Befehl verarbeitet. Aber es geht ja 
ums Prinzip.

Ihr seht, dass die Paramter von Kommas getrennt sind. Meine 
"Befehlsauswertung" soll diese Paramter finden, in Integer-Zahlen 
umwandeln und dann an die passende Funktion, welche zu dem Befehl 
gehört, weitergeben.

Im Moment mache ich das auf die ziemlich "strohdoofe" Variante:
Einfach den String "rx_string" von links nach rechts durchgehen und 
eventuell sinnvolle Sachen in einem tempraeren array namens "tmp_string" 
ablegen.

Tja, meine Implementierung um die Parameter fuer den Befehl aus dem 
String "rx_string" zu "fischen" funktioniert zwar. Aber irgendwie 
gefällt mir das alles nicht. Das ist so richtiger 
"Tipel-Tapel-Tour"-Code:

- zu viele while-Schleifen (gefällt mir nicht in meiner zeit-gesteuerten 
Betriebssystemumgebung)

- zwei Indexvariablen (unuebersichtlich)
- fuer jeden Paramteter eine neue while-Schleife (ineffizient)

Ich muss zugeben, dass ich von Strings in C nicht viel verstehe. Was ich 
weiß ist, dass ein String immer mit "\0" terminiert werden muss. Wie ihr 
steht hab ich das in meiner Implementierung auch umgesetzt ;).

Darum frage ich euch, ob es für mein Problem auch eine bessere 
Implementierung gibt. Vielleicht ist ja mein ganzer Ansatz unguenstig 
gewaehlt. War einfach das Erste, was mir eingefallen ist.

Die Schwerpunkte, nach denen ich immer suche sind:

- bessere Lesbarkeit des Codes, damit ich ihn selber noch nach nem Monat 
verstehe

- hohe Abarbeitungsgeschwindigkeit (ist ja ein Zeitkritischer Prozess)

Ich bedanke mich schonmal im Voraus fuer eure Anregungen und 
Vorschlaege!

LG, Armin

Autor: Floh (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mal ein paar Tips:
- Deine Eingabe wird durch Kommas getrennt. Nutze es.
  Zerlege deinen empfangenen String in
  "Befehl Komma Wert Komma Wert Komma Wert Ende" (ich geh mal davon aus, 
dass die Sequenz immer so ist).

- Den Befehl auswerten, falls dieser immer kleiner gleich 2 oder 3 chars 
ist, lässt sich das sehr gut mit vergleichen erledigen:
uint32_t temp = befehl[0];
temp = temp<<8 + befehl[1];
temp = temp<<8 + befehl[2];
//jetzt ist in temp der Ascii-Wert aller 3 Zeichen drin. 

//Vergleichswert:
const uint32_t setzen = 's'<<16 + 'e'<<8 + 't';

if(temp == setzen)
 //tu was

- Nach dem Befehlauswerten weist du, was du mit wert 1 bis 3 machen 
musst.
:-)

Autor: g457 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ui, echter Spaghetticode :-)

sscanf() regelt. Ist vielleicht weniger performant, erleichtert die 
Wart- und Lesbarkeit aber um Größenordnungen. Ach was sag ich, um 
Dimensionen.

HTH

Autor: Armin S. (hendrix-84)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
g457 schrieb:
> Ui, echter Spaghetticode :-)
>
> sscanf() regelt. Ist vielleicht weniger performant, erleichtert die
> Wart- und Lesbarkeit aber um Größenordnungen. Ach was sag ich, um
> Dimensionen.
>
Jup danke! sscanf() ist das, wonach ich gesucht ab. Aber so ganz klappt 
das Auslesen der einzelnen Werte immer noch nicht. Ich hab jetzt mal nen 
neuen Bsp.-Code ausprobiert. Die Trennung der einzelnen Abschnitte 
erfolgt jetzt ueber Leerzeichen. Fuer ein erstes Versuchsstadium ist das 
vollkommen ausreichend.
#include "main.h"
#include "bibliothek.h"

volatile char rx_string[] = "mv 14 25 135";
volatile  char befehl[2];
volatile uint8_t sscanf_debug;
volatile uint16_t tmp_parameter[3];
volatile uint8_t befehl_1;
volatile uint8_t befehl_2;
volatile uint8_t befehl_3;

int main(void)
{
  sscanf_debug = sscanf(rx_string, "%s %d %d %d", &befehl, &befehl_1, &befehl_2, &befehl_3);
  if(strcmp(befehl, "mv"))  /* MV-Befehl abfragen */
  {
    befehl_1 = tmp_parameter[0];
    befehl_2 = tmp_parameter[1];
    befehl_3 = tmp_parameter[2];  
  }
  for(;;);
  return 0;
}
Blöd nur, es funktioniert nicht. Wenn ich mir das Array "befehl" und die 
Variablen "befehl_1" - "befehl_3" debuggen lasse (darum die 
volatile-Anweisungen) haben sie nach dem Aufruf von sscanf() folgende 
Werte:
befehl[0] = '';
befehl[1] = 'v';
befehl_1 = 14;
befehl_2 = 0;
befehl_3 = 135;

sscanf_debug liefert aber korrekt = 4. Es wurden also 4 unterschiedliche 
formatierte Werte eingelesen. Aber warum stimmt dann die Zuweisung an 
meine Variablen nicht? Was mache ich im Umgang von sscanf() noch falsch?

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
uint8_t befehl[2] ist zu kurz, es müssen mindestens 3 Zeichen darin
Platz haben ("mv\0"). Die letzen 3 Argumente von sscanf müssen vom Typ
(int *) sein, damit sie mit %d eingelesen werden können. Das '&' vor
befehl in sscanf muss weg.

Wenn du mit -Wall die Compiler-Warnungen aktivierst, wirst du übrigens
auf einige dieser Fehler hingewiesen.

Autor: Armin S. (hendrix-84)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Servus :)

Yalu X. schrieb:
> uint8_t befehl[2] ist zu kurz, es müssen mindestens 3 Zeichen darin
> Platz haben ("mv\0").
Heißen Dank für den Hinweis. Klar muss noch Platz fuer die 
0-Terminierung sein. Hab ich im Eifer des Gefechts vergessen. Jetzt geht 
alles ^^.

> Die letzen 3 Argumente von sscanf müssen vom Typ (int *) sein, damit sie
> mit %d eingelesen werden können. Das '&' vor befehl in sscanf muss weg.
Normalerweise nutze ich zur Variablen-Deklaration immer die absoluten 
Angaben in (u)int($BIT_GROESSE)_t. Gut, gewoehne ich mir jetzt einfach 
an, vorher in den Headern nachzuschauen, was die Funktionen als 
Eingangsvariablen-Typ erwarten.

> Wenn du mit -Wall die Compiler-Warnungen aktivierst, wirst du übrigens
> auf einige dieser Fehler hingewiesen.
Ich benutze AVR-GCC/AVR-Libc/AVR-Studio 4.18 als Entwicklungsumgebung. 
Da ist das "-Wall" Flag fuer den GCC ja per default gesetzt. Bei meinem 
Kauderwelsch hat der GCC auch ordentlich mukiert. Aber so richtig schlau 
bin ich aus den Fehlermeldungen leider nicht geworden. Na ja, aber jetzt 
scheint alles io zu sein :).

Dann habt mal ne schöne Arbeitswoche. Ich hab meinen Soll fürs WE dank 
euch jedenfalls geschafft.

Autor: Yalu X. (yalu) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Armin S. schrieb:
> Normalerweise nutze ich zur Variablen-Deklaration immer die absoluten
> Angaben in (u)int($BIT_GROESSE)_t. Gut, gewoehne ich mir jetzt einfach
> an, vorher in den Headern nachzuschauen, was die Funktionen als
> Eingangsvariablen-Typ erwarten.

Du kannst mit *scanf theoretisch auch uint8_t und dergleichen einlesen.
Der offizielle Weg nach dem C99-Standard geht so:
#include <stdint.h>
#include <inttypes.h>

  uint8_t befehl_1, befehl_2, befehl_3;

  sscanf_debug = sscanf(rx_string, "%s %"SCNu8" %"SCNu8" %"SCNu8"",
                        befehl, &befehl_1, &befehl_2, &befehl_3);

Die SCNu8-Makros expandieren zu "hhu" (Format für unsigned char), oder
sollten es zumindest.

Leider wird das Makro in der AVR-Libc nicht definiert, da es dort in ein
#ifdef __avr_libc_does_not_implement_hh_in_scanf
...
#endif

eingeschlossen ist. __avr_libc_does_not_implement_hh_in_scanf ist
zurecht nicht definiert, da das *scanf der AVR-Libc das "hh"-Format laut
Dokumentation und Sourcecode sehr wohl implementiert.

Vielleicht schaut ja der Jörg Wunsch ¹ zufälligerweise vorbei und kann
uns sagen, was sich die AVR-Libc-Entwickler dabei gedacht haben.
Möglicherweise ist es ja ein Bug, und das #ifdef sollte durch ein
#ifndef ersetzt werden (zweimal).

¹) etwas lauter, damit er es im Vorbeigehen vielleicht hört :)

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.