Watchdogtimer.c


1
/*
2
* Langzeittimer mit Watchdog-Interrupt
3
*
4
* Tiny 25/45/85
5
*
6
* Monoflop, wie Treppenlichautomat mit Langzeittimer, nachtriggerbar
7
* alternativ: Puls mit Verzögerung (Futterautomat), wenn bei Reset P2 auf Minimum steht
8
* Prescaler stellt Einheit ein: von 0 (16ms) bis 9 (8s)
9
* l_wcount liefert Anzahl der Einheiten
10
* damit maximale Zeit 2^32-1 * 8,192s = ca 1100 Jahre
11
* Stromverbrauch: ohne Ausgangstrom ca. 5..10µA@3V...4V aktiv (gegenüber 700µA mit Timer); nach Ablauf (Sleep) ca. 0.1µA
12
13
                                    VCC
14
               VCC  VCC              +
15
                +    +               |
16
                |    |               |  100n   
17
                |    |               o--||--.
18
               .-.  .-.              |      |
19
           10k | |  | | 10k          |     ===
20
               | |  | |              |     GND
21
               '-'  '-'    .---------o-----------.
22
                |    |     |                     |
23
                |    |     |                     |                     Zeit Pot 1  | Zeit Pot2
24
                |    o-(1)-o  Res                |   Puls am Ende    | Verzögerung  ______ 
25
                |    |     |                 PB1 o-(6)---           _|_____________|      |_____ Mode 'Futter'
26
                |   ---    |                     |                   | 
27
                |   ---100n|                     |                   +Start
28
                |    |     |                     |   oder Puls       |______________       
29
                |   ===    |                     |   sofort         _| Zeit Pot1    |____________ Mode 'Treppe'
30
                |   GND    |     Tiny25/45/85    |
31
                |          |                     |
32
          .-----)------(5)-o PB0                 |
33
          |     |          |                     |
34
         .-.    |          |                     |
35
      P1 | |<---)------(3)-o PB4 ADC2            |
36
     50k | |    |          |                     |
37
         '-'    |          |                     |
38
          |     |          |                     |  
39
         GND    o------(7)-o PB2 INT             |          
40
                |          |                     |                 
41
                |          |                     |
42
                |    Poti2 o PB3 ADC3 (2)        |
43
              | o    wie P1|                     |
44
       Taste  |=|>         '----------o----------'
45
              | o                     |
46
                |                    ===
47
               ===                   GND
48
               GND
49
50
*/
51
52
#define F_CPU 1e6
53
54
#include <avr/io.h>
55
#include <avr/interrupt.h> 
56
#include <avr/sleep.h>
57
#include <util/delay.h>
58
#include <avr/wdt.h>
59
#include <util/atomic.h>  // wegen Makros für 16/32-Bit Zugriffe 
60
61
#define WD_PRESCALE   2   // Prescaler-Einstellung (Einheit) von 0 (16ms) bis 9 (8s) [4: ca. 250ms, 6: ca 1s]; ACHTUNG: WD-Timer ist wenig genau!
62
// Skalierung der Zeiten: Anzahl der WD-Zeiteinheiten (16ms ... 8s) zur Feinanpassung
63
#define MULT      2  // Multiplikator 2^MULT (0..22) des AD-Wertes für adc_val_PB4 (Verlängerung Einschaltdauer 'Treppenlicht' / Einschaltverzögerung 'Futterautomat')
64
#define DIV             1  // Divisor 2^DIV (0..10) für AD-Wert von adc_val_PB3 (Verkürzung Einschaltdauer bei Mode 'Futterautomat')
65
// Zeiten:
66
// t1 = 2^WD_PRESCALE*16ms * 2^MULT * adc_val_PB4
67
// t2 = 2^WD_PRESCALE*16ms / 2^DIV * adc_val_PB3  (min: 1*2^WD_PRESCALE*16ms)
68
// adc_val_PBx: 1 ... 1023
69
#define TREPPE 1
70
#define FUTTER 0
71
72
// Setup Funktion
73
#define HIGH_ACTIVE  
74
//#define NOT_RETRIGGERABLE   // ggf, Retriggerung des gesamten Zyklus
75
//#define DEBUG    
76
77
// IRQ-Variablen
78
volatile uint32_t l_wcount = 1; // einfach ungleich Null setzen, dann sleep nach Reset 
79
volatile uint16_t pulsdauer;
80
81
/**************************************************************************************************/
82
/* ADC Einzelmessung */
83
uint16_t ADC_Read( void )
84
{
85
  uint16_t adc_res;
86
  ADCSRA |= (1<<ADSC);              // eine Wandlung "single conversion"
87
  while (ADCSRA & (1<<ADSC) ) {}    // auf Abschluss der Konvertierung warten
88
  adc_res = ADCW;
89
  return adc_res;                   // ADC auslesen und zurückgeben
90
}
91
92
/**************************************************************************************************/
93
// ADC Mehrfachmessung mit Mittelwertbildung
94
uint16_t ADC_Read_Avg( uint8_t average )
95
{
96
  uint32_t result = 0;
97
 
98
  for (uint8_t i = 0; i < average; ++i )
99
    result += ADC_Read( );
100
 
101
  return (uint16_t)( result / average );
102
}
103
104
/****************************************************************************************************/
105
106
ISR(WDT_vect)
107
{
108
  l_wcount--;
109
  pulsdauer--;
110
}
111
112
/******************************************************************************************************/
113
114
ISR (INT0_vect)        // Bei Tastendruck über ADC die Zahl der WD-Schleifen lesen l_wcount
115
  { 
116
    uint16_t adc_val_PB4;
117
  PORTB |= (1 << PB0);        // Poti aktivieren
118
    PRR &= ~(1 << PRADC);       // Power ADC aktivieren
119
    ADCSRA |= (1 << ADEN);      // ADC einschalten
120
     ADMUX = 2 ;                 // Kanal 2 (PB4), Poti für Verzögerungszeit
121
  ADC_Read();              // nach dem Einschalten ein Dummy-Aufruf
122
  adc_val_PB4 = ADC_Read_Avg(4);    // ADC mit Average 
123
  if(!adc_val_PB4) adc_val_PB4=1;
124
  l_wcount = (((uint32_t) adc_val_PB4) << MULT);  // Multiplikation mit 2^MULT zur Zeitverlängerung
125
  if(MULT > 22) l_wcount = 0xFFFFFFFF; // wg Überlauf bei Fehleingabe
126
   PORTB  &= ~(1 << PB0);    // Ausgang auf LOW: Poti abschalten --> Strom sparen
127
    ADCSRA &= ~(1 << ADEN);     // ADC ausschalten
128
    PRR |= (1 << PRADC);        // Power ADC ausschalten
129
  GIMSK=0;                    // INT0-IRQ abschalten
130
  }  
131
132
/*****************************************************************************************************/ 
133
134
int main()
135
{
136
  uint8_t wd_timer_prescale;
137
  uint8_t wdt_flags, mode;
138
  uint16_t adc_val_PB3=0;  // ADC-Werte von zwei Potis
139
140
  // Ports definieren
141
  /*
142
  PB0 (Pin 5) Output, MOSI, Spannung an Poti für Zeiteinstellung
143
  PB1 (Pin 6) Output, MISO, Schaltsignal
144
  PB2 (Pin 7) Input,  Starttaste, SCK, INT0
145
  PB3 (Pin 2) Input,  ADC3 für Pulsdauerpoti 2 am Ende (wenn ungenutzt: PB3 auf GND)
146
  PB4 (Pin 3) Input,  ADC2 für Verzögerungszeit (Poti 1)
147
  PB5 (Pin 1) Reset
148
149
  Alle Ausgänge sind LOW-aktiv bzw. HIGH-aktive mit #define HIGH_ACTIVE
150
  INT0 und Taste sind LOW-aktiv
151
  */
152
153
  PORTB  = 0x00;
154
  #ifndef HIGH_ACTIVE
155
    PORTB  = (1<<PB1) ;  // Bei Active-Low ist der Ruhezustand HIGH, Pullup setzen.
156
  #endif
157
  DDRB  = (1<<PB0) | (1<<PB1); // PB0 und PB1 Output, Rest Input
158
  PORTB |= (1<<PB2) ;  // Pullup für Taster
159
160
  // zum Test, ob WD-Reset passiert (Debug), Ausgang PB1 kurz toggeln
161
  #ifdef DEBUG
162
    PORTB ^= (1 << PB1);
163
    _delay_ms (200);
164
    PORTB ^= (1 << PB1);
165
  #endif
166
167
168
  // Strom sparen
169
  PRR |= (1 << PRUSI) | (1 << PRTIM1) | (1 << PRTIM0);   // USI und Timer1/0 ungenutzt: abschalten 
170
171
  // Power-Down Sleep Mode einstellen
172
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  // stromsparend, aufwecken über WD oder INT0
173
  sleep_enable();             // Schlafmodus vorbereiten
174
175
  // AD-Wandler initalisieren 
176
  // REFS0 und REFS1 = 0, VCC als Referenz
177
  ADMUX = 0;
178
  DIDR0 = (1 << ADC2D) | (1 << ADC3D);   // digitalen Input an ADC2/3 abklemmen: Strom sparen
179
  ADCSRA = (1 << ADPS1) | (1<<ADPS0);   // Vorteiler mit 8, hier haben wir Zeit, aber zw. 50 und 200kHz sollten es sein
180
181
  // Mode lesen
182
  PORTB |= (1 << PB0);         // Potis aktivieren
183
  ADCSRA |= (1 << ADEN);      // ADC einschalten
184
  ADMUX = 3 ;                 // Kanal 3 (PB3), Mode Treppe / Futter bestimmen
185
  ADC_Read();              // nach dem Einschalten ein Dummy-Aufruf
186
  adc_val_PB3 = ADC_Read_Avg(4);    
187
  ADCSRA &= ~(1 << ADEN);     // ADC ausschalten
188
  PRR |= (1 << PRADC);        // Power ADC ausschalten
189
    
190
  PORTB &= ~(1 << PB0);      // Poti abschalten
191
  if (adc_val_PB3 <= 5)       // nur zur Sicherheit, eigentlich reicht == 0, die minimale Delayzeit im Mode "Futterautomat" kann nach dem Init wieder auf kleinere Werte gestellt werden
192
  {
193
    mode = TREPPE;          // Wenn Poti auf Null (<= 5LSB) gedreht wird, dann nach Reset / PowerUp wird Mode "Treppenlicht" gewählt
194
  } 
195
  else mode = FUTTER;          // Mode "Futterautomat"
196
197
  l_wcount=1;                 // einfach ungleich Null setzen, damit die Schleife erst mal durchlaufen wird
198
199
  // Externen Pin Level IRQ auf Low Level aktivieren
200
  MCUCR &= ~((1 << ISC00) | (1 << ISC01)); 
201
202
  // Watchdog IRQ vorbereiten
203
  wd_timer_prescale = WD_PRESCALE;
204
  if (wd_timer_prescale > 9 ) wd_timer_prescale=9;
205
  wdt_flags=wd_timer_prescale & 7;
206
  if (wd_timer_prescale > 7) wdt_flags|= (1<<5);  // wdt_flags enthält den Prescalerwert (0 .. 9)
207
  wdt_flags |= (1<<WDCE);
208
209
  sei();
210
  GIMSK |= (1 << INT0);       // Aktivierung INT0 IRQ für Starttaste
211
  
212
  // Hauptschleife
213
  while (1)
214
  { 
215
    if(l_wcount==0)
216
    {
217
      // WD-Timer ist 'l_wcount'-mal abgelaufen
218
219
220
      /*
221
      ADCSRA &= ~(1 << ADEN);     // ADC ausschalten
222
      PRR |= (1 << PRADC);        // Power ADC ausschalten
223
      PORTB  &= ~(1 << PB0);      // Ausgang auf LOW: Poti abschalten
224
      */
225
      if (mode == FUTTER)           // Mode Futterautomat
226
      {
227
        // ATOMIC nicht notwendig, weil nächster WD-IRQ erst in min. 15ms kommt (Änderung von 'pulsdauer')
228
        // und der Block bis "while (pulsdauer>0) nur  <1.5ms (gemessen) benötigt. 
229
        PORTB |= (1 << PB0);           // Potis aktivieren
230
        PRR &= ~(1 << PRADC);          // Power ADC aktivieren
231
        ADCSRA |= (1 << ADEN);         // ADC einschalten
232
        ADMUX = 3 ;                    // Kanal 3 (PB3), Poti für Pulsdauer am Ende
233
        ADC_Read();            // nach dem Einschalten ein Dummy-Aufruf
234
        adc_val_PB3 = ADC_Read_Avg(4);  // ADC mit Average 
235
        PORTB &= ~(1 << PB0);           // Potis abschalten
236
        pulsdauer = (( adc_val_PB3) >> DIV);  // Division mit 2^DIV zur Zeiteinschränkung 
237
        if (!pulsdauer) pulsdauer++;  // +1 um >0 zu bleiben  
238
        ADCSRA &= ~(1 << ADEN);        // ADC ausschalten
239
        PRR |= (1 << PRADC);           // Power ADC ausschalten
240
241
        #ifdef HIGH_ACTIVE       // PB1 einschalten
242
          PORTB |=  (1 << PB1);
243
        #else
244
          PORTB &= ~(1 << PB1);     
245
        #endif
246
247
        while (pulsdauer>0)   // siehe oben: ATOMIC nicht erforderlich
248
        {
249
          #ifdef NOT_RETRIGGERABLE
250
            GIMSK=0;
251
          #else
252
            GIMSK |= (1 << INT0);
253
          #endif
254
          sleep_mode();                  
255
          #ifndef NOT_RETRIGGERABLE
256
            if((PINB & (1<<PINB2)) == 0) {break;} // Unterbrechung der aktiven Phase durch Tastendruck
257
          #endif
258
        }
259
      } // Ende Futterautomat
260
261
      #ifdef HIGH_ACTIVE        // PB1 ausschalten
262
        PORTB &= ~(1 << PB1);
263
      #else
264
        PORTB |= (1 << PB1);     
265
      #endif
266
267
      wdt_disable();   // Sequenz abgelaufen, WD-Timer deaktivieren, sonst weckt der wieder
268
269
      // falls einer bis zum Ende der Sequenz INT0 drückt
270
      while((PINB & (1<<PINB2)) == 0 ) {_delay_ms(50);} // einfache Entprellung
271
272
      GIMSK |= (1 << INT0);   // im nicht-nachtriggerbaren Mode muss INT0-IRQ zum Aufwachen wieder aktiviert werden
273
    }
274
    sleep_mode();         // hier schlafen bis zum INT0-IRQ (Tastendruck), auch nach Reset
275
    // nach INT0 geht es hier weiter
276
    #ifdef NOT_RETRIGGERABLE
277
      GIMSK=0;
278
    #else
279
      GIMSK |= (1 << INT0);   // weiterer INT0-IRQ, so dass Zeitwert neu gesetzt wird
280
    #endif
281
    if (mode == TREPPE)
282
    {
283
      #ifdef HIGH_ACTIVE           // PB1 einschalten
284
        PORTB |=  (1 << PB1);
285
      #else
286
        PORTB &= ~(1 << PB1);     
287
      #endif
288
    }
289
290
    // Watchdog wieder aktivieren
291
    WDTCR |= (1<<WDCE);       // WD Change enable
292
    WDTCR = wdt_flags | (1<<WDIE);  // WD-Timer wieder aktivieren, set watchdog timeout value, start WD-IRQ
293
294
  }  // while Hauptschleife
295
  return 0;
296
}  // main