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.
L'idée consiste à distinguer un créneau court d'un créneau long.
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, last_bit; static uint16_t tmp_code; static uint16_t t, last_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 - last_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 last_bit = 1; // Initialiser le compteur de bits ctr = 0; tmp_code = 0; last_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 last_bit = 1-last_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 |= last_bit; 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... } last_t = t; }