String? Pas bien. Char[]? bien!

Je toujours utilisé pour mes programme l’objet String car je le trouve puissant.
Depuis que je me suis mis à l’arduino, je n’arrête pas d’entendre que il faut éviter d’utiliser String et privilégier char.
OK, message reçu!
Mon code:

String sTrama;

void CapteursPresion()
{
  double  c;
  char cDato[6];
  String sHex;
  byte PosX = 1;
  //sTrama = "";
  while (Serial2.available() > 0)//il y a quelque chose que le BT veut nous transmettre?
  {
    //lit le BT
    sHex = String(Serial2.read(), HEX);//Attention: il ne met pas les 0 non significatifs
    if (sHex.length() == 1) sHex = "0" + sHex;//ajoute les 0 non significatifs
    sTrama += sHex; //Crée la trame

    if (sTrama == OkDisa1 || sTrama == OkDisa2 || sTrama == OkDisa3) sTrama = ""; // efface les "OK+DISA*"

    //Serial.println(sTrama);
    if (sTrama.endsWith("0d0a")) //fin de la trame
    {
      //PrintTFT(42, 182, "Od0a", 2);
      //Serial.println(sTrama);
      // Analise la trame
      if (sTrama.length() == 76)//le capteur transmet 2 differentes trames
      { //seulement la plus longue nous interesse
        if (sTrama.startsWith(MACCaptPressAv)) PosX = 140;//La trame vient elle du capteur avant?
        else if (sTrama.startsWith(MACCaptPressAr)) PosX = 0;//La trame vient elle du capteur arrière?
        else
        {
          sTrama = "";
          return;//on sort car la trame ne provient pas de nos capteurs
        }
        //PrintTFT(42, 182, String(PosX), 2);
        cDato[0] = sTrama.charAt(56);// Preds les char de la pression
        cDato[1] = sTrama.charAt(57);
        cDato[2] = sTrama.charAt(54);
        cDato[3] = sTrama.charAt(55);
        cDato[4] = sTrama.charAt(52);
        cDato[5] = sTrama.charAt(53);
        c = strtoul(cDato, 0, 16);//Converte HEX to DEC.

        if (c > 400000UL)//des fois il trouve de pressions incoerrentes!
        {
          sTrama = "";
          return;
        }

        if (c < 190000UL && tft.readPixel(PosX + 10 , 259) != RED)//pression insuffisante tft.readPixel(posxa + 6 , posya)
        { // PneuAV sert pour eviter qu'il colorie le pneu à chaque fois
          for (byte a = 41; a < 60; a++) tft.drawCircle(PosX + 60, 259, a, RED);//Colora le gomme in ROSSO
        }
        else if (c > 250001UL && tft.readPixel(PosX + 10 , 259) != BLUE)// Pression elevée
        {
          for (byte a = 41; a < 60; a++) tft.drawCircle(PosX + 60, 259, a, BLUE);//Colora le gomme in BLU
        }
        else if (c < 250000UL && c > 189000UL && tft.readPixel(PosX + 10 , 259) != GREEN) //pression optimale
        {
          for (byte a = 41; a < 60; a++) tft.drawCircle(PosX + 60, 259, a, GREEN);//Colora le gomme in VERDE
        }

        PrintTFT(42 + PosX, 180, String(float(c) / 100000.00), 2);//Ecrit la pression

        cDato[0] = sTrama.charAt(64);// Preds les char de la Temperature
        cDato[1] = sTrama.charAt(65);
        cDato[2] = sTrama.charAt(62);
        cDato[3] = sTrama.charAt(63);
        cDato[4] = sTrama.charAt(60);
        cDato[5] = sTrama.charAt(61);
        c = strtoul(cDato, 0, 16);//Converte HEX to DEC
        PrintTFT(112 + PosX, 300, String(int(c / 100)) + " ", 2);// Ecrit sur le TFT la Temperature
      }
      sTrama = "";//efface la trame
    }
  }

J’ai commencé avec la première partie:

 while (Serial2.available() > 0)//il y a quelque chose que le BT veut nous transmettre?
  {
    //lit le BT
    sHex = String(Serial2.read(), HEX);//Attention: il ne met pas les 0 non significatifs
    LenTrama = strlen(sTrama);
    if (strlen(sHex) == 1) //Ajoute le 0 non significatif
    {
      sTrama[LenTrama] = '0';
      sTrama[LenTrama +1] = sHex[0];
      sTrama[LenTrama +2] = '\0';
    }else
    {
      sTrama[LenTrama] = sHex[0];
      sTrama[LenTrama +1] = sHex[1];
      sTrama[LenTrama +2] = '\0';
    }

Je n’ai pas l’habitude d’utiliser char c’est pour cette raison que je vous demande si c’est bien ou pas.

À quoi ressemble une trame?

Trama
Message 18, j'ai juste supprimer les espaces entre chaque byte.

L'un des puissants outils à disposition en C++ pour créer des tableaux de caractères avec un format bien précis - disons créer des trames - c'est sprintf.

Tu crées un tableau vide, de longueur suffisante pour toutes les trames possibles. Disons que la longueur maximale d'une trame sera de 50 caractères, plus le \0 à la fin (du tableau) si nécessaire

char pastis[51];

Ensuite si tu veux y mettre des valeurs (séparées par des espaces) et un entête :

char entete[] = "AT+DISA: MAC(";
int val1, val2, val3;
sprintf (pastis,"%s%d %d %d\0",entete,val1,val2,val3);

Il faut connaitre les codes utilisés pour le format :

  • %s pour un tableau de caractères,

  • %d pour un entier,

  • %c pour un char unique,

  • %u pour un unsigned int,

  • %X pour un hexadécimal en majuscules...
    On peut aussi ajouter des caractères spéciaux :

  • \n (newline)

  • \t (tab)

  • ...
    Ensuite pour jouer avec plusieurs tableaux de caractères, il y a des fonctions sympas:

  • strcat : pour concaténer deux chaines

  • srtcpy : pour copier une chaîne dans une autre

  • strlen : pour connaitre la longueur d'une chaîne
    Il existe aussi des fonctions pour convertir des chiffres en chaines de caractères et vice-versa : itoa, atoi, etc. Mais je pense que les fonctions précédentes sont suffisantes pour créer une trame.

sprintf n’est pas spécifique à C++, mais est récupéré de C; awk utilise aussi, si besoin, sprintf , avec les mêmes règles de formatage que C (bien pratique, pour tester: là, j’ai verifié les tabulations, le newline et le format hexa)

echo "salut" |  awk '{a=sprintf ( "%s\tJupyter\tle\tHomardeux\n hex(85)=%x", $1,85); print a;}'
salut   Jupyter le      Homardeux
 hex(85)=55
    sprintf(sHex,"%02x\0",Serial2.read());
    strcat(sTrama, sHex);

Ca peut fonctionner?

Oui:
j’ai testé, n’ayant pas d’arduino sous la main, avec mon PC (c’est plus facile: on n’a pas à s’ennuyer avec tout plein de prises…)

 gcc strcattest.c && ./a.exe
Trame:55
sh-4.1$ gcc strcattest.c && ./a.exe && cat strcattest.c
Trame:55

   #include <stdio.h> // arduino n'a pas besoin de ces 3 includes (les fournit d'office, IIRC)
   #include <stdint.h>
   #include <string.h>
            // char *strcat(char *DST, const char *SRC);
int main() {
   char sTrama[51] ="Trame:"; // dimensionné depuis post 3
   char sHex[11];
   // sert à dimensionner le resulatat d'une conversion : on pourra  convertir dexs int32_t confortablement (8 cars +\0)
  uint8_t serialRead='U'; // mon pc ne connaôt pas Serial2.read
   sprintf(sHex, "%02x\n\0", serialRead);
   strcat(sTrama, sHex);
   printf(sTrama);
   return 0;// nombre d'erreurs
}

Edité: ce code peut être un peu plus sûr (protège de debordements du tableau sTrama si, à la place de strcat(sTrama, sHex); on mettait strncat(sTrama, sHex,50);
(mais si debordement il y a, on n’en informe pas dans cette version naîve l"auteur).
En changeant l’invocation de gcc “gcc --pedantic -Wall”, on peut voir quelques petits messages d’averissement sans conséquences… (ceci ne peut être fait sur arduino…)

char sTrama[100];
#define MACCaptPressAv "510110ca"
#define MACCaptPressAr "aa0340ca"
#define OkDisa1 "4f4b2b444953413a"
#define OkDisa2 "4f4b2b4449534153"
#define OkDisa3 "4f4b2b4449534345"

void CapteursPression()
{
  uint32_t  c;
  char cDato[6];
  char sHex[3];
  byte PosX = 1;
  int LenTrama;
  //sTrama = "";
  while (Serial2.available() > 0)//il y a quelque chose que le BT veut nous trasmettre?
  {
    //lit le BT
    sprintf(sHex, "%02x\0", Serial2.read());
    strcat(sTrama, sHex);

    if (strncmp(sTrama, OkDisa1, 16) == 0 || strncmp(sTrama, OkDisa2, 16) == 0 || strncmp(sTrama, OkDisa3, 16) == 0) sTrama[0] = '\0'; // efface les "OK+DISA*"

      //Serial.println(sTrama);
      char * pch;
      pch = strstr(sTrama, "0d0a");

      if (pch != NULL) //fin de la trame
      {
        //PrintTFT(42, 182, "Od0a", 2);
        //Serial.println(sTrama);
        // Analise la trame
        if (strlen(sTrama) == 76)//le capteur transmet 2 differentes trames
        { //seulement la plus longue nous interesse
          if (strncmp(sTrama, MACCaptPressAv, 8) == 0) PosX = 140;//La trame vient elle du capteur avant?
          else if (strncmp(sTrama, MACCaptPressAr, 8) == 0) PosX = 0;//La trame vient elle du capteur ariere?
          else
          {
            sTrama[0] = '\0';
            return;//on sort car la trame ne provient pas de nos capteurs
          }
          //PrintTFT(42, 182, String(PosX), 2);
          cDato[0] = sTrama[56];// Preds les char de la pression
          cDato[1] = sTrama[57];
          cDato[2] = sTrama[54];
          cDato[3] = sTrama[55];
          cDato[4] = sTrama[52];
          cDato[5] = sTrama[53];
          cDato[6] = '\0';
         c = strtoul(cDato, 0, 16);//Converte HEX to DEC.

          if (c > 400000UL)//des fois il trouve de pressions incoerrentes!
          {
            sTrama[0] = '\0';
            return;
          }

          if (c < 190000UL && tft.readPixel(PosX + 10 , 259) != RED)//pression insuffisante tft.readPixel(posxa + 6 , posya)
          { // PneuAV sert pour eviter qu'il colorie le pneu à chaque fois
            for (byte a = 41; a < 60; a++) tft.drawCircle(PosX + 60, 259, a, RED);//Colora le gomme in ROSSO
          }
          else if (c > 250001UL && tft.readPixel(PosX + 10 , 259) != BLUE)// Pression elevée
          {
            for (byte a = 41; a < 60; a++) tft.drawCircle(PosX + 60, 259, a, BLUE);//Colora le gomme in BLU
          }
          else if (c < 250000UL && c > 189000UL && tft.readPixel(PosX + 10 , 259) != GREEN) //pression optimale
          {
            for (byte a = 41; a < 60; a++) tft.drawCircle(PosX + 60, 259, a, GREEN);//Colora le gomme in VERDE
          }

          PrintTFT(42 + PosX, 180, String(float(c) / 100000.00), 2);//Ecrit la pression

          cDato[0] = sTrama[64];// Preds les char de la Temperature
          cDato[1] = sTrama[65];
          cDato[2] = sTrama[62];
          cDato[3] = sTrama[63];
          cDato[4] = sTrama[60];
          cDato[5] = sTrama[61];
          cDato[6] = '\0';
          c = strtoul(cDato, 0, 16);//Converte HEX to DEC
          PrintTFT(112 + PosX, 300, String(int(c / 100)) + " ", 2);// Ecrit sur le TFT la Temperature
        }
        sTrama[0] = '\0';//efface la trame
      }
  }

  if (TimerBT + 3000 < millis()) //relance tous les 3 sec la commande "AT+DISA?"
  {
    Serial2.write("AT+DISA?");
    TimerBT = millis();
  }
}

Je ne l’ai pas testé, juste compilé.
A ce rythme d’ici à 30/40 ans je vais devenir aussi fort que vous! :smiley:

Excusez moi, mais je n'ai pas compris cette séquence d' instructions

    char * pch;
      pch = strstr(sTrama, "0d0a");

Il recherche la chaine de 4 caractères "0d0a"?

oui. Il cherche si dans sTrama il y a "0d0a". Trouvé ici
Ces 4 caractères indiquent que la trama est finie.

Tu dois faire erreur :
0x0D ou 13 ou ‘\r’ = retour chariot
0xOA ou 10 ou ‘\n’ = retour à la ligne

      pch = strstr(sTrama, "\r\n");

Vous confondez peut être une séquence de caractères avec leur représentation hexadecimale (horreur assez courante, surtout s'ils sont inimprimables....):
ex:"RU" a pour représentation hexadécimale 52 55 00

Avec le premier code la trame finissait avec 0d0a.
Ca veut dire que:

si serial.read() lit un 13 ou un 10
sprintf ne rajoute pas 0d ou 0a à la chaine?

P.S.

    sHex = String(Serial2.read(), HEX);//Attention: il ne met pas les 0 non significatifs
    if (sHex.length() == 1) sHex = "0" + sHex;//ajoute les 0 non significatifs
    sTrama += sHex; //Crée la trame

me rajoutait bien 0d et 0a à sTrama

Tu confonds le caractère '\x0d' ou '\r' et la chaine de caractères "0d" qui est composée de deux caractères '0' et 'd'.

sprintf ajoute un ‘\0’ à la chaîne, rien d’autre.

Avec le premier code la trame finissait avec 0d0a.

Plutôt “\x0d\x0a”

Tes caractères ‘\x0d’ et ‘\x0a’ provenaient forcément de la ligne série.
Si tu envoies tes chaînes avec un terminal ou le moniteur série ARDUINO c’est normal.

Tu peux régler le moniteur série pour envoyer CR, LF, les deux ou aucun.

Je ne comprends pas!
Mon module bt m’envoie ce que les capteurs envoient.
A la fin de la trame les capteurs envoient un 13 et un 10.
Si je lis avec serial.read un 13,avec
sHex = String(Serial2.read(), HEX);
sHex = "d";
pour avoir toujours la même longueur de ma trame j'ajoute un caractere "0" si shex.length = 1
if (sHex.length() == 1) sHex = "0" + sHex;
maintenant mon sHex ="0d"
sTrama += sHex;
ma sTrama = "...................0d"
Le bt envoie un 10
sHex = String(Serial2.read(), HEX);
sHex = "a";
if (sHex.length() == 1) sHex = "0" + sHex;
maintenant mon sHex ="0a"
sTrama += sHex;
ma sTrama = "...................0d0a"
Pour moi "0d0a" sont maintenant des caractères qui sont partie de ma String sTrama.
avec
if (sTrama.endsWith("0d0a")) //fin de la trame
je comprends que la trame est finie et je commence à l’analyser.
ce bout de code marche presque parfaitement.
Des jours ca ne marche pas. Vu les problèmes qui peuvent survenir à l'utilisation de String je suis en train de refaire cette fonction en utilisant char en gardant la même philosophie.
C'est que j'ai imaginé. Peut être que il ne faut pas faire comme çà?
Peut être que la conversion de HEX à String n'est pas nécessaire mais pour moi c’était plus pratique avoir une String quand j'ai commencé à étudier ces capteurs. Après c'est vrais je n'ai pas cherché plus loin vu que le code marchait.

Si tu veux utiliser des chaines de caractères, il faut le faire partout, à commencer par la réception BT. Comme c'est une liaison série, probablement mise en oeuvre avec un SoftwareSerial, tu peux utiliser les tutoriaux de lecture d'un port série (voir celui de J-M-L par exemple).

Tu reçois tes caractères dans un char (ou un byte, c'est pareil) et tu traites ce char comme il faut pour constituer ta trame au fur et à mesure de l'arrivée des caractères.

byte c = MySerial.read();
char temp[2];
sprintf (temp,"%02X",c);
strcat (trama, temp);

(pas essayé, mais l'idée est là)

sHex = String(Serial2.read(), HEX);//Attention: il ne met pas les 0 non significatifs

Cela vient de ta manière de lire les caractères.

Serial2.read() retourne un caractère c qui vaut '\x0d' ou '\x0a' ou autre.
Ensuite String(c, HEX) transforme c en représentation hexadécimale : "d" ou "a".
Ensuite tu insère '0' devant.
Pourquoi faire cette transformation inutile ?
Effectivement les caractères dans sHex seront transformés en chaîne :
'a' : "61"
'b' : "62"
'c' : "63"
'\x13" : "0d"
'\x10' : "0a"

Si tu fais comme cela une chaîne "abc\r\n" sera transformée en "6162630d0a".

Sinon, si tu ne convertis pas en hexa :

    int c = Serial.read();
    sTrama[LenTrama++] = c;

Ta chaîne "abc\r\n" ou "abc\x0d\x0a" restera telle quelle.

Si tu veux te débarrasser des String il faut éviter de les utiliser jusqu'au bout.

(pas essayé, mais l'idée est là)

sprintf(sHex, "%02x\0", Serial2.read());

c'est pas pareil?

Si tu fais comme cela une chaîne "abc\r\n" sera transformée en "6162630d0a".

Exacte.C'est comme ça qu'il marchait l'ancienne fonction.

Si tu veux te débarrasser des String il faut éviter de les utiliser jusqu'au bout.

Dans le code du message #7, j'ai utilisé des String?

Dans le code du message #7, j'ai utilisé des String?

Non, mais cette transformation est inutile :

    sprintf(sHex, "%02x\0", Serial2.read());

Si tu avais débuté en utilisant de chaînes de caractères "C" au lieu d'utiliser des Srings ce serait moins difficie à expliquer.