/* STAR_momentary version 1.4 * * Changelog * * 1.0 Initial version * 1.1 Added ability for star 2 to change mode order * 1.2 Fix for newer version of AVR Studio * 1.3 Added support for dual PWM outputs and selection of PWM mode per output level * 1.4 Added ability to switch to a momentary mode * */ /* * NANJG 105C Diagram * --- * -| |- VCC * Star 4 -| |- Voltage ADC * Star 3 -| |- PWM * GND -| |- Star 2 * --- * * FUSES * I use these fuse settings * Low: 0x75 (4.8MHz CPU without 8x divider, 9.4kHz phase-correct PWM or 18.75kHz fast-PWM) * High: 0xff * * For more details on these settings, visit http://github.com/JCapSolutions/blf-firmware/wiki/PWM-Frequency * * STARS * Star 2 = Not used * Star 3 = H-L if connected, L-H if not * Star 4 = Switch input * * VOLTAGE * Resistor values for voltage divider (reference BLF-VLD README for more info) * Reference voltage can be anywhere from 1.0 to 1.2, so this cannot be all that accurate * * VCC * | * Vd (~.25 v drop from protection diode) * | * 1912 (R1 19,100 ohms) * | * |---- PB2 from MCU * | * 4701 (R2 4,700 ohms) * | * GND * * ADC = ((V_bat - V_diode) * R2 * 255) / ((R1 + R2 ) * V_ref) * 125 = ((3.0 - .25 ) * 4700 * 255) / ((19100 + 4700) * 1.1 ) * 121 = ((2.9 - .25 ) * 4700 * 255) / ((19100 + 4700) * 1.1 ) * * Well 125 and 121 were too close, so it shut off right after lowering to low mode, so I went with * 130 and 120 * * To find out what value to use, plug in the target voltage (V) to this equation * value = (V * 4700 * 255) / (23800 * 1.1) * */ #define F_CPU 4800000UL // PWM Mode #define PHASE 0b00000001 #define FAST 0b00000011 /* * ========================================================================= * Settings to modify per driver */ #define VOLTAGE_MON // Comment out to disable - ramp down and eventual shutoff when battery is low #define MODES 0,8,32,125,255 // Must be low to high, and must start with 0 #define ALT_MODES 0,2,32,125,255 // Must be low to high, and must start with 0, the defines the level for the secondary output. Comment out if no secondary output #define MODE_PWM 0,PHASE,FAST,FAST,FAST // Define one per mode above, 0 for phase-correct, 1 for fast-PWM #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 #define ADC_LOW 130 // When do we start ramping #define ADC_CRIT 120 // When do we shut the light off #define ADC_DELAY 188 // Delay in ticks between low-bat rampdowns (188 ~= 3s) #define MOM_ENTER_DUR 128 // .16ms each. Comment out to disable this feature #define MOM_EXIT_DUR 128 // .16ms each #define MOM_MODE_IDX 4 // The index of the mode to use in MODES above, starting at index of 0 /* * ========================================================================= */ #include #include #include #include #include #include #include //#include #define STAR3_PIN PB4 // If not connected, will cycle L-H. Connected, H-L #define SWITCH_PIN PB3 // what pin the switch is connected to, which is Star 4 #define PWM_PIN PB1 #define ALT_PWM_PIN PB0 #define VOLTAGE_PIN PB2 #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 #define ALT_PWM_LVL OCR0A // OCR0A is the output compare register for PB0 #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 // Switch handling #define LONG_PRESS_DUR 32 // How many WDT ticks until we consider a press a long press // 32 is roughly .5 s /* * The actual program * ========================================================================= */ /* * global variables */ const uint8_t modes[] = { MODES }; #ifdef ALT_MODES const uint8_t alt_modes[] = { ALT_MODES }; #endif const uint8_t mode_pwm[] = { MODE_PWM }; volatile uint8_t mode_idx = 0; volatile uint8_t press_duration = 0; volatile uint8_t low_to_high = 0; volatile uint8_t in_momentary = 0; // 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); } inline void next_mode() { if (++mode_idx >= sizeof(modes)) { // Wrap around mode_idx = 0; } } inline void prev_mode() { if (mode_idx == 0) { // Wrap around mode_idx = sizeof(modes) - 1; } else { --mode_idx; } } 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 (low_to_high) { next_mode(); } else { prev_mode(); } } else { // Only do turbo check when switch isn't pressed #ifdef TURBO if (modes[mode_idx] == 255) { 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 if (adc_ticks > 0) { --adc_ticks; } if (adc_ticks == 0) { // See if conversion is done if (ADCSRA & (1 << ADIF)) { // See if voltage is lower than what we were looking for if (ADCH < ((mode_idx == 1) ? ADC_CRIT : ADC_LOW)) { ++lowbatt_cnt; } else { lowbatt_cnt = 0; } } // See if it's been low for a while if (lowbatt_cnt >= 4) { prev_mode(); lowbatt_cnt = 0; // If we reach 0 here, main loop will go into sleep mode // Restart the counter to when we step down again adc_ticks = ADC_DELAY; } // 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) | (1 << STAR3_PIN); // Set the switch as an interrupt for when we turn pin change interrupts on PCMSK = (1 << SWITCH_PIN); // Set PWM pin to output #ifdef ALT_MODES DDRB = (1 << PWM_PIN) | (1 << ALT_PWM_PIN); #else DDRB = (1 << PWM_PIN); #endif // Set timer to do PWM for correct output pin and set prescaler timing //TCCR0A = 0x23; // phase corrected PWM is 0x21 for PB1, fast-PWM is 0x23 TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) // Turn features on or off as needed #ifdef VOLTAGE_MON ADC_on(); #else ADC_off(); #endif ACSR |= (1<<7); //AC off // Determine if we are going L-H, or H-L based on Star 3 if ((PINB & (1 << STAR3_PIN)) == 0) { // High to Low low_to_high = 0; } else { low_to_high = 1; } // 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. if (mode_idx > 0) { // No need to change the mode if we are just turning the light off // Check if the PWM mode is different if (mode_pwm[last_mode_idx] != mode_pwm[mode_idx]) { #ifdef ALT_MODES TCCR0A = mode_pwm[mode_idx] | 0b10100000; // Use both outputs #else TCCR0A = mode_pwm[mode_idx] | 0b00100000; // Only use the normal output #endif } } PWM_LVL = modes[mode_idx]; #ifdef ALT_MODES ALT_PWM_LVL = alt_modes[mode_idx]; #endif last_mode_idx = mode_idx; #ifdef ALT_MODES if (modes[mode_idx] == 0 && alt_modes[mode_idx] == 0) { #else if (modes[mode_idx] == 0) { #endif _delay_ms(1); // Need this here, maybe instructions for PWM output not getting executed before shutdown? // Go to sleep sleep_until_switch_press(); } } } return 0; // Standard Return Code }