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