AVR-Tutorial: Power Management

Aus der Mikrocontroller.net Artikelsammlung, mit Beiträgen verschiedener Autoren (siehe Versionsgeschichte)
Wechseln zu: Navigation, Suche

ACHTUNG! Dieser Artikel befindet sich noch im Aufbau!

Vorallem in batteriebetriebenen Systemen spielt die Leistungsaufnahme eine wichtige Rolle, d. h. sie soll so niedrig wie möglich gehalten werden, um eine lange Laufzeit zu erreichen. Den sparsamen Umgang mit der verfügbaren elektrischen Ladung nennt man Power Management (dt. Energiesparen).

Im Rahmen des Power Managements stehen uns beispielsweise die Sleep-Modi zur Verfügung, mit denen wir bestimmte Module zeitweise deaktivieren können. Andere garnicht genutzte Module können wir durch entsprechende Konfiguration (z. B. in den Fuses) auch komplett deaktivieren.

Theorie

Der Stromverbrauch einer CMOS-Schaltung (was auch Mikrocontroller sind) hängt letztlich nur davon ab, wie viele Gates bei welcher Spannung umgeladen werden. Zur Reduktion steht demnach zur Verfügung:

  • Reduktion der Umladevorgänge pro Sekunde, also Reduktion der Taktfrequenz
  • Reduktion der beteiligten Kapazitäten, also Abschalten nicht benutzter Funktionsblöcke
  • Reduktion der Speisespannung (bei manchen Controllern ist das die Kernspannung)

Den statischen Reststrom kann man heutzutage vernachlässigen, auch wenn an dieser Stelle die Anmerkung fallen muss, dass AVRs da eher schlecht abschneiden. Hingegen muss Strom durch ohmsche Lasten, etwa Pull-Up-Widerstände oder LEDs, unbedingt beachtet werden! Außerdem dürfen keine offenen (CMOS!-)Eingänge „herumfliegen“ sondern müssen allesamt an definiertem Potenzial liegen. In der Regel können Mikrocontroller unbenutzte Eingänge im Schlafmodus oder Analogeingangsbetrieb automatisch abtrennen.

Grundsätzlich sollte für Stromsparbetrieb die niedrigstmögliche Taktfrequenz gewählt werden. Übliche Mikrocontroller können direkt von einem Uhrenquarz mit 32,768 kHz gespeist werden. Macht natürlich auch nur maximal 32768 Befehle pro Sekunde. Die Stromaufnahme ist dabei sehr gering und im Gegensatz zu Sleep-Mode-Tricksereien ohne hohe Spitzen.

Da das Antwortverhalten bei derart geringen Taktfrequenzen leidet, wird man eine höhere Frequenz wählen und den Prozessor bei Nicht-Bedarf schlafen schicken. Die Strom-Spitzen sind dementsprechend höher, die Gesamtstromaufnahme bleibt jedoch gering.

Viele Mikrocontroller können einzelne Funktionsblöcke abschalten. Bei den AVRs schlägt da vor allem der 16-Bit-Timer 1 zu Buche. Aber auch die Referenzspannungsquelle für den A/D-Wandler, Analogvergleicher oder den Brown-Out-Detektor kann man abschalten.

Für die Kontrolle der Stromaufnahme aus einer Batterie ist ein Digitalmultimeter mit vielen Strommessbereichen bis 20 µA (Auflösung 10 nA) vorteilhaft. Außerdem ein Plättchen einer doppelseitigen Leiterplatte mit dünnen Drähtchen, das man unter die Batteriekontaktierung schieben kann. Weicht die Stromaufnahme vom Datenblatt ab, hat man wohl noch irgendetwas vergessen.

Sleep-Modi

Welche Sleep-Modi es gibt, hängt vom verwendeten Controller ab, dieser Artikel nimmt jedoch (abweichend vom restlichen AVR-Tutorial) Bezug auf den ATmega32 (Datenblatt). Um einen der verfügbaren Sleep-Modi des ATmega32 zu betreten, müssen folgende Schritte ausgeführt werden:

  1. das SE-Bit im MCUCR-Register wird auf 1 gesetzt,
  2. die SMx-Bits im MCUCR-Register je nach gewünschtem Modus setzen,
  3. der sleep-Befehl wird ausgeführt.

Der Mikrocontroller geht dann sofort in den Sleep-Modus, d. h. noch vor eventuell anstehenden Interrupts, und wacht erst wieder auf, wenn ein Signal eines geeigneten Moduls (je nach Modus) ihn aufweckt.

Die Arbeit wird dann mit der ersten Anweisung hinter dem sleep-Befehl wieder aufgenommen.

MCUCR – MCU Control Register
Bit 7 6 5 4 3 2 1 0
Bezeichnung SE SM2 SM1 SM0 ISC11 ISC10 ISC01 ISC00
Bit 7 – SE – Sleep Enable
Mit diesem Bit wird bestimmt, ob der Sleep-Befehl ausgeführt wird (1) oder nicht (0).
Bit 6…4 – SM2…0 – Sleep Mode Select
Mit diesen drei Bits wird der gewünschte Sleep-Modus gewählt.
SM2 SM1 SM0 Sleep Modus
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)
1 1 1 Extended Standby(1)

(1) Nur verfügbar mit externem Taktgeber

Modi-Übersicht

Generell ist der Modus zu wählen, der die meisten nicht benötigten Module abschaltet.

Aktive Takte Aktive Oszillatoren Weckquellen
Sleep Modus CPU FLASH IO ADC ASY Haupttaktgeber Timer Oszillator INT0-2 TWI Address Match Timer2 SPM/EEPROM Ready ADC Andere I/O
Idle x x x x x(2) x x x x x x
ADC Noise Reduction x x x x(2) x(3) x x x x
Power down x(3) x
Power save x(2) x(2) x(3) x x(2)
Standby(1) x x(3) x
Extended Standby(1) x(2) x x(2) x(3) x x(2)

(1) Nur verfügbar bei externer Taktquelle
(2) Wenn AS2-Bit in ASSR-Register gesetzt
(3) Nur INT2 oder Level Interrupt INT1 und INT0

Manuelles Deaktivieren

Einzelne Module können auch manuell deaktiviert werden um Strom zu sparen – das bietet sich vorallem an, wenn bestimmte Module im gegebenen Projekt generell nicht benötigt werden und damit deaktiviert werden können.

Analog to Digital Converter
Der ADC ist standardmäßig aktiviert. Um ihn zu deaktivieren, muss man ADEN (Bit 7) im Register ADCSRA auf Null setzen.
Analog Comparator
Der Analogkomparator ist standardmäßig aktiviert. Um ihn zu deaktivieren, muss man ACD (Bit 7) im Register ACSR setzen.
Brown-Out Detector
Der Brown-Out-Detektor lässt sich entweder durch das BODEN-Bit in den Fuses oder mit entsprechenden Befehlen aktivieren oder deaktivieren. Das Fuse-Bit ist standardmäßig gesetzt (Achtung: Umgekehrte Logik!) und der BOD damit bereits deaktiviert.
Watchdog
Auch der Watchdog-Timer lässt sich in den Fuses standardmäßig (de-)aktivieren, hier über das WDTON-Bit. Natürlich geht auch das softwareseitig, siehe dazu das Kapitel zum Watchdog.

Praxis

Assembler

Hier mal etwas Code, der nicht aufgeräumt ist, aber funktioniert und sich gut für Versuche eignet. Verbesserungen folgen.

;**********
;* Dateibeschrieb:
;* Interrupttest_Eingängexx.ASM
;* Autor Martin Kuch
;* V02 23.10.2014 mit Sleep und LED Blink in Go raus aus ISR ST:OK!
;* V03 23.10.2014 cli und sei in Go - nur 1x Flanke erkennen nach Schlußdelay ST: OK!
;* V04 23.10.2014 sbic warten bis alle Taster wieder frei sind ST:OK!
;**********
;* Funtionen:
;* 4 Taster an PB0 ... PB3 zur Eingabesteuerung
;* Interrupt 0B (PinChangeInt)
;* PB4 LED
;**********
;* Hardware Zielschaltung:
;* Test-Board mit ATTiny2313 PU
;* Steckernetzgerät Sek: 6VAC 350mA
;* Netzteil eingenbau: Brückengleichrichter, 330uF, 7805CT, 220uF, 6,8nF 2x, LED + 261R
;**********
;* Anschlussschema:
; ATTiny2313
; Port Pin - Pin Funktion
; PB1 12 - Eingang Schalter 1 mit Pullup(+LED) gegen Masse (Set)
; PB2 13 - Eingang Schalter 2 mit Pullup(+LED) gegen Masse (Back)
; PB3 14 - Eingang Schalter 3 " (vor)
; PB4 15 - Eingang Schalter 4 " (zurück)
; PB0 16 - Ausgang Lüfter Ein, LED mit Widerstand gegen +5VDC (Kathode gegen Ausgang)

;* Controllertyp
.device ATtiny2313   ;für Gasm   .include Headerdatei für at2313.asm bei Gasm nicht nötig 
;* Definitionen
.def temp1 = r16 ;Allzweckregister und für LCD_Routines3
.def temp2 = r17 ;für LCD_Routines
.def temp3 = r18 ;für LCD_Routines
.def TM    = r19 ;Allzweckregister Programm
.def TM1   = r20 ;Allzweckregister 2
.def TMz   = r21 ;Zähler allg.
.def Ct0   = r24 ;Counter Delay Vorgabe, Übergabewert für Zeitschleife
.def Ct1   = r25 ;Counter Delay intern

;* Flash Programm
.cseg            ;Flash-Segment
; Interrupt-Handler
.org 0x0000
rjmp Main        ;0x00 Reset
reti;rjmp SInt   ;0x01 INT0 PD2 (6)
reti;rjmp SInt   ;0x02 INT1 PD3 (7)
reti;rjmp SInt   ;0x03 Tim1_Capt
reti;rjmp SInt   ;0x04 Tim1_CompA
reti;rjmp SInt   ;0x05 Tim1_OVF
reti;rjmp SInt   ;0x06 Tim0_OVF
reti;rjmp SInt   ;0x07 Uart0_rxc
reti;rjmp SInt   ;0x08 Uart0_dre
reti;rjmp SInt   ;0x09 Uart0_txc
reti;rjmp SInt   ;0x0A ANA_Comp
rjmp IntPC       ;0x0B PinChangeInt
reti;rjmp SInt   ;0x0C Tim1_CompB
reti;rjmp SInt   ;0x0D Tim0_CompA
reti;rjmp SInt   ;0x0E Tim0_CompB
reti;rjmp SInt   ;0x0F USI_Start
reti;rjmp SInt   ;0x10 USI_OVERFLOW
reti;rjmp SInt   ;0x11 EE_READ
reti;rjmp SInt   ;0x12 WDT_OVERFLOW
nop

;* Main Programm Endlos Steuerung Taster
Main:
;Stack init
        ldi TM, RAMEND
        out SPL, TM
;*Init
        ldi TM, 0b00010000  ;Port B Bit0...3 => Eingang B4 => Ausgang
        out DDRB, TM
        nop
;*Hallo blinken testen, LED-Test und schauen ob uC läuft
    cbi PortB,4             ;LED an
    ldi Ct0, 100
    rcall delayms
    sbi PortB,4             ;LED aus
    rcall delayms
    ldi Ct0, 255

;*Init Interrupt PCINT
        ldi TM, 0b00100000  ;=32 dez. 
        out MCUCR, TM       ;Bit5 = EN enable Sleep
        out GIMSK, TM       ;PCIE PC.Int.Enable
        ldi TM, 0b00001111
        out PCMSK, TM       ;Maske Int. Pin zulassen
        sei                 ;Interrupts zulassen
Go:
    sleep                   ;Schlafen legen = Energiesparen
    nop
    cli
    cbi PortB, 4            ;LED an
    rcall delayms

Go_1:
    sbis PINB, 0            ;Taste noch gedrückt? = 0 => warten
rjmp Go_1
    sbis PINB, 1            ;Taste noch gedrückt? = 0 => warten
rjmp Go_1
    sbis PINB, 2            ;Taste noch gedrückt? = 0 => warten
rjmp Go_1
    sbis PINB, 3            ;Taste noch gedrückt? = 0 => warten
rjmp Go_1

    sbi PortB, 4            ;LED aus
;   rcall delayms
    sei                     ;Interrupts einschalten
rjmp Go

;*ISR für PinChangeInt. (0x0B)
IntPC:
;   push TM
;   in TM, SREG
    nop
;   out SREG, TM
;   pop TM
reti

; Pause Count * 5ms
delayms:
        mov Ct1, Ct0
delayms_:
        rcall delay5ms
        dec Ct1
        brne delayms_
        ret

; Längere Pause für manche Befehle
delay5ms:                               ; 5ms Pause
           Push temp2
           ldi  temp1, (XTAL * 5 / 607) / 1000
WGLOOP0:   ldi  temp2, $C9
WGLOOP1:   dec  temp2
           brne WGLOOP1
           dec  temp1
           brne WGLOOP0
           pop  temp2
           ret                          ; wieder zurück

C

Ein simples Testprogramm, um mit Sleep-Modi im AVR zu spielen. Es funktioniert sehr gut mit dem ATmega8 und ist auch auf andere AVRs portierbar. Teilweise sind weitere Modi verfügbar.

Wichtig ist, daß die Interruptroutine für den „Weckruf“ vorhanden sein muss. Es müssen nicht zwingend Aktionen in ihr durchgeführt werden.

/* ATmega8 with internal 4Mhz clock (6cycle + 64ms) */

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <util/delay.h>

int main(void)
{
   DDRC |= (1 << PC2) | (1 << PC1); // leds for testing

   DDRD &= ~(1 << PD2); // INT0: input...
   PORTD |= (1 << PD2); // ...with pullup.

   // level interrupt INT0 (low level)
   MCUCR &= ~((1 << ISC01) | (1 << ISC00));

   // infinite main loop
   while (1)
   {
      // trigger leds for testing
      PORTC ^= (1 << PC1);
      _delay_ms(500);
      PORTC ^= (1 << PC1);

      // enable external interrupt
      GICR |= (1 << INT0);

      // set sleep mode
      set_sleep_mode(SLEEP_MODE_PWR_DOWN);

      // sleep_mode() has a possible race condition
      sleep_enable();
      sei();
      sleep_cpu();
      sleep_disable();

      // waking up...
      // disable external interrupt here, in case the external low pulse is too long
      GICR &= ~(1 << INT0);

      // disable all interrupts
      cli();
   }
}


ISR(INT0_vect)
{
   // ISR might be empty, but is necessary nonetheless
   PORTC ^= (1 << PC2); // debugging
}

Quellen

Literatur