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.