Récepteur infrarouge RC5

From Eric

Jump to: navigation, search

L'un de mes premiers montage à AVR fut un récepteur infra-rouge RC5.

A l'époque, j'avais opté pour un traitement combinant interruptions et timers :

  • le premier bit état traité par une interruption externe sur le signal RC5 afin de déterminer la durée d'un "petit" créneau ;
  • les autres bits étaient traité par une interruption issue d'un timer dont la durée était déterminée lors de la première étape ci-dessus.

Ca fonctionnait, mais

  • je n'avais pas traité la robustesse (que se passe-t-il si on a perdu un bit en cours de route?)
  • le traitement nécessitait une temporisation de bonne qualité.

J'ai repris en code afin que l'échantillonnage des bits repose (quasi-)uniquement sur l'interruption externe.

La structure d'un trame RC5

Les schémas suivants sont issus du document rc5.pdf réalisé par Daniel Menesplier (que je remercie).

Rc5 timing.jpg


Rc5 frame.jpg


Le logiciel

L'idée consiste à distinguer un créneau court d'un créneau long et à utiliser cette information pour détarminer la valeur du bit n+1 sachant la valeur du bit n.


Prenons la trame suivante (qui début par deux bits à un, comme toute trame RC5).

  ---+  +--+     +--+  +--+--+
     |  |  |     |  |  |     |
     +--+  +--+--+  +--+     +--+
   <---> <---> <---> <---> <--->    
 
     1     1     0     0     1

On constate qu'il y a un changement de valeur du bit n au bit n+1 (inversion) si le créneau est "long" ; sinon, la valeur est conservée.

L'idée est donc de mesurer la largeur d'un "petit" créneau (que l'on sait arriver en début de trame) puis à déterminer, lors de (presque) chaque front si on a affaire à un créneau long ou court.

Le code de la machine à états est donné ci-après (j'ai laissé d'horrible constantes... désolé...).:

 /* -------------------------------------------------------------
  * Handler d'interruption INT1, utilisé pour l'IR, protocole RC5
  * -------------------------------------------------------------
  *                     
  *          t0   t1                   v 
  *  -----+   +---+   +---+       +---+---+
  *       |   |   |   | . |       |     . |
  *       |   |   |   | . |       |     . |
  *       +---+   +---+ . +---+---+     . +---
  *      start. start   . .     . .     .
  Un bit dure 2x889us
  Une trame dure 14x1778us = 24892us
  Il y a au moins 88886us entre deux trames
  */
 
 typedef enum { K_RC5_WAIT_START, K_RC5_WAIT_FIRST_EDGE, K_RC5_WAIT_NEXT_EDGE, K_RC5_WAIT_NEXT_EDGE_AND_SKIP } rc5_int1_state_t;
 ISR(INT1_vect)
 { 
   static uint8_t state = K_RC5_WAIT_START;
   static uint8_t ctr, previous_bit;
   static uint16_t tmp_code; 
   static uint16_t t, previous_t=0, threshold;
   uint16_t dt;
 
   // Acquérir le temps courant
   t = TCNT1;
   
   // Calculer la durée entre deux fronts 
   if (ISBITSET(TIFR,TOV1))
     dt = K_MAX_DT;
   else
     dt = t - previous_t;
   
   // On est dans l'état d'attente d'un début de trame
   if ( state == K_RC5_WAIT_START) 
   {      
     // Ce front arrive-t-il après la période de silence inter-trame?
     if ( dt > 10000 ) 
     {
       // OUI : La trame est arrivée après le temps de silence...
       // Effacer le bit d'overflow (au cas où...)
       SETBIT(TIFR,TOV1);
   
       // Réinitialiser le timer
       TCNT1 = 0;
       
       // Fixer l'origine des temps
       t = 0;
       
       state =K_RC5_WAIT_FIRST_EDGE;
     }
     else
     {
       // NON : Le front n'est pas arrivée après le temps de silence...
       // On l'ignore simplement...
     }        
   
   }
   else
   if ( dt > 3000 )
   {
     state = K_RC5_WAIT_START;
   }
   else
   if ( state == K_RC5_WAIT_FIRST_EDGE)
   { 
     threshold = dt+(dt>>1);
       
     // Le premier bit (ignoré) vaut 1
     previous_bit = 1;
       
     // Initialiser le compteur de bits
     ctr = 0;
 
     tmp_code = 0;
       
     previous_t = t;
       
     state = K_RC5_WAIT_NEXT_EDGE_AND_SKIP;
   }
   else
   if ( state == K_RC5_WAIT_NEXT_EDGE)
   {
   
     // Est-ce un délai long?
     if ( dt  > threshold )
     {
       // C'est un délai long : il y a inversion des deux bits successifs
       previous_bit = 1-previous_bit;  
       // On reste dans cet état
 
     }
     else
     {
       // C'est un délai court : c'est le même bit ; on ignore le prochain front
       state = K_RC5_WAIT_NEXT_EDGE_AND_SKIP;  
     }
                 
     // On stocke le bit
     tmp_code <<= 1;
     tmp_code |= previous_bit;
 
     // On met à jour le compteur.
     ctr++;
 
     // A-t-on reçu tous les bits?
     if ( ctr == 12 )
     {
       // Oui, on stocke le code (si on peut...)
       if ( !queue_is_full(&rc5_queue))
         queue_put(&rc5_queue, tmp_code & 0xFF);
         
       // Et on réinitialise la machine.
       state = K_RC5_WAIT_START;    
     }
   }
   else
   if ( state == K_RC5_WAIT_NEXT_EDGE_AND_SKIP)
   {
       state = K_RC5_WAIT_NEXT_EDGE;    
   }
   else
   {
     // C'est un cas d'erreur...
   }
   
   previous_t = t;
 
 }
 
 // Un message n'est validé que s'il apparait plusieurs fois successivement.
 // Idéalement, il faudrait réinitialiser le compteur de matching tous les x appels
 // sans code <todo>
 bool_t rc5_get_filtered ( uint8_t *code )
 {
   static uint8_t prev_code=0, match_ctr=0;
   uint8_t new_code=0;
 
   // Y a-t-il un message dans la file?
   if ( !rc5_queue_is_empty() )
   {
     // OUI : on le récupère
     new_code = rc5_queue_get();
     // S'agit-il du même code que précédemment?
     if ( new_code == prev_code )
     {
       // OUI : On incrémente le compteur.
       match_ctr++;
       // Le compteur a-t-il atteint le seuil de validation.
       if ( match_ctr == 1 )
       {
         match_ctr=0;
         *code = new_code;
         return TRUE;
       }
     }
     else
     {
       prev_code = new_code;
       match_ctr = 0;
     }
   }
     
   return FALSE;
 }

On notera que lorsqu'une trame est reçue, elle est stockée dans une FIFO. Cette FIFO est utilsée pour communiquer de façon asynchrone avec les autres parties de l'application. Par ailleurs, une commande RC5 n'est validée que la trame qui représente la commande est reçue 2 ou trois fois.

Le matériel

Du point de vue matérel, le montage ne présente aucun intérêt ou difficulté : le capteur RC5 est simplement câblé sur l'entrée INT1 de l'ATMEGA 8.

Sur une plaquette d'expérimentation, ça donne la chose suivante :

Recepteur rc5.jpg
Personal tools