Aller au contenu

Un adaptateur I2C pour capteur à ultrason HC-SR04, pour faciliter les mesures de distances !

Projet adaptateur I2C pour capteur HC-SR04, carte électronique avec schéma et PCB pour ajouter fonctionnalité i2c à un émetteur récepteur ultrasons HCSR04

Si comme moi vous utilisez des capteurs à ultrasons HC-SR04, alors vous savez à quel point ils sont contraignants à utiliser, pour mesurer une distance. Car pour chaque mesure, il faut soi-même gérer l’impulsion à faire sur la ligne TRIG, puis mesurer le temps d’ÉCHO en retour. Sans parler du fait que ça mobilise pas mal de temps-processeur pour ce faire, à chaque fois.

Du coup m’est venu l’idée de faire une carte PCB intermédiaire, qui demanderait au HC SR04 de faire des mesures en permanence, et qui retournerait la dernière valeur mesurée à la demande, via une simple requête I2C. Ainsi, plus de surcharge processeur, car toute la logique de contrôle/mesure/calcul serait déporté sur celle-ci ! Alors voyons sans plus attendre cette carte, que j’ai nommé : Adaptateur I2C pour capteur HCSR04 🙂

Ce contenu vous plaît ? Alors abonnez-vous à la Newsletter pour ne rien louper !

Remarque : dans un article précédent, nous avons déjà vu qu’il existait une version « P » du module HC-SR04, qui offrait justement un accès I2C (et pas que) pour faire des mesures de distances par ultrasons. Mais nous avons également vu que cette version HC-SR04P fonctionnait très mal en i2c, et c’est justement pour cette raison que j’ai construis la présente carte !

Présentation de cet Adaptateur I2C pour module ultrasonique HC-SR04

Pour commencer, voici un aperçu du PCB de cette carte adaptatrice I2C/HC-SR04, une fois soudée (avec plusieurs annotations dessus, pour vous permettre de bien repérer quels sont les éléments importants ici) :

Présentation carte i2c pour HCSR04, avec alimentation 5V et lignes I²C SDA et SCL, avec jumpers de sélection d'adresse sur le bus et microcontrôleur de gestion

Sur cette image, on retrouve :

  • Un connecteur permettant de recevoir un module ultrasonique HC-SR04 (modèle traditionnel, avec les lignes TRIG et ECHO donc)
  • Un connecteur de programmation, qui permettra d’alimenter la carte et programmer le microcontrôleur ATtiny 412 présent sur cette carte, en UPDI (à l’aide d’un convertisseur USB/UPDI compatible 5V, tel que celui que je vous avais déjà présenté)
  • Un connecteur de communication I2C, qui permettra d’alimenter la carte et recevoir les lignes SDA et SCL (bus i2c), afin de pouvoir communiquer chaque dernière mesure de distance effectuée lorsque souhaité
  • Un microcontrôleur ATtiny412, véritable cœur de ce projet, faisant l’interface entre le bus I²C et le module HCSR04 qui sera branché sur cette carte
  • Deux résistances pull-up, avec leurs jumpers associés, à souder ou non selon si on souhaite rendre ces résistances actives ou non, sur les lignes SCL et SDA
  • Trois jumpers de sélection d’adresse I2C, permettant à cette carte (périphérique/esclave i2c) de choisir 1 des 8 adresses I²C possibles (2 à la puissance 3 jumpers donnant 8 combinaisons possibles !)
  • Un réseau R/2R, constitué de 6 résistances, permettant de fournir une tension unique à partir de ces 8 adresses i2c possibles, selon l’état des jumpers de sélection d’adresse

ATTENTION : seul l’un des 2 connecteurs (programmation ou communication I2C) devra être branché sur cette carte, et en aucun cas les deux en même temps. Sans quoi vous risqueriez d’initier un conflit de tension potentiellement destructeur, à partir de là ! Donc attention à brancher soit l’un soit l’autre de ces deux connecteurs d’alim, mais jamais les deux simultanément !

Du reste, au niveau fonctionnel, l’utilisation de cette carte est vraiment simple ! Car il suffit d’interroger cette I²C, pour qu’elle retourne une valeur 16 bits (sur deux octets donc), donnant la dernière valeur mesurée en millimètres.

Par exemple :

  • si l’adresse de cette carte I2C est 0x30
  • et s’il y a un obstacle à 26 cm devant le module HC-SR04
  • et si on demande à la carte de nous retourner une valeur
  • alors la carte devrait théoriquement retourner la valeur 260, qui correspond à 260 mm, soit 26 cm

Difficile de faire plus simple, avouez !

Nota 1 : cette carte adaptatrice I2C va continuellement effectuer des mesures de distance, à partir du HC-SR04. Et lorsque vous solliciterez une mesure via le bus i2c, ce sera tout simplement la dernière valeur mesurée à l’instant qui vous sera retournée.

Nota 2 : dans l’exemple ci-dessus, j’ai choisi l’adresse i2c 0x30. Mais sachez que comme cette carte adaptatrice HC-SR04 dispose de 3 jumpers (bits) de sélection d’adresse, vous aurez donc le choix entre 8 valeurs possible. Et comme l’adresse de base enregistrée par défaut en dur est 0x30, vous aurez donc au final la possibilité de choisir par les 8 valeurs I²C suivantes : 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, ou 0x37.

Nota 3 : à la mise sous tension, il faudra attendre 1 à 2 secondes avant d’interroger cette carte, le temps pour elle de pouvoir correctement s’initialiser (paramétrage registres/interruptions, lecture adresse i2c esclave, et faire des premières lectures).

Schéma électronique

Maintenant que nous avons vu la présentation de cet adaptateur I2C pour module HCSR04, zoomons un peu, et voyons toute la partie électronique, à proprement parler !

Vue d’ensemble

Pour commencer, voici le schéma électronique de cet Adaptateur I²C pour capteur à ultrasons HC-SR04 (nota : si cette image ne vous paraît suffisamment nette, n’hésitez pas à faire un saut en fin d’article, où vous trouverez une version PDF de ce schéma, sur laquelle vous pourrez zoomer !) :

Schéma adaptateur i2c pour capteur ultrasonique HC-SR04, utilisant un microcontrôleur ATtiny 412 pour faire des impulsions TRIG et lectures d'impulsions sur ECHO

Comme d’habitude, l’ensemble a été divisé en plusieurs blocs, pour faciliter la lecture. On retrouve donc ici :

  • un bloc « Bornier alim/pilotage I2C », qui permet à la fois d’alimenter ce projet en 5 volts et d’accueillir les lignes du bus i2c (SDA et SCL)
  • un bloc « Pull-up I2C », qui permet de connecter ou non une résistance pull-up sur chaque ligne I²C (SDA et SCL, donc). Pour rappel, il faut à minima une résistance pull-up par ligne i2c, et veiller à ne pas trop les multiplier (sans quoi le courant sur le bus va monter proportionnellement, jusqu’à devenir trop important, voire destructeur). C’est pourquoi ces résistances sont activables ou désactivables ici, au besoin !
  • un bloc « Sélecteur d’adresses I2C », composé de 3 doubles cavaliers (jumpers), permettant d’avoir 3 bits de configuration pour l’adressage i2c, donc 8 adresses I2C possibles (23_jumpers)
  • un bloc « Convertisseur numérique/analogique », qui est ni plus ni moins qu’un réseau R/2R, permettant, à partir des 3 bits/cavaliers/jumpers précédents, de délivrer une tension unique correspondante (entre 0 et quasi 5V). Ceci permet donc de n’utiliser qu’1 seule entrée du microcontrôleur au lieu de 3, pour déterminer une adresse I2C à partir de ces 3 jumpers !
  • un bloc « Microcontrôleur (ATtiny412) », qui, comme son nom l’indique, contient le cœur de ce projet, à savoir un µC ATtiny 412
  • un bloc « Capteur HC-SR04 », qui est tout bonnement un bornier de raccordement permettant de recevoir n’importe quel module traditionnel HC-SR04 (avec lignes TRIG/ECHO), accompagné d’un condensateur de filtrage et un de découplage, afin de stabiliser son alimentation
  • et un bloc « Bornier alim/prog », qui contient tout simplement le bornier de programmation de l’ATtiny (avec une ligne d’alim +5V, une ligne de prog UPDI, et la masse)

En sachant que vous pouvez faire fi des autres blocs, qui sont :

  • le bloc « ERC Kicad », qui est juste là pour faire du contrôle de règles électriques sous Kicad
  • et le bloc « Trous de montage », qui permet d’auto-importer les empreintes correspondantes, lors de l’ajout automatique des composants au niveau du PCB

Comme vous pouvez le constater, ce schéma est extrêmement simple, mis à part peut-être le réseau R/2R (le fameux « convertisseur numérique vers analogique »), pour ceux qui ne connaissent pas ! D’ailleurs, si tel est votre cas, où si vous souhaitez une piqûre de rappel, voici ce que c’est et comment il fonctionne !

Fonctionnement du réseau R/2R (convertisseur numérique → analogique)

Un réseau R/2R est une topologie de montage électronique en échelle, constitué de résistances ne pouvant prendre que deux valeurs : soit R, soit 2R (avec R une résistance de valeur quelconque, et 2R une résistance de valeur double par rapport à R). On le retrouve dans le schéma électronique précédent, avec R=10K, et donc 2R=20K. Vu autrement, sans les valeurs, ce réseau ressemble à cela :

Exemple réseau R/2R électronique à 3 bits, schéma électronique avec résistances avec entrées tout-ou-rien et sortie analogique, texte descriptif placement résistances

À noter qu’un réseau R/2R a :

  • autant d’entrées qu’on souhaite (3 sur ce schéma)
  • et une seule sortie (toujours)

Et qu’au niveau des tensions :

  • on envoie soit la masse, soit Vcc, au niveau des entrées (c’est pourquoi on parle souvent de « bits d’entrée », du fait que ces entrées ne peuvent prendre que l’état « 0 » ou « 1 », selon si on envoie la masse ou +Vcc)
  • et on récupère un signal analogique en sortie (plus précisément une tension fixe, dont la valeur peut aller de 0 compris à quasiment Vcc, en fonction de l’état des entrées)

Comme vous vous en doutez, le fait d’avoir choisi des valeurs de résistances R et 2R (donc simple ou double) n’est pas un hasard ! En effet, cela permet d’obtenir des divisions de tension successives par deux. Ce qui signifie que la 1ère entrée aura comme « poids » une tension de Vcc/2, la 2ème entrée Vcc/4, la 3ème entrée Vcc/8, puis Vcc/16, puis Vcc/32, etc.

Par exemple, dans le cas d’un réseau R/2R à 3 entrées (donc à 3 bits, puisque les entrées ne peuvent recevoir que 0=masse ou 1=Vcc), la tension de sortie est égale à :

VSortie=Entreˊe_22+Entreˊe_14+Entreˊe_08V_{Sortie}=\frac{Entrée\_2}{2}+\frac{Entrée\_1}{4}+\frac{Entrée\_0}{8}

Ce qui donne le tableau suivant, en fonction de toutes les combinaisons possibles en entrée (23_entrées, donc 8 possibilités) :

Entrée_2Entrée_1Entrée_0Vsortie
0000
0011/8 * Vcc
0102/8 * Vcc
0113/8 * Vcc
1004/8 * Vcc
1015/8 * Vcc
1106/8 * Vcc
1117/8 * Vcc

Ainsi, vous voyez que la sortie d’un réseau R/2R à 3 bits a une tension pouvant aller de 0 à 7/8ème de Vcc inclus. Donc si VCC=5V, par exemple, alors la tension de sortie ira de 0 à 4,375 volts (4,375V = 7/8 de 5V), en fonction des entrées. Dit de manière encore plus explicite, la tension de sortie ne pourra prendre que les valeurs suivantes : 0 V, 0.625 V, 1.25 V, 1.875 V, 2.5 V, 3.125 V, 3.75 V, ou 4.375 V !

Nota 1 : vous remarquerez au passage qu’on atteint jamais la tension +Vcc, lorsqu’on met tous les bits à 1 (toutes les entrées à +Vcc, si vous préférez). Mais ce n’est pas grave, car on obtient bien l’effet escompté, à savoir : obtenir 8 tensions différentes à partir des 8 combinaisons possibles (8 = 2 à la puissance 3, puisque nous avons 3 entrée ici).

Nota 2 : toutes ces valeurs sont théoriques, gardez toujours cela à l’esprit. En effet, en pratique, tout est en fonction des valeurs réelles et comportement des résistances utilisées. Par exemple : si vous utilisez des résistances R de 10K, qui en réalité font 10.07K, 10.03K, et 9.98K, alors vous n’aurez pas exactement les tensions escomptées en sortie, car elles ne font pas toutes exactement 10K ohms. Mais peu importe ! Car les tensions réelles seront toujours « aux alentours » de la tension théorique attendue. Il suffit donc de prendre cela en compte, dans le code de programmation d’un µC lorsqu’on fait une mesure de tension (ADC), en recherchant une tension dans une zone autour d’une valeur attendue, et non chercher une valeur exacte (vous verrez d’ailleurs un peu plus bas, dans le code ATtiny, comment j’ai codé tout ça, si vous voulez un exemple pratique !).

Liste des composants

Pour info, pour ceux que ça intéresse, voici la liste des composants utilisés dans ce projet d’adaptateur i2c pour module HC SR04 :

QtéDésignationLien achat
1Condensateur 47µF 25V X7R (CMS, format 1206)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
2Condensateur 10µF 50V X7R (CMS, format 1206)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
3Condensateur 100nF 50V X7R (CMS, format 1206)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
2Connecteur femelle (pinSocket) 4 broches (traversant, 2.54mm)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
1Connecteur femelle (pinSocket) 3 broches (traversant, 2.54mm)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
3Connecteur mâle (pinHeader) 3 broches (traversant, 2.54mm)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
3Cavaliers/jumpers (2.54mm)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
4Résistance de 20KΩ 250mW ±1% (CMS, format 1206)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
2Résistance de 10KΩ 250mW ±1% (CMS, format 1206)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
2Résistance de 4,7KΩ 250mW ±1% (CMS, format 1206)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
1Microcontrôleur ATtiny412-SSNR (CMS, format SOIC-8)Caddie plein 24x24, icone passion électronique fr, achat de matériels d'élec, idéal débutant et amateurs d'électronique
1Circuit imprimé PCB (voir le lien, tout en bas de cet article)

En espérant n’avoir rien oublié, comme d’habitude 😉

Soudage des composants

À présent, passons à la partie soudage ! Mais avant cela, laissez-moi vous montrer le PCB de cette carte I2C pour HC-SR04, recto/verso, avant que les composants électroniques ne soient soudés dessus :

PCB blanc recto verso sans composants soudés dessus, aperçu d'un projet pour apprendre l'électronique par la pratique, avec soudage et programmation µC ATtiny I2C

Comme vous pouvez le constater, les composants (CMS et traversants) sont tous placés du même côté, l’autre côté étant ni plus ni moins qu’un plan de masse.

Perso, pour aller vite, j’ai soudé composants CMS et traversants au fer à souder. Donc ne faites pas trop attention à mes soudures, si celles-ci sont un peu trop chargées ! Et comme toujours, j’ai soudé les composants les plus bas/petits (donc les CMS) en premier, et tous les composants restants (traversants) ensuite ! Voici ce que ça donne, en images :

Photo de composants électroniques CMS soudés sur un circuit imprimé blanc, avec sérigraphie noire sur white PCB, projet d'apprentissage de l'électronique simplement
Adaptateur i2c pour module HC SR04 fonctionnant en 5V, avec lignes ECHO et TRIG gérées par microcontrôleur ATtiny412 pour mesures perpétuelles à disposition du bus I2C
Vue arrière de soudures étains de composants traversants THT au dos d'un PCB blanc, avec aperçu table d'adressage I2C du projet électronique correspondant

À partir de là, il ne reste plus qu’à mettre les cavaliers (jumper) pour la sélection d’adresse I2C sur PCB, et ça en sera fini pour cette partie ! Voici donc le rendu final, avec tous les composants/accessoires en place :

Carte PCB d'adaptation HC-SR04 en I2C via microcontrôleur ATtiny modèle 412, pour interfacer lignes TRIG/ECHO du capteur ultrasonique vers le bus i2c

Bon… ! Nous voilà au terme de cette partie matérielle, alors passons à la partie logicielle 🙂

Programme principal + programmation ATtiny (cœur de la carte)

Dans cette partie, nous allons voir le programme faisant tourner cet adaptateur I2C pour module HC-SR04, puis le téléversement de ce programme (upload) dans le microcontrôleur ATtiny412, véritable cœur de ce montage !

Programme « Adaptateur I2C pour HC-SR04 » (écrit sous Arduino IDE)

Pour commencer, voyons ce programme ! Et juste avant de vous le détailler, quelques mots à son sujet :

  • le code utilise des manipulations de registres microcontrôleur, pour être efficient. Mais ne prenez pas peur, car tout ceci n’est pas si compliqué que cela, du moment où vous avez la documentation technique (datasheet) du fabricant, qui détaille tout cela ! Je vous mets un lien vers le datasheet de l’ATtiny412 ici, ainsi que tout en bas de cet article, dans la zone « liens et téléchargements »
  • j’ai écrit tout le code sous Arduino IDE, bien que ce soit pour ensuite l’uploader dans un µC ATtiny, et non sur une carte arduino (pour que ce soit plus simple/accessible au plus grand nombre). Au passage, tout ceci est permis grâce au gestionnaire de cartes « megaTinyCore », qu’il faudra avoir installé dans son IDE Arduino. Si ce n’est pas le cas, voici la procédure à suivre, pour installer megatinycore dans Arduino IDE :
    • aller dans le menu Outils > Type de carte > Gestionnaire de carte
    • taper « megatinycore » dans le champ de recherche et valider
    • puis sélectionner la ligne « megaTinyCore by Spence Konde » (attention à ne pas vous tromper ici, avec les autres packages avoisinant !)
    • puis cliquer sur Installer

Cela étant dit/fait, voyons à présent le code ATtiny de cet adaptateur I2C pour HCSR04, sans plus attendre !

// ===================================================================================================
//   ______               _                  _///_ _           _                   _
//  /   _  \             (_)                |  ___| |         | |                 (_)
//  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
//  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
//  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
//  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
//                                                                                      | |
//                                                                                      \_|
// ===================================================================================================
//
//  Nom Projet  :       Adaptateur I2C pour Capteur à ultrasons HC-SR04
//
//  Desc Projet :       Programme "i2c esclave", tournant sur un ATtiny412, permettant d'effectuer
//                      des mesures continuelles de distance (écho) via un HC-SR04, et permettant de
//                      retourner la dernière valeur lue, à la demande, via le bus I2C
//
//  Fichier     :       prgAdaptateurI2CpourHCSR04.ino
//  Créé le     :       10.05.2026
//  Auteur      :       Jérôme TOMSKI
//  Site        :       https://passionelectronique.fr/
//  GitHub      :       https://github.com/PassionElectronique/Programmes-Adaptateur-I2C-pour-capteur-HCSR04
//  Licence     :       https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr (BY-NC-ND 4.0 CC)
//
// ==============================================================================================================
//
//  Prérequis   :       Avoir installé "megatinycore" dans Arduino IDE
//  Prog ATtiny :       Impérativement régler 'Clock' sur '20 MHz internal', dans le menu 'Outils' d'Arduino IDE,
//                      avant d'uploader ce code dans l'ATtiny412 (explications du pourquoi avoir choisi 20 MHz
//                      pour le CPU ATtiny dans la fonction 'configureHorlogePeripheriquesInternesATtiny')
//
// ==============================================================================================================


// Constantes
#define ADRESSE_I2C_DE_BASE        0x30   // Adresse i2c de base, choisie arbitrairement ici, pour ce projet
#define BROCHE_DE_SORTIE_TRIG   PIN_PA3   // Sortie PWM pour envoyer des impulsions de 10 µs toutes les 100 ms environ, sur la ligne TRIG du HC-SR04 (demandes de mesures automatiques/perpétuelles)
#define BROCHE_D_ENTREE_ECHO    PIN_PA7   // Nota : n'importe quelle pin libre aurait pu convenir ici (grâce à EVSYS, qui peut relier n'importe pin au timer TCB0, qui nous intéresse ici)
#define BROCHE_D_ENTREE_ADC     PIN_PA6   // Entrée ADC, permettant de définir l'adresse I2C souhaitée parmi 8 adresses possibles (celle de base, décrite un peu plus haut, et les 7 suivantes)

// Variables
uint8_t adresse_I2C_reelle;  // Tient compte de ADRESSE_I2C_DE_BASE spécifiée ci-dessus (0x30), et BROCHE_D_ENTREE_ADDR (permet d'ajouter de 0 à 7, pour avoir le choix entre 8 adresses i2c différentes)

uint8_t index_buffer_verrouille = 0;              // On utilise 2 buffers ici, car la valeur 16 bits (2 octets) peut être altérée en lecture au moment d'une interruption (ISR)
uint16_t buffer_0_distance_mesuree_en_mm = 0;
uint16_t buffer_1_distance_mesuree_en_mm = 0;

volatile uint8_t index_octet_a_envoyer = 0;       // Permet d'envoyer un octet après l'autre (si égal 0, alors le LSB va être envoyé ensuite ; si égal 1, alors c'est au tour du MSB)

// Vérification de sécurité, afin de s'assurer que le timer TCD0 est bien choisi pour millis() etc
#ifndef MILLIS_USE_TIMERD0
  #error "Ce programme nécessite que le timer TCD0 soit utilisé pour millis()/micros(). Configurez-le dans le menu 'Outils' > 'millis()/micros() Timer'."
#endif


// ========================
// Initialisation programme
// ========================
void setup() {
  
  // Configurations préalables
  configureHorlogePeripheriquesInternesATtiny();
  determineAdresseI2Csouhaitee();
  configureCommunicationsI2CenModeEsclave();
  configureSytemeDeCaptureLigneECHO();
  configurePwmPersistantSurLigneTRIG();

  // Activation des interruptions, et passage à la boucle loop
  sei();
}


// =================
// Boucle principale
// =================
void loop() {
  // Rien ici, car une fois les configurations initiales passées, "tout" se passe au niveau des ISR (en cas de capture sur la ligne ECHO, ou requête I2C)
}


// ======================================================
// Fonction : configureHorlogePeripheriquesInternesATtiny
// ======================================================
void configureHorlogePeripheriquesInternesATtiny() {

  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Explications techniques
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // But de cette fonction :
  //    -> fixer la vitesse d'avancement des timers internes de l'ATtiny à 1/16ème de la fréquence principale (1/16ème de 20 MHz, soit 1,25 MHz donc).
  //       Ainsi, chaque timer sera incrémenté de 1 toutes les 0.8 µs (1 / 1,25 MHz). Et comme les timer A et B qui seront utilisés dans ce programme
  //       sont codés sur 16 bits (comptant de 0 à 65535, donc), la période totale pour ces timers sera de 0,052428 seconde (soit 52 ms environ)
  // Pourquoi avoir choisi 20 MHz comme fréquence principale ATtiny, et une division de fréquence par 16 pour les périphériques internes (timers, etc) ?
  //    -> car ces 52 ms de période couvrent bien les 38 ms MAX du signal ECHO retourné par le capteur HC-SR04 (timeout correspondant à une distance de 6,5m environ)
  //    -> car ces 52 ms de période, si multipliés par 2, nous donnent 104 ms de période, ce qui nous permettra de faire une mesure HC-SR04 toutes les 100ms environ
  //       (en sachant qu'il faut respecter un temps d'attente d'environ 60ms entre deux lectures, ces 100ms sont tout bonnement parfaits !)
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------
  
  CPU_CCP = CCP_IOREG_gc;                                     // Levée éphémère de la protection du registre protégé suivant (MCLKCTRLB)
  CLKCTRL.MCLKCTRLB = CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm;   // Fixation du prescaler d'horloge sur DIVISON par 16, et activation de ce prescaler (PEN)
}


// =======================================
// Fonction : determineAdresseI2Csouhaitee
// =======================================
void determineAdresseI2Csouhaitee() {
  
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Explications techniques
  // ------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // But de cette fonction :
  //    -> déterminer l'adresse I2C "finale" de cet ATtiny, parmi 8 possibles (en partant d'une adresse de base donnée tout en haut)
  // Difficulté :
  //    -> l'ATtiny412 n'a que 8 broches, dont 7 sont déjà réservées (VCC, GND, UPDI, SDA, SCL, entrée_ECHO, et sortie_TRIG) ; cela ne laisse donc qu'une SEULE pin disponible
  // Solution :
  //    -> passer par une tension analogique, à 8 niveaux de tension différente grâce à un réseau R/2R et 3 bits de commande (3 cavaliers/jumpers inverseurs en fait, sur PCB)
  //    -> lire cette tension analogique via une entrées analogique sur l'ATtiny, et la convertir en valeur numérique (paliers allant de 0 à 7, pour avoir au final 8 adresses i2c possibles)

  // Petite pause de stabilisation (tension d'alim, et partie ADC)
  delay(100);

  // Constantes locales
  const uint8_t nombre_de_lectures_ADC = 10;
  const float   tension_d_alim_ATtiny = 5.0;
  const uint8_t pause_entre_chaque_lecture_ADC_en_millisecondes = 20;
  const uint8_t nombre_de_niveaux_de_tension_ADC = 8;     // Nos 3 cavaliers sur PCB nous donnent 3 bits de commande, soit 2 puissance 3 possibilités (2^3 = 8)
  const float   plage_de_tension_d_un_palier_ADC = tension_d_alim_ATtiny / nombre_de_niveaux_de_tension_ADC;  // Avec Valim=5V et NbNiveaux=8, alors on aura 0,625 V par palier

  // Relevé de la tension ADC (plus précisément : réalisation de 10 lectures ADC successives, espacées de 10 ms chacune, et calcul d'une valeur moyenne, pour filtrer les éventuelles parasites)
  float tension_moyenne_ADC = 0.0;
  for (uint8_t i = 0; i < nombre_de_lectures_ADC ; i++) {
    int valeur_brute_entree_ADC = analogRead(BROCHE_D_ENTREE_ADC);    // Lecture ADC (sur 10 bits, soit une valeur entre 0 et 1023 compris, sur un ATtiny)
    float tension_mesuree_sur_ADC = valeur_brute_entree_ADC * (tension_d_alim_ATtiny / 1023.0);
    tension_moyenne_ADC += tension_mesuree_sur_ADC;
    delay(pause_entre_chaque_lecture_ADC_en_millisecondes);
  }
  tension_moyenne_ADC /= nombre_de_lectures_ADC;

  // Remarques :
  //    -> les 8 tensions (paliers) théoriques possibles seront les suivantes : 0, 0.625V, 1.25V, 1.875V, 2.5V, 3.125V, 3.75V, et 4.375V
  //    -> on va donc considérer 8 plages possibles à partir de ces tensions, en acceptant une tension comprise entre -1/2 pas < tension_mesuree < +1/2 pas (en sachant qu'un pas vaut 0,625V)
  //       ainsi, si la tension mesurée n'est pas pile-poil sur sa valeur théorique, on pourra la retrouver en laissant un peu de marge dessus/dessous

  // Détermination du palier choisi (entre 0 et 7 compris, qui se rajouteront à l'adresse I2C de base)
  uint8_t numero_de_palier = 0;
  for (uint8_t i = 0; i < nombre_de_niveaux_de_tension_ADC ; i++) {
    float tension_basse_du_palier_i = (i == 0) ? 0 : (i - 0.5) * plage_de_tension_d_un_palier_ADC;  // Nota : exception pour le "niveau 0", sinon ça donnerait une tension négative
    float tension_haute_du_palier_i = (i + 0.5) * plage_de_tension_d_un_palier_ADC;
    if (tension_moyenne_ADC >= tension_basse_du_palier_i && tension_moyenne_ADC < tension_haute_du_palier_i) {
      numero_de_palier = i;
      break;
    }
  }

  // Enregistrement de l'adresse I2C finale, dans variable globale
  adresse_I2C_reelle = ADRESSE_I2C_DE_BASE + numero_de_palier;        // Si 0x30 est l'adresse I2C de base, alors l'adresse I2C finale pourra être 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, ou 0x37
}


// ==================================================
// Fonction : configureCommunicationsI2CenModeEsclave
// ==================================================
void configureCommunicationsI2CenModeEsclave() {

  // -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // Explications techniques
  // -----------------------------------------------------------------------------------------------------------------------------------------------------------------------
  // But de cette fonction :
  //    -> configurer l'ATtiny en mode "esclave I2C", avec l'adresse i2c qui va bien (fonction de l'adresse de base spécifiée en haut, et de la position des switchs sur PCB
  // Remarques :
  //    -> sur un ATtiny412 (SOIC-8), on a pin_PA1=SDA et pin_PA2=SCL ; mais nul besoin de configurer ces pins PA1/PA2 en sortie manuellement, car le TWI s'en charge
  // -----------------------------------------------------------------------------------------------------------------------------------------------------------------------

  // Adresse I2C "esclave" (pour rappel, les adresses I2C sont codées sur 7 bits, auxquelles on rajoute un bit pour dire lecture ou écriture ; et ces 7 bits doivent figurer
  // en premier dans "l'octet d'adresse i2c", on doit décaler ces bits d'une position vers la gauche)
  TWI0.SADDR = (adresse_I2C_reelle << 1);

  // Configuration du registre SCTRLA
  TWI0.SCTRLA = TWI_ENABLE_bm           // Bit 0 : active l'I2C slave
              | TWI_SMEN_bm             // Bit 1 : active le "Smart Mode" (simplifie le code, au niveau de l'ISR)
              | TWI_PIEN_bm             // Bit 5 : active les interruptions sur "Stop", si la ligne suivante (adresse ou stop) est active
              | TWI_APIEN_bm            // Bit 6 : active les interruptions sur "Address ou Stop" (début ou fin de transmission i2c)
              | TWI_DIEN_bm;            // Bit 7 : active les interruptions sur "Data" ("milieu" des transmissions I2C)
}


// ============================================
// Fonction : configureSytemeDeCaptureLigneECHO
// ============================================
void configureSytemeDeCaptureLigneECHO() {
  
  // ----------------------------------------------------------------------------------------------------------------------------------------------------------
  // Explications techniques
  // ----------------------------------------------------------------------------------------------------------------------------------------------------------
  // But de cette fonction :
  //    -> Permettre de mesurer de manière automatique les durées d'impulsions sur la ligne ECHO, et de déclencher une interruption après chaque impulsion détectée
  // Rappel :
  //    -> les timers de notre ATtiny sont de base rythmés à 1,25 MHz (F_MAIN=20MHz et prescaler DIV/16) ; soit 52ms de période (en sachant que chaque impulsion sur
  //       la ligne ECHO fera au maximum 38ms, classiquement sur les HC-SR04)
  // Remarques :
  //    -> On va utiliser l'Event System ATtiny (EVSYS) pour connecter la pin PA2 (retour ECHO de notre capteur ultrason HC-SR04) à l'entrée de capture du TimerB ATtiny (TCB0)
  //    -> Puis on va configurer ce timer TCB0 en mode "input capture pulse-width measurement" pour qu'il :
  //          - mette à zéro son compteur CNT à chaque front montant détecté (mode "normal" du pulse-width de ce timer, en fait)
  //          - déclenche une interruption (ISR) à chaque front descendant détecté (la valeur CNT sera automatiquement copiée dans CCMP à ce moment là)
  //    -> Et le nombre de ticks comptés (CCMP) par ce compteur TCB0 nous donnera la durée d'impulsion ECHO (en admettant qu'il n'y ait pas le moindre débordement de comptage,
  //       et du fait qu'on connait la vitesse d'avance du compteur TCB0, qui est fonction de F_MAIN=20MHz et du prescaler principal réglé en division de fréquence par 16)
  // ----------------------------------------------------------------------------------------------------------------------------------------------------------
  
  // Configuration de l'Event System
  EVSYS.ASYNCCH0 = EVSYS_ASYNCCH0_PORTA_PIN7_gc;        // Relie Pin_PA7 à Event_CH0 (nota : on aurait pu prendre CH1, CH2, ou CH3 … c'était pareil ! J'ai arbitrairemnt pris CH0 ici)
  EVSYS.ASYNCUSER0 = EVSYS_ASYNCUSER0_ASYNCCH0_gc;      // Relie Event_CH0 à TCB0 (nota : ASYNCUSER0=TCB0, en fait)

  // Configuration du timer TCB0
  TCB0.CTRLB = TCB_CNTMODE_PW_gc;                       // Sélection du mode "Pulse Width measurement"
  TCB0.EVCTRL = TCB_CAPTEI_bm;                          // Active le système de capture d'événements (provoque : reset CNT à chaque front montant, et ISR à chaque front descendant avec valeur CCMP dispo)
  TCB0.INTCTRL = TCB_CAPT_bm;                           // Active les interruptions (ISR) sur capture effective
  TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm;   // Prescaler d'horloge TCB0 sur division par 1 (pas de ralentissement ici, donc) et activation du timer TCB0
}


// =============================================
// Fonction : configurePwmPersistantSurLigneTRIG
// =============================================
void configurePwmPersistantSurLigneTRIG() {

  // ----------------------------------------------------------------------------------------------------------------------------------------------------------
  // Explications techniques
  // ----------------------------------------------------------------------------------------------------------------------------------------------------------
  // But de cette fonction :
  //    -> générer un signal PWM persistant sur la ligne TRIG, d'une fréquence de 10 Hz pour avoir une période de 100ms (le capteur HC-SR04A devant éviter d'être
  //       piloté à moins de 60ms d'intervalle, pour éviter les échos parasites)
  // Rappel :
  //    -> les timers de notre ATtiny sont de base rythmés à 1,25 MHz (F_MAIN=20MHz et prescaler DIV/16) ; cela correspond à 52 ms environ de période
  // Remarques :
  //    -> ici, pour générer une impulsion de 10µs (requis sur la ligne TRIG) toutes les 100ms, on va encore diviser la fréquence d'horloge timer par 2,
  //       pour arriver à une fréquence de 5 Hz, soit une période de 104 ms (qu'on va limiter à 100 ms, en fixant le registre PER du timer)
  //    -> on utilisera le timer TCA0, en mode "single-slope PWM"
  //    -> du fait de la division de fréquence par deux supplémentaire, le timer TCA0 avancera à un rythme de 625 kHz (1.25MHz/2), soit toutes les 1,6 µs exactement
  //    -> on utilisera les registres PERBUF et CMP0BUF au lieu de PER et CMP0, car c'est une "bonne pratique" pour mettre à jour la durée de période/impulsion PWM
  //       de manière synchrone, pour éviter les indésirables/transitoires (bien que ce soit inutile ici, du fait qu'on configure le timer une fois pour toute au démarrage,
  //       sans mise à jour ultérieure des registres ; mais comme c'est une "bonne façon de faire", je vous la montre !)
  // ----------------------------------------------------------------------------------------------------------------------------------------------------------
  
  // Code indispensable, pour prendre le contrôle de TCA0 "contre" megatinycore // (cf. explications ici : https://github.com/SpenceKonde/megaTinyCore/blob/master/megaavr/extras/TakingOverTCA0.md)
  PORTMUX.CTRLC = PORTMUX_TCA00_DEFAULT_gc; // Désactive le déplacement de WO0 sur PA7 via PORTMUX (fait par megatinycore, de base), en ramenant WO0 sur PA3 (indispensable sur les ATtiny à 8 broches, ayant WO0 et WO3 sur la même pin)
  takeOverTCA0();                           // Désactive les fonctionnalités megatinycore utilisant le timer TCA0, et réinitialise ce timer (pour éviter le forçage en mode split 2x8 bits de megatinycore, etc.)

  // Configuration de la broche PA3 en sortie (c'est sur cette pin que sortira le signal PWM à destination de la pin TRIG)
  PORTA.DIRSET = PIN3_bm;

  // Configuration du timer TCA0
  TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP0EN_bm                // Active la sortie de génération de signal WO0 (qui va ressortir sur la pin PA3 de l'ATtiny)
                    | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;   // Active le mode "Single-Slope PWM Generation" (dans ce cas, CMP définit la durée d'impulsion, et PER la durée totale période)
  TCA0.SINGLE.PERBUF  = 62499;                            // Permet d'avoir une période PWM égale à 100 ms <--- (62499+1)*1,6µs = 0,1 seconde (soit 100 ms)
  TCA0.SINGLE.CMP0BUF = 7;                                // Permet d'avoir une durée d'impulsion PWM égale à 11.2 µs <--- 7*1,6µ = 11,2 µs (pour rappel : il faut au moins 10 µs pour le HC-SR04)
  TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV2_gc           // Divise la fréquence d'horloge reçue encore par 2 (donc 20MHz / 16 / 2 = 625 kHz)
                    | TCA_SINGLE_ENABLE_bm;               // Active le timer TCA0, maintenant que tout est bien configuré !
}


// ========================================================================
// Interruption TCB0 (survient à chaque front descendant sur ECHO/PA2, ici)
// ========================================================================
ISR(TCB0_INT_vect)
{
  // Lecture de la valeur capturée par TCB0
  uint16_t nbre_de_ticks_ecoules = TCB0.CCMP;
    
  // Effacement du drapeau d'interruption (impératif, sur tinyAVR)
  TCB0.INTFLAGS = TCB_CAPT_bm;
    
  // Conversion du nombre de ticks (TCB0.CCMP) en millimètres (mm). Pour cela, nous avons comme données :
  //    - un tick sur TCB0 équivaut à 0,8 µs (16/20MHz)
  //    - la vitesse du son est de 343.2 m/s (soit 0.3432 mm/µs)
  // On peut ainsi en déduire le temps d'aller/retour (soit 0.27456 mm/0.8µs), et le temps d'un aller ou retour seul (la distance réelle à mesurer, donc) = 0.27456/2=0.13728 mm/0.8µs
  // Donc pour 1 tick de 0.8 µs, la distance sera ainsi de 0.13728 mm.
  // Du coup, si on fait TCB0.CCMP*0.13728, on obtient la distance en mm, séparant le capteur à ultrason HC-SR04 de l'obstacle réfléchissant
    
  // Remarque technique : l'ATtiny412 n'ayant pas d'unité de calcul flottant, on évite ici de passer par des nombres à virgule (float),
  // histoire d'aller vite pour ne pas perdre de temps dans cet ISR (sinon on risquerait de potentiellement retarder une interruption I2C.
  // On préfère donc faire ici une multiplication puis division entière sur 32 bits (d'où les nombres marqués 'UL' ci-dessous, signifiant "unsigned long")
  
  if (index_buffer_verrouille == 0) {			// Pour éviter les données corrompues
  	 buffer_1_distance_mesuree_en_mm = (nbre_de_ticks_ecoules * 13728UL) / 100000UL;   // Nota : 13728 / 100000 = 0.13728 (astuce pour aller vite, dans le traitement des "nombres à virgule")
  } else {
     buffer_0_distance_mesuree_en_mm = (nbre_de_ticks_ecoules * 13728UL) / 100000UL;
  }
}


// ======================================================
// Interruption I2C (survient à chaque "appel du maître")
// ======================================================
ISR(TWI0_TWIS_vect)
{
  // Remarque : les interruptions I2C sont vraiment multiples ici ! En effet, on en aura une :
  //                - lorsqu'une adresse est reçue (sous-entendu qui correspond à l'adresse "esclave i2c" de ce projet, ou à l'adresse générale 0x00)
  //                - lorsque des données sont reçues
  //                - lorsqu'une erreur de bus est survenue
  //                - si un arrêt (STOP) a été détecté
  //                - etc
  uint8_t status = TWI0.SSTATUS;              // Nota : TWI0.SSTATUS est le registre du contrôleur TWI (Two-Wire Interface / I²C)
  
  if (status & TWI_APIF_bm) {                 // Cas "Address" ou "Stop"
    if (status & TWI_AP_bm) {                 // Cas "Address"
      if (status & TWI_DIR_bm) {              // Cas "Address" + "Master Read"
        index_buffer_verrouille ^= 1;         // Demande de lecture par le maître => on bascule de buffer (ce qui permet de travailler ici sur des données qui ne vont pas bouger malgré les ISR de TCB0, le temps que la transmission I2C soit achevée)
        TWI0.SCTRLB = TWI_SCMD_RESPONSE_gc;   // Répond pour poursuivre la transmission
      } else {                                // Cas "Address" + "Master Write" (cas qui ne doit jamais arriver ici, sauf en cas d'utilisation d'un 'scanner I2C', c'est pourquoi j'ai géré ce cas ici)
        TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc;  // Termine la transmission
      }
    } else {                                  // Cas "Stop"
      TWI0.SCTRLB = TWI_SCMD_COMPTRANS_gc;    // Termine la transmission
      index_octet_a_envoyer = 0;              // Réinitialise l'index de pointage d'octet, pour le tour d'après !
    }
  } else if (status & TWI_DIF_bm) {           // Cas "Data byte" (uniquement en "Read")
    if (index_octet_a_envoyer == 0) {         // Envoi du LSB (partie basse du mot de 16 bits)
      if (index_buffer_verrouille == 0) TWI0.SDATA = (uint8_t)buffer_0_distance_mesuree_en_mm;           // LSB du buffer #0
      if (index_buffer_verrouille == 1) TWI0.SDATA = (uint8_t)buffer_1_distance_mesuree_en_mm;           // LSB du buffer #1
      index_octet_a_envoyer = 1;              // Passage de l'index à 0->1, pour que la fois d'après, ce soit le MSB qui soit envoyé
    }
    else if (index_octet_a_envoyer == 1) {    // Envoi du MSB (partie haute du mot de 16 bits)
      if (index_buffer_verrouille == 0) TWI0.SDATA = (uint8_t)(buffer_0_distance_mesuree_en_mm >> 8);    // MSB du buffer #0
      if (index_buffer_verrouille == 1) TWI0.SDATA = (uint8_t)(buffer_1_distance_mesuree_en_mm >> 8);    // MSB du buffer #1
      index_octet_a_envoyer = 2;              // Valeur quelconque, différente de 0 ou 1, pour filtrer le "NACK du maître", qui déclenche une interruption également ici
    } else {
      TWI0.SDATA = 0;                         // Remarque : ce SDATA à 0 ne sera pas envoyé. Il est juste là pour ne pas bloquer la transmission avant de l'interrompre au niveau du cas "Stop" suivant
    }                                         //            (puisque nos deux octets ont déjà été envoyés, on devrait juste avoir un passage par là, lors du NACK du maître)
  }
}

Comme vous avez pu le voir, je vous ai mis énormément de commentaires dans ce programme ! Ainsi, il vous sera plus facile de comprendre à quoi sert chaque ligne, ou presque ! Et bien sûr, libre à vous d’effacer tous ces commentaires pour alléger ce programme, et y voir plus clair 🙂

Au niveau du fonctionnement de ce programme, la fonction setup résume bien sur quoi repose cet adaptateur I2C pour HC-SR04 :

  • on configure le diviseur d’horloge principal de l’ATtiny (qui tournera sur la base d’une fréquence CPU à « 20MHz interne », réduite ensuite ici pour coller à nos besoins spécifiques)
  • on détermine l’adresse I2C choisie sur le PCB, à partir de la tension analogique retournée par le réseau R/2R (on fait même 10 lectures d’affilé pour avoir une tension moyenne bien stable, que l’on va ensuite comparer à nos seuils/zones de niveaux)
  • on configure l’ATtiny pour qu’il fonctionne en « esclave I2C », c’est à dire pour qu’il devienne un véritable périphérique I²C, capable de répondre aux requêtes d’un « maître I2C »
  • on configure un système de capture d’impulsion sur une broche spécifique de l’ATtiny, afin de pouvoir mesurer les durées d’impulsions de la ligne ECHO, retournée par le capteur à ultrasons HC-SR04
  • on configure un signal PWM sur une autre broche spécifique de l’ATtiny, afin de générer des impulsions spécifiques, calibrées, et perpétuelles, à destination de la ligne TRIG de notre HC-SR04 (remarque : plus précisément, on générera ici une impulsion d’au moins 10µs toutes les 100ms, très exactement 11,2 µs toutes les 100 ms, comme le requiert l’émetteur/récepteur à ultrasons HC SR04)
  • puis on autorise (active) les interruptions sur l’ATtiny, pour donner vie à l’I2C et au système de capture d’échos (issus du HCSR04)

Ainsi divisé ainsi en « petits blocs de code », vous verrez que tout n’est pas si sorcier que ça (même si l’ensemble peut paraître complexe au début !).

Upload de ce programme dans l’ATtiny

Maintenant que nous avons vu la partie code, il va falloir téléverser tout ce contenu dans le microcontrôleur ATtiny en lui-même ! Pour ce faire, il va falloir :

  • paramétrer l’IDE Arduino, pour configurer l’ATtiny à programmer, en même temps que l’envoi du programme
  • brancher le PCB de ce projet sur un convertisseur USB/UPDI afin de pouvoir programmer l’ATtiny depuis un ordi

Alors commençons par la configuration de l’ATtiny dans l’IDE Arduino, après ouverture du programme à uploader. Pour faire cette configuration, il suffit en fait d’aller dans le menu Outils de l’IDE, et configurer les choses ainsi :

Menu Outils de l'IDE Arduino avec programme I2C pour capteur à ultrasons HC SR04, montrant les paramètres de configuration du µC ATtiny412 avec megaTinyCore

Si l’image n’est pas très nette, voici les options que j’ai sélectionné :

  • Type de carte : ATtiny 412/402/212/202
  • Chip : ATtiny412
  • Clock : 20 MHz internal (à respecter impérativement, car tous les calculs/configs de registres microcontrôleur reposent sur cette hypothèse de départ)
  • millis()/micros() Timer : TCD0 (car on va respectivement utiliser les timers TCA0 pour la génération de signal PWM, et TCB0 pour la mesure d’impulsion ECHO)
  • Wire Library Mode : Master or Slave (dans notre cas, il s’agira d’un « esclave i2c » uniquement)
  • Port : mettre le port de votre programmateur d’ATtiny (convertisseur USB/UPDI), une fois qu’il sera branché
  • Programmateur : Serial UPDI – SLOW, pour éviter les erreurs d’upload (comme la programmation est « très » rapide, ce n’est pas gênant d’utiliser le mode « SLOW » ici !)

Une fois tout cela paramétré, il ne reste plus qu’à brancher votre convertisseur USB/UPDI entre le port USB de votre ordi et le PCB du présent projet (broches +5V/UPDI/GND), et à uploader le programme. Voici comment ça se présente de mon côté, en image (avec les 3 fils de connexion carte <-> programmateur UPDI, et le câble USB vers l’ordi à droite) :

Photo carte ATtiny raccordée à un programmateur UPDI USB, pour uploader un programme dans le microcontrôleur à partir d'un ordinateur en USB directement

Au passage, lors de l’upload, vous verrez ceci tout en haut du terminal arduino :

Le croquis utilise 2060 octets (50%) de l'espace de stockage de programmes. Le maximum est de 4096 octets.
Les variables globales utilisent 15 octets (5%) de mémoire dynamique, ce qui laisse 241 octets pour les variables locales. Le maximum est de 256 octets.


Cela veut dire que l’ATtiny412 correspond parfaitement à nos besoins (50% de la mémoire prog et 5% de la RAM seront utilisés).

Du reste, toujours au niveau du terminal arduino après upload, vous devriez retrouver, quelque part dans la console, le texte suivant :

pymcuprog.programmer - INFO - Verifying...
Verify successful. Data in flash matches data in specified hex-file

Cela veut dire que le programme ATtiny a bien été uploadé sur la carte de ce projet ! Et cela étant fait, nous pouvons à présent passer à la partie « tests » 🙂

Programmes de tests arduino

Maintenant que nous avons notre carte ATtiny (adaptateur I2C pour HC SR04) programmée, il va falloir la tester. Et comme cette carte est ni plus ni moins qu’un « esclave i2c », alors il suffira de prendre une carte Arduino et de lui envoyer des commandes I2C, pour voir ce qu’elle retourne !

Alors commençons par câbler cela, à savoir : la carte de ce projet (adaptateur I2C pour HCSR04) et une carte Arduino Uno (par exemple) :

Borne sur PCB de ce projetBorne sur carte Arduino
+5V5V
SCLA5 (scl)
SDAA4 (sda)
GNDGND

En image, cela donne ceci :

Raccordement carte Arduino Uno sur carte adaptatrice I2C pour breakout board HC-SR04, avec alimentation 5 volts et lignes SDA/SCL pour faire des mesures de distances

Ainsi câblé, nous allons à présent pouvoir faire 2 tests successifs :

  • vérifier si la carte de ce projet répond bien sur le bus I²C (et si tel est le cas, à quelle adresse)
  • envoyer des demandes I2C toutes les secondes, et afficher ce qui est retourné par cette carte

Alors allons y, un test après l’autre !

Scanner I2C (pour vérifier si le périphérique i2c répond, et à quelle adresse sur le bus)

Le plus simple, pour vérifier si un esclave i2c est bien connecté au bus (et à quelle adresse), est d’utiliser ce qu’on appelle un « Scanner I2C ». En fait, il s’agit ni plus ni moins que d’un programme qui va interroger toutes les adresses I²C possibles (hormis les adresses 0 et 127 qui sont réservées), pour voir qui répond, et à quelle adresse.

Voici donc un exemple de programme « scanner i2c », fort utile au demeurant !

// ===================================================================================================
//   ______               _                  _///_ _           _                   _
//  /   _  \             (_)                |  ___| |         | |                 (_)
//  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
//  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
//  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
//  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
//                                                                                      | |
//                                                                                      \_|
// ===================================================================================================
//
//  Nom Projet  :       Scanner I2C
//
//  Desc Projet :       Permet de scanner toutes les adresses I2C (de 1 à 126, du fait que 0 et 127 sont réservées),
//                      à partir d'un Arduino Uno ou équivalent
//
//  Fichier     :       prgScannerI2C.ino
//  Créé le     :       05.08.2026
//  Auteur      :       Jérôme TOMSKI
//  Site        :       https://passionelectronique.fr/
//  GitHub      :       https://github.com/PassionElectronique/Programmes-Adaptateur-I2C-pour-capteur-HCSR04
//  Licence     :       https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr (BY-NC-ND 4.0 CC)
//
// =========================================================================================================

// Bibliothèque à inclure
#include <Wire.h>

// ========================
// Initialisation programme
// ========================
void setup() {
  
  // Initialisation des communications séries
  Serial.begin(9600);
  Wire.begin();

  // Affichage de démarrage, à destination du moniteur série d'Arduino IDE
  Serial.println("===========");
  Serial.println("Scanner I2C");
  Serial.println("===========");
  Serial.println();
  Serial.println("Démarrage du scan...");

  // Initialisation compteur de périphériques I2C trouvés
  byte nombreDePeripheriquesTrouves = 0;

  // Scanne les adresses I2C n°0 à 127, ou plutôt 1 à 126, du fait que les adresses 0 et 127 sont réservées
  for (byte adresse_I2C = 1; adresse_I2C <= 126; adresse_I2C++) {

    // Initialise la communication i2c
    Wire.beginTransmission(adresse_I2C);
    byte retour_I2C = Wire.endTransmission();

    // Si "endTransmission" retourne la valeur 0, alors on signale le périphérique détecté à l'adresse en cours
    if (retour_I2C == 0) {
      Serial.print("Périphérique détecté à l'adresse 0x");

      if (adresse_I2C < 16) Serial.print("0");
      Serial.print(adresse_I2C, HEX);
      Serial.print("  (decimal : ");
      Serial.print(adresse_I2C);
      Serial.println(")");

      nombreDePeripheriquesTrouves++;
    }
    // Si "endTransmission" retourne une valeur différente de 0, alors on n'en tient pas compte (erreur ou périphérique absent, dans ce cas)
    else {
      // Ne rien faire
    }
  }

  // Affichage du nombre de périphériques trouvés, sur le moniteur série
  Serial.println("Scan terminé !");
  Serial.println("");
  Serial.print("Bilan = ");
  if (nombreDePeripheriquesTrouves == 0) Serial.println("aucun périphérique I2C détecté");
  else if (nombreDePeripheriquesTrouves == 1) Serial.println("1 seul périphérique détecté");
  else {
    Serial.print(nombreDePeripheriquesTrouves);
    Serial.println(" périphériques détectés");
  }

  // Fin
  Serial.println("");
  Serial.println("(appuyez sur la touche RESET de votre arduino, pour relancer le scan, si besoin !)");
  
}


// =================
// Boucle principale
// =================
void loop() {
   // Vide, car tout se passe dans la fonction setup()
}

Comment on s’en sert ? En fait, c’est super simple ! Car il suffit d’uploader ce programme dans la carte Arduino Uno qu’on a câblé précédemment, puis d’ouvrir le Moniteur Série de l’IDE Arduino pour voir le résultat du scan. Tout simplement 🙂

Si tout se passe bien, vous devriez obtenir quelque chose comme ceci (ici, ma carte projet était configurée sur l’adresse 0x30, avec les jumpers sur A2/A1/A0 en position basse, donc) :

Moniteur série d'Arduino IDE montrant le programme Scanner I2C trouvant un périphérique i2c à l'adresse 0x30, avec affichage du nombre total d'éléments détectés

Vous pouvez d’ailleurs modifier les jumpers de la carte adaptatrice I2C pour HC SR04 pour sélectionner une autre adresse, mais il faudra couper l’alimentation de cette carte et la remettre ensuite, pour que le changement soit pris en compte (au démarrage du µC, en fait). Par exemple, si je mets A2 en position haute (A2=1), A1 en position haute (A1=1), et A0 en position basse (A0=0), alors c’est l’adresse 0x36 qui sera sélectionnée (comme décrit au dos du PCB du présent projet). Et on peut vérifier la nouvelle adresse i2c, en relançant le scanner I2C, comme visible ci-après :

Exemple de scanner I2C arduino trouvant un esclave i2c à l'adresse 0x36 sur le réseau, sur le moniteur série de l'IDE Arduino à 9600 bauds, avec liste des périphériques trouvés

Sinon, si aucun périphérique I2C n’est détecté (en cas de problème), alors un message d’erreur s’affichera à l’écran :

Vue moniteur série Arduino IDE avec programme Scanner I²C ne trouvant aucun périphérique sur le réseau bus I2C, après avoir parcouru toutes les adresses hors 0 et 127

Maintenant que nous avons vu que la carte répond bien sur le bus I2C, à l’adresse sélectionnée, nous pouvons donc passer aux « vrais essais », à proprement parler !

Test de mesures de distances avec cet adaptateur I2C pour module HC-SR04

Arrive le moment tant attendu de l’essai final, pour voir si cette carte I2C/HC-SR04 fait bien son boulot !

Pour ce faire, voici le code qu’il faudra uploader dans l’Arduino Uno, pour ce faire :

// ===================================================================================================
//   ______               _                  _///_ _           _                   _
//  /   _  \             (_)                |  ___| |         | |                 (_)
//  |  [_|  |__  ___  ___ _  ___  _ __      | |__ | | ___  ___| |_ _ __ ___  _ __  _  ___  _   _  ___
//  |   ___/ _ \| __|| __| |/ _ \| '_ \_____|  __|| |/ _ \/  _|  _| '__/   \| '_ \| |/   \| | | |/ _ \
//  |  |  | ( ) |__ ||__ | | ( ) | | | |____| |__ | |  __/| (_| |_| | | (_) | | | | | (_) | |_| |  __/
//  \__|   \__,_|___||___|_|\___/|_| [_|    \____/|_|\___|\____\__\_|  \___/|_| |_|_|\__  |\__,_|\___|
//                                                                                      | |
//                                                                                      \_|
// ===================================================================================================
//
//  Nom Projet  :       Adaptateur I2C pour Capteur à ultrasons HC-SR04
//
//  Desc Projet :       Programme de test pour l'adpatateur I2C pour module HC-SR04,
//                      fonctionnant sur Arduino Uno
//
//  Fichier     :       prgTestAdaptateurI2CpourHCSR04.ino
//  Créé le     :       05.08.2026
//  Auteur      :       Jérôme TOMSKI
//  Site        :       https://passionelectronique.fr/
//  GitHub      :       https://github.com/PassionElectronique/Programmes-Adaptateur-I2C-pour-capteur-HCSR04
//  Licence     :       https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr (BY-NC-ND 4.0 CC)
//
// =========================================================================================================

// Bibliothèque à inclure
#include <Wire.h>

// Constantes
#define ADRESSE_I2C_ADAPTATEUR_HCSR04 0x30


// ========================
// Initialisation programme
// ========================
void setup() {

  // Initialisation des communications séries
  Serial.begin(9600);
  Wire.begin();

  // Affichage de démarrage, à destination du moniteur série d'Arduino IDE
  Serial.println("=================================================");
  Serial.println("Programme de test /// adaptateur I2C pour HC-SR04");
  Serial.println("=================================================");
  Serial.println();

  // Petite pause, pour laisser le temps à l'adaptateur I2C pour HCSR04 de démarrer/se stabiliser
  delay(1000);
}


// =================
// Boucle principale
// =================
void loop() {

  // Interrogation de l'adaptateur I2C (demande 2 octets en retour)
  Wire.requestFrom(ADRESSE_I2C_ADAPTATEUR_HCSR04, 2);

  if (Wire.available() >= 2) {
    // Si deux octets reçus, on les lit
    uint8_t premier_octet = Wire.read();
    uint8_t deuxieme_octet = Wire.read();

    // Puis on en déduit la distance
    uint16_t distance_mm = (uint16_t)deuxieme_octet << 8 | premier_octet;

    // Et affichage (avec octets bruts en hexa, pour diagnostic temporaire)
    Serial.print("Octets recus : 0x");
    if (premier_octet < 0x10) Serial.print("0");
    Serial.print(premier_octet, HEX);
    Serial.print(" 0x");
    if (deuxieme_octet < 0x10) Serial.print("0");
    Serial.print(deuxieme_octet, HEX);
    Serial.print("  =>  Distance : ");
    Serial.print(distance_mm / 10.0, 1);  // Division par 10 pour passer en 'centimètres', puis passage en valeur 'float' au passage, et affichage avec 1 chiffre après la virgule
    Serial.println(" cm");

  } else {
    // Si l'adaptateur I2C n'a pas répondu, alors on affiche le fait qu'il n'y a pas de réponse retournée sur le bus I2C
    Serial.println("[ERREUR] pas de réponse reçue...");
  }

  // Attend 1 seconde, et reboucle
  delay(1000);
}

Comme vous le voyez, ce programme de test I²C/HCSR04 est assez court, et se résume simplement en :

  • demander la dernière mesure effectuée par la carte adaptatrice I2C (sur 2 octets, car la valeur mesurée est exprimée sur 16 bits) ; à noter que cette valeur est ni plus ni moins que la distance, en millimètres, séparant l’émetteur/récepteur HCSR04 d’un éventuel obstacle détectable devant lui
  • afficher ces octets sur le moniteur série, et la transcription de ce nombre 16 bits en centimètres (pour que ce soit plus parlant !)
  • et répéter cela de façon perpétuelle, à raison d’une demande de retour I2C par seconde !

Une fois uploadé, pour voir ce code en action, il suffit alors d’ouvrir le Moniteur Série de l’IDE Arduino, et mettre un obstacle en face du détecteur HC SR04 pour voir une distance s’afficher à l’écran toutes les secondes ! Pour ma part, j’ai mis une feuille de papier à environ 35 cm de distance, et puis l’ai rapprochée jusqu’à environ 2 cm du capteur HC-SR04. Et voici ce que j’ai pu relever sur mon moniteur série, à cette occasion :

Mesures de distances du capteur ultrason HC-SR04 reçues sur le bus I2C, et affichées sur le moniteur série de l'IDE Arduino, avec présentation des octets reçus

Sympa, non ? On pourrait d’ailleurs masquer les « octets reçus » (dont l’affichage m’a servi au débogage) et n’afficher que la valeur en centimètres. Comme ça tout aurait été encore plus clair 🙂

Améliorations possibles

Juste une idée qui m’est venu à la fin : on aurait pu intégrer un petit régulateur de tension 3,3V sur cette carte, afin de fournir cette tension aux 2 résistances pull-up I2C. Ainsi, cette carte I2C pour HC-SR04 aurait été compatible avec les microcontrôleurs 5V ET 3,3V !

Bien sûr, cela ne change rien au fait qu’il faudrait dans tous les cas fournir une alimentation 5V à cette carte, et non 3,3 volts, car :

  • le module HC-SR04 fonctionne en +5V « exclusivement » (mais on aurait pu opter pour un modèle HC-SR04P, fonctionnant aussi bien en 3,3 que 5 volts)
  • l’ATtiny a besoin d’une alimentation de +5V pour tourner à une fréquence de 20 MHz (mais on aurait également pu re-paramétrer tous les registres, afin de tourner avec une cadence processeur plus faible, permise en 3,3 volts !)

Et pourquoi pas faire un upgrade de ce PCB un jour, notamment si y’a d’autres petites améliorations à apporter à ce montage 😉

Liens et téléchargements

Pour ceux que ça intéresse, vous trouverez ici :

Adaptateur I2C pour HC SR04 : conclusion !

Voilà ! Nous voici au terme de cet article sur cet Adaptateur I2C pour module HC-SR04 ! J’espère qu’il vous aura plu, et saura vous inspirer pour vos prochains projets !

Par ailleurs, c’est un projet qui est très intéressant sur le plan matériel (réseau R/2R) et très riche sur le plan logiciel, car il permet de vous montrer comment, sur un microcontrôleur ATtiny :

  • générer un signal PWM (via le timer TCA0)
  • mesurer une durée d’impulsion (avec le timer TCB0)
  • et configurer un mode « esclave I2C », pour transformer la carte en véritable périphérique I²C !

Sur ce je vous laisse, et vous dis à bientôt, pour un prochain tuto 😉
Jérôme.

À découvrir aussi : un convertisseur I2C/PWM pour servomoteur

Ce contenu vous plaît ? Alors abonnez-vous à la Newsletter pour ne rien louper !

(*) Mis à jour le 13/08/2026

2 commentaires sur “Un adaptateur I2C pour capteur à ultrason HC-SR04, pour faciliter les mesures de distances !”

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Symbole danger point d'exclamation site PSE, triangle jaune avec contour noir, texte noir alerte au milieuAfin de filtrer au maximum les messages de type "spam" ou "inappropriés", chaque commentaire est soumis à modération, et validé manuellement. Du coup, il se peut que certains commentaires ne soient pas publiés, ou sinon, avec un peu de retard. Par ailleurs, j'ai malheureusement plus de messages à traiter que de temps pour y répondre ; c'est pourquoi je ne pourrais pas répondre à tout le monde. Désolé …

Soutenir le site

×

Soutenez le site Passion Électronique, en faisant un don via Paypal ou Stripe, afin que celui-ci reste le plus longtemps possible 100% gratuit et sans pub !
→ Logo PayPal
Faire un don via : PayPal
→ Logo Stripe
Faire un don via : Stripe
Merci à vous ❤️