Inhaltsverzeichnis

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.:

Code

#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.

Schaltung

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.

Bilder


Blinklicht


geöffnetes Gehäuse


Schaltung