===== TD Bus de communication : Communication infrarouge NEC étendue ===== cours bus de com {{https://bvdp.inetdoc.net/files/iut/cours_bus_terrain_livret_portrait.pdf}} === Objectifs === * Génération des signaux de commandes infrarouge * Compréhension du format de trame NEC étendue * Utilisation de la maquette Arduino * Configuration des périphériques TIMER et PWM ==== Carte d'extension pour le TD/TP === Le shield à utiliser pour ce TP est présenté en [[tdcom2]]. Nous utiliserons une Led infrarouge pour émettre des trames à l'aide de l'Arduino. Une led verte est utilisée sur la maquette pour visualiser à l’œil nu l'activité en émission. ====Analyse==== Tracé de chronogrammes pour le NEC non étendu: http://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol ===Format des trames=== Une trame NEC étendue est constituée d'un train de bits modulé à 38Khz. Il existe 2 types de trames: - les trames complètes - les trames de répétition La trame complète contient: - un entête de trame - une adresse sur 16 bits (codant le numéro de la télécommande), octet de poids fort d'abord et bit de poids faible d'abord - une valeur sur 8 bits (codant le numéro de la touche), bit de poids faible d'abord - une valeur sur 8 bits codant le complément à 1 de la précédente valeur sur 8 bits (redondance pour la détection d'erreur) - l'envoi d'un bit à 1 et un retour à l'état de repos La trame de répétition contient: - un entête de trame - l'envoi d'un bit à 1 et un retour à l'état de repos La durée séparant le début de 2 trames (complète ou de répétition) doit être au moins de 110ms. ===Codage des bits=== Les bits sont codés de la manière suivante: - Le bit "1" est représenté par un train d'impulsion à 38Khz (avec rapport cyclique 50%) pendant 560us suivi par une inactivité. La durée totale du bit est de 2.25ms. - Le bit "0" est représenté par un train d'impulsion à 38Khz (avec rapport cyclique 50%) pendant 560us suivi par une inactivité. La durée totale du bit est de 1.12ms. Les entêtes de trames sont codés de la manière suivante: - La trame complète commence par un train d'impulsion à 38Khz (avec rapport cyclique 50%) pendant 9ms suivi par une inactivité de durée 4.5ms. - La trame de répétition commence par un train d'impulsion à 38Khz (avec rapport cyclique 50%) pendant 9ms suivi par une inactivité de durée 2.25ms. ===Dessin des chronogrammes=== A faire sur papier et à faire noter par l'enseignant: - Dessiner l'allure des chronogrammes pour l'envoi d'une trame complète depuis la télécommande 0x1234 pour la touche 0x56 en faisant apparaître la valeur des différents bits transmis et la correspondance avec les données à transmettre (adresse de A15 à A0, touche de B7 à B0). - Dessiner l'allure des chronogrammes pour l'envoi d'une trame de répétition depuis la télécommande 0x1234 pour la touche 0x56 (attention au piège!). {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} ===Configuration du timer 2 pour la génération du signal modulé à 38Khz=== Consulter la documentation du timer 2 de l'ATMEGA328P: [[https://bvdp.inetdoc.net/files/iut/Atmega328p_timer2.pdf]] L'Atmega328 dispose de 4 timers : - Timer 0 : 8-bit avec PWM - Timer 1 : 16-bit avec PWM - Timer 2/3 : 8-bit avec PWM et possibilité d'horloge externe Dans la suite des exercices nous utiliserons le Timer 2 en mode 7 "FAST PWM" (voir les différents mode en p 155 de la documentation). A l'aide de la documentation, nous allons définir l'algorithme de configuration du timer 2 pour pouvoir générer un signal à 38Khz sur une broche du microcontrôleur sans solliciter le processeur. Le timer 2 peut piloter la broche 3 (numérotation Arduino) du micro-contrôleur. Cette broche peut recopier l'état d'un signal OC0B, que l'on peut décider de commuter à intervalle de temps fixé. La recopie ou non de ce signal sur la broche permet de générer facilement le signal modulé à 38Khz. Pour configurer le timer, les registres TCCR2A et TCCR2B doivent être réglés en "Normal port operation". Pour OC0A en "disconnected", les bits COM2A doivent être réglés à: COM2A=00 Pour OC0B en "disconnected", (initialement le timer ne doit pas générer de signal PWM) les bits COM2B doivent être réglés à: COM2B=00 Le mode de fonctionnement doit être réglé à 7 (voir table 18-8, p155): WGM2 = 111: Fast PWM, TOP=OCRA Pour régler la fréquence à 38Khz, le facteur de prescaling doit être réglé à 1/8 CS2 = 010 : et le registre OCR2A doit être réglé pour obtenir une fréquence du signal à 38Khz: OCR2A= valeur max du timer= ((16.000.000/8)/38.000)-1 Pour régler la valeur du TON pour obtenir un rapport cyclique de 50%: OCR2B=OCR2A/2 Proposer l'algorithme pour la fonction **void setup(void)** qui permet de configurer le timer en réglant les registres TCCR2A, TCCR2B, OCR2A et OCR2B pour qu'il soit prêt à générer le signal à 38Khz. Vous penserez également à configurer la broche en sortie et à lui imposer un état bas. Le signal modulé est obtenu en alternant les mode de fonctionnement "disconnected" et "Clear OC2B on Compare Match, set OC2B at BOTTOM, (non-inverting mode)" pour OC0B. Pour activer le signal PWM en sortie du timer, OC0B devra être réglé en "Clear OC2B on Compare Match, set OC2B at BOTTOM, (non-inverting mode)": COM2B=10 Pour autoriser la sortie PWM sur cette broche, il faut passer le timer en mode 10 en mettant 1 le bit COM2B1: TCCR2A |= _BV(COM2B1); Pour empêcher la sortie PWM sur cette broche, il faut passer le timer en mode 00 en mettant 0 le bit COM2B1: TCCR2A &= ~(_BV(COM2B1)); Dans la suite, pour maîtriser les durées, nous utiliserons la fonction **delayMicroseconds(time);** Les fonctions **void mark(int time)** et **void space(int time)** sont fournies. Elles permettent respectivement d'activer (ou inhiber) le signal à 38 kHz pendant une durée time exprimée en us. //////////////////////////////////////////////////////////////////////// void mark(int time) // pulse parameters in usec { // Sends an IR mark for the specified number of microseconds. // The mark output is modulated at the PWM frequency. TCCR2A |= _BV(COM2B1); // Enable pin 3 PWM output if (time > 0) delayMicroseconds(time); } //////////////////////////////////////////////////////////////////////// void space(int time) // pulse parameters in usec { // Sends an IR space for the specified number of microseconds. // A space is no output, so the PWM output is disabled. TCCR2A &= ~(_BV(COM2B1)); // Disable pin 3 PWM output if (time > 0) delayMicroseconds(time); } //////////////////////////////////////////////////////////////////////// Proposer l'algorithme pour la fonction **void loop(void)** qui permet de piloter le timer pour activer la sortie à 38Khz pendant 200us puis la désactiver pendant 400us. Proposer (en utilisant les fonctions fournies) l'algorithme pour la fonction **void sendNECBYTE(unsigned char data)** qui permet d'envoyer, bit de poids faible d'abord, les 8 bits de data. Proposer l'algorithme pour la fonction **void sendNECFrame(unsigned int adr, unsigned char cmd)** qui permet d'envoyer la trame NEC complète, **adr** étant le numéro de télécommande et **cmd** le numéro de la touche. Proposer l'algorithme pour la fonction **void sendNECFrameRepeat()** qui permet d'envoyer la trame NEC de répétition. {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} ==== Codage et tests ==== Implémenter les fonctions **void setup(void)** et **void loop(void)** pour configurer la sortie PWM à 38Khz. ** Programmer la maquette et vérifier la fréquence du signal obtenu sur la broche 3 à l'aide d'un oscilloscope.** {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Implémenter les fonctions **void sendNECBYTE(unsigned char data)**, **void sendNECFrame(unsigned int adr, unsigned char cmd)** et **void sendNECFrameRepeat()** et proposer un programme principal de test qui permette de balayer plusieurs adresses et numéros de commandes. Vous veillerez à assurer un temps d'attente entre l'émission de 2 trames consécutives pour obtenir au minimum une durée de 110ms entre chaque début de trame (pour cela calculer la durée minimum pour chacune des trame, et ajouter un délai à l'aide de la fonction Delay(time) ) . {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Programmer la carte Arduino et fermer le cavalier permettant de relier les Leds à la broche 3 de l'Arduino. Utiliser une caméra de téléphone portable pour visualiser l'activité de la Led infrarouge et demander à l'enseignant de valider avec le récepteur/démodulateur infrarouge. {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Solution: //pin3-> timer 2 #define TIMER_PWM_PIN 3 // main Arduino clock = 16000000Hz //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// inline void TimerEnablePWM() { TCCR2A |= _BV(COM2B1); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// inline void TimerDisablePWM() { TCCR2A &= ~(_BV(COM2B1)); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void mark(int time) // pulse parameters in usec { // Sends an IR mark for the specified number of microseconds. // The mark output is modulated at the PWM frequency. TimerEnablePWM(); // Enable pin 3 PWM output if (time > 0) delayMicroseconds(time); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void space(int time) { // Sends an IR space for the specified number of microseconds. // A space is no output, so the PWM output is disabled. TimerDisablePWM(); // Disable pin 3 PWM output if (time > 0) delayMicroseconds(time); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// inline void sendNECBYTE(unsigned char data) { for (int i = 0; i < 8; i++) { if (data & 1) { mark(560); space(1690); } else { mark(560); space(560); } data >>= 1; } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void sendNECFrame(unsigned int adr, unsigned char cmd) { mark(9000); space(4500); sendNECBYTE(adr >> 8); sendNECBYTE(adr & 0xff); sendNECBYTE(cmd); sendNECBYTE(~cmd); mark(560); space(560); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void sendNECFrameRepeat() { mark(9000); space(2250); mark(560); space(560); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void setup(void) { Serial.begin(9600); //configuration signal à 38Khz // COM2A = 00: disconnect OC2A // COM2B = 00: disconnect OC2B; to send signal set to 10: OC2B non-inverted // WGM2 = 111: FAST PWM, TOP=OCRA, // CS2 = 010: prescaling 1/8 //periode= (16.000.000/8) /38.000= 52.6 OCR2A = 51; //periode -1 OCR2B = 25; //TON // mode =7 et prédiviseur à 8 TCCR2A = _BV(WGM21) | _BV(WGM20) ; TCCR2B = _BV(CS21) | _BV(WGM22) ; pinMode(TIMER_PWM_PIN, OUTPUT); digitalWrite(TIMER_PWM_PIN, LOW); // When not sending PWM, we want it low //pour le 1° exercice, vérifier que l'on a bien du 38Khz sur la pin 3 //commutation de la broche OC2B à chaque compare match entre le timer et le contenu de OC2R2B // TCCR2A |= _BV(COM2B1); // while(1); } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// void loop(void) { unsigned int adr = 0x1234; static unsigned char cmd = 0x56; sendNECFrame(adr, cmd); cmd++; delay(20); //le délai est obligatoire pour que le récepteur puisse raccrocher sur la trame suivante sendNECFrameRepeat(); delay(100); //le délai est obligatoire pour que le récepteur puisse raccrocher sur la trame suivante } ==== Réception/décodage==== Lire la documentation du récepteur VISHAY - TSOP2238 - RECEPTEUR IR 38KHZ , ref farnell 4913073 datasheet: http://www.farnell.com/datasheets/30485.pdf Lire et interpréter le fichier suivant: {{https://bvdp.inetdoc.net/files/iut/tp_ir/maeNECPIC2.pdf}} Proposer un portage de cette fonctionnalité sur l'Arduino UNO R3. Solution: //B.Vandeportaele 2018 #define PIN_RX_IR 8 #define PIN_DEBUG_IR 13 #define PINRX (PINB & 0x1) #define PORTDEBUG1 PORTB |=0x20; #define PORTDEBUG0 PORTB &= ~0x20; //////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////// Function to print in hexadecimal with a known number of digits void printHex(int num, int precision) { char tmp[16]; char format[128]; sprintf(format, "%%.%dX", precision); sprintf(tmp, format, num); Serial.print(tmp); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// inline void AttenteBas() { while ( (PINRX) == 0); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// inline void AttenteHaut() { while ( (PINRX) != 0); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// inline bool DetecteBas() { return (digitalRead(PIN_RX_IR) == 0); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// inline bool DetecteHaut() { return (digitalRead(PIN_RX_IR) != 0); } //////////////////////////////////////////////////////////////////////////////////////////////////////////// void setup() { pinMode(PIN_DEBUG_IR, OUTPUT); pinMode(PIN_RX_IR, INPUT); digitalWrite(PIN_RX_IR, LOW); Serial.begin(9600); // The string in Flash Serial.print( F(",Filename: ")); Serial.println( F(__FILE__)); Serial.print( F("Compiled: ")); Serial.print( F(__DATE__)); Serial.print( F(", ")); Serial.println( F(__TIME__)); Serial.print( F("GCC:")); Serial.println( F(__VERSION__)); // -- compiler version as a string Serial.print(F( "Arduino IDE version: ")); Serial.println( ARDUINO, DEC); /* while(1) { PORTDEBUG0 delay(10); PORTDEBUG1 delay(10); } */ } //////////////////////////////////////////////////////////////////////////////////////////////////////////// unsigned int temps = 0; unsigned int temps2 = 0; unsigned int deltat = 0; unsigned char etat = 0; unsigned char error; unsigned char octets[4] = {0, 0, 0, 0}; unsigned int repeat = 0; unsigned char cptbit = 0; unsigned char bytes[4]; //////////////////////////////////////////////////////////////////////////////////////////////////////////// void loop() { // mesure durée à l'état haut, précis à 8us près... /* AttenteBas(); temps = micros(); AttenteHaut(); temps2 = micros(); deltat=temps2 - temps; Serial.println(deltat, DEC); if ( (deltat>4000) && (deltat<5000)) Serial.println("normal"); if ( (deltat>2000) && (deltat<2500)) Serial.println("repeat"); delay(100); AttenteHaut(); } */ switch (etat) { case 0: PORTDEBUG1 if (DetecteBas()) { temps = micros(); etat = 1; error = 0; } break; case 1: PORTDEBUG0 if (DetecteHaut()) { temps2 = micros(); deltat = temps2 - temps; temps = temps2; if ( (deltat > 8000) && (deltat < 9000)) { etat = 2; } else etat = 0; // error = 1; } break; case 2: PORTDEBUG1 if (DetecteBas()) { temps2 = micros(); deltat = temps2 - temps; temps = temps2; //Serial.println(deltat, DEC); if ( (deltat > 4000) && (deltat < 5000)) { etat = 3; repeat = 0; cptbit = 0; bytes[0] = 0; bytes[1] = 0; bytes[2] = 0; bytes[3] = 0; // Serial.println("repeat"); } else if ( (deltat > 2000) && (deltat < 2500)) { etat = 0; repeat++; Serial.print("+"); // Serial.println("repeat"); } break; case 3: PORTDEBUG0 if (DetecteHaut()) { temps2 = micros(); deltat = temps2 - temps; temps = temps2; // Serial.println(deltat, DEC); // delay(100); if (cptbit < 32) etat = 4; else { etat = 0; if (bytes[2] = ~bytes[3]) { Serial.print("="); printHex(bytes[0],2); // Serial.print(bytes[0], HEX); Serial.print(" "); printHex(bytes[1],2); // Serial.print(bytes[1], HEX); Serial.print(" "); printHex(bytes[2],2); // Serial.print(bytes[2], HEX); Serial.print(" "); /* Serial.print("Trame complete:"); Serial.print(bytes[0], HEX); Serial.print(","); Serial.print(bytes[1], HEX); Serial.print(","); Serial.print(bytes[2], HEX); Serial.print(","); Serial.println(bytes[3], HEX); */ } } } break; case 4: PORTDEBUG1 if (DetecteBas() ) { temps2 = micros(); deltat = temps2 - temps; temps = temps2; // Serial.println(deltat, DEC); // delay(100); if (deltat > 800) bytes[cptbit >> 3] |= (1 << (cptbit & 7)); etat = 3; cptbit++; // if (deltat > 2000) } break; } } } ==== Utilisation d'une librarie ==== ==== infrarouge==== protocole: http://www.circuitvalley.com/2013/09/nec-protocol-ir-infrared-remote-control.html arduino: http://www.righto.com/2009/08/multi-protocol-infrared-remote-library.html Hardware setup The library can use ANY of the digital input signals to receive the input from a 38KHz IR receiver module. It has been tested with the Radio Shack 276-640 IR receiver and the Panasonic PNA4602. Simply wire power to pin 1, ground to pin 2, and the pin 3 output to an Arduino digital input pin, e.g. 11. These receivers provide a filtered and demodulated inverted logic level output; you can't just use a photodiode or phototransistor. I have found these detectors have pretty good range and easily work across a room. led emettrice à connecter à PIN 3 (PWM) https://www.pjrc.com/teensy/td_libs_IRremote.html https://gist.github.com/EEVblog/6206934 http://forum.arduino.cc/index.php?topic=18230.0 http://www.build-electronic-circuits.com/arduino-remote-control/ ressources: http://techdocs.altium.com/display/FPGA/NEC+Infrared+Transmission+Protocol http://www.sbprojects.com/knowledge/ir/nec.php il faut du 950nm (j'en ai 10): http://forums.futura-sciences.com/electronique/83501-led-ir.html fonctionne uniquement en alimentation 5V à terme prévoir alim en 5V et signal en sortie en 3.3 ou 5V (via vin et circuit mise à niveau) =====install librairie==== wget https://github.com/shirriff/Arduino-IRremote/archive/master.zip dezipper dans /usr/share/arduino/libraries/IRremote #include IRsend irsend; void setup() {} byte i=0; void loop() { irsend.sendNEC(0xa90, i); i++; delay(200); } #include int RECV_PIN = A0; int VCC_PIN = A1 ; int GND_PIN = A2 ; IRrecv irrecv(RECV_PIN); decode_results results; void setup() { Serial.begin(9600); pinMode(VCC_PIN, OUTPUT); pinMode(GND_PIN, OUTPUT); digitalWrite(VCC_PIN, HIGH); digitalWrite(GND_PIN, LOW); irrecv.enableIRIn(); // Start the receiver } void loop() { if (irrecv.decode(&results)) { Serial.println(results.value, HEX); irrecv.resume(); // Receive the next value } delay(100); } ===== solution sous linux avec port série===== http://www.lirc.org/ http://www.lirc.org/receivers.html