Décoder du CCPM (Arduino et ESP8266)

Désirant utiliser une vielle radio commande pour piloter mon robot, je cherche depuis quelques temps une méthode pour récupérer les infos des positions de manches et interrupteurs sans modifier la matériel.

Il existe depuis ‹ fort fort longtemps › un signal sur les radiocommandes qui fournit sur un fil unique les informations utiles, son petit nom, le CPPM.

En fait, historiquement, c’est un ‹ effet de bord › des premières radiocommandes, comme on ne pouvait pas numériser et/ou multiplexer les infos, les constructeurs les ont sérialisées.

Dans la liaison est envoyée de manière ‹ codée › :
position canal 1 , position canal 2 , position canal 3 , position canal 4 , position canal 5 , position canal 6 …

Dans la pratique, il a été choisi tout simplement de faire des impulsions dont la durée correspond à la position, et un pause sans impulsions pour indiquer le retour au début.

Ce système a par le passé permit également de relier 2 radiocommandes pour les élèves pilotes. Le prof avait un interrupteur qui permettait de choisir entre les impulsions venant de sa radio, ou de celles de son élève.

Au début, il y a eut plusieurs ‹ tailles › d’impulsion possibles selon les fabriquant, puis, petit à petit tout s’est uniformisé.

Aujourd’hui,
la durée mini d’une impulsion correspond à 1000µs
la durée maxi d’une impulsion correspond à 2000µs

Et c’est également de là que provient le signal qui est utilisé pour contrôler des servo moteurs.

Le signal pour contrôler des servo moteur s’appelle ‹ PPM › pour Pulse Position Modulation
Le signal de plusieurs infos PPM rassemblées s’appelle Combined Pulse Position Modulation CPPM.

D’un fabriquant à l’autre, pour simplifier, la valeur correspondante à la position est la durée des impulsions positives, ou la durée entre deux front montants, ou la durée entre deux impulsions positives…

Voilà à quoi ça ressemble


(Ici ce sont des impulsions ‹ negatives ›, mais en fait c’est la durée à l’état haut qui donne la position.)

une autre représentation, avec la ‹ conversion › du CPPM en signaux PPM pour bouger des servos

Quelques infos sur ce thème http://blog.oscarliang.net/pwm-ppm-difference-conversion/

Comme je n’arrive pas à trouver de librairie ‹ arduino › compatible avec ESP8266, je n’ai plus qu’a me créer la fonction :smile:

Plutôt que de partir de rien, j’ai quand même regardé ce qui se fait à droite à gauche. pas évident de s’y retrouver, mais ça donne toujours des idées.

Donc, ce que j’ai trouvé sur le net :
https://github.com/jorisplusplus/AR.Drone-ESP8266/blob/master/Drone.ino , extrait

void loop(void) {        
        attachInterrupt(2, rising, RISING); // pin 2 gérée en interruption, appel fonction 'rising' sur front montant (RISING)
   fait des choses, reste du code
}

// fonction rising, détecte un début de pulse
 void rising(void) {
     start = micros();
     attachInterrupt(2, falling, FALLING); // pin 2 gérée en interruption, appel fonction 'falling' sur front descendant (FALLING)
 }

 // fonction rising, détecte un début de pulse
 void falling(void) {
   unsigned int diff = micros() - start;
     if(diff > 8000) {

       if(pos == 6) {
            dready = true;
            }
         pos = 0;
    } else {
      if(pos < 6) times[pos] = diff;
      pos++;
  }
  if(dready) detachInterrupt(2);
  else attachInterrupt(2, rising, RISING);
 }

Il y a aussi des choses plus complètes par exemple sur http://sourceforge.net/p/arduinorclib/code/HEAD/tree/trunk/ , mais pas forcement plus exploitables.

Des échanges sur plusieurs forums http://forum.arduino.cc/index.php?topic=131905.0

Compte tenu des ‹ variantes › existantes et du signal dispo sur le matériel que je désire employer, je vais tenter de ré-écrire ça pour le rendre plus universel…

Bon, j’ai fais plein d’essais… impossible d’avoir quelque chose qui fonctionne !!!

J’ai eut à un moment des détections de pulse avec un code tout pourri, et depuis que j’essaye de structurer, ça fait n’importe quoi.

Bien sur, je n’ai pas sauvegarder mon code pourri qui commençait à fonctionner… (ça serait trop simple)

Donc, juste pour échanger sur là ou j’en suis (des fois que je soit passé à coté d’un truc gros comme un porte avion), voilà le code.
En le commentant pour vous le présenté, j’ai peut-être trouvé un truc foireux… mais je laisse comme ça pour le moment

/* 

NodeMCU labels table for GPIO

 D0 = 16 / USER / WAKE
 D1 = 05
 D2 = 04 
 D3 = 00 / FLASH
 D4 = 02 / TXD1
 D5 = 14 /      / HSCLK
 D6 = 12 /      / HMISO
 D7 = 13 / RXD2 / HMOSI
 D8 = 15 / TXD2 / HCS
 D9 = 03 / RXD0
 D10 = 01 / TXD0
 SD0 = MISO / SDD0
 SD1 = MOSI / SDD1
 SD2 = 09 / SDD2
 SD3 = 10 / SDD3
 CMD = CS / SDCMD
  
*/


int cppmPIN = 14; // Set input pin for CPPM signal

String foo;

unsigned int pulseLenght;
unsigned int pulseRising;
unsigned int channel;

// Init table ppmValue ( with 'dummy' values for test )
int ppmValue[15] = {1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,1500,1000,1500,2000};

String out;



void setup() {
  
  delay (200);
  Serial.begin ( 115200 );
  delay (200);
  Serial.print ( "NodeMCU CPPM decoding test \n" );   // Hello code :p

  // init I/O and states

  pinMode ( cppmPIN, INPUT ); // Set input for CPPM from real world
  


} // end of setup


void loop() {             //////////////////////// Starting of loop

  
// CPPM pulse detection

  Serial.println ("Call ccpmRead() function");   // for debug

// Go to function cppmRead, ppmValue[15] should be available with values after
  cppmRead();
  detachInterrupt(digitalPinToInterrupt(cppmPIN)); // Release interruption


// Read each values and send it to serial (for debug)
  for (channel=0; channel <= 15; channel++){
    out = "Channel ";
    out = out + (channel);
    out = out + " value : ";
    out = out + ppmValue[channel];
    out = out + " µs";
    Serial.println (out);
  }

  delay (1000);
}                         //////////////////////// End of loop



// CPPM rising detection
void cppmRead(){
  Serial.println ("ccpmRead function called");  //(for debug)

// Call pulseDetect when cppmPIN goes from 0 to HIGH  
  attachInterrupt(digitalPinToInterrupt(cppmPIN), pulseDetect , RISING);
  
//  pulseDetect();
  delay (2);  //(for debug)
  detachInterrupt(digitalPinToInterrupt(cppmPIN));    //(for debug)
  }


void pulseDetect() {
  if (pulseLenght > 3000) {  //(for debug)
    Serial.println ("pulseLenght over than 3000");  //(for debug)
//  pulseRising = micros();

  for (channel=1; channel < 15; channel ++){     // loop to read multiple pulses
    pulseLenght = micros() - pulseRising;
    pulseRising = micros();

    if (pulseLenght > 3000) {
// if pulse longer than 3000µs, end of the CPPM signal reached      
      Serial.println ("pulseLenght over than 3000"); //  for debug
      break; // goes out of pulseDetect loop
      Serial.print ("Should never come...");  // for debug
    }
      else{
// pulse is less than 3000µs, ready to use it        
        Serial.println ("pulseLenght lower than 3000");
      }

// Feed the ppmValue table with index = channel and value = pulseLenght
    ppmValue[channel] = (pulseLenght);

    }

  }
}

Bien sur, partout ou c’est marqué '// for debug", c’est que j’essaie de comprendre :stuck_out_tongue:

Ayyyyyéééé !!!

Bon, je livre ‘brut de pomme’ et je vais me pieuter, je ferais du commentaire de l’affinage demain (par exemple, si je mets pas de delay(100) ça part en sucette…)

int cppmPIN = 14; // Set input pin for CPPM signal

unsigned int channel = 0;
unsigned int pulseLenght;
unsigned int pulsePrevious;
unsigned int pulseRising;

String out;

int ppmValue[15]; // Create a table for 16 channels max

void setup() {
    Serial.begin ( 115200 ); // Init serial port for debug
    pinMode ( cppmPIN, INPUT ); // Set input for CPPM from real world
}  // end of setup

//////////////////////// Starting of loop
void loop() {
    
  detachInterrupt(digitalPinToInterrupt(cppmPIN));

    for (channel=1; channel < 12; channel++){
        out = "Ch ";
        out = out + (channel);
        out = out + " : ";
        out = out + ppmValue[channel];
        out = out + " us/";
        Serial.print (out);
    }
// channel=0;
 Serial.println();
 delay (100);
 attachInterrupt(digitalPinToInterrupt(cppmPIN), pulseDetect , RISING);
 delay (100);

}


void pulseDetect(){
  pulsePrevious = pulseRising;
  pulseRising = micros();
  pulseLenght = micros() - pulsePrevious;
  ppmValue[channel] = pulseLenght;
  channel = channel+1;
  if (pulseLenght > 4000){
    channel = 1;
  }

}

Je touche au but pour pouvoir interfacer n’importe quelle radio de modélisme avec un Arduino ou un ESP8266 :stuck_out_tongue:

Plus ça va et mieux c’est !!

Bon, je ‘publie’ pas encore officiellement, il faut que je teste avec une autre source pour ‘pousser’ à 16 canaux, que je teste aussi sur Arduino et vérifier si ça fonctionne bien, mais là, 9 canaux sur un ESP8266, ça tourne bien. :content_le_gars:

Comme mon niveau de ‘coding’ est très limité, si les ingénieurs-informatiens-qui-aiment-les-ordinateurs (les initiés comme ‘Michel’ comprendront) veulent bien jeter un oeil et me dire si il y a des ‘trucs moches’… (ou me confirmer que je suis un dieu, mais restons simple, appelez moi seulement Maître)

int cppmPIN = 14; // Set input pin for CPPM signal

unsigned int channel = 0;
unsigned int pulseLenght;
unsigned int pulsePrevious;
unsigned int pulseRising;



int ppmValue[17]; // Create a table for 16 channels max, ppmValue[0] is the sync pulse

void setup() {
    Serial.begin (230400);//( 115200 ); // Init serial port for debug
    pinMode ( cppmPIN, INPUT ); // Set input for CPPM from real world
}  // end of setup

//////////////////////// Starting of loop
void loop() {
    
cppmRead();   // Call for a CPPM frame reading - cost less than 50ms

/*  Demo of cppm values reading
 *  You can also directly call the channel you need
 *  ppmValue[3] will return the PPM lenght for channel 3
 *  Note : ppmValue[0] is the sync pulse, not usable value in common case
 */

for (channel=1; channel < 17; channel++){   //ppmValue[0] is the sync pulse, we dont want to display it
  String out; // needed only for demo code
  out = "Ch";
  out = out + (channel);
  out = out + ":";
  out = out + ppmValue[channel];
  out = out + "us|";
  Serial.print (out);
}
Serial.println();
// End of demo code



} //////////////////////// End of loop



void cppmRead(){       // Fonction to interrupt loop during a CPPM frame reading
  delay (5);           // Need for stability
// This delay can be removed if time is enough between 2 cppmRead() call
// Crashes occur if you try to call pulseDetect() again during CPPM Frame Reading
  attachInterrupt(digitalPinToInterrupt(cppmPIN), pulseDetect , RISING);
  delay (30); // time to read full CPPM frame
  detachInterrupt(digitalPinToInterrupt(cppmPIN));
}

void pulseDetect(){
  pulsePrevious = pulseRising;
  pulseRising = micros();
  pulseLenght = micros() - pulsePrevious;
   if (pulseLenght > 3500){    // Sync pulse of CPPM Frame detection
    channel = 0;               // To store sync pulse value in ppmValue[0] 
   }
  ppmValue[channel] = pulseLenght;
  channel = channel+1;
}

P.S. : bien sur vous l’aurez compris, ce message est plein d’ironie, ça doit être l’adrénaline de la victoire

Est ce que ton problème de delay() ne serait pas lié au WiFi.
A priori si la boucle principale est trop longue il faut rajouter un delay pour permettre à l’ESP8266 de gérer le Wifi. (Section Timing and delays)

En fait, c’est pas tout à fait ça. (et mon code n’utilise pas les fonctions wifi de l’ESP8266 pour le moment)
J’ai fais des essais successifs et j’ai finalement réussi à réduire les delay()

J’en ai déduis que c’est tout simplement entre les appels ‘detachInterruptPin’ et ‘attachInteruptPin’ que ça coinçait.

Si la loop est assez longue, le crash que j’observe ne se produit plus !

Possible que le procc ait besoin de quelques cycles d’horloges pour activer ou désactiver les interruptions… et il faut quand même recevoir l’intégralité de la séquence CPPM.

Du coup, le delay(5) dans void cppmRead() suffit.

Expérimentalement, avec la radio que j’utilise pour les tests, un delay(2) suffit, mais cette radio ne sort que 9 voies, il faut que j’expérimente avec une 16 voies… :wink:

Bon, après essais, le décodage fonctionne également sur Arduino et prend jusqu’à 16 voies.

C’est fonctionnel, je l’ai poussé sur https://github.com/acolab/CPPM_ESP8266_Arduino