Forum: Mikrocontroller und Digitale Elektronik STM32 USART Strings verarbeiten


von Robin (Gast)


Lesenswert?

Hallo Ihr,

da ich aktuell etwas mit einem STM32F1 spiele, und leider nicht weiter 
komme, hoffe ich, dass Ihr mir helfen könnt.

Ich möchte via UART einen Befehl, z.B. "motor 75" oder "motor 100" oder 
"servo 45" an den STM32 senden, und so verarbeiten, dass das Motor PWM 
dann 75 oder 100% ist.

Hierzu muss ich den empfangenen String spliten, vergleichen und und dann 
die Zahl in einen anderen Datentyp umwandeln.

Das senden und empfangen klappt...aber mit der String verarbeitung 
scheitert es...

hier mal mein Code:
1
char str;
2
char trennzeichen[] = " ";
3
char *wort;
4
5
void comString(){
6
7
    str = USART_ReadByteSync(USART1);
8
9
     wort = strtok(&str, trennzeichen);
10
11
     if(strcmp(wort,"motor")){
12
                  \\einfach ein test, falls "motor" erkannt wurde
13
       USART_SendData(USART1,0x01);
14
    }
15
  
16
}

von ui (Gast)


Lesenswert?

für string operationen gibts im internet 1000nde Beispiele.
Nur mal nach
string split oder
string to int
etc. suchen.
Natürlich brauchst du auch noch eine Funktionstabelle, also bei welchem 
Schlüsselwort wird welche Funktion ausgeführt. Das ist im einfachsten 
Fall eine switch case

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Robin schrieb:
> if(strcmp(wort,"motor")){

strcmp liefert bei Gleichheit 0.

von Rainer B. (katastrophenheinz)


Lesenswert?

Wo ist denn der Teil deines Codes, der aus einem empfangenen Zeichen 
(str) eine Zeichenkette incl. terminierendem \0 zusammenbastelt? Und das 
dann an strtok übergibt? So wie du es jetzt machst, übergibst du nur 
einen zeiger auf einen char, aber keine 0-terminierte Zeichenkette a 
strtok.

von W.S. (Gast)


Lesenswert?

Robin schrieb:
> char str;
> ...
>     str = USART_ReadByteSync(USART1);

Stell dich nicht so an.

Also:

Trenne die Peripheriebedienung von der Auswertung von Kommandos. Das 
sind zwei völlig unterschiedliche Ebenen.

Also schreib dir einen ordentlichen LowLevel-Handler für deine diversen 
USART's oder suche hier im Forum, den ich hatte sowas schon mehrfach 
gepostet.

Sodann schreib dir ein Kommandoprogramm oder kopiere (und VERSTEHE) es 
von irgendwo. Du kannst z.B. dir die Lernbetty hier herunterladen, dort 
findest du das bereits vorgeturnt.

Beispiel:
 if (match("MOTOR",&Cpt))
 { DoMotorOnOff(Long_In(&Cpt));
   return;
 }

Etwa so kann sowas gehen.

W.S.

von Jan H. (jan_h565)


Lesenswert?

Das hier verwende ich :
In der String receiveRx_2 steht dan eine name und = und sollwerte. Hier 
nur fur positieve integers. Variabele printRx_2 gib an das ein komplette 
string fertig in buffer steht. Include <string.h> wird verwendet.
1
 //Nieuwe string ontvangen via USART1
2
    if(printRx_2){
3
         printf("Ontvangen %s",receiveRx_2);printRx_2=0;
4
         char delimiter[] = "=";//op deze tekens wordt de string gesplitst
5
         char *ptr1,*ptr2;uint32_t i;
6
         // initialisieren en eerste deel tot aan delimiter
7
         ptr1 = strtok(receiveRx_2, delimiter);
8
         ptr2 = strtok(NULL, delimiter);
9
         i=atoi(ptr2);//string omzetten naar int
10
         if(strcmp(ptr1,"bias_x")==0)magbias[0]=i;
11
         if(strcmp(ptr1,"bias_y")==0)magbias[1]=i;
12
         if(strcmp(ptr1,"bias_z")==0)magbias[2]=i;
13
         if(strcmp(ptr1,"nord")==0)offset_north=i;
14
         if(strcmp(ptr1,"radius")==0)track_radius=i;
15
         if(strcmp(ptr1,"k_speed")==0)k_speed=i;
16
         }

von W.S. (Gast)


Lesenswert?

Jan H. schrieb:
> In der String receiveRx_2 steht dan eine name und = und sollwerte. Hier
> nur fur positieve integers. Variabele printRx_2 gib an das ein komplette
> string fertig in buffer steht.

Also, so wie ich das verstehe:

"Amsterdam=128"
"Rotterdam=275"

und so weiter. OK?

Was machst du nun, wenn die Form dort vielleicht einmal etwas anders 
steht? Also so zum Beispiel:

"  Amsterdam= 92128"
"Rotterdam  =14"

Das, was ich in der Lernbetty gepostet hatte, war formatfrei und die 
Funktion 'match' hat das, was sie gefunden hat, sozusagen aufgefressen. 
Deshalb war das Verhalten tolerant zu Leerzeichen und es konnte sehr 
unterschiedliche Formate auf der Kommandozeile verarbeiten, so daß man 
eben nicht an ein bestimmtes Format gebunden ist.

W.S.

von Jan Heynen (Gast)


Lesenswert?

In meinen Beispiel muss der String absolut passen, leerzeichen werden 
auch interpretiert. Mit etwas mehr Programmier Aufwand ist das naturlich 
auch zu vermeiden, aber für mich ist das brauchbar.
MFG, RP6conrad.

von Felix F. (wiesel8)


Lesenswert?

Oder man vermeidet diese fehleranfälligen, unsicheren, langen Strings 
komplett und macht gleich ein einfaches, kurzes, abgesichertes 
Protokoll:
1
#define MOTOR_CMD    0x01
2
#define DIRECTION_CMD   0x02
3
4
typedef struct {
5
  uint8_t CMD;
6
  uint8_t Len;
7
  uint8_t Data[32];
8
  uint8_t CRC;
9
} MyDataPacket_t;
10
11
12
if(DataPacket.CRC == CaclCRC(&DataPacket)) {
13
  // Valid Packet
14
  switch(DataPacket.CMD)
15
  {
16
    case MOTOR_CMD:
17
      // bla bla
18
    case DIRECTION_CMD:
19
      // bla bla
20
    default:
21
  }
22
}

mfg

von W.S. (Gast)


Lesenswert?

Felix F. schrieb:
> Oder man vermeidet diese fehleranfälligen, unsicheren, langen Strings
> komplett und macht gleich ein einfaches, kurzes, abgesichertes
> Protokoll...

mit einem struct, der reine Binärdaten beinhaltet?

Finde ich sehr schlecht, da einerseits fehleranfällig und andererseits 
extrem problematisch und unleserlich. Warum? Nun:
1. man braucht dafür etwas zum Synchronisieren, sonst kann man nicht 
feststellen, an welcher Stelle seines struct's der Sender grad ist.
2. außer dem CRC hast du nichts, um die Fehlerfreiheit deines Paketes 
festzustellen. Möglicherweise ist der Fehler aber in einem konkret 
unbenutzen Bereich? Und wie sagt man es dem Sender, daß man seinen Kram 
nicht angenommen hat?
3. du bist festgenagelt auf ein spezielles Format. Das ist 
grottenschlecht, denn du kannst dir damit keinerlei Erweiterungen, 
Rückmeldungen, Kommentare und sonstiges Zeugs leisten, ohne alles 
nochmal neu zu definieren.

Ein anderes Beispiel:
Von der Sache her ist eine formatfreie Transmission in quasi UPN das 
eleganteste, aber es fehlt en bissel an der Sicherheit. Das sähe dann so 
aus:
Prinzip: dezimaler Parameter Kommandobuchstabe
Beispiel: 0M4711A1M0R5Q
und im Klartext:
schalte Motor aus (0M)
setze Variable A auf 4711  (4711A)
schalte Motor ein (1M)
schalte Relais aus (0R)
berichte den Status von Sensor 0 und Sensor 2 (5Q)

Bei sowas sind alle Textzeichen außer Ziffern und ausgewählte Buchstaben 
wahlfrei zu senden und zu empfangen und alle Operationen sind atomar. 
Sowas kann man leicht erweitern, Statusmeldungen und sonstiges Zeugs 
sind problemlos in den übertragenen Stream einflechtbar ohne daß es zu 
Kollisionen kommt. Beispiel: 0Motto ist da4711A1aber heinz nichtM0R5Q
Das ergibt die gleiche Kommandofolge wie oben.

Bei meinem ersten Beispiel sieht das ganz anders aus: Zuerst hat man mit 
der zeilenweisen Kommandoabarbeitung eine sehr gute Synchronisation 
zwischen TX und RX. Dann hat man durch die Länge der Kennwörter eine 
gute Sicherheit gegen Übertragungsfehler. Weiters kann man das Ganze 
beliebig mit anderen Kommandos anreichern, ohne am bisherigen Konzept 
etwas ändern zu müssen. Und zu guter letzt hat man das Ganze auch noch 
gut menschlich lesbar. Das ist in ganz vielen Fällen wertvoll - immerhin 
handelt es sich um das Synchronisieren zwischen zwei verschiedenen 
Gerätschaften und das Nachschauenkönnen bei Problemen (die es IMMER 
gibt...)

W.S.

von Robin (Gast)


Lesenswert?

Okay...also das mit mit String zusammenbasteln...wie mach ich das am 
besten??

Ich habs jetzt mal so gemacht...kanns leider aber erst kommende Woche 
testen, hab mein Board leider am Sonntag in der Heimat liegen lassen :-(

also ich will fürs erste einfach, dass er beginnt einen String 
zusammenzubauen wenn er ein Ausrufezeichen erkennt.
Zudem soll er bei einem linefeed (0x0A) damit aufhören.

passt das so? oder kann ich daran noch was optimieren?
Kann ich dann strtok(USART_ReadString(USART1,&str)?

1
uint8_t USART_ReadByteSync(USART_TypeDef *USARTx)
2
{
3
    while ((USARTx->SR & USART_SR_RXNE) == 0)
4
    {
5
    }
6
    return (uint8_t)USART_ReceiveData(USARTx);
7
}
8
9
void USART_ReadString(USART_TypeDef *USARTx,uint8_t* str){
10
  
11
  while(USART_ReadByteSync(USARTx)!='!')
12
  {
13
  }
14
  int i = 0;
15
  while(USART_ReadByteSync(USARTx)!=0x0A){
16
    str[i] = USART_ReadByteSync(USARTx);
17
    i++;
18
  }
19
}

von Bernd K. (prof7bit)


Lesenswert?

W.S. schrieb:

> Binärdaten

> 3. du bist festgenagelt auf ein spezielles Format. Das ist
> grottenschlecht, denn du kannst dir damit keinerlei Erweiterungen,
> Rückmeldungen, Kommentare und sonstiges Zeugs leisten, ohne alles
> nochmal neu zu definieren.

Was hat das eine mit dem anderen zu tun? Er könnte jederzeit falls nötig 
neue Kommandos definieren deren Datenbereich dann eine ganz andere 
Bedeutung oder gar Länge hat. Dabei müsste er bestehenden Code und die 
Struktur bestehender Pakete nicht anfassen, nur neuen Code hinzufügen 
für die neuen Befehle.

von Gerald (Gast)


Lesenswert?

W.S. schrieb:
> Beispiel: 0M4711A1M0R5Q
...
> Und zu guter letzt hat man das Ganze auch noch
> gut menschlich lesbar.
Aber eine gewisse autistische Ader besitzt Du auch, oder?!

von Jan Heynen (Gast)


Lesenswert?

So ist meinen ISR für den UART Rx gemacht :
Jeden empfangen byte wird in ein String Puffer kopiert. Wen ein "\n" 
oder "\r" empfangen wird, wird das ganse in eine Zweite Puffer kopiert, 
die erste Puffer fangt wieder ab 0 an. Den Zweite puffer kan in Main() 
weiter verarbeitet worden. In Fall keine \n oder \r empfangen wird, ist 
nach 250 Zeichen wieder ab 0. (Ueberlauf Puffer vermeiden).
Der Synchronisation ist dan immer \n oder \r.
1
//Usart 2 = PC-connection
2
void USART2_IRQHandler(void) {
3
  /* Check if interrupt was because data is received */
4
  if (USART2->SR & USART_SR_RXNE) {
5
      /* Put received data into internal buffer */
6
7
     /* Read one byte from the receive data register */
8
        bufferRx_2[RxCounter_2++] = USART_ReceiveData(USART2);
9
       // USART_SendData(USART1, bufferRx[RxCounter-1]);//echo van elk karakter apart
10
        if((bufferRx_2[RxCounter_2-1]=='\r')|(bufferRx_2[RxCounter_2-1]=='\n')){
11
           strcpy(receiveRx_2, bufferRx_2);
12
           receiveRx_2[RxCounter_2-1]='\0';
13
           RxCounter_2=0;printRx_2=1;
14
          }
15
        if(RxCounter_2 > 250)
16
        {
17
          RxCounter_2=0;//anders overflow van bufferRx[] !!!
18
        }
19
  }
20
}

von W.S. (Gast)


Lesenswert?

Gerald schrieb:
> Aber eine gewisse autistische Ader besitzt Du auch, oder?!

Du kannst erwiesenermaßen nicht gründlich lesen. Also hier mein Sermon 
nochmal in Auszügen:

"Bei meinem ersten Beispiel sieht das ganz anders aus: ...
... Und zu guter letzt hat man das Ganze auch noch gut menschlich 
lesbar."

und das erwähnte erste Beispiel:

"Beispiel:
 if (match("MOTOR",&Cpt))
 { DoMotorOnOff(Long_In(&Cpt));
   return;
 }

Etwa so kann sowas gehen."

Auf der Seriellen sieht man also etwa sowas:

> MOTOR 1 (cr/lf)

Ist das nun klar genug?

W.S.

von W.S. (Gast)


Lesenswert?

Robin schrieb:
> also ich will fürs erste einfach, dass er beginnt einen String
> zusammenzubauen wenn er ein Ausrufezeichen erkennt.
> Zudem soll er bei einem linefeed (0x0A) damit aufhören.

Nein, nein, nein

.. genau SO solltest du nicht herangehen.

Sondern:
Du solltest zu allererst einen Treiber haben, der die zugrundeliegende 
HW abstrahiert und dir ne Schnittstelle liefert, die sowohl von der HW 
unabhängig ist als auch vom konkreten Inhalt unabhängig ist. Sinngemäß 
etwa so:

extern dword InitSerial (long baudrate);
extern char  Char_Out   (char c);
extern bool  RxAvail    (void);
extern char  GetChar    (void);

Sicherlich wirst du den Treiber irgendwann so schreiben, daß er die ein- 
und ausgehenden Datenströme sinnvoll zwischenpuffert und ansonsten 
interruprgesteuert arbeitet, um den Rest der Firmware nicht zu 
blockieren, aber wie nun genau, ist interne Angelegenkeit des Treibers 
und geht den Rest der Welt nix an. Die soll sich mit den genannten 
Funktionen begnügen. Ebenso soll der Treiber sich eben NICHT drum 
kümmern, was für einen Inhalt die Zeichen haben, die er sendet und 
empfängt. Das ist nicht sein Job.

Als nächstes solltest du dir ein allgemeines Kommandoprogramm schreiben, 
was etwa folgendes enthält:
- einen Kommandoprompt ausgeben (oder nicht, falls du sowas nicht magst)
- per Polling eine Kommandozeile hereinholen, die empfangenen Zeichen 
als Echo zurückschicken (damit man am PC im Terminalprogramm auch sieht, 
was tatsächlich angekommen ist) und ggf. ein paar simple 
Editierfunktionen dafür benutzen (Backspace zum Korrigieren oder sowas),
also etwa so:

in der Grundschleife in main:
  if(RxAvail()) Kommandoprogramm();

Und wenn das Kommandoprogramm ein CR/LF gefunden hat oder die 
Kommandozeile voll ist, dann soll es die Kommando-Auswertung aufrufen.

Diese nun muß ihrerseits die Kommandozeile auswerten und das Gewünschte 
tun. Obendrein soll sie bei nichterkanten Kommandos dich anmeckern, 
damit du weißt, daß du nicht verstanden wurdest. Schlußendlich soll sie 
die Kommandozeile löschen, da abgearbeitet und ggf. einen neuen 
Kommandoprompt ausgeben, damit man am anderen Ende der Strippe weiß, daß 
das Kommando jetzt erledigt ist.

Aber fange eben NICHT mit dem sturen Zusammenbauen eines Strings an - 
schon garnicht mit Ausrufezeichen und so. Prinzip: vom Einfachen zum 
Komplexen gehen und nicht mittendrin mit irgendwas anfangen.

Nochwas: das Zeilenende per LF zu finden, ist Unix-Denke. Es trifft 
häufig genug bei Terminalprogrammen nicht zu, weswegen es im Prinzip 
besser ist, auf CR oder LF zu orientieren. Wenn eines von beiden kommt, 
ist die Zeile fertig. Man muß dabei allerdings ein direkt auf CR 
folgendes LF wegschmeißen, weil der zugrunde liegende 
Schreibmaschinen-Standard eigentlich CR und folgendes LF ist und die 
meisten Terminalprogramme genau diese Kombination senden.

W.S.

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.