Frage zu sprintf() und dtostrf()

Einen schönen Start in die Woche wünsch ich Euch Allen!

Eine kurze Frage, zu den im Topic erwähnten Funktionen. Ich habe jetzt sämtliche Foren und Bücher gecheckt zu dem Thema und verstehe einfach nicht, warum es bei mir nicht funktioniert.

Ich möchte ein paar Werte in einen String schreiben um Ihn später auf eine SD Karte zu schieben. Aber egal, was ich mache, die Floats werden nicht gespeichert.... :frowning:

Meine einfaches Beispiel:

#include <stdio.h>

float x = 1.12345;
int y=15;
float z = 2.12345;

char SDbuf[500];

void loop ()
{
sprintf (SDbuf, "%.1f %i %f",  x,  y,  z);   //zum Test einmal mit Nachkomma, einmal einen Int, einmal ohne Nachkomma
                                                                      Formatierung
Serial.print(SDbuf)

while(1)
}

Egal, was ich mache, ich knall mir den Kopf gleich auf die Tischplatte, er gibt immer aus:

? 15 ?

Wenn ich die float vorher mit dtostrf() umwandle, geht es....
Aber das sollte doch gar nicht nötig sein, vor allem, da ich später ganze 27 Werte habe, die ich in meinen Sting schreiben möchte....

Zum ausflippen!! :0 :0 :0

Vielen Dank schon mal!

TEC_MICHL:
Aber egal, was ich mache, die Floats werden nicht gespeichert.... :frowning:

8-Bit AVR-Controller haben keine eingebaute Gleitkomma-Recheneinheit. Alle Gleitkommarechnungen werden nur in Software emuliert, die Softwareemulation als Library dazugelinkt. Resultierende Programme werden aufgebläht.

Und damit die resultierenden Programme nicht zu sehr aufgebläht werden, sind manche Funktionen in der AVR libc abgespeckt, z.B. hat sprintf und snprintf KEINE Unterstützung für Gleitkommazahlen dabei.

Du müßtest Deine Zahlen also anders behandeln oder wenigstens anders formatieren.

In vielen Arduino-Sketchen, die ich hier sehe, werden Gleitkommazahlen überhaupt ohne Notwendigkeit verwendet. Da wird ein Temperatursensor per 10 Bit ADC oder 13 Bit vom digitalen Sensor ausgelesen und statt den Wert in einer Integer-Zahl mit 16 Bit exakt abzulegen, wird der Wert mit Float-Rundungsfehlern in eine 32-Bit float Variable gepackt - mit allen Nachteilen.

Anstatt z.B. eine Temperatur in Zehntelgrad als Integer durch das gesamte Programm zu schleppen und nur bei der Darstellung am Ende den Integerwert mit einfacher Modulo-10 Aritmetik "wie eine Gleitkommazahl" auszugeben. Was dazu noch den Vorteil hätte, Dezimalstellen mit Komma statt Dezimalpunkt für eine menschenlesbare Anzeige abtrennen zu können, so wie es in den Ländern Kontinentaleuropas bei Zahlendarstellungen gebräuchlicher ist.

But anyway. Trotz aller Rundungsfehler und des hohen Speicherbedarfs bevorzugen manche Gleitkommazahlen. Auch, wenn der Controller dafür eigentlich nicht gemacht ist und diese nur notdürftig und teilweise unterstützt.

TEC_MICHL:
Wenn ich die float vorher mit dtostrf() umwandle, geht es....

Ja, das geht. Damit kann man Stück für Stück sogar ellenlange Strings durchformatieren bis der Rattenschwanz am Ende komplett ist.

TEC_MICHL:
Meine einfaches Beispiel:

Mein einfaches Beispiel:

float x = 1.12345;
int y=15;
float z = 2.12345;

char SDbuf[500];

void setup()
{
  SDbuf[0]='\0'; // Nullstring setzen
  dtostrf(x,2,1,&SDbuf[strlen(SDbuf)]); // x mit einer Nachkommastelle formatieren
  strcat(SDbuf," ");
  dtostrf(y,1,0,&SDbuf[strlen(SDbuf)]); // y ohne Nachkommastelle formatieren
  strcat(SDbuf," ");
  dtostrf(z,1,0,&SDbuf[strlen(SDbuf)]); // z ohne Nachkommastelle formatieren
  Serial.begin(9600);
  Serial.println(SDbuf);
}
  
void loop() {}

dtostrf() ist auch kein ISO-C, sondern ein AVR-libc spezifische Funktion

Es gibt Libs und Patches float für Float Unterstützung, z.B.:
http://forum.arduino.cc/index.php?topic=166540.0
http://forum.arduino.cc/index.php?topic=179111.msg1375203#msg1375203

Aber wie gesagt bläht das dein Programm auf.

Wow! Vielen Dank für diese ausführliche Antwort!!

Dann kann es ja nicht klappen, wenn er mit floats nicht umgehen kann.

Das Ding ist ja, dass ich mir, um den Einstieg zu erleichtern, ein Buch von Franzis "Adruino und C" gekauft habe. Da steht mit keinem einzigen Wort in den Beispielen, dass das der Arduino gar nicht kann. Ich hab schon an meinem Verstand gezweifelt!

Ich muss Dir absolut recht geben, wenn ich mir meine Werte mal von der pysikalischen Seite betrachte, gauckelt einem der Float eine Genauigkeit vor, die so eh nicht erreicht wird. Wie z.b. Ultraschallsensoren. Wenn ich da einen Wert auslese, von 10,21312 cm ist das totaler Quatsch. Selbiges bei den Werten der Gyros, Acells, Compass usw... Selbst mir DCM und Filter sind die niemals genauer als 1 Grad... Ich werde, soweit es geht auf floats verzichten. Es geht eigentlich überall, wenn ich es genau betrachte.

Dann spar ich mir die dtostrf() nämlich und kann meinem 27 Werte fetten String einfach Zahl für Zahl zusammen bauen. Ich muss auch gucken, dass ich mit etwa 2-5 Hz schreiben kann, also den Code durch optimieren und floats machen das Ding wirklich langsamer....

Vielen Dank nochmal, da hast Du mir wirklich sehr weitergeholfen! :slight_smile:

Nachtrag:

der einzige Wert, den ich nicht als int nehmen kann, ist meine Geschwindigkeit:

m/s ist zu ungenau, als das ich da nur das int nehmen kann.

Was ist resourcenschonender?

Wenn ich die dtostrf() nehme oder wenn ich einfach, sagen wir mal, auf 2 Nachkommastellen genau, es so mache:

float speed = 12,12345 // als bsp in m/s

int speed_1 = speed;
speed= (speed-speed-1)*100  //Vor dem Komma =0 und dann *100, so hab ich die 2 Nachkommastellen
int speed_2 = speed;

sprintf(SDbuf, "%i , %i" , speed_1, speed_2)

TEC_MICHL:
m/s ist zu ungenau, als das ich da nur das int nehmen kann.

Was ist resourcenschonender?

Den benötigten RAM-Speicher, die Programmgröße und die für Berechnungen notwendige Zeit hältst Du klein, wenn Du nur mit Integerzahlen rechnest, float gar nicht verwendest. Für größere Wertebereiche ggf. long oder unsigned long verwenden, wenn tatsächlich größere Werte mit vielen signifikanten Stellen benötigt werden.

Z.B. wenn m/s als ganzzahliger Wertebereich nicht reicht, kannst Du ja im Programm auch mit cm/s rechnen und am Ende bei der Ausgabe trotzdem auf eine Ausgabe in m/s formatieren. Als Integer reicht cm/s für mindestens einen Bereich -30000 bis +30000 cm/s, also +/-300 m/s.

Ein Programm, das intern ganzzahlig mit cm/s rechnet und am Ende die Werte formatiert in m/s ausgibt, könnte es z.B. so machen:

  int speed = 1202; // Geschwindigkeit in cm/s ausgerechnet
  char SDbuf[500];
  sprintf(SDbuf, "%i.%02i" , speed/100, speed%100);  // Ausgabe in m/s

Bei den gefakten Nachkommastellen mußt Du unbedingt "führende Nullen" mit ausgeben, also Nachkommaformatierung für zwei Stellen mit führenden Nullen "%02i"!

Falls auch negative Werte auszugeben sind, wäre die Formatierung der Nachkommastellen mit Absolutbetrag notwendig:

  sprintf(SDbuf, "%i.%02i" , speed/100, abs(speed)%100);

Falls bei der Ausgabe der Rechenwerte gerundet werden muss, z.B. Du rechnest intern mit mm/s und möchtest ausgeben in m/s mit zwei Nachkommastellen, also die letzte Stelle soll weggerundet werden, könnte man auch das noch einbauen, ohne dass im Programm überhaupt nur eine Gleitkommazahl angefaßt werden müßte.

Wie bereits gesagt: Viele Programme brauchen gar keine Gleitkommazahlen.

Es reicht vielfach auch einfach aus, Zahlenwerte in "ungewöhnlichen Einheiten" durch das Programm durch ganzzahlig zu rechnen, wie Geschwindigkeiten in cm/s oder mm/s oder Temperaturen in Zehntelgrad oder Hundertstelgrad, und am Ende dann für die "menschengerechte Ausgabe" mit Modulo-Arithmetik eine Fake-Gleitkommaformatierung zu machen, so als ob im Programm tatsächlich mit Gleitkommazahlen gearbeitet worden wäre.

Hey Jurs,

vielen Dank für diese ausführliche Antwort und die tollen Tips. Ich werde mein Programm nochmal komplett durchgehen und schaun, wo ich auf floats verzichten kann.

Das Problem ist, das z.b. beim GPS die ganzen Variablen schon in der Header File vergegeben sind. Die Frage, ist, was passiert, wenn ich dort schon die Werte von float zu int ändere und wie Du schon sagstest, umrechne in z.b. cm/s.... Der NMEA Datenstring liefert zum bsp. die Geschwindigkeit in Knoten. Das wird sicher ein bissl ein gebastel.

Vielleicht kannst Du mir in dem Zuge auch nochmal ein Tip geben, was die führenden Nullen betrifft, um auf den NMEA Sting zurück zu kommen, als Bsp. Der liefert eine Uhrzeit ohne führende Nullen und das schaut blöd aus.
Also zum Beispiel: 1:05:07 ---> gibt er dann so aus: 1:5:7 , Schöner wäre: 01:05:07
Gibts da nen Trick?

TEC_MICHL:
Das Problem ist, das z.b. beim GPS die ganzen Variablen schon in der Header File vergegeben sind. Die Frage, ist, was passiert, wenn ich dort schon die Werte von float zu int ändere und wie Du schon sagstest, umrechne in z.b. cm/s.... Der NMEA Datenstring liefert zum bsp. die Geschwindigkeit in Knoten. Das wird sicher ein bissl ein gebastel.

GPS-Daten im NMEA-Format liegen zunächst einmal als reines ASCII-Format vor, als Text, wenn die Daten ankommen. Die haben zunächst mal gar kein numerisches Datenformat. In ein numerisches Datenformat mußt Du die Daten erst selbst umwandeln, wenn Du damit rechnen möchtest.

Solange es nur darum geht, die ankommenden GPS-Daten unverändert auf einer SD-Karte wegzuspeichern, brauchen die Daten auch (vielleicht mit Ausnahme der speziell codierten Werte für "Längengrad" und "Breitengrad") in gar kein numerisches Format umgewandelt zu werden: Die Daten kommen als Text und sie werden als Text auf SD-Karte weggespeichert, das ist die kürzestmögliche, schnellste und direkteste Verarbeitung.

Sofern mit den Daten gerechnet werden soll, bietet es sich vielfach an, das im Gleitkommaformat zu machen. Gängige GPS-Libraries wandeln dazu eintreffende Daten wie "Koordinaten" und "Geschwindigkeiten" tatsächlich in "float" Werte um. Denn trigonometrische Berechnungen auf dem Erd-Geoid sind tatsächlich ein Anwendungsfall, bei dem man um die Verwendung von Gleitkommazahlen nicht herumkommt. Z.B. Abstandsberechnungen zwischen zwei Punkten auf der Erdoberfläche.

TEC_MICHL:
Vielleicht kannst Du mir in dem Zuge auch nochmal ein Tip geben, was die führenden Nullen betrifft, um auf den NMEA Sting zurück zu kommen, als Bsp. Der liefert eine Uhrzeit ohne führende Nullen und das schaut blöd aus.
Also zum Beispiel: 1:05:07 ---> gibt er dann so aus: 1:5:7 , Schöner wäre: 01:05:07
Gibts da nen Trick?

Im NMEA-Datenformat gibt es keine Zeitangaben mit Doppelpunkt. sondern die Daten kommen vom Empfänger wie alles als Text rein, z.B. "110201.00" für 11 Uhr, 2 Minuten, 1 Sekunde (UTC-Zeit, Bruchteile von Sekunden meist nur ausgenullt, aber wohl von manchen Empfängern auch mitgesendet). Wenn Du ein Beispielprogramm und eine Library zum Empfangen und zur Formatierung in einem bestimmten Format verwendest, müßtest Du ggf. mal genauer in das Beispielprogramm oder den Library-Quellcode reinschauen, wo diese Formatierung stattfindet und so gemacht wird.

Ja, das war auch mein erster Gedanke, den NMEA Sting, so wie er vom GPS kommt, auf die SD Karte zu speichern.

Ich verwende diese Lib. GitHub - adafruit/Adafruit_GPS: An interrupt-based GPS Arduino library for no-parsing-required use. Also Standart denke ich. Dort wird der Sting bei Bedarf zerlegt. Man kann natürlich auch direkt den Sting auslesen.

Doch dann dachte ich mir, dass der, der die Daten später auswerten muss, sich leichter tut, wenn er diese gleich schön formatiert bekommt. Also die Geschwindigktei als m/s, nicht Knoten, schön mit Komma getrennt, das man das direkt ins Execl oder Matlab schaufeln kann. Als Bsp hatte ich das verwendet. https://github.com/adafruit/Adafruit-GPS-Library/blob/master/examples/parsing/parsing.pde

Für den Datenlog des kompletten Strings gibts auch ein bsp:
https://github.com/adafruit/Adafruit-GPS-Library/blob/master/examples/parsing/parsing.pde

Somit führen viele Wege nach Rom. Die Geschwindigkeit stellt, so wie ich das sehe, kein Problem dar. Mein jetziges Programm (1535 Zeilen und 17 Unterprogrammen) schafft immer noch alle ca 20-40 millis einen kompletten String auf die SD KArte zu schreiben (aus 27 bis zu 3stelligen Werten. Ziel waren etwas 5 Strings pro Sekunde, also hab ich noch Luft nach oben. Die Updaterate für das Display mit etwa 30 Werten hab ich im Moment bei 100ms, was ich auch noch langsamer machen könnte um noch Resourchen frei zu machen.

Von dem her sollte ich mir also den "Luxus" der schönen Formatierung leisten können.

Da hätte ich auch gleich noch ne Frage:
Die Angabe Nord/Süd und Ost/West, werden in der GPS Header File als char lon und char lat angelegt und beinahlten 'N' oder 'S' bzw. 'E' oder 'W'

Wenn ich jetzt:

    sprintf (SDbuf, " %s" , GPS.lat)

Schreibt er nur mist in den Sting, wo liegt der Fehler?

Danke für die ausführliche Antwort!! :-))

in Adafruit_GPS.h ist

[b]char lat;[/b]

definiert.
"%s" ist aber für einen char* gedacht, einen nullterminierten Text.
"%c" gibt dir den in lat gespeicherten einzelnen Buchstaben aus.

sprintf bräuchtest du nicht unbedingt:

static char latTest[] = "Lat: #";

latTest[5] = gps.lat;  // das # ersetzen durch gps.lat
Serial.println(latTest); // ... und den string ausgeben

Mist ist, dass printf die % und die nachfolgenden Parameter blind zusammenbringt.
Wenn lat z.B. den Wert 'N' hat, wird mit "%s" der Text, der auf Adresse 0x4E liegt, ausgegeben, auch wenn da gar kein Text liegt.

TEC_MICHL:
Ich verwende diese Lib. GitHub - adafruit/Adafruit_GPS: An interrupt-based GPS Arduino library for no-parsing-required use. Also Standart denke ich.

Hm, also hier im Forum verwenden die meisten, die NMEA mit einer GPS-Library auswerten, vermutlich die TinyGPS Library, wenn ich die bisherigen Threads dazu mal gedanklich Revue passieren lasse. Jede Library hat natürlich ihre Vor- und Nachteile. Im Zweifelsfall schreibt man seine eigene Empfangsroutine und einen Parser dazu.

TEC_MICHL:
Doch dann dachte ich mir, dass der, der die Daten später auswerten muss, sich leichter tut, wenn er diese gleich schön formatiert bekommt. Also die Geschwindigktei als m/s, nicht Knoten, schön mit Komma getrennt, das man das direkt ins Execl oder Matlab schaufeln kann.

Geschwindigkeiten in metrischen Einheiten, namentlich "km/h", kannst Du erhalten, wenn Du die entsprechenden NMEA-Sätze auswertest. $GPRMC enthält die Geschwindigkeit in Knoten, aber in $GPVTG Sätzen steht die Geschwindigkeit in km/h. Kann man sich ja herausziehen wie man möchte - abhängig von dem, was der NMEA-Empfänger an Datensätzen bereitstellt.

TEC_MICHL:
Somit führen viele Wege nach Rom.

Das ist immer so.

Vielen Dank für die vielen hilfreichen Antworten.

Nach einigem rumprobieren habe ich jetzt den "Fehler" gefunden, oder besser das Problem lösen können.
%c hatte ich auch erst verwendet. Aber wenn kein GPS Signal da ist, und damit der lon/lat char leer, dann bricht er den Sting an der Stelle ab. In meine Bude hab ich meistens kein Signal, und daher dachte ich beim testen, dass das mit %c generell nicht klappt, und %s verwendet. Wer lesen kann ist klar im Vorteil: In der stdio.h steht das ja, das %s für ein char* gilt.
Einen leeren char mag er wohl nicht.

Jetzt mach ich es einfach so, dass ich die beiden Chars ganz nach hinten verfrachte in meinem String und wenn das Signal mal verloren geht beim Loggen, dann werden alle anderen Daten geschieben.

Damit kann ich leben. Jetzt noch ein wenig mit den GPS libs befassen. Um das ganze noch hübscher und schlanker zu gestallten.

Somit schauts jetzt so aus und funktioniert auch! Juhu!!

    sprintf (SDbuf, "%ld; %i; %i; %i; %i; %i; %i %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %i; %c; %i; %i; %i; %c; ",
                     time_elap ,roll_1, pitch_1, yaw_1, roll_2, pitch_2, yaw_2, heading, speed_cms, RPM, volt_s_0, volt_s_1, volt_s_2, volt_s_3, volt_s_4, volt_s_5, speed_max0, speed_max1, speed_max2, speed_max3, speed_max4, gps_deg_lat, gps_min_lat, gps_sek_lat, GPS.lat, gps_deg_lon, gps_min_lon, gps_sek_lon, GPS.lon,    );