/* Firmware for Ferrero Rocher driver * and other attiny13a-based e-switch lights. * * Copyright (C) 2015 Selene Scriven * * 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 3 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * * ATTINY13A Diagram * ---- * -|1 8|- VCC * E-switch -|2 7|- Voltage ADC * Red LED -|3 6|- PWM * GND -|4 5|- Green LED * ---- */ #define F_CPU 4800000UL // PWM Mode #define PHASE 0b00000001 #define FAST 0b00000011 #define PWM_MODE FAST // PWM mode/speed: PHASE (9 kHz) or FAST (18 kHz) // (FAST has side effects when PWM=0, can't // shut off light without putting the MCU to sleep) // (PHASE might make audible whining sounds) // PFM not supported in this firmware, don't uncomment //#define USE_PFM // comment out to disable pulse frequency modulation // (makes bottom few modes ramp smoother) /* * ========================================================================= * Settings to modify per driver */ #define BLINK_ON_POWER // blink once when power is received // (helpful on e-switch lights, annoying on dual-switch lights) #define VOLTAGE_MON // Comment out to disable all voltage-related functions: // (including ramp down and eventual shutoff when battery is low) #define REDGREEN_INDICATORS // comment out to disable red/green power indicator LEDs #define LOWPASS_VOLTAGE // Average the last 4 voltage readings for smoother results // (comment out to use only one value, saves space) // Switch handling #define LONG_PRESS_DUR 21 // How many WDT ticks until we consider a press a long press // 32 is roughly .5 s, 21 is roughly 1/3rd second #define TICKS_PER_RAMP 32 // How many WDT ticks per step in the ramp (lower == faster ramp) #define LOCK_PRESS_DUR 188 // Hold for 3s from off to (un)lock light. #define RAMP_TIMEOUT 64 // un-reverse ramp dir about 1s after button release // Must be low to high, starting with 0 // (and the lowest values are highly device-dependent) #define MODES 0,1,5,23,65,139,255 #define TURBO // Comment out to disable - full output with a step down after n number of seconds // If turbo is enabled, it will be where 255 is listed in the modes above #define TURBO_TIMEOUT 5625 // How many WTD ticks before before dropping down (.016 sec each) // 90 = 5625 // 120 = 7500 // NOTE: Voltage values are calibrated for the Ferrero Rocher F6-DD driver // (may need different values for nanjg/qlite drivers, but only a little different) #define ADC_42 184 // the ADC value we expect for 4.20 volts #define ADC_100 184 // the ADC value for 100% full (4.2V resting) #define ADC_75 175 // the ADC value for 75% full (4.0V resting) #define ADC_50 165 // the ADC value for 50% full (3.8V resting) #define ADC_25 151 // the ADC value for 25% full (3.5V resting) #define ADC_0 128 // the ADC value for 0% full (3.0V resting) #define VOLTAGE_FULL 170 // 3.9 V under load #define VOLTAGE_GREEN 156 // 3.6 V under load #define VOLTAGE_YELLOW 142 // 3.3 V under load #define VOLTAGE_RED 128 // 3.0 V under load #define ADC_LOW 124 // When do we start ramping down (2.9V) #define ADC_CRIT 114 // When do we shut the light off (2.7V) // these two are just for testing low-batt behavior w/ a CR123 cell //#define ADC_LOW 139 // When do we start ramping down //#define ADC_CRIT 138 // When do we shut the light off #define ADC_DELAY 188 // Delay in ticks between low-bat rampdowns (188 ~= 3s) #define OWN_DELAY // replace default _delay_ms() with ours, comment to disable /* * ========================================================================= */ #ifdef OWN_DELAY #include // Having own _delay_ms() saves some bytes AND adds possibility to use variables as input static void _delay_ms(uint16_t n) { while(n-- > 0) _delay_loop_2(950); } #else #include #endif #include #include #include #include #include #include //#include #define SWITCH_PIN PB3 // what pin the switch is connected to, which is Star 4 #define PWM_PIN PB1 #define VOLTAGE_PIN PB2 #define RED_PIN PB4 // pin 3 #define GREEN_PIN PB0 // pin 5 #define ADC_CHANNEL 0x01 // MUX 01 corresponds with PB2 #define ADC_DIDR ADC1D // Digital input disable bit corresponding with PB2 #define ADC_PRSCL 0x06 // clk/64 #define PWM_LVL OCR0B // OCR0B is the output compare register for PB1 #ifdef USE_PFM #define CEIL_LVL OCR0A // OCR0A is the number of "frames" per PWM loop #endif #define DB_REL_DUR 0b00001111 // time before we consider the switch released after // each bit of 1 from the right equals 16ms, so 0x0f = 64ms /* * The actual program * ========================================================================= */ /* * global variables */ PROGMEM const uint8_t modes[] = { MODES }; volatile int8_t mode_idx = 0; // FIXME: ramp_dir should be a define volatile int8_t ramp_dir = 1; volatile uint8_t locked = 0; uint8_t press_duration = 0; #ifdef LOWPASS_VOLTAGE uint8_t voltages[] = { VOLTAGE_FULL, VOLTAGE_FULL, VOLTAGE_FULL, VOLTAGE_FULL }; #endif // Debounced switch press value int is_pressed() { // Keep track of last switch values polled static uint8_t buffer = 0x00; // Shift over and tack on the latest value, 0 being low for pressed, 1 for pulled-up for released buffer = (buffer << 1) | ((PINB & (1 << SWITCH_PIN)) == 0); return (buffer & DB_REL_DUR); } void ramp() { if (locked) { return; } mode_idx += ramp_dir; // wrap around (can probably remove this) if (mode_idx <= 0) { mode_idx = sizeof(modes)-1; } // wrap around, skip moon mode if (mode_idx > sizeof(modes)-1) { mode_idx = 2; } } void prev_mode() { // only used for turbo step-down and low-voltage step-down... // if we were already at the lowest mode, shut off. if (mode_idx >= 1) { mode_idx -= 1; } } inline void PCINT_on() { // Enable pin change interrupts GIMSK |= (1 << PCIE); } inline void PCINT_off() { // Disable pin change interrupts GIMSK &= ~(1 << PCIE); } // Need an interrupt for when pin change is enabled to ONLY wake us from sleep. // All logic of what to do when we wake up will be handled in the main loop. EMPTY_INTERRUPT(PCINT0_vect); inline void WDT_on() { // Setup watchdog timer to only interrupt, not reset, every 16ms. cli(); // Disable interrupts wdt_reset(); // Reset the WDT WDTCR |= (1< 0 && press_duration < LONG_PRESS_DUR) { // Short press if ( mode_idx == 0 ) { // short press from off == restore saved mode mode_idx = saved_mode_idx; ontime_ticks = 255; // avoid the double-reverse on power-on doubleclick_ticks = 0; // so we can detect double clicks from off } else { if ((doubleclick_ticks < LONG_PRESS_DUR) && (mode_idx < sizeof(modes)-1)) { // double click from off is a shortcut to maximum // (or... triple-click while on also works) mode_idx = sizeof(modes) - 1; } else { // single click while on will turn the light off // remember the last-used mode saved_mode_idx = mode_idx; mode_idx = 0; } } } else if ( press_duration > 0 ) { // long press was just released // FIXME: omit this, no longer used } else { if (ontime_ticks == RAMP_TIMEOUT) { // FIXME: omit this, no longer used } #ifdef TURBO // Only do turbo check when switch isn't pressed //if (modes[mode_idx] == 255) { // takes more space if (mode_idx == sizeof(modes)-1) { turbo_ticks++; if (turbo_ticks > TURBO_TIMEOUT) { // Go to the previous mode prev_mode(); } } #endif // Only do voltage monitoring when the switch isn't pressed #ifdef VOLTAGE_MON // See if conversion is done if (ADCSRA & (1 << ADIF)) { #ifdef LOWPASS_VOLTAGE // Get an average of the past few readings for (i=0;i<3;i++) { voltages[i] = voltages[i+1]; } voltages[3] = ADCH; voltage = (voltages[0]+voltages[1]+voltages[2]+voltages[3]) >> 2; #else voltage = ADCH; #endif // See if voltage is lower than what we were looking for if (voltage < ((mode_idx == 1) ? ADC_CRIT : ADC_LOW)) { ++lowbatt_cnt; } else { lowbatt_cnt = 0; } #ifdef REDGREEN_INDICATORS if (voltage > VOLTAGE_GREEN) { // turn on green LED DDRB = (1 << PWM_PIN) | (1 << GREEN_PIN); PORTB |= (1 << GREEN_PIN); PORTB &= 0xff ^ (1 << RED_PIN); // red off } else if (voltage > VOLTAGE_YELLOW) { // turn on red+green LED (yellow) DDRB = (1 << PWM_PIN) | (1 << GREEN_PIN) | (1 << RED_PIN); // bright green + bright red //DDRB = (1 << PWM_PIN) | (1 << GREEN_PIN); // bright green + dim red PORTB |= (1 << GREEN_PIN) | (1 << RED_PIN); } else { // turn on red LED DDRB = (1 << PWM_PIN) | (1 << RED_PIN); PORTB |= (1 << RED_PIN); PORTB &= 0xff ^ (1 << GREEN_PIN); // green off } #endif } // See if it's been low for a while, and maybe step down if (lowbatt_cnt >= ADC_DELAY) { prev_mode(); lowbatt_cnt = 0; } // Make sure conversion is running for next time through ADCSRA |= (1 << ADSC); #endif } press_duration = 0; } } int main(void) { // Set all ports to input, and turn pull-up resistors on for the inputs we are using DDRB = 0x00; PORTB = (1 << SWITCH_PIN); // Set the switch as an interrupt for when we turn pin change interrupts on PCMSK = (1 << SWITCH_PIN); // Set PWM pin to output DDRB = (1 << PWM_PIN); // Set timer to do PWM for correct output pin and set prescaler timing TCCR0A = 0x20 | PWM_MODE; // phase corrected PWM is 0x21 for PB1, fast-PWM is 0x23 #ifdef USE_PFM // 0x08 is for variable-speed PWM TCCR0B = 0x08 | 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) #else TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) #endif // Turn features on or off as needed #ifdef VOLTAGE_MON ADC_on(); #else ADC_off(); #endif ACSR |= (1<<7); //AC off #ifdef BLINK_ON_POWER // blink once to let the user know we have power //TCCR0A = PHASE | 0b00100000; // Only use the normal output #ifdef USE_PFM CEIL_LVL = 255; #endif PWM_LVL = 255; _delay_ms(3); PWM_LVL = 0; _delay_ms(1); #endif // Enable sleep mode set to Power Down that will be triggered by the sleep_mode() command. set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_until_switch_press(); uint8_t last_mode_idx = 0; while(1) { // We will never leave this loop. The WDT will interrupt to check for switch presses and // will change the mode if needed. If this loop detects that the mode has changed, run the // logic for that mode while continuing to check for a mode change. if (mode_idx != last_mode_idx) { // The WDT changed the mode. PWM_LVL = pgm_read_byte(modes + mode_idx); last_mode_idx = mode_idx; _delay_ms(1); if (mode_idx == 0) { // Finish executing instructions for PWM level change // and/or voltage readout mode before shutdown. do { _delay_ms(1); } while (0); // FIXME: stay on for a while to catch doubleclicks // Go to sleep sleep_until_switch_press(); // FIXME: lock mode can result in fast PWM=0 staying lit // (MCU wakes up and stays awake, should go back to sleep) } } } return 0; // Standard Return Code }