top of page

Avant de lire cette partie, nous vous conseillons d'avoir lu et assimilé la partie Codage, Le codage : Observation et explication.

DECODEUR PPM : FONCTIONNEMENT

Afin de mieux appréhender ce qu'est un décodeur PPM, nous avons décidé d'en réaliser un, qui aura pour but de décoder le signal sortant du microcontrôleur de la radiocommande. Pour cela, nous avons utilisé la carte Arduino Uno, et son microcontrôleur AtMega 328, ainsi que son environnement de programmation. Notre choix s'est en effet rapidement dirigé vers ce produit, pour sa simplicité d'utilisation, son prix modique et ses possibilités de programmation en un langage évolué : le langage C.

 

 

Tout d'abord nous allons, comme pour le codeur (voir partie Codage, Présentation et généralités), établir un cahier des charges que notre décodeur PPM devra respecter :

 

  • pouvoir sélectionner la voie à décoder par l'intermédiaire de boutons poussoirs

  • visualiser à l'aide de LED la voie sélectionnée

  • décoder la voie sélectionnée

  • envoyer l'information par une liaison série  (par liaison USB : Universal Serial Bus) vers un ordinateur

  • afficher le numéro de voie et la valeur correspondante sur l'écran de l'ordinateur

 

Comme nous pouvons le voir dans le cahier des charges, la carte Arduino doit être complétée par un montage électronique permettant de remplir les fonctions de sélection de voie et d'affichage de voie sélectionnée. Le schéma électrique du montage que nous avons réalisé se trouve ci-dessous :

 

 

 

 

 

 

Sur ce schéma, on peut voir que 4 LED et 4 boutons poussoirs sont reliés à Arduino. Les boutons poussoirs serviront donc à acquerir de l'information  (numéro de voie que l'utilisateur veut lire) et les LED à communiquer de l'information (le numéro de voie choisie). On peut également voir une LED nommée "Power On" et qui s'allumera lorsque le montage sera sous tension. L'alimentation en +5V des boutons poussoirs se fait par la sortie 5V de l'Arduino (qui provient en fait de l'ordinateur via son port USB).

CALCUL DE LA VALEUR DES RESISTANCES

La tension de seuil aux bornes d'une LED dépend de la couleur qu'elle émet. Une recherche sur internet nous a permis de trouver ces différentes valeurs qui sont :

 

  • environ 2,3 V pour une LED verte

  • environ 1,8 V pour une LED rouge

  • environ 2,1 V pour une LED jaune

  • environ 2,6 V pour une LED bleue

     

De plus, les constructeurs de LED préconisent un courant de fonctionnement d'environ 10 à 20 mA. Notre montage étant alimenté par du 5V fourni par l'ordinateur, nous choisissons la valeur de 10mA afin de limiter la charge sur le port USB de l'ordinateur.

 

RLEDverte=(5 - 2,3 ) / 0,01=270 Ω

 

RLEDrouge=(5 – 1,8 ) / 0,01=320 Ω

 

RLEDjaune=( 5 – 2,1 ) / 0,01=290 Ω

 

RLEDbleue=( 5 – 2,6 ) / 0,01=240 Ω

 

Dans un premier temps, pour des raisons de facilité, nous prendrons 4 résistances de valeur identique. Notre choix se porte sur des résistances de valeur 330 Ω, ce qui correspond à la valeur normalisée la plus proche de la valeur maximale de la résistance que nous avons calculée. Cela permet aussi, tout en assurant un fonctionnement correct de diminuer la charge à fournir par le port USB.

 

Afin de rendre le montage moins sensible aux parasites lorsque les boutons poussoirs sont ouverts, nous forçons le niveau des entrées de l'Arduino à un potentiel bas, par l'intermédiaire de résistances dites « de rappel ». Celles-ci doivent avoir une valeur suffisamment élevée pour minimiser la consommation de courant mais elles doivent aussi avoir une valeur suffisamment faible pour qu'elles puissent fixer le potentiel. Il est communément admis l'utilisation de résistances de 10KΩ pour assurer cette fonction. 

LE HARDWARE ( l'environnement mis en place pour le décodeur)

Voici quelques photos du décodeur PPM que nous avons réalisé :

Dans le but de faciliter l'utilisation des boutons poussoirs et pour une meilleure esthétique, nous avons utilisé 4 boutons poussoirs présents sur une « boite électronique », fabriquée par nos soins. Celle-ci ayant été fabriquée antérieurement à notre TPE et non dans l'optique de celui-ci, elle comporte plusieurs autres composants, qui ne sont bien entendu pas utilisés pour notre décodeur PPM.

 

Grâce à ce montage, il nous sera possible de sélectionner la voie à décoder grâce aux boutons poussoirs. De plus, il sera également possible d'afficher la voie qui est décodée, grâce aux LED de différentes couleurs. Pour finir, la liaison USB vers l'ordinateur, nous permettra de lire les valeurs décodées par Arduino sur l'écran d'ordinateur.

LE SOFTWARE (partie interne au microcontrôleur)

1/ Algorigramme

Pour comprendre un programme simplement, il est d'usage d'utiliser un algorigramme. Voici celui traduisant le programme que nous avons écrit :

algorigramme de la boucle principale

Comme tous les programmes, il y a une série d'initialisations, qui consistent à créer les variables/constantes dont nous aurons besoin au long du programme. Nous détaillerons cela plus précisément dans la seconde partie.

 

L’algorithme principal est une boucle.

S'ensuivent ensuite 4 tests, testant la position des boutons poussoirs 1,2,3 et 4. Ils permettent de sélectionner la voie à étudier, et d'allumer une LED pour traduire cela.

 

 

 

 

 

 

 

S'ensuit ensuite l'envoi du numéro de voie mesurée ( choisie grâce aux boutons poussoirs ) et des valeurs mesurée.

 

Un délai de 100ms permet de faire une pose et ainsi de préserver le bon fonctionnment du microcontrôleur.

 

La boucle reprend ensuite jusqu'a l'infini.

Vous remarquerez que si le programme s'arrêtait là, aucune mesure ne serait faite sur le code PPM et le programme n’établirait pas sa fonction principale : décoder.

Cette action, s'effectue par le biais de ce que l'on appelle : une interruption. En effet, pour décoder le code PPM, il faut mesurer des temps entre impulsions. Il est compliqué pour un microcontrôleur de détecter une impulsion. Cependant, il est beaucoup plus simple pour lui de détecter un changement de niveau sur une entrée. En effet, une impulsion n'est ni plus ni moins qu'un passage d'un niveau bas à un niveau haut suivi d'un retour au niveau bas (ou inversement), soit 2 changements de niveaux. Nous avons donc utilisé cette astuce pour notre programme; à chaque changement de niveau sur l'entrée PPM, la boucle principale s'interrompt et une routine de traitement d'interruption est appelée. Elle est présentée ci-dessous :

algorigramme de la routine d'interruption de mesure

Si le temps mesuré entre deux changements est inférieur à 0,5ms, c'est qu'il doit s'agir d'une impulsion (temps entre le front descendant et le front montant de l'impulsion). Il n'y a donc aucune mesure à faire dans ce cas là.

Si le temps mesuré entre deux changements est supérieur à 2ms, il s'agît du temps entre la fin d'une trame et le début d'une nouvelle trame (rappelez vous : T=19ms). Il faut alors mettre à 0 le compteur de voie (qui sert à savoir quelle voie est mesurée).

 

Sinon, c'est forcément que le temps correspond au codage d'une voie (1 < t <2 ms), il faut donc mesurer ce temps, et incrémenter de 1 le compteur de voie, car la prochaine impulsion concernera la voie suivante. Il faut préciser que ce numéro de compteur de voie n'a absolument rien à voir avec les numéro de voie ( voie 1, 2 , 3, 4) car celui-ci est uniquement interne au programme.

Le nombre d'interruptions dans le programme est très grand puisqu'il y a à peu près 53 trames PPM par seconde, contenant chacunes 6 impulsions. Comme chaque impulsion correspond à 2 changements de niveaux, on a alors Nint, le nombre d'interruptions par seconde, qui est égal à :

 

Nint=53 x 6 x 2=636 interruptions / seconde

 

Ce qui reste toutefois faible par rapport aux capacités de traitement du microcontrôleur équipant la carte Arduino UNO; celui-ci, cadencé par une horloge à 16 Mhz, étant capable d'exécuter une instruction toutes les 0,0625 micro seconde. 

 

2/ Le programme

Ci-dessous vous trouverez le programme, écrit en lagage C, de décodage PPM que nous avons écrit. En vert se trouve le corps du programme et en jaune, les commentaires. Pour une compréhension simple et globale du programme, il est suffisant de lire les commentaires situés à droite, en blanc. 

/* Ce programme decode n'importe quel code PPM jusqu'à 16 voies, 

  stocke les valeurs décodées dans un tableau

  et selon la voie sélectionnée par bouton poussoir

  transmet ces informations au PC

 */

 

#define LED1 12         // Indicateur de sélection de la voie 0

#define LED2 11         // Indicateur de sélection de la voie 1

#define LED3 10         // Indicateur de sélection de la voie 2

#define LED4 9          // Indicateur de sélection de la voie 3

 

#define BP1 7           // Selection de la voie 0

#define BP2 6           // Selection de la voie 1

#define BP3 5           // Selection de la voie 2

#define BP4 4           // Selection de la voie 3

 

#define PPM_Pin 3                 // Broche d'interruption (peut etre la 2 ou la 3) sur laquelle le signal PPM à décoder est                                                        //appliqué

int voie[16];                            // Tableau de stockage des valeurs décodées pour chacune des 16 voies possibles

 

 

 

 

 

 

 

/*

 * Initialisation

*/

void setup()

{

  /* Initialisation de la communication avec le PC */

  Serial.begin(115200);

  Serial.println("ready");

  

  /* Positionnement des sorties pour les LED */

  pinMode(LED1, OUTPUT);

  pinMode(LED2, OUTPUT);

  pinMode(LED3, OUTPUT);

  pinMode(LED4, OUTPUT);

  

  /* positionement des entrées pour les boutons poussoirs */

  pinMode(BP1, INPUT);

  pinMode(BP2, INPUT);

  pinMode(BP3, INPUT);

  pinMode(BP4, INPUT);

  

  /* Extinction de toutes les LED lors de l'initialisation */

  digitalWrite(LED1, LOW);

  digitalWrite(LED2, LOW);

  digitalWrite(LED3, LOW);

  digitalWrite(LED4, LOW);

 

  /* Attachement de la fonction decode_ppm à l'interruption int1 (broche 3) */

  pinMode(PPM_Pin, INPUT);

  attachInterrupt(PPM_Pin - 2, decode_ppm, CHANGE);

 

  /* Initialisation du timer 16 bits timer1 */

  TCCR1A=0;  //reset timer1

  TCCR1B=0;

  TCCR1B |=(1 << CS11);  // positionne le diviseur interne à 8 pour obtenir un incrément toutes les 0,5 us

}

 

 

 

 

 

/*

 * Boucle de traitement

*/

void loop()

{

  static int numeroVoie=0;

  static int premiereFois=0;

 

  /* Lors de la 1ere exécution allume la LED1 et selectionne la voie 0 */

  /* Nota : cela pourrait etre fait dans le setup. Ce qui éviterait ce bout de code */

  if (!premiereFois) {

    numeroVoie=0;

    digitalWrite(LED1, HIGH);

    ++premiereFois;

  }

 

 /* Allume les LEDs en fonction du bouton poussoir actionné et sélectionne la voie correspondante */ 

  if(digitalRead(BP1)==HIGH) {              // définit que le BP1 allume la LED1 et sélectionne la voie 0

    digitalWrite(LED1, HIGH);

    digitalWrite(LED2, LOW);

    digitalWrite(LED3, LOW);

    digitalWrite(LED4, LOW);

    numeroVoie=0;

  } else {

      if(digitalRead(BP2)==HIGH) {          // définit que le BP2 allume la LED2 et sélectionne la voie 1

        digitalWrite(LED2, HIGH);

        digitalWrite(LED1, LOW);

        digitalWrite(LED3, LOW);

        digitalWrite(LED4, LOW);

        numeroVoie=1;

      } else {

        if(digitalRead(BP3)==HIGH) {       // définit que le BP3 allume la LED3 et sélectionne la voie 2

        digitalWrite(LED3, HIGH);

        digitalWrite(LED1, LOW);

        digitalWrite(LED2, LOW);

        digitalWrite(LED4, LOW);

        numeroVoie=2;

      }

      else {

        if(digitalRead(BP4)==HIGH) {        // définit que le BP4 allume la LED4 et sélectionne la voie 3

        digitalWrite(LED4, HIGH);

        digitalWrite(LED2, LOW);

        digitalWrite(LED3, LOW);

        digitalWrite(LED1, LOW);

        numeroVoie=3;

        }

      }

    }

  }   

  

  /* Envoie les informations de N° de voie et de valeur au PC */

  Serial.print("Voie : ");

  Serial.print(numeroVoie);

  Serial.print(" - Valeur : "); 

  Serial.print(voie[numeroVoie]);

  Serial.println(" ");

 

  /* Prend soin du microcontrôleur en lui faisant faire une pausse de 100ms */

  delay(100);

}

 

 

 

 

 

/*

 * Traitement de l'interruption int1

*/

void decode_ppm(){ 

  static unsigned int largeurImpulsion;

  static unsigned long tempsEcoule;

  static byte canal;

 

  tempsEcoule=TCNT1;

  TCNT1=0;

 

  if(tempsEcoule < 1000){                                    // Si depuis l'interruption précédente le temps écoulé est < à 500us

    largeurImpulsion=tempsEcoule;                   // c'est qu'il doit s'agir d'une impulsion de début de voie

  }

  else if(tempsEcoule > 4200){                           // Si par contre le temps ecoule est > à 2100us alors il doit s'agir                                                                                           //d'une synchro

    canal=0;                                                       // et la prochaine valeur sera celle de la voie 0

  }

  else{                                                                              // Si le temps ecoule se situe entre 500us et 2100us alors                                                                                                      //il s'agit d'une valeur à décoder

    voie[canal]=(tempsEcoule + largeurImpulsion)/2;    // La valeur est égale à la somme de la largeur de l'impulsion 

                                                                                       //et du temps qui s'est écoulé depuis

    canal++;                                                                     // La prochaine valeur sera celle de la voie suivante

  }

}

Cette partie est la partie « initialisations » du programme. C'est ici que l'on définit l'état des broches (entrée / sortie), l'état dans lequel elles doivent se trouver au début du programme.

On précise également l'interruption qui doit être utilisée et, enfin, met en place un "timer" pour mesurer le temps qui sépare deux interuptions.

Au début d'un programme, on commence par donner des noms aux broches que l'on va utiliser, pour se simplifier le travail de programmation et pour simplifier la lecture. Cela s'effectue grâce à la directive « #define » en language C. Par exemple, on remarque que l'on a nommé la broche 12 "LED1" ou encore la broche 11 "LED2". On a fait de même avec les boutons poussoirs. Cette étape est facultative, mais simplifie grandement la maintenance et la création du programme. On a également défini un tableau de stockage, qui permettra de stocker les valeurs décodées.

Cette partie est la partie la plus importante du programme, puisque c'est la boucle principale. La fonction "void loop(){    }", va faire recommencer tout ce qui se trouve à l'intérieur  des accolades à l'infini ( ou du moins jusqu'à ce que l'on coupe le courant ). Comme vu précédemment avec l'algorigramme, la boucle de traitement est constituée de 4 tests consécutifs, qui se traduisent par les expressions « if{ } » et « else{ } », en langage de programmation C. On retrouve donc exactement le même mode de fonctionnement que celui décrit dans la partie algorigramme : on teste les positions des boutons poussoirs.

 

 

 

 

 

 

 

 

 

 

Ici est programmé l'envoi des informations de numéro de la voie étudiée et de la valeur décodée correspondante.

 

On retrouve, comme dans la partie algorigramme, une pose de 100ms

Cette partie est la partie interruption. On voit deux tests consécutifs, qui permettent de savoir dans quel cas on se trouve (cas d'une impulsion, cas d'un espace entre deux impulsions, ou espace entre deux trames). On trouve alors la formule à utiliser pour calculer le temps séparant deux impulsions. C'est cette formule qui sera utilisée dans le programme pour calculer le temps de séparation entre deux impulsions. Enfin, l'expression "canal++" permet d'incrémenter de 1 le compteur intitulé "canal". Cela permet de passer au numéro de la prochaine voie à décoder.

Cette partie qui avait pour but de vous expliquer le principe de fonctionnement d'un décodeur PPM, en prenant l'exemple sur un décodeur conçu par nos soins, s'achève à présent. Le décodeur au sein de l'hélicoptère, fonctionne d'ailleurs de manière quasi identique, à la nuance près qu'il ne possède pas de boutons poussoirs ni de LED, puisqu'il envoie directement les informations décodées vers la partie "traitement de l'information", pour l'envoi vers les servomoteurs et les moteurs. Dans la prochaine partie ( partie Décodeur PPM : Résultats), il s'agira de faire fonctionner le décodeur et d'interprêter les résultats qu'il donne.

bottom of page