[RESOLU] Lisser mes valeurs lues en PPM

Bonjour,

Je lis un signal PPM grâce l'INPUT CAPTURE PIN de l'Atmega 328P.

Pour ceux que n'imagine pas ce qu'est un signal PPM, voici un exemple trouvé sur le net :

Dans mon cas j'ai 6 canaux, voici ma sortie COM de DEBUG :

En rouge se sont les valeurs calculées des 6 canaux (ce sont des demi micro-secondes, 2000 vaut donc 1ms)

En vert ce sont des valeurs mappées comme suit, à partir des canaux 2,3 & 6 et qui servent à allumer une LED RVB.

    R=map(cha[1],1960,3960,0,255);
    V=map(cha[2],1960,3960,0,255);
    B=map(cha[3],1960,3960,0,255);

En Orange, ce sont les "jitters" qui me posent problème.
Ma question :
Comment "lisser" ces valeurs ?

PS : Le code complet dans le message suivant...

Le code :

// Lecture PPM6ch & pilotage LED RVB
// C'est le timer1 qui est utilisé la lecture du PPM

#define ledRouge 3 // Constante pour la broche 3
#define ledVert 5 // Constante pour la broche 5
#define ledBleu 6 // Constante pour la broche 6
#define ledPin 13 // Pin de la LED "locale" de l'Arduino


// Données de capture du Rx
boolean endTrame=0,failsafe=0,firstEdge=1;
volatile unsigned int vchUp[8];
volatile unsigned int vchPos[6];
unsigned int cha[6];
byte curEdge;

// Valeurs RVB
int R,V,B; 

// Variables permettant de faire clignoter la led de l'Arduino
int ledState = LOW;
long previousMillis = 0;
long interval = 500; 

void setup()
{
  // Pin setup, parmetrage des PIN en sortie :
  pinMode (ledVert,OUTPUT); // Broche ledVert configurée en sortie
  pinMode (ledRouge,OUTPUT); // Broche ledRouge configurée en sortie
  pinMode (ledBleu,OUTPUT); // Broche ledBleu configurée en sortie
  pinMode(ledPin,OUTPUT); // Arduino LED

  digitalWrite(ledPin,HIGH); // Allumage de la LED de l'Arduino

  // Sortie DEBUG
  Serial.begin(57600);
  Serial.println("");
  Serial.println("PPM read & Led RVB");
  // Timer1 setup
  TCCR1A=B00000000; // OCR2A/OCR2B : disconnected, Timer: normal mode
  TCCR1B=B00000010; // Falling edge CAPTUREPIN detection, Timer:normal mode, Prescaler=clk/8 
  TIMSK1=B00100001; // Activate CAPTUREPIN interrupt & OVF interrupt

  delay(1000); // pour etre certain que les interruptions aient tourné avant la main loop

}

ISR(TIMER1_CAPT_vect)
{
  vchUp[curEdge]=ICR1; // Capture des timestamp 1 à 6
  curEdge++;
  if (curEdge>7) {     // A partie du 7ème...   
    TCNT1=0;           // RESET du counter pour éviter le FailSafe
    if ((vchUp[7]-vchUp[1]) > 30000) {  //Si la trame totale dépasse 15ms - Trame KO ou mauvaise synchro
      curEdge=0;            //Remise à 0 du compteur de fronts                      
    }
    else {                  //Sinon, une bonne trame est capturée, on calcule donc la valeur des canaux
      curEdge=1;
      for (int i=0;i<6;i++) {
        vchPos[i]=(vchUp[i+2]-vchUp[i+1]); //Mesure des canaux (diviser par 2 pour obtenir des µs)
      }
      endTrame=1;           // Pour dire à la MainLoop que de nouvelles valeurs sont disponibles
    }
  }
}

ISR(TIMER1_OVF_vect)
{
  failsafe=1;  // Le compteur a attends sa limite (32ms), on déclenche donc le FailSafe
}

void loop()
{
  if (endTrame==1) {
    cli();
    for (int i=0;i<6;i++) {
      cha[i]=vchPos[i]; // Recopie les valeurs des canaux
    }
    sei();
    endTrame=0;
    LedBlink(500);
    if (failsafe){ 
      failsafe=0;
      Serial.println("End of FailSafe");
    }

    // La 1ere partie "intelligente" du code se trouve ICI
    // Elle n'est jouée que si les canaux ont "bougé"

    for (int i=0; i < 6; i++){ // Imprimer les valeurs des canaux sur le port série
      Serial.print(cha[i]);
      Serial.print(";");
    } 

    // Mapper trois canaux vers les couleurs RVB

    R=map(cha[1],1960,3960,0,255);
    V=map(cha[2],1960,3960,0,255);
    B=map(cha[3],1960,3960,0,255);

    ledRVBpwm(R,V,B); // génère impulsion largeur voulue pour la couleur

    // Imprimer les valauers de la LED

    Serial.print(R);
    Serial.print(";");
    Serial.print(V);
    Serial.print(";");
    Serial.print(B);
    Serial.println(";");
  }

  // La 2eme partie "intelligente" du code se trouve ICI
  // Elle est jouée à chaque tour de boucle

  if (failsafe){

    if (R>255) R=0;    // En mode FAILSAFE, on fait varier les 3 couleurs progressivement & simultanément
    else R++;
    if (V>255) V=0; 
    else V++;
    if (B>255) B=0; 
    else B++;


    ledRVBpwm(R,V,B); // génère impulsion largeur voulue pour la couleur
    Serial.print("FAILSAFE");
    LedBlink(50);
    Serial.print(R);
    Serial.print(";");
    Serial.print(V);
    Serial.print(";");
    Serial.print(B);
    Serial.println(";");
  }
}


void ledRVBpwm(int pwmRouge, int pwmVert, int pwmBleu) { // reçoit valeur 0-255 par couleur

  //--- attention - avec une LED RGB anode commune : la LED s'allume sur niveau BAS !

  analogWrite(ledRouge, 255-pwmRouge); // impulsion largeur voulue sur la broche 0 = 0% et 255 = 100% haut
  analogWrite(ledVert, 255-pwmVert); // impulsion largeur voulue sur la broche 0 = 0% et 255 = 100% haut
  analogWrite(ledBleu, 255-pwmBleu); // impulsion largeur voulue sur la broche 0 = 0% et 255 = 100% haut


}

void LedBlink(long interval) { // reçoit délais de clignotement

  unsigned long currentMillis = millis();

  if(currentMillis - previousMillis > interval) {
    // save the last time you blinked the LED 
    previousMillis = currentMillis;   

    // if the LED is off turn it on and vice-versa:
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;

    // set the LED with the ledState of the variable:
    digitalWrite(ledPin, ledState);
  }
}

EDIT : Le mode FAILSAFE indique que le signal PPM en entrée n'est plus fourni...

PPM, c'est le principe des signaux pour les servos de modelisme non ?

Comment "lisser" ces valeurs ?

Filtrage sur une fenêtre glissante
on fait un tableau avec les n dernières valeurs et on calcule la moyenne sur ces valeurs.

ou alors un filtre récursif
valeur = nouvelle_valeurp + valeur(1-p)
0 < p < 1
on joue sur p pour fixer le temps de réaction du filtre. Plus p est petit plus lente sera la réaction à une variation sur la nouvelle valeur.

fdufnews:

Comment "lisser" ces valeurs ?

Filtrage sur une fenêtre glissante
on fait un tableau avec les n dernières valeurs et on calcule la moyenne sur ces valeurs.

ou alors un filtre récursif
valeur = nouvelle_valeurp + valeur(1-p)
0 < p < 1
on joue sur p pour fixer le temps de réaction du filtre. Plus p est petit plus lente sera la réaction à une variation sur la nouvelle valeur.

bonjour fdufnews
pas mieux et le recursif simple ici est surement la meilleure méthode sur arduino.
je n'arrive pas a remettre la main sur la vieille étude qui concluait statistiquement que P=0.85 (0.15) était le meilleur compromis
C'est ce que j'utilise "tout venant"

Artouste:
je n'arrive pas a remettre la main sur la vieille étude qui concluait statistiquement que P=0.85 (0.15) était le meilleur compromis
C'est ce que j'utilise "tout venant"

Le principe de Pareto

Jean-François:
PPM, c'est le principe des signaux pour les servos de modelisme non ?

Oui, c'est le signal avant "dégroupage", le récepteur génère à partir de cette trame un signal PWM servo pour chacun des servo :

J'utilise une radio 6 voies comme "joystick" de DEBUG :wink:

Merci pour vos réponses... je m'y plonge.

fdufnews:

Comment "lisser" ces valeurs ?

Filtrage sur une fenêtre glissante
on fait un tableau avec les n dernières valeurs et on calcule la moyenne sur ces valeurs.

ou alors un filtre récursif
valeur = nouvelle_valeurp + valeur(1-p)
0 < p < 1
on joue sur p pour fixer le temps de réaction du filtre. Plus p est petit plus lente sera la réaction à une variation sur la nouvelle valeur.

Dans cet exemple, doit-on faire le calcul à chaque fois avec l'ancienne valeur CALCULEE et l'ancienne valeur LUE ?

Pas sur que ce soit celui dont parle fddunews :

Si, si c'est bien la même chose

Yes.... XD

Merci à tous !

Après différent essais avec Excel, j'ai finalement opté pour un solution plus "radicale".

Je ne prend en compte la nouvelle valeur mesurée que si elle dépasse un écart minimum.

int const ecart=3;    // C'est la valeur de l'hystérésis...

    for (int i=0;i<6;i++) {
      if (vchPos[i] > (cha[i]+ecart) || vchPos[i]< (cha[i]-ecart)) { // Si la nouvelle valeur dépasse l'hystérésis
      cha[i]=vchPos[i]; // Recopie les valeurs des canaux
      }

Ici c'est 3, ça représente 1,5 µs, ça ne bouge donc qu'à partir de 2µs d'écart, pour un servo ça laisse 500 pas, ça me parait correct.

Hey,

Ca me donne une idée, cette fameuse lecture PPM, je l'utilise finalement assez souvent, et ça me fait faire pas mal de copier/coller de la reprendre à chaque fois.

Vous parait-il possible de la "librariser" ?

Ca serait ma première... :~

Voici le code en question :

// Lecture PPM6ch par INPUT CAPTURE UNIT
// C'est le timer1 qui est utilisé pour la lecture du PPM
// La lecture du PPM ne peut se faire QUE sur la PIN 8 (ICP1)

boolean endTrame=0,failsafe=0,firstEdge=1;
volatile unsigned int vchUp[8];
volatile unsigned int vchPos[6];
unsigned int cha[6];
byte curEdge;
int const ecart=3;    // C'est la valeur de l'hystérésis...

void setup()
{
  // Timer1 setup
  TCCR1A=B00000000; // OCR2A/OCR2B : disconnected, Timer: normal mode
  TCCR1B=B00000010; // Falling edge CAPTUREPIN detection, Timer:normal mode, Prescaler=clk/8 
  TIMSK1=B00100001; // Activate CAPTUREPIN interrupt & OVF interrupt
}

ISR(TIMER1_CAPT_vect)
{
  vchUp[curEdge]=ICR1; // Capture des timestamp 1 à 6
  curEdge++;
  if (curEdge>7) {     // A partie du 7ème...   
    TCNT1=0;           // RESET du counter pour éviter le FailSafe
    if ((vchUp[7]-vchUp[1]) > 30000) {  //Si la trame totale dépasse 15ms - Trame KO ou mauvaise synchro
      curEdge=0;            //Remise à 0 du compteur de fronts                      
    }
    else {                  //Sinon, une bonne trame est capturée, on calcule donc la valeur des canaux
      curEdge=1;
      for (int i=0;i<6;i++) {
        vchPos[i]=(vchUp[i+2]-vchUp[i+1]); //Mesure des canaux (diviser par 2 pour obtenir des µs)
      }
      endTrame=1;           // Pour dire à la MainLoop que de nouvelles valeurs sont disponibles
    }
  }
}

ISR(TIMER1_OVF_vect)
{
  failsafe=1;  // Le compteur a attends sa limite (32ms), on déclenche donc le FailSafe
}

On y retrouve dans l'ordre :

  • La déclaration des variable (dois-je les garder en volatile ?)
  • Le paramétrage du timer1
  • L'interruption sur ICP1
  • L'interruption sur overflow du timer1

C'est principalement ces parties que je souhaite "librariser"...

J'ouvre un nouveau sujet pour l'histoire de la librairie, ça sera plus clair :roll_eyes:

Lisser les valeurs PPM est résolu.