3
* TheStar Off-time NANJG105C Firmware by _the_ (originally based on STAR 1.1 & STAR Off-time 1.3 by JonnyC)
6
* - Heavily optimized for size -> free up enough size for all the options
7
* - Star logic changed to match BLF preferences
8
* - Hidden modes (Strobe, 1s beacon, 10s alpine distress beacon, turbo that stays on..)
9
* - Combination of normal (for basic modes) and short cycle memory (for hidden modes)
10
* - High-to-low support rewritten (for size and supporting hidden modes)
11
* - Turbo step down to predefined PWM level (allows leaving the high away for simpler UI)
12
* - Ramp down smoothly during turbo step down to make it less annoying / noticeable
13
* - Voltage monitoring rewritten to consume less power and support blinky modes
18
* Star 4 -| |- Voltage ADC
23
* CPU speed is 4.8Mhz without the 8x divider when low fuse is 0x75
25
* define F_CPU 4800000 CPU: 4.8MHz PWM: 9.4kHz ####### use low fuse: 0x75 #######
26
* /8 PWM: 1.176kHz ####### use low fuse: 0x65 #######
27
* define F_CPU 9600000 CPU: 9.6MHz PWM: 19kHz ####### use low fuse: 0x7a #######
28
* /8 PWM: 2.4kHz ####### use low fuse: 0x6a #######
30
* Above PWM speeds are for phase-correct PWM. This program uses Fast-PWM, which when the CPU is 4.8MHz will be 18.75 kHz
33
* I use these fuse settings
38
* Star 2 = Memory if NOT connected
39
* Star 3 = H-L if connected, L-H if not
40
* Star 4 = Cap for off-time memory
43
* Resistor values for voltage divider (reference BLF-VLD README for more info)
44
* Reference voltage can be anywhere from 1.0 to 1.2, so this cannot be all that accurate
48
* Vd (~.25 v drop from protection diode)
50
* 1912 (R1 19,100 ohms)
54
* 4701 (R2 4,700 ohms)
58
* ADC = ((V_bat - V_diode) * R2 * 255) / ((R1 + R2 ) * V_ref)
59
* 125 = ((3.0 - .25 ) * 4700 * 255) / ((19100 + 4700) * 1.1 )
60
* 121 = ((2.9 - .25 ) * 4700 * 255) / ((19100 + 4700) * 1.1 )
62
* To find out what value to use, plug in the target voltage (V) to this equation
63
* value = (V * 4700 * 255) / (23800 * 1.1)
66
#define F_CPU 4800000UL
70
* =========================================================================
71
* Settings to modify per driver - consumed bytes are only for reference, they may change radically because of some other settings (and global optimization)
74
// Three basic modes by default, define this for four basic modes (lowlow included in main mode cycle) - consumes about 24 bytes more
77
// Things to do with star 2 - select one of the following
78
// Turbo timer is enabled by default, it can be turned off by soldering star 2 if this is enabled - consumes 22 bytes
79
// #define ENABLE_TURNING_OFF_TURBO_TIMER
81
// Light can be changed to tactical mode (strobe, turbo, low) + hidden goodies with star 2 - consumes 70 bytes
82
//#define ENABLE_TACTICAL_MODE
83
//#define TACTICAL_MODE_ON_BY_DEFAULT
85
// Light can be changed to single mode + hidden goodies with star 2 - consumes 18 bytes
86
//#define ENABLE_SINGLE_MODE
88
// Memory is on by default, it can be turned off by soldering star 2 if any of the above star 2 functionality is not enabled
90
// High-to-low mode order, selectable by star 3, consumes 72 bytes
91
#define ENABLE_HIGH_TO_LOW
92
//#define HIGH_TO_LOW_ON_BY_DEFAULT
94
// Normal strobe by default, define this for alternating - consumes 20 bytes
95
//#define NORMAL_STROBE
96
//#define ALTERNATING_STROBE // +18 bytes
97
#define RANDOM_STROBE // +40 bytes
99
// SOS mode - consumes 70 bytes
102
// Set of different beacons - consumes 76 to 84 bytes (4- / 3-modes)
103
#define ENABLE_BEACONS
105
// Voltage monitoring - consumes 180 bytes
106
#define ENABLE_VOLTAGE_MONITORING
108
// Some standard PWM values
109
#define PWM_OFF 0 // Used for signaling
110
#define PWM_MIN 7 // Less than this won't light up reliably :(
111
#define PWM_MAX 255 // Maximum brightness
113
// PWM values for modes
114
#define MODE_LOWLOW PWM_MIN // 2mA@2.8A 3mA@4.2A
115
//#define MODE_LOW 11 // 28mA@2.8A 38mA@4.2A
116
#define MODE_LOW 16 // 67mA@2.8A 100mA@4.2A
117
#define MODE_MED 70 // 0.66A@2.8A 1.0A@4.2A
118
#define MODE_HIGH 120 // 1.2A@2.8A 1.8A@4.2A
119
#define MODE_TURBO 252
120
#define MODE_TURBO_STAY_ON PWM_MAX
122
#define MODE_INDEX_TURBO_RAMPDOWN 0xf0
124
// Low light during the pauses in some beacons
125
#define PWM_BEACON_BACKGROUND MODE_MED // ~0.66A (@2.8A)
126
#define PWM_SLOW_BEACON_BACKGROUND MODE_LOW // ~0.3A (@2.8A)
128
// Turbo timeout - How many WTD ticks before before ramping down (0.5s each)
129
//#define TURBO_TIMEOUT 60 // 30s for hot rods
130
//#define TURBO_TIMEOUT 180 // 90s for normal usage
131
#define TURBO_TIMEOUT 120 // 60s for normal usage (@4.2A)
133
// Turbo stepdown PWM value (select one suitable for your light)
134
//#define PWM_TURBO_STEPDOWN 140 // ~1.4A (@2.8A)
135
#define PWM_TURBO_STEPDOWN 200 // ~2.1A (@2.8A)
136
//#define PWM_TURBO_STEPDOWN 140 // ~2.1A (@4.2A)
137
//#define PWM_TURBO_STEPDOWN 100 // ~1.4A (@4.2A)
139
// Special mode PWM values - Must be under PWM_MIN
140
#define MODE_STROBE 1
141
#define MODE_BEACON 2
142
#define MODE_ALPINE_DISTRESS_BEACON 3
144
#define MODE_BEACON_WITH_BACKGROUND 5
145
#define MODE_SLOW_BEACON_WITH_BACKGROUND 6
146
#define MODE_MOTION_STOPPING_STROBE 254
147
//#define MODE_MOTION_STOPPING_STROBE_SLOW 253
151
#define N_BASIC_MODES 4
152
#define BASIC_MODES MODE_LOWLOW, MODE_LOW, MODE_MED, MODE_TURBO
154
#define N_BASIC_MODES 3
155
#define BASIC_MODES MODE_LOW, MODE_MED, MODE_TURBO
160
# define N_HIDDEN_MODES 7
161
# define HIDDEN_MODES MODE_STROBE, MODE_BEACON_WITH_BACKGROUND, MODE_SLOW_BEACON_WITH_BACKGROUND, MODE_BEACON, MODE_ALPINE_DISTRESS_BEACON, MODE_SOS, MODE_TURBO_STAY_ON
163
# define N_HIDDEN_MODES 9
164
# define HIDDEN_MODES PWM_MIN, MODE_STROBE, MODE_MOTION_STOPPING_STROBE, MODE_BEACON_WITH_BACKGROUND, MODE_SLOW_BEACON_WITH_BACKGROUND, MODE_BEACON, MODE_ALPINE_DISTRESS_BEACON, MODE_SOS, MODE_TURBO_STAY_ON
167
// When to enter hidden modes?
169
# define HIDDEN_MODE_THRESHOLD 0x70
171
# define HIDDEN_MODE_THRESHOLD 0x50
174
// Ramping down from turbo consumes additional 18 bytes (currently used only if MODE_TURBO is on)
177
#define WDT_TIMEOUT 6 // Number of WTD ticks light needs to be turned on before resetting short click counter (.5 sec each)
179
#define ADC_LOW 125 // When do we warn the user and start ramping down
180
#define ADC_CRIT 120 // When do we shut the light off
182
#define CAP_THRESHOLD 130 // Value between 1 and 255 corresponding with cap voltage (0 - 1.1v) where we consider it a short press to move to the next mode
183
// Not sure the lowest you can go before getting bad readings, but with a value of 70 and a 1uF cap, it seemed to switch sometimes
184
// even when waiting 10 seconds between presses.
187
* =========================================================================
190
//#include <avr/pgmspace.h>
192
#include <util/delay_basic.h>
193
// Having own _delay_ms() saves some bytes
194
static void _delay_ms(uint16_t n)
203
#include <avr/interrupt.h>
205
#include <avr/eeprom.h>
206
#include <avr/sleep.h>
207
//#include <avr/power.h>
209
#define STAR2_PIN PB0
210
#define STAR3_PIN PB4
213
#define CAP_CHANNEL 0x03 // MUX 03 corresponds with PB3 (Star 4)
214
#define CAP_DIDR ADC3D // Digital input disable bit corresponding with PB3
217
#define VOLTAGE_PIN PB2
219
#define ADC_CHANNEL 0x01 // MUX 01 corresponds with PB2
220
#define ADC_DIDR ADC1D // Digital input disable bit corresponding with PB2
221
#define ADC_PRSCL 0x06 // clk/64
223
#define PWM_LVL OCR0B // OCR0B is the output compare register for PB1
233
#ifdef ENABLE_TURNING_OFF_TURBO_TIMER
234
uint8_t turbo_timer_enabled = 0;
237
// Modes (not const, as this gets adjusted when the light starts up based on stars)
238
static uint8_t modes[] = { BASIC_MODES, HIDDEN_MODES };
240
uint8_t mode_idx = 0;
241
uint8_t selected_pwm = 0;
242
volatile uint8_t adjusted_pwm = 0; // Volatile, because can be altered in WDT
244
void store_mode_idx(uint8_t lvl)
246
// Central method for writing (with wear leveling)
247
uint8_t oldpos=eepos;
248
eepos=(eepos+1)&31; //wear leveling, use next cell
250
// Write the current mode
251
EEARL=eepos; EEDR=lvl; EECR=32+4; EECR=32+4+2; //WRITE //32:write only (no erase) 4:enable 2:go
253
while(EECR & 2) //wait for completion
254
; // Empty loop, do nothing, just wait..
256
// Erase the last mode
257
EEARL=oldpos; EECR=16+4; EECR=16+4+2; //ERASE //16:erase only (no write) 4:enable 2:go
260
inline uint8_t read_stored_idx()
263
eeprom_read_block(eep, 0, 32);
265
while((*peep == 0xff) && (eepos < 32))
276
inline void next_mode(uint8_t short_clicks)
278
if(++mode_idx >= N_BASIC_MODES)
280
if(short_clicks < HIDDEN_MODE_THRESHOLD || // too little short clicks -> wrap around, else go to hidden
281
mode_idx >= (N_BASIC_MODES + N_HIDDEN_MODES)) // or all hidden modes used -> wrap around
282
mode_idx = 0; // Wrap around
288
// Setup watchdog timer to only interrupt, not reset, every 500ms.
289
cli(); // Disable interrupts
290
wdt_reset(); // Reset the WDT
291
WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
292
WDTCR = (1<<WDTIE) | (1<<WDP2) | (1<<WDP0); // Enable interrupt every 500ms
293
sei(); // Enable interrupts
296
inline void WDT_off()
298
cli(); // Disable interrupts
299
wdt_reset(); // Reset the WDT
300
MCUSR &= ~(1<<WDRF); // Clear Watchdog reset flag
301
WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
302
WDTCR = 0x00; // Disable WDT
303
sei(); // Enable interrupts
308
ADMUX = (1 << REFS0) | (1 << ADLAR) | ADC_CHANNEL; // 1.1v reference, left-adjust, ADC1/PB2
309
DIDR0 |= (1 << ADC_DIDR); // disable digital input on ADC pin to reduce power consumption
310
ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL; // enable, start, prescale
313
inline void ADC_off()
315
ADCSRA &= ~(1<<7); //ADC off
318
#ifdef ENABLE_VOLTAGE_MONITORING
319
uint8_t low_voltage(uint8_t voltage_val)
321
static uint8_t lowbatt_cnt = 0;
324
ADCSRA |= (1 << ADSC);
326
// Wait for completion
327
while(ADCSRA & (1 << ADSC))
328
; // Empty loop, do nothing, just wait..
330
// See if voltage is lower than what we were looking for
331
if(ADCH < voltage_val)
333
// See if it's been low for a while
334
if(++lowbatt_cnt > 8)
351
static uint8_t ticks = 0;
356
if(ticks == WDT_TIMEOUT)
358
store_mode_idx(mode_idx); // Reset short click counter
361
#ifdef ENABLE_TURNING_OFF_TURBO_TIMER
362
else if(turbo_timer_enabled && ticks == TURBO_TIMEOUT && selected_pwm == MODE_TURBO) // Turbo mode is *not* always at end
364
else if(ticks == TURBO_TIMEOUT && selected_pwm == MODE_TURBO)
367
store_mode_idx(mode_idx | MODE_INDEX_TURBO_RAMPDOWN); // Store the knowledge of ramp down, so that next mode can be turbo again - do it *before* ramping down
370
while(adjusted_pwm > PWM_TURBO_STEPDOWN) // might be already less if low voltage stepdown was performed before this!
372
PWM_LVL = --adjusted_pwm;
376
if(adjusted_pwm > PWM_TURBO_STEPDOWN) // might be already less if low voltage stepdown was performed before this!
377
adjusted_pwm = PWM_TURBO_STEPDOWN;
382
#ifdef ENABLE_VOLTAGE_MONITORING
383
// Block for voltage monitoring
385
static uint8_t adjusted_to_min = 0;
387
if(adjusted_to_min) // Already in lowest mode, check only against critically low voltage
389
if(low_voltage(ADC_CRIT))
390
adjusted_pwm = PWM_OFF; // Signal main loop to turn off the light
392
else if(low_voltage(ADC_LOW))
394
// We need to go to a lower PWM - blink to notify the user
395
// One blink is enough to save program space
398
//PWM_LVL = adjusted_pwm; // Turned back on in the main loop, unnecessary to waste program space here
402
if(adjusted_pwm < PWM_MIN) // Can't go any lower -> Change to lowest possible PWM level and stay there until critical voltage
404
adjusted_pwm = PWM_MIN;
412
#ifdef ENABLE_HIGH_TO_LOW
413
void revert_modes(void) // Revert modes in place
415
uint8_t spare, *plow = modes, *phigh = modes+N_BASIC_MODES-1;
429
uint8_t short_click = 0;
431
// All ports default to input, but turn pull-up resistors on for the stars (not the ADC input! Made that mistake already)
432
PORTB = (1 << STAR2_PIN) | (1 << STAR3_PIN);
434
// Start up ADC for capacitor pin
435
DIDR0 |= (1 << CAP_DIDR); // disable digital input on ADC pin to reduce power consumption
436
ADMUX = (1 << REFS0) | (1 << ADLAR) | CAP_CHANNEL; // 1.1v reference, left-adjust, ADC3/PB3
437
ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL; // enable, start, prescale
439
// Wait for completion
440
while (ADCSRA & (1 << ADSC))
442
// Start again as datasheet says first result is unreliable
443
ADCSRA |= (1 << ADSC);
444
// Wait for completion
445
while (ADCSRA & (1 << ADSC))
448
if(ADCH > CAP_THRESHOLD)
454
// Charge up the capacitor by setting CAP_PIN to output
455
DDRB |= (1 << CAP_PIN); // Output
456
PORTB |= (1 << CAP_PIN); // High
458
// Set PWM pin to output
459
DDRB = (1 << PWM_PIN);
461
// Set timer to do PWM for correct output pin and set prescaler timing
462
TCCR0A = 0x23; // phase corrected PWM is 0x21 for PB1, fast-PWM is 0x23
463
TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
465
// Turn features on or off as needed
466
#ifdef ENABLE_VOLTAGE_MONITORING
471
ACSR |= (1<<7); //AC off
473
#ifdef ENABLE_TURNING_OFF_TURBO_TIMER
474
turbo_timer_enabled = ((PINB & (1 << STAR2_PIN)) > 0) ? 1 : 0;
477
#ifdef ENABLE_SINGLE_MODE
478
if((PINB & (1 << STAR2_PIN)) == 0)
480
modes[0] = modes[1] =
486
// Last basic mode is already MODE_TURBO - no need to waste bytes changing that
490
#ifdef ENABLE_HIGH_TO_LOW
491
#ifdef HIGH_TO_LOW_ON_BY_DEFAULT
492
if((PINB & (1 << STAR3_PIN)) > 0)
494
if((PINB & (1 << STAR3_PIN)) == 0)
499
#ifdef ENABLE_TACTICAL_MODE
500
#ifdef TACTICAL_MODE_ON_BY_DEFAULT
501
if((PINB & (1 << STAR2_PIN)) > 0)
503
if((PINB & (1 << STAR2_PIN)) == 0)
506
modes[1] = MODE_TURBO;
508
if(modes[0] == MODE_TURBO) // Single mode
518
modes[0] = MODE_STROBE;
521
modes[3] = MODE_LOWLOW;
528
// Enable sleep mode set to Idle that will be triggered by the sleep_mode() command.
529
// Will allow us to go idle between WDT interrupts
530
set_sleep_mode(SLEEP_MODE_IDLE);
532
// Mode memory handling block
534
uint8_t n_short_clicks = 0;
536
// Determine what mode we should fire up
537
// Read the last mode that was saved
538
mode_idx = read_stored_idx();
540
// Handle short press counter
541
n_short_clicks = (mode_idx & 0xf0);
544
if(short_click) // This click was short
546
if(n_short_clicks == MODE_INDEX_TURBO_RAMPDOWN) // TODO: Test if this logic works in practice, or do we need to use always double tap from turbo?
547
n_short_clicks = 0; // Keep turbo, reset counter
549
next_mode(n_short_clicks); // Will handle wrap arounds
551
store_mode_idx(mode_idx | ((n_short_clicks < HIDDEN_MODE_THRESHOLD) ? n_short_clicks+0x10 : n_short_clicks));
553
else // Didn't have a short press, keep the same mode, stored without short click counter
555
if((PINB & (1 << STAR2_PIN)) == 0) // Tactical or Single or No memory, reset to 1st mode
558
store_mode_idx(mode_idx);
562
// Start watchdog timer (used for storing the mode after delay, turbo timer, and voltage monitoring)
565
// Now just fire up the mode
566
selected_pwm = modes[mode_idx]; // Note: Actual PWM can be less than selected PWM (e.g. in case of low voltage)
568
if(selected_pwm < PWM_MIN) // Hidden blinky modes
569
adjusted_pwm = PWM_MAX; // All blinky modes initially with full power
571
adjusted_pwm = selected_pwm;
573
// block for main loop
575
uint8_t ii = 0; // Loop counter, used by multiple branches
576
#ifdef ENABLE_BEACONS
577
uint8_t beacon_background = PWM_OFF;
582
#ifdef ENABLE_VOLTAGE_MONITORING
583
if(adjusted_pwm == PWM_OFF) // Voltage monitoring signaled us to turn off the light -> break out of the main loop
587
PWM_LVL = adjusted_pwm; // must be set inside loop, is volatile & might have changed because of voltage monitoring
591
case MODE_STROBE: // Disorienting alternating strobe
593
// 51ms = ~19.5Hz, ~60% DC
598
#ifdef ALTERNATING_STROBE
601
if(ii < 19) // 51ms = ~19.5Hz, ~60% DC
603
else if(ii < 32) // 77ms = ~13Hz, ~40% DC
609
{ // 77ms = 13Hz, 51ms = 19.5Hz / 40-60% DC
613
_delay_ms(ii > 127 ? 46 : 20);
617
case MODE_MOTION_STOPPING_STROBE: // 8Hz, 1.6% DC
625
_delay_ms(600); // Dash for 'O' (3xDot)
627
_delay_ms(200); // Dot for 'S'
634
_delay_ms(200); // Pause inside a letter (1xDot)
638
_delay_ms(600); // Pause between letters (3xDot)
641
_delay_ms(2500); // Pause between "words" (should be 7xDot, but I like it longer)
647
#ifdef ENABLE_BEACONS
648
case MODE_BEACON_WITH_BACKGROUND:
649
beacon_background = PWM_BEACON_BACKGROUND;
651
case MODE_SLOW_BEACON_WITH_BACKGROUND:
652
beacon_background = PWM_SLOW_BEACON_BACKGROUND;
653
// no break - fall through to beacon code
655
case MODE_ALPINE_DISTRESS_BEACON:
658
PWM_LVL = beacon_background;
661
if(selected_pwm == MODE_ALPINE_DISTRESS_BEACON)
671
else if(selected_pwm == MODE_SLOW_BEACON_WITH_BACKGROUND)
680
ii++; // Loop counter, used by multiple branches
684
#ifdef ENABLE_VOLTAGE_MONITORING
686
// Critically low voltage -> Turn off the light
689
// Disable WDT so it doesn't wake us up from sleep
692
// Would be nice to blink a couple of times with lowest brightness to notify the user, but not implemented due space restrictions
694
// Turn the light off
697
// Disable ADC so it doesn't consume power
700
// Power down as many components as possible
701
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
703
// Deep sleep until powered off - consumes ~110uA during the deep sleep
708
return 0; // Standard Return Code -> would return to idle loop with interrupts disabled.