Récepteur infrarouge RC5
From Eric
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).
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 :