+ OCR1C = PWM_TOP;
+ // OCR1B = steps[0];
+ OCR1B = 0;
+ TIMSK = _BV(OCIE1B) | _BV(TOIE1);
+}
+
+static void pwm_susp()
+{
+ TCCR1 = 0;
+ TIMSK = 0;
+ GTCCR = 0;
+ PORTB &= ~_BV(PB4);
+}
+
+ISR(TIM1_OVF_vect)
+{
+ adc_start_measurement(1);
+}
+
+ISR(TIM1_COMPB_vect)
+{
+ adc_start_measurement(0);
+}
+
+static void pwm_set(unsigned char pwm)
+{
+ OCR1B = pwm;
+}
+
+/* ===================== Status LED on pin PB2 ======================= */
+static void status_led_init()
+{
+ DDRB |= _BV(PB2);
+ PORTB &= ~_BV(PB2);
+}
+
+static void status_led_on()
+{
+ PORTB |= _BV(PB2);
+}
+
+static void status_led_off()
+{
+ PORTB &= ~_BV(PB2);
+}
+
+static unsigned char status_led_is_on()
+{
+ return PORTB & _BV(PB2) ? 1 : 0;
+}
+
+/* ================== Buttons on pin PB0 and PB1 ===================== */
+static void buttons_init()
+{
+ DDRB &= ~(_BV(PB0) | _BV(PB1)); // set as input
+ PORTB |= _BV(PB0) | _BV(PB1); // internal pull-up
+
+ GIMSK &= ~_BV(PCIE); // disable pin-change IRQs
+ PCMSK = 0; // disable pin-change IRQs on all pins of port B
+}
+
+static void buttons_susp()
+{
+ buttons_init();
+
+ GIMSK |= _BV(PCIE);
+ PCMSK |= _BV(PCINT0) | _BV(PCINT1);
+}
+
+static unsigned char buttons_pressed()
+{
+ return (
+ (PINB & _BV(BUTTON1) ? 0 : 1)
+ |
+ (PINB & _BV(BUTTON2) ? 0 : 2)
+ );
+}
+
+static unsigned char buttons_wait_for_release()
+{
+ uint16_t wake_count = 0;
+
+ do {
+ if (++wake_count > WAKEUP_LIMIT)
+ status_led_on(); // inform the user
+
+ _delay_ms(WAKEUP_POLL);
+ } while (buttons_pressed());
+
+ status_led_off();
+
+ return wake_count > WAKEUP_LIMIT;
+}
+
+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;
+ static unsigned char display_power_level;
+
+ if (display_power_level) {
+ n_blinks = power_level + 1;
+ if (batt_on >> 8 == batt_off >> 8) { // load unplugged
+ n_blinks = 2 * n_blinks;
+ blink_on_time = 0;
+ blink_off_time = 0;
+ } else {
+ blink_on_time = 2;
+ blink_off_time = 2;
+ }
+ } 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 = 4;
+ blink_off_time = 0;
+ }
+
+ blink_counter = 12;
+ display_power_level = !display_power_level;
+}
+
+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
+ if (long_press) {
+ power_down();
+ return;
+ } else { // short press
+ if (button == 1) {
+ if (power_level > 0) {
+ --power_level;
+ }
+ } else if (button == 2) {
+ if (power_level < N_POWER_LEVELS-1) {
+ ++power_level;
+ }
+ }
+ }
+ 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) {
+ pwm_set(0);
+ // TODO power_down() after some time
+ return;
+ }
+
+ if (!batt_on) {
+ batt_on = batt_off;
+ };
+
+ 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;
+
+ if (pwm < PWM_MIN)
+ pwm = PWM_MIN;
+
+#if 0
+ log_byte(0x10 + power_level);
+ log_byte(batt_on8);
+ log_byte(pwm & 0xFF);
+#endif
+
+ 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]);