BTS Mesure BTS Mesure
Présentation2014CapteursModulationTyponsArduinoDémodulationApp InventorCourbesVidéos

Objectif

Une fois le signal radio réceptionné, nous souhaitons démoduler le signal et réaliser un traitement automatisé des données afin de tracer automatiquement les courbes et visualiser la trajectoire du ballon sur Google Map.

De plus ce dispositif nous sera utile pour la chasse au ballon :  nous irons avec le scanner radio sur la dernière position connue du ballon et ainsi nous devrions capter la position exacte de l'atterrissage.

 

Présentation

Pour réaliser la démodulation FSK nous avons utiliser le circuit spécialisé XR2211.

Comme le montre le schéma de principe, il est constitué :

  • d'un étage amplificateur à contrôle automatique du gain (patte2)
  • d'une boucle à verrouillage de phase (PLL) et d'un VCO
  • d'un filtre passe bas externe RF et CF
  • d'un trigger pour la remise en forme du signal.

CAO - Proteus

A partir de ce datasheet nous avons élaboré notre carte d'extension Arduino.

Pour le choix des composants nous avons aussi utilisé le document Planète Sciences suivant.

Nous avons adapté l'étage de sortie pour être compatible avec un arduino : plus besoin d'un MAX 232, l'Arduino nous offre une liaison UART 0 - 5 V compatible avec le XR2211 et une sortie USB (ainsi qu'une liaison bluetooth avec le module HC06).

Son entrée est la sortie audio de notre scanner radio. Elle récupère notre modulation FSK audio sur la patte 2 :

  • 900 Hz     -> 0 logique
  • 1500 Hz   -> 1 logique
Les fréquences porteuses  (900Hz - 1500 Hz => moyenne logarithmique 1100 Hz) et les fréquences modulantes (signal UART 600 bauds, soit du 300 Hz et ses harmoniques) sont très proches.

Pour les séparer à la sortie de la PLL nous allons utiliser un filtre passe bas du second ordre constitué :
  • d'une cellule  RF CF ;
  • d'un suiveur ;
  • d'une seconde cellule RF2 CF2.
A la sortie du trigger (patte 7) notre signal est inversé, nous ajoutons donc un inverseur avant d'injecter ce signal à la liaison UART Rx de notre Arduino.

 

Test et analyse de notre montage

 

Oscillogramme 1 : signal modulé

  • Voie 1 : Signal modulant UART
  • Voie 2 : Signal modulé point de test PT1

Sortie de notre modulateur XR2206 modulé par la sortie UART de notre Arduino modulateur

Oscillogramme 2 : sortie PLL

  • Voie 1 : Signal modulant UART
  • Voie 2 : PT2, signal sortie PLL

On observe que le signal de sortie PLL contient le signal modulant et des résidus de porteuse très important.


Oscillogramme 3 : sortie passe bas donnée 1

  • Voie 1 : Signal modulant UART
  • Voie 2 : PT3, sortie du premier étage de notre filtre passe bas (1er ordre)

Les résidus de porteuse sont légèrement atténués.


Oscillogramme 4 : sortie passe bas donnée 2

  • Voie 1 : Signal modulant UART
  • Voie 2 : PT4, sortie du deuxième étage de notre filtre passe bas (2ème ordre)

Les résidus de porteuse sont atténués. Nous pouvons bien séparer l'état haut (> 2,5 V) et l'état bas (<0.94 V) comme le montre les curseurs.

Oscillogramme 5 : sortie trigger intégré (patte 7 : Data Output)

  • Voie 1 : Signal modulant UART
  • Voie 2 : PT5, sortie du trigger intégré dans le circuit XR2211

Nous récupérons bien le signal modulant mais inversé.

Oscillogramme 6 : sortie inverseur

  • Voie 1 : Signal modulant UART
  • Voie 2 : PT6, sortie inverseur

On note les défauts de l’ampli Op  suivants (Alimentation 0 - 5 V)

Le 0 est  à 0.64 V

Le 1 est à 3.92 V

Mais cela reste compatible avec la liaison UART.

Les signaux de départ et d’arrivée transportent bien le même message

Le Contrôle Automatique de Gain

Nous avons utilisé un analyseur logique associé au logiciel Saleae afin d'acquérir le signal traité.

Lors d'un premier essai, réalisé par une liaison filaire, nous n'avions pas de bruit. Mais lors du test de la chaîne de transmission complète avec liaison radio nous avons récupéré beaucoup de bruits lorsqu'il n'y avait aucun signal émis.

On récupère bien les 3 trames mais lors de la période de "silence", le contrôle automatique de gain du XR2211 amplifie le bruit  et génère un signal aléatoire.

Pour éliminer ce signal aléatoire de manière logicielle nous disposons de  3 repères :

  • le signal de synchro (octet 0) qui commence par un 255,
  • les 10 octets suivants ne comportent pas de 255,
  • le cheksum qui se situe 11 octets après.

Un signal comportant un 255 suivi de 10 octets sans 255 avec sur l'octet 11 un checksum valide sera considéré comme bon.

Après traitement logiciel nous n'isolons que le signal valide :

Détail d'une trame (Synchro : 255 ; 8 capteurs : 0,0,0,0,0,0,0,0 ; batterie : 115 ; checksum : 57) :

Voici le traitement réalisé par l'Arduino pour supprimer le bruit :


void loop() {
  if(Serial.available()) {
    c=Serial.read();
    if (c==255) j=0;     // initialisation de j lors de la rencontre d'un 255
    if (j<11) Trame[j]= c;  // Stockage des 10 octets suivant le 255 dans Trame[j]
    if (j==10) Check(); // vérification validité trame
    j++;   
    if (valide) {
         Traitement();   
         while(millis()-t1<1000) Serial.read();   // on vide le buffer série et on attend une seconde depuis la trame valide
         valide=false;   
        }
      }   


 void Check() {
      for ( l=1;l<10;l++)chk+=Trame[l];  // Calcul du checksum des octets 1 à 9
      chk=chk/2;
      // debug();
      if (chk==Trame[10]){            // Comparaison du checksum avec l'octet 10
        valide=true;
        dt=millis()-t1;
        t1=millis();
        }
      oldchk=chk;
      chk=0;}

Le typon

Pour éviter les faux contacts le jour J nous avons réalisé sur ARES un typon.

Cette carte a les bonnes dimensions pour s'enficher avec un Arduino UNO.

                      

 

 

Le programme démodulation simple

Voici un premier programme démodulation pour fonctionner avec Kicapt uniquement.

L'électronique reçoit  sur les pattes FSK le signal de sortie de notre scanner radio, le XR2211 démodule le signal et le transmet à la patte Rx de l'Arduino Uno.

Attention pour téléverser le programme il faut donc déconnecter la carte d'extension sinon il y a un conflit sur la patte Rx et le programme ne se téléverse pas.

Le programme ci-dessous vérifie la validité des trames reçues et retransmet sur la patte Tx les trames valides répétées 3 fois.

Le programme Kicapt recoit alors ces trames sur le port com de l'Arduino.


// 1.c) Les variables globales
int LED=13;

byte Trame[11];
byte i,j,c,chk;
unsigned long t1;

/* 2) Zone 2 : Initialisation (le setup) */
void setup() {
  // put your setup code here, to run once:
  pinMode(LED,OUTPUT);            // Control de la LED
  Serial.begin(600) ;
}

/* 3) Zone 3 : le Programme Principal */
void loop() {
  if(Serial.available()) {
    c=Serial.read();
    if (c==255) j=0;         // initialisation de j lors de la rencontre d'un 255
    if (j<11) Trame[j]= c;  // Stockage des 10 octets suivant le 255 dans Trame[j]
    if (j==10) Check();     // vérification validité trame
    j++;   
      }           
} 

/* 4) Zone 4 : les sous programmes (ou fonctions) */
 void Check() {
       chk=0;
      for ( i=1;i<10;i++)chk+=Trame[i];  // Calcul du checksum des octets 1 à 9
      chk=chk/2;
      // debug();
      if (chk==Trame[10]){            // Comparaison du checksum avec l'octet 10
        t1=millis();
        digitalWrite(LED,HIGH);
        for (byte trame=0;trame<3;trame++) {                // Pour être compatible avec le logiciel Kicapt
          for(i=0;i<11;i++) Serial.write(Trame [i]);         // On renvoie 3 fois la trame valide reçue
          Serial.flush();                                    // on attend que la trame soit réellement envoyée
          delay(32);                                        // durée environ 615 ms
        }
        while(millis()-t1<1000) Serial.read();            // on vide le buffer série et on attend une seconde depuis la trame valide
        digitalWrite(LED,LOW); 
        }
        }

Notre programme démodulation complet :

Ce programme reprend le même algorithme que précédemment mas en ajoutant de nouvelles fonctions :

  • traiter les trames reçues afin de reconstituer les coordonnées GPS et les mesures effectuées,
  • afficher les coordonnées GPS reçues sur un écran LCD,
  • transmettre à un émetteur bluetooth les coordonnées GPS et les mesures
  • stocker toutes les mesures reçues sur une carte SD.

Il nécessite les bibliothèques I2C (Wire), écran LCD I2C, sérial software et SD. De ce fait il utilise quasiment toute la mémoire d'un Arduino Uno.


/* 1) Zone 1 : les déclarations */
// 1.a) Les bibliothèques et création d'objets  
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>
#include <SD.h>

LiquidCrystal_I2C lcd(0x27,16,4);  //création de l'objet LCD(I2C adresse 0x27) 16 colonnes 4 lignes
int Txd=6, Rxd=7;   // HC05 Bluetooth
SoftwareSerial BT(Txd, Rxd); // RX, TX : il faut relier Rx de l'Arduino au Tx du Bluetooth

// 1.c) Les variables globales
int chipSelect=10;      // chipSelect est la seule patte à déclarer : 
                        // Les autres sont déjà déclarées par la bibliothèque
int LED=13;

unsigned long t0,t1,dt;
word n=0;
byte Trame[11];
float N,E;
word Alti;
word E1,N1,Alti1;
word E2,N2,Alti2;
word AltiOld,NOld,EOld;
byte chk=0,oldchk,voie;
int Pext,L1,A;
int nerreur;
float Tint,Text,Pile;
byte c;
byte i,j,k,l,m;
boolean valide=false, OK=false, silence=true, SDOK;

/* 2) Zone 2 : Initialisation (le setup) */
void setup() {
  // put your setup code here, to run once:
  pinMode(LED,OUTPUT);            // Control de la LED
  lcd.init();                      
  lcd.noBacklight();
  lcd.print("Demodulateur Kiwi");
  Serial.begin(600) ;
  BT.begin(9600);
  BT.println("Demodulateur Kiwi;");
  enteteSD();
}

/* 3) Zone 3 : le Programme Principal */
void loop() {
  if(Serial.available()) {
    c=Serial.read();
    if (c==255) j=0;     // initialisation de j lors de la rencontre d'un 255
    if (j<11) Trame[j]= c;  // Stockage des 10 octets suivant le 255 dans Trame[j]
    if (j==10) Check(); // vérification validité trame
    j++;   
    if (valide) {
         digitalWrite(LED,HIGH);
         Traitement();   
         AfficheLcd();
         ecritureSD();
         while(millis()-t1<1000) Serial.read();   // on vide le buffer série et on attend une seconde depuis la trame valide
         valide=false;   
         digitalWrite(LED,LOW);    
        }
      }      
       if (millis()-t1>3999) erreur();         
     
} 


/* 4) Zone 4 : les sous programmes (ou fonctions) */
 void Check() {
      for ( l=1;l<10;l++)chk+=Trame[l];  // Calcul du checksum des octets 1 à 9
      chk=chk/2;
      // debug();
      if (chk==Trame[10]){            // Comparaison du checksum avec l'octet 10
        valide=true;
        dt=millis()-t1;
        t1=millis();
        }
      oldchk=chk;
      chk=0;}
   
 void Traitement() {
           n++;                // Numéro de trame valide
           nerreur=0;
           if (Trame[9]==254)
              {if (m==4) OK=true;
               m=0;}
            else m++;
            switch (m%5){
              case 1:
                N1=Trame[9]; break; 
              case 2:
                E1=Trame[9]; break; 
              case 3:
                Alti1=Trame[9]; break; 
              case 4:
                Pile=(Trame[9]+14.0)/16.0; break; }
          AltiOld=Alti2;
          NOld=N2;
          EOld=E2;
          Alti2=Trame[1];
          E2=Trame[2];
          N2=Trame[3];
          L1=Trame[4];
          A=Trame[5];
          Tint=(Trame[6]-80.0)/4.0;
          Text=(Trame[7]-150.0)/3.0;
          Pext=Trame[8]*4;
          if (abs(float(Alti2)-float(AltiOld))>100 && m%5!=3) {   // Si brusque variation de Alti1 et Alti2 non transmis
            if (AltiOld>200) Alti1++;                             // Si l'ancien Alti1 > 200 alors on incrémente l'octet de poids fort (Alti2)
            if (AltiOld<100) Alti1--;}                            // Si l'ancien Alti1 < 100 alors on décrémente l'octet de poids fort (Alti2)
          if (abs(float(N2)-float(NOld))>100 && m%5!=1) {
            if (NOld>200) N1++;
            if (NOld<100) N1--;} 
          if (abs(float(E2)-float(EOld))>100 && m%5!=2) {
            if (EOld>200) E1++;
            if (EOld<100) E1--;}              
          Alti=Alti1*256+Alti2;
          N=float(N1*256+N2)/10000.0 + 40.0;
          E=float(E1*256+E2)/10000.0;}
          
 void AfficheLcd() {
   
    lcd.setCursor(0, 0);                               // Envoi des coordonnées sur l'écran LCD
    if (OK) lcd.print("Trame OK num = ");
       else lcd.print("Insuffisant! = ");
    lcd.print(n);
    lcd.print ("  ");         
    lcd.setCursor(0, 1);
    lcd.print("N=");lcd.print(N,4);
    lcd.print(" E=");lcd.print(E,4);
    lcd.setCursor(0, 2);
    lcd.print("Altitude = ");lcd.print(Alti);
    lcd.print (" m  ");
    lcd.setCursor(0, 3);
    lcd.print("Pile = ");lcd.print(Pile); 
    lcd.print (" V  ");
    lcd.setCursor(0, 0); 
    
    if (OK) BT.print("Trame OK");                          // Envoi des coordonnées sur le Bluetooth
       else BT.print("Insuffisant!");                      // Durée approximative : 75 ms d'après l'analyseur logique
    if(!SDOK) BT.print(" SD! ");
    BT.print(";");BT.print(n);BT.print(";");
    BT.print(N,5);BT.print (";");BT.print(E,5);BT.print (";");
    BT.print(Alti);BT.print (";");BT.print(Pext);BT.print (";");
    BT.print(Text,1);BT.print (";");BT.print(Tint,1);BT.print (";");
    BT.print(L1);BT.print (";");BT.print(A);BT.print(";");
    BT.print(Pile);BT.println (";");
    
    for (byte trame=0;trame<3;trame++) {                // Pour être compatible avec le logiciel Kicapt
    for(l=0;l<11;l++) Serial.write(Trame [l]);          // On renvoi 3 fois la trame valide reçue
    Serial.flush();                                     // on attend que la trame soit réellement envoyée
    delay(32);                                          // durée environ 615 ms
    }

    }  
    
void erreur() { 
          dt=millis()-t1;
          t1=millis();
          nerreur=nerreur+2;
          while (Serial.available()) Serial.read();      // on vide le buffer série 
          lcd.setCursor(0, 0);
          lcd.print("Erreur trame = ");lcd.print(n);
          lcd.print ("  "); 
          BT.print("Trame = ");BT.print(n+nerreur);
          BT.print(" Perte = ");BT.print(2*nerreur);BT.print(" s ");
          BT.print("Taux = ");BT.print(100*n/(n+nerreur));BT.print(" % ");
          BT.println(";");
          }
          
void debug() {
          lcd.setCursor(0, 2);
          lcd.print("Chk");lcd.print(chk);
          lcd.setCursor(0, 3);
          lcd.print("Trame");lcd.print(Trame[10]);
          for(l=0;l<11;l++) {
          Serial.print(Trame [l]); Serial.print("\t");
             }
         Serial.print ("Chk=");Serial.println(chk);
         Serial.println(dt);          
}

void enteteSD(){                  // Exemple d'une série de mesure à stocker
// Format csv le séparateur de colonne est : ";"
  pinMode(chipSelect, OUTPUT);
  if (!SD.begin(chipSelect)) {lcd.println("Erreur carte SD"); SDOK=false; return;}
  SDOK=true;
  File GPStab = SD.open("Mesures.csv", FILE_WRITE);
  if (GPStab) {
    GPStab.println("Statut;Trame;Latitude;Longitude;Altitude;Tint;Text;Pression;L1;A;Pile"); 
    GPStab.close();
  }
}

void ecritureSD() { 
  File GPStab = SD.open("Mesures.csv", FILE_WRITE);
  if (GPStab) {
    if (OK) {GPStab.print("OK"); GPStab.print(";");}
    else {GPStab.print("Ins"); GPStab.print(";");}
    GPStab.print(n+nerreur); GPStab.print(";");
    GPStab.print(N,5); GPStab.print(";");
    GPStab.print(E,5); GPStab.print(";");
    GPStab.print(Alti); GPStab.print(";");
    GPStab.print(Tint,1); GPStab.print(";");
    GPStab.print(Text,1); GPStab.print(";");
    GPStab.print(Pext); GPStab.print(";");
    GPStab.print(L1); GPStab.print(";"); 
    GPStab.print(A); GPStab.print(";");     
    GPStab.print(Pile); GPStab.print(";");         
    GPStab.println();          // fin de ligne
    GPStab.close();
    SDOK=true;
  }
  else  {
    SDOK=false;
  }
}