Critique de mon circuit : lâchez-vous !

Hello tout le monde !

Je suis sur un projet semble-t-il un poil trop complexe pour mon niveau de débutant car je fais face à pas mal de problèmes.
Tous ces problèmes sont liés à l'électricité et non le code, car c'est en électricité que je pêche le plus.

En plus des bugs qui le démontrent, je suis pleinement conscient que j'ai des choses à revoir dans le circuit de mon shield dont vous trouverez une capture d'écran ci-dessous.

Pour info, le projet est le suivant : un terrarium automatisé !
3 éléments principaux : l'éclairage (Light), la ventilation (Fans), la brumisation (Misting).
Tout est piloté via un Arduino UNO R3 de chez Elegoo, et les réglages peuvent être modifiés via des potentiomètres (XXX POT sur le circuit).
Il est aussi possible de forcer la marche/arrêt d'un élément via des boutons-poussoirs rétro-éclairés (XXX SWITCH sur le circuit).
Les paramètres et les éventuels codes erreur sont affichés via 3 tubes Nixie IN-12.

Un détecteur de niveau d'eau (5V) permet de savoir quand empêcher la pompe de fonctionner pour la mettre en sécurité et d'afficher un message d'erreur sur les Nixies.

L'éclairage est assuré par 3 panneaux LED (en série), la ventilation par 4 ventilateurs de PC (en série), la brumisation par une pompe.

L'éclairage et la ventilation sont pilotés via PWM.

L'horloge est gérée par une RTC DS3231 de chez AZ Delivery. Celle-ci semble être connectée à rien du tout sur la carte car... je voyais pas où la placer :-[ En réalité, elle est connectée aux bons pins via des jumpers (OUI JE SAIS !).

Il y a 4 circuits :

  • 220VAC qui vient alimenter un convertisseur 220VAC/12VCC + la pompe de brumisation (externe au shield)
  • 170VCC qui alimente les tubes Nixies
  • 12VCC qui alimente les boutons-poussoirs, les ventilos et les panneaux LED
  • 5VCC qui alimente le reste du circuit

N'y aurait-il pas des interférences ?
La carte fait 1.6mm d'épaisseur.

Les 170VCC sont obtenus via un DC booster (externe à la carte) qui prend 12VCC en entrée (12Vout sur le schéma). Le 170VCC est réinjecté sur la carte via l'entrée "170Vin".
Pour bien comprendre le circuit des Nixies :

  • les barres N1/N2/N3 représentent les broches auxquelles je connecte les tubes via des jumpers (pas bon non plus car pas assez épais, et il existe sûrement de meilleures broches ! )
  • Les K155N1 sont les puces qui pilotent les tubes
  • Les résistances de 10K sont connectées aux anodes car les tubes ont besoin de 170V pour démarrer mais 145V pour se maintenir
  • Etant donné que je n'ai pas 1 million de pins, j'utilise 2 GPIO expanders connectés entre eux pour piloter les puces via un binaire
  • Les 2 GPIO expanders sont connectés via jumpers (outch) aux puces K155N1 via les broches ABCD que vous pouvez voir

D1, D2 et D3 sont des diodes Schottky.

Je précise que tout semblait fonctionner correctement quand je testais sur breadboard. Pour autant, les nixies ont été testés à part.

Je suis conscient d'un certains nombre de problèmes de conception, mais je ne vais volontairement rien dire pour ne pas biaiser votre raisonnement.

Je mentionnerai seulement des bugs rencontrés :

  • Modifier le PWM des ventilos impacte le PWM des lights => poser une diode pour empêcher le courant de partir dans l'autre sens ?
  • Après quelques temps, le potard du N2, que j'ai déjà remplacé, ne réagit plus qu'en fin de course. C'est à dire que pour une course de 0 à 100, il m'affiche 0 jusqu'à 90 puis fonctionne normalement. Il est grillé ?

Allez-y, lâchez les chiens !!! :slight_smile:

Hello,
Il nous faudrait un schéma

+1. Sans schéma il manque l'essentiel

Au stade où tu en es, tu fais tirer un proto de ton circuit, tu montes les composants, tu soudes et tu essayes. Si ça marche tant mieux. Si ça marche pas, tu viens demander de l'aide pour comprendre pourquoi. Et comme dit plus haut, tu montres un schéma et ton logiciel.

Arf, j'ai pas travaillé le schéma car je trouve le circuit beaucoup plus intuitif...
Je reviens vers vous quand ce sera fait, mais sans doute dans quelques jours du coup.

Edit : Vous voyez bien la capture d'écran de mon circuit en pièce-jointe ?

@JiPe38 :

JiPe38:
Au stade où tu en es, tu fais tirer un proto de ton circuit, tu montes les composants, tu soudes et tu essayes. Si ça marche tant mieux. Si ça marche pas, tu viens demander de l'aide pour comprendre pourquoi. Et comme dit plus haut, tu montres un schéma et ton logiciel.

Comme je disais plus haut, j'ai déjà tout monté et je rencontre quelques problèmes dont ceux cités.

Vous avez vraiment besoin du code ? Car je suis convaincu que mes problèmes sont strictement électriques et je veux voir comment optimiser mon circuit, en plus de corriger ses défauts.

Personne ne va passer des plombes à suivre les pistes de ton circuit imprimé pour reconstituer le schéma. Et sans le schéma et le logiciel, comment veux tu qu'on comprenne ce que tu as fait. Pour te donner une comparaison, tu es comme un mec qui montre une voiture qu'il vient de construire et demande qu'on critique la carrosserie et la peinture, alors que - tu le dis toi meme - quand tu t'installes au volant et que tu veux la conduire ça ne marche pas !

Oui la capture d'écran du circuit imprimé est visible mais faute de pouvoir analyser préalablement le fonctionnement c'est sans intérêt.

Le fonctionnnement s'exprime par la combinaison schéma+code.
La compréhension du fonctionnement est un preliminaire a l'analyse critique d'une proposition d'implantation et de routage

seuls commentaires à ce stade :
-je n'aime pas beaucoup le choix des couleurs pour représenter les deux couches et les pastillles
-ton logiciel ne sort pas une vue 3D ? ce serait 'encore plus intuitif' :slight_smile:

Arf, j'ai pas travaillé le schéma car je trouve le circuit beaucoup plus intuitif...

Casse figure assuré !

Comment faire pour voir s'il y a des erreurs de schéma ?
Comment faire pour savoir si l'implantation correspond au schéma électrique ?
D'après toi pourquoi on a inventé les logiciels intégrés Schématique/ Circuit Imprimé si ce n'est pour éviter de faire 10 tours de circuit (et j'ai vu pire) avant d'avoir quelque chose qui marchotte.
J'ai connu cette époque et franchement pour rien au monde, j'y reviendrai.

Schéma électrique indispensable et j'ajoute qu'il faut aussi les dimensions, car il y a du 220 V sur ton circuit et il y a des normes de sécurité à respecter.

Vous avez vraiment besoin du code ?

Si on te le demande, c'est qu'il a des raisons qui sont liées à notre expérience.
Tu lis le message épinglé "Règles du forum francophone", tu appliques ce qu'on demande et tu donnes tous les renseignements en une seule fois.

C'est la meilleure solution pour que tu ais la meilleure réponse dans les meilleurs délais.
Après, c'est toi qui vois.

Hey, du calme les gars !
Si je demandais si vous aviez vraiment besoin du code (tout en étant conscient des règles du forum), c'est parce qu'étant donné qu'il est un peu costaud, j'ai peur qu'il vous embrouille plus qu'autre chose et que ce soit contre-productif au final.

Le voici (en 2 parties, à cause de la limite à 9000 caractères) :

#include <Wire.h>
#include "RTClib.h"
#include <Manu_Nixie.h>
#include <Manu_PWM.h>

// pins
const int FAN_PIN         = 10; // ~ had to be pin 9 or 10 because changing their PWM frequency won't affect the time related functions
const int FAN_POT_PIN     = A3;
const int FAN_BTN_PIN     = 12;
const int LIGHT_PIN       = 11; // ~
const int LIGHT_POT_PIN   = A2;
const int LIGHT_BTN_PIN   = 4;
const int MIST_PIN        = 7;
const int MIST_POT_PIN    = A1;
const int MIST_BTN_PIN    = 13;
const int WATER_LEVEL_PIN = 2;
/*
   RTC SCL PIN = A5
   RTC SDA PIN = A4

   DO NOT connect A0 pin
*/

// fans intensity management
const int FAN_MIN_VAL       = 0;
const int FAN_MAX_VAL       = 100;
const int FAN_DEFAULT_VAL   = 100;

// light duration management
const int LIGHT_MIN_VAL     = 6;
const int LIGHT_MAX_VAL     = 14;
const int LIGHT_DEFAULT_VAL = 12;

// misting frequency management
const int MIST_MIN_VAL      = 0;
const int MIST_MAX_VAL      = 10;
const int MIST_DEFAULT_VAL  = 5;

// error codes
const int ERROR_START       = 123;
const int ERROR_RTC         = 111;
const int ERROR_WATER_FULL  = 888; // not used for now
const int ERROR_WATER_EMPTY = 999;

// editable values
int fan_val               = FAN_DEFAULT_VAL;
int light_val             = LIGHT_DEFAULT_VAL;
int mist_val              = MIST_DEFAULT_VAL;
int mistCount             = 0;
int nextMisting           = 0;
bool stopMistingOn        = false;

bool fan_pot_active       = false;
bool light_pot_active     = false;
bool mist_pot_active      = false;

int fan_pot_last_active   = millis();
int light_pot_last_active = millis();
int mist_pot_last_active  = millis();

int start_time            = 9;
int stop_time             = 21;

bool killError            = false;
bool errorModOn           = false;
bool errorWaterOn         = false;

int addr1                 = 0x20; // PCF8574 device 1 for Nixie display
int addr2                 = 0x21; // PCF8574 device 2 for Nixie display
Manu_Nixie nixie(addr1, addr2);

Manu_PWM pwm;

RTC_DS1307 rtc;

void displayError(int errorCode, bool activateErrorMode = true) {
  errorModOn = activateErrorMode;
  nixie.blinkHard(errorCode);
}

void calcStopTime(int duration) {
  stop_time = start_time + duration;
}

void handleFanPot() {
  int this_fan_val = readPot(FAN_POT_PIN, FAN_MIN_VAL, FAN_MAX_VAL);

  if (this_fan_val != fan_val) {
    fan_pot_active = true;
    fan_val = this_fan_val;
    nixie.printNumber(fan_val);
  } else {
    if (fan_pot_active) {
      fan_pot_active = false;
      nixie.printNumber(fan_val);
      fan_pot_last_active = millis();
    } else {
      int milliseconds = millis(); 
      if (milliseconds - fan_pot_last_active > 550 && milliseconds - fan_pot_last_active <= 1550) {
        nixie.printNumber(fan_val);
        delay(1000);
      }
    }
  }
  delay(50);
}

void handleLightPot() {
  int this_light_val = readPot(LIGHT_POT_PIN, LIGHT_MIN_VAL, LIGHT_MAX_VAL);

  if (this_light_val != light_val) {
    light_pot_active = true;
    light_val = this_light_val;
    nixie.printNumber(light_val);
  } else {
    if (light_pot_active) {
      light_pot_active = false;
      nixie.printNumber(light_val);
      light_pot_last_active = millis();
      calcStopTime(light_val);
    } else {
      int milliseconds = millis(); 
      if (milliseconds - light_pot_last_active > 550 && milliseconds - light_pot_last_active <= 1550) {
        nixie.printNumber(light_val);
        delay(1000);
      }
    }
  }
  delay(50);
}

void handleMistPot() {
  int this_mist_val = readPot(MIST_POT_PIN, MIST_MIN_VAL, MIST_MAX_VAL);

  if (this_mist_val != mist_val) {
    mist_pot_active = true;
    mist_val = this_mist_val;
    nixie.printNumber(mist_val);
  } else {
    if (mist_pot_active) {
      mist_pot_active = false;
      nixie.printNumber(mist_val);
      mist_pot_last_active = millis();
    } else {
      int milliseconds = millis(); 
      if (milliseconds - mist_pot_last_active > 550 && milliseconds - mist_pot_last_active <= 1550) {
        nixie.printNumber(mist_val);
        delay(1000);
      }
    }
  }
  delay(50);
}

bool isNightTime(DateTime thisNow) {
  return thisNow.hour() < start_time || thisNow.hour() >= stop_time;
}

bool isDimupTime(DateTime thisNow) {
  return thisNow.hour() == start_time && thisNow.minute() <= 30;
}

bool isDimdownTime(DateTime thisNow) {
  return thisNow.hour() == (stop_time - 1) && thisNow.minute() >= 30;
}

void dimUpLight(int pin, int now_min) {
  now_min = now_min == 0 ? 1 : now_min;
  int dimVal = map(now_min, 0, 30, 0, 255);
  analogWrite(pin, dimVal);
}

void dimUpFans(int pin, int now_min, int now_sec, int pwmMaxVal = 255) {
  int dimVal = map(now_min, 0, 30, 0, pwmMaxVal);
  if (dimVal < 70) {
    dimVal = 0; // the fans won't move before 70 anyway. So let's cut the noise out.
  }
  
  if (now_sec == 0 && dimVal == 70) {
    analogWrite(pin, 255); // just to give the fans a kick so they keep on after being set the dimVal
    delay(1000);
  }
  analogWrite(pin, dimVal);
}

void dimDown(int pin, int now_min, int pwmMaxVal = 255) {
  int dimVal = map(now_min, 59, 30, 0, pwmMaxVal);
  analogWrite(pin, dimVal);
}

void checkWaterLevel() {
  bool water_level_down = digitalRead(WATER_LEVEL_PIN);

  if (water_level_down) {
    displayError(ERROR_WATER_EMPTY);
    errorWaterOn = true;
  } else {
    errorModOn = false;
    errorWaterOn = false;
  }
}

La 2e partie :

void manageMisting(DateTime thisNow) {

  if (!errorWaterOn) {
    if (!isNightTime(thisNow)) {
      if (mistCount < mist_val) {
        int thisMinute = thisNow.hour() * 60 + thisNow.minute();
        int firstMinuteOfTheDay = start_time * 60 + 30; // mists 30 mins after day time
        int lastMinuteOfTheDay  = stop_time * 60 - 30;  // mists 30 mins before night time

        if (thisMinute == firstMinuteOfTheDay || thisMinute == lastMinuteOfTheDay || thisMinute == nextMisting) {
          digitalWrite(MIST_PIN, HIGH);
          stopMistingOn = false;
        } else if (thisMinute == nextMisting + 1 || thisMinute == firstMinuteOfTheDay + 1 || thisMinute == lastMinuteOfTheDay + 1) {
          stopMisting(thisMinute, thisNow);
        } else {
          digitalWrite(MIST_PIN, LOW);
          stopMistingOn = false;
        }
      }
    } else {
      mistCount = 0;
      digitalWrite(MIST_PIN, LOW);
    }
  } else {
    digitalWrite(MIST_PIN, LOW);
  }
}

void stopMisting(int thisMinute, DateTime thisNow) {
  if (!stopMistingOn) { // so it increments mistCount just once for this whole minute
    mistCount++;
    stopMistingOn = true;
    setNextMisting(thisMinute, thisNow);
  }
  digitalWrite(MIST_PIN, LOW);
}

void setNextMisting(int thisMinute, DateTime thisNow) {

  if (mistCount < mist_val && mist_val - mistCount > 1 && !isDimdownTime(thisNow)) { // so the last minute of the day (before diming down) remains as the last misting
    int mistsRemaining = mist_val - mistCount;
    int spareTime = mistsRemaining * 30; // forces to spare 30mins minimum between every mistings
    int minutesLeft = stop_time * 60 - thisMinute - spareTime;
    nextMisting = thisMinute + random(minutesLeft) + 1; // +1 is to avoid setting nextMisting to thisMinute (=now)
  } else {
    nextMisting = 0;
  }
}

void clearDisplay() {
  nixie.blank();
}

bool handleFanBtn(DateTime thisNow) {
  bool thisState = digitalRead(FAN_BTN_PIN);
  
  if (thisState == 1) {
    if (isNightTime(thisNow)) {
      analogWrite(FAN_PIN, 255);
    } else {
      analogWrite(FAN_PIN, 0);
    }
  }

  return thisState;
}

bool handleLightBtn(DateTime thisNow) {
  bool thisState = digitalRead(LIGHT_BTN_PIN);
  
  if (thisState == 1) {
    if (isNightTime(thisNow)) {
      analogWrite(LIGHT_PIN, 255);
    } else {
      analogWrite(LIGHT_PIN, 0);
    }
  }

  return thisState;
}

bool handleMistBtn() {
  bool thisState = digitalRead(MIST_BTN_PIN);

  if (thisState == 1) {
    digitalWrite(MIST_PIN, HIGH);
  }

  return thisState;
}

// gets more stable potentiometer reading
int readPot(byte pin, int minVal, int maxVal) {

  int potValues[5];
  int nbPotValues = 5;
  for (int i = 0; i < nbPotValues; i++) {
    int thisVal = analogRead(pin);
    potValues[i] = thisVal;
  }

  int goodVal = average(potValues, nbPotValues);

  return map(goodVal,0,1023,minVal,maxVal); 
}

int average (int * array, int len) {
  long sum = 0L ;
  for (int i = 0 ; i < len ; i++)
    sum += array [i] ;
  return  ((int) sum) / len ;
}

/**
 * ************************
 * ************************
 * ************************
 * ************************
   SETUP and LOOP functions
 * ************************
 * ************************
 * ************************
 * ************************
*/
void setup () {

  // Set pin 10's PWM frequency to 31 Hz (31250/1024 = 31) so the fans are less noisy when the electric frequency is low
  pwm.setFrequency(FAN_PIN, 1024);

  // *********** RTC setup *************
  if (! rtc.begin() || ! rtc.isrunning()) {
    killError = true;
  } else {
    randomSeed(analogRead(0)); // considering PIN A0 is NOT connected

    //rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); => this sets the date to the compile date/time
    rtc.adjust(DateTime(2020, 12, 17, 9, 0, 0)); // => this sets the date to a whatever date but 9am everytime the Arduino is rebooted
    
    // *********** RTC setup *************

    // *********** Fans setup ************
    pinMode(FAN_PIN, OUTPUT);
    analogWrite(FAN_PIN, 0);
    // *********** Fans setup ************

    // *********** Lights setup ************
    pinMode(LIGHT_PIN, OUTPUT);
    analogWrite(LIGHT_PIN, 0);
    // *********** Lights setup ************

    // *********** Misting setup ************
    pinMode(WATER_LEVEL_PIN, INPUT);
    pinMode(MIST_PIN, OUTPUT);
    digitalWrite(MIST_PIN, LOW);
    // *********** Misting setup ************

    // Display start code
    displayError(ERROR_START, false);

    delay(1000);
  }
}

void loop () {
  if (killError) {
    displayError(ERROR_RTC);
  } else {

    checkWaterLevel();
    if (!errorModOn) {
      clearDisplay();
      // so the potentiometers aren't usable when an error message is being displayed
      handleFanPot();
      handleLightPot();
      handleMistPot();
    }
 
    DateTime thisNow = rtc.now();

    bool isFanBtnOn   = handleFanBtn(thisNow);
    bool isLightBtnOn = handleLightBtn(thisNow);
    bool isMistBtnOn  = handleMistBtn();

    if (!isNightTime(thisNow)) {
      if (!isMistBtnOn) {
        manageMisting(thisNow);
      }

      int nowMin    = thisNow.minute();
      int nowSec    = thisNow.second();
      int fanPwmVal = map(fan_val, FAN_MIN_VAL, FAN_MAX_VAL, 0, 255);

      if (isDimupTime(thisNow)) {
        if (!isFanBtnOn){
          dimUpFans(FAN_PIN, nowMin, nowSec, fanPwmVal);
        }
        if (!isLightBtnOn){
          dimUpLight(LIGHT_PIN, nowMin);
        }
      } else if (isDimdownTime(thisNow)) {
        if (!isFanBtnOn){
          dimDown(FAN_PIN, nowMin, fanPwmVal);
        }
        if (!isLightBtnOn){
          dimDown(LIGHT_PIN, nowMin);
        }
      } else {
        if (!isFanBtnOn){
          analogWrite(FAN_PIN, fanPwmVal);
        }
        if (!isLightBtnOn){
          analogWrite(LIGHT_PIN, 255);
        }
      }
    } else {
      if (!isFanBtnOn){
        analogWrite(FAN_PIN, 0);
      }
      if (!isLightBtnOn){
        analogWrite(LIGHT_PIN, 0);
      }
      if (!isMistBtnOn) {
        digitalWrite(MIST_PIN, LOW);
      }
    }
  }
}

68tjs:
Schéma électrique indispensable et j'ajoute qu'il faut aussi les dimensions, car il y a du 220 V sur ton circuit et il y a des normes de sécurité à respecter.

Ok donc voici les dimensions : 180.8 mm* 70.8 mm
Pour l'épaisseur, je suis sur du 1.6mm, comme indiqué plus haut.

Ok donc voici les dimensions : 180.8 mm* 70.8 mm

La réponse ne me rassure pas. :confused:

Les dimensions de la plaque, je m'en contrefiche, c'est l'écartement entre pistes et la largeur des pistes qui sont importantes.
Je suis inquiet.

As-tu entendu parler des arcs électriques ?

1 schema est plus facile a lire qu'une implantation
il n'y a pas besoin du schema des nixies+k155+pcf8574, si j'ai compris les abcd des k155 sont commandés par 8574 qui est piloté en i2c ? c'es le h manu_nixie qui s'en occupe ?
le schéma : les alims les pot les mosfets les relais, l'I2C, le potard N2 il doit dissiper trop de puissance pour devenir "mort" au bout d'un temps

Si tu as le schema, je peux faire la partie PCB...

les contraintes entre 2 potentiel 230V c'est 4mm
230V et petite tension c'est 6/7mm
et 230V et la terre c'est 7/8mm

jeanmanu:
Arf, j'ai pas travaillé le schéma car je trouve le circuit beaucoup plus intuitif...
Je reviens vers vous quand ce sera fait, mais sans doute dans quelques jours du coup.

Le problème en faisant comme ça, c'est que le schéma présentera ce que tu voulais et pas ce que tu as implanté. Lorsqu'on fait le schéma avant le logiciel de CAO assure justement cette partie du travail.

Si le schéma ET le routage sont faits manuellement sans logiciel CAO, rien ne garantit que le routage soit conforme au schéma.
Deuxio : un logiciel CAO permettra de régler l'espacement entre pistes pour le 230V.

Sans schema c'est compliqué, qui dit qu'il manque pas des capa de decouplagge par exemple ou autre petit composant mais essentiel souvent...

et

TRoisio : eviter les piste a 90°

hazerty565:
TRoisio : eviter les piste a 90°

Alors là il faut être plus précis :

Les croisement de pistes doivent se faire à 90°. C'est impératif pour minimiser les couplages.

Si la piste doit être parcourue par des courants élevés, ou par des signaux haute fréquence il faut éviter les coudes à 90°.
De plus les coudes à 90° ne facilitent pas la gravure du cuivre.

L'idéal étant de remplacer le coude à 90° par une courbe, malheureusement kicad ne le fait pas (encore ?) et cette possibilité se trouve plutôt dans des logiciels pro spécialisé hyper.

Avec un logiciel courant il suffit de remplacer le coude à 90° par deux coudes à 45°.
'Cest amplement suffisant dans la pluspart des applications.

La raison est que les électrons ne longent pas les bords de la piste, ils vont au plus dirrect.
La conséquence est qu'au niveau du coude à 90° la section "utile" de la piste est beaucoup plus faible que la section "géométrique" de la piste.
Ce qui se traduit par une résistance locale de la piste, ce qui induit une élévation de température qui peut provoquer un décollement de la piste.

oui abus de language, je parlais des coudes à 90°

  • éviter les coudes à 135°
    135.png

135.png