~gabe/flashlight-firmware/anduril2

117.1.2 by Selene Scriven
Switched from off-time capacitor to memory decay trick, to measure off time.
1
/* STAR_noinit version 1.3:1.0
117.1.1 by Selene Scriven
Copied STAR_offtime to STAR_noinit, so I can mod it to use memory decay instead of OTC.
2
 *
3
 * Changelog
4
 *
5
 * 1.0 Initial version
6
 * 1.1 Bug fix
7
 * 1.2 Added support for dual PWM outputs and selection of PWM mode per output level
8
 * 1.3 Added ability to have turbo ramp down gradually instead of step down
117.1.2 by Selene Scriven
Switched from off-time capacitor to memory decay trick, to measure off time.
9
 * 1.3:1.0 Changed from off-time capacitor to "noinit" memory decay trick
117.1.1 by Selene Scriven
Copied STAR_offtime to STAR_noinit, so I can mod it to use memory decay instead of OTC.
10
 *
11
 */
12
13
/*
14
 * NANJG 105C Diagram
15
 *           ---
16
 *         -|   |- VCC
17
 *  Star 4 -|   |- Voltage ADC
18
 *  Star 3 -|   |- PWM
19
 *     GND -|   |- Star 2
20
 *           ---
21
 *
22
 * FUSES
188.4.9 by Selene Scriven
Replaced outdated fuse value messages with a pointer to more actively maintained scripts.
23
 *      (check bin/flash*.sh for recommended values)
117.1.1 by Selene Scriven
Copied STAR_offtime to STAR_noinit, so I can mod it to use memory decay instead of OTC.
24
 *
25
 * STARS
26
 *		Star 2 = Moon if connected and alternate PWM output not used
27
 *		Star 3 = H-L if connected, L-H if not
28
 *		Star 4 = Capacitor for off-time
29
 *
30
 * VOLTAGE
31
 *		Resistor values for voltage divider (reference BLF-VLD README for more info)
32
 *		Reference voltage can be anywhere from 1.0 to 1.2, so this cannot be all that accurate
33
 *
34
 *           VCC
35
 *            |
36
 *           Vd (~.25 v drop from protection diode)
37
 *            |
38
 *          1912 (R1 19,100 ohms)
39
 *            |
40
 *            |---- PB2 from MCU
41
 *            |
42
 *          4701 (R2 4,700 ohms)
43
 *            |
44
 *           GND
45
 *
46
 *		ADC = ((V_bat - V_diode) * R2   * 255) / ((R1    + R2  ) * V_ref)
47
 *		125 = ((3.0   - .25    ) * 4700 * 255) / ((19100 + 4700) * 1.1  )
48
 *		121 = ((2.9   - .25    ) * 4700 * 255) / ((19100 + 4700) * 1.1  )
49
 *
50
 *		Well 125 and 121 were too close, so it shut off right after lowering to low mode, so I went with
51
 *		130 and 120
52
 *
53
 *		To find out what value to use, plug in the target voltage (V) to this equation
54
 *			value = (V * 4700 * 255) / (23800 * 1.1)
55
 *      
56
 */
57
#define F_CPU 4800000UL
58
59
/*
60
 * =========================================================================
61
 * Settings to modify per driver
62
 */
63
64
#define VOLTAGE_MON			// Comment out to disable
65
66
#define MEMORY				// Comment out to disable
67
68
//#define TICKS_250MS		// If enabled, ticks are every 250 ms. If disabled, ticks are every 500 ms
69
							// Affects turbo timeout/rampdown timing
70
71
#define MODE_MOON			3	// Can comment out to remove mode, but should be set through soldering stars
72
#define MODE_LOW			14  // Can comment out to remove mode
73
#define MODE_MED			39	// Can comment out to remove mode
74
//#define MODE_HIGH			255	// Can comment out to remove mode
75
#define MODE_TURBO			255	// Can comment out to remove mode
76
#define MODE_TURBO_LOW		140	// Level turbo ramps down to if turbo enabled
77
#define TURBO_TIMEOUT		240 // How many WTD ticks before before dropping down.  If ticks set for 500 ms, then 240 x .5 = 120 seconds.  Max value of 255 unless you change "ticks"
78
								// variable to uint8_t
79
//#define TURBO_RAMP_DOWN			// By default we will start to gradually ramp down, once TURBO_TIMEOUT ticks are reached, 1 PWM_LVL each tick until reaching MODE_TURBO_LOW PWM_LVL
80
								// If commented out, we will step down to MODE_TURBO_LOW once TURBO_TIMEOUT ticks are reached
81
82
#define FAST_PWM_START	    8 // Above what output level should we switch from phase correct to fast-PWM?
83
//#define DUAL_PWM_START		8 // Above what output level should we switch from the alternate PWM output to both PWM outputs?  Comment out to disable alternate PWM output
84
85
#define ADC_LOW				130	// When do we start ramping
86
#define ADC_CRIT			120 // When do we shut the light off
87
88
/*
89
 * =========================================================================
90
 */
91
92
//#include <avr/pgmspace.h>
93
#include <avr/io.h>
94
#include <util/delay.h>
95
#include <avr/interrupt.h>
96
#include <avr/wdt.h>	
97
#include <avr/eeprom.h>
98
#include <avr/sleep.h>
99
//#include <avr/power.h>
100
101
#define STAR2_PIN   PB0
102
#define STAR3_PIN   PB4
103
#define PWM_PIN     PB1
104
#define VOLTAGE_PIN PB2
105
#define ADC_CHANNEL 0x01	// MUX 01 corresponds with PB2
106
#define ADC_DIDR 	ADC1D	// Digital input disable bit corresponding with PB2
107
#define ADC_PRSCL   0x06	// clk/64
108
109
#define PWM_LVL		OCR0B	// OCR0B is the output compare register for PB1
110
#define ALT_PWM_LVL OCR0A   // OCR0A is the output compare register for PB0
111
112
/*
113
 * global variables
114
 */
115
117.1.2 by Selene Scriven
Switched from off-time capacitor to memory decay trick, to measure off time.
116
// offtime detection
117
volatile uint8_t noinit_decay __attribute__ ((section (".noinit")));
118
117.1.1 by Selene Scriven
Copied STAR_offtime to STAR_noinit, so I can mod it to use memory decay instead of OTC.
119
// Mode storage
120
uint8_t eepos = 0;
121
uint8_t eep[32];
122
uint8_t memory = 0;
123
124
// Modes (gets set when the light starts up based on stars)
125
static uint8_t modes[10];  // Don't need 10, but keeping it high enough to handle all
126
volatile uint8_t mode_idx = 0;
127
int     mode_dir = 0; // 1 or -1. Determined when checking stars. Do we increase or decrease the idx when moving up to a higher mode.
128
uint8_t mode_cnt = 0;
129
130
uint8_t lowbatt_cnt = 0;
131
132
void store_mode_idx(uint8_t lvl) {  //central method for writing (with wear leveling)
133
	uint8_t oldpos=eepos;
134
	eepos=(eepos+1)&31;  //wear leveling, use next cell
135
	// Write the current mode
136
	EEARL=eepos;  EEDR=lvl; EECR=32+4; EECR=32+4+2;  //WRITE  //32:write only (no erase)  4:enable  2:go
137
	while(EECR & 2); //wait for completion
138
	// Erase the last mode
139
	EEARL=oldpos;           EECR=16+4; EECR=16+4+2;  //ERASE  //16:erase only (no write)  4:enable  2:go
140
}
141
inline void read_mode_idx() {
142
	eeprom_read_block(&eep, 0, 32);
143
	while((eep[eepos] == 0xff) && (eepos < 32)) eepos++;
144
	if (eepos < 32) mode_idx = eep[eepos];//&0x10; What the?
145
	else eepos=0;
146
}
147
148
inline void next_mode() {
149
	if (mode_idx == 0 && mode_dir == -1) {
150
		// Wrap around
151
		mode_idx = mode_cnt - 1;
152
	} else {
153
		mode_idx += mode_dir;
154
		if (mode_idx > (mode_cnt - 1)) {
155
			// Wrap around
156
			mode_idx = 0;
157
		}
158
	}
159
}
160
161
inline void check_stars() {
162
	// Load up the modes based on stars
163
	// Always load up the modes array in order of lowest to highest mode
164
	// 0 being low for soldered, 1 for pulled-up for not soldered
165
	// Moon
166
#ifdef MODE_MOON
167
#ifndef DUAL_PWM_START
168
	if ((PINB & (1 << STAR2_PIN)) == 0) {
169
#endif
170
		modes[mode_cnt++] = MODE_MOON;
171
#ifndef DUAL_PWM_START
172
	}
173
#endif
174
#endif
175
#ifdef MODE_LOW
176
	modes[mode_cnt++] = MODE_LOW;
177
#endif
178
#ifdef MODE_MED
179
	modes[mode_cnt++] = MODE_MED;
180
#endif
181
#ifdef MODE_HIGH
182
	modes[mode_cnt++] = MODE_HIGH;
183
#endif
184
#ifdef MODE_TURBO
185
	modes[mode_cnt++] = MODE_TURBO;
186
#endif
187
	if ((PINB & (1 << STAR3_PIN)) == 0) {
188
		// High to Low
189
		mode_dir = -1;
190
	} else {
191
		mode_dir = 1;
192
	}
193
}
194
195
inline void WDT_on() {
196
	// Setup watchdog timer to only interrupt, not reset
197
	cli();							// Disable interrupts
198
	wdt_reset();					// Reset the WDT
199
	WDTCR |= (1<<WDCE) | (1<<WDE);  // Start timed sequence
200
	#ifdef TICKS_250MS
201
	WDTCR = (1<<WDTIE) | (1<<WDP2); // Enable interrupt every 250ms
202
	#else
203
	WDTCR = (1<<WDTIE) | (1<<WDP2) | (1<<WDP0); // Enable interrupt every 500ms
204
	#endif
205
	sei();							// Enable interrupts
206
}
207
208
inline void WDT_off()
209
{
210
	cli();							// Disable interrupts
211
	wdt_reset();					// Reset the WDT
212
	MCUSR &= ~(1<<WDRF);			// Clear Watchdog reset flag
213
	WDTCR |= (1<<WDCE) | (1<<WDE);  // Start timed sequence
214
	WDTCR = 0x00;					// Disable WDT
215
	sei();							// Enable interrupts
216
}
217
218
inline void ADC_on() {
219
	DIDR0 |= (1 << ADC_DIDR);							// disable digital input on ADC pin to reduce power consumption
220
	ADMUX  = (1 << REFS0) | (1 << ADLAR) | ADC_CHANNEL; // 1.1v reference, left-adjust, ADC1/PB2
221
	ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL;   // enable, start, prescale
222
}
223
224
inline void ADC_off() {
225
	ADCSRA &= ~(1<<7); //ADC off
226
}
227
228
void set_output(uint8_t pwm_lvl) {
229
	#ifdef DUAL_PWM_START
230
	if (pwm_lvl > DUAL_PWM_START) {
231
		// Using the normal output along with the alternate
232
		PWM_LVL = pwm_lvl;
233
	} else {
234
		PWM_LVL = 0;
235
	}
236
	#else
237
	PWM_LVL = pwm_lvl;
238
	#endif
239
	// Always set alternate PWM value even if not compiled for dual output as we will use this value
240
	// throughout the code when trying to see what the current output level is.  Setting this wont affect
241
	// the output when alternate output is disabled.
242
	ALT_PWM_LVL = pwm_lvl;
243
}
244
245
#ifdef VOLTAGE_MON
246
uint8_t low_voltage(uint8_t voltage_val) {
247
	// Start conversion
248
	ADCSRA |= (1 << ADSC);
249
	// Wait for completion
250
	while (ADCSRA & (1 << ADSC));
251
	// See if voltage is lower than what we were looking for
252
	if (ADCH < voltage_val) {
253
		// See if it's been low for a while
254
		if (++lowbatt_cnt > 8) {
255
			lowbatt_cnt = 0;
256
			return 1;
257
		}
258
	} else {
259
		lowbatt_cnt = 0;
260
	}
261
	return 0;
262
}
263
#endif
264
265
ISR(WDT_vect) {
266
	static uint8_t ticks = 0;
267
	if (ticks < 255) ticks++;
268
	// If you want more than 255 for longer turbo timeouts
269
	//static uint16_t ticks = 0;
270
	//if (ticks < 60000) ticks++;
271
	
272
#ifdef MODE_TURBO	
273
	//if (ticks == TURBO_TIMEOUT && modes[mode_idx] == MODE_TURBO) { // Doesn't make any sense why this doesn't work
274
	if (ticks >= TURBO_TIMEOUT && mode_idx == (mode_cnt - 1) && PWM_LVL > MODE_TURBO_LOW) {
275
		#ifdef TURBO_RAMP_DOWN
276
		set_output(PWM_LVL - 1);
277
		#else
278
		// Turbo mode is always at end
279
		set_output(MODE_TURBO_LOW);
280
		if (MODE_TURBO_LOW <= modes[mode_idx-1]) {
281
			// Dropped at or below the previous mode, so set it to the stored mode
282
			// Kept this as it was the same functionality as before.  For the TURBO_RAMP_DOWN feature
283
			// it doesn't do this logic because I don't know what makes the most sense
284
			store_mode_idx(--mode_idx);
285
		}
286
		#endif
287
	}
288
#endif
289
117.1.2 by Selene Scriven
Switched from off-time capacitor to memory decay trick, to measure off time.
290
	// for some reason, it behaves like on-time mem without this here
291
	// (but it shouldn't be needed)
292
	noinit_decay = 0;
117.1.1 by Selene Scriven
Copied STAR_offtime to STAR_noinit, so I can mod it to use memory decay instead of OTC.
293
}
294
295
int main(void)
296
{	
297
	// All ports default to input, but turn pull-up resistors on for the stars (not the ADC input!  Made that mistake already)
298
	#ifdef DUAL_PWM_START
299
	PORTB = (1 << STAR3_PIN);
300
	#else
301
	PORTB = (1 << STAR2_PIN) | (1 << STAR3_PIN);
302
	#endif
303
	
304
	// Determine what mode we should fire up
305
	// Read the last mode that was saved
306
	read_mode_idx();
307
	
308
	check_stars(); // Moving down here as it might take a bit for the pull-up to turn on?
309
	
117.1.2 by Selene Scriven
Switched from off-time capacitor to memory decay trick, to measure off time.
310
	if (! noinit_decay) {
117.1.1 by Selene Scriven
Copied STAR_offtime to STAR_noinit, so I can mod it to use memory decay instead of OTC.
311
		// Indicates they did a short press, go to the next mode
312
		next_mode(); // Will handle wrap arounds
313
		store_mode_idx(mode_idx);
314
	} else {
315
		// Didn't have a short press, keep the same mode
316
	#ifndef MEMORY
317
		// Reset to the first mode
318
		mode_idx = ((mode_dir == 1) ? 0 : (mode_cnt - 1));
319
		store_mode_idx(mode_idx);
320
	#endif
321
	}
117.1.2 by Selene Scriven
Switched from off-time capacitor to memory decay trick, to measure off time.
322
	// set noinit data for next boot
323
	noinit_decay = 0;  // will decay to non-zero after being off for a while
324
117.1.1 by Selene Scriven
Copied STAR_offtime to STAR_noinit, so I can mod it to use memory decay instead of OTC.
325
    // Set PWM pin to output
326
    DDRB |= (1 << PWM_PIN);
327
	#ifdef DUAL_PWM_START
328
	DDRB |= (1 << STAR2_PIN);
329
	#endif
330
331
    // Set timer to do PWM for correct output pin and set prescaler timing
332
    TCCR0A = 0x23; // phase corrected PWM is 0x21 for PB1, fast-PWM is 0x23
333
    TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
334
	
335
	// Turn features on or off as needed
336
	#ifdef VOLTAGE_MON
337
	ADC_on();
338
	#else
339
	ADC_off();
340
	#endif
341
	ACSR   |=  (1<<7); //AC off
342
	
343
	// Enable sleep mode set to Idle that will be triggered by the sleep_mode() command.
344
	// Will allow us to go idle between WDT interrupts
345
	set_sleep_mode(SLEEP_MODE_IDLE);
346
	
347
	uint8_t prev_mode_idx = mode_idx;
348
	
349
	WDT_on();
350
	
351
	// Now just fire up the mode
352
    // Set timer to do PWM for correct output pin and set prescaler timing
353
	if (modes[mode_idx] > FAST_PWM_START) {
354
		#ifdef DUAL_PWM_START
355
		TCCR0A = 0b10100011; // fast-PWM both outputs
356
		#else
357
		TCCR0A = 0b00100011; // fast-PWM normal output
358
		#endif
359
	} else {
360
		#ifdef DUAL_PWM_START
361
		TCCR0A = 0b10100001; // phase corrected PWM both outputs
362
		#else
363
		TCCR0A = 0b00100001; // phase corrected PWM normal output
364
		#endif
365
	}
366
	TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
367
	
368
	set_output(modes[mode_idx]);
369
	
370
	uint8_t i = 0;
371
	uint8_t hold_pwm;
372
	while(1) {
373
	#ifdef VOLTAGE_MON
374
		if (low_voltage(ADC_LOW)) {
375
			// We need to go to a lower level
376
			if (mode_idx == 0 && ALT_PWM_LVL <= modes[mode_idx]) {
377
				// Can't go any lower than the lowest mode
378
				// Wait until we hit the critical level before flashing 10 times and turning off
379
				while (!low_voltage(ADC_CRIT));
380
				i = 0;
381
				while (i++<10) {
382
					set_output(0);
383
					_delay_ms(250);
384
					set_output(modes[0]);
385
					_delay_ms(500);
386
				}
387
				// Turn off the light
388
				set_output(0);
389
				// Disable WDT so it doesn't wake us up
390
				WDT_off();
391
				// Power down as many components as possible
392
				set_sleep_mode(SLEEP_MODE_PWR_DOWN);
393
				sleep_mode();
394
			} else {
395
				// Flash 3 times before lowering
396
				hold_pwm = ALT_PWM_LVL;
397
				i = 0;
398
				while (i++<3) {
399
					set_output(0);
400
					_delay_ms(250);
401
					set_output(hold_pwm);
402
					_delay_ms(500);
403
				}
404
				// Lower the mode by half, but don't go below lowest level
405
				if ((ALT_PWM_LVL >> 1) < modes[0]) {
406
					set_output(modes[0]);
407
					mode_idx = 0;
408
				} else {					
409
					set_output(ALT_PWM_LVL >> 1);
410
				}					
411
				// See if we should change the current mode level if we've gone under the current mode.
412
				if (ALT_PWM_LVL < modes[mode_idx]) {
413
					// Lower our recorded mode
414
					mode_idx--;
415
				}
416
			}
417
			// Wait 3 seconds before lowering the level again
418
			_delay_ms(3000);
419
		}
420
	#endif
421
		sleep_mode();
422
	}
423
424
    return 0; // Standard Return Code
117.1.2 by Selene Scriven
Switched from off-time capacitor to memory decay trick, to measure off time.
425
}