status LED visual feedback
[heater.git] / firmware / main.c
1 #include <avr/interrupt.h>
2 #include <avr/io.h>
3 #include <avr/power.h>
4 #include <avr/sleep.h>
5 #include <avr/wdt.h>
6 #include <util/delay.h>
7
8 #include "logging.h"
9
10 #define N_STEPS 5
11 static unsigned char steps[] = { 60, 85, 121, 171, 242 };
12 static unsigned char intensity = 0;
13
14 static void timer_init()
15 {
16         power_timer1_enable();
17
18         DDRB |= _BV(PB4);
19
20         TCCR1 = _BV(CS10); // clk/1 = 1 MHz
21         // TCCR1 = _BV(CS11) | _BV(CS13); // clk/512 = 2 kHz
22         GTCCR = _BV(COM1B1) | _BV(PWM1B);
23         OCR1C = 255;
24         OCR1B = 0;
25 }
26
27 static void set_pwm(unsigned char pwm)
28 {
29         OCR1B = pwm;
30 }
31
32 static void adc_init()
33 {
34         power_adc_enable();
35
36         ADCSRA = _BV(ADEN) | _BV(ADPS1) | _BV(ADPS0); // clk/8 = 125 kHz
37         ADMUX = _BV(REFS1) | _BV(MUX1) | _BV(MUX0); // 1.1V ref., PB3 single-ended
38         DIDR0 = _BV(ADC3D);
39 }
40
41 static void status_led_init()
42 {
43         DDRB |= _BV(PB2);
44         PORTB &= ~_BV(PB2);
45 }
46
47 static void status_led_on()
48 {
49         PORTB |= _BV(PB2);
50 }
51
52 static void status_led_off()
53 {
54         PORTB &= ~_BV(PB2);
55 }
56
57 static unsigned char status_led_is_on()
58 {
59         return PORTB & _BV(PB2) ? 1 : 0;
60 }
61
62 static void buttons_init()
63 {
64         DDRB &= ~(_BV(PB0) | _BV(PB1)); // set as input
65         PORTB |= _BV(PB0) | _BV(PB1);   // internal pull-up
66
67         GIMSK &= ~_BV(PCIE); // disable pin-change IRQs
68         PCMSK = 0; // disable pin-change IRQs on all pins of port B
69 }
70
71 static void buttons_susp()
72 {
73         buttons_init();
74
75         GIMSK |= _BV(PCIE);
76         PCMSK |= _BV(PCINT0) | _BV(PCINT1);
77 }
78
79 static unsigned char buttons_pressed()
80 {
81         return (
82                 (PINB & _BV(PB0) ? 0 : 1)
83                 |
84                 (PINB & _BV(PB1) ? 0 : 2)
85         );
86 }
87
88 #define WAKEUP_POLL 100 // msec
89 #define WAKEUP_LIMIT 5  // times WAKEUP_POLL
90
91 static unsigned char buttons_wait_for_release()
92 {
93         uint16_t wake_count = 0;
94
95         do {
96                 if (++wake_count > WAKEUP_LIMIT)
97                         status_led_on(); // inform the user
98
99                 _delay_ms(WAKEUP_POLL);
100         } while (buttons_pressed());
101
102         status_led_off();
103
104         return wake_count > WAKEUP_LIMIT;
105 }
106
107 ISR(PCINT0_vect)
108 {
109         // empty - let it wake us from sleep, but do nothing else
110 }
111
112 static void wdt_init()
113 {
114         WDTCR = _BV(WDIE) | _BV(WDP1); // interrupt mode, 64 ms
115 }
116
117 static void wdt_susp()
118 {
119         wdt_disable();
120 }
121
122 static void hw_setup()
123 {
124         power_all_disable();
125
126         timer_init();
127         adc_init();
128         status_led_init();
129         wdt_init();
130 }
131
132 static void hw_suspend()
133 {
134         ADCSRA &= ~_BV(ADEN); // disable ADC
135         TCCR1 = 0; // disable T/C 1
136
137         status_led_init();
138         buttons_susp();
139         wdt_susp();
140
141         power_all_disable();
142 }
143
144 static volatile unsigned char wdt_timer_fired;
145
146 ISR(WDT_vect) {
147         wdt_timer_fired = 1;
148 }
149
150 static void power_down()
151 {
152         hw_suspend();
153
154         do {
155                 // G'night
156                 set_sleep_mode(SLEEP_MODE_PWR_DOWN);
157                 sleep_enable();
158                 sleep_bod_disable();
159                 sei();
160                 sleep_cpu();
161
162                 // G'morning
163                 cli();
164                 sleep_disable();
165
166                 // allow wakeup by long button-press only
167         } while (!buttons_wait_for_release());
168
169         // OK, wake up now
170         hw_setup();
171 }
172
173 static void button_one_pressed()
174 {
175         if (intensity > 0) {
176                 set_pwm(steps[--intensity]);
177         } else {
178                 power_down();
179         }
180 }
181
182 static void button_two_pressed()
183 {
184         if (intensity < N_STEPS-1) {
185                 set_pwm(steps[++intensity]);
186         }
187 }
188
189 static unsigned char button_state, button_state_time;
190
191 static void timer_check_buttons()
192 {
193         unsigned char newstate = buttons_pressed();
194
195         if (newstate == button_state) {
196                 if (newstate && button_state_time < 4)
197                         ++button_state_time;
198                 return;
199         }
200
201         if (newstate) {
202                 button_state = newstate;
203                 button_state_time = 0;
204                 return;
205         }
206
207         // just released
208         switch (button_state) {
209         case 1: button_one_pressed();
210                 break;
211         case 2: button_two_pressed();
212                 break;
213         default: // ignore when both are preseed
214                 break;
215         }
216
217         button_state = newstate;
218 }
219
220 static unsigned char blink_on_time, blink_off_time, n_blinks;
221 static unsigned char blink_counter;
222
223 static void timer_blink()
224 {
225         if (blink_counter) {
226                 blink_counter--;
227         } else if (status_led_is_on()) {
228                 status_led_off();
229                 blink_counter = blink_off_time;
230         } else if (n_blinks) {
231                 --n_blinks;
232                 status_led_on();
233                 blink_counter = blink_on_time;
234         } else {
235                 n_blinks = intensity + 1;
236                 blink_on_time = 0;
237                 blink_off_time = 2;
238                 blink_counter = 10;
239         }
240 }
241
242 int main()
243 {
244         log_init();
245
246         power_down();
247
248 #if 0
249         ADCSRA |= _BV(ADSC);
250         while (!(ADCSRA & _BV(ADIF)))
251                 ;
252         log_word(ADCW);
253         ADCSRA |= _BV(ADSC);
254         while (!(ADCSRA & _BV(ADIF)))
255                 ;
256         log_word(ADCW);
257         log_flush();
258 #endif
259         sei();
260
261         // we try to be completely IRQ-driven, so just wait for IRQs here
262         while(1) {
263                 cli();
264                 set_sleep_mode(SLEEP_MODE_IDLE);
265                 sleep_enable();
266                 // keep BOD active, no sleep_bod_disable();
267                 sei();
268                 sleep_cpu();
269                 sleep_disable();
270
271                 if (wdt_timer_fired) {
272                         wdt_timer_fired = 0;
273                         timer_check_buttons();
274                         timer_blink();
275                 }
276         }
277 }