/* * "Biscotti" firmware (attiny13a version of "Bistro") * This code runs on a single-channel driver with attiny13a MCU. * It is intended specifically for nanjg 105d drivers from Convoy. * * Copyright (C) 2017 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 . * * * ATTINY13 Diagram * ---- * -|1 8|- VCC * -|2 7|- Voltage ADC * -|3 6|- PWM (Nx7135) * GND -|4 5|- * ---- * * FUSES * (check bin/flash*.sh for recommended values) * * CALIBRATION * * 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. */ // Choose your MCU here, or in the build script //#define ATTINY 13 //#define ATTINY 25 // FIXME: make 1-channel vs 2-channel power a single #define option //#define FET_7135_LAYOUT // specify an I/O pin layout #define NANJG_LAYOUT // specify an I/O pin layout // Also, assign I/O pins in this file: #include "tk-attiny.h" /* * ========================================================================= * Settings to modify per driver */ #define VOLTAGE_MON // Comment out to disable LVP //#define OFFTIM3 // Use short/med/long off-time presses // instead of just short/long // ../../bin/level_calc.py 64 1 10 1300 y 3 0.23 140 #define RAMP_SIZE 7 // log curve //#define RAMP_7135 3,3,3,3,3,3,4,4,4,4,4,5,5,5,6,6,7,7,8,9,10,11,12,13,15,16,18,21,23,27,30,34,39,44,50,57,65,74,85,97,111,127,145,166,190,217,248,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 //#define RAMP_FET 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,6,11,17,23,30,39,48,59,72,86,103,121,143,168,197,255 // x**2 curve //#define RAMP_7135 3,5,8,12,17,24,32,41,51,63,75,90,105,121,139,158,178,200,223,247,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 //#define RAMP_FET 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,4,6,9,12,16,19,22,26,30,33,37,41,45,50,54,59,63,68,73,78,84,89,94,100,106,111,117,123,130,136,142,149,156,162,169,176,184,191,198,206,214,221,255 // x**3 curve //#define RAMP_7135 3,3,4,5,6,8,10,12,15,19,23,28,33,40,47,55,63,73,84,95,108,122,137,153,171,190,210,232,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 //#define RAMP_FET 6,12,34,108,255 // level_calc.py 1 4 7135 9 8 700 // level_calc.py 1 3 7135 9 8 700 #define RAMP_FET 1,7,32,63,107,127,255 // x**5 curve //#define RAMP_7135 3,3,3,4,4,5,5,6,7,8,10,11,13,15,18,21,24,28,33,38,44,50,57,66,75,85,96,108,122,137,154,172,192,213,237,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0 //#define RAMP_FET 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,6,9,13,17,21,25,30,35,41,47,53,60,67,75,83,91,101,111,121,132,144,156,169,183,198,213,255 // uncomment to ramp up/down to a mode instead of jumping directly //#define SOFT_START // Enable battery indicator mode? #define USE_BATTCHECK // Choose a battery indicator style #define BATTCHECK_4bars // up to 4 blinks //#define BATTCHECK_8bars // up to 8 blinks //#define BATTCHECK_VpT // Volts + tenths // output to use for blinks on battery check (and other modes) //#define BLINK_BRIGHTNESS RAMP_SIZE/4 #define BLINK_BRIGHTNESS 3 // ms per normal-speed blink #define BLINK_SPEED (750/4) // 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,STROBE,TURBO . //#define HIDDENMODES BATTCHECK,STROBE,TURBO #define TURBO RAMP_SIZE // Convenience code for turbo mode #define BATTCHECK 254 // Convenience code for battery check mode #define GROUP_SELECT_MODE 253 //#define TEMP_CAL_MODE 252 // Uncomment to enable tactical strobe mode #define ANY_STROBE // required for strobe or police_strobe //#define STROBE 251 // Convenience code for strobe mode // Uncomment to unable a 2-level stutter beacon instead of a tactical strobe #define BIKING_STROBE 250 // Convenience code for biking strobe mode // comment out to use minimal version instead (smaller) #define FULL_BIKING_STROBE //#define RAMP 249 // ramp test mode for tweaking ramp shape #define POLICE_STROBE 248 //#define RANDOM_STROBE 247 #define SOS 246 // thermal step-down //#define TEMPERATURE_MON // Calibrate voltage and OTC in this file: #include "tk-calibration.h" /* * ========================================================================= */ // Ignore a spurious warning, we did the cast on purpose #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" #include //#include #include #include #include //#include #include #define OWN_DELAY // Don't use stock delay functions. #define USE_DELAY_4MS #define USE_DELAY_S // Also use _delay_s(), not just _delay_ms() #include "tk-delay.h" #include "tk-voltage.h" #ifdef RANDOM_STROBE #include "tk-random.h" #endif /* * global variables */ // Config option variables //#define USE_FIRSTBOOT #ifdef USE_FIRSTBOOT #define FIRSTBOOT 0b01010101 uint8_t firstboot = FIRSTBOOT; // detect initial boot or factory reset #endif uint8_t modegroup; // which mode group (set above in #defines) #define enable_moon 0 // Should we add moon to the set of modes? #define reverse_modes 0 // flip the mode order? uint8_t memory; // mode memory, or not (set via soldered star) #ifdef OFFTIM3 uint8_t offtim3; // enable medium-press? #endif #ifdef TEMPERATURE_MON uint8_t maxtemp = 79; // temperature step-down threshold #endif #define muggle_mode 0 // simple mode designed for muggles // Other state variables uint8_t mode_override; // do we need to enter a special mode? uint8_t mode_idx; // current or last-used mode number uint8_t eepos; // counter for entering config mode // (needs to be remembered while off, but only for up to half a second) uint8_t fast_presses __attribute__ ((section (".noinit"))); uint8_t long_press __attribute__ ((section (".noinit"))); // total length of current mode group's array #ifdef OFFTIM3 uint8_t mode_cnt; #endif // number of regular non-hidden modes in current mode group uint8_t solid_modes; // number of hidden modes in the current mode group // (hardcoded because both groups have the same hidden modes) //uint8_t hidden_modes = NUM_HIDDEN; // this is never used //PROGMEM const uint8_t hiddenmodes[] = { HIDDENMODES }; // default values calculated by group_calc.py // Each group must be 8 values long, but can be cut short with a zero. #define NUM_MODEGROUPS 12 // don't count muggle mode PROGMEM const uint8_t modegroups[] = { 1, 2, 3, 5, 7, POLICE_STROBE, BIKING_STROBE, BATTCHECK, 1, 2, 3, 5, 7, 0, 0, 0, 7, 5, 3, 2, 1, 0, 0, 0, 2, 4, 7, POLICE_STROBE, BIKING_STROBE, BATTCHECK, SOS, 0, 2, 4, 7, 0, 0, 0, 0, 0, 7, 4, 2, 0, 0, 0, 0, 0, 1, 2, 3, 6, POLICE_STROBE, BIKING_STROBE, BATTCHECK, SOS, 1, 2, 3, 6, 0, 0, 0, 0, 6, 3, 2, 1, 0, 0, 0, 0, 2, 3, 5, 7, 0, 0, 0, 0, 7, 4, POLICE_STROBE,0,0, 0, 0, 0, 7, 0, }; uint8_t modes[8]; // make sure this is long enough... // Modes (gets set when the light starts up based on saved config values) //PROGMEM const uint8_t ramp_7135[] = { RAMP_7135 }; PROGMEM const uint8_t ramp_FET[] = { RAMP_FET }; #define WEAR_LVL_LEN (EEPSIZE/2) // must be a power of 2 void save_mode() { // save the current mode index (with wear leveling) uint8_t oldpos=eepos; eepos = (eepos+1) & (WEAR_LVL_LEN-1); // wear leveling, use next cell /* eepos ++; if (eepos > (EEPSIZE-4)) { eepos = 0; } */ eeprom_write_byte((uint8_t *)(eepos), mode_idx); // save current state eeprom_write_byte((uint8_t *)(oldpos), 0xff); // erase old state } //#define OPT_firstboot (EEPSIZE-1) #define OPT_modegroup (EEPSIZE-1) #define OPT_memory (EEPSIZE-2) //#define OPT_offtim3 (EEPSIZE-4) //#define OPT_maxtemp (EEPSIZE-5) #define OPT_mode_override (EEPSIZE-3) //#define OPT_moon (EEPSIZE-7) //#define OPT_revmodes (EEPSIZE-8) //#define OPT_muggle (EEPSIZE-9) void save_state() { // central method for writing complete state save_mode(); #ifdef USE_FIRSTBOOT eeprom_write_byte((uint8_t *)OPT_firstboot, firstboot); #endif eeprom_write_byte((uint8_t *)OPT_modegroup, modegroup); eeprom_write_byte((uint8_t *)OPT_memory, memory); #ifdef OFFTIM3 eeprom_write_byte((uint8_t *)OPT_offtim3, offtim3); #endif #ifdef TEMPERATURE_MON eeprom_write_byte((uint8_t *)OPT_maxtemp, maxtemp); #endif eeprom_write_byte((uint8_t *)OPT_mode_override, mode_override); //eeprom_write_byte((uint8_t *)OPT_moon, enable_moon); //eeprom_write_byte((uint8_t *)OPT_revmodes, reverse_modes); //eeprom_write_byte((uint8_t *)OPT_muggle, muggle_mode); } #ifndef USE_FIRSTBOOT inline void reset_state() { mode_idx = 0; modegroup = 0; mode_override = 0; save_state(); } #endif void restore_state() { uint8_t eep; #ifdef USE_FIRSTBOOT // check if this is the first time we have powered on eep = eeprom_read_byte((uint8_t *)OPT_firstboot); if (eep != FIRSTBOOT) { // not much to do; the defaults should already be set // while defining the variables above save_state(); return; } #else uint8_t first = 1; #endif // find the mode index data for(eepos=0; eepos= NUM_MODEGROUPS) reset_state(); #endif } inline void next_mode() { mode_idx += 1; if (mode_idx >= solid_modes) { // Wrap around, skipping the hidden modes // (note: this also applies when going "forward" from any hidden mode) // FIXME? Allow this to cycle through hidden modes? mode_idx = 0; } } #ifdef OFFTIM3 inline void prev_mode() { // simple mode has no reverse //if (muggle_mode) { return next_mode(); } if (mode_idx == solid_modes) { // If we hit the end of the hidden modes, go back to moon mode_idx = 0; } else if (mode_idx > 0) { // Regular mode: is between 1 and TOTAL_MODES mode_idx -= 1; } else { // Otherwise, wrap around (this allows entering hidden modes) mode_idx = mode_cnt - 1; } } #endif void count_modes() { /* * Determine how many solid and hidden modes we have. * * (this matters because we have more than one set of modes to choose * from, so we need to count at runtime) */ // copy config to local vars to avoid accidentally overwriting them in muggle mode // (also, it seems to reduce overall program size) //uint8_t my_modegroup = modegroup; //uint8_t my_enable_moon = enable_moon; //uint8_t my_reverse_modes = reverse_modes; // override config if we're in simple mode #if 0 if (muggle_mode) { my_modegroup = NUM_MODEGROUPS; my_enable_moon = 0; my_reverse_modes = 0; } #endif uint8_t *dest; //const uint8_t *src = modegroups + (my_modegroup<<3); const uint8_t *src = modegroups + (modegroup<<3); dest = modes; // Figure out how many modes are in this group //solid_modes = modegroup + 1; // Assume group N has N modes // No, how about actually counting the modes instead? // (in case anyone changes the mode groups above so they don't form a triangle) uint8_t count; for(count=0; (count<8) && pgm_read_byte(src); count++, src++ ) { *dest++ = pgm_read_byte(src); } solid_modes = count; // add moon mode (or not) if config says to add it #if 0 if (my_enable_moon) { modes[0] = 1; dest ++; } #endif // add regular modes //memcpy_P(dest, src, solid_modes); // add hidden modes //memcpy_P(dest + solid_modes, hiddenmodes, sizeof(hiddenmodes)); // final count #ifdef OFFTIM3 //mode_cnt = solid_modes + sizeof(hiddenmodes); mode_cnt = solid_modes; #endif #if 0 if (my_reverse_modes) { // TODO: yuck, isn't there a better way to do this? int8_t i; src += solid_modes; dest = modes; for(i=0; i