Fortsetzung AVR-Tutorial: 9V-LED-Lampe Teil II
Winfried Mueller, www.reintechnisch.de, Start: 31.12.2005, Stand: 31.12.2005
Es geht weiter mit dem 9V-LED-Lampenprojekt. War der erste Artikel dazu da, die ersten Schritte im Umgang mit AVR-Prozessoren zu lernen, so verfeinern wir hier vor allem die Software. Es soll um eine ordentliche Tastaturentprellung gehen - etwas, was jeder früher oder später braucht, wenn er mit Mikrocontrollern hantiert. Aber auch mit dem eingebauten Comparator werden wir experimentieren.
Hardware-Erweiterung

(Rechte Maustaste > Grafik anzeigen zeigt den Schaltplan größer an)
Die Hardware hat sich ein wenig verändert. Wenn man Akkus für die Lampe einsetzt, so ergibt sich eine ungünstige Situation. Der Akku wird bis hinunter zu etwa 4 Volt leergesaugt, was ihm nicht sonderlich gut bekommt. Gut wäre es also, wenn der Prozessor sich darum kümmert, solche Tiefentladungen zu verhindern.
Die entstandene Lösung ist noch nicht optimal, aber einfach. Sie lässt sich mit dem ATtiny 12 bewerkstelligen. Hierzu verwenden wir den enthaltenen Comparator. Er vergleicht eine interne Referenzspannung von 1.22 Volt mit der Spannung an Portpin PB1.
Wie können wir das nutzen? In der ursprünglichen Schaltung sieht es nicht so rosig aus. Wir können nicht einfach den Portpin irgendwie an Plus der Batterie klemmen, weil dann auch im ausgeschalteten Zustand ein Strom fließen würde. Wir müssen vielmehr versuchen, irgendwie erst am Kollektor von T1 zu messen, weil der im ausgeschalteten Zustand entkoppelt ist. Nun wird aber gerade der Kollektor immer etwa die 3.4-3.6 Volt der weißen Leuchtdiode haben, was uns nichts nützt.
Die Lösung besteht darin, einen Widerstand in Reihe zur LED zu klemmen. Die Spannung, die an ihm abfällt, wählen wir so, dass bei Nennstrom wir eine Spannung von etwa 6-7 Volt am Kollektor haben. Was passiert dann? Fließt ein korrekter Strom, wie er durch die Stromquelle aufgeprägt wird, liegt der Kollektor immer auf z.B. 7 Volt. Sinkt jedoch die Eingangsspannung auf z.B. 6V ab, so kommt die Stromquelle in einen Bereich, wo sie nicht mehr als Stromquelle funktioniert. Sie ist außerhalb ihres Regelbereiches und der Transistor ist voll durchgesteuert, auch wenn die Spannung noch niedriger wird. Folglich sinkt die Spannung am Kollektor genauso ab, der Strom durch die LED auch. Bei 6 Volt Eingangsspannung bleiben so nur noch etwa 5.5 V am Kollektor übrig.
Wenn wir jetzt also die Spannung am Kollektor über einen Spannungsteiler so aufteilen, dass wir unterhalb der 1.22 V Referenzspannung dann rutschen, wenn die Stromquelle außerhalb ihres Regelbereiches ist, dann haben wir genau die Bedingung, die wir brauchen.
Die Schaltung sollte so dimensioniert sein, dass im Regelbereich der Stromquelle, die in den Prozessor eingespeiste Spannung über PB1 etwas über der Referenzspannung von 1.22 V liegt. Hier sollte man nicht zu knapp kalkulieren, um keine Fehlauslöser zu bekommen. Temperaturschwankungen können ja z.B. Parameter verschieben. Wir könnten z.B. die Spannung am Prozessor auf etwa 1.3 Volt halten, das macht dann 80mV Abstand, vorausgesetzt der Tiny12 hat auch wirklich die 1.22V Referenz. Zu hoch dürfen wir sie nicht wählen, weil sich sonst der Auslösepunkt zu einer sehr niedrigen Eingangsspannung verschiebt, was wir ja nicht wollen. Eigentlich wären 6.3-7Volt Eingangsspannung ein idealer Auslösepunkt. (0.9Vx7Zellen=6.3Volt)
Die Dimensionierung ist hier etwas kniffelig. R8, R9 und R10 müssen gut abgestimmt sein. R8 wählt man so groß, dass die Spannung am Kollektor im Regelbereich der Stromquelle bei etwa UBmin - UR2 = 7V - 0.7V = 6.3V liegt. R9 und R10 stimmt man dann so ab, dass bei dieser Spannung etwa 1.3Volt an PB1 ankommen. Man kann hierzu R10 mit 47 K fest auswählen und dann nur noch R9 abstimmen. Der Gesamtwiderstand R9 und R10 sollte nicht zu klein werden, um nicht sinnlos Strom zu verbrauchen. Ansonsten ist man hier recht frei in der Auswahl der Werte. Man kann natürlich auch einen Trimmer zusätzlich einbauen, mir war das aber zu aufwändig.
Normal versieht man den Eingangsping PB1 noch mit einem kleinen Kondensator, um Spikes und kurzzeitige Schwankungen zu filtern. Wir lassen das hier hauptsächlich aus dem Grund, dass so ein Kondensator die Programmierung des Chips unmöglich machen würde. Der Pin PB1 wird ja zusätzlich zum programmieren benötigt. Man müsste so einen Kondensator dann für die Programmierung z.B. mit einem Jumper abklemmen. Andererseits können wir aber auch sehr gut softwaremäßig Spikes oder Schwankungen eleminieren. Zumindest ausreichend für unseren Zweck hier.
Soweit zur veränderten Hardware. Alles andere ist gegenüber der ersten Version gleich geblieben.
Software
; 9V-Taschenlampe nach Idee vom 28.10.2005 ; Interner RC-Oszi, Tiny 12 ; Beginn: 28.10.2005, Stand: 30.12.2005 ; Author: Winfried Mueller, www.reintechnisch.de ; Changelog: ; 30.12.2005 ; - Unterspannungserkennung ; - Tastenentprellung im Timerinterrupt ; - Universal-Wait-Schleife ; PORTB ; PB0: IN P NC ; PB1: IN - Unterspannungserkennung ; PB2: IN P NC ; PB3: IN - Switch (Pull-Down extern) ; PB4: IN P/- On-Transistor, wird durch internen Pullup durchgesteuert ; PB5: IN Reset .include "tn12def.inc" ; IO-PINS .equ P_SWITCH = 3 .equ P_ON = 4 ; Register Definition ; General Purpose .def a = r16 .def b = r17 .def c = r18 ; Interrupt .def i_a = r19 .def sSREG = r1 ; sonstig .def key_0_longcount = r4 ;Counter für key_0 LongPressedEvent .def low_u_count = r5 .def sec_divider = r20 ;Timer-Divider Sekundentakt .def cnt_s = r21 ;Count Sekunden .def cnt_m = r22 ;Count Minuten .def flags = r23 ;General Flag Register .def key_0 = r24 ;Bit 0..2 Prellcount ;Bit 3: Keystatus (0=losgel., 1=gedrückt) ;Bit 4 Event Key OFF (losgelassen) ;Bit 5 Event Key ON (gedrückt) ;Bit 6 Event Key LONG_ON (lange gedrückt) ;(Bit 4/5/6 wird von Applika rückgesetzt ; und von ISR gesetzt) ; Constants .equ TM0_PRESET = 209 ;kalibrieren auf Timereinsprung alle 10ms (256-x) .equ LAMP_OFF_TIME_S = 0 .equ LAMP_OFF_TIME_M = 10 .equ FLAG_SEC = 0 .equ FLAG_MASK_SEC = 0x01 .equ FLAG_LOW_U = 1 .equ FLAG_MASK_LOW_U = 0x02 .equ KEY_EVENT_OFF = 4 .equ KEY_EVENT_ON = 5 .equ KEY_EVENT_LONG_ON = 6 .MACRO INIT_IO_ON ldi a, 0b00010101 out PORTB, a ldi a, 0b00000000 out DDRB, a .ENDMACRO .MACRO INIT_IO_OFF ldi a, 0b00000101 out PORTB, a ldi a, 0b00010000 out DDRB, a .ENDMACRO .MACRO INIT_TIMER ldi a, 0b00000100 ;Prescale Timer = 256 -> ca alle 213us out TCCR0, a ldi a, TM0_PRESET ;Timer-Counter Preset out TCNT0, a ; Set Oscillator Calibration default ldi a, 0x60 out OSCCAL, a .ENDMACRO .MACRO RESET_TIME ldi sec_divider, 0 ldi cnt_s, 0 ldi cnt_m, 0 .ENDMACRO .MACRO INIT_INT_ON cli ldi a, 0b00000000 ;kein External/Pin-Change Interrupt out GIMSK, a ldi a, 0b00000010 ;Timer Overflow Interrupt Enable out TIMSK, a sei .ENDMACRO .MACRO INIT_INT_OFF cli .ENDMACRO .MACRO INIT_COMPARATOR cbi ACSR, ACIE ;No Interrupt cbi ACSR, ACD ;Comparator enable sbi ACSR, AINBG ;Internal Reference .ENDMACRO .MACRO INIT_SLEEP ldi a, 0b00100000 ;Idle out MCUCR, a .ENDMACRO .MACRO RESET_KEY ldi key_0, 0 .ENDMACRO .MACRO RESET_KEY_EVENT andi key_0, 0b10001111 .ENDMACRO ;-------------------------------------- ; Interruptvektoren ;-------------------------------------- rjmp RESET rjmp EXT_INT0 rjmp PIN_CHANGE rjmp TIMER0 rjmp EE_RDY rjmp COMPARATOR ;-------------------------------------- ; Interrupt Service Routine ;-------------------------------------- EXT_INT0: reti PIN_CHANGE: reti EE_RDY: reti COMPARATOR: reti TIMER0: in sSREG, SREG ldi i_a, TM0_PRESET ;Timer-Counter Preset out TCNT0, i_a ;--- inc sec_divider cpi sec_divider, 100 brne t0_l1 ; Endwert erreicht ldi sec_divider, 0 sbr flags, FLAG_SEC inc cnt_s cpi cnt_s, 60 brne t0_l2 ; Sekundenüberlauf inc cnt_m ldi cnt_s, 0 t0_l2: t0_l1: t0_kb: ;-- Tastencheck sbrc key_0, 3 ; -> Taste gedrückt gemerkt? rjmp t0_l5 ;-> Taste gemerkt losgelassen, Check, ob gedrückt sbis PINB, P_SWITCH rjmp t0_l8 ;->gemerkt losgelassen, ist losgelassen ;-> gemerkt losgelassen, ist gedrückt inc key_0 mov i_a, key_0 andi i_a, 0x07 cpi i_a, 0x02 brne t0_l4 ; -> noch kein Wechsel einleiten ;-> gilt jetzt als gedrückt, Event gedrückt andi key_0, 0b11111000 ori key_0, 0b00101000 ldi i_a, 200 ;Init LongKeyPressedCount mov key_0_longcount, i_a rjmp t0_l4 t0_l5: ;-> Taste gemerkt gedrückt, Check, ob losgelassen sbic PINB, P_SWITCH rjmp t0_l6 ;->gemerkt gedrückt, ist gedrückt inc key_0 mov i_a, key_0 andi i_a, 0x07 cpi i_a, 0x03 brne t0_l4 ; -> noch kein Wechsel einleiten ;-> gilt jetzt als losgelassen, Event losgelassen andi key_0, 0b11110000 ori key_0, 0b00010000 rjmp t0_l4 t0_l6: tst key_0_longcount breq t0_l8 dec key_0_longcount brne t0_l8 ;-> jetzt Null -> KeyLongPressed-Event ori key_0, 0b01000000 t0_l8: ;->Merker und Tastenstatus gleich -> Prellcount Reset andi key_0, 0b11111000 ;Count Reset rjmp t0_l4 t0_l4: ;--- out SREG, sSREG reti RESET: MAIN: INIT_IO_ON RESET_TIME INIT_SLEEP INIT_TIMER INIT_INT_ON INIT_COMPARATOR clr flags clr low_u_count ; Check, ob Taster noch gedrückt, wenn nicht, dann war es ein Spike ; und das Gerät soll sofort wieder ausschalten sbis PINB, P_SWITCH rjmp m_l2 ldi a, 200 rcall WAIT_M ;Warten damit Taste nicht sofort geprüft wird RESET_KEY_EVENT m_l1: sleep cpi cnt_m, LAMP_OFF_TIME_M breq m_l2 sbrc key_0, KEY_EVENT_ON rjmp m_l3 rcall Check_U sbrc flags, FLAG_LOW_U rjmp m_l2 ; Unterspannung -> Abschalten rjmp m_l1 m_l3: ;Taste gedrückt, warte bis losgelassen sleep sbrc key_0, KEY_EVENT_OFF rjmp m_l2 rjmp m_l3 m_l2: ;Zeit abgelaufen/Taste gedrückt INIT_INT_OFF INIT_IO_OFF ;jetzt sollte Prozessor ausschalten ldi a, 255 rcall WAIT_M rjmp RESET ;Reset, falls Fehler ;Check auf Unterspannung ; erst, wenn n-mal hintereinander Unterspannung detektiert, wird ; ein Unterspannungs-Event generiert Check_U: sbic ACSR, ACO rjmp cu_l2 ; Unterspannung -> Count clr low_u_count rjmp cu_l1 cu_l2: inc low_u_count ldi a, 200 cp low_u_count, a brcs cu_l1 ; noch kleiner MaxCount ori flags, FLAG_MASK_LOW_U ;Unterspannung Event cu_l1: ret ;Zeitschleife ;Übergabe: a - Länge= 10ms * a (a:1..255) WAIT_M: WM_0: ldi b, 0xFF WM_1: lpm lpm lpm lpm lpm lpm lpm lpm lpm lpm lpm lpm lpm lpm wdr wdr dec b brne WM_1 dec a brne WM_0 ret
Programmbeschreibung
Fangen wir mit den einfachen Veränderungen an. Die festen Zeitschleifen in der ersten Version wurden durch eine flexible Zeitschleife ersetzt (WAIT_M anstatt WAIT_2S oder WAIT_10MS). Mittels Register a übergibt man ein Wert, der mit 10ms multipliziert die Schleifendauer ergibt. Damit können wir jetzt flexibel Zeitspannen von 10ms bis etwa 2.5 Sekunden überbrücken.
In der Initialisierung wurde nun auch der Oszillator auf einen Standard-Korrekturfaktor gesetzt:
.MACRO INIT_TIMER ldi a, 0b00000100 ;Prescale Timer = 256 -> ca alle 213us out TCCR0, a ldi a, TM0_PRESET ;Timer-Counter Preset out TCNT0, a hier --> ; Set Oscillator Calibration default ldi a, 0x60 out OSCCAL, a .ENDMACRO
Ich wollte damit wegen korrekter Berechnung der Zeitschleifen die Prozessorfrequenz auf 1.2MHz kalibrieren. Defaultmäßig liefen meine Prozessoren eher zu schnell. Hier sollte man also kalibrieren, wenn die Zeitspanne der Einschaltdauer nicht korrekt ist.
Die Tastenentprellung ist sicherlich der aufwändigste Teil, der erneuert wurde. Sie geschieht im Timer0 Interrupt (Label TIMER0). Dort beginnt sie bei Label t0_kb. Alle 10ms wird ja in den Timer gesprungen. Das ist schon ein gut gewählter Wert, was Tastaturentprellung anbelangt. Ein Taste gilt nun dann als gedrückt, wenn sie für n Timereinsprünge als gedrückt erkannt wurde. Dabei wird bei jedem Einsprung, wo sie nicht gedrückt ist, der Entprellcounter wieder auf 0 gesetzt. So muss die Taste also n mal hintereinander als gedrückt erkannt werden.
Wird sie nun als gedrückt erkannt, wird im Register key_0 das Bit 3 gesetzt. Es ist sozusagen ein Merker für den aktuellen Tastenstatus. Bit 0-2 werden für den Entprellcount benutzt. Für n habe ich hier 2 eingestellt, die Taste wird also recht schnell als gedrückt erkannt. Das ist sinnvoll. Es gibt sogar Algorithmen, die bei der ersten Erkennung schon reagieren. Problematisch ist dabei, wenn irgendein Glitch auf der Leitung dafür sorgen kann, dass die Taste als gedrückt erkannt wird. Glitche oder Spikes wollen wir ja zuverlässig rausfiltern. Da Glitche eher kurz sind (im us-Bereich, auf jeden Fall kürzer als 10ms), können wir recht sicher davon ausgehen, das bei einem zweiten positiven Ergebnis die Taste wirklich gedrückt ist. Das zufällig ein zweiter Glitch genau nach 10ms auftaucht, ist extrem unwahrscheinlich.
Wird die Taste nun als gedrückt erkannt, wird zusätzlich noch ein Eventbit "KEY_EVENT_ON" gesetzt. Eine Unterscheidung zwischen Event und Zustand ist hier wichtig. Ein Event wird einmal ausgelöst, genau beim Übergang von losgelassen auf gedrückt. Die Anwendung kann dieses Bit auswerten, reagieren und dann dieses zurücksetzen. Das erleichtert uns die Programmierung, im Gegensatz zur Auswertung des aktuellen Tastenzustandes (Bit 3 von key_0).
Hier werden die Events immer durch die Interrupt-Service-Routine (ISR) gesetzt (ausgelöst) und von der Anwendung ggf. zurückgesetzt (verarbeitet). In aufwändigeren Systemen verwendet man Event-Queues, damit mehrere schnell hintereinander auftauchende Events nicht verloren gehen. Denn wenn hier zwei Tasten-On-Events eintreffen, bevor wir sie in der Anwendung verarbeitet haben, dann sieht die Anwendung nur einen Event. Der andere ist verlorengegangen.
Beim loslassen der Taste sind wir nun vorsichtiger, hier findet vor allem die Entprellung statt. Das Schließen der Taste geschieht ja unpräzisse, der Kontakt federt oder bei Schleifkontakten gibt es Momente, wo der Kontakt kurzzeitig wieder uinterbrochen ist. Hier wollen wir uns nicht unzählige KEY_ON_EVENT's einfangen.
Eine Taste gilt erst dann also losgelassen, wenn über n Timereinsprünge sie als losgelassen erkannt wird. Auch hier verwenden wir wieder Bit 0..2 von key_0 als Entprell-Counter. Wir setzen n auf 3. Auch hier ist es so: Wird auch nur einmal die Taste wieder als gedrückt erkannt, wird der Entprell-Counter auf 0 zurückgesetzt. Es braucht also schon 3 Einsprünge direkt hintereinander, wo die Taste als losgelassen erkannt wurde. Den Wert kann man noch bis zu 7 erhöhen, weil der Prellcount 3 Bit hat. Jedoch sollte man es nicht zu sehr ausdehnen, weil dann die Taste träge wird. Wenn jemand schnelle Eingaben macht, könnte es Probleme geben. Nicht jedoch hier in unserer Anwendung, da werden ja keine schnellen Eingaben gemacht.
Ist die Taste wirklich losgelassen, wird ein KEY_OFF_EVENT generiert und der jetzige Zustand der Taste in Bit 2 von key_0 gespeichert (0=offen). Die Anwendung kann also auch den Loslasszustand auswerten und reagieren. Tatsächlich machen wir das auch an einer Stelle.
Auf Events zu reagieren beinhaltet auch eine Gefahr. Verliert oder vergisst man irgendwo einen Event, dann könnte man in eine nie endende Endlosschleife geraten. Dies wäre z.B. der Fall, wenn wir an folgender Stelle dieses Programm geschrieben hätten:
m_l3: RESET_KEY_EVENT m_l99: ;Taste gedrückt, warte bis losgelassen sleep sbrc key_0, KEY_EVENT_OFF rjmp m_l2 rjmp m_l99
Mit RESET_KEY_EVENT löschen wir zuvor alle Events, was mitunter sinnvoll sein kann, um nicht auf ältere Tastendrücke zu reagieren. Hier warten wir jedoch unbedingt auf den KEY_EVENT_OFF, weil wir auch davon ausgehen können, das die Taste gedrückt sein muss. Wenn die Taste jedoch vor dem "RESET_KEY_EVENT" schon wieder losgelassen wurde (was theoretisch sein kann), löschen wir damit diesen Event. Jetzt wird kein erneuter KEY_EVENT_OFF kommen und die Schleife würde erst dann verlassen, wenn der Anwender erneut die Taste drückt und loslässt. Dies schonmal als Warnung, solche Zustände oder Bugs werden einem als Softwareentwickler irgendwann mal begegnen.
Als Schmankerl gibt es nun noch einen weiteren Event: KEY_EVENT_LONG_ON. Dieser wird ausgelöst, wenn die Taste für etwa 2 Sekunden geschlossen gehalten wird. So etwas kann sinnvoll sein. Auf Computertastaturen wird damit die Dauerfunktion einer Taste aktiviert, bei fast jeder Digital-Armbanduhr wird damit in einen Einstellmodus umgeschaltet (Bedienungsanleitung: "halten sie für 2 Sekunden die Einstelltaste gedrückt...").
Hierfür benötigen wir einen weiteren Counter (key_0_longcount), der 200 Einsprünge in den Timer abwartet, bevor er einen Event auslöst. Gesetzt wird er dort, wo auch der KEY_EVENT_ON gesetzt wird. Ist er bei 0 angekommen, löst er einmal den Event aus und verbleibt dann auf 0. Gesetzt wird er erst wieder, wenn ein erneuter KEY_EVENT_ON kommt.
Wir brauchen diese Funktionalität in unserer Anwendung nicht, aber die Tastenentprellung ist ja eher universeller Art. Beim Schreiben von Code sollte man sich immer überlegen, wie generisch die Lösung sein soll. Es geht dabei um Code-Wiederverwendung. Diese anzustreben ist oft sinnvoll. Und gerade eine Tastenentprellung schreit förmlich nach Wiederverwendung, hat doch fast jede Mikrocontrolleranwendung mindestens einen Taster zu bedienen. Wiederverwendung bedeutet Zeitersparniss und Code, der in der Qualität immer besser wird. Hier lohnt es sich, einmal mehr Zeit für einen generischen Code zu spendieren, als eine schnelle Lösung für einen konkreten Anwendungsfall zu generieren.
In dieser Art könnte man natürlich weitere Tasten einbinden. Für jede Taste bräuchte es 2 Byte, will man den KEY_EVENT_LONG_ON, ansonsten reicht 1 Byte. Allerdings sollte man indirekte Adressierung verwenden, um nicht n mal den gleichen Code schreiben zu müssen. Bei mehr als 3-4 Tasten lohnt sich ein Matrix-Konzept, wo man mit Zeilen- und Spaltenleitungen arbeitet. Diese Matrix wird dann regelmäßig gescannt.
In der Anwendung hat man es nun bequem, man braucht nur die EVENT-Bits auszuwerten, sie zurückzusetzen und entsprechend zu reagieren. Der Code im Timer-Interrupt nimmt uns also schon eine Menge Arbeit ab.
Kommen wir zum Comparator. Die Initialisierung machen wir wegen der Übersichtlichkeit in einem Macro:
.MACRO INIT_COMPARATOR cbi ACSR, ACIE ;No Interrupt cbi ACSR, ACD ;Comparator enable sbi ACSR, AINBG ;Internal Reference .ENDMACRO
Wir brauchen keinen Interrupt. Und die interne Bandgap-Referenz soll benutzt werden.
Folgend brauchen wir nur noch das Bit ACO im ACSR Register auszuwerten. Ist es gesetzt, haben wir Unterspannung und könnten die Lampe ausschalten.
Allerdings wäre das ungünstig. Auch hier könnten Glitches oder Spikes dazu führen, dass im wahrsten Sinne des Wortes "das Licht ausgeht wo wir eigentlich erleuchtet sein wollen".
Realisiert habe ich eine sehr vorsichtige Auswertung: Erst wenn 2 Sekunden lang alle 10 ms hintereinander die Spannung unterhalb des Schwellwertes liegt, gilt die Bedingung Unterspannung. Dies wird durch das Unterprogramm Check_U realisiert:
;Check auf Unterspannung ; erst, wenn n-mal hintereinander Unterspannung detektiert, wird ; ein Unterspannungs-Event generiert Check_U: sbic ACSR, ACO rjmp cu_l2 ; Unterspannung -> Count clr low_u_count rjmp cu_l1 cu_l2: inc low_u_count ldi a, 200 cp low_u_count, a brcs cu_l1 ; noch kleiner MaxCount ori flags, FLAG_MASK_LOW_U ;Unterspannung Event cu_l1: ret
Aufgerufen wird es alle 10ms über die Hauptschleife. Nachfolgend wird in dieser Hauptschleife das FLAG_MASK_LOW_U ausgewertet. Bewusst ist hier eine lose Kopplung zwischen Unterprogramm und Hauptprogramm über ein Flag gewählt worden. Damit kann Check_U irgendwo, irgendwie in bestimmten Zeitabständen aufgerufen werden und irgendwo anders die Auswertung geschehen. Diese Art von Design ist damit recht flexibel, auch wenn es hier im konkreten Fall so nicht hätte sein müssen. Man hätte das Unterprogramm mit einem Rückgabewert versehen können, z.B. über ein gesetztes Carry-Flag.
Anregungen
Die Abschaltung bei Unterspannung schützt den Akku. Wenn man nun aber doch noch ein paar Minuten Licht braucht, ist die Lampe dazu nicht zu bewegen. Eigentlich wäre es besser, wenn man frühzeitig gewarnt würde und dann noch Zeit hat.
Solch eine Warnung könnte man mit einer Extra-LED bewerkstelligen. Die weiße Haupt-LED kann man leider nicht blinken lassen, weil die ja zur Versorgung des Prozessors immer bestromt sein muss. Man kann jedoch einen Portpin des Prozessors z.B. mit einem 330 Ohm Widerstand an Plus klemmen. Wird dieser Pin dann nach Masse gezogen, fließen darüber etwa 10 mA. Diese fehlen der LED, welche dann deutlich dunkler wird. So könnte man ein Blinken mit Heller-Dunkler hinbekommen. Ein paar Portpins sind ja dafür noch zu gebrauchen, die, die sonst nur für die Programmierung benutzt werden. Allerdings muss man evtl. mit Jumpern zusätzliche Bauteile entkoppeln, damit man den Chip noch programmieren kann.
Benutzt man einen anderen Chip mit AD-Wandler, so lässt sich viel komfortabler eine leerwerdende Batterie erkennen. Bei einem Comparator hat man ja nur einen Schwellwert, bei einem AD-Wandler kann man genauere Aussagen über die Spannung machen. So kann man schon frühzeitig erkennen, wann die Batterie leer wird, um noch einen längeren Übergangsbereich zu haben, bevor man die Lampe abschaltet. Mögliche Chips für diese Anforderung wären der ATtiny 13, ATtiny 15, ATtiny 26 oder der recht neue ATtiny 45.
Eine Einfachlösung für die jetzige Lampe, könnte darin bestehen: Die erste Minute nach dem Einschalten überprüft man die Unterspannung nicht. Damit hat man im Notfall zumindest die Möglichkeit, jeweils 1 Minute Licht zu haben.