Stromsparendes Blinklicht

Vor einigen Jahren habe ich eine Programm erstellt, dass regelmäßig eine LED blinken lässt. Die Schaltung hängt am Garagenfenster und läuft seitdem mit dem ersten Satz an Batterien (schon ca. 4Jahre). Der Stromverbrauch ist also nur minimal – vermutlich leicht über der Selbstentladung der Batterien.

Das ganze läuft mit einem Attiny13. Software- & Hardwareseitig wurden diverse Tricks zum Energiesparen umgesetzt. Z.B.:

  • Attiny schläft die meiste Zeit und wird durch den Watchdog-Timer regelmäßig geweckt
  • Takt des Attinys minimiert
  • Tagsüber blinkt die Schaltung nicht (wird über LDR erkannt)
  • LDR wird nur kurz bestromt
  • Keine Kondensatoren (wegen Leckströmen) – Spannung von den Batterien ist stabil genug
  • Blink-LED nur kurz an

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#define F_CPU 128000
#define DEBUG 0
 
#define LED_PULS 90                 //ms
#define LED_PAUSE 10                //s
#define ADC_THRESHOLD 757           //3,7V
#define ADC_INTERVAL 60*30          //s
#define ADC_PIN_PREENABLE_TIME 1    //s
#define ADC_CALIBRATE_TIME 15000L   //ms
 
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <avr/sleep.h>
 
//Fuses:
//  High: 0xFF
//  Low:  0x73
 
//Ggf. ISP-Takt runtersetzen -> 25kHz    bwz. falls versehentlich CHKDIV8 aktiv -> 2kHz
 
//PB0 => PIN5 => LED
//PB1 => PIN6 => Spannungsteiler Enabled
//PB2 => PIN7 => Spannungsteiler
 
unsigned int adc_measure(int ch) {
    //ADC aktivieren
    ADCSRA |= (1 << ADEN);
    //ADC Channel wählen
    ADMUX = ch;
    //Messung starten
    ADCSRA |= (1 << ADSC);
    //Warten bis Messung fertig
    while(!(ADCSRA & (1<<ADIF)));
    //Messung fertig Flag resetten (durch schreiben einer 1)
    ADCSRA |= (1<<ADIF);
    //ADC deaktivieren
    ADCSRA &= ~(1 << ADEN);
    //ADC-Wert zurückgeben
    return ADC;
}
 
int main(void)
{
     
    //LED-Pin als Ausgang setzen
    DDRB |= (1 << 0);
     
    //AD-Komperator deaktivieren
    ACSR |= ACD;
     
    //Spannungsteiler-Pin als Ausgang setzen
    DDRB |= (1 << 1);
     
    //ADC konfigurieren
    ADCSRA |= (1<<ADPS1) | (1<<ADPS0); //ADC-Prescale=8
    ADMUX &= ~(1<<REFS0); //Vcc = Ref
    ADCSRA |= (1 << ADEN); //ADC aktivieren
     
    //ADC=Eingang & Pullup DISABLED
    DDRB &= ~(1 << 2);
    PORTB &= ~(1 << 2);
     
    //Lichtschwelle am Poti einstellen, nach Programmstart
    uint16_t j=0;
    PORTB |= (1 << 1);
    _delay_ms(1000);
    while(j<ADC_CALIBRATE_TIME) {
        uint16_t measure_value=0;
        measure_value=adc_measure(1);
        if(measure_value>ADC_THRESHOLD) {
            PORTB |= (1 << 0);
        } else {
            PORTB &= ~(1 << 0);
        }
        j++;
        _delay_ms(1);
    }
    PORTB &= ~(1 << 1);
     
    //Watchdog Interval Timer
    WDTCR |= (1<<WDCE) | (1<<WDE);  //Enable Reset
    MCUSR &= ~(1<<WDRF);  //Clear WDRF in MCUSR
    WDTCR |= (1<<WDCE);  //Watchdog ChangeEnable
    WDTCR = 0b1000110;  //Enable Interrupt, Disable Reset, Set Prescaler 1s
     
    //Interrupts aktivieren
    sei();
     
    //Sleep aktivieren und auf Powerdown stellen
    sleep_enable();
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
     
    while(1)
    {
        //CPU schlafen legen
        sleep_cpu();
    }
}
 
 
volatile uint16_t i1=0;
volatile uint16_t i2=0;
volatile uint16_t blink_enabled=1;
 
//wird sekündlich aufgerufen
ISR(WDT_vect)
{
    i1++;
    i2++;
     
    if(i1==LED_PAUSE) {
        if(blink_enabled==1) {
            PORTB |= (1 << 0);
            _delay_ms(LED_PULS);
            PORTB &= ~(1 << 0);
        }          
         
        i1=0;
    }  
     
     
    if(i2==(ADC_INTERVAL-ADC_PIN_PREENABLE_TIME)) {
        #if DEBUG == 0
            PORTB |= (1 << 1);
        #endif
    }      
     
    if(i2==ADC_INTERVAL) {
        uint16_t measure_value=0;
         
        measure_value=adc_measure(1);
        #if DEBUG == 0
            PORTB &= ~(1 << 1);
        #endif
         
        #if DEBUG == 1
            _delay_ms(1);
            uint16_t x=0;
            while (x<measure_value) {
                PORTB |= (1 << 1);
                x++;
                _delay_us(100);
            }
            PORTB &= ~(1 << 1);
        #endif
         
        if(measure_value>ADC_THRESHOLD) {
            blink_enabled=1;
        } else {
            blink_enabled=0;
        }
         
        i2=0;
    }
}

Der Prozessor läuft bei mit 128khz. Deswegen muss man beim Programmieren die ISP-Frequenz heruntersetzen. Nach dem Programmstart werden erstmal alle I/Os und der ADC initialisiert. Dann folgt ein Zeitfenster, bei dem man den Poti auf eine richtige Position einstellen kann, damit die Schaltung Tag & Nacht korrekt unterscheiden kann. Anschließend wird der Watchdog und der Sleepmode konfiguriert und der AVR schlafen gelegt.

Aufgeweckt wird der AVR sekündlich. Es wird geprüft, ob eine Aktion folgen muss, sonst legt der AVR sich gleich wieder schlafen. Wenn die definierte Zeit verstrichen ist, dann blinkt die LED kurz auf (sofern es dunkel ist). Der Code für das Überprüfen der Helligkeit wird auch regelmäßig aufgerufen (bei mir halbstündlich). Bevor der ADC die Helligkeit misst, wird der Spannungsteiler eingeschaltet, damit der genug Zeit hat, einen korrekten Wert auszugeben.

Die LED ist bei mir alle 10s für 90ms an. Das reicht, um dies als deutliches Aufblitzen zu erkennen. Habe allerdings auch eine klare, rote 5mm High-Power-LED. Bei schwächeren/low Current-LEDs kann das anders sein.

Ich hatte ursprünglich leider keine Zeichnung angefertigt, weshalb ich diese eben noch per Kopf nachgebaut habe. Die Werte der Widerstände/des Potis weiß ich nicht mehr genau, aber das dürfte ja kein Problem sein…

Versorgt wird das ganze mit 3x AA-Batterien, also mit 4,5V bei vollen Batterien.


Blinklicht


geöffnetes Gehäuse


Schaltung


Diese Website verwendet Cookies. Durch die Nutzung der Website stimmen Sie dem Speichern von Cookies auf Ihrem Computer zu. Außerdem bestätigen Sie, dass Sie unsere Datenschutzbestimmungen gelesen und verstanden haben. Wenn Sie nicht einverstanden sind, verlassen Sie die Website.Weitere Information