3
* TheStar NANJG105C Firmware by _the_ (originally based on STAR 1.1 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 = Tactical mode (Single mode if Star 4 connected)
39
* Star 3 = H-L if connected, L-H if not
40
* Star 4 = Memory if NOT connected
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) - 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
// High-to-low mode order, selectable by star 3, consumes 72 bytes
89
#define ENABLE_HIGH_TO_LOW
90
//#define HIGH_TO_LOW_ON_BY_DEFAULT
92
// Memory is on by default, it can be turned off by soldering star 4 if this is enabled - consumes 22 bytes
93
#define ENABLE_TURNING_OFF_MEMORY
94
#define MEMORY_ON_BY_DEFAULT
96
// Normal strobe by default, define this for alternating - consumes 20 bytes
97
//#define NORMAL_STROBE
98
//#define ALTERNATING_STROBE // +18 bytes
99
#define RANDOM_STROBE // +40 bytes
101
// SOS mode - consumes 70 bytes
104
// Set of different beacons - consumes 76 to 84 bytes (4- / 3-modes)
105
#define ENABLE_BEACONS
107
// Alpine distress beacon (6 x 10s beacon + 60s delay) - consumes 38 bytes
108
#define ENABLE_ALPINE_DISTRESS_BEACON
110
// Voltage monitoring - consumes 180 bytes
111
#define ENABLE_VOLTAGE_MONITORING
113
// Some standard PWM values
114
#define PWM_OFF 0 // Used for signaling
115
#define PWM_MIN 7 // Less than this won't light up reliably :(
116
#define PWM_MAX 255 // Maximum brightness
118
// PWM values for modes
119
#define MODE_LOWLOW 7 // 2mA@2.8A 3mA@4.2A
120
//#define MODE_LOW 11 // 28mA@2.8A 38mA@4.2A
121
#define MODE_LOW 16 // 67mA@2.8A 100mA@4.2A
122
#define MODE_MED 70 // 0.66A@2.8A 1.0A@4.2A
123
#define MODE_HIGH 120 // 1.2A@2.8A 1.8A@4.2A
124
#define MODE_TURBO 253
125
#define MODE_TURBO_STAY_ON PWM_MAX
127
// Low light during the pauses in some beacons
128
#define PWM_BEACON_BACKGROUND MODE_MED // ~0.66A (@2.8A)
129
#define PWM_SLOW_BEACON_BACKGROUND MODE_LOW // ~0.3A (@2.8A)
131
// Turbo timeout - How many WTD ticks before before ramping down (0.5s each)
132
//#define TURBO_TIMEOUT 60 // 30s for hot rods
133
#define TURBO_TIMEOUT 180 // 90s for normal usage
134
//#define TURBO_TIMEOUT 120 // 60s for normal usage (@4.2A)
136
// Turbo stepdown PWM value (select one suitable for your light)
137
//#define PWM_TURBO_STEPDOWN 140 // ~1.4A (@2.8A)
138
//#define PWM_TURBO_STEPDOWN 200 // ~2.1A (@2.8A)
139
#define PWM_TURBO_STEPDOWN 140 // ~2.1A (@4.2A)
140
//#define PWM_TURBO_STEPDOWN 100 // ~1.4A (@4.2A)
142
// Special mode PWM values - Must be under PWM_MIN
143
#define MODE_STROBE 1
144
#define MODE_BEACON 2
145
#define MODE_ALPINE_DISTRESS_BEACON 3
147
#define MODE_BEACON_WITH_BACKGROUND 5
148
#define MODE_SLOW_BEACON_WITH_BACKGROUND 6
149
#define MODE_MOTION_STOPPING_STROBE 254
153
#define N_BASIC_MODES 4
154
#define BASIC_MODES MODE_LOWLOW, MODE_LOW, MODE_MED, MODE_TURBO
156
#define N_BASIC_MODES 3
157
#define BASIC_MODES MODE_LOW, MODE_MED, MODE_TURBO
162
# define N_HIDDEN_MODES 7
163
# 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
165
# define N_HIDDEN_MODES 9
166
# 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
169
// When to enter hidden modes?
171
# define HIDDEN_MODE_THRESHOLD 0x80
173
# define HIDDEN_MODE_THRESHOLD 0x60
176
// Ramping down from turbo consumes additional 18 bytes (currently used only if MODE_TURBO is on)
179
#define WDT_TIMEOUT 2 // Number of WTD ticks before mode is saved (.5 sec each)
181
#define ADC_LOW 125 // When do we warn the user and start ramping down
182
#define ADC_CRIT 120 // When do we shut the light off
186
* =========================================================================
189
//#include <avr/pgmspace.h>
191
#include <util/delay_basic.h>
192
// Having own _delay_ms() saves some bytes
193
static void _delay_ms(uint16_t n)
202
#include <avr/interrupt.h>
204
#include <avr/eeprom.h>
205
#include <avr/sleep.h>
206
//#include <avr/power.h>
208
#define STAR2_PIN PB0
209
#define STAR3_PIN PB4
210
#define STAR4_PIN PB3
212
#define VOLTAGE_PIN PB2
213
#define ADC_CHANNEL 0x01 // MUX 01 corresponds with PB2
214
#define ADC_DIDR ADC1D // Digital input disable bit corresponding with PB2
215
#define ADC_PRSCL 0x06 // clk/64
217
#define PWM_LVL OCR0B // OCR0B is the output compare register for PB1
227
#ifdef ENABLE_TURNING_OFF_MEMORY
228
uint8_t memory_enabled = 0;
231
#ifdef ENABLE_TURNING_OFF_TURBO_TIMER
232
uint8_t turbo_timer_enabled = 0;
235
// Modes (not const, as this gets adjusted when the light starts up based on stars)
236
static uint8_t modes[] = { BASIC_MODES, HIDDEN_MODES };
238
uint8_t mode_idx = 0;
239
uint8_t selected_pwm = 0;
240
volatile uint8_t adjusted_pwm = 0; // Volatile, because can be altered in WDT
242
uint8_t wdt_timeout = WDT_TIMEOUT;
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((wdt_timeout != 1 && 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
#ifdef ENABLE_TURNING_OFF_MEMORY
362
store_mode_idx(mode_idx);
363
#ifdef ENABLE_TURNING_OFF_MEMORY
367
// Reset the mode to the start for next time
373
#ifdef ENABLE_TURNING_OFF_TURBO_TIMER
374
else if(turbo_timer_enabled && ticks == TURBO_TIMEOUT && selected_pwm == MODE_TURBO) // Turbo mode is *not* always at end
376
else if(ticks == TURBO_TIMEOUT && selected_pwm == MODE_TURBO)
380
while(adjusted_pwm > PWM_TURBO_STEPDOWN) // might be already less if low voltage stepdown was performed before this!
382
PWM_LVL = --adjusted_pwm;
386
if(adjusted_pwm > PWM_TURBO_STEPDOWN) // might be already less if low voltage stepdown was performed before this!
387
adjusted_pwm = PWM_TURBO_STEPDOWN;
392
#ifdef ENABLE_VOLTAGE_MONITORING
393
// Block for voltage monitoring
395
static uint8_t adjusted_to_min = 0;
397
if(adjusted_to_min) // Already in lowest mode, check only against critically low voltage
399
if(low_voltage(ADC_CRIT))
400
adjusted_pwm = PWM_OFF; // Signal main loop to turn off the light
402
else if(low_voltage(ADC_LOW))
404
// We need to go to a lower PWM - blink to notify the user
405
// One blink is enough to save program space
408
//PWM_LVL = adjusted_pwm; // Turned back on in the main loop, unnecessary to waste program space here
412
if(adjusted_pwm < PWM_MIN) // Can't go any lower -> Change to lowest possible PWM level and stay there until critical voltage
414
adjusted_pwm = PWM_MIN;
422
#ifdef ENABLE_HIGH_TO_LOW
423
void revert_modes(void) // Revert modes in place
425
uint8_t spare, *plow = modes, *phigh = modes+N_BASIC_MODES-1;
439
// All ports default to input, but turn pull-up resistors on for the stars (not the ADC input! Made that mistake already)
440
PORTB = (1 << STAR2_PIN) | (1 << STAR3_PIN) | (1 << STAR4_PIN);
442
// Set PWM pin to output
443
DDRB = (1 << PWM_PIN);
445
// Set timer to do PWM for correct output pin and set prescaler timing
446
TCCR0A = 0x23; // phase corrected PWM is 0x21 for PB1, fast-PWM is 0x23
447
TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
449
// Turn features on or off as needed
450
#ifdef ENABLE_VOLTAGE_MONITORING
455
ACSR |= (1<<7); //AC off
457
#ifdef ENABLE_TURNING_OFF_MEMORY
458
// Soldering Star 4 disables memory
459
#ifdef MEMORY_ON_BY_DEFAULT
460
memory_enabled = ((PINB & (1 << STAR4_PIN)) > 0) ? 1 : 0;
462
memory_enabled = ((PINB & (1 << STAR4_PIN)) == 0) ? 1 : 0;
466
#ifdef ENABLE_TURNING_OFF_TURBO_TIMER
467
turbo_timer_enabled = ((PINB & (1 << STAR2_PIN)) > 0) ? 1 : 0;
470
#ifdef ENABLE_SINGLE_MODE
471
if((PINB & (1 << STAR2_PIN)) == 0)
473
modes[0] = modes[1] =
479
// Last basic mode is already MODE_TURBO - no need to waste bytes changing that
483
#ifdef ENABLE_TACTICAL_MODE
484
#ifdef TACTICAL_MODE_ON_BY_DEFAULT
485
if((PINB & (1 << STAR2_PIN)) > 0)
487
if((PINB & (1 << STAR2_PIN)) == 0)
490
#ifdef ENABLE_TURNING_OFF_MEMORY
491
if(memory_enabled) // Single mode
493
modes[0] = modes[1] =
499
// Last basic mode is already MODE_TURBO - no need to waste bytes changing that
504
modes[0] = MODE_STROBE;
505
modes[1] = MODE_TURBO;
508
modes[3] = MODE_LOWLOW;
516
#ifdef ENABLE_HIGH_TO_LOW
517
#ifdef HIGH_TO_LOW_ON_BY_DEFAULT
518
if((PINB & (1 << STAR3_PIN)) > 0)
520
if((PINB & (1 << STAR3_PIN)) == 0)
525
// Enable sleep mode set to Idle that will be triggered by the sleep_mode() command.
526
// Will allow us to go idle between WDT interrupts
527
set_sleep_mode(SLEEP_MODE_IDLE);
529
// Mode memory handling block
531
uint8_t short_clicks = 0;
533
// Determine what mode we should fire up
534
// Read the last mode that was saved
535
mode_idx = read_stored_idx();
537
// Handle short press indicator
538
short_clicks = (mode_idx & 0xf0);
541
if(short_clicks) // One or more short clicks
543
// Indicates we did a short press last time, go to the next mode
544
next_mode(short_clicks); // Will handle wrap arounds
546
// else: Didn't have a short press, keep the same mode, nothing to do
548
// Store mode with short press indicator
549
store_mode_idx(mode_idx | ((short_clicks < HIDDEN_MODE_THRESHOLD) ? short_clicks+0x10 : short_clicks));
552
// Start watchdog timer (used for storing the mode after delay, turbo timer, and voltage monitoring)
555
// Now just fire up the mode
556
selected_pwm = modes[mode_idx]; // Note: Actual PWM can be less than selected PWM (e.g. in case of low voltage)
558
if(selected_pwm < PWM_MIN) // Hidden blinky modes
559
adjusted_pwm = PWM_MAX; // All blinky modes initially with full power
561
adjusted_pwm = selected_pwm;
563
// block for main loop
565
uint8_t ii = 0; // Loop counter, used by multiple branches
566
#ifdef ENABLE_BEACONS
567
uint8_t beacon_background = PWM_OFF;
572
#ifdef ENABLE_VOLTAGE_MONITORING
573
if(adjusted_pwm == PWM_OFF) // Voltage monitoring signaled us to turn off the light -> break out of the main loop
577
PWM_LVL = adjusted_pwm; // must be set inside loop, is volatile & might have changed because of voltage monitoring
581
case MODE_STROBE: // Disorienting alternating strobe
587
#ifdef ALTERNATING_STROBE
590
if(ii < 28) // 51ms = ~13Hz, ~60% DC
592
else if(ii < 48) // 77ms = ~19.5Hz, ~40% DC
598
{ // 77ms = 13Hz, 51ms = 19.5Hz / 40-60% DC
602
_delay_ms(ii > 127 ? 46 : 20);
606
case MODE_MOTION_STOPPING_STROBE: // 10Hz, 2% DC
614
_delay_ms(600); // Dash for 'O' (3xDot)
616
_delay_ms(200); // Dot for 'S'
623
_delay_ms(200); // Pause inside a letter (1xDot)
627
_delay_ms(600); // Pause between letters (3xDot)
630
_delay_ms(2500); // Pause between "words" (should be 7xDot, but I like it longer)
636
#ifdef ENABLE_BEACONS
637
case MODE_BEACON_WITH_BACKGROUND:
638
case MODE_SLOW_BEACON_WITH_BACKGROUND:
640
beacon_background = PWM_SLOW_BEACON_BACKGROUND;
644
case MODE_BEACON_WITH_BACKGROUND:
645
beacon_background = PWM_BEACON_BACKGROUND;
647
case MODE_SLOW_BEACON_WITH_BACKGROUND:
648
beacon_background = PWM_SLOW_BEACON_BACKGROUND;
652
// no break - fall through to beacon code
654
case MODE_ALPINE_DISTRESS_BEACON:
656
PWM_LVL = beacon_background;
659
if(selected_pwm == MODE_ALPINE_DISTRESS_BEACON)
669
else if(selected_pwm == MODE_SLOW_BEACON_WITH_BACKGROUND)
678
ii++; // Loop counter, used by multiple branches
682
#ifdef ENABLE_VOLTAGE_MONITORING
684
// Critically low voltage -> Turn off the light
687
// Disable WDT so it doesn't wake us up from sleep
690
// Would be nice to blink a couple of times with lowest brightness to notify the user, but not implemented due space restrictions
692
// Turn the light off
695
// Disable ADC so it doesn't consume power
698
// Power down as many components as possible
699
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
701
// Deep sleep until powered off - consumes ~110uA during the deep sleep
706
return 0; // Standard Return Code -> would return to idle loop with interrupts disabled.