AVR-Mikrocontroller Einsteiger-Tutorial: 9V-LED-Lampe

Winfried Mueller, www.reintechnisch.de, Start: 30.10.2005, Stand: 09.02.2006

Die Welt der Mikrocontroller ist schon faszinierend. Verbindet sich hier doch Soft- und Hardwareentwicklung auf eine interessante Art und Weise. Man schreibt eine Software auf seinem Computer, lädt sie in einen kleinen Chip hinein, der dann eine elektronische Schaltung steuert.

Wer in diese Technik einsteigen möchte, macht das am besten anhand eines überschaubaren Beispielprojektes. Eigentlich ist die Sache ja ganz leicht, jedoch nur für den, der es kann. Wer neu einsteigt, kann tausende von Fehlern machen und für viele reichen schon wenige, um frustriert aufzugeben. Mit einem guten Beispielprojekt vermeidet man viele Fehler und lernt eine Menge für erste eigene Projekte. Die eigene Geduld wird nicht überspannt.

Wichtig finde ich dabei, dass man auch wirklich versteht, was man tut. Viele wollen gleich mit coolen Megaprojekten anfangen - ein Webserver mit MP3, der auch noch mit Handy kommuniziert und SMS empfangen kann. Nichts wird richtig verstanden und es braucht Glück, damit überhaupt irgendwas funktioniert. Wenn es gut läuft, kann man dann sagen: Es geht, frag mich aber nicht, warum. Wer wirklich verstehen will, warum etwas wie funktioniert, muss tiefer schauen.

Und so hab ich mir das hier gedacht - mit einem einfachen Einstiegsprojekt mehr in die Tiefe schauen. Dabei soll es aber nicht zu trocken werden, weshalb mir auch schnelle praktische Ergebnisse wichtig sind. Denn dadurch entsteht der Spaß an der Sache, der einen motiviert, bald eigene Projekte in Angriff zu nehmen.

Das Projekt benutzt einen kleinen AVR-Mikrocontroller von Atmel. Diese sind in Bastlerkreisen wie auch in der Industrie recht beliebt. Sie sind preiswert und man bekommt jede Menge Datenblätter, Anleitungen und Entwicklungswerkzeuge. Und man trifft auf eine große Community im Netz, die einem hilfreich zur Seite stehen kann. Zudem sind die einfachsten Bausteine dieser Familie recht überschaubar in ihrer Funktionalität.

Voraussetzungen

Was braucht man, um in so ein Projekt einzusteigen?

  • Ein Programmieradapter: Die meisten AVR-Prozessoren lassen sich seriell über eine sogenannte ISP-Schnittstelle programmieren. Durch den eingebauten Flash Speicher kann man sie sehr oft neu programmieren. Im Netz findet man Bauanleitungen, wie man sich aus wenigen Bauteilen einen eigenen Programmieradapter bauen kann. Weil man da aber auch wieder viel verkehrt machen kann, empfehle ich, dass man sich ein fertiges Gerät kauft: Den AVR-ISP Adapter. Den gibt es bei http://www.reichelt.de z.B. für derzeit knapp 40 Euro.
  • Eine Lochrasterplatine und ein paar Standardbauteile, die es für die Idee hier braucht. 0.6er Silberdraht und 0.2er Kupferlackdraht zum verdrahten der Platine.
  • Standard-Elektronikbastelwerkzeug: Lötkolben, Seitenschneider, Flachzange, Pinzette, Labornetzteil, Universalmessgerät.
  • Natürlich noch den Mikrocontroller. Wir setzen einen ATtiny 12L ein, der kostet derzeit bei Reichelt 1.50 Euro. Davon am besten 3-4 Stück bevorraten, falls man durch Fehler die Teile "himmelt". Ich habe mich bewusst für den ATtiny 12 entschieden. Einerseits benutzt er den gleichen Kern, wie die größeren AVR-Prozessoren. Andererseits ist er leicht zu überblicken, weil auf die meisten peripheren Komponenten verzichtet wurde (AD-Wandler, serielle Schnittstellen, PWM-Subsystem). Er ist damit ein guter Einstieg in die AVR-Welt.

Die Idee

Die Idee ist eine kleine LED-Nachttischlampe. Seit es leistungsstarke weiße Leuchtdioden gibt, hat ja ein richtiger LED-Lampen-Boom eingesetzt. Grund genug, selber damit zu experimentieren. Zumal in sehr kurzen Zeitabständen neue interessante Leuchtdioden auf den Markt kommen.

Betrieben werden soll die Lampe mit einer 9V-Batterie. Der LED-Strom soll geregelt werden, so dass sie immer gleichhell leuchtet, egal wie voll die Batterie noch ist.

Die LED soll mit einem Taster eingeschaltet werden und damit auch wieder ausgeschaltet werden können. Außerdem soll die Lampe nach einer gewissen Zeit von alleine ausschalten - am besten so, dass sie dann überhaupt keinen Strom mehr verbraucht.

So eine automatische Ausschaltfunktion ist eine interessante Sache, merkwürdig dass die meisten Taschenlampen nicht über so eine Funktion verfügen.

Natürlich lässt sich die Schaltung auch zum Bau einer LED-Taschenlampe nutzen. Bei Modifikation auch für leistungsstarke Luxeon-Leuchtdioden.

Der Schaltplan

(Rechte Maustaste > Grafik anzeigen zeigt den Schaltplan größer an)

Der Schaltplan ist bewusst nicht mit einem Programm erstellt, weil das die typische Art zeigt, wie ich an neue Projekte dieser Größenordnung herangehe. Ein Blatt Papier und ein Stift reichen aus, um Schaltungsideen entstehen zu lassen.

Die Schaltung beinhaltet einige Raffinessen, damit wir mit wenigen Bauteilen zu unserem gewünschten Ergebnis kommen.

Beginnen wir mit der geregelten Stromquelle. R2, T1, D1, D2 und R3 bzw. R4 ergeben die Stromquelle. Dies stellt eine Grundschaltung dar, wie man sie in vielen Lehrbüchern findet: An die Basis von T1 wird eine definierte Spannung angelegt, die über die BE-Strecke und dem Widerstand R2 wieder abfallen muss. Weil die Spannung über D1+D2 recht konstant ist (2x0.6Volt in etwa) und die BE-Spannung ebenfalls mit etwa 0.7 V konstant ist, müssen etwa 0.5V über den Widerstand R2 abfallen. Damit ist auch der Strom festgelegt, der durch R2 fließt: I = U/R = 0.5V/20Ohm = 25mA. Das ist der Strom, der hauptsächlich durch die weiße Leuchtdiode D3 fließen soll.

Eigentlich bräuchten wir für den Mikrocontroller einen Spannungsregler, denn er darf nur im Bereich von 2.7-5 Volt betrieben werden. Weil die Schaltung einfach werden soll, kommt jetzt der erste Trick: An einer weißen Leuchtdiode fallen im Betrieb etwa 3.3-3.8 Volt Spannung ab. Das ist eine passende Spannung für den Mikrocontroller. Deshalb hängt der Prozessor parallel zur Leuchtdiode. Ein kleiner Stützkondensator von 100nF (C1) stabilisiert die Spannung, dieser ist generell für den Mikrocontroller nötig und natürlich auch für fast alle anderen digitalen Schaltungen.

Die Stromquelle funktioniert nur, wenn ein Basisstrom aus T1 fließen kann. Und dafür gibt es zwei Wege: Über R3/S1 oder über R4/T2. S1 ist der Einschalter der Schaltung. Sobald dieser betätigt wird, fließt der Strom über R3, die Stromquelle liefert Strom, der in die LED fließt und zu einem kleinen Teil auch in den Prozessor. Der Prozessor wird im unserem Betriebsfall deutlich weniger als 1 mA verbrauchen. Und auch nur, wenn die Lampe eingeschaltet ist.

Wenn der Prozessor also nun Strom bekommt, beginnt er zu laufen und das Programm abzuarbeiten. Im Programm werden wir als eines der ersten Aktionen Port PB4 so schalten, dass der Transistor T2 durchgesteuert wird. Dann kann nämlich Strom durch R4/T2 fließen. Das bedeutet, dass man den Taster S1 loslassen kann und der Strom fließt trotzdem weiter. Eine Art Selbsthaltung durch den Controller könnte man das nennen.

Der erfahrene Bastler wird vielleicht mit Sorge festgestellt haben, dass wir für T2 keinen Basis-Vorwiderstand verbaut haben. In der Tat wäre das problematisch, wenn wir den Port auf Logisch 1 (High) programmieren würden. Dann würde ein Kurzschlußstrom aus dem Port fließen, weil die Basis von T2 diesen auf etwa 0.7 Volt halten will. Wir vermeiden jedoch strikt, diese Programmierung vorzunehmen. Stattdessen programmieren wir den Port als Eingang mit Pullup. Das ist der Trick: Wir nutzen den internen Pullup als Basiswiderstand, worüber der Strom in die Basis fließt. Warum? Weil wir damit einen Widerstand sparen. Das macht die Schaltung einfacher.

R6 und R1 braucht es übrigens, um die Basis der Transistoren auf einem definiertem Potential zu halten, was dem des Emitters entspricht. Damit wird sichergestellt, dass die Tranistoren wirklich sperren. Man sollte nie Transistoren mit offener Basis in der Schaltung haben. Kleinste elektromagnetische Felder im Umfeld würden die Schaltung dann undefiniert beeinflussen. Die Widerstände wurde recht hochohmig gewählt, um nicht sinnlos Strom zu verbrauchen. Im Falle von R6 muss dieser auch hochohmig genug sein, damit der interne Pullup in die Basis von T2 genügend Strom fließen lassen kann. R1 ist dagegen recht unkritisch und hätte auch niedriger gewählt sein können. Bedenken sollte man, dass bei so hochohmigen Widerständen die Schaltung recht empfindlich gegenüber elektromagnetischen Einflüssen der Umwelt wird, was uns aber hier konkret nicht sonderlich stören soll.

Der Prozessor braucht erstmal nichts weiter zu tun, als PB4 mit einem Pullup zu versehen, damit wird T2 durchgesteuert und hält die Schaltung aktiv. Die LED leuchtet, der Lampenstrom wird geregelt, alles wunderbar. Nach einer Zeitspanne jedoch, z.B. 10 Minuten, soll die Schaltung komplett ausgeschaltet werden. Und auch das geht sehr einfach: Der Prozessor entzieht sich seine Grundlage - seine Betriebsspannung. Das tut er, in dem er den Port PB4 auf Ausgang Low (0) umprogrammiert. Damit fließt kein Basisstrom mehr, T2 sperrt und nach wenigen Mikrosekunden ist C1 entladen und der Prozessor hat keinen Strom mehr. T1 sperrt auch vollständig, weshalb die Schaltung nun nahezu keine Strom mehr verbraucht. Es sind nur noch vernachlässigbare Restströme, die irgendwo durch die Bauteile "wabern".

Wozu braucht es eigentlich R5? Man hätte den Taster auch direkt mit einer Seite an Masse anklemmen können. Wir wollen jedoch den Taster mit dem Mikrocontroller abfragen können. Der Taster erhält also eine Doppelfunktion - einerseits ist er Einschalter für die Stromquelle, andererseits generiert er ein Eingangssignal für den Controller. Das bedeutet, dass der Taster entweder Low- oder High-Level generieren können muss. Und dazu braucht es R5. Im eingeschalteten Zustand zieht R5 den Port PB3 auf Low (Pull-Down-Widerstand). Wird der Taster jedoch betätigt, wird der Port über R3, D2, D1 auf einen High-Level gezogen. Das funktioniert in einem recht großen Eingangs-Spannungsbereich noch. Bei hohen Eingangsspannungen, z.B. 9V würde die Spannung höher sein, als die Betriebsspannung des Controllers. Aber auch das macht nichts, weil der Controller in den Portpins Schutzdioden eingebaut hat, die gewisse Ströme dann gegen Masse oder VCC ableiten. So auch hier: Die Spannung wird dann auf etwa VCC+0.7V an PB3 begrenzt. Der Strom, der dabei in den Pin fließt, ist maximal etwas über 1mA, was unkritisch ist.

Die restliche Verdrahtung brauchen wir noch für die Programmierung. Man braucht den Chip nicht auf dieser Platine zu programmieren und könnte sich so die Beschaltung von PB0, PB1, PB2, Reset und den Connector sparen. Es ist jedoch für einen ersten Lampen-Prototypen bequemer, direkt in der Schaltung programmieren zu können. Wenn man die Beschaltung weglässt, muss zumindest Reset mit einem Widerstand oder auch ohne direkt an VCC gelegt werden. Die Resetfunktion per Sofware abzuschalten, sollte man unterlassen, weil dann der Prozessor per ISP nicht mehr programmierbar ist.

Die Schaltung habe ich spezifiziert von 5..9 V Betriebsspannung. Das ist die Spannung, die an einer 9V Batterie im Laufe der Entladung zu erwarten ist. Die Schaltung funktioniert generell jedoch auch noch unter 5 V. Bei etwa 4.2 Volt wird jedoch die Stromregelung langsam nicht mehr im geregelten Bereich sein und damit der Strom abnehmen. Funktionieren tut es trotzdem noch bis etwa 3.3 Volt runter. Dann mit stark vermindertem Lampenstrom. Man kann also einen 9V-Block gehörig leersaugen. Mit Akkus sollte man das natürlich nicht machen, weil die darunter leiden.

Eine Warnung: Die Schaltung ist ja so ausgelegt, dass die Leuchtdiode gleichzeitig als Spannungsquelle für den Prozessor dient. Lötet man sie aus, bekommt der Prozessor nahezu die volle Batteriespannung, was ihn zerstört. Die Schaltung darf also immer nur mit Leuchtdiode betrieben werden. Besonders gefährlich wäre es, wenn man ohne LED noch den Programmieradapter angeschlossen hat. Dann würde auch dieser zerstört. Deshalb sollte man während der Experimentierphase die Eigangsspannung auf 5 V begrenzen.

Kleine Unzulänglichkeiten des Schaltplans: D1 und D2 sind 1N4148, es kann aber auch jede andere Wald- und Wiesen-Silizium Diode eingesetzt werden. T1 ist ein BC328, es kann aber auch ein anderer Kleinleistungs-PNP Typ benutzt werden, ebenso für T2, der hier ein BC547 ist. T1 sollte jedoch eine hohe Verstärkung haben. Reset Pin1 des Prozessors geht natürlich auch auf den Programmier-Connector. Der Controller ist ein ATtiny 12L. Das L steht für Low-Voltage. Der normale ATtiny 12 funktioniert hier nicht, weil er mit 5 Volt betrieben werden muss.

Die Schaltung hat übrigens noch einen interessanten Effekt: Der Strom, der über LED und Prozessor fließt, kann nicht über ca. 25 mA ansteigen. Selbst wenn man z.B. Ports falsch schaltet oder Kurzschlüsse eingelötet hat, ist das in weiten Teilen der Schaltung unproblematisch. Der Strom wird einfach durch die Stromquelle begrenzt.

Noch ein paar Worte zur Dimensionierung der Schaltung. Bei der Dimensionierung hat man meist bei jedem Wert einen gewissen Spielraum. Welche Kombination von Werten man konkret wählt, hängt von vielen Überlegungen ab und ist vor allem auch auf Erfahrung begründet.

D1 und D2 sind unkritisch, sie müssen nur wenige mA im Flußrichtung aushalten. Dafür eignen sich nahezu beliebige Siliziumdioden. Die 1N4148 ist eine sehr gebräuchliche kleine Glasdiode, die zudem extrem günstig ist. Jeder hat seine Favoriten bei Wald- und Wiesen-Bauelementen und die 1N4148 ist mein Favorit für solche Anwendungen. Beim BC328 (T1) verhält es sich ähnlich. Wenn dessen Stromverstärkung recht hoch ist, dann kommen wir mit kleinen Basisströmen aus. Nehmen wir an, er hat eine Verstärkung von mindestens 100, dann brauchen wir 30mA/100 = 0.3mA oder 300uA für die Basis. Gleichzeitig sollte jedoch auch durch die Dioden ein geringer Strom fließen, nehmen wir hier mal mindestens 100 uA. Dieser Strom muss im eingeschalteten Zustand über R4 und T2 fließen. Es spricht jedoch nichts dagegen, nicht zu knapp zu dimensionieren, weshalb ich mit 4.7K etwa doppelt so hohe Ströme fließen lasse bei 5 V: I = U / R = (5V - 0.6V - 0.6V - 0.1V) / 4.7K = 787 uA. Letzere 0.1 V ist der vermutete Spannungsabfall am Transistor T2, also die Sättigungsspannung.

T1 ist unkritisch, er muss ja nur eine Kollektorstrom etwa 800 uA fließen lassen, selbst bei einer geringen Verstärkung von 40 bräuchte man dafür nur 20 uA Basisstrom. Das wäre jedoch auch schon im Worst-Case nahe das, was wir an Verstärkung mindestens brauchen. Der interne Pullup-Widerstand des Tiny 12 kann 150K betragen und über R6 geht uns auch noch ein wenig Strom verloren, der zwar aus dem Prozessor-Pin aber nicht in die Basis fließt. Um nicht zu viel Strom für die Basis zu verlieren, ist R6 auch recht hochohmig.

Der Zweig R3/S1/R5 ist etwas hochohmiger angelegt, was aber nichts macht. Erstens hatten wir mit 4.7K R4 sowieso überdimensioniert, er hätte ja auch 10K betragen können. Außerdem muss dieser Taster-Zweig gar nicht so viel leisten, weil er nicht dafür sorgen muss, dass wirklich schon der gesamte Strom über T1 fließen muss. Es reicht ein Strom von wenigen mA, um den Prozessor zu starten und der übernimmt dann ja über T2 den ausreichenden Stromfluß für Basis von T1. Umgedreht macht es nichts, wenn in die ein etwas größerer Strom fließt, wenn sowohl T1 durchgesteuert ist, wie auch der Taster betätigt wird.

Das Verhältnis von R3 und R5 dagegen ist für den Fall wichtig, dass wir ja den Taster am Port einlesen wollen und dieser dann klar auf Low oder High liegen muss. Mit R5 doppelt so groß wie R3 liegen wir da recht gut. R5 darf für den Prozessor nicht zu groß sein, weil mit bis zu 8uA Reststrom zu rechnen ist, der in den Pin reinfließt. Und R5+R3 darf natürlich auch nicht zu groß sein, damit ein ausreichender Strom in die Basis von T1 fließt.

R1 ist in weiten Grenzen wählbar, aber auch hier wollen wir nicht, dass zu viel Strom an der Basis von T1 und an den Dioden D1 und D2 vorbeifließt. Je größer dieser Strom wäre, um so kleiner müssten dann auch R3, R4 ausfallen. Und das wäre auch wieder mehr vergeudeter Strom, der nicht in Licht umgewandelt wird. Von diesem Aspekt sollten wir also zu sehen, alle Ströme so niedrig wie möglich zu wählen bzw. alles recht hochohmig zu machen. Eine hochohmige Schaltung ist allerdings wieder sehr empfindlich auf Störeinflüsse der Umgebung. 1 MOhm ist schon recht hoch aber noch ganz praktikabel. Bei 10MOhm wäre das dagegen schon sehr problematisch. Dies weiß man auch aus Erfahrung.

R7 mit 10K ist ein Standardwert, man will ihn nicht zu niederohmig machen, um ihn störfest zu bekommen. Hochohmiger würde hier auch keinen Strom sparen, er wird ja nur für's programmieren benutzt.

C1 mit 100nF ist wieder ein Standardwert, den man in vielen Fällen als Stützkondensator vorsieht. Natürlich als Keramikkondensator, weil er niederohmig und induktionsarm sein soll. Er soll ja schnelle Spannungssprünge stützen.

Für T1 brauchen wir noch eine Leistungsbetrachtung. Die maximale Verlustleistung tritt bei maximaler Eingangsspannung auf. Dann fallen etwa 9V - 3.6V (LED D3) - 0.5V (R2) = 4-9V an T1 ab. Bei 25 mA Strom sind es dann P = U * I = 4.9V * 0.025A = 0.123 Watt. Damit reicht der 500mW Typ völlig aus. Wir haben auch noch Luft, um z.B. einen größeren LED-Strom fließen zu lassen.

Schaltungsaufbau

Der Schaltungsaufbau findet z.B. auf einer 40x40mm großen Lochrasterplatine Platz. Verdrahten kann man entweder mit Silberdraht oder mit Kupferlackdraht bzw. Fädeldraht. Auch dünner Klingeldraht ist geeignet. Bei Lackdraht sollte man darauf achten, immer erst den Lack ordentlich wegzuschmelzen, bevor man ihn anlötet. Dazu braucht es einen recht heißen Lötkolben, etwa 380-400 Grad. Und auch ausreichend Lötzinn braucht es, damit die gute Mischung aus Flußmittel und Lötzinn eine ordentliche Wärmeübertragung auf den Draht gewährleistet.

Als Programmier-Connector nehme ich für die meisten Projekte eine 6polige einfache Pfostenleiste. Weil der ISP-Programmieradapter einen 10poligen zweireihigen Anschluß benutzt, muss man sich einmal dafür ein Adapterkabel bauen, was die Signale entsprechend umsetzt.

Vom Layout sollte es so sein, dass C1 sehr dicht und mit dicken Leitungen am IC Pin 8 und Pin 4 sitzt. R6 und R1 sollten direkt am Transistor sein und die Basisleitungen sollten recht kurz sein. Man möchte sich ja keine Störungen einfangen.

Ich gehe davon aus, dass man schonmal ein paar handgefädelte Platinen aufgebaut hat, weshalb ich hier auf weitere Details verzichte.

Die Software

Um die Software auf den Prozessor zu bekommen, installiert man sich zuerst einmal das AVR-Studio, welches beim ISP-Programmieradapter dabeiliegen sollte. Ansonsten kann man es sich auch aus dem Internet kostenlos herunterladen.

Beim Start legt man ein neues Projekt an und kopiert dann als Hauptprogramm das unten stehende Programm hinein. Dann kann man unter Projekt > Build das Programm assemblieren lassen. Heraus kommt ein Hex-File, welches dann auf den Chip programmiert werden kann.

Für die Programmierung wird zuerst der ISP-Programmer über die serielle Schnittstelle angeschlossen. Dann muss der ISP an die Schaltung angeschlossen werden, mit oben beschriebenem Adapterkabel. Bitte kontrolliere sorgfältig, ob alle Kabel richtig gelötet sind, der Programmieradapter verzeiht einem nicht, wenn man z.B. die Versorgungsspannung falsch anschließt.

Wenn Programmieradapter und eigene Schaltung korrekt miteinander verbunden ist, kann man an die Schaltung 5 Volt anlegen - nicht mehr. Im Fehlerfalle würde sonst der ISP zerstört. Wenn der ISP-Adapter korrekt verbunden ist, sollte er nach kurzer Zeit der Initialisierung grün leuchten.

Jetzt kann man unter Tools > AVRISP > AVRISP den Programmierdialog öffnen, zuerst die Fuses einstellen (siehe Hinweise unten) und dann das entsprechende Hex-File unter Flash einstellen und dann "Program" betätigen.

Hinweise:

  • Die Fuses sollten so gesetzt sein, dass Brownout eingeschaltet ist und bei 2.7Volt liegt. Der interne Taktgeber sollte eingeschaltet sein (default).
  • Im AVR-Studio wird nicht automatisch die entsprechende Hex-Datei des Projektes übernommen. Wenn man also unter Tools > AVRISP > AVRISP den Programmierdialog öffnet, muss man hier die entsprechende Hexdatei unter Flash einstellen.
  • Bei Programmänderungen dran denken: PB4 niemals auf Ausgang High programmieren.
 
; 9V-Taschenlampe nach Idee vom 28.10.2005
; Interner RC-Oszi, Tiny 12
; Beginn: 28.10.2005, Stand: 30.10.2005
; Author: Winfried Mueller, www.reintechnisch.de  
; Changelog:
; 
; PORTB
; PB0: IN    P   NC
; PB1: IN    P   NC
; 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 sec_divider = r20      ;Timer-Divider Sekundentakt
.def cnt_s       = r21      ;Count Sekunden
.def cnt_m       = r22      ;Count Minuten

.def flags       = r23      ;General Flag Register


; Constants

.equ TM0_PRESET      = 218      ;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   = 1


.MACRO INIT_IO_ON
              ldi a, 0b00010111
              out PORTB, a
              ldi a, 0b00000000   
              out DDRB, a
.ENDMACRO

.MACRO INIT_IO_OFF
              ldi a, 0b00000111
              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
.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_SLEEP
              sbi ACSR, ACD      ;Analog Comp Off
              cbi ACSR, AINBG    ;URef off
              ldi a, 0b00100000  ;Idle
              out MCUCR, a
.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:         
              ;---
              out SREG, sSREG
              reti

RESET:
MAIN:         INIT_IO_ON
              RESET_TIME
              INIT_SLEEP
              INIT_TIMER
              INIT_INT_ON

              ; 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_l4

              rcall WAIT_2S         ;Warten damit Taste nicht sofort geprüft wird

m_l1:         sleep
              cpi cnt_m, LAMP_OFF_TIME_M
              breq m_l2
              sbic PINB, P_SWITCH
              rjmp m_l3
              rjmp m_l1

m_l3:         ;Taste gedrückt

              ;wirklich gedrückt und nicht nur Spike?
              rcall WAIT_10MS
              sbis PINB, P_SWITCH
              rjmp m_l1            ;doch nicht gedrückt -> Spike

m_l4:         ;warte, bis losgelassen
              sbic PINB, P_SWITCH
              rjmp m_l4

              ;warte sicherheitshalber wg. nachprellen
              rcall WAIT_10MS
              rcall WAIT_10MS
              rcall WAIT_10MS
              rcall WAIT_10MS
              rcall WAIT_10MS
              rcall WAIT_10MS
              rjmp m_l2

m_l2:         ;Zeit abgelaufen/Taste gedrückt
              INIT_INT_OFF
              INIT_IO_OFF           ;jetzt sollte Prozessor ausschalten
              rcall WAIT_2S
              rjmp RESET            ;Reset, falls Fehler


;Warte 2 Sekunden
WAIT_2S: 
              ldi a, 0xFF
W2S_0:        ldi b, 0xFF
W2S_1:        lpm
              lpm
              lpm
              lpm
              lpm
              lpm
              lpm
              lpm
              lpm
              wdr
              dec b
              brne W2S_1
              dec a
              brne W2S_0
              ret


;Warte 10ms
WAIT_10MS:
              ldi a, 0xFF
W10MS_0:      lpm
              lpm
              lpm
              lpm
              lpm
              lpm
              lpm
              lpm
              wdr
              dec a
              brne W10MS_0
              ret


Programmbeschreibung

Wenn man ein Programm betrachtet, ist es gut, den Reifegrad einer Software zu kennen. Dann weiß man, wie man Ungereimtheiten einstufen muss. Es ist ja immer die Frage: Verstehe ich das Programm nicht oder ist das Programm fehlerhaft?

Obiges Programm ist ein Schnell-Hack, der in etwa 5 Stunden entstanden ist. Prinzipiell funktionieren tut es. Es ist aber möglich, dass sich Fehler eingeschlichen haben, die bisher nicht aufgefallen sind. Außerdem sind manche Dinge vom Design nicht optimal gelöst, es wurde vielmehr der Kompromiss gewählt, in kurzer Zeit und unkompliziert zu Ergebnissen zu kommen.

Damit beinhaltet das Programm aber eine wesentliche Eigenschaft für ein Einsteigerprojekt: Selbst der Anfänger findet eine Menge von Verbesserungsmöglichkeiten und kann sich darin dann ausprobieren.

Wie funktioniert nun das Programm:

Als erstes holen wir uns mit .include erstmal die Definitionen für den verwendeten Chip rein. Es geht dabei vor allem darum, später symbolische Namen für Register, Bits usw. benutzen zu können.

Es folgen einige Register-Definitionen und Konstantenvereinbarungen. Manche davon lege ich in jedem Programm standardmäßig fest, obwohl sie gar nicht unbedingt benötigt werden. So auch hier.

Mit TM0_PRESET kalibrieren wir später die Uhr so, dass alle 10ms in die Timer-Interruptroutine eingesprungen wird. Mit LAMP_OFF_TIME_M können wir festlegen, nach wieviel Minuten die Lampe ausschalten soll. Wem Minutenabstände nicht reichen, kann das Programm erweitern und LAMP_OFF_TIME_S einbinden.

Es folgen ein paar Makros, die durchweg dazu benutzt wurden, um eine bessere Übersichtlichkeit hinzubekommen. In einem Makro habe ich logisch Dinge zusammengefasst und ihnen einen sinnvollen Namen gegeben. Die Befehle eines Makros werden später im Programmtext vom Assemlber eingesetzt, wenn man das Makro aufruft.

Viele dieser Makros drehen sich um Initialisierung bestimmter Sub-Systeme.

Wichtig ist die Festlegung der Interruptvektoren. Diese müssen auch immer die ersten Befehle sein, die im Programm auftauchen, weil sie sich im Speicher ganz vorn befinden. Wir springen hier für jeden Interrupt zu einem definierten Label. Weil wir fast keine Interrupts nutzen, steht am Sprungziel fast immer nur ein reti. Reti braucht es immer zum Verlassen einer Interrupt-Service-Routine (ISR).

Lediglich den Timer0 Interrupt nutzen wir. Für jede ISR ist es wichtig, zuerst einmal mindestens das SREG Register zu sichern. Sonst bringen wir das Hauptprogramm durcheinander, was nach einer Interruptroutine dann ein verändertes SREG erhalten würde. Der Prozessor kümmert sich nicht automatisch darum, wir müssen es tun.

Das gleiche gilt für den Counter-Preset - er soll ja nicht von 0 loslaufen sondern von einem voreingestellten Wert. Dies deshalb, damit der nächste Einsprung nach etwa 10ms passiert.

In der Timer ISR haben wir erstmal einen Divider, der alle 100 Einsprünge einen Pfad abarbeitet. Das wäre damit jede Sekunde. Hier wird dann cnt_s hochgezählt, was unser Counter für die vergangenen Sekunden seit Einschalten ist. Wenn cnt_s 60 erreicht hat, wird es auf 0 zurückgesetzt und cnt_m für unseren Minutencounter eins hochgezählt, so wie das bei einer echten Uhr auch geschieht. Die ISR stellt also sicher, dass wir vom Hauptprogramm über cnt_s und cnt_m die abgelaufene Zeit in Sekunden und Minuten abgreifen können. Mehr tut die Timer ISR nicht.

Das Hauptprogramm (Label RESET) initialisiert zuerst einmal die verschiedenen Subsysteme. Durch INIT_IO_ON wird gleichzeitig der Port so konfiguriert, dass der Selbsthalte-Transistor eingeschaltet wird.

Nach der Initialisierung, die innerhalb weniger uS erledigt ist, kontrollieren wir nochmal, ob die Taste wirklich gedrückt ist. Es hätte ja sein können, dass ein sogenannter Spike unsere Schaltung ausgelöst hat. Das passiert z.B., wenn neben unserer Lampe ein leistungsstarkes Relais einschaltet. Dies kann so hohe Störungen verursachen, dass die in unsere Schaltung eingekoppelt werden und den Prozessor einschalten.

Wenn jemand wirklich einen Schalter betätigt hat, dann wird dieser mit Sicherheit länger, als ein paar Mikrosekunden gedrückt. Also sollte der Schalter nun nach der Inititalisierung immer noch gedrückt sein. Mit

 sbis PINB, P_SWITCH

wird dies überprüft. Wenn der Schalter nicht betätigt ist, dann wird an eine Stelle gesprungen, die nach kurzer Zeit wieder ausschaltet. Das wir hier an eine Stelle springen, die noch etwas verzögert, bis wirklich ausgeschaltet wird, ist ganz sinnvoll. So vermeiden wir, dass durch Kontaktprellen des Tasters oder schnellen mehrfachen Störungen die Schaltung in kurzen Abständen mehrfach ein- und ausgeschaltet wird.

Wenn wir uns sicher sind, dass die Lampe leuchten soll, dann warten wir erstmal mit

 rcall WAIT_2S

für 2 Sekunden. Warum? Das Programm muss später testen, ob die Taste gedrückt wurde, weil damit die Lampe ja ausgeschaltet werden soll. Wenn das Programm jedoch sofort nach dem Einschalten testet, dann wird die Lampe innerhalb ein paar Mikrosekunden gleich wieder ausgeschaltet, weil wir die Taste vom Einschalten noch gar nicht losgelassen haben.

Die 2 Sekunden warten ist dabei ein Einfachheits-Kompromiss. Besser kann man es machen, in dem man einfach abwartet, bis der Taster sicher losgelassen wurde. Hierfür prüft man den Port-Pin, an dem der Taster angeklemt ist. Wenn dem so ist, wird das Programm fortgesetzt. Wer Lust hat, kann das später für sich so umprogrammieren.

Nach den 2 Sekunden Wartezeit beginnt eine Schleife, in der der Prozessor so lange ist, bis er abgeschaltet wird. Man kann es sozusagen als Hauptschleife bezeichnen. Sie beginnt mit einem Sleep. Wir wollen Strom sparen und der Prozessor soll erstmal nichts tun. Wir bleiben jedoch im Idle-Mode, damit der Timer weiterläuft. Der Chip braucht so nur etwa 200uA, im aktiven Zustand wären es 800uA, was im konkreten Fall auch nicht tragisch wäre.

Aufgeweckt wird der Prozessor durch den Timer0-ISR. Nach Ablauf dieser Interrupt-Routine macht unser Programm zwei Dinge: Erstens checkt es, ob die Zeit schon abgelaufen ist, um abzuschalten und zweitens, ob die Taste gedrückt wurde.

Die Taste müssen wir wieder ein zweites mal testen, damit wir wegen eines Spikes nicht abschalten. Und hier machen wir es dann auch ordentlich: Wir warten, bis der Taster losgelassen wurde. Das ist hier sinnvoller, weil wir ja erwarten, dass die LED sofort abschalten soll, sobald wir den Taster loslassen. So ganz ideal ist die Sache aber auch nicht gelöst, weil wir eigentlich ein Tastenprellen noch abfangen sollten. Wir sollten also z.B. nach 10ms nochmal testen, ob die Taste wirklich losgelassen ist. Vielleicht sogar noch ein drittes mal.

Hier kommt dann wieder die derzeitige Einfachlösung: Wenn der Taster auch nur einmal also losgelassen erkannt wurde, warten wir einfach noch 60ms und gehen davon aus, dass bis dahin nichts mehr prellt und der Taster wirklich losgelassen ist.

Mit

 INIT_IO_OFF

schalten wird dann die Betriebsspannung des Prozessors ab. Der Prozessor wird noch ein paar uS weiterlaufen und was soll er in dieser Zeit tun? Wir werden ihn einfach in eine 2Sekunden Zeitschleife schicken. Wenn ein Fehler auftaucht, und er nicht abschaltet, wird er den nächsten Befehl erreichen und das ist ein Sprung zu RESET. Es ist immer gut, ein wenig für den Fehlerfall zu überlegen, was der Prozessor dann tun soll. Vielleicht wäre es hier auch besser, ihn erneut zum Abschalten zu bewegen.

Noch ein Wort zu den Zeitschleifen WAIT_2S und WAIT_10MS. Je länger eine Zeitschleife dauern soll, um so mehr Befehle müssen dort abgearbeitet werden. Dies erreicht man durch eine hohe Anzahl von Schleifendurchläufen oder durch zeitintensive Befehle. Hohe Schleifendurchläufe brauchen mehrere Register. Für die 2 Sekundenschleife wollte ich aber nicht mehr als 2 Universalregister spendieren. Deshalb beinhaltet sie eine Anzahl von zeitintensiven Befehlen. Der Befehl lpm bietet sich an, weil er 3 Taktzyklen benötigt und damit recht viel Zeit braucht. Er hat also nur den Sinn, die Schleife zu verzögern und richtet in unserem Fall auch keinen Unfug an.

Sind die Schleifen genau berechnet? Nein. Wer Lust hat, kann die genauer justieren. Und natürlich kann man die Schleifen auch universeller machen, in dem man sie variabel vom Durchlauf hält. Man könnte z.B. über das Register A einen Anfangswert übergeben. Mit A=1 würde sie dann z.B. 10ms brauchen und mit A=10 100ms. Dann bräuchten wir weiter oben auch nicht 5 mal hintereinander einen Einsprung in die Zeitschleife.

Wenn wir uns das Programm anschauen, so fällt eine Sache besonders auf, um die wir uns mehrfach kümmern müssen: Die Tastenentprellung bzw. die sichere Erkennung eines Tastendrucks. Um dies zu vereinfachen, gibt es zwei Wege: Wir könnten ein Unterprogramm schreiben, was sich damit beschäftigt. Und wir könnten die komplette Tastenerkennung in die Timer0-ISR legen. Aus diesem Grund ist die auch schon auf 10ms ausgelegt, was ein guter Wert ist, um dort auch Tasten zu "scannen". Man beobachtet dort sozusagen alle 10ms die Taste. Wenn sie innerhalb von z.B. 3 Einsprüngen auf Logisch 1 war, dann gilt sie als losgelassen, wenn sie nach 3 Einspüngen Logisch 0 war, gilt sie als gedrückt. Die Übergabe zum Hauptprogramm erfolgt dann wieder über ein Austauschregister, wie wir das schon mit cnt_s und cnt_m gemacht haben. Wir können dafür auch ein Flag-Register verwenden, wo wir ein oder mehrere Bits für den Tastenzustand reservieren.

Das wäre also eine interessante Erweiterung für dieses Programm. Und nicht nur für dieses: Man braucht sehr oft die Funktionalität, den Zustand von Tasten abzufragen, weshalb sich der Aufwand auch für zukünftige Projekte lohnt.

An Erweiterungen und Ergänzungen des Programms bin ich immer interessiert. Ich freue mich, wenn ihr mir da was zuschickt.

Das Endprodukt

Eine Nivea-Soft Plastikdose ist genau das Richtige für unseren ersten Prototypen. Damit sparen wir uns das Geld für ein spezielles Elektronik-Gehäuse und tun gleichzeitig was für eine ökologische Müllverwertung. Die Dose ist zudem noch recht formstabil und sieht ganz nett aus.

Die Batterie und die Schaltung wurde innen auf eine runde 4mm Sperrholzplatte aufgebracht, die dem Innendurchmesser in etwa entspricht. Die Batterie kann mit einem Kabelbinder gehalten werden. Die Holzplatte kann man dann mit zwei Schrauben an dem Gehäuseboden befestigen.

Als Nachttischlampe sollte man eine LED auswählen, die einen breiten Öffnungswinkel hat. Hochgezüchtete 5mm weiße LED's haben normal eine schmalen Winkel von 10-20 Grad. Diese Bündelung sorgt zwar für viel Licht, aber nur in einem schmal ausgeleuchteten Bereich. Für unsere Nachttischlampe sind Leuchtdioden mit 70..160 Grad Öffnungswinkel besser geeignet. Da wären z.B. die Superflux LED's gut geeignet. Sie bringen 6000mcd bei einem Öffnungswinkel von 100 Grad.

Natürlich eignet sich die Schaltung auch, um sie als Taschenlampe zu verwenden. Dann sucht man sich ein kleines Gehäuse, in dem man eine 9V-Batterie unterbringen kann. Ein Aufbau der Schaltung in SMD, um Platz zu sparen, empfiehlt sich dann auch.

Ideen

Natürlich gibt es jede Menge Möglichkeiten, diese Idee weiter auszubauen. Hier schonmal einige Anregungen:

  • Wenn man die Schaltung für eine Taschenlampe verwendet, sollte man ein Doppelklick-Mechanismus einbauen, um versehentliches Einschalten zu vermeiden. Die Lampe schaltet sich dabei nur dann ein, wenn man einen Doppelklick auf die Taste gemacht hat.
  • Die Leuchtdauer könnte man variabel machen und durch mehrere Tastendrücke "programmieren".
  • Mit mehreren Leuchtdioden oder farbigen LED's könnte man experimentieren. Bei farbigen LED's muss man evtl. 2 Stück in Reihe schalten, damit die Prozessorspannung passt.
  • Als Nachttischlampe wäre es gut, wenn man auch im Dunkeln den Schalter findet. Experimente mit ultrahellen Leuchtdioden haben mir gezeigt, dass selbst bei Strömen von wenigen Mikroamper eine ausreichende Helligkeit zur Orientierung erreicht wird. Man könnte also eine extrahelle rote Leuchtdiode zusätzlich einbauen und mit z.B. 2-5 uA permanent betreiben.
  • Über die Ports PB0..PB2 könnte man zusätzliche Sachen ansteuern, vielleicht auch weitere LED's.
  • Wie verhält die Schaltung sich bei versehentlicher Falschpolung? Evtl. eine Schottky-Diode in Reihe zur Betriebsspannung.
  • Batterieunterspannungserkennung: Der Tiny12 hat einen Comparator, der das testen könnte. Hier aber darauf achten, dass eine zusätzliche Beschaltung im ausgeschalteten Zustand keinen Strom zieht.
  • Anderen Prozessor verwenden, z.B. ATtiny 13 oder 15, der zusätzliche Funktionalitäten hat.

Wer eigene interessante Erweiterungen realisiert oder sonstige Verbesserungen zu diesem Einstiegsprojekt hat, informiere mich bitte. Mailadresse findest du auf der Kontakt-Seite.

Weblinks

Fortsetzung

Zu diesem Artikel gibt es eine Fortsetzung: AVRProjekt-9V-LED-Lampe-II