![]() |
Mesure de la temperature |
décembre 2019 |
Le montage a simplement pour but d'afficher la température sur un petit LCD et de transmettre la température à un serveur mosquitto.
Le composant utilisé, le MCP 9808 affiche l'ambition de fournir des valeurs fiables d'une précision de l'ordre de 0.0625°
Plutôt que d'utiliser la librairie habituelle genre Adafruit, je vais tenter de décortiquer la datashett du composant et je vous embarque
dans cette aventure.
Nous sommes bien d'accord que ce montage pourrait se résumer à quelques lignes de code comme c'est le cas dans d'autres montages
présentés sur ce site, mais si vous aimez l'informatique et la programmation, je vous invite dans le "bac à sable" des informaticiens,
le binaire, l'hexadécimal, les registres.
Le circuit est orchestré par un petit esp8266-1.
Un petit boitier 3D à imprimer vous sera proposé si vous souhaitez réaliser ce montage.
Tous les commentaires présents dans cette description s'appuient sur cette datasheet.
Au chapitre 3.5 :
On nous explique comment est réalisé l'adressage du boitier. A3, A4, A5 et A6 sont toujours identique "0011" ensuite il est
possible à l'utilisateur de personnaliser l'adresse du boitier avec les bits A0, A1 et A2 dans le cas par exemple ou plusieurs capteurs sont utilisés
dans le même montage.
Pour quelqu'un qui n'a jamais travaillé avec du binaire ça commence fort !
Essayons de clarifier un peu cela :
Donc si vous n'avez pas raccordé un des fils A0 à A2 à un +3volts de votre circuit par défaut ces trois bits sont des 0,
nous avons de A6 à A0 => 0011000
Si vous utilisez une librairie pour piloter le MCP-9808, on vous dirait que l'adresse par défaut est 0x18. Ben voila
que maintenant on utilise une autre notation ! Alors voyons comment s'y retrouver :
L'information de base dans l'ordinateur c'est le BIT ou eb (élément binaire).
On appelle OCTET un ensemble de 8 bits.
On appelle MOT ou (BYTE) un ensemble de x octets (mot de 8 bits sur les anciens microprocesseurs, mot de 32, 64 ou 128 bits (voir plus) sur les microprocesseurs récents).
Souvent le mot désigne sur un microprocesseur sa capacité pour réaliser les opérations binaires (addition, soustraction, décalage, comparaison...)
Revenons à nos moutons !
Donc pour représenter un octet qui est composé de 8 bits le plus facile est de parler en hexadécimal. L'hexa va donc de 0 à 15 (soit 16 valeurs) mais comme 10 où 15 demandent 2 caractères,
on va utiliser la notation suivante 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E et F.
Le A vaut 10, le B 11, le C 12, le D 13, le E 14 et le F 15
Donc dans un octet la plus petite valeur que l'on peut écrire,
c'est en binaire 00000000 en hexa 00 et la plus grande 11111111 et en hexa FF
Avec un peu d'habitude on fait la conversion du binaire à l'hexa trés facilement : Par convention le bit de poids faible est toujours celui le plus à droite.
Donc dans l'octet on a b7,b6,b5,b4,b3,b2,b1,b0 pour la conversion vers l'hexa on converti par paquet de 4 bits vers un chiffre hexa.
b3 vaut 0 ou 8,
b2 vaut 0 ou 4,
b1 vaut 0 ou 2,
b0 vaut 0 ou 1
donc si nous reprenons notre adresse par défaut : 0011000
et si nous constituons deux paquets de 4 bits en partant de la droite (je sais il manque un eb dans le paquet de gauche on a que 3 eb et bien on le remplace par un 0 et le compte est bon !)
Premier paquet 1000 donc en hexa 8 Deuxième paquet 0001 donc en hexa 1
Dans le bon ordre on retrouve notre 18 hexa dont je vous parlais tout à l'heure ! ouf !
Pour exemple si on avait un paquet binaire 1111 en hexa il vaudrait F
si on avait 1100 il vaudrait C ... OK ?
Au chapitre 4.1.4 :
Voilà que l'on nous reparle de l'adresse mais que les bits sont déplacés vers la gauche et que l'on nous parle d'un nouveau eb0
qui va indiquer si on fait une écriture (valeur à 0) ou une lecture (valeur à 1)! On se souvient qu'il manquait un eb à gauche au chapitre 3.5, on avait
7 bits alors qu'un octet c'est 8 bits ! Donc notre problème est réglé, on est à 8 et la bonne nouvelle c'est que la librairie "wire" va gérer pour nous ce
8eme bit, en effet quand on utilisera un "write" elle le mettra à 0 et à 1 quand on utilisera un "read". Dans les exemples de la datasheet (qui utilise une autre librairie) on verra que cet eb0
est géré manuellement.
Au chapitre 5.0 :
On voit un petit registre "REGISTER POINTER" qui permet de diriger le choix du registre sur lequel nous souhaitons travailler.
Au chapitre 5.1 :
On nous explique comment utiliser ce petit registre. Les bits 4 à 7 sont inutilisés (toujours à 0). Les bits 0 à 3 permettent de choisir le registre cible.
On a avancé, on sait comment lire ou écrire et on sait comment choisir ou on va écrire ou lire !
Au chapitre 5.1.6 :
Nous allons nous intéresser au registre de RESOLUTION, il est vrai qu'il est mentionné dans la doc qu'une résolution est programmée par défaut, mais
j'ai trouvé des informations contradictoires sur la valeur utilisée, donc on va commencer par du simple et programmer notre choix nous-même.
Ce registre va donc nous permettre de décider le niveau de détail des mesures il est accessible à l'adresse "00001000", c'est donc cette adresse que nous devrons
utiliser pour indiquer au registre "POINTER" que nous voulons travailler avec le registre "RESOLUTION".
Au chapitre 5.2.4 :
On nous précise un délai à respecter entre deux mesures de température, il faut donc veiller à utiliser des délais suffisants, on voit que plus la précision de mesure
est importante moins on peut faire de mesure par seconde et plus le délai entre mesures est long. Dans le cas de la précision de 0.0625°C il ne faut pas dépasser
4 mesures par seconde et attendre un minimum de 250ms entre deux mesures.
Au chapitre 5-7 :
On nous indique que pour utiliser une résolution de 0.0625°C il faut mettre b0 à 1 et b1 à 1.
Le registre RESOLUTION est présenté ici.
Comment allons-nous procéder : Nous allons nous inspirer de l'exemple de la figure 5-2 (qui montre comment changer un eb d'événement).
Pour bien comprendre et cependant pouvoir s'inspirer des exemples de la datasheet il faut noter qu'ils utilisent une librairie pour gerer l'I2C (décrite en bas de la datasheet) différente
(dans notre cas c'est "wire").
Dans leur cas chaque fois qu'ils lancent une commande vers le composant ils spécifient l'adresse (le 18) et ils indiquent en poids faible le 1 ou le 0 pour la lecture
ou l'écriture comme expliqué plus haut.
Voici leur commande :
i2c_write(AddressByte & 0xFE);
Alors 0xFE c'est de l'hexa c'est 0x qui l'indique dans le cas d'une valeur exprimée en binaire on écrirait 0b avant.
On a vu que FE en hexa ça fait 1111 pour le F et 1110 pour le E en binaire,
ils auraient donc pu écrire aussi & 0b11111110
La notion de "masque" avec un & ou un | revient souvent en programmation binaire le petit tableau suivant montre le principe :
Dans notre cas c'est wire qui gere le bit 0 donc on ne précise que l'adresse du composant.
Wire.beginTransmission(adr_mcp9808);
On va indiquer dans le registre pointeur que l'on souhaite accéder au registre de précision
Wire.write(0b00001000);
Choix de la résolution maximale (0.0625°C)
Wire.write(0b00000011);
On envoie les données sur le bus I2C
i2c_ok = Wire.endTransmission();
Le résultat de l'opération d'écriture va être retournée dans i2c_ok si tout s'est bien passé le retour sera égal à 0.
Ces lignes de code vont donc pouvoir être inscrites dans le setup du programme.
Ben voilà on sait lui parler à ce circuit !
La vraie question, c'est comment lire la température, interressons nous au chapitre 5.1.3.
On voit que la température est stockée dans le registre "AMBIENT TEMPERATURE" et l'adresse de ce registre est 00000101
La température est indiquée dans les bits de b0 à b11 et qu'il y a un bit de signe en b12, les autres données du registre ne nous
interresseront pas dans cet article.
La datasheet au chapitre 5-1 nous donne ici encore un exemple de lecture de la température, mais attention ils ne lisent que la partie
entiere de la valeur, les décimales ne sont donc pas exploitées dans leur exemple, dans notre cas, si nous choisissons une précision de 0.0625
il serait domage de ne pas en tenir compte...
Alors en s'appuyant sur leur exemple (mais avec nos spécificités liées à "wire") commençons par lire les deux octets (poids fort et poids faible).
Début de la transaction avec le circuit
Wire.beginTransmission(adr_mcp9808);
On va indiquer dans le registre pointeur que l'on souhaite acceder au registre de temperature
Wire.write(0b00000101);
On envoie les données sur le bus I2C
i2c_ok = Wire.endTransmission();
Si ca c'est bien passé on va attaquer la lecture
if (i2c_ok == 0){
On prépare I2c pour lire 2 octets
Wire.requestFrom(adr_mcp9808, nb_octets_a_lire);
Lecture de la partie haute de la temperature
lect_poid_fort = Wire.read();
Lecture de la partie basse de la temperature
lect_poid_faible = Wire.read();
On envoie les demandes sur le bus I2C et on ferme i2C
Wire.endTransmission();
Nous voilà donc à la tête de deux octets, il faut faire un peu de tri, comme nous l'avons vu, seulement les eb0 à eb12 (avec le signe) nous intéressent.
On ne garde que les 5 eb de poids faible (les masques, vous connaissez maintenant sinon revoir un peu ci-dessus).
lect_poid_fort &= 0b00011111;
Calcul de la partie entière du résultat
On commence par vérifier le signe (l'eb12).
if ((lect_poid_fort & 0b00010000) == 0b00010000){
Température négative
On efface le signe avec le masque, on ne garde que les eb8 à eb11
lect_poid_fort &= 0b00001111;
Comme c'est un nombre négatif en complément à deux on utilise cette formule (256 en binaire c'est 0b100000000
resultat = 256 - (lect_poid_fort * 16 + lect_poid_faible / 16);
// Avec "lect_poid_faible / 16" on enlève la partie décimale
}else
Température positive on cumule donc le poids faible au poids fort et on obtient la partie entière de la température.
resultat = word(lect_poid_fort, lect_poid_faible) / 16;
Intérressons nous maintenant à la partie décimale de la mesure
Avec le masque suivant on ne garde que la partie décimale les eb0 à eb3
lect_poid_faible = lect_poid_faible & 0x0F;
On ajoute la partie décimale à la partie entière trouvée précédemment
resultat += bitRead(lect_poid_faible,3)*0.5 + bitRead(lect_poid_faible,2)*0.25 + bitRead(lect_poid_faible,1)*0.125 + bitRead(lect_poid_faible,0)*0.0625;
Un petit tableau pour expliquer ce qui est fait ci-dessus, ce type de notation binaire correspond à un traitement en virgule fixe, à ne pas
confondre avec une notation en virgule flottante beaucoup plus puissante mais avec une complexité qui va avec.
Dans notre cas nous avons 4 bits, l'eb3 vaut 2 puissance 0, l'eb2 vaut 2 puissance 1.... comme mentionné dans la doc. Dans le tableau ci-dessous on voit différents
système de notation :
On voit que la partie décimale ne peut prendre que 16 valeurs différentes ce qui peut paraitre surprenant quand on voit sur la doc une précision de 0.0625°C.
Au chapitre 5.2.1 il est mentionné une fonction interressante "Mode shutdown" du circuit qui permet de limiter la consommation de celui-ci en stoppant les mesures mais en laissant
l'accès au registres. Voyons comment procéder...
Dans un premier temps on va lire les valeurs des registres et ensuite on va pouvoir écrire l'eb concerné et seulement celui-ci.
On envoie que l'adresse c'est wire qui va forcer le bit de poids faible à 1 en lecture et à 0 en ecriture
Wire.beginTransmission(adr_mcp9808);
On va indiquer dans le registre pointeur que l'on souhaite acceder au registre de config
Wire.write(0b00000001);
On envoie les données sur le bus I2C
i2c_ok = Wire.endTransmission();
Si tout est ok
if (i2c_ok == 0){
On prépare I2c pour lire 2 octets (il faut connaitre l'état de tous les bits du registre de facon à ne modifier que le bit 0 du poids fort)
Wire.requestFrom(adr_mcp9808, 2);
Lecture de la partie haute de la config
lect_poid_fort = Wire.read();
Lecture de la partie basse de la config
lect_poid_faible = Wire.read();
On envoie les demandes sur le bus I2C et on ferme i2C
Wire.endTransmission();
lect_poid_fort |= 0b00000001;
On force à 1 le bit 8 (bit 0 du poid fort) de façon a passer en mode basse conso (shutdown)
Wire.beginTransmission(adr_mcp9808);
On envoie que l'adresse du circuit
Wire.write(0b00000001);
On va indiquer dans le registre pointeur que l'on souhaite acceder au registre de config
Wire.write(lect_poid_fort);
Ecriture de la partie haute de la config
Wire.write(lect_poid_faible);
Ecriture de la partie basse de la config
Wire.endTransmission();
On envoie les demandes sur le bus I2C et on ferme i2C
Cette partie de code sera donc appellée deux fois, une fois pour passer en mode shutdown en fin de programme et une fois en début pour sortir du mode shutdown.
La partie électronique est très simple comme chaque fois que l'on utilise un circuit I2C.
Pour alimenter le montage par usb, il faut integrer un petit régulateur qui va transformer le 5v de l'usb en 3v3 pour l'esp.
Le régulateur au format CMS est directement soudé sur la prise usb. Voir petit schéma plus bas.
Il reste à concevoir un petit boitier...
Le câblage de l'esp 1 est simple, il est intéressant que des fonctions I2C soit intégrées dans un si petit système !
Pour la programmation de l'esp 1 je vous conseille d'utiliser le petit programmateur que l'on voit sur les photos plus bas dans l'article, c'est du bonheur !
Le code du programme, il est trés commenté, j'espere que cela sera claire pour vous, bonne lecture...
// Mesure de la température intérieure avec un ESP8266-1
// Exploitation de l'I2C et du sensor de température MCP 9808
// Beaucoup de commentaire pour donner des pistes sur une façon de gérer le MCP 9808 sans passer par une librairie
// Je ne veux pas jouer au prof, j'en suis bien incapable, mais je vous propose simplement de partager cette petite escapade dans le code binaire.
// Toutes les lignes de programme en relation avec les traitements sur le MCP peuvent très facilement être
// intégrées dans des fonctions, ceci n'a pas été fait pour faciliter la compréhension des étapes nécessaires.
// Une fois les principes assimilés il sera préférable d'utiliser des fonctions
// voir un fichier d'entête .h et son pendant le .cpp Cela facilitera la lecture du code...
// De même pour la compréhension, j'ai privilégié la notation en binaire plutôt qu'en hexa comme c'est fait habituellement.
// decembre 2019
// Castoo
#include "ESP8266WiFi.h"
#include "PubSubClient.h"
#include "SSD1306Wire.h"
#include "Wire.h"
// l'adresse I2C (ici 0x3C) peut être différente en fonction du module utilisé
SSD1306Wire display(0x3c, 0, 2); //Le LCD OLED est à l'adresse 0x3C
// L'adresse par défaut du MCP9808
int adr_mcp9808 = 0b00011000; // En hexa 18 adr par defaut, il est possible de la changer en positionnant A0 A1 et A2 sur les broches du CI
uint8_t i2c_ok = 0; // Fanion de retour des échange bus i2c
const char* box = "raspi-mqtt"; // Nom du ssid du serveur raspberry Mosquitto
const char* m_passe = "xxxxxxxxxxxxxx"; // Passe WiFi
const char* mqtt_server = "10.3.141.1"; // Adr de serveur mosquitto
const char* mqtt_user = "admin"; // login sur serveur mosquitto
const char* mqtt_password = "xxxxxxxxxxxxxxx"; // Passe sur serveur mosquitto
char msg_envoye[50];
char ch_result[10];
WiFiClient espClient; // déclaration Objet wifi
PubSubClient client(espClient); // déclaration objet mosquitto
// ------------------- Cnx au serveur Raspberry Mosquitto ------------------
void setup_wifi() {
delay(10);
WiFi.begin(box, m_passe);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
randomSeed(micros());
}
// ----------------------------- Cnx à mosquitto ------------------------
void reconnect() {
while (!client.connected()) {
String clientId = "ESP8266temp9808-"; // Creatation d'un ID aléatoire (identification ESP sur mosquitto)
clientId += String(random(0xffff), HEX);
if (!client.connect(clientId.c_str(), mqtt_user, mqtt_password)) { delay(5000); }
}
}
void setup(){
Wire.begin(); // Démarrage de la librairie I2C
// initialisation de l'ecran LCD (voir la documentation de la librairie)
display.init();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_24); // Une grande police pour l'affichage sur le LCD OLED
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.clear();
delay(500);
// Fin init LCD
// cnx wifi
setup_wifi();
client.setServer(mqtt_server, 1883); // cnx mosquitto
delay(2000);
// Fin cnx wifi
// Init résolution du MCP 9808
Wire.beginTransmission(adr_mcp9808); // On envoie que l'adresse c'est wire qui va forcer le bit de poids faible à 1 en lecture et à 0 en ecriture
Wire.write(0b00001000); // On va indiquer dans le registre pointeur que l'on souhaite accéder au registre de précision
Wire.write(0b00000011); // Choix de la résolution maximale (0.0625°C)
i2c_ok = Wire.endTransmission(); // On envoie les données sur le bus I2C
delay(500);
}
void loop(){
float resultat = 0;
uint8_t lect_poid_fort = 0x00;
uint8_t lect_poid_faible = 0x00;
int nb_octets_a_lire = 2; // Nb octets à lire sur la liaison I2C
// Passage en mode actif (sortie du mode shutdown)
Wire.beginTransmission(adr_mcp9808); // On envoie que l'adresse c'est wire qui va forcer le bit de poids faible à 1 en lecture et à 0 en ecriture
Wire.write(0b00000001); // On va indiquer dans le registre pointeur que l'on souhaite acceder au registre de config
i2c_ok = Wire.endTransmission(); // On envoie les données sur le bus I2C
if (i2c_ok == 0){
Wire.requestFrom(adr_mcp9808, nb_octets_a_lire); // On prépare I2c pour lire 2 octets
lect_poid_fort = Wire.read(); // Lecture de la partie haute de la config
lect_poid_faible = Wire.read(); // Lecture de la partie basse de la config
Wire.endTransmission(); // On envoie les demandes sur le bus I2C et on ferme i2C
lect_poid_fort &= 0b11111110; // On force à 0 le bit 8 (bit 0 du poid fort)
Wire.beginTransmission(adr_mcp9808); // On envoie que l'adresse c'est wire qui va forcer le bit de poids faible à 1 en lecture et à 0 en ecriture
Wire.write(0b00000001); // On va indiquer dans le registre pointeur que l'on souhaite acceder au registre de config
Wire.write(lect_poid_fort); // Ecriture de la partie haute de la config
Wire.write(lect_poid_faible); // Ecriture de la partie basse de la config
Wire.endTransmission(); // On envoie les demandes sur le bus I2C et on ferme i2C
}
delay(400); // On laisse la bête se reveiller doucement...
lect_poid_fort = 0x00;
lect_poid_faible = 0x00;
// Lecture de la temperature
Wire.beginTransmission(adr_mcp9808); // On envoie que l'adresse c'est wire qui va forcer le bit de poids faible à 1 en lecture et à 0 en ecriture
Wire.write(0b00000101); // On va indiquer dans le registre pointeur que l'on souhaite acceder au registre de temperature
i2c_ok = Wire.endTransmission(); // On envoie les données sur le bus I2C
if (i2c_ok == 0){
Wire.requestFrom(adr_mcp9808, nb_octets_a_lire); // On prépare I2c pour lire 2 octets
lect_poid_fort = Wire.read(); // Lecture de la partie haute de la temperature
lect_poid_faible = Wire.read(); // Lecture de la partie basse de la temperature
Wire.endTransmission(); // On envoie les demandes sur le bus I2C et on ferme i2C
lect_poid_fort &= 0b00011111; //On ne garde que les 5 eb de poids faible
// Calcul de la partie entiere du résultat
if ((lect_poid_fort & 0b00010000) == 0b00010000){ // Lecture du signe
// Température négative
lect_poid_fort &= 0b00001111; //On efface le signe
// Comme c'est un nombre négatif en complèment à deux on utilise cette formule (256 en binaire c'est 0b100000000
resultat = 256 - (lect_poid_fort * 16 + lect_poid_faible / 16); // Avec "lect_poid_faible / 16" on enleve la partie decimale
}else
// Température positive
resultat = word(lect_poid_fort, lect_poid_faible) / 16;
// On va maintenant s'occuper de la partie décimale
lect_poid_faible = lect_poid_faible & 0x0F; // On ne garde que la partie décimale
// On ajoute la partie decimale à la partie entiere trouvée précédement
resultat += bitRead(lect_poid_faible,3)*0.5 + bitRead(lect_poid_faible,2)*0.25 + bitRead(lect_poid_faible,1)*0.125 + bitRead(lect_poid_faible,0)*0.0625;
snprintf (ch_result, 50, "%.3f °", resultat); // Mise en forme du résultat
display.clear();
display.setFont(ArialMT_Plain_24);
display.setTextAlignment(TEXT_ALIGN_CENTER);
display.drawString(64, 22, ch_result); // Affichage en gros de la temperature sur le LCD
display.display();
if (!client.connected()) { reconnect(); }
client.loop();
snprintf (msg_envoye, 50, "T %.2f °", resultat);
client.publish("temp_9808", msg_envoye); // Envoie de la temperature à Mosquitto
}
delay(100); // Petite pause pour laisser le bus I2C reprendre son souffle !
lect_poid_fort = 0x00;
lect_poid_faible = 0x00;
// Passage en mode veille (passage en mode shutdown)
Wire.beginTransmission(adr_mcp9808); // On envoie que l'adresse c'est wire qui va forcer le bit de poids faible à 1 en lecture et à 0 en ecriture
Wire.write(0b00000001); // On va indiquer dans le registre pointeur que l'on souhaite acceder au registre de config
i2c_ok = Wire.endTransmission(); // On envoie les données sur le bus I2C
if (i2c_ok == 0){
Wire.requestFrom(adr_mcp9808, 2); // On prépare I2c pour lire 2 octets
lect_poid_fort = Wire.read(); // Lecture de la partie haute de la config
lect_poid_faible = Wire.read(); // Lecture de la partie basse de la config
Wire.endTransmission(); // On envoie les demandes sur le bus I2C et on ferme i2C
}
lect_poid_fort |= 0b00000001; // On force à 1 le bit 8 (bit 0 du poid fort)
Wire.beginTransmission(adr_mcp9808); // On envoie que l'adresse c'est wire qui va forcer le bit de poids faible à 1 en lecture et à 0 en ecriture
Wire.write(0b00000001); // On va indiquer dans le registre pointeur que l'on souhaite acceder au registre de config
Wire.write(lect_poid_fort); // Ecriture de la partie haute de la config
Wire.write(lect_poid_faible); // Ecriture de la partie basse de la config
Wire.endTransmission(); // On envoie les demandes sur le bus I2C et on ferme i2C
delay(100000); // environ 3 minutes d'attente avant de relancer une mesure et une émission vers Mosquitto
}
Ce circuit MCP-9808 est performant même si je suis un peu déçu de l'interprétation que je m'étais fait du degré de précision.
Beaucoup d'autres fonctions sont disponibles sur ce circuit, des seuils d'alerte, des comparaisons... A vous de les exploiter si bon vous semble...
Je rencontre des difficultés pour isoler thermiquement la puce du MCP-9808 du reste du circuit, depuis que tout est dans le boitier j'ai au moins
deux degrés de plus que quand le montage était en volant, la chaleur déployée par les composants (surtout l'esp) dans le boitier fausse la température mesurée par le circuit ! A suivre...
L'analyse de la datasheet montre que comme les constructeurs souhaitent que les circuits qu'ils mettent sur le marché soient vendus un maximum,
ils donnent beaucoup d'informations sur la façon d'utiliser leurs circuits et qu'il est alors facile de les mettre en œuvre.
Pour la partie mosquitto, node-red, InfluxDB et les graphiques avec Grafana je vous invite à consulter les autres exemples de ce site.
Un site sympa qui propose des explications sur les fonctions de la Librairie wire I2C.
Pour imprimer le boitier je vous propose le fichier au format STL dans la partie Impression 3D du site.
Tous les commentaires présents dans cette description s'appuient sur cette datasheet.