/* * Generic clicky-switch-with-offtime-cap firmware. * Expects a FET+1 style driver, supports two independent power channels. * Similar to tk-otc.c but with extra blinkies. * (expects to be configured at compile-time, not runtime) * * 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 . * * * NANJG 105C Diagram * --- * -| |- VCC * OTC -| |- Voltage ADC * Star 3 -| |- PWM (FET) * GND -| |- PWM (1x7135) * --- * * FUSES * (check bin/flash*.sh for recommended values) * * STARS * Star 2 = second PWM output channel * Star 3 = not used * Star 4 = Capacitor for off-time * * 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 * * To find out what values to use, flash the driver with battcheck.hex * and hook the light up to each voltage you need a value for. This is * much more reliable than attempting to calculate the values from a * theoretical formula. * * Same for off-time capacitor values. Measure, don't guess. */ #define F_CPU 4800000UL /* * ========================================================================= * Settings to modify per driver */ //#define FAST 0x23 // fast PWM channel 1 only //#define PHASE 0x21 // phase-correct PWM channel 1 only #define FAST 0xA3 // fast PWM both channels #define PHASE 0xA1 // phase-correct PWM both channels #define VOLTAGE_MON // Comment out to disable LVP // Adjust the timing per-driver, since the hardware has high variance // Higher values will run slower, lower values run faster. #define DELAY_TWEAK 950 #define OFFTIM3 // Use short/med/long off-time presses // instead of just short/long // Mode group 1 // xp-g2 triple: moon (3) + ./level_calc.py 5 1 10 1950 y 20 8 140 // number of regular non-hidden modes #define SOLID_MODES 6 // PWM levels for the big circuit (FET or Nx7135) #define MODESNx 0,0,3,33,110,255 // PWM levels for the small circuit (1x7135) #define MODES1x 3,20,150,255,255,0 // PWM speed for each mode #define MODES_PWM PHASE,FAST,FAST,FAST,FAST,PHASE // Hidden modes are *before* the lowest (moon) mode, and should be specified // in reverse order. So, to go backward from moon to turbo to strobe to // battcheck, use BATTCHECK,STROBE1,TURBO . #define NUM_HIDDEN 4 #define HIDDENMODES BATTCHECK,STROBE2,BIKING_STROBE3,TURBO #define HIDDENMODES_PWM PHASE,PHASE,PHASE,PHASE #define HIDDENMODES_ALT 10,11,6,0 // short-press goes to this mode index //#define HIDDENMODES_ALT 6,7,12,0 // short-press goes to this mode index // Secondary hidden modes are technically between regular and hidden modes. // Specify them in their normal forward order. #define NUM_HIDDEN2 10 #define HIDDEN2 BIKING_STROBE1,BIKING_STROBE2,BIKING_STROBE3,BATTCHECK,HEART_BEACON,STROBE1,STROBE2,STROBE3,VAR_STROBE1,VAR_STROBE2 //#define HIDDEN2 HEART_BEACON,STROBE1,STROBE2,STROBE3,VAR_STROBE1,VAR_STROBE2,BIKING_STROBE1,BIKING_STROBE2,BIKING_STROBE3,BATTCHECK // The last one tells it where to go on short press from final hidden2 mode // (by default, battcheck loops back around to heart beacon) #define HIDDEN2_NEXT 0,0,0,0,0,0,0,0,0,SOLID_MODES // You probably want PHASE for each of these #define HIDDEN2_PWM PHASE,PHASE,PHASE,PHASE,PHASE,PHASE,PHASE,PHASE,PHASE,PHASE // Configure options for hidden modes // output to use for blinks on battery check mode (primary PWM level, alt PWM level) // Use 20,0 for a single-channel driver or 0,20 for a two-channel driver #define BLINK_BRIGHTNESS 0,20 // The values here will be used for both FET and 7135 channels // (the relative brightness of the blink will be the difference between channels) #define BIKING_STROBE1_LVL 4 // Dim biking strobe #define BIKING_STROBE2_LVL 25 // Med biking strobe #define BIKING_STROBE3_LVL 255 // Bright biking strobe // Strobe speeds are ontime,offtime in ms (or 0 for a third of a ms) //#define STROBE1_SPEED 50,50 // 10 Hz tactical strobe #define STROBE1_SPEED 1,79 // 12.5 Hz party strobe #define STROBE2_SPEED 0,41 // 24 Hz party strobe #define STROBE3_SPEED 0,15 // 60 Hz party strobe #define ENABLE_TURBO // enable turbo step-down without WDT // Any primary-channel PWM level above this will be treated as "turbo" #define TURBO_LEVEL 100 // How many timer ticks before before dropping down. // Each timer tick is 500ms, so "60" would be a 30-second stepdown. // Max value of 255 unless you change "ticks" #define TURBO_TIMEOUT 30 // These values were measured using wight's "A17HYBRID-S" driver built by DBCstm. // Your mileage may vary. #define ADC_42 176 // the ADC value we expect for 4.20 volts #define ADC_100 176 // the ADC value for 100% full (4.2V resting) #define ADC_75 166 // the ADC value for 75% full (4.0V resting) #define ADC_50 158 // the ADC value for 50% full (3.8V resting) #define ADC_25 145 // the ADC value for 25% full (3.5V resting) #define ADC_0 124 // the ADC value for 0% full (3.0V resting) #define ADC_LOW 116 // When do we start ramping down (2.8V) #define ADC_CRIT 112 // When do we shut the light off (2.7V) // the BLF EE A6 driver may have different offtime cap values than most other drivers // Values are between 1 and 255, and can be measured with offtime-cap.c // These #defines are the edge boundaries, not the center of the target. #ifdef OFFTIM3 #define CAP_SHORT 250 // Anything higher than this is a short press #define CAP_MED 190 // Between CAP_MED and CAP_SHORT is a medium press // Below CAP_MED is a long press #else #define CAP_SHORT 190 // Anything higher than this is a short press, lower is a long press #endif #define TURBO 255 // Convenience code for max turbo mode, must be 255 // Comment out each of the following when not used, to save space // Also, make sure each enabled item has a different value #define BATTCHECK 254 // Battery check mode #define BIKING_STROBE1 253 // Dim biking strobe #define BIKING_STROBE2 252 // Medium biking strobe #define BIKING_STROBE3 251 // Full-bright biking strobe #define STROBE1 250 // Party strobe 1 #define STROBE2 249 // Party strobe 2 #define STROBE3 248 // Party strobe 3 #define HEART_BEACON 247 // Heartbeat beacon #define VAR_STROBE1 246 // Variable-speed strobe #define VAR_STROBE2 245 // Variable-speed strobe /* * ========================================================================= */ // Ignore a spurious warning, we did the cast on purpose #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" #include // Having own _delay_ms() saves some bytes AND adds possibility to use variables as input void _delay_ms(uint16_t n) { // TODO: make this take tenths of a ms instead of ms, // for more precise timing? if (n==0) { _delay_loop_2(400); } else { while(n-- > 0) _delay_loop_2(DELAY_TWEAK); } } void _delay_s() // because it saves a bit of ROM space to do it this way { _delay_ms(1000); } #include //#include //#include #include #include //#include #define STAR2_PIN PB0 // But note that there is no star 2. #define STAR3_PIN PB4 #define CAP_PIN PB3 #define CAP_CHANNEL 0x03 // MUX 03 corresponds with PB3 (Star 4) #define CAP_DIDR ADC3D // Digital input disable bit corresponding with PB3 #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 /* * global variables */ // Config / state variables uint8_t eepos = 0; uint8_t mode_idx = 0; // current or last-used mode number // number of regular non-blinky non-hidden modes #define solid_modes SOLID_MODES // Number of solid and secondary hidden modes #define NUM_MODES SOLID_MODES+NUM_HIDDEN2 // total length of current mode group's array #define mode_cnt NUM_MODES+NUM_HIDDEN // Modes (hardcoded at compile time) PROGMEM const uint8_t modesNx[] = { MODESNx, HIDDEN2, HIDDENMODES }; PROGMEM const uint8_t modes1x[] = { MODES1x, HIDDEN2_NEXT, HIDDENMODES_ALT }; PROGMEM const uint8_t modes_pwm[] = { MODES_PWM, HIDDEN2_PWM, HIDDENMODES_PWM }; PROGMEM const uint8_t voltage_blinks[] = { ADC_0, // 1 blink for 0%-25% ADC_25, // 2 blinks for 25%-50% ADC_50, // 3 blinks for 50%-75% ADC_75, // 4 blinks for 75%-100% ADC_100, // 5 blinks for >100% 255, // Ceiling, don't remove }; void save_state() { // central method for writing (with wear leveling) // Only save the current mode number uint8_t eep; uint8_t oldpos=eepos; eepos = (eepos+1) & 63; // wear leveling, use next cell eep = mode_idx; eeprom_write_byte((uint8_t *)(eepos), eep); // save current state eeprom_write_byte((uint8_t *)(oldpos), 0xff); // erase old state } inline void restore_state() { uint8_t eep; // find the config data for(eepos=0; eepos<64; eepos++) { eep = eeprom_read_byte((const uint8_t *)eepos); if (eep != 0xff) break; } // unpack the config data if (eepos < 64) { mode_idx = eep; } // unnecessary, save_state handles wrap-around // (and we don't really care about it skipping cell 0 once in a while) //else eepos=0; } inline void next_mode() { mode_idx += 1; // Advance by one if (mode_idx == SOLID_MODES) { // wrap-around from turbo to moon mode_idx = 0; } else if (mode_idx >= NUM_MODES) { // if this mode is a hidden one, the "next" mode is whatever is // specified in modes1x at its index // (should be 0 for last non-hidden mode, // or whatever other mode makes sense otherwise) mode_idx = pgm_read_byte(modes1x + mode_idx-1); } } #ifdef OFFTIM3 inline void prev_mode() { if (mode_idx == NUM_MODES) { // hit the end of primary hidden modes mode_idx = 0; // back to moon } else if (mode_idx == SOLID_MODES) { // hit the end of secondary hidden modes mode_idx = NUM_MODES - 1; // loop secondary hidden modes } else if (mode_idx > 0) { // Regular non-moon mode mode_idx --; // go dimmer } else { // Moon goes back into the primary hidden modes mode_idx = mode_cnt - 1; } } #endif #ifdef VOLTAGE_MON inline void ADC_on() { DIDR0 |= (1 << ADC_DIDR); // disable digital input on ADC pin to reduce power consumption ADMUX = (1 << REFS0) | (1 << ADLAR) | ADC_CHANNEL; // 1.1v reference, left-adjust, ADC1/PB2 ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL; // enable, start, prescale } #else inline void ADC_off() { ADCSRA &= ~(1<<7); //ADC off } #endif void set_output(uint8_t pwm1, uint8_t pwm2) { // Need PHASE to properly turn off the light if ((pwm1==0) && (pwm2==0)) { TCCR0A = PHASE; } PWM_LVL = pwm1; ALT_PWM_LVL = pwm2; } void set_mode(mode) { TCCR0A = pgm_read_byte(modes_pwm + mode); set_output(pgm_read_byte(modesNx + mode), pgm_read_byte(modes1x + mode)); } #ifdef VOLTAGE_MON uint8_t get_voltage() { // Start conversion ADCSRA |= (1 << ADSC); // Wait for completion while (ADCSRA & (1 << ADSC)); // See if voltage is lower than what we were looking for return ADCH; } #endif inline void blink(uint8_t val) { for (; val>0; val--) { set_output(BLINK_BRIGHTNESS); _delay_ms(100); set_output(0,0); _delay_ms(400); } } void strobe(uint8_t ontime, uint8_t offtime) { set_output(255,0); _delay_ms(ontime); set_output(0,0); _delay_ms(offtime); } void bike_flash(uint8_t lvl) { uint8_t i; // 2-level stutter beacon for biking and such for(i=0;i<4;i++) { set_output(lvl, 0); _delay_ms(5); set_output(0, lvl); _delay_ms(65); } _delay_ms(720); } int main(void) { uint8_t cap_val; // Read the off-time cap *first* to get the most accurate reading // Start up ADC for capacitor pin DIDR0 |= (1 << CAP_DIDR); // disable digital input on ADC pin to reduce power consumption ADMUX = (1 << REFS0) | (1 << ADLAR) | CAP_CHANNEL; // 1.1v reference, left-adjust, ADC3/PB3 ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL; // enable, start, prescale // Wait for completion while (ADCSRA & (1 << ADSC)); // Start again as datasheet says first result is unreliable ADCSRA |= (1 << ADSC); // Wait for completion while (ADCSRA & (1 << ADSC)); cap_val = ADCH; // save this for later // All ports default to input, but turn pull-up resistors on for the stars (not the ADC input! Made that mistake already) // only one star, because one is used for PWM channel 2 // and the other is used for the off-time capacitor PORTB = (1 << STAR3_PIN); // Set PWM pin to output DDRB |= (1 << PWM_PIN); // enable main channel DDRB |= (1 << ALT_PWM_PIN); // enable second channel // 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...) TCCR0A = PHASE; // Set timer to do PWM for correct output pin and set prescaler timing TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...) // Read config values and saved state restore_state(); // idea for later: use memory decay instead of OTC for short / med press threshold? if (cap_val > CAP_SHORT) { // Indicates they did a short press, go to the next mode next_mode(); // Will handle wrap arounds #ifdef OFFTIM3 } else if (cap_val > CAP_MED) { // User did a medium press, go back one mode prev_mode(); // Will handle "negative" modes and wrap-arounds #endif } else { // Long press, reset to the first mode // (this UI is incompatible with mode memory) mode_idx = 0; } save_state(); // Charge up the capacitor by setting CAP_PIN to output DDRB |= (1 << CAP_PIN); // Output PORTB |= (1 << CAP_PIN); // High // Turn features on or off as needed #ifdef VOLTAGE_MON ADC_on(); #else ADC_off(); #endif //ACSR |= (1<<7); //AC off uint8_t output; #ifdef ENABLE_TURBO uint8_t ticks = 0; #endif #ifdef VOLTAGE_MON uint8_t lowbatt_cnt = 0; uint8_t i = 0; uint8_t voltage; // Make sure voltage reading is running for later ADCSRA |= (1 << ADSC); #endif while(1) { output = pgm_read_byte(modesNx + mode_idx); switch(output) { #ifdef STROBE1 case STROBE1: // 12.5 Hz party strobe strobe(STROBE1_SPEED); break; #endif #ifdef STROBE2 case STROBE2: // 24 Hz party strobe strobe(STROBE2_SPEED); break; #endif #ifdef STROBE3 case STROBE3: // 60 Hz party strobe strobe(STROBE3_SPEED); break; #endif #ifdef BIKING_STROBE1 case BIKING_STROBE1: // dim biking flasher bike_flash(BIKING_STROBE1_LVL); break; #endif #ifdef BIKING_STROBE2 case BIKING_STROBE2: // med biking flasher bike_flash(BIKING_STROBE2_LVL); break; #endif #ifdef BIKING_STROBE3 case BIKING_STROBE3: // max biking flasher bike_flash(BIKING_STROBE3_LVL); break; #endif #ifdef HEART_BEACON case HEART_BEACON: // Heartbeat beacon strobe(1,249); strobe(1,249); _delay_ms(500); break; #endif #ifdef VAR_STROBE1 case VAR_STROBE1: // slow variable-speed strobe // smoothly oscillating frequency ~7 Hz to ~18 Hz { uint8_t j, speed; for(j=0; j<66; j++) { if (j<33) { speed = j; } else { speed = 66-j; } strobe(1,(speed+33-6)<<1); } } break; #endif #ifdef VAR_STROBE2 case VAR_STROBE2: // fast variable-speed strobe // smoothly oscillating frequency ~16 Hz to ~100 Hz { uint8_t j, speed; for(j=0; j<100; j++) { if (j<50) { speed = j; } else { speed = 100-j; } strobe(0, speed+9); } } break; #endif #ifdef BATTCHECK case BATTCHECK: // battery check mode { // turn off and wait one second before showing the value // (also, ensure voltage is measured while not under load) set_output(0,0); _delay_s(); voltage = get_voltage(); //voltage = get_voltage(); // the first one is unreliable // one blink per voltage range for (i=0; voltage > pgm_read_byte(voltage_blinks + i); i ++) {} // blink zero to five times to show voltage // (~0%, ~25%, ~50%, ~75%, ~100%, >100%) blink(i); //_delay_s(); // wait at least 1 second between readouts } break; #endif default: // Regular non-hidden solid mode set_mode(mode_idx); // This part of the code will mostly replace the WDT tick code. #ifdef ENABLE_TURBO // Do some magic here to handle turbo step-down //if (ticks < 255) ticks++; // don't roll over ticks ++; // actually, we don't care about roll-over prevention if ((ticks > TURBO_TIMEOUT) && (output >= TURBO_LEVEL)) { if (mode_idx >= SOLID_MODES) { // handle step-down from hidden turbo mode_idx = SOLID_MODES - 2; } else { // step-down from non-hidden turbo(s) mode_idx = mode_idx - 1; // step down one level } ticks = 0; // in case we need to step down again set_mode(mode_idx); save_state(); } #endif // Otherwise, just sleep. _delay_ms(500); } #ifdef VOLTAGE_MON #if 1 if (ADCSRA & (1 << ADIF)) { // if a voltage reading is ready voltage = ADCH; // get_voltage(); // See if voltage is lower than what we were looking for //if (voltage < ((mode_idx <= 1) ? ADC_CRIT : ADC_LOW)) { if (voltage < ADC_LOW) { lowbatt_cnt ++; } else { lowbatt_cnt = 0; } // See if it's been low for a while, and maybe step down if (lowbatt_cnt >= 8) { // DEBUG: blink on step-down: //set_output(0,0); _delay_ms(100); i = mode_idx; // save space by not accessing mode_idx more than necessary // properly track hidden vs normal modes if (i >= solid_modes) { // step down from blinky modes to medium i = 2; } else if (i > 0) { // step down from solid modes one at a time i -= 1; } else { // Already at the lowest mode i = 0; // Turn off the light set_output(0,0); // Power down as many components as possible set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_mode(); } set_mode(i); mode_idx = i; save_state(); lowbatt_cnt = 0; // Wait at least 2 seconds before lowering the level again _delay_ms(250); // this will interrupt blinky modes } // Make sure conversion is running for next time through ADCSRA |= (1 << ADSC); } #endif #endif // ifdef VOLTAGE_MON //sleep_mode(); // incompatible with blinky modes } //return 0; // Standard Return Code }