Emils Ampel: Attiny45 im Tiefschlaf

Send to Kindle

Während meiner Arbeit am Buch „Hausautomation mit Arduino“ entdeckte mein mittlerweile vierjähriger Sohn drei Leuchtdioden. Rot, gelb, grün. Die Frage war nahe liegend: „Papa, baust Du mir eine Ampel?“. Klar baut Papa eine Ampel, zumal ein paar kleine ATTINY45-20PU herumliegen, die dafür genau richtig erscheinen. Eine größere Herausforderung ist da schon die Frage nach dem Energieverbrauch: Ich wollte, dass die Ampel auch mit drei fast toten (0,9V Klemmenspannung) AA-Zellen noch ewig Standby hat und dass man sie nicht einfach anschalten und vergessen kann – denn bei ca. 20mA sind selbst frische AA-Zellen in fünf Tagen leer. Und fünf Tage in der Spielzeugkiste sind keine Zeit…

Schnell auf Lochrasterplatine zusammengelötet...

Schnell auf Lochrasterplatine zusammengelötet…

Es musste also ein Taster her, mit dem die Ampel in Gang gesetzt wird, dann ein oder zwei Minuten läuft und dann bei praktisch Null Leistungsaufnahme schläft. Ich habe zuerst überlegt, einen externen Interrupt zu verwenden, mich dann aber gefragt „Warum nicht ewig schlafen und per Reset aufwecken?“. Auf diese Art und Weise behalte ich zwei Pins statt nur einem und kann so irgendwann mal eine Fußgängerampel nachrüsten. Die Schaltung ist straightforward: Der Reset-Pin wird über 10 bis 22kΩ auf VCC gelegt. Ein Taster zieht diesen bei Bedarf auf Masse. Die LEDs liegen mit Widerständen von 220 bis 560Ω auf den Pins 0 bis 2 des Attiny45.

Aufbau von Emils Ampe: Der Taster zieht den Reset-Pin auf Masse.

Aufbau von Emils Ampel: Der Taster zieht den Reset-Pin auf Masse.

Als minimaler Sketch kommt ein besseres Blink-Beispiel zum Einsatz. Wie bei einer richtigen Ampel folgt auf rot rot-gelb, dann grün, dann gelb, dann rot (das Pinout eines Attiny45/Attiny85):

int red = 2;
int yell = 1;
int grn = 0;
 
void setup() {
  pinMode(red, OUTPUT);
  pinMode(yell, OUTPUT);
  pinMode(grn, OUTPUT);
}

void loop() {
  lcount += 1;
  digitalWrite(red, HIGH); 
  delay(5000); 
  digitalWrite(yell, HIGH); 
  delay(1000);
  digitalWrite(red, LOW);
  digitalWrite(yell, LOW);
  digitalWrite(grn, HIGH); 
  delay(5000); 
  digitalWrite(yell, HIGH);
  digitalWrite(grn, LOW);
  delay(1000);
  digitalWrite(yell, LOW);
}

Nun der Schlafsketch: Die auskommentierten Partien beziehen sich auf Interrupts – die wir ebenfalls einfach abschalten. Ein so konfigurierter AVR legt sich quasi für immer schlafen – bis ihn ein Reset ereilt. Natürlich bedeutet der Interrupt mehr Freiheiten, weil man ihn auf fallende oder steigende Flanke legen kann und er eben nicht durch einen einfachen Batteriewechsel ausgelöst wird.

#include <avr/sleep.h>
#include <avr/interrupt.h>
#define BODS 7                   //BOD Sleep bit in MCUCR
#define BODSE 2                  //BOD Sleep enable bit in MCUCR

uint8_t mcucr1, mcucr2;
int red = 2;
int yell = 1;
int grn = 0;
int lcount = 0;

void goToSleep(void) {
    // GIMSK |= _BV(INT0);                    //enable INT0
    // MCUCR &= ~(_BV(ISC01) | _BV(ISC00));   //INT0 on low level
    ACSR |= _BV(ACD);                         //disable the analog comparator
    ADCSRA &= ~_BV(ADEN);                     //disable ADC
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    sleep_enable();
    //turn off the brown-out detector.
    //must have an ATtiny45 or ATtiny85 rev C or later for software to be able to disable the BOD.
    //current while sleeping will be <0.5uA if BOD is disabled, <25uA if not.
    cli();
    mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE);  //turn off the brown-out detector
    mcucr2 = mcucr1 & ~_BV(BODSE);
    MCUCR = mcucr1;
    MCUCR = mcucr2;
    sei();                         //ensure interrupts enabled so we can wake up again
    sleep_cpu();                   //go to sleep
    cli();                         //wake up here, disable interrupts
    // GIMSK = 0x00;               //disable INT0
    sleep_disable();               
    sei();                         //enable interrupts again (but INT0 is disabled from above)
}

void setup() {
  pinMode(red, OUTPUT);
  pinMode(yell, OUTPUT);
  pinMode(grn, OUTPUT);
}

void loop() {
  lcount += 1;
  digitalWrite(red, HIGH); 
  delay(5000); 
  digitalWrite(yell, HIGH); 
  delay(1000);
  digitalWrite(red, LOW);
  digitalWrite(yell, LOW);
  digitalWrite(grn, HIGH); 
  delay(5000); 
  digitalWrite(yell, HIGH);
  digitalWrite(grn, LOW);
  delay(1000);
  digitalWrite(yell, LOW);
  if (lcount > 5) { goToSleep(); }
}

Und nun ein paar Feinheiten für all jene, die bislang nur mit Atmegas gearbeitet haben: Der Attiny wird in der Regel ohne Bootloader programmiert. Das heisst, er wird zunächst wie hier abgebildet (Fritzing-Datei) an einen Uno oder Pro Mini, der mit Arduino ISP versehen ist, angeschlossen. Hardwaredefinitionen für Atmega/Attiny mit Arduino 1.5 biete ich hier an. Und die Leistungsaufnahme? Ohne ADC und mit dem per Software abgeschaltetem BOD: Sagenhafte 0,12µA!