| 
  • If you are citizen of an European Union member nation, you may not use this service unless you are at least 16 years old.

  • You already know Dokkio is an AI-powered assistant to organize & manage your digital files & messages. Very soon, Dokkio will support Outlook as well as One Drive. Check it out today!

View
 

Reaktivlicht in C (Variante mit analog abgefragten Fotowiderstand) - Programm

Page history last edited by thomas_st 14 years, 5 months ago

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.

 

Programmcode:

 

/*

* -------------------------------------------------------------------------

* 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 <geocaching@mail.thomas-stief.de>

*/

 

#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);

}

 

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

Comments (0)

You don't have permission to comment on this page.