Forum: Mikrocontroller und Digitale Elektronik Arduino - String-Eingabe in num. Wert verwandeln


von Peter B. (olduri)


Lesenswert?

Hi allerseits,
als gestandener Pascal-Programmierer bin ich ja allerlei 
Komfort-Funktionen für diese Aufgabe gewohnt. Jetzt bei meinen 
Arduino-Spielereien habe ich mir bald einen abgebrochen:
1
    String st = Serial.readString ();
2
    int i, j;
3
    int wert = 0;
4
    int hoch = 1;
5
    st.trim();
6
    j = st.length() - 1;
7
    for (i = j; i >= 0; i--) {           // in einzelne Bytes zerlegen
8
      wert += (int (st[i]) - 48) * hoch; // aus ACII num. Wert erzeugen
9
      hoch *= 10;                        // ins 10er System bringen
10
      Serial.println (wert);             // Kontrollausdruck
11
    }

Und da sind jetzt noch keine Plaus-Tests oder Range-Checks dabei.
Geht das irgendwie auch einfacher? Habe ich irgendeine Standard-Lib 
nicht gefunden?

MfG
OdlUri

von ThomasW (Gast)


Lesenswert?

bin nicht der C-Profi, aber gibt es dafür nicht "atoi"?

von Uwe H. (uhomm)


Lesenswert?


von STK500-Besitzer (Gast)


Lesenswert?

atoi für Integer
atof für float/double

von Stefan F. (Gast)


Lesenswert?

Neben atoi() aus dem C Standard hat die Ardiono String Klasse noch eine 
Methode toInt() und sie Serial Klasse hat eine parseInt() Methode.

von Peter D. (peda)


Lesenswert?

Peter B. schrieb:
> Geht das irgendwie auch einfacher?

Die Komfortfunktion nennt sich sscanf. Sie liefert die Anzahl der 
fehlerfrei konvertierten Argumente zurück und gestattet verschiedene 
Formate (hex, dez, int, long, float).
Näheres dazu im Wiki oder jedem anderen C-Tutorial.

atoi und Konsorten geben leider keinen Fehlerstatus zurück. Es kann eine 
0 gelesen worden sein oder Mumpitz.

von Einer K. (Gast)


Lesenswert?

Peter B. schrieb:
> Geht das irgendwie auch einfacher?
Dummer weise sagst du nicht, was du tun willst!
Zeigst auch keine Quelldaten.

Ansonsten haben dir meine Vorredner schon die üblichen Möglichkeiten 
vorgebetet.
Nutze die Methoden der Serial Klasse, welche von Stream einige Parser 
erbt.
Auch String bringt Parser/Konverter mit.
Die C-Lib, die ist auch voll damit.

von Peter B. (olduri)


Lesenswert?

Hi,
danke für die Antworten. toint, und atoi habe ich getestet und irgendwie 
nicht hinbekommen. Oder hat nicht funktioniert. sscanf werde ich noch 
testen.
Grüße, OldUri

Nachtrag - so funktionierts:
1
void loop() {
2
  if (Serial.available()) {
3
    String str = Serial.readString ();
4
    int wert = str.toInt();
5
    Serial.println (wert);
6
  }
7
}

Ich hatte nur "toint" mit kleinem "i" für kleine Integer getestet, und 
das scheint nicht implementiert zu sein. - Danke für die Antworten.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

Peter B. schrieb:
> String str = Serial.readString ();
>     int wert = str.toInt();

um String dein str in c-str() zu wandeln gibt es doch str.c_str()
das in wert = atoi(str.c_str()); sollte doch klappen

https://www.arduino.cc/reference/de/language/variables/data-types/string/functions/c_str/

von Einer K. (Gast)


Lesenswert?

Joachim B. schrieb:
> um String dein str in c-str() zu wandeln gibt es doch str.c_str()

String::c_str() liefert eine Zeiger auf den internen Buffer von String.
Es wandelt nicht.
Eigentlich ist die Methode recht kritisch, da man der Klasse so einen 
unterjubeln kann. z.B. strtok() usw. verträgt sich nicht unbedingt damit
Auch kann die Instanz verworfen werden und du behältst einen Zeiger auf 
freigegebene Speicherbereiche. Zudem weiß man die wahre Größe des 
Buffers nicht.

Zum Export ist eigentlich String::toCharArray() gedacht.

von Peter B. (olduri)


Lesenswert?

Joachim B. schrieb:

> wert = atoi(str.c_str()); sollte doch klappen

Ja, tut es auch. Habe soeben getestet: es ist mit 56 Byte weniger 
Programmspeicherplatz sogar sparsamer gegenüber "wert = str.toInt();".
Funktionelle Unterschiede habe ich keine gefunden, beide schnippeln 
Blanks vorne und hinten ab, beide hören beim 1. nichtnum. Zeichen auf.
Wieder was gelernt!

MfG, OldUri

von Joachim B. (jar)


Lesenswert?

Arduino Fanboy D. schrieb:
> Zudem weiß man die wahre Größe des
> Buffers nicht.

deswegen mag ich die Stringklasse nicht, aber keiner hindert mich Buffer 
passend anzulegen nach C-Manier, vor dem Überlauf abzubrechen den Puffer 
zu leeren und nur sinnvolle Kommandos die erkannt wurden zu nutzen.
Damit ist zumindest mein Ramverbrauch definiert!

In einem Beispiel wird mal eben der Serial In buffer STRING auf 200 Byte 
angelegt mit reserve, kein Prüfen auf Überlauf oder fand ich nur 
schlechte Beispiele?

Keine Reservierung, keine Prüfung auf Überlauf
https://www.best-microcontroller-projects.com/arduino-string.html

von Einer K. (Gast)


Lesenswert?

Joachim B. schrieb:
> Keine Reservierung,
> https://www.best-microcontroller-projects.com/arduino-string.html

Wenn du dich an schlechten Beispielen orientieren möchtest, dann men 
los.

Dass da jemand keinen Speicher reserviert, und so der Fragmentierung auf 
kleinen µC Tür und Tor öffnet, kannst/darfst du weder Arduino noch mir 
ans Hemd kleben.

Merke:
https://www.arduino.cc/reference/en/language/variables/data-types/string/functions/reserve/


> keine Prüfung auf Überlauf
Leider gibt es keine Execptions unter AVR GCC, daher ist es 
Problematisch ein Versagen der Speicherverwaltung an das aufrufende 
Programm/Funktion/Methode zu melden. Allerdings wird ein jeder 
Überlauf/Speichermangel in sofern abgehandelt, dass eben die Erweiterung 
des internen Buffers nicht stattfindet.
Das macht leider den String unvollständig. Aber immerhin läuft der Rest 
des Programms unbeeinflusst weiter.

von W.S. (Gast)


Lesenswert?

Peter B. schrieb:
> Und da sind jetzt noch keine Plaus-Tests oder Range-Checks dabei.
> Geht das irgendwie auch einfacher? Habe ich irgendeine Standard-Lib
> nicht gefunden?

Kommt drauf an, was du tatsächlich haben willst. In deinem Beispiel hast 
du dir ja einen abgebrochen... s.u.

Ich für meinen Teil brauche im Grunde immer wieder eine einfache 
Kommandozeilen-Auswertung und da stehen eben nicht nur Zahlen auf der 
Kommandozeile, sondern es kann alles mögliche sein je nach Fall. Also 
muß es ein möglichst einfach zu benutzender Parser sein und den hab ich 
mir selber geschrieben, weil die Standard-Funktionen von C zuviel Vor- 
oder Nacharbeit verlangen und nicht wirklich flexibel genug sind. Da 
kann ich dir nur empfehlen, dir sowas auch zuzulegen, wenn man's da hat, 
gebraucht man es allenthalben und es erleichtert die Arbeit immens.

Ansonsten ist dein Ansatz etwas seltsam:
1
  for (i = j; i >= 0; i--) {           // in einzelne Bytes zerlegen
2
     wert += (int (st[i]) - 48) * hoch; // aus ACII num. Wert erzeugen
3
     hoch *= 10;                        // ins 10er System bringen

Es reicht doch aus, etwa so zu verfahren:
1
 char* P;
2
3
 wert = 0;
4
 und die Schleife dazu:
5
  P = SkipBlanks(meineZeile)  
6
  do
7
  { ch =  *P++;
8
    if ((ch>='0')&&(ch<='9')
9
    { ch = ch - '0';
10
      wert = wert * 10 + ch; 
11
   // C kennt keinen Unterschied zwischen char und byte
12
    }
13
    else fertig und raus hier
14
  } ...
Eine Multiplikation mit 10 mußt du dir in jedem Falle gönnen, allenfalls 
bei Platformen ohne Mul dann eben die *10 auflösen in shl 2,add,shl 1

Und eine Zahl im nur möglichen Bereich 0..9 zu einem int oder longint zu 
addieren, geht immer. Dafür brauchst du keinen cast.

Und wenn du es so machst wie ich gezeigt habe, dann brauchst du auch 
nicht erst das Ende zu finden, um von da aus rückwärts die Ziffern zu 
verarbeiten, sondern kannst von vorn bis zum Trennzeichen bzw. null am 
Ende vorwärts arbeiten. Eben bei jeder Ziffer Wert=Wert*10+Ziffer 
rechnen.

W.S.

von Peter B. (olduri)


Lesenswert?

W.S. schrieb:
> kannst von vorn bis zum Trennzeichen bzw. null am
> Ende vorwärts arbeiten.

Ja, deine Version ist zweifellos eleganter. Ich war ja froh, dass ich 
mit dem mir völlig ungewohnten "C" überhaupt ein korrektes Ergebnis 
hinbekam, nach längerem Herumprobieren. (Und ich habe durchaus gesucht!)

Mein Ziel war / ist, dem blöden Arduino irgendwelche Kommandos, 
bestehend aus jeweils einem Zeichen, zu schicken. In der Ardoino-IDE 
geht das per "Seriellem Monitor" mit Zeichen + Return, ohne diese, z.B. 
mit microcom-Terminal, einfach das Zeichen eingeben, Return nicht nötig. 
Ist logisch, weil der Serial.read() auf der anderen Seite auch einzelne 
Zeichen einliest. Dann kam der Wunsch auf, auch num. Werte zu 
übermitteln. Naheliegend, aber zu kurz gedacht: Buchstabe u. 
Ziffernfolge als String eingeben.

Aufgrund der bisherigen Diskussion werde ich es jetzt umdrehen: Wenn 
Ziffer ankommt, dann in num. Wert umwandeln bis der abschließende 
Bef.Char. bestimmt, was damit geschieht. Wird deutlich einfacher!

Dann brauche ich nicht mal einen String und auch keinen 
readString-Befehl

Danke für Eure Tipps!

Nachtrag: besonders Chic finde ich: "ch = ch - '0';"

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Peter B. schrieb:
> Nachtrag: besonders Chic finde ich: "ch = ch - '0';"

Ist besser als "magische" Zahlen im Quelltext, die man nur erkennt, wenn 
man diverse Tabellen im Kopf hat.

von Joachim B. (jar)


Lesenswert?

Peter B. schrieb:
> Nachtrag: besonders Chic finde ich: "ch = ch - '0';"

die man immer noch behandeln muss, da neheme ich lieber isnum()

von Stefan F. (Gast)


Lesenswert?

Joachim B. schrieb:
> isnum()

isnum() subtrahiert nicht.

von Einer K. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> isnum() subtrahiert nicht.
Trotzdem gut, um "kaputte" Daten zu erkennen und zu melden.

Peter B. schrieb:
> besonders Chic finde ich: "ch = ch - '0';"
Ich nicht.
Denn da wird nicht nur der Wert, sondern auch die Bedeutung einer 
Variablen geändert.
Das ist unanständig, da es dem "Prinzip der geringsten Verwunderung" 
widerspricht.
Es ist ein (folgenloser?) Verstoß gegen die Typisierungsprinzipien.

Besser:
1
char    ch   = '5';
2
uint8_t num  = ch - '0';

von sehe keinen Unterschied (Gast)


Lesenswert?

uint8_t ist auch nur ein char.

nur halt per typedef anders genannt.

von Einer K. (Gast)


Lesenswert?

sehe keinen Unterschied schrieb:
> uint8_t ist auch nur ein char.

char ist dafür bestimmt, Buchstaben/Zeichen aufzunehmen.
uint8_t ist für binäre numerische Values.

Das ist ein Prinzip.
Von mir aus auch ein Glaubensbekenntnis.
Dass beide dabei den gleichen Raum einnehmen ist irrelevant.

Es ist eine Frage der Semantik, nix anderes.

Wiederholung:
> besonders Chic finde ich: "ch = ch - '0';"
Ich nicht.
Denn da wird nicht nur der Wert, sondern auch die Semantik einer
Variablen geändert.

Zusatz:
Das kann zu schwer findbaren Fehlern führen.
Gerade wenn man Fremdcode wartet, ist das u.U. sehr hinterhältig.

von sehe keinen Unterschied (Gast)


Lesenswert?

W.S. schrieb:
> Also
> muß es ein möglichst einfach zu benutzender Parser sein und den hab ich
> mir selber geschrieben, weil die Standard-Funktionen von C zuviel Vor-
> oder Nacharbeit verlangen

Ok,  atoi() liefer dafür aber für char* str="-123" zumindest richtige 
Ergebnisse ;-)

von Stefan F. (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> char ist dafür bestimmt, Buchstaben/Zeichen aufzunehmen.
> uint8_t ist für binäre numerische Values.

Vor der Einführung von uint8_t war char mit Sicherheit auch für Zahlen 
vorgesehen.

von sehe keinen Unterschied (Gast)


Lesenswert?

Peter B. schrieb:
> Mein Ziel war / ist, dem blöden Arduino irgendwelche Kommandos,
> bestehend aus jeweils einem Zeichen, zu schicken.

1
  char* cmd_str = "123boom";
2
   char* p = cmd_str;
3
4
   while (*p) {
5
      switch (*p++) {
6
         case '1':
7
            do_one();
8
            break;
9
         case '2':
10
            do_two();
11
            break;
12
         case '3':
13
            do_three();
14
            break;
15
         default:
16
            do_nothing();
17
            // or
18
            explode();
19
      }
20
   }

einfach alle Zahlen im String abarbeiten.

von Einer K. (Gast)


Lesenswert?

Stefan ⛄ F. schrieb:
> Vor der Einführung von uint8_t war char mit Sicherheit auch für Zahlen
> vorgesehen.

Und?
Ist das ein gutes Argument um das "Prinzip der geringsten Verwunderung" 
auszuhebeln?

Merke:
Wir sprechen hier nicht über einen uralt C Compiler, sondern über 
relativ modernes C++.


Tipp:
Früher war alles besser, sogar die Zukunft.

von Joachim B. (jar)


Lesenswert?

Stefan ⛄ F. schrieb:
> isnum() subtrahiert nicht.

will ich auch nicht und deswegen gilt dein Einwand nicht!
Man muss schon wissen was man will, wer will denn von allen möglichen 
Char stets 48 abziehen?

Peter B. schrieb:
> dem blöden Arduino

der Arduino ist nicht blöde, also wirklich.......

weiter schreibe ich nicht!

von sehe keinen Unterschied (Gast)


Lesenswert?

isdigit()

von Stefan F. (Gast)


Lesenswert?

Joachim B. schrieb:
>>>>> besonders Chic finde ich: "ch = ch - '0';"
>>> die man immer noch behandeln muss, da nehme ich lieber isnum()
>> isnum() subtrahiert nicht.
> will ich auch nicht und deswegen gilt dein Einwand nicht!

Du hast isnum() als Alternative für die Subtraktion empfohlen.

> Man muss schon wissen was man will, wer will denn von allen möglichen
> Char stets 48 abziehen?

Ich nehme an, dass du so wie ich die avr-libc gerne verwendest, weil du 
der Meinung bist, dass sie gut und verlässlich ist, richtig?

Die atoi() Funktion avr-libc ruft intern strtol() aus. Schau mal was ich 
da gefunden habe:
1
if (c >= '0' && c <= '9')
2
  c -= '0';
https://github.com/vancegroup-mirrors/avr-libc/blob/master/avr-libc/libc/stdlib/strtol.c

Da wird auch subtrahiert und die Variable wird wechselweise für Zeichen 
und Zahlen verwendet. Wer hätte das gedacht!

Wie würdest du denn ein atoi() ohne Subtraktion "besser" implementieren?

von W.S. (Gast)


Lesenswert?

Peter B. schrieb:
> Nachtrag: besonders Chic finde ich: "ch = ch - '0';"

Naja, kann man so oder so sehen. Im Grunde finde ich es absolut 
unzeitgemäß, char und byte/word/long/int64 und Konsorten in einen 
einzigen Topf zu werfen. Daß C damals eben derart schlampig entworfen 
wurde, mag man ja verstehen, die Erfinder hatten als Vorlage lediglich 
BCPL vor der Nase und offenbar keine Ahnung davon was das damals 
durchaus übliche Algol war. Vielleicht hatten sie auch bloß ihren Stolz, 
etwas zu schaffen, das möglichst in allen Dingen anders ist als Algol 
und Fortran war und ist eigentlich zu mathematisch orientiert.

Aber so ist das, ich benutze C eben, weil es für µC derzeit nichts 
besseres gibt - aber ohne es zu mögen. Falls es vom Free Pascal mal eine 
echte Version für Arm bzw. Cortex bare metal gibt, würde ich 
umschwenken.

Und du hast mittlerweile die Kommandoweise für dich entdeckt, die ich 
für diverse PC-Peripherie verwende: zuerst numerisches Argument und dann 
als Abschluß ein Buchstabe. Für die Kommandogabe von Programm-->µC geht 
das ausgezeichnet, aber wenn man mal etwas für menschliche Kommunikation 
schreibt, dann ist das nicht so gut. Da läßt man lieber eine ganze 
Kommandozeile in einem Puffer auflaufen und wertet sie anschließend aus. 
Das macht es möglich, auch mal sowas wie BackSpace zu verwenden, wenn 
man sich mal vertippt hat und man kann als Kommandos beliebige Worte 
verwenden.
Ich geb dir mal ne Kostprobe in Form von "match":
1
char match (char* item, char** Zptr)
2
{ char* P;
3
  IgnoreSpace(Zptr);
4
  P = *Zptr;
5
  if (UpCase(*P) != *item) return 0;
6
  while (*item) { if (UpCase(*P++)!=(*item++)) return 0; }
7
  *Zptr = P;
8
  IgnoreSpace(Zptr);
9
  return 1;
10
}
Das liest aus der Kommandozeile und zwar von der gerade aktuellen Stelle 
die Zeichen (Zptr zeigt auf den Zeiger, der auf die aktuelle Stelle 
zeigt), vergleicht sie mit einem Referenztext (item zeigt dort drauf) 
und liefert 0 oder 1 je nachdem ob die Zeichenfolge übereinstimmt oder 
nicht. Wenn übereinstimmt, dann wird der Zeiger für die aktuelle Stelle 
hinter das Kommando gesetzt, sonst nicht. Ach ja, Referenz in 
Großbuchstaben, weil ich hier keine Case-Sensivität haben wollte. Und 
die Funktion ist steinalt, heute würde ich nicht char sondern bool 
schreiben.
Und die Verwendung wäre:
1
  if (match("BLOCKERASE",&Cpt))
2
    { L = Hex_In(&Cpt);
3
      Adresse = L;
4
      BlockLoeschen(wo);
5
      return;
6
    }
7
8
  if (match("ERASE",&Cpt)) 
9
    { if (match("ALL",&Cpt))
10
        { String_Out("Lösche Flash 0..", wo);
11
          ChipErase(0, wo);
12
          String_Out("Lösche Flash 1..", wo);
13
          ChipErase(FlashSize, wo);
14
          return;
15
        }
16
      if (match("FL0",&Cpt))
17
        { String_Out("Lösche Flash 0..", wo);
18
          ChipErase(0, wo);
19
          return;
20
        }
21
      if (match("FL1",&Cpt))
22
        { String_Out("Lösche Flash 1..", wo);
23
          ChipErase(FlashSize, wo);
24
          return;
25
        }
26
      String_Out(" Args: FL0 oder FL1\r\n", wo);
27
      return;
28
    }
29
30
  if (match("PROG",&Cpt))  
31
    { L = Hex_In(&Cpt);
32
      while (*Cpt)
33
      { B = Hex_In(&Cpt);
34
        erg = ProgFlash (L, B, wo);
35
        if (!erg)
36
        { String_Out("..Fehler!\r\n", wo);
37
          return;
38
        }
39
        ++L;
40
    }
Das ist nur zum Erkennen, wie match verwendet wird. Man kann es je nach 
Bedarf auch für mehrere Textargumente nehmen. Und die Funktionen Hex_In, 
Long_In usw. benutzen dieselbe 'Mechanik': sie kriegen einen Zeiger auf 
den Zeiger in die Kommandozeile ab, konvertieren ab dieser Stelle in der 
Kommandozeile und aktualisieren dann den Zeiger in die Kommandozeile.

W.S.

von Mono (Gast)


Lesenswert?

Mir wird ganz schwindelig!
Seit ihr alle studierte und C spezialisierte Informatiker?
Oder Aliens🤓?
Oder macht Ihr seit Jahrzehnten nix anderes?
Hut ab.

von Joachim B. (jar)


Lesenswert?

Stefan ⛄ F. schrieb:
> Du hast isnum() als Alternative für die Subtraktion empfohlen.

ne!
nur für den Einzelzeichentest!
abgesehen davon das ich mich irrte, alle meine Codes durchsucht und nur 
Zeichen eingesammelt die isprint() waren somit fallen alle anderen raus!
Mit isnum irrte ich!

und du hast meinen Text nicht verstanden

Joachim B. schrieb:
> Peter B. schrieb:
>> Nachtrag: besonders Chic finde ich: "ch = ch - '0';"
>
> die man immer noch behandeln muss, da neheme ich lieber isnum()

wenn ich alle eintrudelnen Zeichen mit '0' subtrahiere kommt Müll raus!
wenn ich auf Ziffer testen will isnum! aber nur für das einzelne Zeichen 
und isnum subtrahiert nicht, war falsch und ich meinte isdigit

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Joachim B. schrieb:
> und du hast meinen Text nicht verstanden
> wenn ich alle eintrudelnen Zeichen mit '0' subtrahiere kommt Müll raus!
> wenn ich auf Ziffer testen will isnum! aber nur für das einzelne Zeichen
> und isnum subtrahiert nicht, war falsch und ich meinte isdigit

Ach so, jetzt ergibt es auch für mich Sinn.

von sehe keinen Unterschied (Gast)


Lesenswert?

W.S. schrieb:
> char match (char* item, char** Zptr)
> { char* P;
>   IgnoreSpace(Zptr);
>   P = *Zptr;
>   if (UpCase(*P) != *item) return 0;
>   while (*item) { if (UpCase(*P++)!=(*item++)) return 0; }
>   *Zptr = P;
>   IgnoreSpace(Zptr);
>   return 1;
> }

Die Zeile
1
if (UpCase(*P) != *item) return 0;
ist nicht nötig. Die while-Schleife macht das doch schon.

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.