Affichage de flottant

Bonjour à toutes et à tous,

J'ai un problème pour afficher des flottants. J'utilise la méthode suivante :

float iEnrg;
float wConso;
char stx[64]; // Buffer de stockage
        sprintf(stx, "%.2f;%.2f;%.3f", millis()/3600000.0, iEnrg, wConso); // Temps en heures décimales
        Serial.println(stx);

Au résultat, ça m'affiche :

?;?;?

Il me semble me souvenir qu'il y a un problème avec ce type d'instruction, mais je en me souviens plus lequel.

Une idée ?

Cordialement.

Pierre.

sprintf ne gere pas les floats utilise string
String stx = String(millis() / 3600000.0, 2) + ";" + String(iEnrg, 2) + ";" + String(wConso, 3);
Serial.println(stx);

sprintf sur petit arduino ne gère pas les float en effet.

Les Strings peuvent emmener des soucis mémoire parfois ou ne pas réaliser l'opération voulue si la mémoire est limitée.

Le plus simple, le moins couteux en mémoire et en temps CPU pour votre besoin est de ne pas passer par le buffer du tout...

Serial.print(millis()/3600000.0, 2); // 2 chiffres après la virgule
Serial.write(';');                   // le séparateur
Serial.print(iEnrg, 2);              // en flottant, 2 chiffres après la virgule
Serial.write(';');                   // le séparateur
Serial.println(wConso, 3);           // 3 chiffres après la virgule

fabriquer le buffer en amont ne fera pas imprimer plus vite...

Merci pour votre piqure de rappel.

Effectivement, en utilisant les String, ça passe tout seul.

Cordialement.

Pierre.

jusqu'au moment où ça ne passe plus à cause de la mémoire.

Si vous n'avez pas besoin du buffer, passez par les print séparés.

Si vous avez besoin du buffer et voulez être un peu plus prudent tout en utilisant les String quand même, utilisez += au lieu de la concaténation

c'est à dire que au lieu de

String stx = String(millis() / 3600000.0, 2) + ";" + String(iEnrg, 2) + ";" + String(wConso, 3);

faites

String stx;
stx.reserve(30); // une estimation du nombre de caractères nécessaires
stx = String(millis() / 3600000.0, 2);
stx += ";" ;
stx +=  String(iEnrg, 2);
stx +=  ";";
stx += String(wConso, 3);

c'est un peu plus long à taper mais ça consomme moins de mémoire intermédiaire.

(si vous n'êtes pas juste du tout en RAM alors bien sûr faites comme vous voulez)

Que j'utilise ma méthode ou la votre - avec ou sans reserve() - cela me prend le même espace mémoire dynamique (936 octets) à la compilation avec un UNO.

Par contre, si je place String stx; avant le setup() au lieu de la mettre dans le flux, cela consomme 6 octets de plus (942 octets), quelque soit la méthode utilisée.

Maintenant, si je regarde la mémoire à l'exécution avec cette méthode :

int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

ATTENTION : dans ce qui suit, le valeurs données représentent ce qui reste de mémoire dynamique et non pas ce qui est consommé.

Si String stx; avant le setup() :

  • avec ma méthode : 895 octets
  • avec votre méthode et reserve(24) : 926 octets
  • avec votre méthode sans reserve(24) : 912 octets

Si String stx; dans le flux :

  • avec ma méthode : 895 octets
  • avec votre méthode et reserve(24) : 936 octets
  • avec votre méthode sans reserve(24) : 918 octets

Donc un gain de trois ou quatre dizaines d'octets pour votre méthode (sans reserve(24)).

Cordialement.

Pierre

freeRam vous donne une photo à l'instant T

quand vous faites

String stx = String(millis() / 3600000.0, 2) + ";" + String(iEnrg, 2) + ";" + String(wConso, 3);

vous ne le voyez pas mais lors de la construction de cette String, de nombreuses petites String intermédiaires sont créées, étendues, copiées puis libérées.

➜ il commence par instancier une String pour stx. cette string est vide.
➜ il évalue la formule mathématique millis() / 3600000.0 et crée une String intermédiaire qui est la représentation décimale du nombre avec 2 chiffres après la virgule. appelons la S1
➜ il crée une String temporaire pour ";". appelons là S2

Maintenant il veut concaténer S1 et S2. Pour cela il regarde s'il y a la place dans S1 pour copier S2 au bout. Ce n'est pas le cas puisque S1 a été dimensionnée que pour le nombre. Donc le compilateur alloue un buffer temporaire assez grand pour S1 et S2, (appelons le S1.1) et copie S1 dedans, ajoute S2 à la fin et met le pointeur vers S1.1 à la place de S1, puis libère S1 et S2.

➜ ensuite il crée une String temporaire pour String(iEnrg, 2). appelons là S3

il faut maintenant rajouter S3 à la fin de S1.1

Pour cela il regarde s'il y a la place dans S1.1 pour copier S3 au bout. Ce n'est pas le cas puisque S1.1 a été dimensionnée au plus juste. Donc le compilateur alloue un buffer temporaire assez grand pour S1.1 et S3, (appelons le S1.2) et copie S1.1 dedans, ajoute S3 à la fin et met le pointeur vers S1.2 à la place de S1.1, puis libère S1.1 et S3.

la même chose recommence pour ";" et pour String(wConso, 3); avec chaque fois allocation d'une String, puis d'un buffer plus grand pour faire la copie et le déplacement des données... à la fin quand la String finale est construite, il l'affecte à stx.

Toutes ces allocations intermédiaires et toutes ces copies, vous ne les voyez pas si vous mettez un appel à freeRam() avant et après la construction de stx et si la mémoire est limitée, une ou plusieurs de ces opérations "invisibles" ne vont pas marcher mais l'arduino ne vous dira rien ni à la compilation et (sur AVR) ni à l'exécution. L'instruction ne sera juste pas exécutée complètement et vous aurez dans stx quelque chose pas forcément complet.

si vous faites

Serial.print(millis()/3600000.0, 2); // 2 chiffres après la virgule
Serial.write(';');                   // le séparateur
Serial.print(iEnrg, 2);              // en flottant, 2 chiffres après la virgule
Serial.write(';');                   // le séparateur
Serial.println(wConso, 3);           // 3 chiffres après la virgule

il n'y a pas d'allocation dynamique du tout.

Merci "J-M-L" pour cette fine analyse qui permet de comprendre le processus d'utilisation de la mémoire.

Cordialement.

Pierre.