HEX conversion mit sprintf

Hallo,
Ich habe eine kleine Funktion geschrieben, zum anhängen einer NMEA-Checksumme an einen NMEA-Datensatz. Dabei habe ich das Problem, dass bei jedem 20.-30. Datensatz die Checksumme nicht korrekt übergeben wird. Wenn ich das überprüfe, und vor dem Return die Checksumme per Serial.print ausgebe, sehe ich, dass die soweit i.O. ist. Nur die Übergabe scheint nicht hinzuhauen (es steht dann einfach nur eine "4" da). Ich nehme an, da das restliche Programm sehr groß ist (Binary sketch size: 18492 bytes), dass sich da was in den Variablen überscheibt. Die Checksumme hänge ich in einem anderen Programmteil per strcat an den Datensatz an. Also so
strcpy( outbuf, "NMEA-Datensatz ohne CS");
strcat( outbuf, checksum(outbuf));
strcat( outbuf, "\r\n");
Serial.print(outbuf);

Ich schätze also, dass mit sprintf nun den Speicher zu stark beansprucht, und suche nach einer Alternative. Alles was ich bisher zum Ersetzen von sprinft gefunden habe, bezog sich eher auf das umwandeln von floats nach ASCII.
Hat jemand eine Idee?
Gruß
J

char* checksum(char* csbuf){

  char CSout[4];
  // Vorige Checksumme löschen
  int cs=0;
  // Ohne '

for (int n=1; n < strlen(csbuf)+1; n++) {
   // berechnen der Checksumme
   cs ^= csbuf[n];
 }  
 // Formatieren in zweistellig, führende Null in HEX
 sprintf(CSout, "*%02X\0" , cs);
 return CSout;
}

Hallo Jürgen,

das ist kein Problem von sprintf, sondern ein Fehler in deinem Programm. Du gibt die Adresse einer lokalen Variablen auf dem Stack zurück, die längst überschrieben worden sein könnte.

Es gibt zwei einfache Lösungsmöglichkeiten:

a) den String für den Hexwert als Parameter an die Funktion übergeben
b) den String nicht statisch (also char CSout[4]), sondern dynamisch per malloc() zu reservieren - dann musst du ihn aber in der aufrufenden Funktion auch wieder freigeben.

Warum hast du eigentlich CSout in der Länge von 4 definiert? Brauchst du wirklich 3 Zeichen für die Hex-Zahl?

Ciao,

Rudi

Danke für deine Antwort. Vierstellig weil die Checksumme immer aus einem Stern und zwei Hex-Werten besteht. Abschließend die \0, also vier, oder?
Das übergeben an die Funkition scheint ja kein Problem zu sein, denn wenn ich die CS am ende per Print ausgebe, dann stimmt sie. Nur das, was an den outbuf angehängt wird, wurde überschrieben. Wenn ich CSout als globale Variable deklariere? Ist natürlich unelegant. Den malloc-Teil verstehe ich so leider nicht, damit hab ich noch nicht gearbeitet.

edit: Als globale Variable läuft es. Ein char-Array aus einer Funtion zu übergeben ist wohl nicht ohne weiteres machbar...

Ein char-Array aus einer Funktion zu übergeben ist wohl nicht ohne weiteres machbar...

Was ist schon "ohne weiteres" in c/c++ :wink:

Da wir beim Arduino mit seinem kleinen RAM und "unendlicher Laufzeit" mit dynamischem Speicher vorsichtig sein sollten:

Klassischerweise übergibt der Aufrufer die Adresse des Ergebnis-Puffers und erlaubte Länge an die Funktion.
Die Funktion sieht die Adresse als pointer. Oft gibt sie die tatsächlich beschriebene Länge als Ergebnis (oder -1 als Fehler) zurück.

int myFunction (char*, int); // Deklaration

void loop() 
{
 char buffer[80];
 int result = myFunction(buffer, sizeof(buffer));
 if (result > 0) 
   // buffer contains data
}

Du könntest die Funktion z. B. folgendermaßen realisieren:

void checksum(char* csbuf, char *CSout){

  // Vorige Checksumme löschen
  int cs=0;
  // Ohne '

Aufrufen kannst du das dann folgendermaßen

char outBuf[100], CSout[4];

strcpy( outbuf, "NMEA-Datensatz ohne CS");
checksum(outbuf, CSout);
strcat( outbuf, CSout);
strcat( outbuf, "\r\n");
Serial.print(outbuf);

for (int n=1; n < strlen(csbuf)+1; n++) {
   // berechnen der Checksumme
   cs ^= csbuf[n];
 }  
 // Formatieren in zweistellig, führende Null in HEX
 sprintf(CSout, "*%02X\0" , cs);
}


Aufrufen kannst du das dann folgendermaßen

§DISCOURSE_HOISTED_CODE_1§

was spricht gegen diese Variante ohne sprintf und mit "optimierten" Datentypen ?

char HexNibble(byte b) {
   b &= 0x0F; // just to protect against bad usage
   if (b <= 9)  return '0' + b; 
   return 'A'+(b-10);
}

void checksum(const char* csbuf, char *CSout) {

  // Checksumme initialisieren
  byte cs=0;
  // Ohne führendes '

for (int n=1; n < strlen(csbuf); n++) {
    // berechnen der Checksumme
    cs ^= csbuf[n];
  } 
  // Formatieren
  CSout[0] = '*';
  CSout[1] = HexNibble(cs >>4);
  CSOut[2] = HexNibble(cs&0x0f);
  CSOut[3]= 0;
}

Das ist eine Superlösung! So in etwa hatte ich mir das Gedacht. Und fast 1500 Bytes gespart, durch weglassen von sprintf!
Danke!

Hallo,

gegen Michaels Lösung spricht gar nichts, immerhin spart sie doch ziemlich viel Speicher ein. Allerdings hatte Jürgen ja den Verdacht, dass sein Problem durch zu wenig Speicher käme - dem war aber nicht so, sondern der Fehler lag in seinem Code, daher war es mir wichtig, diesen Irrtum aufzuklären.

Rudi