+ISR(PCINT0_vect)
+{
+ // empty - let it wake us from sleep, but do nothing else
+}
+
+/* ==== Watchdog Timer for timing blinks and other periodic tasks ==== */
+static void wdt_init()
+{
+ next_clock_tick = 0;
+ jiffies = 0;
+ WDTCR = _BV(WDIE) | _BV(WDP1); // interrupt mode, 64 ms
+}
+
+static void wdt_susp()
+{
+ wdt_disable();
+}
+
+ISR(WDT_vect) {
+ next_clock_tick = 1;
+ jiffies++;
+}
+
+/* ====== Hardware init, teardown, powering down and waking up ====== */
+static void hw_setup()
+{
+ power_all_disable();
+
+ pwm_init();
+ adc_init();
+ status_led_init();
+ wdt_init();
+}
+
+static void hw_suspend()
+{
+ adc_susp();
+ pwm_susp();
+ status_led_init(); // we don't have a separate _susp() here
+ buttons_susp();
+ wdt_susp();
+
+ power_all_disable();
+}
+
+static void power_down()
+{
+ hw_suspend();
+
+ do {
+ // G'night
+ set_sleep_mode(SLEEP_MODE_PWR_DOWN);
+ sleep_enable();
+ sleep_bod_disable();
+ sei();
+ sleep_cpu();
+
+ // G'morning
+ cli();
+ sleep_disable();
+
+ // allow wakeup by long button-press only
+ } while (!buttons_wait_for_release());
+
+ // OK, wake up now
+ hw_setup();
+}
+
+/* ============ Status LED blinking =================================== */
+static unsigned char blink_on_time, blink_off_time, n_blinks;
+static unsigned char blink_counter;
+
+static unsigned char battery_level()
+{
+ unsigned char i, adc8;
+
+ // NOTE: we use 8-bit value only, so we don't need lock to protect
+ // us against concurrently running ADC IRQ handler:
+ adc8 = batt_off >> 8;
+
+ for (i = 0; i < BATT_N_LEVELS; i++)
+ if (batt_levels[i] > adc8)
+ break;
+
+ return i;
+}
+
+static void status_led_next_pattern()
+{
+ static unsigned char battery_exhausted;
+
+ if (power_level_changed) {
+ power_level_changed--;
+ n_blinks = power_level + 1;
+ } else {
+ unsigned char b_level = battery_level();
+ if (b_level) {
+ battery_exhausted = 0;
+ } else if (battery_exhausted) {
+ if (!--battery_exhausted)
+ power_down();
+ } else {
+ battery_exhausted = LED_BATTEMPTY_COUNT;
+ }
+
+ n_blinks = b_level + 1;
+ }
+
+ blink_on_time = 2;
+ blink_off_time = 1;
+ blink_counter = 10;
+}
+
+static void timer_blink()
+{
+ if (blink_counter) {
+ blink_counter--;
+ } else if (!status_led_is_on()) {
+ status_led_on();
+ blink_counter = blink_on_time;
+ } else if (n_blinks) {
+ --n_blinks;
+ status_led_off();
+ blink_counter = blink_off_time;
+ } else {
+ status_led_next_pattern();
+ }
+}
+
+/* ======== Button press detection and handling ===================== */
+static void button_pressed(unsigned char button, unsigned char long_press)
+{
+ // ignore simlultaneous button 1 and 2 press
+ // Note: we set power_level_changed after each button press,
+ // even when the power is at maximum, to provide visual feedback
+ // with status LED.
+ if (long_press) {
+ if (button == 1) {
+ power_down();
+ return;
+ } else if (button == 2) {
+ power_level = N_POWER_LEVELS-1;
+ }
+ } else { // short press
+ if (button == 1) {
+ if (power_level > 0) {
+ --power_level;
+ } else {
+ power_down();
+ return;
+ }
+ } else if (button == 2) {
+ if (power_level < N_POWER_LEVELS-1) {
+ ++power_level;
+ }
+ }
+ }
+ power_level_changed = LED_PWRCHANGE_COUNT;
+ status_led_next_pattern();
+}
+
+static unsigned char button_state, button_state_time;
+
+static void timer_check_buttons()
+{
+ unsigned char newstate = buttons_pressed();
+
+ if (newstate == button_state) {
+ if (newstate && button_state_time < BUTTON_LONG_MIN)
+ ++button_state_time;
+
+ if (newstate && button_state_time >= BUTTON_LONG_MIN) {
+ status_led_on();
+ }
+ return;
+ }
+
+ if (newstate) {
+ button_state = newstate;
+ button_state_time = 0;
+ return;
+ }
+
+ // just released
+ if (button_state_time >= BUTTON_SHORT_MIN)
+ button_pressed(button_state,
+ button_state_time >= BUTTON_LONG_MIN ? 1 : 0);
+
+ button_state = newstate;
+ button_state_time = 0;
+}
+
+/* ===================== Output power control ======================== */
+static void calculate_power_level()
+{
+ uint32_t pwm;
+ unsigned char batt_on8;
+
+ if (battery_level() == 0 || batt_on == 0) {
+ pwm_set(0);
+ // TODO power_down() after some time
+ return;
+ }
+
+ batt_on8 = batt_on >> 8;
+
+ pwm = (uint32_t)PWM_TOP * power_levels[power_level]
+ * power_levels[power_level];
+ pwm /= (uint32_t)batt_on8 * batt_on8;
+
+ if (pwm > PWM_MAX)
+ pwm = PWM_MAX;
+
+ log_byte(0x10 + power_level);
+ log_byte(batt_on8);
+ log_byte(pwm & 0xFF);
+
+ pwm_set(pwm);
+}
+
+int main()
+{
+ log_init();
+
+#if 0
+ log_word(batt_levels[0]);
+ log_word(batt_levels[1]);
+ log_word(batt_levels[2]);
+ log_flush();
+#endif
+ log_byte(power_levels[0]);
+ log_byte(power_levels[4]);
+ log_flush();
+
+ power_down();
+
+ sei();
+
+ // we try to be completely IRQ-driven, so just wait for IRQs here