Skip to main content

Système d'arrosage automatique

TLDR; L'intégralité des fichiers sources sont téléchargeables en PJ de cette page sous forme d'archive ZIP.

Ce projet est la suite de celui commencé par les emplois-étudiant. Il avait presque abouti mais le circuit consommait trop de courant et l'autonomie de la batterie prévue n'était pas suffisante.

La première chose à faire était donc de vérifier la consommation de courant du circuit au repos et lorsque la pompe était activée. En faisant cela on pouvait constater que le circuit consommait environ 30mA au repos, ce qui est beaucoup trop. La batterie de 9V prévue étant capable de fournir ~650mAh. La batterie se vide ainsi en moins de 24h.

Première piste de solution : mettre le microcontrôleur en veille (sleep) pour économiser de l'énergie. Lorsqu'il se met en veille, le microcontrôleur éteint tous les systèmes non-essentiels et ne garde que le timer d'actif (ou une entrée pour un bouton en fonction du besoin) pour pouvoir se réveiller.

La bibliothèque Arduino LowPower n'étant pas compatible avec le microcontrôleur utilisé (ATTiny412), il a fallu se tourner vers une programmation directe des registres. Le projet Sleeping Lighthouse est un bon exemple pour notre besoin et nous allons reprendre ce code pour tester la consommation de courant. L'auteur du projet a mesuré une consommation d'à peine quelques µA.

Dans notre cas cela n'a malheureusement pas fonctionné... on était toujours à une consommation de l'ordre de 30mA.

Deuxième piste de solution : supprimer le régulateur de tension. Le régulateur de tension consomme une certaine quantité d'énergie pour passer des 9V de la batterie aux 5V nécessaires pour le microcontrôleur. Faisons un essai en le supprimant et en alimentant le circuit directement avec une alimentation externe.

On mesure désormais une consommation de 20mA... c'est mieux mais pas suffisant !

Troisième piste de solution : il n'y a pas de court-circuits mais il y a peut-être une fuite de courant en raison de mauvaises soudures. On va donc essayer avec un autre circuit avec le même microcontrôleur.

La consommation mesurée est de 300µA. On tient notre solution : usiner un nouveau circuit et le souder au four pour que les soudures soient parfaites, ne plus utiliser de batterie 9V avec un régulateur de tension mais une batterie CR2032 de 3V. C'est contre-intuitif puisque ces petites batteries ont une capacité plus faible de 250mAh, mais l'économie d'énergie réalisée en n'utilisant pas de régulateur de tension est bien plus importante.

Calcul de la consommation

On a une capacité de batterie en mAh, un circuit au repos qui consomme normalement 0.3mA et des pics de consommation de 150mA pour la pompe qui durent une trentaine de secondes une fois tous les 2 jours environ.

Cette réponse a été générée par l'IA Gemini avec le prompt suivant : "I want to estimate the longevity of my battery for a circuit i made. My battery has 240mAh of capacity, my circuit consumes 0.4mA of current most of the time and once every 2 days it consumes 150mA for 30 seconds. How can I do the math?"

  1. Convertir la durée d'impulsion en heures : La durée d'impulsion est donnée en secondes (30 secondes) mais on a besoin qu'elle soit en heures pour correspondre à la consommation continue (mesurée en milliAmpères-heures). Il y a 3600 secondes dans une heure, donc :

durée_impulsion_heures = durée_impulsion_secondes / 3600 = 30 secondes / 3600 = 0,0083 heures

  1. Calculer la consommation moyenne par impulsion par jour : Le circuit consomme 150 mA pendant 30 secondes tous les 2 jours. Pour trouver la consommation moyenne par jour, divisez la consommation totale par impulsion (courant x durée) par le nombre de jours par cycle d'impulsion :


consommation_moyenne_impulsion_par_jour = courant_impulsion * durée_impulsion_heures / nombre_de_jours_par_cycle_impulsion
consommation_moyenne_impulsion_par_jour = 150 mA * 0,0083 heures / 2 jours
consommation_moyenne_impulsion_par_jour = 0,6225 mAh/jour

  1. Calculer la consommation totale par jour : Additionnez la consommation continue et la consommation moyenne par impulsion par jour pour trouver la consommation quotidienne totale :


consommation_totale_par_jour = consommation_continue + consommation_moyenne_impulsion_par_jour
consommation_totale_par_jour = 0,4 mA/jour + 0,6225 mAh/jour
consommation_totale_par_jour = 1,0225 mAh/jour

  1. Estimer la durée de vie de la batterie: Divisez la capacité de la batterie (240 mAh) par la consommation quotidienne totale (1,0225 mAh/jour) pour obtenir l'autonomie estimée de la batterie en jours :


autonomie_batterie = capacité_batterie / consommation_totale_par_jour
autonomie_batterie = 240 mAh / 1,0225 mAh/jour
autonomie_batterie ≈ 23,4 jours

D'après ces calculs, la durée de vie de notre batterie peut être estimée à environ 23 jours.

Après réalisation du nouveau circuit on tombe même à une consommation de 0.1µA ! Ce qui allonge la durée de vie de notre batterie à 255 jours !

Le circuit

Schéma

Schematic.png

Le circuit est très simple : on a le capteur d'humidité résistif qui va renvoyer une tension analogique et une broche connectée à la pompe via un mosfet qui sert donc d'interrupteur.

Routage

On obtient ensuite un circuit simple face simple :

Layout.png

On peut noter que pour simplifier le circuit et s'économiser une résistance 0Ω, on alimente le capteur d'humidité directement par une broche du microcontrôleur. Comme le capteur est grosso-modo un simple pont diviseur de tension, il ne consomme que très peu de courant et cela ne pose aucun problème. Il ne faut juste pas oublier de l'activer dans le programme.

3D.png

Réalisation

Le PCB a ensuite été usiné sur la graveuse laser LPKF.

pcb-fresh.jpeg

Pour avoir une soudure parfaite il a été aussi nécessaire de graver un stencil en laiton pour pouvoir appliquer la pâte à braser.

soudure.jpeg

Le boîtier

Modélisation

Le boîtier a été modélisé dans Fusion360 en partant du modèle 3D du circuit exporté de KiCAD. Le PCB est fixé sur le boîtier à l'aide de 2 vis M3 et de 2 trous de fixation.

boitier3D.png

Le boîtier est refermé par un couvercle dévissable. Faire des vis dans Fusion est très facile, mais il y a des choses à respecter pour qu'elles s'impriment correctement. Pour cela, j'ai suivi le tutoriel suivant : 

boitier-coupe.png

Impression 3D

Le boîtier a été imprimé sur la Prusa MK4 et l'impression préparée sur Orca Slicer :

Orca.png

Seul le couvercle a besoin de quelques supports pour être imprimé correctement, j'ai donc configuré chaque objet individuellement (cf capture). Le reste des paramètres est classique : 0.28mm de hauteur de couche et 15% de remplissage.

Slice.png

Ce qui nous donne une durée d'impression de 1h09 pour 32g de PLA Prusament Galaxy Black.

[photo]

Découpe laser

La découpe a été réalisée sur la Trotec Speedy 360 en PMMA transparent 3mm.

Firmware

La programmation du microcontrôleur a été faite en passant directement par les registres pour la mise en veille et également pour la lecture du convertisseur digital analogique (ADC).

On injecte le programme dans l'ATTiny412 à l'aide d'un programmeur UPDI et du connecteur UPDI prévu sur le circuit.

[photo]

#include <avr/sleep.h>
#define  F_CPU  4000000 // 4 MHz

#define SLEEP     255       // sleep for 255 cycles, so 2mn
#define INTERVAL  720       // 1440mn in 24h, sleep is 2mn long, so 720 intervals
#define WATER     45000     // watering duration 45 seconds

volatile uint8_t rtcIntSemaphore;  // flag from RTS interrupt that may be used by polPUMP function
uint16_t counter = 0;

void setup() {
  initSerialGPIO();     // initialize serial and GPIO

  init32kOscRTCPIT();   // init the 32K internal Osc and RTC-PIT for interrupts
  initSleepMode();      // set up the sleep mode
}

void loop() {
  while(1) {
    counter += 1;

    if( counter > INTERVAL ){
      counter = 0;

      ADC0_init();
      int humidity = ADC0_read();

      if( humidity < 50 ){ // 0 means dry and wet is above 600
        pumpOn(); // turn on PUMP
        delay(WATER);
        pumpOff(); // turn off PUMP
      }
    }

    sleepNCycles(SLEEP); // cycles are about 500ms each / max 255 cycles so 2mn
  }
}

void ADC0_init(void) {
  // Disable digital input buffer
  PORTA.PIN2CTRL &= ~PORT_ISC_gm;
  PORTA.PIN2CTRL |= PORT_ISC_INPUT_DISABLE_gc;

  // Disable pull-up resistor 
  PORTA.PIN2CTRL &= ~PORT_PULLUPEN_bm; 

  ADC0.CTRLC =  ADC_PRESC_DIV4_gc       // CLK_PER divided by 4 
              | ADC_REFSEL_VDDREF_gc;   // VDD reference 
  ADC0.CTRLA =  ADC_RESSEL_10BIT_gc     // 10-bit mode 
              | ADC_ENABLE_bm;          // ADC Enable: enabled 
  ADC0.MUXPOS = ADC_MUXPOS_AIN2_gc;     // Select ADC channel 
}

uint16_t ADC0_read(void) {
  ADC0.COMMAND = ADC_STCONV_bm;                 // Start ADC conversion

  while ( !(ADC0.INTFLAGS & ADC_RESRDY_bm) );   // Wait until ADC conversion done 
  ADC0.INTFLAGS = ADC_RESRDY_bm;                // Clear the interrupt flag by writing 1

  return ADC0.RES;
}


//////////////////////////////////////////////////////////////////
//  ISR(RTC_PIT_vect)
//
//  Interrupt Service Routine for the RTC PIT interrupt
//
ISR(RTC_PIT_vect) {
  RTC.PITINTFLAGS = RTC_PI_bm;  // clear the interrupt flag   
  rtcIntSemaphore = 1;          // mark to PUMP function that interrupt has occurred

}


//////////////////////////////////////////////////////////////////
//  initSerialGPIO()
//
//  initialize all needed GPIO
//
void initSerialGPIO(void) {
  PORTA.DIRSET = PIN1_bm; //  set port to output for PUMP
  PORTA.OUTCLR = PIN1_bm; // turn off PUMP

  PORTA.DIRSET = PIN3_bm; //  set port to output for SENSOR
  PORTA.OUTSET = PIN3_bm; // turn on SENSOR
}


//////////////////////////////////////////////////////////////////
//  init32kOscRTCPIT()
//
//  initialize the internal ultra low power 32 kHz osc and periodic Interrupt timer
//
//  these two peripherals are interconnected in that the internal 32 kHz
//  osc will not start until a peripheral (PIT in this case) calls for it,
//  and the PIT interrupt should not be enabPUMP unit it is confirmed that
//  the 32 kHz osc is running and stable
//
//  Note that there is ERRATA on the RTC counter,  see:
//  https://ww1.microchip.com/downloads/en/DeviceDoc/ATtiny212-214-412-414-416-SilConErrataClarif-DS80000933A.pdf
//
//  That document currently states "Any write to the RTC.CTRLA register resets the 15-bit prescaler
//  resulting in a longer period on the current count or period".  So if you load the prescaler
//  value and then later enable the RTC (both on the same register) you get a very long
//  (max?) time period.  My solution to this problem is to always enable the RTC in the same
//  write that sets up the prescaler.  That seems to be an effective work-around. 
//
void init32kOscRTCPIT(void) {
  _PROTECTED_WRITE(CLKCTRL.OSC32KCTRLA, CLKCTRL_RUNSTDBY_bm);   // enable internal 32K osc   
  RTC.CLKSEL = RTC_CLKSEL_INT1K_gc;                             // Select 1.024 kHz from 32KHz Low Power Oscillator (OSCULP32K) as clock source
  RTC.PITCTRLA = RTC_PERIOD_CYC512_gc | RTC_PITEN_bm;           // Enable RTC-PIT with divisor of 512 (~500 milliseconds)
  while (!(CLKCTRL.MCLKSTATUS & CLKCTRL_OSC32KS_bm));           // wait until 32k osc clock has stabilized
  while (RTC.PITSTATUS > 0);                                    // wait for RTC.PITCTRLA synchronization to be achieved
  RTC.PITINTCTRL = RTC_PI_bm;                                   // allow interrupts from the PIT device  
}

//////////////////////////////////////////////////////////////////
//  initSleepMode()
//
//  this doesn't invoke sleep, it just sets up the type of sleep mode
//  to enter when the MCU is put to sleep by calling "sleep_cpu()"
//
void initSleepMode(void) {
  SLPCTRL.CTRLA = SLPCTRL_SMODE_PDOWN_gc;   // set sleep mode to "power down"
  SLPCTRL.CTRLA |= SLPCTRL_SEN_bm;          // enable sleep mode
}


//////////////////////////////////////////////////////////////////
//  sleepNCycles(uint16_t val)
//
//  cause system to go to sleep for N cycles, where each cycle
//  is about 500mS.  Note that processor does wakeup every 500 ms
//  but goes back to sleep almost immediately if it has not yet
//  done the desired number of cycles.
//
//  At 4 MHz MCU speed, the awake duration of each iternation in the
//  for loop below is approximately 1.5 microseconds.
//
void sleepNCycles(uint8_t val) {
  // first cycle may not be full cycle
  disableAllPeripherals(); // all off

  for (uint8_t i = 0; i < val ; i++) {
    sleep_cpu();  // put MCU to sleep
  }
  // now awake, sleep cycles complete, continue on
  initSerialGPIO(); // initialize serial and GPIO 
}


//////////////////////////////////////////////////////////////////
//  disableAllPeripherals()
//
//  To achieve minimum current consumption during sleep, it's best to
//  disable everything that might draw current during sleep.
//  This function disables everything except the RTC-PIT and 32K
//  internal low power oscillator.  This function does not disable
//  the counter used by the Arduino framework to implement millis(),
//  although the clock that drives that counter is suspended during sleep.
//
void disableAllPeripherals(void) {
  PORTA.DIRCLR = PIN0_bm; //  set port A0 to input
  PORTA.DIRCLR = PIN1_bm; //  set port A1 to input
  PORTA.DIRCLR = PIN2_bm; //  set port A2 to input
  PORTA.DIRCLR = PIN3_bm; //  set port A3 to input
  PORTA.DIRCLR = PIN6_bm; //  set port A4 to input
  PORTA.DIRCLR = PIN7_bm; //  set port A5 to input        
        
  PORTA.PIN0CTRL = PORT_ISC_INPUT_DISABLE_gc; // disable input buffers
  PORTA.PIN1CTRL = PORT_ISC_INPUT_DISABLE_gc;
  PORTA.PIN2CTRL = PORT_ISC_INPUT_DISABLE_gc;
  PORTA.PIN3CTRL = PORT_ISC_INPUT_DISABLE_gc;
  PORTA.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc;
  PORTA.PIN7CTRL = PORT_ISC_INPUT_DISABLE_gc;
}

void pumpOn(void)     {PORTA.OUTSET = PIN1_bm;} // turn on active high PUMP on PA1
void pumpOff(void)    {PORTA.OUTCLR = PIN1_bm;} // turn off active high PUMP on PA1

Assemblage du système

Montage des inserts

Les inserts filetés s'insèrent dans une impression 3D à l'aide d'un fer à souder. Il faut prévoir un trou de dimensions légèrement plus petit (-0.2mm) que l'insert lui-même de manière à bien accrocher le plastique quand il va fondre.

inserts.jpeg

Branchements

[photo]

Montage final

[photo]