Comportement etrange communication serie

Bonjour,

J'ai un soucis de communication serie entre mon arduino et mon pc . Je m'explique:

L'arduino recoit parfaitement ce que je lui envoi si je commente la ligne //affichage_emplacement(); dans la void loop()

En revanche si je l'a decommente, la reception devient aleatoire. Si par exemple, je lui en envoi <1,Rouge,2> , elle va parfois recevoir correctement le message, mais la plupart du temps elle recevra seulement des morceaux du messsage (<1,Rou,2> ou <1Rouge,>)
Auriez-vous une idée de la cause de ce peobleme ?

Remarque: J'utilise le "serial monitor" de l'IDE Arduino pour envoyer des messages à la carte configuré avec l'option "No line ending"

Merci par avance pour votre aide,

#include <FastLED.h>
#define NB_EMPLACEMENT 2
#define NB_LED_PAR_EMPLACEMENT 66
// How many leds in your strip?
#define NUM_LEDS NB_EMPLACEMENT* NB_LED_PAR_EMPLACEMENT
#define VITESSE_CLIGNOTEMENT 1000       //tps en ms utilisé pour l'animation de clignotement
#define VITESSE_ROTATION_CHENILLARD 10  //tps en ms utilisé pour l'animation de la rotation du chenillard
#define POURCENTAGE_CHENILLARD 30       //section allumé du chenillard representant un pourcentage du cercle
#define NB_LED_CHENILLARD floor(NB_LED_PAR_EMPLACEMENT* POURCENTAGE_CHENILLARD / 100)
#define FREQUENCE_RAFRAICHISSEMENT 15
 
// For led chips like WS2812, which have a data line, ground, and power, you just
// need to define DATA_PIN.  For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN
// Clock pin only needed for SPI based chipsets when not using hardware SPI
#define DATA_PIN 5
struct emplacement {
  CRGB couleur;
  //Explication concernant le type de l'animation
  //0 -> éteint
  //1 -> allumé fixe
  //0 -> allumé clignotant
  //0 -> chenillard
  int type_anim;
  int led_start;
  int led_curseur;
  int led_end;
  unsigned long last_updated_time_clignotement = 0;
  unsigned long last_updated_time_chenillard = 0;
};
// Define the array of leds
CRGB leds[NUM_LEDS];
struct emplacement empl_pf[NB_EMPLACEMENT];  //tableau de 0 à NB_EMPLACEMENT - 1
unsigned long current_time = 0;
unsigned long current_time_rafraichissement = 0;
static boolean recvInProgress = false;
int led_restante_chenillard = 0;
const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];  // temporary array for use when parsing
 
int consigne_emplacement = 0;
char consigne_couleur[numChars] = { 0 };
int consigne_anim = 0;
 
boolean newData = false;
 
void description_emplacements(emplacement* emplx) {
  Serial.println("");
  Serial.println("------------------------ETAT-----------------------------");
  Serial.print("EMPLACEMENT \t");
  for (int i = 1; i <= NB_EMPLACEMENT; ++i) {
    Serial.print(i);
    Serial.print("\t\t");
  }
  Serial.println("");
  Serial.print("COULEUR \t");
  for (int i = 0; i < NB_EMPLACEMENT; ++i) {
    Serial.print(emplx[i].couleur.red);
    Serial.print(";");
    Serial.print(emplx[i].couleur.green);
    Serial.print(";");
    Serial.print(emplx[i].couleur.blue);
    Serial.print(";");
    Serial.print("\t\t");
  }
  Serial.println("");
  Serial.print("TYPE ANIM \t");
  for (int i = 0; i < NB_EMPLACEMENT; ++i) {
    Serial.print(emplx[i].type_anim);
    Serial.print("\t\t");
  }
  Serial.println("");
  Serial.print("LED START \t");
  for (int i = 0; i < NB_EMPLACEMENT; ++i) {
    Serial.print(emplx[i].led_start);
    Serial.print("\t\t");
  }
  Serial.println("");
  Serial.print("LED END \t");
  for (int i = 0; i < NB_EMPLACEMENT; ++i) {
    Serial.print(emplx[i].led_end);
    Serial.print("\t\t");
  }
  Serial.print("\n");
}
 
 
void affichage_emplacement() {
  current_time = millis();
  for (int i = 0; i < NB_EMPLACEMENT; i++) {
    //Cas 1 --> on allume fixement toutes les leds de l'emplacement i dans la couleur i
    if (empl_pf[i].type_anim == 1) {
      for (int j = empl_pf[i].led_start; j <= empl_pf[i].led_end; j++) {
        leds[j] = empl_pf[i].couleur;
      }
    }
    //Cas 2 --> on allume fixement toutes les leds de l'emplacement i dans la couleur i
    else if (empl_pf[i].type_anim == 2) {
      if (current_time - empl_pf[i].last_updated_time_clignotement > VITESSE_CLIGNOTEMENT) {
        empl_pf[i].last_updated_time_clignotement = current_time;
        if (leds[empl_pf[i].led_start] == CRGB::Black) {
          for (int j = empl_pf[i].led_start; j <= empl_pf[i].led_end; j++) {
            leds[j] = empl_pf[i].couleur;
          }
        } else {
          for (int j = empl_pf[i].led_start; j <= empl_pf[i].led_end; j++) {
            leds[j] = CRGB::Black;
          }
        }
      }
    }
    //Cas 3 --> animation du chenillard de l'emplacement i dans la couleur i
    else if (empl_pf[i].type_anim == 3) {
      if (current_time - empl_pf[i].last_updated_time_chenillard > VITESSE_ROTATION_CHENILLARD) {
        empl_pf[i].last_updated_time_chenillard = current_time;
        if (empl_pf[i].led_curseur + NB_LED_CHENILLARD - 1 <= empl_pf[i].led_end) {
          for (int j = empl_pf[i].led_start; j <= empl_pf[i].led_end; j++) {
            if (j < empl_pf[i].led_curseur) {
              leds[j] = CRGB::Black;
            } else if (j >= empl_pf[i].led_curseur && j <= empl_pf[i].led_curseur + NB_LED_CHENILLARD - 1) {
              leds[j] = empl_pf[i].couleur;
            } else {
              leds[j] = CRGB::Black;
            }
          }
        } else {
          led_restante_chenillard = empl_pf[i].led_curseur + NB_LED_CHENILLARD - empl_pf[i].led_end - 1;
          for (int j = empl_pf[i].led_start; j <= empl_pf[i].led_end; j++) {
            if (j < empl_pf[i].led_start + led_restante_chenillard) {
              leds[j] = empl_pf[i].couleur;
            } else if (j >= empl_pf[i].led_curseur) {
              leds[j] = empl_pf[i].couleur;
            } else {
              leds[j] = CRGB::Black;
            }
          }
        }
        if (empl_pf[i].led_curseur < empl_pf[i].led_end) {
          empl_pf[i].led_curseur++;
        } else {
          empl_pf[i].led_curseur = empl_pf[i].led_start;
        }
      }
    }
    //Cas 0 --> on éteint les leds de l'emplacement i
    else {  
      for (int j = empl_pf[i].led_start; j <= empl_pf[i].led_end; j++) {
        leds[j] = CRGB::Black;
      }
    }
  }
  FastLED.show();
}
 
void setup() {
  Serial.begin(115200);
 
  FastLED.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS);  // GRB ordering is typical
 
  for (int i = 0; i < NB_EMPLACEMENT; i++) {
    empl_pf[i].couleur = CRGB::Black;
    empl_pf[i].type_anim = 0;
    empl_pf[i].led_start = NB_LED_PAR_EMPLACEMENT * i;
    empl_pf[i].led_curseur = empl_pf[i].led_start;
    empl_pf[i].led_end = empl_pf[i].led_start + NB_LED_PAR_EMPLACEMENT - 1;
    empl_pf[i].last_updated_time_clignotement = 0;
    empl_pf[i].last_updated_time_chenillard = 0;
  }
  description_emplacements(empl_pf);
}
 
//============
 
void loop() {
  recvWithStartEndMarkers();
  if (newData == true) {
    strcpy(tempChars, receivedChars);
    // this temporary copy is necessary to protect the original data
    //   because strtok() used in parseData() replaces the commas with \0
    parseData();
    newData = false;
  }
  /*
  if(millis() - current_time_rafraichissement > FREQUENCE_RAFRAICHISSEMENT){
    current_time_rafraichissement = millis();
    affichage_emplacement();
  }
 
  if(recvInProgress == false){
  affichage_emplacement();
  }
  */
  affichage_emplacement();
}
 
//============
 
void recvWithStartEndMarkers() {
 
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;
 
  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();
 
    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx >= numChars) {
          ndx = numChars - 1;
        }
      } else {
        receivedChars[ndx] = '\0';  // terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
 
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}
 
//============
 
void parseData() {  // split the data into its parts --> format <11,Rouge,2>
 
  char* strtokIndx;  // this is used by strtok() as an index
  Serial.println(tempChars);
  strtokIndx = strtok(tempChars, ",");
  consigne_emplacement = atoi(strtokIndx) - 1;  // l'indice de l'emplacement reçu est compris entre 1 et NB_EMPLACEMENT
 
  strtokIndx = strtok(NULL, ",");        // this continues where the previous call left off
  strcpy(consigne_couleur, strtokIndx);  // consigne de couleur "Rouge", "Vert", "Bleu", "Orange" ou "Noir"
 
  strtokIndx = strtok(NULL, ",");    // this continues where the previous call left off
  consigne_anim = atoi(strtokIndx);  // consigne anim 0 -> eteindre l'emplacement i | 1 -> allumer l'emplacement i | 2 -> clignoter l'emplacement i | 3 -> chenillard l'emplacement i
 
  // Vérification des valeurs extraites
  if (consigne_emplacement >= 0 && consigne_emplacement < NB_EMPLACEMENT && consigne_anim >= 0 && consigne_anim <= 3) {
    if (strncmp("Rouge", consigne_couleur, strlen("Rouge")) == 0) {
      empl_pf[consigne_emplacement].type_anim = consigne_anim;
      empl_pf[consigne_emplacement].couleur = CRGB::Red;
    } else if (strncmp("Vert", consigne_couleur, strlen("Vert")) == 0) {
      empl_pf[consigne_emplacement].type_anim = consigne_anim;
      empl_pf[consigne_emplacement].couleur = CRGB::Green;
    } else if (strncmp("Bleu", consigne_couleur, strlen("Bleu")) == 0) {
      empl_pf[consigne_emplacement].type_anim = consigne_anim;
      empl_pf[consigne_emplacement].couleur = CRGB::Blue;
    } else if (strncmp("Orange", consigne_couleur, strlen("Orange")) == 0) {
      empl_pf[consigne_emplacement].type_anim = consigne_anim;
      empl_pf[consigne_emplacement].couleur = CRGB::Orange;
    } else if (strncmp("Noir", consigne_couleur, strlen("Noir")) == 0) {
      empl_pf[consigne_emplacement].type_anim = consigne_anim;
      empl_pf[consigne_emplacement].couleur = CRGB::Black;
    } else {
      Serial.println("Erreur : Valeurs extraites invalides.");
    }
    description_emplacements(empl_pf);
  }
}
 


:warning:
Post mis dans la mauvaise section, on parle anglais dans les forums généraux. déplacé vers le forum francophone.

Merci de prendre en compte les recommandations listées dans Les bonnes pratiques du Forum Francophone

Essayez de baisser la vitesse à 9600 bauds juste pour voir si ça change de comportement.

Modifiez aussi le code pour appeler FastLED.show(); uniquement quand les LEDs ont changé. (ça prend du temps et ça bloque les interruption pendant ce temps là ce qui fait que potentiellement (si ça dure trop longtemps) vous ratez des caractères.

Merci pour votre réponse !
J'ai testé avec plein de Baud rate differents mais malheureusement ca ne change rien...

En ce qui concerne la vitesse d'execution du fastLed.show(), j'ai eu les mêmes suspissions que vous, et j'avais fait quelques tests (voir la partie commentée dans loop() principale) mais malheuresment ca n'avait rien changé ..

Je ne suis très loin d'etre un expert en communication serie, mais je n'arrive pas à m'expliquer la chose suivante:
Si l'execution du fastLed.show() perturbe la reception sur la voix serie, pourquoi est-ce que je ne loupe jamais le caractère de debut de de fin < et > ?
Les caractères ne devraient-ils pas attendre dans le buffer ? Et en cas d'execution trop longue de fastled.show(), le buffer ne devrait-il pas être entièrement vidé ?

Parce que tu les attends explicitement
Tu testes l'arrivé de

  • < pour commencer l'acquisition
  • > pour terminer l'acquisition

Donc < sera toujours au début et > à la fin.
Quel est la longueur de la chaîne transmise?

Au contraire si show() dure trop longtemps, le buffer va déborder et une partie de son contenu sera écrasé.


Il faudrait chiffrer le temps passé dans description_emplacements() pour voir si la cause de la perturbation est bien là.

combien de trames de contrôle envoyez vous ? une seule ou plein ?

Si vous envoyez plusieurs trames en continu, vous pouvez capter le < d'une trame et son début et la fin d'une autre trame et son >, vous faisant penser à une réception correcte.

FastLED.show(); bloque les interruptions. ça veut dire que votre arduino reçoit l'interruption "attention des bits arrivent sur la voie série" et le hardware reçoit l'octet mais le code correspondant au traitement de l'interruption qui va transférer l'octet reçu dans le buffer de réception n'est pas déclenché puisque les interruptions sont bloquées et les bits ne sont pas lus. Si d'autres octets arrivent, il vont remplacer celui/ceux du buffer hardware (1 ou 2 suivant les architectures) et quand l'interruption sera vue, c'est cet octet qui viendra dans le buffer, les autres sont perdus.

Les NeoPixels reçoivent des données d'un flux de données à fréquence fixe de 800 KHz. Chaque bit de données nécessite donc 1/800 000 seconde, soit 1,25 microseconde. Un pixel nécessite 24 bits (8 bits pour le rouge, le vert et le bleu) - soit 30 microsecondes. Après l'émission des données correspondant au dernier pixel, le flux doit s'arrêter pendant au moins 50 microsecondes pour que les nouvelles couleurs se "verrouillent".

Pour une bande de 122 pixels, cela représente (122 * 30) + 50, soit 3 710 microsecondes pendant lesquels les interruptions sont bloquées.

à 115200 bauds, vous recevez en gros 11520 octets par secondes, soit 11,5 octet par milliseconde.

➜ en bloquant les interruptions pendant 3.71ms vous pouvez rater jusqu'à 43 octets....

Merci beaucoup pour vos explications.

@J-M-L je suppose que je n'envoi qu'une seule trame de contrôle etant donné que j'utilse seulement le "Serial Monitor" de l'IDE arduino (sauf si par defaut le serial monitor envoie plusieurs fois la même trame automatiquement)

A terme, ce code controlera 65 leds × 14 emplacements, ce qui donnera un temps d'interruption de (65×14×30) +50 = 27350 µs = 27, 35 ms

@fdufnews le trame de controle est de cette forme:
<id de l'emplacement, couleur, id_animation> soit dans le pire des cas <14,Orange,3> soit 13 caractères.

@fdufnews pour estimer le temps d'execution de affichage_emplacement(), je peux ecrire le code ci-dessous ?

void loop(){

//le reste de du code de la loop()

tps_exec = millis();
affichage_emplacement();
serial.println(millis() - tps_exec());
}

Auriez-vous une idée pour prioriser la reception serie à l'execution de FastLed.show() ?

J'avais un fait test (voir la partie commentée dans la loop() principale)

if(recvInProgress == false){
  affichage_emplacement();
  }

Mais j'ai conscience que ce code ne fonctionne pas si jamais l'execution se trouve dans la fonction affichage_emplacement() au moment du debut de la reception serie.

Si vous n'envoyez qu'une trame le comportement doit être un peu aléatoire.

Le buffer hardware contient 2 octets (celui reçu et celui en cours) donc vous avez toujours bien le caractère de fin de trame dans le buffer (puisque rien n'est arrivé ensuite), et éventuellement celui qui précède ou celui lu avant le blocage des interruptions; ça semble cohérent avec ce que vous décrivez

Vous ne pouvez pas avoir un code dépendant des interruptions si vous utilisez des neopixels - d'autant plus si vous bloquez les interruptions pendant 27ms !

Un solution matérielle : si vous passez sur des APA102 par exemple, il n'y a plus le souci de timing et de blocage des interruptions. Ces pixels coûtent cependant un peu plus cher.

sinon dès que vous détectez un caractère entrant sur le port série, il faut cesser tout appel à FastLED.show(); tant que la trame n'est pas entièrement reçue. Si votre trame fait une dizaine d'octets, à 115200 bauds ça ne se verra pas car vous bloquez l'affichage pendant moins d'une milliseconde.

Voici les leds que j'utilise, ce sont bien des neopixels ?

https://amzn.eu/d/06VzGEx0

oui les WS2812B sont des ne-pixels

(j'ai édité mon post ci dessus pendant que vous répondiez)

Merci beaucoup pour cette explication c'est beaucoup plus clair. Mais du coup si j'ai bien compris, theoriquement si l'execution est au niveau du fastLed.show et que la communication serie commence, je peux theoriquement louper le debut d'une trame, etant donnée le caractère "<" risque d'être remplacé par le suivants dans le buffer ?

Malheuresement j'ai déjà 15 m de bandeaux
WS2815 donc je vais essayer votre deuxième solution :slight_smile:

Donc la solution que je proposais dans mon message precedent devrait fonctionner (etant donné que l'appel a fastLed.show() se fait dans ma fonction affichage_emplacement()) ?

Je vois une amelioration possible à ce code:

  • envoyer, depuis le pc, une trame d'un seul octect pour demarrer la communication serie (au moins ce caractère ne risque pas d'etre remplacé dans le buffer pendant le temps d'exécution de fastLed.show())
  • attendre que l'arduino me renvoi une trame m'indiquant qu'elle est prète à ecouter ce que je vais lui envoyer (l'execution de fastLed.show() est en pause pendant cette periode)
  • envoyer la trame complète à l'arduino (du type <11,Rouge,3>
  • l'arduino me renvoie une trame m'indiquant qu'elle a bien reçu mon message et reprend l'execution de fastLed.show()

Ca vous parait cohérent ?

Oui la synchro avec 1 octet devrait fonctionner - dès que vous le voyez vous stoppez l'execution de fastLed.show()