Hallo zusammen,
hier kommt meine Variante des Reaktivlichtes – basierend auf dem im Nachbartread Reaktives Licht mit Atmel AVR entwickelten Schaltung und den dort gemachten Überlegungen, wie das Ganze möglichst stromsparend zu realisieren ist.
Reaktivlicht in C – Teil 1: Schaltung und Programm
Die verwendete Schaltung entspricht der aus Abbildung 5 des Kochbuches, nutzt also einen digitalen Ausgang des Attiny als ein- und ausschaltbare Spannungsquelle für den Spannungsteiler LDR/R2.
Der Grund hierfür ist der relativ niederohmige Spannungsteiler – den Widerstandswert für R2 hatte ich so optimiert wie im Ursprungstread angedeutet: maximale Spannungsdifferenz zwischen Hell und Dunkel.
Zwei LEDs hatte ich jetzt nur genommen, weil es einfach "schöner" ist – später könnte es dazu dienen (und ist momentan auch im Programm so realisiert), dass die eine LED den Nordwert morst und die andere gleichzeitig den Ostwert – ja ich bin fies Ach so, die unterschiedlichen Vorwiderstände der LEDs sollen nur die unterschiedlichen Helligkeiten der verwendeten roten und blauen LED ausgleichen.
Das Ganze sieht dann auf dem Board folgendermaßen aus (die Tiefenschärfe ist jetzt nicht so prickelnd, aber ich hoffe, dass man es dennoch erkennt):
Ach übrigens: wer findet die Abweichung zum obigen Schaltplan?
Das Programm habe ich in C geschrieben, da ich mit dieser Programmiersprache einfach vertauter bin. Dani_B hatte ja vor ca. 1 Jahr schon das Originalprogramm - noch mit der LED als Lichtsensor – in C "übersetzt; jetzt kommt also die Variante für den LDR. Das Programm setzt auf den Watchdog timer (WDT) interrupt, was dazu führt, dass das "Hauptprogramm" eigentlich keines mehr ist. Die Hauptarbeit liegt jetzt in der interrupt service routine (ISR) des WDT.
Leider ist der Code etwas länger geworden, was aber auch an einer etwas umfangreichen Kommentierung liegen kann. Der Code gefällt mir insofern noch nicht, dass er mit Konstanten arbeitet, die in die Register geschrieben werden (insbesondere in initChip(); ). Das macht zwar einerseits den HEX-Code kleiner, andererseits aber das Ganze auch unflexibel und schwer zu warten. Nun aber der Code:
Die Aufgabe des Hauptprogramms liegt in der Initialisierung des Chips, der Zustandsvariablen und der Initialisierung des WDT auf den Tagmodus. Ab dann beschränkt es sich darauf, in einer Endlosschleife den Prozessor immer wieder schlafen zu schicken. Die Hauptarbeit hat die ISR des WDT. Diese wird am Tag vom WDT alle 8s und in der Nacht alle 120ms aufgerufen (siehe Konstanten). Ok, das ist auch nur die halbe Wahrheit: nach den 8s Schlaf im Tagmodus, wird erstmal die Spannungsversorgung des LDR eingeschaltet und anschließend der Prozessor für die "Nachtzeit" wieder schlafen geschickt. Erst dann erfolgt die Abfrage des analogen Eingangs. Was jetzt abläuft ist ähnlich dem BASIC Programm von windi: Abfrage of es dunkel ist => ab in den Nachtmodus.
Im Nachtmodus wird bei jedem Aufruf der ISR getestet, ob der Helligkeitspegel seid dem letzten Aufruf angestiegen ist -> blinken und ob die Helligkeit wieder über die Tagschwelle gestiegen ist -> Tagmodus.
Zum Blinken wird von der ISR eine separate Funktion (doBlink() aufgerufen, in der alles weiter implementiert wird. Ich habe bei dem Programm allgemein auf Funktionen gesetzt, soweit ich es als sinnvoll erachtet habe -> der Code wird übersichtlicher. Die damit notwendigen Funktionsaufrufe kosten allerdings Zeit und auch Programmspeicher -> eventuell die Funktionen, die nur von einer Stelle aus aufgerufen werden, als INLINE deklarieren.
Den Programmcode habe ich mit WinAVR compiliert und mit PonyProg auf den Attiny gebrannt. Letzteres kam bei mir zum Einsatz, da es auch meinen exotischen seriellen Programmer kennt. Allerdings sollte der mit WinAVR mitgelieferte AvrDude des auch können, aber eine grafische Benutzerschnittstelle hat schon was
So damit bin ich am Ende des 1. Teils und
to be continued ....
Edit: Code entsprechend Hinweis von NC666 korrigiert.
hier kommt meine Variante des Reaktivlichtes – basierend auf dem im Nachbartread Reaktives Licht mit Atmel AVR entwickelten Schaltung und den dort gemachten Überlegungen, wie das Ganze möglichst stromsparend zu realisieren ist.
Reaktivlicht in C – Teil 1: Schaltung und Programm
Die verwendete Schaltung entspricht der aus Abbildung 5 des Kochbuches, nutzt also einen digitalen Ausgang des Attiny als ein- und ausschaltbare Spannungsquelle für den Spannungsteiler LDR/R2.
Der Grund hierfür ist der relativ niederohmige Spannungsteiler – den Widerstandswert für R2 hatte ich so optimiert wie im Ursprungstread angedeutet: maximale Spannungsdifferenz zwischen Hell und Dunkel.
Zwei LEDs hatte ich jetzt nur genommen, weil es einfach "schöner" ist – später könnte es dazu dienen (und ist momentan auch im Programm so realisiert), dass die eine LED den Nordwert morst und die andere gleichzeitig den Ostwert – ja ich bin fies Ach so, die unterschiedlichen Vorwiderstände der LEDs sollen nur die unterschiedlichen Helligkeiten der verwendeten roten und blauen LED ausgleichen.
Das Ganze sieht dann auf dem Board folgendermaßen aus (die Tiefenschärfe ist jetzt nicht so prickelnd, aber ich hoffe, dass man es dennoch erkennt):
Ach übrigens: wer findet die Abweichung zum obigen Schaltplan?
Das Programm habe ich in C geschrieben, da ich mit dieser Programmiersprache einfach vertauter bin. Dani_B hatte ja vor ca. 1 Jahr schon das Originalprogramm - noch mit der LED als Lichtsensor – in C "übersetzt; jetzt kommt also die Variante für den LDR. Das Programm setzt auf den Watchdog timer (WDT) interrupt, was dazu führt, dass das "Hauptprogramm" eigentlich keines mehr ist. Die Hauptarbeit liegt jetzt in der interrupt service routine (ISR) des WDT.
Leider ist der Code etwas länger geworden, was aber auch an einer etwas umfangreichen Kommentierung liegen kann. Der Code gefällt mir insofern noch nicht, dass er mit Konstanten arbeitet, die in die Register geschrieben werden (insbesondere in initChip(); ). Das macht zwar einerseits den HEX-Code kleiner, andererseits aber das Ganze auch unflexibel und schwer zu warten. Nun aber der Code:
Code:
/*
* -------------------------------------------------------------------------
* Reaktivlicht für Nachtcaches mit Atmel ATTINY 13V
* Version 0.4 für Schaltung RL_V0.3
* -------------------------------------------------------------------------
*
* entstanden im Elektronik-Unterforum auf http://www.geoclub.de
* (http://www.geoclub.de/ftopic5753.html)
*
* Umsetzung des Reaktivlichtes mit LDR wie von windi entworfen
* in C, wobei die Stromaufnahme so weit als möglich minimiert wurde.
*
* Thomas Stief <[email protected]>
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>
#include <stdint.h>
/* =================================================================
Deklarationen
================================================================= */
typedef uint8_t BYTE;
typedef int8_t SBYTE;
typedef uint16_t WORD;
typedef int16_t SWORD;
typedef int8_t BOOL;
#define TRUE (0==0)
#define FALSE (0!=0)
void doBlink(void);
/* =================================================================
Globale Variable des Zustands
================================================================= */
WORD wLetzteHelligkeit; // Zwischenspeicher des letzten Helligkeitswerts
BOOL fNachtMode; // == TRUE wenn der Nachtmodus aktiv ist
BOOL fBlinkMode; // == TRUE wenn die LED blinken soll
BOOL fPrepareTag; // == TRUE wenn im Tagmodus die Versorgung des LDR
// aktiviert ist und die Abfrage erfolgen kann
WORD wTagCounter; // Zähler wie oft "Tag" gemessen wurde
WORD wBlinkCounter; // Zähler für den Blinkgenerator
/* =================================================================
Initialisierung des Chips
In: nix
Out: nix
================================================================= */
void initChip(void)
{
// ADC
// ------------------------------------------------------
// Aktivieren ADC2 (Pin 3) // xxxxxx10
// Vcc als Referenzspannung // x0xxxxxx
// Ergebnisse nach Rechts ausgerichtet // xx0xxxxx
ADMUX = 0x02; // 00000010
// Digital IO
// ------------------------------------------------------
// Erstmal alles auf Eingang
// außer für LED1, LED2, die ADC_VERSORGUNG // xx001011
DDRB = 0x0b; // 00001011
// Pullup-Widerstände für alles, außer für die
// digitalen Ausgängen und dem analogen Eingang ADC2
// (PB4) aktivieren // xx100100
// => Soll Strom sparen
PORTB = 0x24; // 00100100
// Die nicht gebrauchten Digitalen Eingänge deaktivien
// => spart Strom
// es werden keine Digitalen Eingänge gebraucht // xx111111
DIDR0 = 0x3F; // 00111111
}
/* =================================================================
Einlesen des analogen Eingangs
die Funktion startet die Erfassung des Analogsignals und
gibt den aktuellen Wert zurück
In: nix
Out: (WORD) Aktuelles Analogsignal
================================================================= */
WORD readADC(void)
{
WORD wValue;
// Vorbereitung des Schlafenlegens des Prozessors
// --> Prozessor in den ADC Noise reduction mode schicken:
// tiefster Schlafmodus, der ein Erwachen durch ADC gestattet
set_sleep_mode(SLEEP_MODE_ADC);
sleep_enable();
sei();
// ADC einschalten, // 1xxxxxxx
// ADC starten // x1xxxxxx
// kein AUTO Trigger // xx0xxxxx
// ADC Interrupt aktivieren // xxxx1xxx
// Teiler des ADC-Taktes (1:2) // xxxxx000
ADCSRA = 0xc8; // 11001000
// Fehlerkorrektur (danke NC666):
// der ursprüngliche Wert 0xd8 ist falsch, da er auch ADIF setzt
//ADCSRA = 0x88; // 10001000
// Vorschlag NC666: es geht wohl auch ohne gesetztes Bit ADSC
// dann startet die ADC im Ruhemodus automatisch - habe ich noch nicht getestet
// Prozessor schlafen schicken
sleep_cpu();
// Guten Morgen, Prozessor! Gut geschlafen ...
// ... und damit du nicht wieder einschläft:
sleep_disable();
cli();
// Einlesen des Wertes (die unteren zwei Bit von ADCH
// (um 8 bit nach links geschoben) + ADCL
wValue = ((WORD)ADCL + (((WORD)(ADCH))<<8)) & 0x03ff;
// ADC ausschalten
ADCSRA = 0x00; // 00000000
return wValue;
}
/* =================================================================
Interrupt service routine für die Beendigung der AD-Wandlung
sie macht eigentlich gar nichts
================================================================= */
ISR(SIG_ADC)
{
}
/* =================================================================
Werte auf den digitalen Ausgängen ausgeben
mögliche Zustände:
ON (= 1) LED wird eingeschaltet (auf Vcc gezogen)
OFF (= 0) LED wird ausgeschaltet (auf Masse gelegt)
TOGGLE (= 2) Zustand der LED wird gewechselt
In: (SBYTE) sbLedID
(SBYTE) sbLedChange
Out: nix
================================================================= */
#define LED1 PORTB0
#define LED2 PORTB1
#define ADC_VERSORGUNG PORTB3
#define ON 1
#define OFF 0
#define TOGGLE 2
void controlDO(SBYTE sbLedID, SBYTE sbLedChange)
{
switch (sbLedChange)
{
case ON: // Einschalten
PORTB |= _BV(sbLedID);
break;
case OFF: // Ausschalten
PORTB &= ~_BV(sbLedID);
break;
case TOGGLE:// Zustand tauschen
PORTB ^= _BV(sbLedID);
break;
default:
break;
}
}
/* =================================================================
Initialisierung des Watchdogtimers und setzen des
Auswachintervalls
er wird auf den Interruptmode gesetzt
In: (BYTE) bTimeConst
Out: nix
================================================================= */
void setWD(BYTE bTimeConst)
{
// Interrupt ausschalten
cli();
// Zeit setzen
wdt_enable(bTimeConst);
// Watchdog-RESET deaktivieren (wir wollen nur den Interrupt)
// erst das entsprechende Bit im MCUSR löschen, sonst hat die Änderung
// im WDTCR kein Effekt
MCUSR &= ~_BV(WDRF);
// Watchdog Timer Interrupt einschalten WDIE -> 1
// um den Reset auszuschalten, muss das "Sicherungsbit"
// WDCE muss gesetzt sein WDCE -> 1
// Reset ausschalten WDE -> 0
WDTCR = (WDTCR & ~_BV(WDE)) |_BV(WDCE) |_BV(WDTIE);
// Interrupt einschalten
sei();
}
/* =================================================================
Interrupt service routine zur Bearbeitung des Watchdog interrupts
=> eigentlich das Hauptprogramm
================================================================= */
#define SCHWELLE_NACHT (WORD)500
#define SCHWELLE_TAG (WORD)530
#define SCHWELLE_TAG_COUNTER (WORD)64
#define SCHWELLE_BLINKMODE (SWORD)1
#define WARTEZEIT_NACHT WDTO_120MS
#define WARTEZEIT_TAG WDTO_8S
ISR(SIG_WATCHDOG_TIMEOUT)
{
WORD wHelligkeit;
SWORD swDeltaHelligkeit;
if(!fNachtMode && !fPrepareTag) // Wenn Tag ist und die Versorgung des
{ // LDRs deaktiviert ist anschalten und
controlDO(ADC_VERSORGUNG,ON); // etwas warten; erst dann abfragen
fPrepareTag = TRUE;
setWD(WARTEZEIT_NACHT);
}
else
{
wHelligkeit = readADC();
swDeltaHelligkeit = wHelligkeit - wLetzteHelligkeit;
wLetzteHelligkeit = wHelligkeit;
if(!fNachtMode) // Alles was am Tage zu machen ist
{ // --> Testen ob's inzwischen dunkel ist und der Nachtmodus
//aktiviert werden kann
if(wHelligkeit < SCHWELLE_NACHT) // Wenn es dunkel ist
{ // => ab in den Nachtmodus
fNachtMode = TRUE;
setWD(WARTEZEIT_NACHT);
}
else // Wenn es immer noch hell ist
{ // => in 8s nochmal testen
controlDO(ADC_VERSORGUNG,OFF);
fPrepareTag = FALSE;
setWD(WARTEZEIT_TAG);
}
}
else // Alles was in der Nacht zu erledigen ist
{
if(swDeltaHelligkeit > SCHWELLE_BLINKMODE
&& !fBlinkMode) // Wenn die Helligkeit in letzten Zyklus
{ // angestiegen ist => etwas rumblinken
wBlinkCounter = 0; // Blinkgenerator initialisieren
fBlinkMode = TRUE; // und Blink-Flag setzen
}
if(fBlinkMode) // Wenn Blinkmodus aktiv
{ // => rumblinken und EXIT
doBlink();
setWD(WARTEZEIT_NACHT);
}
else // Wenn Blinkmodus nicht aktiv
{ // => testen ob es inzwischen hell geworden ist
if(wHelligkeit > SCHWELLE_TAG)
{ // Helligkeit übersteigt den Tagwert => ab in den Tagmodus
wTagCounter++; // aber nicht sofort
if(wTagCounter > SCHWELLE_TAG_COUNTER)
{ // sondern erst wenn es mehr als
fNachtMode = FALSE; // SCHWELLE_TAG_COUNTER x so war
wTagCounter = 0;
controlDO(ADC_VERSORGUNG,OFF);
fPrepareTag = FALSE;
setWD(WARTEZEIT_TAG);
}
else
{
setWD(WARTEZEIT_NACHT);
}
}
else // Wenn es immer noch dunkel ist
{ // => den Zähler wieder auf Null setzen
wTagCounter=0;
setWD(WARTEZEIT_NACHT);
}
}
}
}
}
/* =================================================================
Blink-Routine - wird von der ISR des Watchdog aufgerufen
In: nix
Out: nix
================================================================= */
// Morsecode für "N 50 12 345"
const BYTE bSequenz1[] PROGMEM = { 0xe8, 0x0a, 0xa8, 0xee,
0xee, 0xe0, 0x2e, 0xee,
0xe2, 0xbb, 0xb8, 0x0a,
0xbb, 0x8a, 0xae, 0x2a,
0xa0, 0x00, 0x00, 0x00 };
// Morsecode für "E 008 54 321"
const BYTE bSequenz2[] PROGMEM = { 0x80, 0xee, 0xee, 0xe3,
0xbb, 0xbb, 0x8e, 0xee,
0xa0, 0x2a, 0xa2, 0xab,
0x80, 0xab, 0xb8, 0xae,
0xee, 0x2e, 0xee, 0xe0 };
void doBlink(void)
{
BYTE bByte,bBit;
if(wBlinkCounter < 128)
{
// Byte aus dem Flashspeicher lesen (alles ab Bit 3 des Blinkzählers
// bestimmt die Position des Bytes
bByte = pgm_read_byte(&bSequenz1[(BYTE)(wBlinkCounter>>3)]);
// Bit 0:2 bestimmen die Bit im Datenbyte, aber in umgedrehter
// Reihenfolge
bBit = 7 - (BYTE)(wBlinkCounter&7);
// Testen ob das Bit gesetzt ist dann ...
if((bByte&(1<<bBit)) != 0)
controlDO(LED1,ON); // ... LED an
else // sonst ...
controlDO(LED1,OFF);// ... LED aus
// Das Ganze noch für die andere LED
bByte = pgm_read_byte(&bSequenz2[(BYTE)(wBlinkCounter>>3)]);
if((bByte&(1<<bBit)) != 0)
controlDO(LED2,ON);
else
controlDO(LED2,OFF);
}
else // Wenn die Sequenz zuende ist: LEDs ausschalten
{
controlDO(LED1,OFF);
controlDO(LED2,OFF);
}
if(wBlinkCounter++ > 130) // Noch zwei Zyklen warten, dann ist die
fBlinkMode = FALSE; // Sequenz zu Ende und das Flag wird gelöscht
}
/* =================================================================
Hauptprogramm
=> eigentlich macht es kaum etwas, außer den Prozessor zu
initialisieren, den Watchdog zu starten und in eine
Endlosschleife einzutreten
================================================================= */
int main (void)
{
// Chip initialisieren
initChip();
// Zustand initialisieren
fNachtMode = FALSE;
wLetzteHelligkeit = 0;
fBlinkMode = FALSE;
wTagCounter = 0;
fPrepareTag = FALSE;
controlDO(ADC_VERSORGUNG,OFF);
wBlinkCounter = 0;
// Watchdog aktivieren und zwar für den Tagmodus
setWD(WARTEZEIT_TAG);
// Endlosschleife, die den Prozessor immer wieder schlafen schickt
while(1)
{
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_cpu();
}
return (0);
}
Im Nachtmodus wird bei jedem Aufruf der ISR getestet, ob der Helligkeitspegel seid dem letzten Aufruf angestiegen ist -> blinken und ob die Helligkeit wieder über die Tagschwelle gestiegen ist -> Tagmodus.
Zum Blinken wird von der ISR eine separate Funktion (doBlink() aufgerufen, in der alles weiter implementiert wird. Ich habe bei dem Programm allgemein auf Funktionen gesetzt, soweit ich es als sinnvoll erachtet habe -> der Code wird übersichtlicher. Die damit notwendigen Funktionsaufrufe kosten allerdings Zeit und auch Programmspeicher -> eventuell die Funktionen, die nur von einer Stelle aus aufgerufen werden, als INLINE deklarieren.
Den Programmcode habe ich mit WinAVR compiliert und mit PonyProg auf den Attiny gebrannt. Letzteres kam bei mir zum Einsatz, da es auch meinen exotischen seriellen Programmer kennt. Allerdings sollte der mit WinAVR mitgelieferte AvrDude des auch können, aber eine grafische Benutzerschnittstelle hat schon was
So damit bin ich am Ende des 1. Teils und
to be continued ....
Edit: Code entsprechend Hinweis von NC666 korrigiert.