===== TP Projet Objets connectés: Bus I2C sur Arduino ===== cours bus de com {{https://bvdp.inetdoc.net/files/iut/cours_bus_terrain_livret_portrait.pdf}} chronogrammes solutions sur: [[tpi2cobjetsconnectessoluce]] Utilitaire wget pour windows: https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/wget.exe Programmes demo PCF8574 compilé: https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/demo_8574_IUT.ino.hex Programmes demo DS1307 compilé: https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/demo_DS1307_IUT.ino.hex === Objectifs === * Comprendre les chronogrammes des transactions I2C * Comprendre l'énumération des composants sur un bus I2C * Comprendre les notions d'adressage, (sur le bus et dans le composant) ==== Carte d'extension pour le TP === {{https://bvdp.inetdoc.net/files/iut/td2_capt//CARTECOM.png}} Schéma de la carte principale: {{https://bvdp.inetdoc.net/files/iut/td2_capt/schemaCom.png}} Schéma de la carte amovible: {{https://bvdp.inetdoc.net/files/iut/td2_capt/moduleRTC.jpg}} fichiers eagle: https://bvdp.inetdoc.net/files/iut/td2_capt/carte-arduino-8574-12.sch et https://bvdp.inetdoc.net/files/iut/td2_capt/carte-arduino-8574-12.brd La carte d'extension utilisée pour ce TD/TP permet de connecter plusieurs périphériques à l'Arduino. Elle dispose: - de deux port E/S sur bus I2C dont un est piloté par des interrupteurs et l'autre pilote des leds. Le composant utilisé est un circuit PCF 8574A, dont la documentation est à l'adresse: https://bvdp.inetdoc.net/files/iut/td2_capt/pcf8574.pdf . L'adresse de chacun de ces composants est en partie configurable via 3 cavaliers dont la fermeture revient à imposer un 0 logique sur le bit correspondant. - d'une EEPROM SPI AT25040 (documentation: http://www.atmel.com/images/doc3348.pdf ) qui sera utilisée dans un prochain TP concernant le bus SPI: https://bvdp.inetdoc.net/wiki/doku.php?id=tdethindus1 - d'une led infrarouge pour assurer la fonction de télécommande - d'un module amovible assurant la fonction de RTC via le composant DS1307 (documentation: http://datasheets.maximintegrated.com/en/ds/DS1307.pdf ) et EEPROM I2C AT24C32N (documentation: http://www.atmel.com/images/doc0336.pdf ). Une sonde de températeur DS18B20 sur bus OneWire est également présente (documentation: http://datasheets.maximintegrated.com/en/ds/DS18B20.pdf ) et sera utilisée dans un prochain TP concernant le bus OneWire: https://bvdp.inetdoc.net/wiki/doku.php?id=tp_one_wire ===== Exercice 1 : Détermination des adresses des esclaves I2C ===== Lancer l'IDE Arduino, brancher la carte au port USB du PC et cliquer sur Outils->Port. Sélectionner le dernier de la liste et noter son numéro. {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 1.1: Programmer l'Arduino avec le sketch suivant et ouvrir la console arduino en 9600 8N1. (sketch récupéré sur http://playground.arduino.cc/Main/I2cScanner ) // -------------------------------------- // i2c_scanner // // Version 1 // This program (or code that looks like it) // can be found in many places. // For example on the Arduino.cc forum. // The original author is not know. // Version 2, Juni 2012, Using Arduino 1.0.1 // Adapted to be as simple as possible by Arduino.cc user Krodal // Version 3, Feb 26 2013 // V3 by louarnold // Version 4, March 3, 2013, Using Arduino 1.0.3 // by Arduino.cc user Krodal. // Changes by louarnold removed. // Scanning addresses changed from 0...127 to 1...119, // according to the i2c scanner by Nick Gammon // http://www.gammon.com.au/forum/?id=10896 // Version 5, March 28, 2013 // As version 4, but address scans now to 127. // A sensor seems to use address 120. // // // This sketch tests the standard 7-bit addresses // Devices with higher bit address might not be seen properly. // #include void setup() { Wire.begin(); Serial.begin(9600); Serial.println("\nI2C Scanner"); } void loop() { byte error, address; int nDevices; Serial.println("Scanning..."); nDevices = 0; for(address = 1; address < 127; address++ ) { // The i2c_scanner uses the return value of // the Write.endTransmisstion to see if // a device did acknowledge to the address. Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address<16) Serial.print("0"); Serial.print(address,HEX); Serial.println(" !"); nDevices++; } else if (error==4) { Serial.print("Unknow error at address 0x"); if (address<16) Serial.print("0"); Serial.println(address,HEX); } } if (nDevices == 0) Serial.println("No I2C devices found\n"); else Serial.println("done\n"); delay(5000); // wait 5 seconds for next scan } Vous devriez obtenir un affichage ressemblant à: {{https://bvdp.inetdoc.net/files/iut/td2_capt/scanI2C.png}} {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 1.2: Débrancher et rebrancher le module amovible RTC et interpréter le résultat. Jouez sur les cavaliers permettant de configurer l'adresse matérielle des 8574. En déduire l'adresse de tous les composants que vous allez utiliser. ===== Exercice 2 : Utilisation du PCF8574 ===== {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 2.1: Positionner les cavaliers pour attribuer l'adresse 0x3e au composant 8574_A et 0x3f au composant 8574_B. Pour cela vous devez déterminer la valeur des 3 bits de poids faible des adresses et les régler, un cavalier ouvert permet de régler un bit à 1 et un cavalier fermé à 0. Vérifier les adresses à l'aide du programme de l'exercice 1 puis programmer l'Arduino avec le sketch suivant: #include //adresses codées sur 7bits #define SLAVE_ADDR_8574_A 0x3e #define SLAVE_ADDR_8574_B 0x3f ////////////////////////////////////////// char readPort8574(char addr, char * ptr_value) /*addr, l'adresse du PCF8574 ptr_value, pointeur pour renvoyer la valeur lue sur le port retourne -1 si échec 0 sinon*/ { Wire.requestFrom((byte)addr, (byte)1);// demande la lecture d'1 octet depuis l'adresse du pérpiphérique if (Wire.available()==1) //si l'octet est disponible { (* ptr_value) = Wire.read(); // lire l'octet return 0; } else { (* ptr_value) =0; //valeur par défaut si le composant n'a pas acquité return -1; } } ////////////////////////////////////////// char writePort8574(char addr, char value) /*addr, l'adresse du PCF8574 value, la valeur à écrire sur le port retourne -1 si échec 0 sinon */ { Wire.beginTransmission((byte)addr);//démarre la transmission avec l'adresse du pérpiphérique Wire.write((byte)value); //envoie la donnée if (Wire.endTransmission()==0) //stoppe la transmission return 0; else return -1; } ////////////////////////////////////////// void setup() { Serial.begin(9600); // start serial port at 9600 bps: Serial.print("Bonjour"); Wire.begin(); // joindre le bus i2c en tant que maître writePort8574( SLAVE_ADDR_8574_B , 0xff); //configure le composant B en entrée } ////////////////////////////////////////// ////////////////////////////////////////// void loop() { writePort8574(SLAVE_ADDR_8574_A, 0x12); delay(1000); writePort8574(SLAVE_ADDR_8574_A, ~0x12); delay(1000); } {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 2.2: Relever les chronogrammes des signaux SDA et SCL. Faites une capture d'écran et insérez-la dans le bloc note. Faire apparaître sur les chronogrammes relevés les conditions de start et stop et les différentes phases (adressage et échange de données) ainsi que les bits transmis en indiquant quel composant pilote le bus à chaque instant. Déterminer la fréquence du signal d'horloge du bus I2C. Chronogramme Ecriture à l'adresse 0x3E avec acquittement: {{https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/scope_1.png}} {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 2.3: Retirer le cavalier A0 du PCF8574_A et relever à nouveau les chronogrammes des signaux SDA et SCL. Faites une capture d'écran et insérez-la dans le bloc note. Quelles sont les différences avec 2.2. Pourquoi ? Chronogramme Ecriture à l'adresse 0x3E sans acquittement: {{https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/scope_2.png}} {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 2.4: Remettre le cavalier A0 du PCF8574_A et modifier le programme pour réaliser la recopie du port d'entrée PCF8574_B vers le port de sortie PCF8574_A toutes les 100ms. Dans le cas où la lecture n'est pas possible (la fonction **readPort8574()** retourne -1), il ne faudra pas appeler l'écriture sur le port de sortie. Programmer l'Arduino et vérifier que les interrupteurs permettent de piloter les LEDs. Programme compilé pour les étudiants: #include //adresses codées sur 7bits #define SLAVE_ADDR_8574_A 0x38+6 #define SLAVE_ADDR_8574_B 0x38+7 ////////////////////////////////////////// char readPort8574(char addr, char * ptr_value) /*addr, l'adresse du PCF8574 ptr_value, pointeur pour renvoyer la valeur lue sur le port retourne -1 si échec 0 sinon*/ { Wire.requestFrom((byte)addr, (byte)1);// demande la lecture d'1 octet depuis l'adresse du pérpiphérique if (Wire.available()==1) //si l'octet est disponible { (* ptr_value) = Wire.read(); // lire l'octet return 0; } else { (* ptr_value) =0; //valeur par défaut si le composant n'a pas acquité return -1; } } ////////////////////////////////////////// char writePort8574(char addr, char value) /*addr, l'adresse du PCF8574 value, la valeur à écrire sur le port retourne -1 si échec 0 sinon */ { Wire.beginTransmission(addr);//démarre la transmission avec l'adresse du périphérique Wire.write((byte)value); //envoie la donnée if (Wire.endTransmission()==0) //stoppe la transmission return 0; else return -1; } ////////////////////////////////////////// void setup() { Serial.begin(9600); // start serial port at 9600 bps: Serial.print("Bonjour"); Wire.begin(); // joindre le bus i2c en tant que maître writePort8574( SLAVE_ADDR_8574_B , 0xff); //configure le composant B en entrée } ////////////////////////////////////////// void loop() { char val; if (readPort8574(SLAVE_ADDR_8574_B,&val)==0) writePort8574(SLAVE_ADDR_8574_A,val); delay(100); } cd ..\AppData\Local\Temp\arduino_build_123575 copy sketch_mar15a.ino.hex S:\commun\2EN\Projet_IOT\demo_8574_IUT.ino.hex Dans le cas où vous n'arriveriez pas à modifier le programme par vous même vous pourrez programmer l'Arduino directement en faisant: Si vous travaillez sous Windows (c'est dommage pour vous): * Dans Arduino, relever le nom du port série utilisé par la carte (Menu Outils->Port Série) * Taper sur "touche Windows"+R puis taper "cmd" et entrée. * Copier coller dans la console Windows en remplaçant COM6 par le nom du port série relevé précédemment: set PORT="COM6" * Copier coller la ligne suivante dans la console Windows: C:\applis\arduino-1.8.7\hardware\tools\avr/bin/avrdude -CC:\applis\arduino-1.8.7\hardware\tools\avr/etc/avrdude.conf -v -patmega328p -carduino -P%PORT% -b115200 -D -Uflash:w:S:\commun\2EN\Projet_IOT\demo_8574_IUT.ino.hex:i Si vous travaillez sous Linux (vous avez bien raison): * Copier coller la ligne suivante dans la console Linux: cd ~/ && wget https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/demo_8574_IUT.ino.hex && /usr/local/share/arduino-1.8.9/hardware/tools/avr/bin/avrdude -C/usr/local/share/arduino-1.8.9/hardware/tools/avr/etc/avrdude.conf -v -patmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:demo_8574_IUT.ino.hex:i ou cd ~/ && wget https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/demo_8574_IUT.ino.hex && /usr/local/share/arduino-1.8.9/hardware/tools/avr/bin/avrdude -C/usr/local/share/arduino-1.8.9/hardware/tools/avr/etc/avrdude.conf -v -patmega328p -carduino -P/dev/ttyUSB0 -b115200 -D -Uflash:w:demo_8574_IUT.ino.hex:i {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 2.5: Positionner les interrupteurs du ports d'entrée dans une configuration de votre choix (mais tous les bits ne doivent pas être au même état) et relever les chronogrammes des signaux SDA et SCL. Faites une capture d'écran et insérez-la dans le bloc note. Identifier sur le chronogramme les différentes transactions I2C en indiquant leurs directions. Vérifier que les chronogrammes changent en fonction de la position des interrupteurs. Chronogramme Lecture à l'adresse 0x3F puis écriture à l'adresse 0x3E, 2 combinaisons différentes pour les interrupteurs d'entrée: {{https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/scope_3.png}} {{https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/scope_4.png}} Chronogramme Lecture à l'adresse 0x3F puis écriture à l'adresse 0x3E, mais adresse du composant PCF8574 en écriture changée avec les cavaliers: {{https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/scope_5.png}} Chronogramme Lecture à l'adresse 0x3F, mais adresse du composant PCF8574 en lecture changée avec les cavaliers: {{https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/scope_6.png}} ===== Exercice 3 : Utilisation du DS1307 ===== {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 3.1: Programmez l'Arduino avec le programme fourni en copiant collant la ligne suivante: Si vous travaillez sous Windows (c'est dommage pour vous): C:\applis\arduino-1.8.7\hardware\tools\avr/bin/avrdude -CC:\applis\arduino-1.8.7\hardware\tools\avr/etc/avrdude.conf -v -patmega328p -carduino -P%PORT% -b115200 -D -Uflash:w:S:\commun\2EN\Projet_IOT\demo_DS1307_IUT.ino.hex:i Si vous travaillez sous Linux (vous avez bien raison): * Copier coller la ligne suivante dans la console Linux: cd ~/ && wget https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/demo_DS1307_IUT.ino.hex && /usr/local/share/arduino-1.8.9/hardware/tools/avr/bin/avrdude -C/usr/local/share/arduino-1.8.9/hardware/tools/avr/etc/avrdude.conf -v -patmega328p -carduino -P/dev/ttyUSB0 -b115200 -D -Uflash:w:demo_DS1307_IUT.ino.hex:i ou cd ~/ && wget https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/demo_DS1307_IUT.ino.hex && /usr/local/share/arduino-1.8.9/hardware/tools/avr/bin/avrdude -C/usr/local/share/arduino-1.8.9/hardware/tools/avr/etc/avrdude.conf -v -patmega328p -carduino -P/dev/ttyACM0 -b115200 -D -Uflash:w:demo_DS1307_IUT.ino.hex:i {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 3.2: Le module RTC étant connecté à la carte, relever les chronogrammes des signaux SDA et SCL. Faites une capture d'écran et insérez-la dans le bloc note. Interpréter les chronogrammes. Identifier sur le chronogramme les différentes transactions I2C en indiquant leurs directions et interpréter les chronogrammes. Chronogramme de 2 transactions pour la lecture du registre 0 du DS13072: transaction en écriture pour indiquer l'adresse du registre puis transaction en lecture pour récupérer la valeur contenue dans ce registre: {{https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/scope_7.png}} {{https://bvdp.inetdoc.net/files/iut/tp_tns/TODO.jpg}} 3.3: Déconnecter le module RTC et relever les chronogrammes des signaux SDA et SCL. Faites une capture d'écran et insérez-la dans le bloc note. Interpréter les chronogrammes. Chronogramme de la lecture du registre 0 du DS13072, mais composant DS13072 retiré: la transaction en écriture pour indiquer l'adresse du registre échoue: {{https://bvdp.inetdoc.net/files/iut/tpi2cobjetsconnectes/scope_8.png}} copy sketch_mar15b.ino.hex S:\commun\2EN\Projet_IOT\demo_DS1307_IUT.ino.hex Programme compilé pour les étudiants: #include //adresses codées sur 7bits #define SLAVE_ADDR_DS13072 0x68 ////////////////////////////////////////// char readRegI2C(char I2Caddr, byte Regaddr, byte * ptr_value) /*I2Caddr, l'adresse du composant sur le bus Regaddr, l'adresse du registre à lire ptr_value, pointeur pour renvoyer la valeur lue dans le registre retourne -1 si échec 0 sinon */ { Wire.beginTransmission(I2Caddr);//démarre la transmission avec l'adresse du pérpiphérique Wire.write((byte)Regaddr); //envoie l'adresse de la case if (Wire.endTransmission()!=0) //stoppe la transmission return -1; Wire.requestFrom((byte)I2Caddr,(byte) 1);// demande la lecture d'1 octet depuis l'adresse du pérpiphérique if (Wire.available()==1) //si l'octet est disponible { (* ptr_value) = Wire.read(); // lire l'octet return 0; } else return -1; } ////////////////////////////////////////// char writeRegI2C(char I2Caddr, byte Regaddr, byte value) /* I2Caddr, l'adresse du composant sur le bus Regaddr, l'adresse du registre à écrire value,la valeur à écrire dans le registre retourne -1 si échec 0 sinon */ { Wire.beginTransmission(I2Caddr);//démarre la transmission avec l'adresse du pérpiphérique Wire.write((byte)Regaddr); //envoie l'adresse de la case Wire.write((byte)value); //envoie la donnée if (Wire.endTransmission()==0) //stoppe la transmission return 0; else return -1; } ////////////////////////////////////////// void displayTime() { byte val; /* if (readRegI2C(SLAVE_ADDR_DS13072, 2, &val)==0) { Serial.print(val>>4, HEX); Serial.print(val&0xf, HEX); Serial.print(":"); } if (readRegI2C(SLAVE_ADDR_DS13072, 1, &val)==0) { Serial.print(val>>4, HEX); Serial.print(val&0xf, HEX); Serial.print(":"); } */ if (readRegI2C(SLAVE_ADDR_DS13072, 0, &val)==0) { Serial.print(val>>4, HEX); Serial.print(val&0xf, HEX); digitalWrite(3,val&1); //clignotement LED IR à 0.5Hz Serial.println(" sec"); } } ////////////////////////////////////////// void displayDate() { byte val; /*if (readRegI2C(SLAVE_ADDR_DS13072, 4, &val)==0) { Serial.print(val>>4, HEX); Serial.print(val&0xf, HEX); Serial.print("/"); } /* if (readRegI2C(SLAVE_ADDR_DS13072, 5, &val)==0) { Serial.print(val>>4, HEX); Serial.print(val&0xf, HEX); Serial.print("/"); } if (readRegI2C(SLAVE_ADDR_DS13072, 6, &val)==0) { Serial.print(val>>4, HEX); Serial.print(val&0xf, HEX); Serial.println(""); }*/ } ////////////////////////////////////////// void dumpDS13072() { //A COMPLETER pour lire toutes les cases mémoire et afficher leurs adresses et contenus byte val,add, col; col=1; for(add=0x00 ; add<=0x3f; add++) { if (readRegI2C(SLAVE_ADDR_DS13072, add, &val)==-1) Serial.println(" problème affichage mémoire"); else { if ( (add %8)==0) { Serial.print(add>>4,HEX); Serial.print(add & 0xf,HEX); Serial.print(" : "); } Serial.print(val>>4,HEX); Serial.print(val & 0xf,HEX); Serial.print(' '); if ( (add %8)==7) { Serial.println(); } } } Serial.println(); } ////////////////////////////////////////// void setTime(byte hour,byte minute,byte sec) { if (writeRegI2C(SLAVE_ADDR_DS13072, 2, hour)!=0) Serial.println(" problème réglage heure"); if (writeRegI2C(SLAVE_ADDR_DS13072, 1, minute)!=0) Serial.println(" problème réglage minute"); if (writeRegI2C(SLAVE_ADDR_DS13072, 0, sec)!=0) Serial.println(" problème réglage sec"); } ////////////////////////////////////////// void setDate(byte day,byte month,byte year) { if (writeRegI2C(SLAVE_ADDR_DS13072, 4, day)!=0) Serial.println(" problème réglage jour"); if (writeRegI2C(SLAVE_ADDR_DS13072, 5, month)!=0) Serial.println(" problème réglage mois"); if (writeRegI2C(SLAVE_ADDR_DS13072, 6, year)!=0) Serial.println(" problème réglage annee"); } ////////////////////////////////////////// void setup() { Serial.begin(9600); // start serial port at 9600 bps: Serial.print("Bonjour chargeur batterie"); pinMode(3,OUTPUT); Wire.begin(); // joindre le bus i2c en tant que maître if (1) { //activation de l'oscillateur interne, remet les secondes à 0 if (writeRegI2C(SLAVE_ADDR_DS13072, 0, 0x00)==-1) Serial.println(" problème activation oscillateur"); //réglage de l'heure setTime(0x14,0x00,0x00); setDate(0x17,0x02,0x18); } //activation sortie SQ à 1hz if ( writeRegI2C(SLAVE_ADDR_DS13072, 7, (byte)0x10)!=0) Serial.println(" problème réglage SquareWave"); } ////////////////////////////////////////// void loop() { /* //alterner sortie SQ clignote 1hz et allumée en continu writeRegI2C(SLAVE_ADDR_DS13072, 7, (byte)0x10); delay(5000); writeRegI2C(SLAVE_ADDR_DS13072, 7, (byte)0x0); delay(5000); */ /* //commande de SQ comme une GPIO writeRegI2C(SLAVE_ADDR_DS13072, 7, (byte)0x80); delay(1000); writeRegI2C(SLAVE_ADDR_DS13072, 7, (byte)0x00); delay(1000); */ displayTime(); delay(500); //dumping de toute la mémoire // dumpDS13072(); } //////////////////////////////////////////