/* * OpenPSU firmware * * Copyright (C) 2007-2010 Michael Buesch * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "main.h" #include "lcd.h" #include "calibration.h" #include "ltc1446.h" #include "ext_control.h" #include #include #include #include #include #include #include #define KEYS_PIN PINC #define KEYS_DDR DDRC #define KEYS_PORT PORTC #define KEY_PROFILE_MSK (1 << 1) #define KEY_SELECT_MSK (1 << 2) #define KEY_UP_MSK (1 << 3) #define KEY_DOWN_MSK (1 << 4) #define KEY_FAST_MSK (1 << 5) #define KEYS_MASK (KEY_PROFILE_MSK | KEY_SELECT_MSK | KEY_UP_MSK | \ KEY_DOWN_MSK | KEY_FAST_MSK) #define POWERSIG_PORT PORTD #define POWERSIG_DDR DDRD #define POWERSIG_BIT (1 << 7) #define EDIT_ENABLE_DELAY 5000 /* msec */ struct profile { /* The output voltage value. */ uint16_t voltage; /* The output maximum current value. */ uint16_t maxcurrent; }; struct voltage_calib { uint16_t mV; /* Setting in mV */ int8_t offset; /* Offset in DAC steps */ }; struct current_limit_calib { uint16_t mA; /* Setting in mA */ int8_t offset; /* Offset in DAC steps */ }; struct current_measure_calib { uint16_t adc; /* Measured ADC value */ int8_t offset; /* Offset in mA */ }; struct calibration { struct voltage_calib vcal[32]; /* Voltage calibration */ struct current_measure_calib imeas_cal[32]; /* Current measurement calibration */ struct current_limit_calib ilimit_cal[32]; /* Current limit calibration */ }; /* Operation modes. */ enum { MODE_MAIN, /* Standard PSU mode. */ MODE_BALANCE, /* Hardware balancing mode. Used to balance the potentiometers. */ }; /* Possible selections for the MAIN menu. */ enum { SEL_MAIN_VOLTAGE_HI, SEL_MAIN_VOLTAGE_LO, SEL_MAIN_MAXCURRENT_HI, SEL_MAIN_MAXCURRENT_LO, NR_SEL_MAIN, }; /* Possible selections for the BALANCE menu. */ enum { SEL_BALANCE_VOLTAGE, SEL_BALANCE_CURRENT, NR_SEL_BALANCE, }; enum balance_state_value { BALANCE_OFF, BALANCE_HALF, BALANCE_FULL, }; struct balance_state { uint8_t voltage; /* enum balance_state_value */ uint8_t maxcurrent; /* enum balance_state_value */ }; struct adc_state { /* The measured current flow (from ADC). */ uint16_t measured_current; /* The temporary sample value. We make 4 samples. */ uint16_t samples; /* The number of samples we already got. */ uint8_t nr_samples; }; enum key_types { KEY_NORMAL, KEY_REPETITIVE, KEY_AUTORELEASE, }; enum key_flags { KEY_FLG_PRESSED = (1 << 0), /* Interpreted key state */ KEY_FLG_NOTIFY = (1 << 1), /* Key press notification */ KEY_FLG_HW_PRESSED = (1 << 2), /* Debounced hardware state */ }; #define KEY_DEBOUNCE 30 /* Key debouncing. In milliseconds. */ struct key { /* Configuration */ uint8_t type; /* enum key_types */ uint8_t mask; /* The port bitmask for this key. */ uint16_t rep_delay; /* Repetition delay, in jiffies. */ /* Status */ uint8_t flags; uint8_t debounce; uint16_t rep_timer; }; /* The profile data. */ static struct profile profiles[NR_PROFILES]; /* The currently selected profile. */ static uint8_t active_profile; static uint8_t preselected_profile; /* The operation MODE. */ static uint8_t opmode; /* The currently active selection. */ static uint8_t active_selection; /* Editing enable counter. */ static uint16_t edit_enable = EDIT_ENABLE_DELAY; /* Is the circuitry limiting the current? */ static bool current_limiting; /* Is the FAST key pressed? */ static bool fast_key_pressed; /* Is the PROFILE key pressed? */ static bool profchange_key_pressed; /* Asynchronous LCD update request. */ static bool update_lcd_request; /* Set to true when INT0 triggers. */ static bool int0_triggered; /* State for opmode==MODE_BALANCE */ static struct balance_state balance; /* ADC state. */ static struct adc_state adc; /* True if some value that has to be saved to the eeprom changed. */ static bool configuration_changed; static uint16_t config_change_timer; /* Debounced key states. */ static struct key keys[] = { { .type = KEY_NORMAL, .mask = KEY_PROFILE_MSK, }, { .type = KEY_AUTORELEASE, .mask = KEY_SELECT_MSK, }, { .type = KEY_REPETITIVE, .mask = KEY_UP_MSK, .rep_delay = JIFFIES_PER_SECOND / 4, }, { .type = KEY_REPETITIVE, .mask = KEY_DOWN_MSK, .rep_delay = JIFFIES_PER_SECOND / 4, }, { .type = KEY_NORMAL, .mask = KEY_FAST_MSK, }, }; enum key_indices { KEY_PROFILE, KEY_SELECT, KEY_UP, KEY_DOWN, KEY_FAST, }; /* The eeprom values. */ #define EE_PROF_INIT(v, c) { \ .voltage = v, \ .maxcurrent = c, \ } static struct profile EEMEM ee_profiles[NR_PROFILES]; static uint8_t EEMEM ee_active_profile; static uint16_t EEMEM ee_checksum; /* The voltage and current calibration. */ //TODO //static struct calibration EEMEM ee_calibration; //static struct calibration calibration; static inline void wdt_do_enable(void) { wdt_enable_irq_mode(WDTO_60MS); } static bool check_edit_enabled(void) { bool enabled; uint8_t sreg; sreg = irq_disable_save(); enabled = !!edit_enable; edit_enable = EDIT_ENABLE_DELAY; lcd_cmd_dispctl(1, 0, 1); /* blink cursor */ irq_restore(sreg); return enabled; } static void set_config_changed(void) { uint8_t sreg; sreg = irq_disable_save(); configuration_changed = 1; config_change_timer = 0; irq_restore(sreg); } static uint16_t millivalue_to_dac(uint16_t milli, uint16_t max_milli) { uint32_t tmp; tmp = milli; tmp *= MAX_DAC_VALUE; tmp /= max_milli; return (uint16_t)tmp; } /* Convert a millivolt value to the Digital-Analog-Converter value. */ static uint16_t millivolt_to_dac(uint16_t mV) { uint16_t dac; dac = millivalue_to_dac(mV, MAX_VOLTAGE); dac += calib_voltage_offset(mV); return dac; } /* Convert a milliamps value to the Digital-Analog-Converter value. */ static uint16_t milliamps_to_dac(uint16_t mA) { uint16_t dac; dac = millivalue_to_dac(mA, MAX_CURRENT); dac += calib_maxcurrent_offset(mA); return dac; } /* Convert the measured current value from ADC0 into milliamps. */ static uint16_t adc_to_milliamps(uint16_t adc) { uint32_t tmp; tmp = MAX_CURRENT; tmp *= adc; tmp /= ((1 << 10) - 1); /* 10 bit ADC converter. */ return (uint16_t)tmp; } /* Update the maxcurrent and voltage outputs. */ static void update_output(void) { uint16_t mV, mA; /* The DAC values (in millivolts/milliamps) that are * currently loaded into the DAC. */ static uint16_t active_mV = MAX_DAC_VALUE + 1; static uint16_t active_mA = MAX_DAC_VALUE + 1; if (unlikely(opmode == MODE_BALANCE)) { mV = 0; mA = 0; if (balance.voltage == BALANCE_FULL) mV = MAX_DAC_VALUE; else if (balance.voltage == BALANCE_HALF) mV = MAX_DAC_VALUE / 2; if (balance.maxcurrent == BALANCE_FULL) mA = MAX_DAC_VALUE; else if (balance.maxcurrent == BALANCE_HALF) mA = MAX_DAC_VALUE / 2; } else { struct profile *prof = &(profiles[active_profile]); mV = millivolt_to_dac(prof->voltage); mA = milliamps_to_dac(prof->maxcurrent); } if ((mV == active_mV) && (mA == active_mA)) { /* No need to update. These values are already uploaded. */ return; } /* Upload the values to the DAC chip. */ ltc1446_write(mV, mA); active_mV = mV; active_mA = mA; } static void update_selection(void) { uint8_t line = 0, col = 0; switch (opmode) { case MODE_MAIN: if (profchange_key_pressed) { line = 0; col = 11; } else { switch (active_selection) { case SEL_MAIN_VOLTAGE_HI: line = 0; col = 1; break; case SEL_MAIN_VOLTAGE_LO: line = 0; if (fast_key_pressed) col = 3; else col = 4; break; case SEL_MAIN_MAXCURRENT_HI: line = 1; col = 9; break; case SEL_MAIN_MAXCURRENT_LO: line = 1; if (fast_key_pressed) col = 11; else col = 12; break; default: BUG_ON(1); } } break; case MODE_BALANCE: switch (active_selection) { case SEL_BALANCE_VOLTAGE: line = 1; col = 0; break; case SEL_BALANCE_CURRENT: line = 1; col = 8; break; default: BUG_ON(1); } break; default: BUG_ON(1); } lcd_cmd_cursor(line, col); } static void print_millivalue(uint16_t value) { uint16_t tmp; tmp = value / 1000; lcd_printf("%u.", tmp); tmp = value % 1000; tmp /= 10; /* Don't print the last digit. */ if (tmp < 10) lcd_put_char('0'); lcd_printf("%u", tmp); } static void update_lcd_main(void) { struct profile *prof = &(profiles[active_profile]); if (profchange_key_pressed) prof = &(profiles[preselected_profile]); else prof = &(profiles[active_profile]); /* First line is voltage. */ if (prof->voltage < 10000) lcd_cursor(0, 1); else lcd_cursor(0, 0); print_millivalue(prof->voltage); lcd_put_char('V'); lcd_cursor(0, 9); if (profchange_key_pressed) { lcd_put_str("?<"); lcd_printf("%u", preselected_profile + 1); lcd_put_str(">?"); } else { lcd_put_str(" ["); lcd_printf("%u", active_profile + 1); lcd_put_str("] "); } if (configuration_changed) { lcd_cursor(0, 15); lcd_put_char('*'); } /* Second line is measured current and maximum current. */ if (current_limiting) { lcd_cursor(1, 0); lcd_put_str("ON LIMIT "); print_millivalue(prof->maxcurrent); lcd_put_str("A !"); } else { lcd_cursor(1, 1); print_millivalue(adc_to_milliamps(adc.measured_current)); lcd_put_str("A / "); print_millivalue(prof->maxcurrent); lcd_put_char('A'); } } static void update_lcd_balance(void) { lcd_put_str("Hardware balance\n"); lcd_put_str("V-"); if (balance.voltage == BALANCE_FULL) lcd_put_str("MAX"); else if (balance.voltage == BALANCE_HALF) lcd_put_str("HALF"); else lcd_put_str("MIN"); lcd_cursor(1, 8); lcd_put_str("I-"); if (balance.maxcurrent == BALANCE_FULL) lcd_put_str("MAX"); else if (balance.maxcurrent == BALANCE_HALF) lcd_put_str("HALF"); else lcd_put_str("MIN"); } static void update_lcd(void) { lcd_clear_buffer(); switch (opmode) { case MODE_MAIN: update_lcd_main(); break; case MODE_BALANCE: update_lcd_balance(); break; default: BUG_ON(1); } lcd_commit(); update_selection(); } static void update_lcd_and_output(void) { update_lcd(); update_output(); } static void print_banner(void) { lcd_clear_buffer(); lcd_printf("OpenPSU - %02u%02u%02u", COMPILE_YEAR - 2000, COMPILE_MONTH, COMPILE_DAY); #ifdef __GNUC__ lcd_cursor(1, 0); lcd_printf("GCC-%u.%u.%u", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); #endif /* __GNUC__ */ lcd_commit(); _delay_ms(1500); } static inline bool key_raw_is_pressed(uint8_t key_mask) { return !(KEYS_PIN & key_mask); } static bool key_is_pressed(uint8_t key_index) { struct key *key = &keys[key_index]; uint8_t sreg; bool pressed; sreg = irq_disable_save(); switch (key->type) { case KEY_NORMAL: pressed = !!(key->flags & KEY_FLG_PRESSED); break; case KEY_REPETITIVE: case KEY_AUTORELEASE: pressed = !!(key->flags & KEY_FLG_PRESSED); key->flags &= ~KEY_FLG_PRESSED; break; default: BUG_ON(1); } irq_restore(sreg); return pressed; } static bool edit_key_is_pressed(uint8_t key_index) { bool pressed; pressed = key_is_pressed(key_index); if (pressed) { if (check_edit_enabled()) return 1; } return 0; } static void key_hardware_debounce(struct key *key) { if (key->flags & KEY_FLG_HW_PRESSED) { if (key_raw_is_pressed(key->mask)) { key->debounce = 0; } else { key->debounce++; if (key->debounce >= KEY_DEBOUNCE) { key->flags &= ~KEY_FLG_HW_PRESSED; key->debounce = 0; } } } else { if (key_raw_is_pressed(key->mask)) { key->debounce++; if (key->debounce >= KEY_DEBOUNCE) { key->flags |= KEY_FLG_HW_PRESSED; key->debounce = 0; } } else { key->debounce = 0; } } } static void key_work(struct key *key) { key_hardware_debounce(key); if (key->flags & KEY_FLG_HW_PRESSED) { if (key->type == KEY_REPETITIVE) { if (key->rep_timer) { if (!(key->flags & KEY_FLG_PRESSED)) key->rep_timer--; } else { key->flags |= KEY_FLG_PRESSED; key->rep_timer = key->rep_delay; } } else { if (!(key->flags & KEY_FLG_NOTIFY)) { key->flags |= KEY_FLG_PRESSED; key->flags |= KEY_FLG_NOTIFY; } } } else { key->flags &= ~(KEY_FLG_NOTIFY | KEY_FLG_PRESSED); key->rep_timer = 0; } } static void keys_work(void) { uint8_t i; for (i = 0; i < ARRAY_SIZE(keys); i++) key_work(&keys[i]); } static void keys_init(void) { /* Configure port as input */ KEYS_DDR &= ~KEYS_MASK; /* with pullups */ KEYS_PORT |= KEYS_MASK; } #define prof_default(prof, index, v, c) do { \ BUILD_BUG_ON((index) >= ARRAY_SIZE(prof)); \ prof[index].voltage = v; \ prof[index].maxcurrent = c; \ } while (0) static void load_default_config(void) { memset(&profiles, 0, sizeof(profiles)); prof_default(profiles, 0, 0, 100); prof_default(profiles, 1, 3300, 500); prof_default(profiles, 2, 5000, 500); prof_default(profiles, 3, 6000, 500); prof_default(profiles, 4, 9000, 500); prof_default(profiles, 5, 12000, 500); prof_default(profiles, 6, 24000, 1000); active_profile = 0; } static inline uint16_t crc16_update_byte(uint16_t crc, uint8_t data) { return _crc16_update(crc, data); } static uint16_t crc16_update_buf(uint16_t crc, void *_buf, uint8_t size) { uint8_t *buf = _buf; uint8_t i; for (i = 0; i < size; i++) crc = crc16_update_byte(crc, buf[i]); return crc; } /* Store config values to eeprom. */ static void eeprom_store_config(void) { uint16_t crc = 0xFFFF; uint8_t sreg; BUILD_BUG_ON(ARRAY_SIZE(ee_profiles) != ARRAY_SIZE(profiles)); sreg = irq_disable_save(); wdt_disable(); eeprom_busy_wait(); eeprom_write_block(&profiles, &ee_profiles, sizeof(ee_profiles)); crc = crc16_update_buf(crc, &profiles, sizeof(profiles)); eeprom_write_byte(&ee_active_profile, active_profile); crc = crc16_update_byte(crc, active_profile); eeprom_write_word(&ee_checksum, crc); eeprom_busy_wait(); wdt_do_enable(); irq_restore(sreg); } static void eeprom_crc_fault(void) { uint8_t i; load_default_config(); eeprom_store_config(); lcd_clear_buffer(); lcd_put_str("EEPROM CRC ERROR\n" "press profile"); lcd_commit(); for (i = 0; i < 3; i++) { if (!key_raw_is_pressed(KEY_PROFILE_MSK)) i = 0; _delay_ms(KEY_DEBOUNCE); } while (key_raw_is_pressed(KEY_PROFILE_MSK)); _delay_ms(KEY_DEBOUNCE); } /* Load config from eeprom. */ static void eeprom_load(void) { uint16_t crc = 0xFFFF, expected_crc; BUILD_BUG_ON(ARRAY_SIZE(ee_profiles) != ARRAY_SIZE(profiles)); eeprom_busy_wait(); eeprom_read_block(&profiles, &ee_profiles, sizeof(profiles)); crc = crc16_update_buf(crc, &profiles, sizeof(profiles)); active_profile = eeprom_read_byte(&ee_active_profile); crc = crc16_update_byte(crc, active_profile); expected_crc = eeprom_read_word(&ee_checksum); if (crc != expected_crc) eeprom_crc_fault(); } void emergency_shutdown(void) { TCCR1B = 0; /* Disable system timer */ TIMSK1 = 0; /* Disable IRQs */ UCSR0B = 0; /* Disable USART transceiver and IRQs */ } /* Enable external IRQ 0 */ static void int0_enable(void) { EIMSK |= (1 << INT0); } /* Disable external IRQ 0 */ static void int0_disable(void) { EIMSK &= ~(1 << INT0); } static void poke_adc(void) { if (ADCSRA & (1 << ADSC)) { /* already running. */ return; } /* Disable digital input. */ DIDR0 = (1 << ADC0D); ADCSRB = 0; /* Start ADC0 with AVCC reference and a prescaler of 128. */ ADMUX = (1 << REFS0); ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADSC) | (1 << ADPS0) | (1 << ADPS1) | (1 << ADPS2); } /* ADC conversion complete. */ ISR(ADC_vect) { adc.samples += ADC; adc.nr_samples++; if (adc.nr_samples == 4) { adc.measured_current = adc.samples / 4; adc.nr_samples = 0; adc.samples = 0; update_lcd_request = 1; } else poke_adc(); } /* External IRQ 0. * This IRQ fires, if the control circuitry notifies * the current-limiting condition. */ ISR(INT0_vect) { /* INT0 has highest priority. * To avoid stalling the ADC and system IRQs, disable int0 here * and enable it again in the tick IRQ. */ int0_disable(); int0_triggered = 1; } static void check_currentlimiting_condition(void) { static uint16_t int0_on_jiffies; static uint16_t int0_off_jiffies; const uint16_t on_threshold = JIFFIES_PER_SECOND / 5; const uint16_t off_threshold = JIFFIES_PER_SECOND / 2; bool old_current_limiting; if (int0_triggered) { int0_triggered = 0; if (int0_on_jiffies < on_threshold) int0_on_jiffies++; int0_off_jiffies = 0; } else { int0_on_jiffies = 0; if (int0_off_jiffies < off_threshold) int0_off_jiffies++; } BUG_ON((int0_on_jiffies != 0) && (int0_off_jiffies != 0)); BUG_ON((int0_on_jiffies == 0) && (int0_off_jiffies == 0)); old_current_limiting = current_limiting; if (int0_on_jiffies == on_threshold) current_limiting = 1; if (int0_off_jiffies == off_threshold) current_limiting = 0; if (old_current_limiting != current_limiting) update_lcd_request = 1; } /* System timer. Triggers every millisecond. */ ISR(TIMER1_COMPA_vect) { static uint16_t every_500msec_timer; keys_work(); if (edit_enable) { edit_enable--; if (!edit_enable) lcd_cmd_dispctl(1, 0, 0); /* no cursor */ } if (configuration_changed) { config_change_timer++; /* 10 seconds after the last config change * write them to eeprom. */ if (config_change_timer >= (JIFFIES_PER_SECOND * 10)) { eeprom_store_config(); configuration_changed = 0; update_lcd_request = 1; } } if (++every_500msec_timer >= (JIFFIES_PER_SECOND / 2)) { /* Triggers every 500 milliseconds. */ every_500msec_timer = 0; poke_adc(); } check_currentlimiting_condition(); int0_enable(); } static void timer_init(void) { /* Initialize the system timer */ TCCR1B = (1 << WGM12) | SYSTIMER_TIMERFREQ; /* Speed */ OCR1A = SYSTIMER_CMPVAL; /* CompareMatch value */ TIMSK1 |= (1 << OCIE1A); /* IRQ mask */ } static void opmode_init(void) { uint8_t i; opmode = MODE_MAIN; /* If the FAST key is pressed during startup, we enable balance mode */ for (i = 0; i < 3; i++) { if (!key_raw_is_pressed(KEY_FAST_MSK)) return; _delay_ms(KEY_DEBOUNCE); } opmode = MODE_BALANCE; update_output(); lcd_clear_buffer(); lcd_put_str("Hardware balance\n" "mode enabled"); lcd_commit(); while (key_raw_is_pressed(KEY_FAST_MSK)); _delay_ms(KEY_DEBOUNCE); } //FIXME These are called from irq context uint16_t get_voltage_from_prof(uint8_t profile) { struct profile *prof = &(profiles[profile]); return prof->voltage; } void set_voltage_in_prof(uint8_t profile, uint16_t voltage) { struct profile *prof = &(profiles[profile]); if (opmode == MODE_MAIN && !profchange_key_pressed) { prof->voltage = voltage; set_config_changed(); update_lcd_and_output(); } } uint16_t get_maxcur_from_prof(uint8_t profile) { struct profile *prof = &(profiles[profile]); return prof->maxcurrent; } void set_maxcur_in_prof(uint8_t profile, uint16_t maxcur) { struct profile *prof = &(profiles[profile]); if (opmode == MODE_MAIN && !profchange_key_pressed) { prof->maxcurrent = maxcur; set_config_changed(); update_lcd_and_output(); } } uint8_t get_active_profile(void) { return active_profile; } void switch_to_profile(uint8_t profile) { if (profile == active_profile) return; if (opmode == MODE_MAIN && !profchange_key_pressed) { active_profile = profile; set_config_changed(); update_lcd_and_output(); } } static void key_select_pressed(void) { uint8_t max_selection; if (profchange_key_pressed) return; switch (opmode) { case MODE_MAIN: max_selection = NR_SEL_MAIN - 1; break; case MODE_BALANCE: max_selection = NR_SEL_BALANCE - 1; break; default: BUG_ON(1); } if (active_selection == max_selection) active_selection = 0; else active_selection++; update_selection(); } static void key_up_pressed(void) { struct profile *prof; switch (opmode) { case MODE_MAIN: prof = &(profiles[active_profile]); if (profchange_key_pressed) { preselected_profile++; if (preselected_profile >= NR_PROFILES) preselected_profile = 0; update_lcd(); } else { switch (active_selection) { case SEL_MAIN_VOLTAGE_HI: if (fast_key_pressed) prof->voltage += 5000; else prof->voltage += 1000; break; case SEL_MAIN_VOLTAGE_LO: if (fast_key_pressed) prof->voltage += 100; else prof->voltage += 10; break; case SEL_MAIN_MAXCURRENT_HI: prof->maxcurrent += 1000; break; case SEL_MAIN_MAXCURRENT_LO: if (fast_key_pressed) prof->maxcurrent += 100; else prof->maxcurrent += 10; break; default: BUG_ON(1); } if (prof->voltage > MAX_VOLTAGE) prof->voltage = MAX_VOLTAGE; if (prof->maxcurrent > MAX_CURRENT) prof->maxcurrent = MAX_CURRENT; set_config_changed(); update_lcd_and_output(); } break; case MODE_BALANCE: switch (active_selection) { case SEL_BALANCE_VOLTAGE: if (balance.voltage == BALANCE_FULL) balance.voltage = BALANCE_OFF; else balance.voltage++; break; case SEL_BALANCE_CURRENT: if (balance.maxcurrent == BALANCE_FULL) balance.maxcurrent = BALANCE_OFF; else balance.maxcurrent++; break; default: BUG_ON(1); } update_lcd_and_output(); break; default: BUG_ON(1); } } static void key_down_pressed(void) { struct profile *prof; switch (opmode) { case MODE_MAIN: prof = &(profiles[active_profile]); if (profchange_key_pressed) { preselected_profile--; if (preselected_profile > NR_PROFILES) preselected_profile = NR_PROFILES - 1; update_lcd(); } else { switch (active_selection) { case SEL_MAIN_VOLTAGE_HI: if (fast_key_pressed) prof->voltage -= 5000; else prof->voltage -= 1000; break; case SEL_MAIN_VOLTAGE_LO: if (fast_key_pressed) prof->voltage -= 100; else prof->voltage -= 10; break; case SEL_MAIN_MAXCURRENT_HI: prof->maxcurrent -= 1000; break; case SEL_MAIN_MAXCURRENT_LO: if (fast_key_pressed) prof->maxcurrent -= 100; else prof->maxcurrent -= 10; break; default: BUG_ON(1); } /* Unsigned integer overflow trick. */ if (prof->voltage > MAX_VOLTAGE) prof->voltage = 0; if (prof->maxcurrent > MAX_CURRENT) prof->maxcurrent = 0; set_config_changed(); update_lcd_and_output(); } break; case MODE_BALANCE: switch (active_selection) { case SEL_BALANCE_VOLTAGE: if (balance.voltage == BALANCE_OFF) balance.voltage = BALANCE_FULL; else balance.voltage--; break; case SEL_BALANCE_CURRENT: if (balance.maxcurrent == BALANCE_OFF) balance.maxcurrent = BALANCE_FULL; else balance.maxcurrent--; break; default: BUG_ON(1); } update_lcd_and_output(); break; default: BUG_ON(1); } } void wdt_early_init(void) __attribute__((naked, section(".init3"))); void wdt_early_init(void) { MCUSR = 0; wdt_disable(); } int main(void) { opmode = MODE_MAIN; irq_disable(); POWERSIG_PORT |= POWERSIG_BIT; POWERSIG_DDR |= POWERSIG_BIT; UCSR0B = 0; /* Disable USART transceiver */ int0_disable(); keys_init(); ltc1446_init(); lcd_init(); eeprom_load(); update_output(); timer_init(); /* Signal to the world that we are done. */ POWERSIG_PORT &= ~POWERSIG_BIT; _delay_ms(100); print_banner(); opmode_init(); update_lcd(); if (opmode == MODE_MAIN) extctl_init(); wdt_do_enable(); irq_enable(); while (1) { bool need_lcd_update; bool was_pressed; wdt_reset(); was_pressed = fast_key_pressed; fast_key_pressed = edit_key_is_pressed(KEY_FAST); if (was_pressed != fast_key_pressed) update_selection(); switch (opmode) { case MODE_MAIN: if (edit_key_is_pressed(KEY_PROFILE)) { if (!profchange_key_pressed) { profchange_key_pressed = 1; preselected_profile = active_profile; update_lcd(); } } else { if (profchange_key_pressed) { profchange_key_pressed = 0; if (active_profile != preselected_profile) { active_profile = preselected_profile; set_config_changed(); update_lcd_and_output(); } } } if (profchange_key_pressed && fast_key_pressed) { if (edit_key_is_pressed(KEY_SELECT)) reboot(); } break; case MODE_BALANCE: if (edit_key_is_pressed(KEY_PROFILE)) reboot(); break; default: BUG_ON(1); } if (edit_key_is_pressed(KEY_SELECT)) key_select_pressed(); if (edit_key_is_pressed(KEY_UP)) key_up_pressed(); if (edit_key_is_pressed(KEY_DOWN)) key_down_pressed(); irq_disable(); need_lcd_update = update_lcd_request; update_lcd_request = 0; irq_enable(); if (need_lcd_update) update_lcd(); } }