2
* This is intended for use on flashlights with a clicky switch and off-time
3
* memory capacitor. Ideally, a triple XP-L powered by a BLF17DD in a Sinner
4
* Cypreus tri-EDC host. It's mostly based on JonnyC's STAR firmware and
5
* ToyKeeper's s7.c firmware.
7
* Original author: JonnyC
8
* Modifications: ToyKeeper / Selene Scriven
13
* mem cap -|2 7|- Voltage ADC
18
* CPU speed is 4.8Mhz without the 8x divider when low fuse is 0x75
20
* define F_CPU 4800000 CPU: 4.8MHz PWM: 9.4kHz ####### use low fuse: 0x75 #######
21
* /8 PWM: 1.176kHz ####### use low fuse: 0x65 #######
22
* define F_CPU 9600000 CPU: 9.6MHz PWM: 19kHz ####### use low fuse: 0x7a #######
23
* /8 PWM: 2.4kHz ####### use low fuse: 0x6a #######
25
* Above PWM speeds are for phase-correct PWM. This program uses Fast-PWM,
26
* which when the CPU is 4.8MHz will be 18.75 kHz
29
* I use these fuse settings
36
* Resistor values for voltage divider (reference BLF-VLD README for more info)
37
* Reference voltage can be anywhere from 1.0 to 1.2, so this cannot be all that accurate
41
* Vd (~.25 v drop from protection diode)
43
* 1912 (R1 19,100 ohms)
47
* 4701 (R2 4,700 ohms)
51
* ADC = ((V_bat - V_diode) * R2 * 255) / ((R1 + R2 ) * V_ref)
52
* 125 = ((3.0 - .25 ) * 4700 * 255) / ((19100 + 4700) * 1.1 )
53
* 121 = ((2.9 - .25 ) * 4700 * 255) / ((19100 + 4700) * 1.1 )
55
* Well 125 and 121 were too close, so it shut off right after lowering to low mode, so I went with
58
* To find out what value to use, plug in the target voltage (V) to this equation
59
* value = (V * 4700 * 255) / (23800 * 1.1)
62
#define F_CPU 4800000UL
67
* =========================================================================
68
* Settings to modify per driver
71
#define VOLTAGE_MON // Comment out to disable all battery monitoring
72
#define OWN_DELAY // Should we use the built-in delay or our own?
73
#define USE_PFM // Use PFM to make moon mode brighter
74
// Obsoleted; use moon_ceilings below instead
75
//#define MOON_PFM_LVL 30 // lower is brighter, 255 is max (same as no PFM)
76
// NOTE: WDT is required for on-time memory and WDT-based turbo step-down
77
// NOTE: WDT isn't tested, and probably doesn't work
78
//#define ENABLE_WDT // comment out to turn off WDT and save space
79
#define NON_WDT_TURBO // enable turbo step-down without WDT
80
#define TURBO_TIMEOUT 4000 // "ticks" until turbo step-down (~0.005s each)
81
// (maximum is 65535, or 127 seconds)
82
// Remove me: (maximum is 254, or 127 seconds)
83
#define TURBO_MODES 2 // Treat top N modes as "turbo" modes with timed step-down
84
// (1 for just highest, 2 for two highest, etc)
86
// Set your PWM levels here (low to high, maximum 255)
87
// 1,8,39,120,255 for 5-step phase-correct PWM
88
#define MODE_MOON 0 // can use PFM to fine-tune brightness
89
#define MODE_LOW 1 // lowest normal mode, ~0.4% / ~12 lm
90
#define MODE_MED 8 // ~3.1% / ~94 lm
91
#define MODE_HIGH 42 // ~16.5% / ~494 lm
92
#define MODE_HIGHER 120 // ~47% / ~1411 lm
93
#define MODE_MAX 255 // direct drive, ~3000lm
94
// If you change these, you'll probably want to change the "modes" array below
95
// Each value must be cumulative, so include the value just above it.
96
// How many non-blinky modes will we have?
98
// How many beacon modes will we have (with background light on)?
99
#define DUAL_BEACON_MODES 3+SOLID_MODES
100
// How many beacon modes will we have (without background light on)?
101
#define SINGLE_BEACON_MODES 1+DUAL_BEACON_MODES
102
// How many constant-speed strobe modes?
103
#define FIXED_STROBE_MODES 3+SINGLE_BEACON_MODES
104
// How many variable-speed strobe modes?
105
#define VARIABLE_STROBE_MODES 1+FIXED_STROBE_MODES
106
// battery check mode index
107
#define BATT_CHECK_MODE 1+VARIABLE_STROBE_MODES
108
// Note: don't use more than 32 modes,
109
// or it will interfere with the mechanism used for mode memory
110
#define TOTAL_MODES BATT_CHECK_MODE
112
// NOTE: mode isn't saved this way, this value only applies to on-time memory
114
#define WDT_TIMEOUT 2 // Number of WTD ticks before mode is saved (.5 sec each)
117
// These values were measured using a Ferrero Rocher F6DD driver and a DMM
118
// Your mileage may vary. May be off by up to 0.1V or so on different hardware.
119
#define ADC_42 185 // the ADC value we expect for 4.20 volts
120
#define VOLTAGE_FULL 171 // 3.9 V, 4 blinks
121
#define VOLTAGE_GREEN 156 // 3.6 V, 3 blinks
122
#define VOLTAGE_YELLOW 141 // 3.3 V, 2 blinks
123
#define VOLTAGE_RED 126 // 3.0 V, 1 blink
124
#define ADC_LOW 125 // When do we start ramping down
125
#define ADC_CRIT 115 // When do we shut the light off
126
// these two are just for testing low-batt behavior w/ a CR123 cell
127
//#define ADC_LOW 139 // When do we start ramping down
128
//#define ADC_CRIT 138 // When do we shut the light off
129
// These were JonnyC's original values
130
//#define ADC_LOW 130 // When do we start ramping
131
//#define ADC_CRIT 120 // When do we shut the light off
133
// Values between 1 and 255 corresponding with cap voltage (0 - 1.1v) where we
134
// consider it a short press to move to the next mode.
135
// Not sure the lowest you can go before getting bad readings, but with a value
136
// of 70 and a 1uF cap, it seemed to switch sometimes even when waiting 10
137
// seconds between presses.
138
#define CAP_SHORT 190 // Above this is a "short" press
139
#define CAP_MED 100 // Above this is a "medium" press
140
// ... and anything below that is a "long" press
142
#define CAP_CHANNEL 0x03 // MUX 03 corresponds with PB3 (Star 4)
143
#define CAP_DIDR ADC3D // Digital input disable bit corresponding with PB3
147
* =========================================================================
151
#include <util/delay_basic.h>
152
// Having own _delay_ms() saves some bytes AND adds possibility to use variables as input
153
static void _delay_ms(uint16_t n)
155
// TODO: make this take tenths of a ms instead of ms,
156
// for more precise timing?
157
// (would probably be better than the if/else here for a special-case
158
// sub-millisecond delay)
159
if (n==0) { _delay_loop_2(400); }
166
#include <util/delay.h>
169
#include <avr/interrupt.h>
173
#include <avr/pgmspace.h>
174
#include <avr/eeprom.h>
175
#include <avr/sleep.h>
177
#define STAR2_PIN PB0
178
#define STAR3_PIN PB4
179
#define STAR4_PIN PB3
181
#define VOLTAGE_PIN PB2
182
#define ADC_CHANNEL 0x01 // MUX 01 corresponds with PB2
183
#define ADC_DIDR ADC1D // Digital input disable bit corresponding with PB2
184
#define ADC_PRSCL 0x06 // clk/64
186
#define PWM_LVL OCR0B // OCR0B is the output compare register for PB1
188
#define CEIL_LVL OCR0A // OCR0A is the number of "frames" per PWM loop
198
// change to 1 if you want on-time mode memory instead of "short-cycle" memory
199
// NOTE: Not currently implemented; leave it at 0.
202
// Modes (hardcoded to save space, const also to save space)
203
const uint8_t modes[] = {
204
// regular solid modes
205
MODE_MOON, MODE_LOW, MODE_MED, MODE_HIGH, MODE_HIGHER, MODE_MAX,
206
// dual beacon modes (this level and this level + 2)
207
MODE_MOON, MODE_LOW, MODE_MED,
210
// constant-speed strobe modes (12.5 Hz, 24 Hz, 60 Hz)
211
// these values represent delay in ms between 1ms flashes
213
// variable-speed strobe mode
215
// battery check mode
218
// Semi-hidden modes, only accessible via a "backward" press from first mode.
219
// Each value is an index into the modes[] array.
220
const uint8_t neg_modes[] = {
221
SOLID_MODES-1, // Turbo / "impress" mode
222
FIXED_STROBE_MODES-2, // 24Hz strobe
223
BATT_CHECK_MODE-1, // Battery check
225
PROGMEM const uint8_t moon_ceilings[] = {
226
(VOLTAGE_FULL+ADC_42)/2, 160, // > 4.05V
227
VOLTAGE_FULL, 60, // > 3.9V
228
(VOLTAGE_YELLOW+VOLTAGE_FULL)/2, 30, // > 3.75V
229
VOLTAGE_YELLOW, 5, // > 3.6V
232
PROGMEM const uint8_t voltage_blinks[] = {
233
VOLTAGE_RED, // 1 blink
234
VOLTAGE_YELLOW, // 2 blinks
235
VOLTAGE_GREEN, // 3 blinks
236
VOLTAGE_FULL, // 4 blinks
239
volatile uint8_t mode_idx = 0;
240
// 1 or -1. Do we increase or decrease the idx when moving up to a higher mode?
241
// Was set by checking stars in the original STAR firmware, but that's removed
243
// NOTE: Only '1' is known to work; -1 will probably break and is untested.
246
void store_mode_idx() { //central method for writing (with wear leveling)
247
uint8_t oldpos=eepos;
248
eepos=(eepos+1)&31; //wear leveling, use next cell
249
// Write the current mode
250
EEARL=eepos; EEDR=mode_idx; EECR=32+4; EECR=32+4+2; //WRITE //32:write only (no erase) 4:enable 2:go
251
while(EECR & 2); //wait for completion
252
// Erase the last mode
253
EEARL=oldpos; EECR=16+4; EECR=16+4+2; //ERASE //16:erase only (no write) 4:enable 2:go
255
inline void read_mode_idx() {
256
eeprom_read_block(&eep, 0, 32);
257
while((eep[eepos] == 0xff) && (eepos < 32)) eepos++;
258
if (eepos < 32) mode_idx = eep[eepos];
262
inline void next_mode() {
263
mode_idx += mode_dir;
264
if (mode_idx > (TOTAL_MODES - 1)) {
270
inline void prev_mode() {
271
if ((0x40 > mode_idx) && (mode_idx > 0)) {
272
// Regular mode: is between 1 and TOTAL_MODES
273
mode_idx -= mode_dir;
274
} else if ((mode_idx&0x3f) < sizeof(neg_modes)) {
275
// "Negative" mode (uses 0x40 bit to indicate "negative")
276
mode_idx = (mode_idx|0x40) + mode_dir;
277
// FIXME: should maybe change mode group instead?
279
// Otherwise, always reset to first mode
280
// (mode was too negative or otherwise out of range)
286
inline void WDT_on() {
287
// Setup watchdog timer to only interrupt, not reset, every 500ms.
288
cli(); // Disable interrupts
289
wdt_reset(); // Reset the WDT
290
WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
291
WDTCR = (1<<WDTIE) | (1<<WDP2) | (1<<WDP0); // Enable interrupt every 500ms
292
sei(); // Enable interrupts
295
inline void WDT_off()
297
cli(); // Disable interrupts
298
wdt_reset(); // Reset the WDT
299
MCUSR &= ~(1<<WDRF); // Clear Watchdog reset flag
300
WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
301
WDTCR = 0x00; // Disable WDT
302
sei(); // Enable interrupts
306
inline void ADC_on() {
307
DIDR0 |= (1 << ADC_DIDR); // disable digital input on ADC pin to reduce power consumption
308
ADMUX = (1 << REFS0) | (1 << ADLAR) | ADC_CHANNEL; // 1.1v reference, left-adjust, ADC1/PB2
309
ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL; // enable, start, prescale
312
inline void ADC_off() {
313
ADCSRA &= ~(1<<7); //ADC off
317
uint8_t get_voltage() {
319
ADCSRA |= (1 << ADSC);
320
// Wait for completion
321
while (ADCSRA & (1 << ADSC));
322
// Return the raw value, caller can decide what to do with it
329
// Even if this function does nothing,
330
// having the WDT enabled will at least wake up the main loop
331
// and force it to do voltage monitoring plus optional step-down
332
// (then again, doing a _delay_ms() instead of sleep() in the main loop
333
// can achieve the same purpose, and makes fast PWM=0 moon mode work)
334
static uint8_t ticks = 0;
335
if (ticks < 255) ticks++;
337
// do a turbo step-down (NOTE: untested)
338
if ((mode_idx == SOLID_MODES-TURBO_MODES) && (ticks > TURBO_TIMEOUT/100)) {
343
// FIXME: this is short-cycle memory, remove it
344
// (we use off-time memory instead)
345
if (ticks == WDT_TIMEOUT) {
349
// Reset the mode to the start for next time
350
uint8_t foo = mode_idx;
362
// All ports default to input, but turn pull-up resistors on for the stars
363
// (not the ADC input! Made that mistake already)
365
//PORTB = (1 << STAR2_PIN) | (1 << STAR3_PIN) | (1 << STAR4_PIN);
367
// Set PWM pin to output
368
DDRB = (1 << PWM_PIN);
370
// Set timer to do PWM for correct output pin and set prescaler timing
371
// defaulting to PHASE allows PWM=0 to mean "off"
372
// will override later for solid modes where PWM=0 means "moon"
375
// 0x08 is for variable-speed PWM
376
TCCR0B = 0x08 | 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
377
CEIL_LVL = 255; // default
379
TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
382
// Determine what mode we should fire up
383
// Read the last mode that was saved
386
// Start up ADC for capacitor pin
387
// disable digital input on ADC pin to reduce power consumption
388
DIDR0 |= (1 << CAP_DIDR);
389
// 1.1v reference, left-adjust, ADC3/PB3
390
ADMUX = (1 << REFS0) | (1 << ADLAR) | CAP_CHANNEL;
391
// enable, start, prescale
392
ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL;
394
// Wait for completion
395
while (ADCSRA & (1 << ADSC));
396
// Start again as datasheet says first result is unreliable
397
ADCSRA |= (1 << ADSC);
398
// Wait for completion
399
while (ADCSRA & (1 << ADSC));
400
if (ADCH > CAP_SHORT) {
401
// Indicates they did a short press, go to the next mode
402
next_mode(); // Will handle wrap arounds
404
} else if (ADCH > CAP_MED) {
405
// User did a medium press, go back one mode
406
prev_mode(); // Will handle "negative" modes and wrap-arounds
411
// Keep the same mode
413
// Reset to the first mode
421
// Charge up the capacitor by setting CAP_PIN to output
422
DDRB |= (1 << CAP_PIN); // Output
423
PORTB |= (1 << CAP_PIN); // High
425
// Turn features on or off as needed
429
//ADC_off(); // was already off
431
ACSR |= (1<<7); //AC off
434
// Enable sleep mode set to Idle that will be triggered by the sleep_mode() command.
435
// Will allow us to go idle between WDT interrupts
436
// (not necessary if we're staying awake during all modes, such as to
437
// enable fast PWM=0 for moon mode)
438
set_sleep_mode(SLEEP_MODE_IDLE);
440
// enable turbo step-down timer, if there is one
441
// also makes voltage monitor work, by interrupting sleep
445
// Convert the "negative" mode into its actual (positive) mode
446
//if (mode_idx < 0) {
447
// The 0x40 bit is used, because I had issues getting eeprom to store
448
// and retrieve negative values on a signed integer. So, I'm setting
449
// a "negative" bit manually.
450
if (mode_idx & 0x40) {
451
mode_idx = neg_modes[(mode_idx&0x3f)-1];
454
// Hey look, variable declarations after executable code
455
// (we must not be using a strict or ancient C compiler)
457
uint8_t j = 0; // only used for strobes
459
uint16_t ontime_ticks = 0;
461
uint8_t strobe_len = 0;
463
uint8_t lowbatt_cnt = 0;
465
// Make sure voltage reading is running for later
466
//ADCSRA |= (1 << ADSC);
467
// ... and prime the battery check for more accurate first reading
468
voltage = get_voltage();
472
if(mode_idx < SOLID_MODES) { // Just stay on at a given brightness
474
PWM_LVL = modes[mode_idx];
477
//CEIL_LVL = MOON_PFM_LVL;
478
voltage = get_voltage();
479
for (i=0; i<sizeof(moon_ceilings); i+=2) {
480
if(voltage > pgm_read_byte(moon_ceilings + i)) {
481
//CEIL_LVL = pgm_read_byte(moon_ceilings + i+1);
482
//CEIL_LVL = (CEIL_LVL + pgm_read_byte(moon_ceilings + i+1)) >> 2;
484
if (j < pgm_read_byte(moon_ceilings + i+1)) {
492
} /* else { // was already set to 255
497
if (modes[mode_idx] < 3) {
498
// use phase-correct for really low modes
499
TCCR0A = 0x21; // phase corrected PWM is 0x21 for PB1, fast-PWM is 0x23
503
// Saves a little power, but makes fast PWM=0 moon mode not work
506
_delay_ms(5); // can't sleep, fast PWM=0 will eat me
508
if (ontime_ticks < 65535) { ontime_ticks ++; }
509
if ((mode_idx >= SOLID_MODES-TURBO_MODES)
510
&& (ontime_ticks > TURBO_TIMEOUT)) {
511
// step down one level
513
// reset in case there's more than one level of turbo
515
// save, so we can short-press to go back up
520
} else if (mode_idx < DUAL_BEACON_MODES) { // two-level fast strobe pulse at about 1 Hz
523
PWM_LVL = modes[mode_idx-SOLID_MODES+2];
525
PWM_LVL = modes[mode_idx];
529
} else if (mode_idx < SINGLE_BEACON_MODES) { // heartbeat flasher
530
PWM_LVL = modes[SOLID_MODES-1];
534
PWM_LVL = modes[SOLID_MODES-1];
538
} else if (mode_idx < FIXED_STROBE_MODES) { // strobe mode, fixed-speed
540
// bigger, better-looking version
541
j = modes[mode_idx]; // look up only once, saves a few bytes
543
if (j < 50) { strobe_len = 0; }
544
// loop to make timing more consistent
545
// (voltage check messes with timing)
546
for(i=0; i<30; i++) {
547
PWM_LVL = modes[SOLID_MODES-1];
548
_delay_ms(strobe_len);
553
// minimal version to save space
554
PWM_LVL = modes[SOLID_MODES-1];
557
_delay_ms(modes[mode_idx]);
559
} else if (mode_idx == VARIABLE_STROBE_MODES-1) {
560
// strobe mode, smoothly oscillating frequency ~10 Hz to ~24 Hz
561
for(j=0; j<60; j++) {
562
PWM_LVL = modes[SOLID_MODES-1];
565
if (j<30) { strobe_len = j; }
566
else { strobe_len = 60-j; }
567
_delay_ms(2*(strobe_len+20));
569
} else if (mode_idx < BATT_CHECK_MODE) {
571
//PWM_LVL = MODE_MED; // brief flash at start of measurement
572
voltage = get_voltage();
573
// turn off and wait one second before showing the value
574
// (or not, uses extra space)
578
// division takes too much flash space
579
//voltage = (voltage-ADC_LOW) / (((ADC_42 - 15) - ADC_LOW) >> 2);
580
// a table uses less space than 5 logic clauses
581
for (i=0; i<sizeof(voltage_blinks); i++) {
582
if (voltage > pgm_read_byte(voltage_blinks + i)) {
587
// blink up to five times to show voltage
588
// (~0%, ~25%, ~50%, ~75%, ~100%, >100%)
589
for(i=0; i<blinks; i++) {
595
_delay_ms(2000); // wait at least 2 seconds between readouts
598
if (ADCSRA & (1 << ADIF)) { // if a voltage reading is ready
599
voltage = ADCH; // get_voltage();
600
// See if voltage is lower than what we were looking for
601
if (voltage < ((mode_idx <= 1) ? ADC_CRIT : ADC_LOW)) {
606
// See if it's been low for a while, and maybe step down
607
if (lowbatt_cnt >= 3) {
608
if (mode_idx >= SOLID_MODES) {
609
// step down from blinky modes to medium
611
} else if (mode_idx > 1) {
612
// step down from solid modes one at a time
613
// (ignore mode 0 because it's probably invisible anyway
614
// if the voltage is this low)
616
} else { // Already at the lowest mode
618
// Turn off the light
621
// Disable WDT so it doesn't wake us up
624
// Power down as many components as possible
625
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
630
// Wait at least 2 seconds before lowering the level again
631
_delay_ms(2000); // this will interrupt blinky modes
634
// Make sure conversion is running for next time through
635
ADCSRA |= (1 << ADSC);