Akkuwächter
Diese kleine Schaltung, habe ich
zur Überwachung eines 12 Volt Bleiakku
(Autobatterie) aufgebaut,
der
in Verbindung mit einem Solarpanel und Laderegler einen Wohnwagen mit
Strom versorgt. Leider verfügt der Regler über keine Anzeige der auf
den Ladezustand des Akkus schließen lässt. Hier soll dieses
Miniprojekt Abhilfe schaffen.
Auf der Platine befinden sich 8 LEDs, die in einer Reihe angeordnet sind
und als Bargraph Anzeige dienen. Je höher die Spannung am Akku
ist, desto mehr LEDs leuchten. Dabei füllt sich der Bargraph von links
nach rechts, wie man es z.B. von der Ladeanzeige eines Handys her
kennt. Da bei einem 12 Volt Akku Hauptsächlich der Bereich von ca. 12 - 14 Volt
Interessant ist, leuchtet bei 12 Volt nur die erste (rote) LED.
Alle weiteren LEDs leuchten dann in einstellbaren Schritten (z.B. 12,3
V
12,6 V usw.), bis dann bei 13,8 Volt alle 8 LEDs aufleuchten und
somit einen vollen Akku anzeigen. Der Akkuwächter ist also ein
Voltmeter, welches den Bereich von 12 - 14 Volt (wenn auch nur mit
geringer Auflösung) anzeigt. Aber es kann noch mehr. Da ein
Bleiakku sehr empfindlich auf eine Tiefentladung
reagiert, zeigt die
erste (rote) Led durch blinken an, wenn die Spannung am Akku,
unter einen einstellbaren Schwellwert gefallen
ist. Ich habe
hier 11,5 V eingetragen, da die LED ja schon vor der Tiefentladung (lt.
Wiki 10,5 Volt) warnen soll.
Ab 14,2 Volt am Akku kann man annehmen, dass der Akku gerade aufgeladen
wird. Die
LEDs zeigen mit einem sich immer wieder füllenden Bargraph an, das sich
das Akku im Lademodus befindet (auch hier lässt die Handyanzeige
grüssen). Bei der Anzeige des Auflademodus, habe ich etwas gespielt. So
kann man aus mehreren Lauflichteffekten wählen, welche den Lademodus
anzeigen. Mehr dazu bei der Beschreibung der Firmware.
Wer sich etwas mit der Programmierung der AVR Controller auskennt könnte mit dieser Schaltung auch leicht andere Anwendungen programmieren. Z.B währe eine Pegelanzeige für Audio oder ein minimal Thermometer kein Problem. Fast alles eben, wo eine Bargraph Anzeige und ein Analogeingang ausreicht.
Die Schaltung

Die Schaltung wird vom Akku mit Strom versorgt und hat deshalb einen kleinen 78L05 Spannungsregler mit auf der Platine. Vor dem Spannungsregler ist der Eingang des Spannungsteiler (10k, 3k3) verbunden der die Akkuspannung passend für den internen Analog - Digitalwandler des Tiny13 ADC2 (Referenzspannung=5Volt) ca. 4:1 herunterteilt. So sind Spannungen bis 20 Volt keine Gefahr für den Analogeingang des ATTiny13. In der Software stehen dann für den Interessanten Bereich von 10 - 15 Volt 256 Bit zur Verfügung, was für diese Anwendung mehr als genug ist. Die Toleranz der Widerstände ist nicht kritisch, da der Abgleich über die Software mit einem Multimeter erfolgt.
-Achtung- Ich habe in dieser Schaltung keinen Verpolungsschutz vorgesehen, deshalb aufpassen beim anschließen.
Charlie lässt grüssen
Den meisten werden wohl sofort die ungewöhnliche Anordnung der LEDs
auffallen, welche notwendig ist um mit nur 4 Portpins 8 LEDs zu
schalten. Diese Technik ist nicht neu. Da ein Mann namens Charlie auf
diese Tricky Schaltung gekommen ist, nennt man diese Charlieplexing.
Diese Art der LED-Ansteuerung macht sich zunutzen, das es in der
digitalen Welt eben doch nicht nur high und low gibt, sondern noch
einen 3. Zustand. Tristate eben. Um einen Pin des Tiny in den Tristate
zu schalten, muss dieser als Eingang programmiert werden. Der Pullup
Widerstand wird abgeschaltet, dann ist der Pin Hochohmig. Der
Rest dürfte klar sein. Für die LED die leuchten soll, werden beide
Portbits, die mit der LED verbunden sind auf Ausgang programmiert. Die Anode wird auf high geschaltet
die Kathode auf low. Damit wird klar, das die Ansteuerung von der
Softwareseite etwas komplexer wird. Anstatt wie bei der herkömmlichen
Methode nur ein Bit zu setzen um eine LED zu schalten, muss man beim
Charlieplexing 2 Bitmuster in 2 Register schreiben um nur eine LED
leuchten zu lassen.
Übrigens sehe ich keinen Grund, bei meiner Schaltung nach jedem AVR-Pin einen Vorwiderstand
mit halben Wert zu schalten, wie man es oft bei ähnlichen Schaltungen
im WWW sieht.
Leider hat
diese Technik auch ihre
Grenzen (zumindest ohne speziellen Interfacebausteine) da, wenn alle
LEDs gleich hell leuchten sollen, immer nur
eine LED gleichzeitig leuchten darf. Will man alle LEDs (wie in meiner
Schaltung), unabhängig von einander schalten, muss man
multiplexen. Da beim muxen jede der 8 LEDs nur ein achtel ihrer Zeit
leuchtet, muss man auch den 8-fachen Strom durch die LED treiben um auf
die normale Helligkeit der Led zu kommen. So überschreitet man sehr
schnell die im Datenblatt angegebenen Grenzen der LEDs und des
Mikrocontrollers. Wollte man Treiber verwenden, müssten diese den Strom
in beide Richtungen treiben können, das währe zwar möglich, aber dann
könnte man auch gleich Schieberegister (evtl. sogar mit mit internen
Treibern) verbauen und evtl. über eine normale Matrix multiplexen.
Die LEDs oben sind sogenannte Low
Current LEDs die schon ab 2 mA
genügend hell leuchten. Man könnte sogar zu den vorhandenen 8 LEDs noch
4 weitere LEDs anschließen (jeweils ein Pärchen zwischen PB0 und PB1
und zw. PB2 und PB3, hier währe ein weiterer Vorwiderstand nötig) und
hätte dann insgesamt 12 LEDs an 4 Portpins ,
währe dann aber schon mit 24mA an einem Portpin im roten (oder
rötlichem )
Bereich. Da 8 LEDs in dieser Schaltung durchaus genügen und diese bei
einem 8Bit-µC auch einfacher zu handhaben sind, wollte ich hier nicht
gierig sein. Natürlich könnte man auch einen grösseren Controller
nehmen und die LEDs in Herkömmlicher Technik ansteuern, aber ich wollte
Charlieplexing schon immer mal ausprobieren und diese Schaltung gab mir
die Möglichkeit dazu.
Interessant dürfte die Schaltung auf jeden Fall für den Modellbau sein,
da hier oft auf engstem Raum viele LEDs geschaltet werden sollen.
Die Platine...
...ist
einseitig und in THT Technik
aufgebaut und deshalb auch
hervorragend für Anfänger geeignet. Die 8 LEDs lassen sich auch
abgewinkelt einbauen, wenn die Anschlussdrähte um 90° gebogen werden.
Ich habe bei mir für LED1 die Farbe rot gewählt, dann 4 mal gelb und
drei mal grün. Natürlich kann man die Farben auch anders als bei mir
wählen, besonders dann, wenn man die Schwellwerte individuell an die
Entladekurve seines Akkus anpassen möchte und in der Software andere
Schwellwerte der Spannungen einträgt, was leicht möglich ist. Für den
ATTiny13, habe ich eine 8polige Fassung vorgesehen. Diese ist
notwendig, wenn der Tiny13 mit einem AVR-Dragon geflasht werden soll.
Der Dragon weigert sich nämlich strikt den Tiny in der Schaltung (ISP)
zu flashen, da die LEDs (hauptsächlich SCK) direkt mit den ISP Leitungen
verbunden sind. in diesem Fall kann man sich auch gleich das bestücken
des ISP Steckers JP1 sparen. Der Tiny kann natürlich außerhalb
der Schaltung (Nullkraftfassung oder Breadboard) auch mit einem Dragon
geflasht werden. Da ich auch einen AVRISP2 besitze, habe ich
diesen ausprobiert und siehe da, das proggen des Tiny klappt mit
diesem auch in der Schaltung per ISP.
Stückliste
R1, R5 |
10k |
Alle Widerstände
1/4 Watt Metallfilm |
R2 |
3,3K |
|
R3, R5 |
220R |
|
C1 |
10..47µF |
Elko |
C2...C4 |
100nF |
Kerko |
LED1...8 |
LED |
3mm Low Current LED |
IC1 |
78L05 |
Spannungsregler 5Volt 100mA |
IC2 |
ATTiny13 |
µC Atmel |
JP1 |
Pfostenleiste |
6 polig (2*3), 90° abgewinkelt, RM 2,54 |
Die Software
Das Programm ist wie bei allen meinen Projekten in Assembler
geschrieben. Um eine brennbare Hex Datei zu erhalten, muss der Code mit
dem (kostenlosen) AVR-Studio 4.19 assembliert werden. Eine kleine Einführung dazu gibt es hier. Man
braucht in diesem Fall nur die Hex Datei brennen, die Fusebits können
bleiben wie sie sind.
Da der 78L05 als Referenzspannungsquelle benutzt wird, der ADC und
auch unsere Widerstände Toleranzen haben, wird der Abgleich über
Software durchgeführt. So hängt die Genauigkeit fast nur noch vom
verwendetem
Multimeter ab, bei welchen auch schon preiwertere Modelle, recht gute
Messergebnisse im Spannungsbereich liefern.
Abgleich
Dazu brauchen wir eine installierte und lauffähige Version von AVR-Studio 4.19, ein Netzteil mit einstellbarer Spannung und ein Multimeter. Natürlich ist ein Labornetzteil mit genauer Spannungsanzeige hierfür optimal. Nach dem einfügen des Quellcodes in das Studio, steht im oberen Teil unter Programdefs alles, was für den Abgleich nötig ist.
;Programmdefs
;***********************************************************************************
.set messen = 0 ;Auf 1 setzen, um den Binärwert der AD-Messung anzuzeigen
.set player = 4 ;Einen Player wählen (0-5)
.equ delay = 100 ;Je höher der Wert, desto langsamer das Lauflicht (1-255)
.equ load = 0b11100010 ;Der AD-Wert bei 14,2 Volt
.equ lowbat = 0b01011000 ;Der AD-Wert bei 11,5 Volt
;-----------------------------------------------------------------------------------
;Der AD-Wert (Schwellwert) bei dem der Bargraph bis zur LED X aufleuchten soll
;-----------------------------------------------------------------------------------
;LED1 (rot) leuchtet von 11,5V - 12.0V (Wert in lowbat). Unter 11,5V blinkt LED1
.equ led2 = 0b01110010 ;gelb 12,0 V
.equ led3 = 0b10000001 ;gelb 12,3 V
.equ led4 = 0b10010001 ;gelb 12,6 V
.equ led5 = 0b10100000 ;gelb 12,9 V
.equ led6 = 0b10110000 ;grün 13,2 V
.equ led7 = 0b10111100 ;grün 13,5 V
.equ led8 = 0b11001100 ;grün 13,8 V
Zunächst wird .set
messen = 0 in .set messen
= 1
geändert und mit dieser Einstellung der ATTiny13 geflasht. Dies
bewirkt, das auf den 8 LEDs der rohe Binärwert der Versorgungsspannung
angezeigt wird, welcher der ADC liefert. Die Spannung muss sich hierbei
zwischen etwa 10V und 15 V bewegen. Bei Spannungen unter 10 Volt sind
die LEDs aus, über 15 Volt leuchten alle LEDs. Achtung, nicht über 20
Volt einstellen, da sonst der 78L05 und evtl. der Elko
(Spannungsfestigkeit) Schaden nehmen könnten. Dreht man also am Poti
der Spannugsquelle zwischen 10..15 Volt, ändert sich fortlaufend der
Binärwert der 8 LEDs.
Nun stellt man das Netzteil auf den Spannungswert ab dem die
Ladeanimation angezeigt werden soll (bei mir 14,2 Volt). Diesen Wert
trägt man bei load ein, wobei
man meine Messung Natürlich entfernen muss. LED1 kommt dabei in die
Stelle ganz rechts, LED8 nach ganz links. Es ist also einfacher die
Platine um 180° zu drehen so das die 8 LEDs oben sind. Das 0b muss vor
jedem Bitmuster eingetragen werden, damit der Assembler weis, das es
sich um eine Binärzahl handelt.
Der Wert bei lowbat (bei mir 11,5V) ist die untere Schwelle bei der
LED1 blinkt. Wird diese Schwelle unterschritten, blinkt die LED, beim
überschreiten leuchtet LED1 fortlaufend. Sind die beiden Sonderfälle
erledigt, stellt man fortlaufend die Spannungen für LED2...LED8 ein,
liest den Binärwert ab und trägt diesen gleich in den Code ein.
Nun kann der Eintrag messen
wieder auf 0 geändert werden. Nach dem assemblieren und flashen prüft
man die Anzeige durch ändern der Spannung zwischen 10 und
15 Volt auf plausible Werte, wobei sich die Bar zwischen 12 und 14 Volt
von links nach rechts füllen sollte.
Sonstige Einstellungen
Der Wert delay enthält die Zeitdauer für das blinken der LED und den Player. Ein Tick dauert 1,7 mS. Man kann Werte zwischen 0 und 255 eintragen.
Der Player
Es kann
ein Player aus 6 möglichen augewählt werden (0..5).
Bei Werten von player =
0..2 wird ein Lauflicht nach Knight Rider Vorbild gewählt, wobei bei 0
immer nur eine LED von rechts nach links wandert. Bei 1 sind es 2
wandernde LEDs und bei dem Wert 2 sind es dann letztendlich 3 LEDs.
player = 3 ist ein einfacher
Binärzähler. player = 4 ist
der voreingestellte Ladebalken nach Handymanier.
Bei player = 5 wird eine
Tabelle als eine Art Daumenkino benutzt. Die Bitmuster werden von oben
nach unten der Reihe nach angezeigt und fangen nach dem Ende wieder am
Anfang an. Will man sich hier kreativ betätigen, muss im Quellcode
folgender Programmteil gesucht werden.
;Tabelle
;---------------------------------------------------------------------------
.if player ==5
play: ldi temp3,(tabende-tab)*2;Anzahl der Tabelleneinträge
;(Der Assembler kennt nur Words im Flash, deshalb *2)
ldi zl,low(tab*2) ;Zeiger auf Tab
ldi zh,high(tab*2)
tab1: lpm leds,z+ ;Tabelleneintrag nach LEDs holen
rcall ad ;Zwischenduch messen
cpi temp1,load ;Sind wir noch über 14,2 Volt?
brcs main ;Wenn nein, Rücksprung
rcall wait ;Zeit vertrödeln
dec temp3 ;Anzahl Tabeinträge - 1
brne tab1 ;Ist noch was da? Wenn nein nächsten Tabeintrag bearbeiten
rjmp play ;Ansonsten, das ganze von vorn
;---------------------------------------------------------------------------
;Tab mit dem Daumenkino
;---------------------------------------------------------------------------
tab: .db 0b00000000,0b00011000
.db 0b00111100,0b01111110
.db 0b11111111,0b01111110
.db 0b00111100,0b00011000
tabende:
.endif
;---------------------------------------------------------------------------
Die eigentliche Tabelle steht zwischen tab: und tabende:. Diese beiden Labels bitte stehen lassen. Es müssen immer eine gerade Anzahl von Bytes in einer Zeile stehen, ansonsten produziert der Assembler Warnings und fügt selbst nullen ein um auf Wortbreite zu kommen. Die Anzahl der Bytes ist nicht beschränkt. Man muss nur darauf achten, das das Bitmuster des letzten Bytes wieder zum ersten Byte in der Tabelle passt.
Programm
Wer mit dem Quellcode experimentieren, oder wissen will, wie
Charlieplexing in meinem Assemblerprogramm funktioniert, findet hier
ein paar
Infos. Wer den Akkuwächter nur nachbauen möchte, braucht diesen
Teil nicht lesen. Ich halte diesen Teil besonders für Programmierer Interessant, deshalb schildere ich kurz diese Funktion.
Das Charlieplexing selbst, wird komplett im Timerinterrupt erledigt. Im Hauptprogramm braucht nur das Register leds
mit dem Bitmuster geladen werden. Dabei bedeutet eine 1 LED an und eine
0 LED aus, ganz wie man es vom beschreiben eines Ports gewohnt ist.
Multiplexing
Um alle LEDs unabhängig von einander ein und ausschalten zu können
müssen diese gemultiplext werden. Sollen z.B. alle LEDs eingeschaltet
werden, leuchtet eine LED in Wirklichkeit nur ein 1/8 ihrer Zeit, bevor
die nächste LED ihr 1/8 bekommt. Sind alle LEDs durch, fängt das ganze
wieder von vorne an. Macht man das nur schnell genug, entsteht der
Eindruck, das jede LED andauernd leuchtet. In
meinem Programm dauert das 1/8 der Zeit 1,7 Millisekunden (mS).
Da der ATTiny genau zu diesen Zweck einen Timer eingebaut hat, wird
dieser dazu benutzt, alle 1,7 mS einen Interrupt zu erzeugen. Der
Interrupt wird also ca. (1 / 1,7mS) 588 mal, pro Sekunde aufgerufen.
Da sich die 8 LEDs die Aufrufe Teilen, wird jede einzelne LED (588/8)
73,5
mal pro Sekunde angesteuert sodass unser Auge zu unserem Gehirn sagt: Ja, die LED ist an, da flackert nix .
Für
die Bitmuster, die in die Portregister geschrieben werden, habe ich
eine Tabelle angelegt. Da für jede LED, das beschreiben von 2 Registern
erforderlich ist, hat die Tabelle 16 Einträge. Die ersten beiden
Einträge sind
für LED1 bestimmt und enthalten den Wert für das Datenrichtungsregister
ddrb (1.Wert) und PortB
(2.Wert) . Danach folgen die beiden Einträge für LED2. So wird die
Tabelle bis zum Ende fortgeführt.

Im Schaltplanausschnitt oben, habe ich die Leitungen eingefärbt um
zu verdeutlichen, wie PB0..3 anzusteuern sind, damit LED1 leuchtet. Rot
= +5Volt, blau= GND, gelb = Tristate. Um den ersten Wert der Tabelle
(ddrb) zu bestimmen, setzt man die beiden Portbits die Tristate
geschaltet werden auf 0 (Eingang) und die restlichen beiden auf 1
(Ausgang) und erhält Binär 0b1010. Damit die LED leuchtet, setzt man im
2. Wert (portb) die Anode von LED1 auf 1 und erhält 0b0010, wobei Bit3
PB3 auf GND schaltet und Bit 0 und 2 die Pullups der Tristateausgänge
abschalten.
Beim Programmstart wird diese Tabelle
ins SRam kopiert, da AVRs nur mit Hilfe des Z-Register auf den Flash
Speicher zugreifen können und dieses auch im
Hauptprogramm benötigt wird. Die ISR ist Geschwindigkeitsoptimiert. Man
könnte auch eine Tabelle mit 8 Bytes erstellen, müsste dann aber die
Nibbles swappen und ausmaskieren was zusätzliche Zeit in Anspruch
nehmen würde.
Der Interrupt macht folgendes:
- Alle Portbits Tristate schalten (LEDs aus)
- Nächste LED ins Carry holen
- Prüfen ob alle 8 LEDs durch waren, wenn ja Tabellenzeiger Rücksetzen und wieder bei LED1 anfangen
- Nächsten Tabellenwerte holen und Tabellenzeiger erhöhen
- Carry auf LED einschalten testen, wenn C=1 LED einschalten (Tabellenwerte in DDRB und PortB Register schreiben)
- Ansonsten, Interrupt Ende (LED bleibt aus, Tabellenwerte verwerfen)
Hier die ISR (Interrupt Service Routine), die alle 1,7 mS aufgerufen wird.
;Timerinterrupt ca. alle 1,7 mS. Ca. 73 Hz Muxfrequenz
;***********************************************************************************
in s,sreg ;SREG wegspeichern
dec tick ;Alle 1,7 mS (Variable für Unterprogramm wait)
out ddrb,null ;Alle Leds aus (Tristate)
out portb,null ;Pullups auch aus
lsl ileds ;Nächste Led ins Carry
brne LEDtst ;War LED8 schon durch?
movw y,arrayl:arrayh ;Wenn ja, Tabellenzeiger auf LED1
mov ileds,leds ;ileds Update
sec ;Zeroflag wird nach dem 8. "lsl ileds" gesetzt
rol ileds ;LED1 ins Carry
LEDtst: ld itemp1,y+ ;Tristate und Portdaten der gerade aktiven LED
ld itemp2,y+ ;Immer laden damit Tabellenzeiger erhöht wird (Y+)
brcc LEDoff ;Bei C=0, LED ausgeschaltet lassen
out ddrb,itemp1 ;in die µC Register schreiben
out portb,itemp2
LEDoff: out sreg,s ;SREG zurück
reti
;***********************************************************************************
;LED Tabelle
;***********************************************************************************
;---------------------------------------------------------------------------
;Zum LED Einschalten Tabwert in DDRB - PORTB schreiben
;Zum Ausschalten der LED in beide Register eine null (Tristate, Pullup aus)
;Die Tab wird um den Z-Pointer freizuhalten ins SRam kopiert
;---------------------------------------------------------------------------
LEDTab: .db 0b0101,0b0100 ;LED1 einschalten, 1. Wert in DDRB, 2. Wert PortB
.db 0b0101,0b0001 ;LED2 ein usw.
.db 0b1001,0b1000
.db 0b1001,0b0001
.db 0b0110,0b0100
.db 0b0110,0b0010
.db 0b1010,0b1000
.db 0b1010,0b0010
;***********************************************************************************
Nach dem sichern vom Statusregister und dem dekrementieren einer
Variablen (soll hier nicht Interessieren), werden erst einmal
alle
Ausgänge auf Tristate gelegt und somit die LEDs abgeschaltet. In ileds befindet sich eine Kopie von leds. Letzteres wird im Hauptprogramm beschrieben.
Bei jedem Interrupt, wird aus ileds ein Bit ins Carry geschoben mit welchem entschieden wird, ob die LED leuchten soll oder nicht. Gleichzeitig schiebt der lsl Befehl eine 0 in ileds, so das nach 8 Interrupts Ileds zu 0b00000000 wird und der bedingte Sprung brne LEDtest
nicht stattfindet. So wird fortlaufend alle 8 Interrupts der
Y-Pointer auf den Tabellenanfang (Bitmuster für LED1) gesetzt, und ileds neu geladen. Beim Laden des Bitmusters wird mit dem nachfolgenden rol Befehl eine Zusätzliche 1 mit eingeschoben um sicherzugehen, das immer 8 Schiebebefehle ausgeführt werden, bevor ileds
wieder 0 wird. Die eingeschobene 1 ist dann durch das komplette Byte
gewandert und wieder im Carry angekommen (deshalb könnte man sogar noch
den sec Befehl wegoptimieren , zum besseren Verständnis lasse ich den mal stehen).
Mit dieser Technik spart man sich ein Register das man als
Interruptzähler Reservieren müsste und macht den Code noch etwas kürzer
(und schneller), was in der ISR besonders wünschenswert ist. Auch aus
Geschwindigkeitsgründen, wurde hier der
Y-Pointer (Zeigt auf Tabelle LED1) beim Programmstart in die Register
arrayl und arrayh kopiert, und kann so mit einem Assemblerbefehl in der
ISR, als 16Bit-Wort gemoved werden. Die Tabellenwerte werden immer
geladen, auch wenn die entsprechende LED nicht leuchten soll, da mit
dem laden auch gleichzeitig der Tabellenzeiger erhöht wird. Das
geht deutlich schneller als vergleichen, verzweigen und
anschließendem addieren zum Zeiger. Hier werden die beiden out
Befehle einfach übersprungen, wenn die LED ausbleiben soll.
Anmerkung am Schluss:
Nach Fertigstellung meiner Platine, habe ich einen Denkfehler
meinerseits, in der Software ausgeglichen. Der Bargraph bewegte sich von
rechts nach links bei einer Spannungserhöhung .
Da ich die gewohnte Richtung von links nach rechts bervorzuge, habe ich
die Reihenfolge der Werte in der Tabelle so geändert, das man die Tabelle nun von
unten nach oben lesen muss.
Download
Hier könnt ihr euch die Eagle Dateien und den Quellcode downloaden.
Viel Spaß beim Basteln,
Jürgen