Drucken
Kategorie: Arduino / ATMEL
Zugriffe: 89974

Für mein Vorhaben, einen batteriebetrieben Außensensor auf Arduino-Basis zu bauen, ist ein möglichst sparsames System von großer Bedeutung. Es wäre wenig praktikabel, alle paar Tage die Batterien erneuern zu müssen. Auch alle paar Monate nicht. Es wird eine Mindestlaufzeit von einem Jahr oder mehr angestrebt.

Es sollen die verbreiteten AA-Batterien (aka Mignon) verwendet werden. Sie stellen einen guten Kompromis zwischen größe, Verfügbarkeit, Kapazität und Prei dar. Unter der Annahme einer mittleren Kapazität von 2500mAh, darf die Platine incl. aller Peripherie nicht mehr als etwa 0,29 mA verbrauchen. Es soll jedoch möglich sein, einen Atmel (Arduino-CPU) mit wenigen µA (im Leerlauf) zu betreiben. Das ist mein Ziel.

Der erste Schritt besteht darin, ein möglichst sparsames Board auszuwählen. Meine Wahl fiel auf ein Arduino Pro Mini (http://arduino.cc/de/Main/ArduinoBoardProMini). Dieses Board hat keinen fest eingelöteten USB-Serial-Konverter, das allein spart schon etwas Energie. Dazu ist es sehr klein, was für meine Zwecke von Vorteil ist.

Der Stromverbrauch einer CPU steigt mit den höheren Spannung (quadratisch) und Frequenz (linear). Von Arduino Pro Mini gibt es Versionen mit 5V/16Mhz und 3,3V/8Mhz. Die zweite Variante muss definitiv genügsamer sein und lässt sich auch besser mit Batterien oder Akkus versorgen (z.B. 3 x 1,2V (Akku) ergibt 3,6V - praktisch perfekt). Leider besitze ich gerade jetzt kein solches Arduino, daher werde ich meine ersten Tests mit der 5V-Version durchführen.

Ein typischer Außensensor tut die meiste Zeit nichts. In weiten Abständen (mehrere Minuten können für einen Mikroprozessor eine halbe Ewigkeit bedeuten) werden angeschlossenen Sensoren abgefragt und die ermittelten Werte an die Basisstation übermittelt. Wie viell Strom bei der Auswertung und der Übertragung fliesst ist nicht so wichtig, denn genau das passiert im Vergleich mit der Ruhezeit sehr selten und dauert jeweils nur ganz kurz. Interessant ist der Verbrauch in der restlichen Zeit.

Was tut ein Prozessor, wenn er nichts zu tun hat?

Der einfachste Fall: CPU läuft in einer Schleife und tut gar nichts (sinnvolles). Um zu sehen, was dabei an Strom verbraucht wird, habe ich das Arduino Standardbeispiel 'Blink' leicht modifiziert. Mein Programm lässt eine LED in einer Endlosschleife für 1 Sekunde leuchten und anschließend wird sie für 4 Sekunden gelöscht. Die Werte 1 und 4 sind aus Bequemlichkeitsgründen gewählt. Zwar wären Werte wie 1 Sekunde (Auswertung und Übertragung) zu 5 Minuten (Inaktivitästszeit) realitätsnäher, zu Testzwecken sind jedoch kürzere Zeiträume wesentlich bequemer. 

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(4000);               // wait 4 seconds
}

 

Die Messwerte sind ernüchternd: 18,95 mA (Ruhephase) zu 21,40 mA mit der leuchtenden LED. Der Unterschied liegt durchaus im Bereicht eines typischen Verbrauchs einer LowCurrent LED. Dem zweiten Wert wird ab jetzt nicht weiter Beachtung geschenkt.

Bei diesem Ruheverbrauch wird die Batterie nach ca. 132 Stunden leer. Das sind gerade mal 5,5 Tage. Völlig inakzeptabel.

 

Das Board im Bild wird von einem Programmer (USBasp) versorgt. Dieser tut an sich nichts zur Sache. Es war mir damit gerade einfacher, einen Ampermeter in Arduinos Stromkreis einzuschleusen (wollte gerade kein USB-Kabel aufschneiden).


Der zweite Ansatz führt in die Tiefe der Prozessor-Technik. Die CPU hat mehrere Stromsparmodi: "Idle", "Powerdown", "Powersave", "Standby" und "ADC Noise Reduction mode" (genauer nachzulesen z.B. hier). Der sparsamste davon ist der Modus "Powersave".

Nach einer kurzer Suche habe ich einen Beispiel gefunden, der praktisch alles an Funktionalität enthält, was ich für meine Zwecke benötige (link). Im Quellcode ist zu sehen, wie der Prozessor in den tiefen Schlaf zu versetzen ist und auch wie man ihn daraus wieder weckt. Letztere ist nicht so selbstverständlich, wie man zunächst denken kann. Denn eine tief schlummernde CPU führt keine Programme mehr und reagiert auch auf fast nichts. Um aufzuwachen muss diese "unterbrochen" werden. Dafür wird hier ein sogenannter "Watchdog" genutzt.Ein Watchdog ist eine Schaltung im CPU, die eine Art Countdown durchführt und beim Ablauf diesen einen Reset auslöst. Normalerweise wird im Programm der Timer immer ieder zurückgesetzt. Wenn der Prozessor "hängt", starter der "Wachhund" ihn neu. Eine dre Massnahmen, um eine hohe Systemverfügbarkeit zu garantieren. Der Wachtdog kann bei Atmel-Prozessoren auch dazu verwendet werden, um diese aus dem Schlafmodus aufzuwecken. Auch wenn diese Schaltung im aktiven Zustand selbst etwas Strom verbraucht, scheint sie in diesem Fall genau das richtige Mittel zu sein.

In dem (hoffentlich irgendwann) fertigen Gerät kann ich mich auch eine Taste gut verstellen (z.B. um die aktuelle Werte auf dem LCD anzuzeigen), die auch zum Wiederaufwachen führt. Dies wird wohl mittels eines Interraupts geschehen müssen. Dies soll aner an dieser Stelle ohne Beachtung bleiben.

Ich habe den gefundenen Beispielcode (s.o.) etwas für meine Zwecke modifiziert.

#include <avr/sleep.h>
#include <avr/wdt.h>

// Macros zum Setzen und Löschen von Steuer-Bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

// Pin 13 has an LED connected on most Arduino boards.
int led = 13;

// Steuervariable. Erkennung des Timerablaufs (um z.B. andere Interrupts zu ignorieren)
volatile boolean f_wdt=1;

void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);    
 
  // CPU Sleep Modes 
  // SM2 SM1 SM0 Sleep Mode
  // 0   0   0   Idle
  // 0   0   1   ADC Noise Reduction
  // 0   1   0   Power-down
  // 0   1   1   Power-save
  // 1   0   0   Reserved
  // 1   0   1   Reserved
  // 1   1   0   Standby(1)

  cbi( SMCR,SE );      // sleep enable, power down mode
  cbi( SMCR,SM0 );     // power down mode
  sbi( SMCR,SM1 );     // power down mode
  cbi( SMCR,SM2 );     // power down mode

  setup_watchdog(8); 
}

// the loop routine runs over and over again forever:
void loop() {
  if (f_wdt==1) {  // wait for timed out watchdog / flag is set when a watchdog timeout occurs
    f_wdt=0;       // reset flag

    digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(1000);               // wait for a second
    digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  }
  
  pinMode(led,INPUT);          // set all used port to intput to save power
  system_sleep();
  pinMode(led,OUTPUT);         // set all ports into state before sleep
}

//****************************************************************  
// set system into the sleep state 
// system wakes up when wtchdog is timed out
void system_sleep() {

  cbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter OFF

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_enable();

  sleep_mode();                        // System sleeps here

    sleep_disable();                     // System continues execution here when watchdog timed out 
    sbi(ADCSRA,ADEN);                    // switch Analog to Digitalconverter ON

}

//****************************************************************
// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
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;
  //Serial.println(ww);


  MCUSR &= ~(1<<WDRF);
  // start timed sequence
  WDTCSR |= (1<<WDCE) | (1<<WDE);
  // set new watchdog timeout value
  WDTCSR = bb;
  WDTCSR |= _BV(WDIE);


}

//****************************************************************  
// Watchdog Interrupt Service / is executed when  watchdog timed out
ISR(WDT_vect) {
  f_wdt=1;  // set global flag
}

 

Schon besser.  Bei diesem Stromverbrauch hält die Batterie ca. 31 Tage. Immer noch Faktor 10 zu wenig, aber die Richtung stimmt.

An dieser Stelle denke ich an die hell läuchtende PowerLED. Diese kann sich durchaus 2-4 mA genehmigen. Leider kann sie nicht softwaretechnisch aufgeschaltet werden und mus daher ausgelötet werden. Dies ist nicht ganz einfach, denn es andelt sich hier um eine sehr kleine SMD-Version. Ich werde diese Tätigkeit in die Zukunft verschieben, wenn meine sparsame 3,3V Boards geliefert werden.

 

Der ober Code funktioniert schon recht gut, jedoch will man so ein Mix von Low-Level Anweisungen nicht unbedingt im geschäftslogik-Teil des fertigen Programms sehen. Es muss eine Bibliothek her. Bevor man jedoch eine neue schreibt, sollte man nachsehen, ob nicht schon so eine existiert.

Schnell sind mehrere gefunden: Narcoleptic, Low-Power, Enerlib.

Bei Narcoleptic gefiel mir ihre Einfachheit, geringe Größe und die Möglichkeit, den Inaktivitätsdauer frei vorzugeben. Die Bibliothek wird jedoch seit längerem nicht mehr weiterentwickelt und verursachte im Test einen etwas höheren Verbrauch alt der Code oben. Der Grund dazu ist nicht deaktivierter Analog-Digital-Wandler.

Enerlib hat mir auch nicht zugesagt. Diese könnte ich nicht ohne Modifikation verwendet, denn sie erlaub gar keine Zeitvogabe für den Tielschlaf.

Dafür ist die Low-Power ein Glückstreffer. Gut dokumentiert, mit vielen Hintergrunfinformationen fersehen, sehr mächtig und aktuell. Was mir nicht so gefällt, ist die starre Vorgabe des Schlafdauers. Es sind nur wenige feste Werte (max. 8 Sekunden) möglich (low-level, eben das, was der Watchdog-Timer bietet). Dafür kannn eine sehr feine Konfiguration bezüglich den abzuschaltenden Hardwarekomponenten vorgegeben werden. Gefällt mir sehr. Ich möchte allerdings eine Methode schreiben schreiben, um eine einfache und flexible "delay"-Funktion (etwa wie im Narcoleptic) zu erhalten. Bis dahin werde ich noch einige Test mit Narcoleptic durchführen. Zuvor schalte ich zusätzlich noch den A/D-Wandler mit ab.

#include <Narcoleptic.h>

#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);    
}

// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                  // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  
  cbi(ADCSRA,ADEN);          // disable A/C-Converter
  pinMode(led,INPUT);       // set all used port to intput to save power 
  Narcoleptic.delay(4000);
  pinMode(led,OUTPUT);    // set all ports into state before sleep
  sbi(ADCSRA,ADEN);         // enable A/C-Converter
}

 

Eigentlich ist mein Board mit 16mHz nicht für 3,3V geeignet, es lief mit dieser Spannung jedoch stabil. Der Verbrauch sank auf 1,57 mA. Das entspricht schon ca. 66 Tagen.

 

 

Um den Verbrauch weiter zu senken, sind schon radikalere Maßnahmen notwendig. Dafür habe ich die Power-LED abgeschaltet. Dafür habe ich den LED-Vorwiderstand herausgelötet. (War etwas schwierig, die SMD-Bauteile liegen auf dem Boars recht eng).

Die Maßnahme wirkt. Der Verbrauch liegt bei 29,5 µA! Und das bei 5 Volt. Damit habe ich mein oben definiertes Ziel eigentlich schon erreicht. Bei den Werten darf die Batterie Jahre halten. Da fällt schon Selbstentladung ins Gewicht.

 

 

Aus dem sportlichen Interesse will ich wissen, was man noch ohne größeren Aufwand erreichen kann.

3,3 Volt-Test war zunächts misslungen. Im µA-Bereich hat das Messgerät wohl einen etwas höheren Widerstand, so dass sich das Board sofort wieder resettet hat. Das habe ich bestimmt dem Brown out Detector (BOD erkennt eine Unterspannung und bewirkt daraufhin Reset der CPU. Damit wird instabiler Betrieb verhindert) zu verdanken. In mA-Bereich war der Wert 0,02 mA angezeigt.

Also muss noch der BOD raus. Das soll aus dem laufenden Programm möglich sein, aber nicht mit Narcoleptic. Eine andere Methode führt über einen neuen Arduino-Bootloader. Eine gute Anleitung dazu liefert 'Rocket Scream', der Hersteller von Low-Power-Bibloithek (Link).

Ich habe die Datei 'boards.txt' (Arduino IDE, Verzeichnis 'hardware\arduino') um eine neue Sektion erweitert.

##############################################################

pro5v328BOD.name=Arduino Pro or Pro Mini (5V, 16 MHz) w/ ATmega328 without BOD

pro5v328BOD.upload.protocol=arduino
pro5v328BOD.upload.maximum_size=30720
pro5v328BOD.upload.speed=57600

pro5v328BOD.bootloader.low_fuses=0xFF
pro5v328BOD.bootloader.high_fuses=0xDA
pro5v328BOD.bootloader.extended_fuses=0x07
pro5v328BOD.bootloader.path=atmega
pro5v328BOD.bootloader.file=ATmegaBOOT_168_atmega328.hex
pro5v328BOD.bootloader.unlock_bits=0x3F
pro5v328BOD.bootloader.lock_bits=0x0F

pro5v328BOD.build.mcu=atmega328p
pro5v328BOD.build.f_cpu=16000000L
pro5v328BOD.build.core=arduino
pro5v328BOD.build.variant=standard

##############################################################


Damit kennt IDE jetzt ein 'neues' Board. Der einzige Unterschied zum Original ist eine anders gesetzt Fuse (Original: ...extended_fuses=0x05).

Nach dem Flaschen des Bootloaders (hier war der Programmer notwendig) war der BOD aus. Das sieht man gut an dem Stromverbrauch und an der Tatsache, dass das Board jetzt mit dem Ampermeter in µA-Bereich bei 3,3V problemlos funktioniert.

 


Der Wert ist zwar nicht so fantastisch, wie bei Rocket Scream beschrieben, aber dennoch sehr beeindrückend. Ich bin gespannt, wie das mit dem 8mHz Arduino aussieht. ;-) In jedem Fall ist der Prozessor nicht der limitierende Faktor für die Laufzeit. In einer fertigen Schaltung werden andere Komponenten über den endgültigen Verbrauch stärker entscheiden. So führt sogar ein angeschlossener FTDI-Chip (zuständig für USB-Anschluss) zu einem messbaren Mehrverbrauch. Dabei war der Chip nicht mal an die USB-Schnittstelle des Computers angeschlossen.

Eine weitere Möglichkeit wäre die Verwendung des internen Oszillators zum Erzeugen des CPU-Taktes. Da dieser nicht so genau wie ein Quarzoszillator ist, kann dabei zu Problemen z.B. bei seriellen Verbindungen kommen (zumindest bei höheren Baudraten). Aus diesem Grund will ich diesen Ansatz nicht weiter verfolgen.

 

Nachdem ich mit den Tests fertig war, habe ich den LED-Vorwiderstand wiedereingelötet. Es ist leider etwas schief geworden, war bei diesem kleinen Raster nicht ganz einfach. Merkwürdigerweise muss dabei der Bootloader Schaden genommen haben. Das Board lief nicht mehr und ließ sich auch nicht über USB bespielen. Mit einem Programmer konnte ich jedoch einen neuen über die ISP-Schnittstelle installieren. Jetzt läuft Pro Mini wieder einwandfrei. Ich verstehe nicht ganz, wie das passieren konnte. Evtl. hat das etwas mit statischen Elektrizität zu tun.

 

 Im Folgenden noch mein Testprogramm unter der Verwendung der LowPower-Bibliothek.

/* 
 * PowerSave
 *
 * Author: Alexander Schulz 
 * Version: 1.0
 * Datum:  17.07.2013
 *
 * Dieses Programm lässt die auf dem Arduino- Board vorhandene LED 
 * alle 4 Sekunden blinken. In den Pausen wird der Prozessor 
 * in den Schlaffmodus versetzt.
 * 
 */
 
#include <LowPower.h>

// Pin 13 ist auf den meisten Boards mit einer LED verbunden.
int led = 13;

void setup() {                
  // Definiert den LED-Pin als Ausgang.
  pinMode(led, OUTPUT);    
}

void loop() {
  digitalWrite(led, HIGH);   // LED an (Ausgang HIGH)
  delay(1000);               // eine Sekunde brennen lassen
  digitalWrite(led, LOW);    // wieder ausmachen (Ausgang LOW)
  
  pinMode(led,INPUT); // Alle benutzen Ports als Eingang definieren, das spart Energie
  delay(4000);
  pinMode(led,OUTPUT); // benutzte Ports wieder in den benötigten Zustand versetzen
}

/*
 * Versetzt den Prozessort für eine angegebene Zeitspanne ins Tiefschlaf.
 * Nach dem Auswecken wird geprüft, ob die gewünschte Zeit bereits abgelaufen ist
 * und wird ggf. wieder Schlafmodus aktiviert.
 * Man sollte bei der Dauer nicht mit einer besonderen Genauigkeit rechnen.
 */
void delay(int milliseconds) {
  while (milliseconds >= 8000) { sleep(SLEEP_8S); milliseconds -= 8000; }
  if (milliseconds >= 4000)    { sleep(SLEEP_4S); milliseconds -= 4000; }
  if (milliseconds >= 2000)    { sleep(SLEEP_2S); milliseconds -= 2000; }
  if (milliseconds >= 1000)    { sleep(SLEEP_1S); milliseconds -= 1000; }
  if (milliseconds >= 500)     { sleep(SLEEP_500MS); milliseconds -= 500; }
  if (milliseconds >= 250)     { sleep(SLEEP_250MS); milliseconds -= 250; }
  if (milliseconds >= 125)     { sleep(SLEEP_120MS); milliseconds -= 120; }
  if (milliseconds >= 64)      { sleep(SLEEP_60MS); milliseconds -= 60; }
  if (milliseconds >= 32)      { sleep(SLEEP_30MS); milliseconds -= 30; }
  if (milliseconds >= 16)      { sleep(SLEEP_15Ms); milliseconds -= 15; }
}

/*
 * Verwendet LowPower-Bibliothek zum Aktivieren des Schlafmodus der CPU.
 */
void sleep(period_t period) {
  LowPower.powerDown(period, ADC_OFF, BOD_OFF); 
}

 

Kommentare (7)

Cancel or