samedi 13 avril 2013

Optimisation de la consommation d'une Arduino

Dans le cadre d'une réflexion sur la conception d'une centrale domotique, je me suis intéressé aux possibilités de réduction d'une carte Arduino. En effet, les mesures effectuées par la centrale seront à un rythme d'environ une mesure par minute. Il n'est donc pas utile que la carte "travaille" en permanence.
 

Cet article est la synthèse (et la traduction) des informations qui n'ont été utiles. Celles-ci proviennent du site officiel Arduino (http://playground.arduino.cc/Main/InterfacingWithHardware) et d'une très bonne série d'articles(en Anglais) que vous trouverez à l'adresse : http://donalmorrissey.blogspot.fr/2010/04/putting-arduino-diecimila-to-sleep-part.html.

Il existe plusieurs façons de réduire la consommation d'une carte Arduino. On peut :
  1. réduire la fréquence à laquelle la carte fonctionne,
  2. désactiver certaines parties de la carte,
  3. mettre la carte "en sommeil" durant un interval de temps
Je n'ai pas testé la 1ère solution bien que ce soit celle qui semble la plus prometteuse en terme de gain. En effet, la modification de fréquence entraine des effets de bord sur toutes les fonctions sensibles au temps (timer, échange de données, ...). Cela ne m'est pas apparu compatible avec mon usage (suspendre la carte entre 2 mesures) et peut entrainer des problèmes de stabilité de la carte.
 

La 2ème solution est simple à utiliser (une instruction par module à désactiver) mais n'apporte qu'un faible gain. Les modules concernés sont l'UART, les timers et le convertisseur ADC des entrées analogiques. Dans mon cas, je ne peux les désactiver car j'utilise les timers pour des interruptions, et les entrées analogiques pour les mesures.
 

Reste la 3ème solution qui est celle décrite dans cet article.
 

Il est à noter que la carte Arduino UNO (celle que j'utilise) n'est pas la mieux conçu du point de vue optimisation de la consommation. Elle dispose toutefois de différents modes de gestion de l'alimentation et de mise "en sommeil" qui permettent de réduire sa consommation.

Les différents mode de "veille" 

Les différents modes, du moins au plus économique, sont :
  • SLEEP_MODE_IDLE
  • SLEEP_MODE_ADC
  • SLEEP_MODE_PWR_SAVE
  • SLEEP_MODE_STANDBY
  • SLEEP_MODE_PWR_DOWN
A noter que, au moins le mode consomme, au moins il comporte de fonctionnalités. Ainsi, le mode PWR_DOWN, qui est celui qui consomme le moins, ne peut être réveillé que par une interruption externe ou par le WDT (watch dog timer). Dans le mode IDLE, les timers, l'ADC, l'UART, .. restent actifs.

Les timers d'une Arduino et les délais de veille 

Quand le micro-controller entre en veille dans le mode choisi, l'exécution du code est mis en pause. Pour reprendre l'exécution du code, il faut "réveiller" la carte. Cela peut se faire par un timer interne, une interruption externe, ou le WDT (watch dog timer).

Le tableau ci-dessous liste les différents timers utilisables pour réveiller la carte Arduino. Il indique pour chaque timer, la durée maximale d'attente et le mode de consommation le plus bas dans lequel la carte peut être placée.

TimerMax Timeout Min Power Mode
Timer016.4msIDLE
Timer14.1sIDLE
Timer216.4msPOWER_SAVE
Watch Dog Timer8sPWR_DOWN

A noter que le mode PWR_DOWN est celui qui consomme le moins.


Si vous avez besoin d'un temps d'attente supérieur à celui indiqué, il suffit d'enchainer le temps d'attente de base avec un compteur logiciel qui se décrémente jusqu'à atteindre le temps souhaité.
Dans mon cas d'une mesure par minute, je peux faire une boucle de 8 attentes du watch dog timer, ce qui me donne une pause globale de 8 * 8 = 64 secondes. Si je veux exactement 60 secondes, il suffit de réduire la dernière attente à 4 secondes au lieu de 8.

On peut également utiliser un timer externe qui aura en charge de réveiller la carte en déclenchant une interruption après le délai souhaité.

Gestion logicielle du mode de veille

Au niveau du code, il existe plusieurs fonctions pour gérer le mode de mise en veille :
  • set_sleep_mode(mode) - Configure la carte dans le mode de veille passé en paramètre
  • sleep_enable() - active le mode veille
  • sleep_mode() - déclenche la mise en veille. Veillez à avoir tout d'abord activé le mécanisme de révei.
  • sleep_disable() - désactive le mode veille

Solution retenue et exemple de code

Vous trouverez le détail des mesures et des gains dans les différents modes, sur le site indiqué dans l'introduction. Dans mon cas, j'ai choisi l'approche par WDT (watch dog timer) car c'est celle qui offre le temps de pause de base le plus long, et qui offre le meilleur mode de veille.

Le WDT des Arduino a une seule source pour le piloter : son propre oscillateur intene à 128 Khz (les timers internes de l'Arduino peuvent utiliser l'horloge à 16Mhz ou une horloge externe). C'est la présence de cet oscillateur dédié qui permet au WDT d'être activé dans le mode d'alimentation minimal SLEEP_MODE_PWR_DOWN.

Le WDT a aussi un étage d'adaptation qui est utilisé pour configurer le délai d'attente. Il prend en charge des délais de 16 ms à 8 secondes :

Pour terminer, voici le code que j'utilise :
//Optimisation de la consommation
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/wdt.h>

volatile int f_wdt=1;

// Watchdog Interrupt Service est exécité lors d'un timeout du WDT
ISR(WDT_vect) {
if(f_wdt == 0) {
  f_wdt = 1; // flag global }
}

// paramètre : 0=16ms, 1=32ms, 2=64ms, 3=128ms, 4=250ms, 5=500ms, 6=1 sec,7=2 sec, 8=4 sec, 9=8 sec
void setup_watchdog(int ii) {
  byte bb;
  int ww;
  if (ii > 9 ) ii=9;
  bb=ii & 7;
  if (ii > 7) bb|= (1<<5);
  bb|= (1<<WDCE);
  ww=bb;
  // Clear the reset flag
  MCUSR &= ~(1<<WDRF);
  // start timed sequence
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  // set new watchdog timeout value
  WDTCSR = bb;
  WDTCSR |= _BV(WDIE);
}

void enterSleep(void) {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  sleep_mode(); //Entre dans le mode veille choisi

  // Le programme va reprendre ici après le timeout du WDT

  sleep_disable(); // La 1ère chose à faire est de désactiver le mode veille
}

//************* SETUP *************
void setup() {
  //Optimisation de la consommation
  //power_adc_disable(); // Convertisseur Analog / Digital pour les entrées analogiques

  //power_spi_disable();
  //power_twi_disable();
  // Si pas besoin de communiquer par l'usb
  //power_usart0_disable();

  //Extinction des timers, attention timer0 utilisé par millis ou delay
  //power_timer0_disable();
  //power_timer1_disable();
  //power_timer2_disable();

  setup_watchdog(9);
}

//************* LOOP *************

void loop() {
  if (f_wdt == 1) {
    // Effectuer les mesures ici
    f_wdt = 0; // Ne pas oublier d'initialiser le flag
    enterSleep(); //Revenir en mode veille
  } else {
    /* Do nothing. */
  }
}

5 commentaires:

  1. Bonjour,
    Merci pour cet article bien clair. Je suis cependant débutant en Arduino Uno; je viens de terminer un projet qui doit fonctionner de manière autonome durant plusieurs mois et je m'aperçois que l'alimentation prévue - 12V 2Ah - ne tiendra que 24-48h ; j'aimerais donc vous poser quelques questions basiques; puis-je le faire en MP ? Merci.

    RépondreSupprimer
  2. Bonjour,
    j'ai une question concernant la boucle qui permet d'augmenter la durée, comment s'y prend t-on ? Car j'ai essayé avec une boucle for mais ça ne fonctionne pas.
    Merci

    RépondreSupprimer
  3. Un grand bravo pour cet article très didactique, qui m’a permis de limiter la consommation de mes capteurs alimentés par panneau solaire.

    RépondreSupprimer
  4. Ce commentaire a été supprimé par l'auteur.

    RépondreSupprimer
  5. Salut à tous
    Il est possible de réduire fortement la consommation d'un ARDUINO mini en supprimant la LED et le régulateur 5V.

    Voici un exemple : https://riton-duino.blogspot.fr/2018/01/un-thermometre-domoticz-sur-batterie-le.html

    RépondreSupprimer