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
|