LED-Stroboskop



zurück

Das hier beschriebene Stroboskop ist als Drehzahlmeßgerät konzipiert und verfügt über folgende Eigenschaften:

  • Frequenzbereich 5 .. 500 Hz bzw. 300 .. 30000 1/min
  • Anzeige über LCD-Display; Maßeinheit umschaltbar 1/s oder 1/min
  • zunehmende Änderungsgeschwindigkeit bei längerem Drücken der "schneller" oder "langsamer"-Taste
  • Batteriebetrieb (9V)

Die Schaltung kommt mit wenig Bauteilen aus; der Controller (AT90S2313) bedient das LCD-Display im 4bit-Modus und fragt die drei Tastenfunktionen direkt ab. Ein Ausgang liefert die Impulse für die LEDs. Diese werden mit 50%-Tastverhältnis generiert und durch ein (Hardware-)Monoflop auf ca. 20µs verkürzt.

Der Grund für diese Lösung ist folgender: Da die LEDs mit deutlich über dem zulässigen Dauerstrom liegenden Stromstärken betrieben werden, um ausreichend Helligkeit zu erzielen, wäre ein Verbleiben im eingeschalteten Zustand - z. B. durch Softwarefehler in der Testphase - deren sicheres Ende. Das Verfahren mit der softwareunabhängigen Impulslänge ist bei EPROM-Brennern bereits lange üblich.

Klicken, um Bild zu vergrößern Schaltplan der Hauptplatine

(auf das Bild klicken, um den Schaltplan zu vergrößern)

Der Controller arbeitet mit 4 MHz, bei entsprechender Softwareanpassung sind andere Frequenzen möglich. Da die Taktzeit intern aus der gewählten Frequenz und einer vom Systemtakt abhängigen Konstante berechnet wird, entstehen ohnehin meist "krumme" Werte für die Zeit, so daß eine glatte Quarzfrequenz hierfür keinerlei Vorteile bringt. Die Bildung der Konstanten ist im Quelltext kommentiert und kann leicht angepaßt werden. Grenzen entstehen nur durch die zulässige Frequenz des Controllers und aus den internen Berechnungen. Hierbei wurden Vereinfachungen hinsichtlich der Datenbreite getroffen, deren Einhaltung zu prüfen ist.

Die Hauptplatine wird von einer 9V-Batterie versorgt. Für den Controller und das LCD-Display sowie die Impulsverkürzung werden 5V bereitgestellt, lediglich die LEDs werden über Vorwiderstände direkt an 9V betrieben.

Man könnte eine Ausgang des Controllers sparen, indem man die R/W-Leitung des Display fest mit Gnd verbindet - da aber in diesem Fall genug Pins frei waren, habe ich mir die Möglichkeit offen gehalten, den Display-RAM auch wieder auszulesen.

Klicken, um Bild zu vergrößern Schaltplan des Impulsformers

(auf das Bild klicken, um den Schaltplan zu vergrößern)

Da das Handgerät mit den LEDs über ein längeres Kabel mit der Hauptplatine verbunden ist, habe ich die Impulsverkürzung unmittelbar bei den LEDs untergbracht, um nicht die 20µs-Impulse nicht über eine längere Leitung zu schicken. Zur Verbindung sind somit 4 Adern erforderlich - +9V; +5V, Gnd und die Taktleitung. Als Monoflop fungiert ein Timer 555. Um die Batterie zu schonen, wird die Basisleitung des Schalttransistors nur bei Bedarf über einen Taster mit dem Ausgang des Timers verbunden.

Das Foto zeigt den praktischen Aufbau des Gerätes in einem robusten Gehäuse. Das Display ist durch eine zusätzliche Plexiglasscheibe geschützt.

Der rote Taster rechts ist durch die Anordnung neben der Kabelverschraubung gegen versehentliche Betätigung geschützt. Er dient der Bereichsumschaltung. Anstelle zweier getrennter Taster für "schneller" und "langsamer" wurde ein Wippschalter eingesetzt. Dieser ist oben rechts neben der Platine zu erkennen; rechts daneben befindet sich der Ein/Aus-Schalter.

Die Software

Vor dem eigentlichen Programm laufen einige Prozesse zur Initialisierung ab. Die Hauptschleife des Programms tut nichts außer die drei Tasten zyklisch abfragen. Nur wenn eine Taste gedrückt ist, wird eine Auswertung vorgenommen und entweder die Anzeigeeinheit geändert und entsprechend umgerechnet oder die Frequenz geändert. Anschließend wird die aktuelle Frequenz für die Anzeige konvertiert und ans LCD geschickt, die Timereinstellung neu berechnet und hinterlegt, um abschließend wieder zur Tastenabfrage zurückzukehren. Der Programmablauf sieht somit folgendermaßen aus:

Aus der Initialisierungsphase sind vor allem die Einstellungen des Timers von Interesse. Er wird im compare-match-Mode betrieben, das heißt in sein Output Compare Register wird eine Zeitkonstante eingetragen, nach deren Erreichen jedesmal ein Interrupt ausgelöst wird. Gleichzeitig wird der Timer auf Null gesetzt und der Zählvorgang beginnt von vorn.

Timer 1 verfügt über ein 2teiliges Timer/Counter Control Register (TCCR1A, TCCR1B), das entsprechend einzustellen ist. Außerdem ist der entsprechende Interrupt zu aktivieren:

;---Timer-Init---------------------------------------------------------------
         ldi YH, 0b00000000                      ; Timer1 OC1+PWM disable
         out TCCR1A, YH
         ldi YH, 0b00001001                      ; Timer1 Prescaler=1 CTC=1
         out TCCR1B, YH
         ldi YH, 0b01000000                      ; Timer1 OCIE1A-Interrupt ein
         out TIMSK, YH

Die globale Interruptfreigabe erfolgt später, bis jetzt sind ja für die Takterzeugung noch keine gültigen Werte vorhanden. Noch ein Wort zur gewählten Betriebsart: Es wäre ebenso möglich, den Timer über den vollen 16bit-Umfang zählen zu lassen und nach einer entsprechenden Anzahl Interrupts den Rest der Zeit anderweitig abzuarbeiten. In Anbetracht des höheren Programmaufwandes und der eher geringen erforderlichen Genauigkeit bei der vorgesehen Verwendung des Gerätes habe ich mich aber für diesen Weg entschieden.

Intern wird immer mit der angezeigten Frequenz gerechnet. Da sich die Einheiten umschalten lassen, ist es also erforderlich, ab hier die gerade gewählte Frequenzeinheit zu berücksichtigen. Diese wird im Register bereich gespeichert. Die Bedeutung der Werte und der Wechsel zwischen den Bereichen sieht folgendermaßen aus:

;---umschalten 1/s <-> 1/min-------------------------------------------------
         com bereich                             ; bereich toggeln
                                                 ; bereich=$FF --> 1/sec
                                                 ; bereich=$00 --> 1/min

Zur Ermittlung der erforderlichen Dauer einer Halbperiode des Taktes wird die Frequenzeinheit als Verzweigungskriterium herangezogen. Sollte jemand Änderungen am Programm - insbesondere am Frequenzumfang - vornehmen, muß hier beachtet werden, daß die verarbeitete Datenbreite von Zeit und Frequenz eingeschränkt wurden. Die Maximalfrequenz von 50000 Umdrehungen pro Minute paßt in eine 16-bit-Variable und daraus resultierend reichen für das Ergebnis der Division in jedem Fall 24 bit aus:

;---Timer-Einstellung berechnen----------------------------------------------
         rcall pushdiv                           ; Registerinhalte vor Division retten
         tst bereich                             ; welche Frequenzeinheit?
         brne t_s                                ; Bereich =$FF --> 1/sec
         ldi DvdQu1, 0b00000000                  ; Divisor = 120000000 für 1/min
         ldi DvdQu2, 0b00001110                  ; = 60 * Prozessortakt / 2
         ldi DvdQu3, 0b00100111                  ;
         ldi DvdQu4, 0b00000111                  ; 00000111 00100111 00001110 00000000
         rjmp t_w
t_s:     ldi DvdQu1, 0b10000000                  ; Divisor = 2000000 für 1/sec
         ldi DvdQu2, 0b10000100                  ; = Prozessortakt / 2
         ldi DvdQu3, 0b00011110                  ;
         ldi DvdQu4, 0b00000000                  ; 00000000 00011110 10000100 10000000
t_w:     mov Dvsr1, freq_l                       ; Divisor = Frequenz (16bit-Wert)
         mov Dvsr2, freq_h
         ldi Dvsr3, 0
         ldi Dvsr4, 0
         rcall udiv32                            ; 32bit-Div. Erg. max. 24bit in r16..r19

Das Timer-Register ist nur 16 bit breit. Deswegen wird die Interruptroutine gf. mehrfach aufgerufen, bevor der Taktausgang umgeschaltet wird. Zur Ermittlung der erforderlichen Anzahl wird die ermittelte Zeit solange halbiert, bis sie maximal noch 16 bit umfaßt; gleichzeitig wird jeweils die Anzahl der Interruptaufrufe verdoppelt. Das ist mit Rechts- bzw. Linksschieben einfach zu lösen. Abschließend wird die ermittelte Zeit ins OCR1AH:L-Register geschrieben und die globale Interruptfreigabe gegeben:

;---T1-ISR-Einstellung--Zeit/2 und Schleifenzahl*2 bis Zeit nur noch 16bit--
         clr tloop                               ; Schleifenzähler = 0
         inc tloop                               ; Schleifenzähler = 1
div2:    tst DvdQu3                              ; DvdQu3 = 0 (3. Byte)?
         breq divrdy                             ; ja, Zeit max. 16bit breit
         clc
         rol tloop                               ; Schleifen für Timer verdoppeln
         clc
         ror DvdQu3                              ; Timerkonstante / 2
         ror DvdQu2
         ror DvdQu1
         rjmp div2
divrdy:  out OCR1AH, DvdQu2                      ; Taktzahl Timer-Voreinstellung
         out OCR1AL, DvdQu1
         mov loops, tloop                        ; Schleifenzahl-Merker
         rcall popdiv                            ; Register wiederherstellen
         sei                                     ; globale Int's ein

In der Darstellung des Programmablaufs weiter oben ist die Abfrage der Tasten und deren Auswertung stark vereinfacht - ganz am Anfang wurde erwähnt, daß bei längerem Gedrückthalten der "schneller"- bzw. "langsamer"-Taste die Änderungsgeschwindigkeit der Frequenz zunimmt. Wie das umgesetzt wurde, ist am Beispiel der "schneller"-Funktion Inhalt der folgenden Erläuterungen. Zuerst die Bedeutung zweier dabei verwendeter Variablen:

;---Auszug aus dem Definitions-Teil mit Erläuterungen------------------------
.def tast_f = r10                                ; Flagregister für zuletzt gedrückte Taste:
                                                 ; tast_f = 0b00000100 Taste 2 "schneller"
                                                 ; tast_f = 0b00001000 Taste 3 "langsamer"
                                                 ; tast_f = 0b00000000 keine
.def tast_c = r11                                ; zählt, wie oft die gleiche Taste
                                                 ; ununterbrochen gedrückt war; maximal 255

Es wird also mitgezählt, wie lange ununterbrochen hintereinander die gleiche Änderungsrichtung gedrückt war. Vor jeder neuen Abfrage der Tasten läuft eine Zeitschleife von ca. 250ms zur Entprellung. Sobald die Taste kurz losgelassen wurde, werden tast_f und tast_c zurückgesetzt, wie auch der folgende Ablaufplan zeigt:

Für das Drücken der Taste 3 ("langsamer") läuft die Auswertung analog - auch dort wird erkannt, wenn zuvor eine andere Taste aktiv war (praktisch wird kaum der Fall auftreten, daß direkt nach der "langsamer"-Taste die "schneller"-Taste erkannt wird, vielmehr wird zwischendurch wenigstens einmal keine Taste aktiv sein und das Rücksetzten bewirken).

Dem Programm ist jetzt also bekannt, zum wievielten Mal hintereinander die gleiche Taste aktiv war, nun wird abhängig davon die Schrittweite für die Frequenzerhöhung ermittelt. Dabei sind die am Anfang des Programms festgelegten Werte für die Frequenzgrenzen zu berücksichtigen:

;---Auszug aus dem Definitions-Teil mit Erläuterungen------------------------
.equ min_s = 5                                   ; untere Grenze für Umdr. pro Sekunde
.equ max_s = 500                                 ; obere Grenze für Umdr. pro Sekunde
.equ min_m = 300                                 ; untere Grenze für Umdr. pro Minute = 60 * min_s
.equ max_m = 30000                               ; obere Grenze für Umdr. pro Minute = 60 * max_s
                                                 ; WICHTIG: Verhältnis 1:60 einhalten, sonst
                                                 ; kann Grenze bei Bereichsumschaltung durch
                                                 ; Rundungsfehler überwunden werden!

Der Programmablauf setzt sich für die Auswertung der "schneller"-Taste in folgender Weise fort:

Damit ist die künftige Taktfrequenz ermittelt, um sie wirksam und im Display sichtbar zu machen, geht es an der Einsprungstelle nach dem Initialisierungsteil des Programms weiter mit ASCII-Konvertierung und Übertragung zum LCD, Berechnung der Taktzeit und Einstellung der Interruptroutine. Es folgt die 250ms-Zeitschleife zur Tastenentprellung und das Warten auf den nächsten Tastendruck. Hier der Quelltext dazu:

;---Taste 2 gedrückt---------------------------------------------------------
t2sub:   mov YH, tast_f                          ; Flagregister laden
         andi YH, 0b00000100                     ; Flag 2 schon gesetzt?
         brne f2on                               ; ja
         ldi YH, 0b00000100                      ; nein, Flag 2 ..
         mov tast_f, YH                          ; im Flagregister setzten
         clr tast_c                              ; Zähler auf Null
f2on:    inc tast_c                              ; für diesen Tastendruck incr.
         brne f2ok                               ; Überlauf? (tast_c = 0)
         com tast_c                              ; ja, tast_c = $FF wieder eintragen
f2ok:    ldi YH, 0
         ldi YL, 1                               ; kleinste Schrittweite laden
         tst bereich                             ; eingestellten Bereich ermitteln
         brne ta2s                               ; Umdr./sec

         mov XL, tast_c                          ; Zähler Taste2 nach XL
         cpi XL, 10                              ; carry, wenn 10 > XL
         brcs a2                                 ; ja, Schrittweite bleibt 1
         ldi YL, 10                              ; nein, Schrittweite = 10
         cpi XL, 20                              ; carry, wenn 20 > XL
         brcs a2                                 ; ja, Schrittweite bleibt 10
         ldi YL, 100                             ; nein, Schrittweite = 100
a2:      add freq_l, YL                          ; Schrittweite addieren
         adc freq_h, YH
         ldi XH, High(max_m)
         cp XH, freq_h                           ; High-Byte Freq. zu groß?
         brcs rr2                                ; ja
         brne ta2x                               ; nein; auch nicht gleich?
         ldi XH, Low(max_m)                      ; nein..
         cp XH, freq_l                           ; .. Low-Byte Freq. zu groß?
         brcs rr2                                ; ja
         rjmp ta2x                               ; nein, Freq. i.O.
rr2:     ldi freq_h, High(max_m)                 ; Freq. zu hoch, Maximum laden
         ldi freq_l, Low(max_m)
         rjmp ta2x

ta2s:    mov XL, tast_c                          ; Zähler Taste2 nach XL
         cpi XL, 10                              ; carry, wenn 10 > XL
         brcs a22                                ; ja, Schrittweite bleibt 1
         ldi YL, 5                               ; nein, Schrittweite = 5
         cpi XL, 20                              ; carry, wenn 20 > XL
         brcs a22                                ; ja, Schrittweite bleibt 10
         ldi YL, 10                              ; nein, Schrittweite = 10
a22:     add freq_l, YL                          ; Schrittweite addieren
         adc freq_h, YH
         ldi XH, High(max_s)
         cp XH, freq_h                           ; High-Byte Freq. zu groß?
         brcs rr22                               ; ja
         brne ta2x                               ; nein; auch nicht gleich?
         ldi XH, Low(max_s)                      ; ja..
         cp XH, freq_l                           ; .. Low-Byte Freq. zu groß?
         brcs rr22                               ; ja
         rjmp ta2x                               ; nein, Freq. i.O.
rr22:    ldi freq_h, High(max_s)                 ; Freq. zu hoch, Maximum laden
         ldi freq_l, Low(max_s)
ta2x:    ret

Einen Hinweis zur Festlegung der Schrittweitenabstufung möchte ich noch geben. Ich habe verschiedene Varianten probiert. Die größte Schrittweite sollte es in annehmbarer Zeit ermöglichen, die Maximal- bzw. Minimalwerte zu erreichen, andererseits aber auch nicht zu groß sein. Wichtiger erscheint mir aber, daß sich immer nur eine Stelle im Display ändert, sonst irritiert das Zahlengewirr. Man wählt am besten einstellige Schrittweiten oder glatte Zehner, Hunderter usw. - im Bereich "Umdr./min" könnte man noch Tausender-Schritte einfügen. Da höhere Drehzahlen als 3000 1/min aber selten benötigt werden, bin ich den Kompromiß eingegangen, in diesem Fall vorübergehend auf "1/s" umzuschalten bis die richtige Größenordnung erreicht ist.

Ich möchte abschließend auf zwei Webadressen verweisen, von denen ich Routinen verwendet habe. Unter www.avr-asm-tutorial.net stellt Gerhard Schmidt Routinen zur Zahlenumwandlung zur Verfügung und auf www.umnicom.de werden unter anderem Divisions- und Multiplikationsroutinen angeboten.

Das fertige Gerät

Es kommen vier weiße LEDs mit je ca. 9000mcd zum Einsatz. Das ist unter etwas schattigen Verhältnissen ausreichend, einige LEDs mehr wären aber von Vorteil.

Hier kann die Datei strobo.zip (113kB) mit den Schaltplänen im Eagle-Foramt und dem Assembler-Quelltext für den AT90S2313 heruntergeladen werden.