#include #include #include #include #include "board.h" #include "setpoints.h" /*! ADMUX setting for selecting 1.1V reference */ #define ADC_REF_1V1 (2 << REFS0) /*! ADMUX setting for mains input voltage reading */ #define ADC_MUX_MAINS (ADC_REF_1V1 | 0x00) /*! ADMUX setting for solar input voltage reading */ #define ADC_MUX_SOLAR (ADC_REF_1V1 | 0x01) /*! ADMUX setting for battery input voltage reading */ #define ADC_MUX_BATT (ADC_REF_1V1 | 0x02) /*! ADMUX setting for temperature reading */ #define ADC_MUX_TEMP (ADC_REF_1V1 | 0x22) /*! * Macro for computing ADC measurements. This assumes the input to the * ADC pin is via a voltage divider made up of resistors R1 and R2, with * the input voltage applied across both resistors and the ADC measuring * across R2. * * @param mv Voltage in millivolts * @returns Approximate ADC reading */ # define ADC_READ(mv) ( \ (ADC_MAX * ((uint64_t)(mv)) * VDIV_R2) \ / \ (ADC_REF * (VDIV_R1 + VDIV_R2)) \ ) /* --- Thresholds --- */ #define V_H_ADC ADC_READ(V_H_MV) #define V_L_ADC ADC_READ(V_L_MV) #define V_CL_ADC ADC_READ(V_CL_MV) #define V_SOL_MIN_ADC ADC_READ(V_SOL_MIN_MV) /* --- Timeouts --- */ #define T_ADC_TICKS TIMER_TICKS(T_ADC_MS) #define T_LED_TICKS TIMER_TICKS(T_LED_MS) #define STATE_INIT (0) /*!< Initial start-up state */ #define STATE_SOLAR (1) /*!< Running from solar */ #define STATE_MAINS_CHG (2) /*!< Charging from mains */ #define STATE_MAINS_FLT (3) /*!< Floating on mains */ /*! * Charger state machine state. We have four states we can be in. */ static volatile uint8_t charger_state = STATE_INIT; /*! * For state machine, the last state of the ADC MUX so we know whether * to ignore the sample or not. Datasheet recommends discarding samples * to let things stabalise when switching sources/references. */ static volatile uint8_t last_admux = 0; /*! * Current reading of the battery voltage in ADC units. */ static volatile uint16_t v_bat_adc = 0; /*! * Current reading of the solar voltage in ADC units. */ static volatile uint16_t v_sol_adc = 0; /*! * Current reading of the internal temperature sensor in ADC units. */ static volatile uint16_t temp_adc = 0; /*! * One-second event timer */ static volatile uint16_t t_second = 0; /*! * How long before we next take a reading? */ static volatile uint16_t t_adc = 0; /*! * Battery voltage timeout, used for: * - low voltage timeout * - charge timeout * - float timeout */ static volatile uint16_t t_batt = 0; /*! * Fan kick-start timeout */ static volatile uint8_t t_fan = 0; /*! * LED blink interval */ static volatile uint8_t t_led = 0; /*! * HAL state */ static volatile uint8_t hal_state = 0; #define HAL_STATE_ADC_CHECKED (1 << 0) #define HAL_STATE_LED_BLINK_POL (1 << 7) /*! * LED blink state */ static volatile uint8_t led_blink = 0; /*! * Switch to charging from mains power. */ static void enter_mains_chg(void) { /* Reset timer */ t_batt = T_CHARGE_S; /* Enable mains power */ FET_PORT &= ~FET_MAINS; /* Indicate via LEDs */ if (charger_state == STATE_MAINS_FLT) { /* We have regressed from floating state */ led_blink &= ~LED_BATT_CHG; led_blink |= LED_BATT_FLT; } else { led_blink &= ~(LED_BATT_CHG | LED_BATT_FLT); } LED_PORT |= LED_BATT_CHG; LED_PORT &= ~LED_BATT_FLT; /* Enter state */ charger_state = STATE_MAINS_CHG; } /*! * Switch to floating on mains power. */ static void enter_mains_float(void) { /* Reset timer */ t_batt = T_FLOAT_S; /* Indicate via LEDs */ led_blink &= ~(LED_BATT_CHG | LED_BATT_FLT); LED_PORT &= ~LED_BATT_CHG; LED_PORT |= LED_BATT_FLT; /* Enter state */ charger_state = STATE_MAINS_FLT; } /*! * Switch to running on solar. */ static void enter_solar(void) { /* Inhibit mains */ FET_PORT |= FET_MAINS; /* Indicate via LEDs */ led_blink &= ~(LED_BATT_CHG | LED_BATT_FLT); LED_PORT &= ~(LED_BATT_FLT | LED_BATT_CHG); /* Enter state */ charger_state = STATE_SOLAR; } /*! * Checks at start-up */ static void init_check(void) { /* Wait until we have our first readings from the ADC */ if (!(hal_state & HAL_STATE_ADC_CHECKED)) return; if ( /* Battery is low */ (v_bat_adc < V_L_ADC) /* Solar voltage is low */ || (v_sol_adc < V_SOL_MIN_ADC) ) /* Battery/solar is low, begin charging */ enter_mains_chg(); else /* Run from solar */ enter_solar(); } /*! * Checks whilst running on solar */ static void solar_check(void) { if (v_bat_adc > V_L_ADC) { /* Battery is above low threshold, reset low timer */ t_batt = T_LOW_S; } else if ( /* Battery is low */ v_bat_adc < V_L_ADC ) { if ( /* Battery is critically low */ (v_bat_adc < V_CL_ADC) /* Battery is low for >T_LOW_S seconds */ || (!t_batt) /* Solar voltage is low */ || (v_sol_adc < V_SOL_MIN_ADC) ) { /* Move to mains power */ enter_mains_chg(); return; } else { /* * Blink charge LED to indicate we consider * the battery low. */ led_blink |= LED_BATT_CHG; } } } /*! * Checks whilst charging from mains */ static void mains_chg_check(void) { if ( /* Charger has been active for T_CHARGE_S seconds */ (!t_batt) /* Battery has reached the floating voltage */ && (v_bat_adc >= V_H_ADC) ) { /* We've reached the float voltage */ enter_mains_float(); return; } } /*! * Checks whilst floating on mains */ static void mains_float_check(void) { if (v_bat_adc < V_H_ADC) { /* We've regressed, go back to charging state! */ enter_mains_chg(); return; } else if ( /* Battery is high for ≥T_FLOAT_S seconds */ !t_batt ) { /* Solar can take it from here */ enter_solar(); } } /*! * Check the state of the solar input, and update the output state * accordingly. */ static void adc_check_solar(void) { /* * The "SOLAR" FET is no longer fitted, so this is more * an indication of whether we consider solar to be * "good enough". In short, it's just controlling the * LED where the MOSFET was now. */ if (v_sol_adc < V_SOL_MIN_ADC) FET_PORT |= FET_SOLAR; else FET_PORT &= ~FET_SOLAR; } /*! * Check the state of the temperature sensor and the fan. Adjust * motor PWM and LED outputs as required. */ static void adc_check_temp(void) { /* Temperature LED and Fan control */ if (t_fan) { /* Kick-start mode */ OCR0A = FAN_PWM_MAX; } else if (temp_adc > TEMP_MAX) { /* We're at the maximum temperature, FULL SPEED! */ OCR0A = FAN_PWM_MAX; led_blink &= (LED_TEMP_HIGH | LED_TEMP_LOW); LED_PORT &= ~LED_TEMP_LOW; LED_PORT |= LED_TEMP_HIGH; } else if (temp_adc > TEMP_MIN) { /* Scale fan speed linearly with temperature */ uint8_t pwm = (((temp_adc - TEMP_MIN) * FAN_PWM_MAX) / (TEMP_MAX - TEMP_MIN)); LED_PORT &= ~LED_TEMP_HIGH; if (OCR0A < FAN_PWM_MIN) { /* Enter kick-start mode */ t_fan = T_FAN_S; led_blink |= LED_TEMP_LOW; } else { led_blink &= ~LED_TEMP_LOW; LED_PORT |= LED_TEMP_LOW; if (pwm > FAN_PWM_MIN) { OCR0A = pwm; } else { OCR0A = FAN_PWM_MIN; } } } else { /* Turn fans off completely. */ OCR0A = 0; led_blink &= (LED_TEMP_HIGH | LED_TEMP_LOW); LED_PORT &= (LED_TEMP_HIGH | LED_TEMP_LOW); } } /*! * Check the battery voltage and update the battery LEDs accordingly. */ static void adc_check_batt(void) { /* Battery state LED control */ if (v_bat_adc <= V_CL_ADC) { /* Battery is critically low */ led_blink |= LED_BATT_GOOD; } else if (v_bat_adc <= V_L_ADC) { /* Battery is low */ led_blink &= ~LED_BATT_GOOD; LED_PORT &= ~LED_BATT_GOOD; } else { led_blink &= ~LED_BATT_GOOD; LED_PORT |= LED_BATT_GOOD; } } /*! * Check to see if a second has elapsed, if it has, tick down each of * the one-second resolution timers. */ static void loop_second_tasks(void) { if (!t_second) { /* One second passed, tick down the 1-second timers. */ t_second = TIMER_FREQ; if (t_batt) t_batt--; if (t_fan) t_fan--; } } /*! * Check to see if our ADC state machine has advanced, If so, * check the state of all our ADC-dependent inputs. */ static void loop_adc_tasks(void) { if (!t_adc) { t_adc = T_ADC_TICKS; ADCSRA |= (1 << ADEN) | (1 << ADSC); while(ADCSRA & (1 << ADEN)); adc_check_solar(); adc_check_temp(); adc_check_batt(); /* Charger control */ switch (charger_state) { case STATE_INIT: init_check(); break; case STATE_SOLAR: solar_check(); break; case STATE_MAINS_CHG: mains_chg_check(); break; case STATE_MAINS_FLT: mains_float_check(); break; default: charger_state = STATE_INIT; } } } /*! * Check to see if our LED control timer has elapsed. If so * blink the LEDs that have been selected for blinking. */ static void loop_led_tasks(void) { if (!t_led) { /* * Decide whether we're turning blinking LEDs on * or off. One bit keeps all blinking LEDs in phase. */ hal_state ^= HAL_STATE_LED_BLINK_POL; /* Apply that state to all selected LEDs */ if (hal_state & HAL_STATE_LED_BLINK_POL) LED_PORT |= led_blink; else LED_PORT &= ~led_blink; /* Reset timer */ t_led = T_LED_TICKS; } } /*! * Main entrypoint */ int main(void) { /* Configure LEDs */ LED_PORT_DDR_REG = LED_PORT_DDR_VAL; LED_PORT = 0; /* Configure MOSFETs */ FET_PORT_DDR_REG = FET_PORT_DDR_VAL; FET_PORT = FET_MAINS | FET_SOLAR; /* Turn on ADC and timers */ PRR &= ~((1 << PRTIM0) | (1 << PRTIM1) | (1 << PRADC)); /* Configure Timer0: Fan PWM */ TCCR0A = (1 << COM0A1) | (1 << WGM01) | (1 << WGM00); TCCR0B = (1 << CS00); OCR0A = 0; /* * Configure Timer1: TIMER_FREQ System tick timer * / baud rate generator for debug output */ TCCR1A = 0; TCCR1B = (1 << WGM12) | (1 << CS10); TCCR1C = 0; OCR1A = F_CPU/TIMER_FREQ; TIMSK1 = (1 << OCIE1A); /* ADC configuration */ DIDR0 = ADC_CH_EN; ADMUX = ADC_MUX_TEMP; ADCSRB = (1 << ADLAR); ADCSRA = (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); /* Start interrupts */ sei(); MCUSR = 0; /* Enter core loop */ while(1) { loop_second_tasks(); loop_adc_tasks(); loop_led_tasks(); } return 0; } ISR(TIM1_COMPA_vect) { /* One-second timer for longer events */ if (t_second) t_second--; /* ADC tick events */ if (t_adc) t_adc--; } ISR(ADC_vect) { uint16_t adc = ADCW; if (last_admux == ADMUX) { switch(last_admux) { case ADC_MUX_TEMP: temp_adc = adc; ADMUX = ADC_MUX_BATT; ADCSRA |= (1 << ADSC); break; case ADC_MUX_BATT: v_bat_adc = adc; ADMUX = ADC_MUX_SOLAR; ADCSRA |= (1 << ADSC); break; case ADC_MUX_SOLAR: v_sol_adc = adc; /* Once we get here, we've done a full cycle */ hal_state |= HAL_STATE_ADC_CHECKED; default: ADMUX = ADC_MUX_TEMP; ADCSRA &= ~(1 << ADEN); } } else { ADCSRA |= (1 << ADSC); last_admux = ADMUX; } }