/*
* BLF EE A6 firmware (special-edition group buy light)
* This light uses a FET+1 style driver, with a FET on the main PWM channel
* for the brightest high modes and a single 7135 chip on the secondary PWM
* channel so we can get stable, efficient low / medium modes. It also
* includes a capacitor for measuring off time.
*
* 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 = mode memory if soldered, no memory by default
* 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.
*/
// 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
// Also, assign I/O pins in this file:
#include "../tk-attiny.h"
/*
* =========================================================================
* 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
#define OFFTIM3 // Use short/med/long off-time presses
// instead of just short/long
// comment out to use extended config mode instead of a solderable star
// (controls whether mode memory is on the star or if it's a setting in config mode)
//#define CONFIG_STARS
// 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
// Mode group 1
#define NUM_MODES1 7
// PWM levels for the big circuit (FET or Nx7135)
#define MODESNx1 0,0,0,7,56,137,255
// PWM levels for the small circuit (1x7135)
#define MODES1x1 3,20,110,255,255,255,0
// My sample: 6=0..6, 7=2..11, 8=8..21(15..32)
// Krono sample: 6=5..21, 7=17..32, 8=33..96(50..78)
// Manker2: 2=21, 3=39, 4=47, ... 6?=68
// PWM speed for each mode
#define MODES_PWM1 PHASE,FAST,FAST,FAST,FAST,FAST,PHASE
// Mode group 2
#define NUM_MODES2 4
#define MODESNx2 0,0,90,255
#define MODES1x2 20,230,255,0
#define MODES_PWM2 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,STROBE,TURBO .
#define NUM_HIDDEN 4
#define HIDDENMODES BIKING_STROBE,BATTCHECK,STROBE,TURBO
#define HIDDENMODES_PWM PHASE,PHASE,PHASE,PHASE
#define HIDDENMODES_ALT 0,0,0,0 // Zeroes, same length as NUM_HIDDEN
#define TURBO 255 // Convenience code for turbo mode
#define BATTCHECK 254 // Convenience code for battery check mode
// Uncomment to enable tactical strobe mode
#define STROBE 253 // Convenience code for strobe mode
// Uncomment to unable a 2-level stutter beacon instead of a tactical strobe
#define BIKING_STROBE 252 // Convenience code for biking strobe mode
// comment out to use minimal version instead (smaller)
#define FULL_BIKING_STROBE
#define NON_WDT_TURBO // enable turbo step-down without WDT
// 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 90
// 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
#define OWN_DELAY // Don't use stock delay functions.
#define USE_DELAY_MS // use _delay_ms()
#define USE_DELAY_S // Also use _delay_s(), not just _delay_ms()
#include "../tk-delay.h"
#define USE_BATTCHECK
//#define BATTCHECK_4bars
#define BATTCHECK_8bars
#define BLINK_SPEED 500
#include "../tk-voltage.h"
/*
* global variables
*/
// Config / state variables
uint8_t eepos = 0;
uint8_t memory = 0; // mode memory, or not (set via soldered star)
uint8_t modegroup = 0; // which mode group (set above in #defines)
uint8_t mode_idx = 0; // current or last-used mode number
// 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")));
// NOTE: Only '1' is known to work; -1 will probably break and is untested.
// In other words, short press goes to the next (higher) mode,
// medium press goes to the previous (lower) mode.
#define mode_dir 1
// total length of current mode group's array
uint8_t mode_cnt;
// 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
// Modes (gets set when the light starts up based on saved config values)
PROGMEM const uint8_t modesNx1[] = { MODESNx1, HIDDENMODES };
PROGMEM const uint8_t modesNx2[] = { MODESNx2, HIDDENMODES };
const uint8_t *modesNx; // gets pointed at whatever group is current
PROGMEM const uint8_t modes1x1[] = { MODES1x1, HIDDENMODES_ALT };
PROGMEM const uint8_t modes1x2[] = { MODES1x2, HIDDENMODES_ALT };
const uint8_t *modes1x;
PROGMEM const uint8_t modes_pwm1[] = { MODES_PWM1, HIDDENMODES_PWM };
PROGMEM const uint8_t modes_pwm2[] = { MODES_PWM2, HIDDENMODES_PWM };
const uint8_t *modes_pwm;
void save_state() { // central method for writing (with wear leveling)
// a single 16-bit write uses less ROM space than two 8-bit writes
uint8_t eep;
uint8_t oldpos=eepos;
eepos = (eepos+1) & (EEPSIZE-1); // wear leveling, use next cell
#ifdef CONFIG_STARS
eep = mode_idx | (modegroup << 5);
#else
eep = mode_idx | (modegroup << 5) | (memory << 6);
#endif
eeprom_write_byte((uint8_t *)(eepos), eep); // save current state
eeprom_write_byte((uint8_t *)(oldpos), 0xff); // erase old state
}
void restore_state() {
uint8_t eep;
// find the config data
for(eepos=0; eepos> 5) & 1;
#ifndef CONFIG_STARS
memory = (eep >> 6) & 1;
#endif
}
// 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;
if (mode_idx >= solid_modes) {
// Wrap around, skipping the hidden modes
// (note: this also applies when going "forward" from any hidden mode)
mode_idx = 0;
}
}
#ifdef OFFTIM3
inline void prev_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
#ifdef CONFIG_STARS
inline void check_stars() {
// Configure options based on stars
// 0 being low for soldered, 1 for pulled-up for not soldered
#if 0 // not implemented, STAR2_PIN is used for second PWM channel
// Moon
// enable moon mode?
if ((PINB & (1 << STAR2_PIN)) == 0) {
modes[mode_cnt++] = MODE_MOON;
}
#endif
#if 0 // Mode order not as important as mem/no-mem
// Mode order
if ((PINB & (1 << STAR3_PIN)) == 0) {
// High to Low
mode_dir = -1;
} else {
mode_dir = 1;
}
#endif
// Memory
if ((PINB & (1 << STAR3_PIN)) == 0) {
memory = 1; // solder to enable memory
} else {
memory = 0; // unsolder to disable memory
}
}
#endif // ifdef CONFIG_STARS
void count_modes() {
/*
* Determine how many solid and hidden modes we have.
* The modes_pwm array should have several values for regular modes
* then some values for hidden modes.
*
* (this matters because we have more than one set of modes to choose
* from, so we need to count at runtime)
*/
if (modegroup == 0) {
solid_modes = NUM_MODES1;
modesNx = modesNx1;
modes1x = modes1x1;
modes_pwm = modes_pwm1;
} else {
solid_modes = NUM_MODES2;
modesNx = modesNx2;
modes1x = modes1x2;
modes_pwm = modes_pwm2;
}
mode_cnt = solid_modes + NUM_HIDDEN;
}
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(uint8_t mode) {
TCCR0A = pgm_read_byte(modes_pwm + mode);
set_output(pgm_read_byte(modesNx + mode), pgm_read_byte(modes1x + mode));
/*
// Only set output for solid modes
uint8_t out = pgm_read_byte(modesNx + mode);
if ((out < 250) || (out == 255)) {
set_output(pgm_read_byte(modesNx + mode), pgm_read_byte(modes1x + mode));
}
*/
}
void blink(uint8_t val)
{
for (; val>0; val--)
{
set_output(BLINK_BRIGHTNESS);
_delay_ms(BLINK_SPEED / 5);
set_output(0,0);
_delay_ms(BLINK_SPEED * 4 / 5);
}
}
#ifndef CONFIG_STARS
void toggle(uint8_t *var) {
// Used for extended config mode
// Changes the value of a config option, waits for the user to "save"
// by turning the light off, then changes the value back in case they
// didn't save. Can be used repeatedly on different options, allowing
// the user to change and save only one at a time.
*var ^= 1;
save_state();
blink(2);
*var ^= 1;
save_state();
_delay_s();
}
#endif // ifndef CONFIG_STARS
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 << V_REF) | (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
#ifdef CONFIG_STARS
// 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);
#endif
// 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
#ifdef CONFIG_STARS
check_stars();
#endif
restore_state();
// Enable the current mode group
count_modes();
// memory decayed, reset it
// (should happen on med/long press instead
// because mem decay is *much* slower when the OTC is charged
// so let's not wait until it decays to reset it)
//if (fast_presses > 0x20) { fast_presses = 0; }
if (cap_val > CAP_SHORT) {
// We don't care what the value is as long as it's over 15
fast_presses = (fast_presses+1) & 0x1f;
// 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) {
fast_presses = 0;
// User did a medium press, go back one mode
prev_mode(); // Will handle "negative" modes and wrap-arounds
#endif
} else {
// Long press, keep the same mode
// ... or reset to the first mode
fast_presses = 0;
if (! memory) {
// Reset to the first mode
mode_idx = 0;
}
}
save_state();
// Turn off ADC
//ADC_off();
// 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
// Enable sleep mode set to Idle that will be triggered by the sleep_mode() command.
// Will allow us to go idle between WDT interrupts
//set_sleep_mode(SLEEP_MODE_IDLE); // not used due to blinky modes
uint8_t output;
#ifdef NON_WDT_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);
if (fast_presses > 0x0f) { // Config mode
_delay_s(); // wait for user to stop fast-pressing button
fast_presses = 0; // exit this mode after one use
mode_idx = 0;
#ifdef CONFIG_STARS
// Short/small version of the config mode
// Toggle the mode group, blink, then exit
modegroup ^= 1;
save_state();
count_modes(); // reconfigure without a power cycle
blink(1);
#else
// Longer/larger version of the config mode
// Toggle the mode group, blink, un-toggle, continue
toggle(&modegroup);
// Toggle memory, blink, untoggle, exit
toggle(&memory);
#endif // ifdef CONFIG_STARS
}
#ifdef STROBE
else if (output == STROBE) {
// 10Hz tactical strobe
set_output(255,0);
_delay_ms(50);
set_output(0,0);
_delay_ms(50);
}
#endif // ifdef STROBE
#ifdef BIKING_STROBE
else if (output == BIKING_STROBE) {
// 2-level stutter beacon for biking and such
#ifdef FULL_BIKING_STROBE
// normal version
for(i=0;i<4;i++) {
set_output(255,0);
_delay_ms(5);
set_output(0,255);
_delay_ms(65);
}
_delay_ms(720);
#else
// small/minimal version
set_output(255,0);
_delay_ms(10);
set_output(0,255);
_delay_s();
#endif
}
#endif // ifdef BIKING_STROBE
#ifdef BATTCHECK
else if (output == BATTCHECK) {
// blink zero to five times to show voltage
// (~0%, ~25%, ~50%, ~75%, ~100%, >100%)
blink(battcheck());
// wait between readouts
_delay_s(); _delay_s();
}
#endif // ifdef BATTCHECK
else { // Regular non-hidden solid mode
set_mode(mode_idx);
// This part of the code will mostly replace the WDT tick code.
#ifdef NON_WDT_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)) {
mode_idx = solid_modes - 2; // step down to second-highest mode
set_mode(mode_idx);
save_state();
}
#endif
// Otherwise, just sleep.
_delay_ms(500);
// If we got this far, the user has stopped fast-pressing.
// So, don't enter config mode.
fast_presses = 0;
}
#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
// If we got this far, the user has stopped fast-pressing.
// So, don't enter config mode.
//fast_presses = 0; // doesn't interact well with strobe, too fast
}
//return 0; // Standard Return Code
}