Go Down

Topic: Problem mit snprintf Formatierung (Read 2012 times) previous topic - next topic

Doc_Arduino

Hallo,

hier die Funktion

BatterieVoltageBuf ist global deklariert
char BatterieVoltageBuf[9];     // Buffer zum Zwischenspeichern der formatierten Batteriespannung

ADvoltage kommt als float rein und ist als float auch global definiert.

Code: [Select]

void BatterieVolt_to_DOGM (byte Spalte, byte Zeile)
{
static int vorK, nachK = 0;
static int Volt = 0;
char linebuf[9];
Volt = (int) (ADvoltage*5);   // Faktor Spannungsteiler, mV in Volt zerlegen | 2482,34*5 = 12411mV
vorK = Volt/1000;             // =  12  Vorkomma vereinzeln  | 12411mV > 12
nachK = Volt-(vorK*1000);     // = 411  Nachkomma vereinzeln | 12411mV - 12000 = 411
nachK = abs(nachK);           // Nachkommastelle immer positiv wandeln (Betrag)
// Spannungswert für Ausgabe formatieren
// reserviert Anzeigestellen, für Nachkomma-Wert mit bedingter 0 Anzeige
snprintf(BatterieVoltageBuf,sizeof(linebuf),"%3d,%02dV", vorK, nachK);
lcd.setCursor(Spalte,Zeile);
lcd.print(BatterieVoltageBuf);
}


Mein Problem ist, dass der Nachkommestellen Parameter scheinbar ignoriert wird.
snprintf(BatterieVoltageBuf,sizeof(linebuf),"%3d,%02dV", vorK, nachK);
Ob ich %02dV oder %03dV schreibe ist vollkommen egal. Ich bekomme immer 3 Kommastellen auf das Display.
Die Vorkommastelle mit ggf. - Vorzeichen funktioniert.
Buffergröße von 9 Zeichen sollte auch ausreichen. Ist sogar laut meiner Rechnung ums 1 größer wie benötigt.
Auf dem Display sollte dann entweder 12,41V oder -12,41V erscheinen.
Es erscheinen jedoch 12,411 oder -12,411 ohne "V".

Was mach ich falsch?   :smiley-roll:

Tschau
Doc Arduino '\0'

Messschieber auslesen: http://forum.arduino.cc/index.php?topic=273445
EA-DOGM Display - Demos: http://forum.arduino.cc/index.php?topic=378279

Serenifly

#1
Jun 12, 2014, 10:45 pm Last Edit: Jun 12, 2014, 11:03 pm by Serenifly Reason: 1
Wieso legst du einen Puffer namens linebuf an, aber schreibst das dann in BatterieVoltageBuf?

Die Variablen müssen hier übrigens nicht static sein. Das schadet zwar nichts, aber static sorgt dafür dass die Variablem ihren Wert beim nächsten Funktionsaufruf noch haben. Das ist hier unnötig, da du sie jedesmal überschreibst.

Ich habe es nur in Visual C++ ausprobiert und nicht auf dem Arduino, aber da geht das. Jedenfalls mit linebuf.

EDIT:
Er schreibt zumindest das V.

Das sonstige Verhalten ist allerdings normal:
http://www.cplusplus.com/reference/cstdio/printf/
Quote

Minimum number of characters to be printed. If the value to be printed is shorter than this number, the result is padded with blank spaces. The value is not truncated even if the result is larger.

Quote

For integer specifiers (d, i, o, u, x, X): precision specifies the minimum number of digits to be written. If the value to be written is shorter than this number, the result is padded with leading zeros. The value is not truncated even if the result is longer. A precision of 0 means that no character is written for the value 0.



Probier mal dtostrf(). Das macht was du willst:
http://www.mikrocontroller.net/topic/86391
http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html#ga060c998e77fb5fc0d3168b3ce8771d42

Doc_Arduino

Hallo,

okay, dann habe ich den/die Parameter falsch verstanden. Dann kürze ich das gleich vorher noch um eine Stelle. Ist am einfachsten für mich. Ist nur noch eine kleiner Fehler drin, wenn negative 0,xxx Werte auftreten. Die werden positiv dargestellt. Da muß ich mir nochwas einfallen lassen.

Warum muß man dann die Buffergröße doppelt angegeben? Einmal die Variable und dann nochmal. Die Buffergröße ist doch durch die Variable schon bekannt.   :smiley-roll:
snprintf(BatterieVoltageBuf,9,"%3d,%02dV", vorK, nachK);

Code: [Select]

void BatterieVolt_to_DOGM (byte Spalte, byte Zeile)
{
int vorK, nachK = 0;
int Volt = 0;
Volt = (int) (ADvoltage*5);   // Faktor Spannungsteiler, mV in Volt umgerechnet | 2482,34*5 = 12411mV
vorK = Volt/1000;             // =  12  Vorkomma vereinzeln  | 12411mV > 12
nachK = Volt-(vorK*1000);     // = 411  Nachkomma vereinzeln | 12411mV - 12000 = 411
nachK = nachK/10;             // und um eine Stelle kürzen auf 41
nachK = abs(nachK);           // Nachkommastelle immer positiv wandeln (Betrag)
// Spannungswert für Ausgabe formatieren
// reserviert je 2 Anzeigestellen, für Nachkomma-Wert mit bedingter 0 Anzeige
//snprintf(BatterieVoltageBuf,sizeof(linebuf),"%3d,%02dV", vorK, nachK);
snprintf(BatterieVoltageBuf,9,"%3d,%02dV", vorK, nachK);
lcd.setCursor(Spalte,Zeile);
lcd.print(BatterieVoltageBuf);
}

Tschau
Doc Arduino '\0'

Messschieber auslesen: http://forum.arduino.cc/index.php?topic=273445
EA-DOGM Display - Demos: http://forum.arduino.cc/index.php?topic=378279

Serenifly

Die erste Variable ist der Puffer selbst. Die zweite ist die Größe. Ja, theoretisch hätte man das auch so implementieren können dass sizeof() intern abgefragt wird. Ist halt so :)
Sollte so aussehen:
Code: [Select]

snprintf(linebuf, sizeof(linebuf), "%3d,%02dV", vorK, nachK);



Wie gesagt ist dtostrf() hier vielleicht besser zu handhaben:
Code: [Select]

  float val = -1.1466;
  char buf[9];
  dtostrf(val, 5, 2, buf);
  Serial.println(buf);

Das formatiert einen Float Wert auf min. 5 Zeichen (inklusive Punkt und Vorzeichen!) und 2 Nachkommastellen. Die Nachkommastellen werden auch tatsächlich abgeschnitten

Doc_Arduino

Hallo,

aha, Danke für die schöne Erklärung. sprintf (ohne n) ginge wohl nicht? Woher weis das die Stringlänge? Je länger ich darüber nachdenke umso weniger erschließt sich mir dann der Sinn von snprintf ....   :smiley-roll:

Habe das jetzt mit dem Vorzeichen gelöst wie folgt. ... und sehe gerade Dein Bsp. mit dtostrf. 
Sieht doch um Längen einfacher aus. Na Hauptsache meine grauen Zellen haben geschwitzt.   ;)   Funktionieren tut es.   :)

Wegen der Variablendefinition nochmal. Ich möchte die Variablen lokal definieren, aber nicht das sie jedesmal beim Funktionsaufruf neu definiert werden. Deshalb dachte ich static wäre richtig. Weil die sowieso immer lokal überschrieben werden, dachte ich das ständige nullen ist überflüssig und könnte man sich damit ersparen.

Code: [Select]

void BatterieVolt_to_DOGM (byte Spalte, byte Zeile)
{
int vorK, nachK = 0;
int Volt = 0;
char VorZeichen = ' ';
Volt = (int) (ADvoltage*5);   // Faktor Spannungsteiler, mV in Volt umgerechnet | 2482,34*5 = 12411mV
if (Volt < 0)
   {
    VorZeichen = '-';
    Volt = Volt * -1;
   }
vorK = Volt/1000;             // =  12  Vorkomma vereinzeln  | 12411mV > 12
nachK = Volt-(vorK*1000);     // = 411  Nachkomma vereinzeln | 12411mV - 12000 = 411
nachK = nachK/10;             // und um eine Stelle kürzen auf 41
// Spannungswert für Ausgabe formatieren
// reserviert je 2 Anzeigestellen, für Nachkomma-Wert mit bedingter 0 Anzeige
snprintf(BatterieVoltageBuf,9,"%c%2d,%02dV", VorZeichen, vorK, nachK);
lcd.setCursor(Spalte,Zeile);
lcd.print(BatterieVoltageBuf);
}

Tschau
Doc Arduino '\0'

Messschieber auslesen: http://forum.arduino.cc/index.php?topic=273445
EA-DOGM Display - Demos: http://forum.arduino.cc/index.php?topic=378279

Serenifly

#5
Jun 13, 2014, 01:51 am Last Edit: Jun 13, 2014, 01:58 am by Serenifly Reason: 1

Woher weis das die Stringlänge?

Gar nicht. Deshalb kann es einen Puffer-Überlauf verursachen. Und genau deshalb gibt es es das sicherere snprintf

Quote
Wegen der Variablendefinition nochmal. Ich möchte die Variablen lokal definieren, aber nicht das sie jedesmal beim Funktionsaufruf neu definiert werden. Deshalb dachte ich static wäre richtig. Weil die sowieso immer lokal überschrieben werden, dachte ich das ständige nullen ist überflüssig und könnte man sich damit ersparen.

Lokale Variablen werden sowie nicht initialisiert. Deshalb geht da auch keine Zeit verloren. Und selbst wenn fiele das angesichts der langen Laufzeit von Funktionen wie sprintf() oder dtostrf() nicht ins Gewicht.

An dieser Stelle verstehe ich deine Sorgen nicht wirklich :)

EDIT:
Es ist auch völlig sinnlos Variablen die du gleich wieder beschreibst auf 0 zu initialisieren.

Und das hier macht nicht was du denkst:
Code: [Select]

int vorK, nachK = 0;

vorK ist danach nicht initialisiert. Was aber wie gesagt Wurscht ist.

Doc_Arduino

Hallo,

okay, dass mit dem lokalen nicht initialisieren wußte ich nicht noch nicht. Alles klar.

Habe dtostrf  ausprobiert. Muß man ja mal machen.   :)

Code: [Select]

void BatterieVolt_to_DOGM (byte Spalte, byte Zeile)
{
float Volt = 0;
Volt = ADvoltage*5/1000;     // mal Faktor Spannungsteiler [mV] / 1000 [V]
dtostrf(Volt, 4, 2, BatterieVoltageBuf);
lcd.setCursor(Spalte,Zeile);
lcd.print(BatterieVoltageBuf);
lcd.print("V");
}


Funktioniert sehr schön einfach. Nur leider auf dem Display mit Schönheitsfehler. Die Position des Wertes wandert rüber und nüber je nach Vorzeichen und ob einstellig oder zweistellig vorm Komma. Sieht ehrlich gesagt etwas blöd aus und man müßte dann wieder die nicht mehr aktualisierten Displayzellen dahinter gezielt löschen sonst flimmert der Rest dahinter. Der Codeaufwand wäre dann wieder ähnlich hoch wie vorher. Oder was sagst Du?

Schade ist, dass für snprintf eine Formatierung mit float wie  %4.2f nicht in der IDE implentiert ist. Das hätte bestimmt gepaßt. Denke ich.

Tschau
Doc Arduino '\0'

Messschieber auslesen: http://forum.arduino.cc/index.php?topic=273445
EA-DOGM Display - Demos: http://forum.arduino.cc/index.php?topic=378279

Serenifly

#7
Jun 13, 2014, 02:09 am Last Edit: Jun 13, 2014, 02:15 am by Serenifly Reason: 1
Du kannst sowas machen:
Code: [Select]

if(BatterieVoltageBuf[0] == '-')
  Serial.println(BatterieVoltageBuf)
else
  Serial.println(BatterieVoltageBuf + 1)

Nicht getestet, aber man kann einfach den Zeiger eins erhöhen, dann gibt er das erste Zeichen nicht aus.

[edit]+1 macht hier mehr Sinn als ++. Und es muss natürlich == und nicht = sein  :smiley-red:[/edit]


Die Float-Funktionalität bei printf() kann man mit einem Parameter für den Compiler aktivieren. Für den Arduino gibt es dafür einen Patch:
http://forum.arduino.cc/index.php/topic,124809.msg940922.html#msg940922

Aber Achtung: das addiert nochmal ca. 1,5kB zu der Funktion, die so schon recht groß ist. Auch wenn man Float nicht verwendet. Das ist der Grund weshalb das nicht enthalten ist und weshalb es dtostrf() als Ersatz gibt (das ist keine ISO C Funktion).

Das ideale wäre natürlich ein Häkchen in der IDE für die Compile Schalter Optionen, aber das ist wohl zuviel verlangt...

Doc_Arduino

Hallo,

Du hast das ja echt im Blut. Der Trick mit +1 funktioniert, damit fällt das Leerzeichen zwischen einem Minuszeichen und der Einerstelle weg. Allerdings wandert die gesamte Zahl dennoch hin und her. Da ich es optisch gern rechtsbündig haben möchte, habe ich nun folgendes gemacht. Zusätzlich habe ich das [V] aus dem String wieder rausgenommen, das würde später in Excel nur stören. Der Spannungswert wird auch noch auf der SD gespeichert.

Code: [Select]

void BatterieVolt_to_DOGM (byte Spalte, byte Zeile)
{
int vorK, nachK = 0;
int Volt = 0;
char VorZeichen = ' ';
lcd.setCursor(Spalte,Zeile);
Volt = (int) (ADvoltage*5);   // mal Faktor Spannungsteiler | 2482,34*5 = 12411mV
if (Volt < 0) {               // Vorzeichen bestimmen
    VorZeichen = '-';
    Volt = Volt * -1;          // negativen Wert positiv machen
   }
vorK = Volt/1000;               // =  12  Vorkomma vereinzeln  | 12411mV > 12V
if (vorK < 10) lcd.print(" ");  // wenn Vorkomma einstellig, dann Anzeigeposition eins nach rechts korrigieren
nachK = Volt-(vorK*1000);       // = 411  Nachkomma vereinzeln | 12411mV - 12000 = 411
nachK = nachK/10;               // und um eine Stelle kürzen auf 41
// Spannungswert für Ausgabe formatieren
// reserviert Anzeigestellen, für Nachkomma-Wert mit bedingter 0 Anzeige
snprintf(BatterieVoltageBuf,8,"%1c%1d,%02d", VorZeichen, vorK, nachK);
lcd.print(BatterieVoltageBuf); lcd.print("V");



Besten Dank - wie immer.   :)
Tschau
Doc Arduino '\0'

Messschieber auslesen: http://forum.arduino.cc/index.php?topic=273445
EA-DOGM Display - Demos: http://forum.arduino.cc/index.php?topic=378279

Serenifly


Du hast das ja echt im Blut. Der Trick mit +1 funktioniert, damit fällt das Leerzeichen zwischen einem Minuszeichen und der Einerstelle weg.

Moment mal. Ich dachte das Problem ist, dass dtostrf() für das Vorzeichen konstant eine Stelle reserviert, auch wenn der Wert positiv ist. Eigentlich verhindert das gerade dass der Wert hin und her springt. Jedenfalls wenn die Anzahl der Stellen konstant ist. Mit dem Code oben macht er es dann immer linksbündig. Das ist dann doch nicht was du wolltest.

Das rechtsbündig zu machen ist etwas komplizierter. Dazu müsste mit strlen() die Länge des Strings berechnen und dann die Differenz zur maximalen Stellenanzahl vorher mit Leerzeichen beschreiben. Das geht aber auch relativ einfach mit einer for-Schleife.

Doc_Arduino

Hallo,

nicht ganz.

Der Trick mit dem Zeiger + 1 entfernt nur das Leerzeichen vor einer Einersteller vorm Komma. Sodass nach dem "-"Zeichen sofort die Zahl erscheint ohne Leerzeichen dazwischen.
Code: [Select]

void BatterieVolt_to_DOGM (byte Spalte, byte Zeile)
{
float Volt = 0;
Volt = ADvoltage*5/1000;     // mal Faktor Spannungsteiler [mV] / 1000 [V]
dtostrf(Volt, 4, 2, BatterieVoltageBuf);
if (BatterieVoltageBuf[0] == '-')
   Serial.println(BatterieVoltageBuf);
else
   Serial.println(BatterieVoltageBuf + 1);  // Zeiger um eins erhöht, dadurch wird erstes Zeichen (hier Leerzeichen) nicht ausgegeben
lcd.setCursor(Spalte,Zeile);
lcd.print(BatterieVoltageBuf);
lcd.print("V");
}

Das ganze sieht dann so aus. Ein zweites "V" könnte ich noch einfach korrigieren, aber nicht zwei. Weil nach "V" ist nur ein leeres Feld Luft auf dem Display, dahinter wird die Uhrzeit angezeigt. Wenn ich da was belegtes zusätzlich leer überschreibe flimmert die Anzeige.
Code: [Select]

-12,41V
-6,19VV
3,34VVV
10,43VV


Meine Variante ist wirklich rechtsbündig, weil ich nur noch ein Leerzeichen korrigieren muß, je nachdem ob es vorm Komma einstellig oder zweistellig ist. Ich verschiebe den gesamten String um eine Display "sichtbare" Leerzelle oder nicht.
Code: [Select]

void BatterieVolt_to_DOGM (byte Spalte, byte Zeile)
{
int vorK, nachK = 0;
int Volt = 0;
char VorZeichen = ' ';
lcd.setCursor(Spalte,Zeile);
Volt = (int) (ADvoltage*5);   // mal Faktor Spannungsteiler | 2482,34*5 = 12411mV
if (Volt < 0) {               // Vorzeichen bestimmen
    VorZeichen = '-';
    Volt = Volt * -1;          // negativen Wert positiv machen
   }
vorK = Volt/1000;               // =  12  Vorkomma vereinzeln  | 12411mV > 12V
if (vorK < 10) lcd.print(" ");  // wenn Vorkomma einstellig, dann Anzeigeposition eins nach rechts korrigieren
nachK = Volt-(vorK*1000);       // = 411  Nachkomma vereinzeln | 12411mV - 12000 = 411
nachK = nachK/10;               // und um eine Stelle kürzen auf 41
// Spannungswert für Ausgabe formatieren
// reserviert Anzeigestellen, für Nachkomma-Wert mit bedingter 0 Anzeige
snprintf(BatterieVoltageBuf,8,"%1c%1d,%02d", VorZeichen, vorK, nachK);
lcd.print(BatterieVoltageBuf); lcd.print("V");



Sieht dann so aus, genauso wie ich es haben wollte.   :)

Code: [Select]

-12,40V
-6,19V
  0,49V
10,82V


Nichtdestotrotz habe ich mit snprintf usw. wieder viel gelernt von Dir.   ;)
Tschau
Doc Arduino '\0'

Messschieber auslesen: http://forum.arduino.cc/index.php?topic=273445
EA-DOGM Display - Demos: http://forum.arduino.cc/index.php?topic=378279

Go Up