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