Pb d'interférences I2C

Bonjour,

Sur ma MEGA, j'ai connecté en I2c un écran lcd 20x4 avec moule IC2 sur les broches 20 et 21 et une RTC DS3231 sur les broches SCL et SDA qui sont à côté de AREF et des broches USB.

Jusqu'à la tout fonctionne bien.

J'ai ajouté un module de mesure laser VL6180X en I2C aussi. Comme je n'avais plus de broches disponibles sur la MEGA, j"ai raccordé ma RTC sur les broches SDA/SCL qu'ils y avaient en réserve sur le module du lcd. J'ai fait un test, ça fonctionne.
J'ai ensuite connecté mon VL6180X sur les broches libérées par la RTC, installé la bibliothèque dediée et là : ça ne fonctionne plus.

Ca fonctionne un certain temps, et puis soit j'ai des caractères bizarres qui s'affichent sur le lcd, soit l'heure se fige (à l'affichage seulement). Je reset la MEGA et ça repart, avec l'heure sauvegardée par la pile, pour une durée inconnue.
Petite précision, ce module de mesure fonctionne parfaitement quand je l'essaie seul.

Quelqu'un a t il déjà eu ce problème?

Merci pour votre aide.

bonjour,
un code?
peut être un soucis d'alim

bonsoir,

voici le code :

#include <VL6180X.h>
#include <Wire.h>
#include <OneWire.h>
#include <config.h>
#include <ds3231.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,20,4);
VL6180X sensor;

  // Variables et broches pour la mesure de température d'eau et d'air
    float newTempEau; 
    float newTempAir;
    float tempEau;
    float tempAir;
    const byte BROCHE_ONEWIRE = 19;
    OneWire ds(BROCHE_ONEWIRE);

  // Adresses des capteurs de température */
    const byte SENSOR_ADDRESS_0[] = { 0x28, 0xFF, 0x66, 0x3F, 0x60, 0x16, 0x05, 0xCB };
    const byte SENSOR_ADDRESS_1[] = { 0x28, 0xFF, 0xE4, 0x02, 0xA4, 0x16, 0x05, 0x9B };
   
  // Variables et broches pour la RTC DS3231 & la gestion du temps
    struct ts t;
    const char *strJdlS[]  = {"Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"};
    const char *strMois[]  = {"Janvier", "Fevrier", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembr", "Octobre", "Novembr", "Decembr"};
    float tempRTC;
    
    

  // Variables et broches pour l'indication de niveau d'eau
    long sondeNiveau;  
    long sonde;
    int s;
    unsigned long previousMillis = 0; unsigned long previousMillis3 = 0; unsigned long previousMillis4 = 0;
    const int nHH = 40;
    const int nH = 41; 
    const int nN = 42; 
    const int nB = 43; 
    const int nBB = 44; 
    int etatLed2 = LOW; int etatLed3 = LOW;

  // Variables pour le programme
    int hProg; 
    const int Minute = 0;
    int jdM; 
    int k = 0;
    unsigned long time1;  unsigned long interval1 = 121807L;
    unsigned long time2;  unsigned long interval2 = 60000L;
    unsigned long time3;  unsigned long interval3 = 181000L;
    unsigned long previousMillis1 = 0;  unsigned long previousMillis2 = 0;
    unsigned long interval4 = 5000L;  unsigned long interval5 = 11000L;
    unsigned long interval6 = 20000L;
    //const int interval7 = 1000; 
    const int interval8 = 150;
    const int interval9 = 50; const int interval10 = 2;
    const int interval11 = 1000;
    unsigned long dureeAffich;
    int etatNiveau;
          
  // Fonction de démarrage de la prise de mesure de la température via un capteur DS18B20.
  
    void mesureTemp (const byte addr[]) //addr[] : Adresse du module 1-Wire détecté
      {
        // Reset le bus 1-Wire et sélectionne le capteur
        ds.reset();
        ds.select(addr);
        // Lance une prise de mesure de température et attend la fin de la mesure
        ds.write(0x44);
        delay(800);
      }
  
  // Fonction de récupération de la prise de mesure de la température via un capteur DS18B20
 
    float lectureMesureT (const byte addr[]) //addr[] : Adresse du module 1-Wire détecté
      {
        byte data[9]; //data[] : Données lues depuis le scratchpad
        // Reset le bus 1-Wire, sélectionne le capteur et envoie une demande de lecture du scratchpad
        ds.reset();
        ds.select(addr);
        ds.write(0xBE);
        // Lecture du scratchpad
        for (byte i = 0; i < 9; i++) 
          {
            data[i] = ds.read();
          }
        // Calcul de la température en degré Celsius
        return ((data[1] << 8) | data[0]) * 0.0625; 
      }
      
   // Affichage de l'heure et de la date

  void affichHeure()
      {
        lcd.setCursor(0,0); lcd.print("        ");
        if ((t.hour) < 10)
          {
            lcd.print(" ");
          }
        lcd.print(t.hour);
        lcd.print("H");
        if ((t.min) < 10)
          {
            lcd.print("0");
          }
        lcd.print(t.min); lcd.print("       ");
        
      }
    
  void affichDate()
      {
        lcd.setCursor(0,0); lcd.print(strJdlS[t.wday-1]); lcd.print(" ");
        lcd.print(t.mday); lcd.print(" ");
        lcd.print(strMois[t.mon-1]); lcd.print(" "); lcd.print(t.year);
      }  

 
  //  Lecture du niveau d'eau
  
   void lectureSondeNiveau()
       {
         sondeNiveau = (sensor.readRangeSingleMillimeters()); Serial.println(sondeNiveau);
         sonde += sondeNiveau; Serial.println(sonde);
         s += 1;Serial.println(s);
         previousMillis = millis();
       }

    void clignoLednHH()
      {
        if(millis() - previousMillis3 > interval8)
          {
             previousMillis3 = millis();  
                if (etatLed2 == LOW)
                  {
                    etatLed2 = HIGH;
                  }
                else
                  {
                    etatLed2 = LOW;
                  }         
             digitalWrite(nHH, etatLed2);
          }      
      }

    void clignoLednBB()
      {
        if(millis() - previousMillis4 > interval8)
          {
            previousMillis4 = millis();  
              if (etatLed3 == LOW)
                {
                  etatLed3 = HIGH;
                }
              else
                {
                  etatLed3 = LOW;
                }
            digitalWrite(nBB, etatLed3);
          }      
      }
          
void setup()

  {
  pinMode(nHH, OUTPUT); pinMode(nH, OUTPUT);
  pinMode(nN, OUTPUT); pinMode(nB, OUTPUT); pinMode(nBB, OUTPUT);
    
  lcd.begin();    
  Wire.begin();
  DS3231_init(DS3231_INTCN);
  sensor.init();
  sensor.configureDefault();
  Serial.begin(9600);
  
  }

void loop()

  { 
    {

    DS3231_get(&t);

    // Affichage de la date, de l'heure et des températures
  
    {
      if (millis() - dureeAffich < interval4) 
        {
          affichDate();
        }
    }
     }
    // Calcul HProg 
    {
     hProg = (t.hour*100) + t.min;
    }
     
  //  GESTION DU NIVEAU D'EAU

  {
      {
         if (millis() - previousMillis > interval11)
          {
            lectureSondeNiveau();
          }
      }
              
      if (s == 10)
        {
          sonde = sonde / 10; Serial.println(sonde);
        
          if (sonde <= 42) 
            {
              etatNiveau = 1;
            }
          else if (sonde > 42 && sonde <= 44) 
            {
              etatNiveau = 2;
            }
          else if (sonde > 44  && sonde <= 46) 
            {
              etatNiveau = 3;
            }
          else if (sonde > 46 && sonde <= 48) 
            {
              etatNiveau = 4;
            }
          else if (sonde > 48 && sonde <= 50) 
            {
              etatNiveau = 5;
            }
          else if (sonde >50 && sonde <= 52) 
            {
              etatNiveau = 6;
            }
          else
            {
              etatNiveau = 7;
            }  
        sonde = 0; s = 0;
        }
    
      switch (etatNiveau)
          {
            case 1:
              clignoLednHH();
              break;
            case 2:
              digitalWrite(nBB, HIGH); digitalWrite(nB, HIGH); digitalWrite(nN, HIGH); digitalWrite(nH,HIGH); digitalWrite(nHH, HIGH);
              break;
            case 3: 
              digitalWrite(nBB, HIGH); digitalWrite(nB, HIGH); digitalWrite(nN, HIGH); digitalWrite(nH,HIGH); digitalWrite(nHH, LOW);
              break;                 
            case 4: 
              digitalWrite(nBB, HIGH); digitalWrite(nB, HIGH); digitalWrite(nN, HIGH); digitalWrite(nH,LOW); digitalWrite(nHH, LOW);
              break;               
            case 5:
              digitalWrite(nBB, HIGH); digitalWrite(nB, HIGH); digitalWrite(nN, LOW); digitalWrite(nH,LOW); digitalWrite(nHH, LOW);
              break;             
            case 6: 
              digitalWrite(nBB, HIGH); digitalWrite(nB, LOW); digitalWrite(nN, LOW); digitalWrite(nH,LOW); digitalWrite(nHH, LOW);
              break;
            case 7:
              clignoLednBB();
              break;
            }
      }
  
//  AFFICHAGE DES TEMPERATURES RTC - AIR - EAU

  {
    float temperature[2];
    
      //  lecture des températures
    {
     if (millis() - time1 >= interval1)
       { 
         time1 = millis();
         mesureTemp(SENSOR_ADDRESS_0);
         temperature[0] = lectureMesureT(SENSOR_ADDRESS_0);
         newTempAir = temperature[0];
       }
     
    if (millis() - time2 >= interval2)
       {
         time2 = millis();
         mesureTemp(SENSOR_ADDRESS_1);
         temperature[1] = lectureMesureT(SENSOR_ADDRESS_1);
         newTempEau = temperature[1];
       }
   
     }
      }
        }

Pour l'alim, j'utilise une alim 5V séparée pour les relais et le lcd avec la masse en commun avec la MEGA et une alim pour la MEGA.

J'avoue que je galère un peu à trouver d'ou peut venir ce pb

Lorsqu'il y a plus d'un périphérique en I2C, il est recommandé de placer sur chacun de signaux SDA et SCL une résistance de rappel au VCC (4.7 k ou 10 k).

C'est peut-être à essayer.

Cordialement.

Pierre

Bonjour,

J'ai des 10k en réserve, je vais essayer.

Merci pour l'aide.

Lorsqu'il y a plus d'un périphérique en I2C, il est recommandé de placer sur chacun de signaux SDA et SCL une résistance de rappel au VCC (4.7 k ou 10 k).

Il ne faut pas être systématique car toutes les résistances des modules se retrouvent placées en parallèle et il peut y avoir un courant trop fort dans les transitors SCL ou SDA.

Les limites sont :

  • Inférieure : le courant dans le transistor du module "qui parle" ne doit pas être trop élevé pour ne pas détruire ce transistor --> souvent dans les datasheet on trouve un courant max de 3 mA.
  • supérieure : s'il y a beaucoup de modules les capacités d'entrée de chaque module s'additionnent.
    Le collecteur ouvert de l'I2C n'est rien d'autre qu'un transistor monté en émetteur commun.
    Dans un montage en émetteur commun l'impédance équivalente de sortie est égale à la résistance de collecteur ( la fameuse "pull-up" qui est bien une résistance de charge, nom d'une pipe).

Le phénomène qui détériore la transmission dans l'I2C est la charge du réseau RC qui déforme le signal.
Ce réseau est constitué par la résistance équivalente de collecteur et la somme des différentes capacités des entrées des modules connectés.

C'est pour cela qu'on dit que quand il y a trop de module (donc trop de capacité) il faut réduire la résistance équivalente de charge (pour fonctionner à valeur de R*C constant).

Comme déjà écrit , souvent on trouve souvent une valeur max de 3 mA dans les transistor de SCL et SDA, ce qui correspond à 1,700 k sous 5V.
Donc avec 10 modules, mettre 10 résistances de 4,7k ou même de 10 k en parallèle risque d’endommager le transistor.

Il y a une autre solution complètement oubliée : baisser la fréquence horloge.
Si le temps de montée du signal est trop grand devant la période horloge et bien on augmente la période horloge en baissant sa fréquence.

L'I2C normalise 2 fréquences principales 100kHz et 400kHz, il en existe bien d'autres.
On peut utiliser une fréquence inférieure à 100 kHz.

La bibliothèque arduino pour l'I2C est réglée par défaut sur 100 kHz mais elle propose une fonction pour changer cette fréquence.
Cette fonction n'est pas documentée car elle marchote.

Pour s'en servir il faut récupérer la formule dans la datasheet,

  1. Choisir une fréquence et calculer la valeur du registre, on trouve une valeur décimale.
  2. Prendre la valeur entière la plus proche et calculer la fréquence correspondante
  3. appeler la fonction "setClock() de la bibliothèque Wire avec la valeur de la fréquence calculée

Ce n'est pas terrible mais cela a le mérite d'exister !

oulalalalala

J'ai lu avec attention ce que 68tjs a écrit et cela amène de ma part deux questions (de débutant :slight_smile: ):

  • le système fonctionne parfaitement avec le lcd et la RTC en I2C sans résistance de rappel supplémentaire.

Comme je viens ajouter un troisième laron toujours en I2C, est il possible de ne mettre des résistances de
10k sur SDA/SCL du nouveau module seulement ? (ai-je dit une grosse bêtise).

La bibliothèque arduino pour l'I2C est réglée par défaut sur 100 kHz

.
D'après la datasheet du VL6180X, on parle d'une fréquence de 400kHz, le problème ne viendrait il pas de
là ?

Merci par avance pour votre aide

re

Je viens de lire ceci dans une ST du VL6180X :

Communication with the VL6180X
Communication with the VL6180X is via the I²C bus. The default 7-bit address of the
VL6180X is 0x29. It can be changed by the user to any 7-bit addresses by writing to the
I2C_SLAVE__DEVICE_ADDRESS {0x212} register. The SCL and SDA lines should each
have a pull-up resistor on the I²C bus.

Par contre, ils ne préconisent pas de valeur pour ces résistances.

EGT59:
... Comme je viens ajouter un troisième laron toujours en I2C, est il possible de ne mettre des résistances de
10k sur SDA/SCL du nouveau module seulement ? (ai-je dit une grosse bêtise). ...

Oui, vous avez dit une bêtise, mais rassurez-vous, tout le monde en dit (moi y compris :wink: )

Comme on parle de bus, les lignes SDA et SCL sont uniques et partagées par tous les périphériques qui y sont connectés. Donc si vous placez une résistance sur une de ces lignes, tous les périphériques la partage.

La norme dit qu'il ne faut pas dépasser 6 mA en mode Fast (400 kHz).

Sous 5 V cela veut dire que vous pouvez descendre jusqu'à 5 / 0.006 = 833 Ohms.

Je pense que pour trois périphériques, comme je vous l'ai déjà dit, 4.7 k à 10 k devrait faire l'affaire.

Si cela ne fonctionne pas, le problème vient certainement d'ailleurs (recouvrement d'infos sur le bus par les périphériques ?).

Cordialement.

Pierre

j'ai peut être trouvé mon erreur :

Le lcd et la RTC sont dans un boitier avec la MEGA et des connexions filaires de 20cm max.

Le module de mesure laser est connecté à la MEGA avec un câble "téléphone" 8 fils non torsadés par paire et sans blindage d'une longueur de 3 mètres.

Je viens de lire que l'I2C n'est pas fait pour des liaisons longues ou alors il faut amplifier le signal (je suis vraiment un débutant de 2éme classe :confused: )

Je vais donc revoir mon système de cablâge et ajouter des résistances de 10k.

Si quelqu'un a déjà amplifier un signal I2C pour 3m de distance, je suis preneur. Cela m'évitera d'autres bêtises.

Merci

Pour des longueurs de fils de 3 m et plus, il semble plus prudent d'opter pour un protocole voisin, le CAN (et son bus). De plus le CAN est quasi insensible aux parasites car justement conçu dès le départ pour les véhicules qui sont dans un environnement très parasité. A noter que le bus CAN est utilisé dans bien d'autres applications que l'automobile.

Ici se trouve un tutoriel intéressant sur l'emploi du bus CAN sur Arduino.

EGT59:
... Si quelqu'un a déjà amplifier un signal I2C pour 3m de distance, je suis preneur. ...

Amplifier un signal consiste à modifier ses niveaux. A moins que votre ligne de 3 mètres présente une résistance série importante par rapport à celle de rappel, l'amplification ne sert à rien. Par ailleurs, le retard amené par une telle longueur est négligeable : environ une quinzaine de nS.

Par contre, une telle ligne présente une capacité, selon la technologie qui peut avoisiner les 100 pF, voire plus. Avec une résistance de 10 kOhms, on avoisine des temps de commutation de l'ordre de 1 à 2 µS, ce qui devient rédhibitoire vis à vis d'une fréquence de 400 kHz, soit une période de 2.5 µS (mode Fast standard). Il faut alors considérablement réduire la valeur de la résistance de rappel sans toutefois dépasser le courant max autorisé.

Autre problème d'une telle ligne, sa longueur fait qu'elle peut ramasser d'importants parasites. Leur influence sera d'autant moindre que la résistance de rappel sera faible.

Une autre solution consiste à diminuer la fréquence d'horloge. S'il n'y a qu'un seul maître, en l’occurrence je pense que seul l'Arduino l'est dans votre cas, on peut le faire par l'instruction (en fin de setup() pour être sûr que c'est cette instruction qui prédominera) :

Wire.setClock(votre fréquence en Hz);

Cordialement.

Pierre

Oui mais attention a ce que j'ai déjà érit , si la fréquence demandée dans setClock n'est pas une valeur réalisable par le micro il ne se passera aucun changement et pire on ne verra rien, sauf à avoir un analyseur logique ou un oscillo..
C'est ce que j'ai vérifié avec une vieille 1.7.x, aucun changement n'a été annoncé pour les 1.8.x.

Il faut utiliser la formule de la datasheet :

  1. dans le sens valeur_registre = f(freq) ---> on obtient une valeur décimale pour valeur_registre
  2. prendre la valeur entière la plus proche pour "valeur_registre"
  3. Calculer la fréquence réellement faisable par le micro avec la forrmule dans le sens freq= f(valeur_registre)

Effectivement l'I2C n'est pas faite pour des longues distances.

L'I2C a été conçue pour faire dialoguer sur une même carte ce circuit imprimé des boîtiers de circuit intégré distant de quelques cm.
Comme ce bus est bidirectionel il est impossible d'utiliser des lignes de transmission adaptées au deux extrémités, seule condition pour masquer les effets capactifs et inductifs des fils de connexion.

Néanmoins il existe des circuits intégrés conçus spécialement pour augmenter les distances de communication. Par contre je ne sais pas s'ils sont d'un usage fréquent, seule condition pour qu'il soient facilement disponibles et que leur prix soit abordable.

PS : j'en profite pour rappeler qu'on trouve à moins de 10 € des analyseurs logiques, clone de Saleae, qui sont d'une grande utilité et entre autre pour le cas présent celle de faire fréquencemètre.
Un oscillo coûte cher, cet analyseur ne replace pas un oscillo mais rend de grands services.

Bonsoir,

Un grand merci pour toutes ces informations, j'ai appris mal de choses du coup.

Malheureusement, je reste avec mon problème. J'ai ajouté des résistances de 10k, j'ai raccourci les câbles, j'ai fait de multiples essais mais sans succès. Ca fonctionne un moment puis ça bloque.

Je vais opter pour une solution de "fainéant" : je vais traiter la partie mesure, qui me crée ces fameux soucis, avec une autre carte que j'ai en stock (Uno).
Au moins toutes mes fonctions seront opérationnelles (lcd et rtc sur la mega, VL6180X sur la uno). Elles n'entreront plus en conflit puisque séparées physiquement.

C'est pas top, je reconnais, mais au moins ça fonctionne. :slight_smile:

Le traitement des parasites en I2C est le principal problème, même avec des câbles de quelques centimètres, alors d'autant plus pour des longueurs de 3 m. Néanmoins pour le filtrage de glitchs jusqu'à 50 ns en I2C, il y a par exemple le PCA9517A.

Et un tutoriel pour calculer les meilleures résistances de tirages sur SDA/SCL :

EGT59:
... Malheureusement, je reste avec mon problème. J'ai ajouté des résistances de 10k, j'ai raccourci les câbles, ...

Je vous avait dir qu'avec de 10 k, vous étiez trop limite. Essayez avec de 2.2 k.

Cordialement.

Pierre

Bonjour,

Je viens vous donner quelques nouvelles :

  • ça ne fonctionne pas avec des 2.2k, ça éteint même le lcd et avance l'heure de la RTC.

Merci pour l'aide, j'ai appris pas mal de choses.

Vous avez tous de la patience avec les débutants.

Merci :slight_smile:

EGT59:
... - ça ne fonctionne pas avec des 2.2k, ça éteint même le lcd et avance l'heure de la RTC. ...

Ce n'est pas normal. Chaque résistance de 2.2 k va créer un courant de l'ordre de 2.5 mA, ce qui est très largement supportable (20 mA) par les sorties de l'Arduino. Vous devez avoir un autre problème électrique.

Cordialement.

Pierre

ce qui est très largement supportable (20 mA) par les sorties de l'Arduino.

Je ne suis pas aussi confiant.
Les sorties A4 et A5 n'ont pas les mêmes caractéristiques en mode I2C et en mode digital normal.
Ce n'est pas simple de le voir dans la datasheet mais c'est clairement écrit.
Chapitre 29.7 page 308 éditio. 8271J – 11/2015

  1. les seuils de basculement ne sont plus ~2,5V +/- 0,5V mais les seuils normaliséss CMOS.
  2. les caractéristiques sont données pour un courant de 3 mA et non pas 20 mA.
    J'en conclu, même si ce n'est pas limpide, qu'il ne faut pas dépasser 3 mA dans le transistor de sortie sans quoi le reste des caractéristiques n'est plus garanti en particulier le niveau bas qui du fait de la chute de tension dans la résistance Rdson du transistor de sortie va s'éloigner du potentiel GND.

Extrait de la datasheet :

Oui, c'est vrai, il ne faut pas compter sur les 20 mA d'une sortie numérique. Pour autant, dans le tableau ci-dessus, à la ligne "Value of pullup resistor", on y lit pour la valeur min: (Vcc-0.4) /0.003, soit pour un Vcc de 5 V, cela fait 1.53 kOhms. Je suis donc dans les clous avec la résistance proposée de 2.2 kOhms.

Cordialement.

Pierre