~gabe/flashlight-firmware/anduril2

« back to all changes in this revision

Viewing changes to ToyKeeper/uv/uv.c

  • Committer: Selene Scriven
  • Date: 2015-03-18 02:41:51 UTC
  • mto: (126.1.1 trunk)
  • mto: This revision was merged to the branch mainline in revision 127.
  • Revision ID: ubuntu@toykeeper.net-20150318024151-7lj6e4j83511lzq5
Copied ToyKeeper/s7/s7.c to uv/uv.c for use as a base for UV lights.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * This is intended for use on flashlights with a clicky switch.
 
3
 * Ideally, a Nichia 219B running at 1900mA in a Convoy S-series host.
 
4
 * It's mostly based on JonnyC's STAR on-time firmware and ToyKeeper's
 
5
 * tail-light firmware.
 
6
 *
 
7
 * Original author: JonnyC
 
8
 * Modifications: ToyKeeper / Selene Scriven
 
9
 *
 
10
 * NANJG 105C Diagram
 
11
 *           ---
 
12
 *         -|   |- VCC
 
13
 *  Star 4 -|   |- Voltage ADC
 
14
 *  Star 3 -|   |- PWM
 
15
 *     GND -|   |- Star 2
 
16
 *           ---
 
17
 *
 
18
 * CPU speed is 4.8Mhz without the 8x divider when low fuse is 0x79
 
19
 *
 
20
 * define F_CPU 4800000  CPU: 4.8MHz  PWM: 9.4kHz       ####### use low fuse: 0x79  #######
 
21
 * 
 
22
 * Above PWM speeds are for phase-correct PWM.  This program uses Fast-PWM,
 
23
 * which when the CPU is 4.8MHz will be 18.75 kHz
 
24
 *
 
25
 * FUSES
 
26
 *      I use these fuse settings
 
27
 *      Low:  0x79
 
28
 *      High: 0xed
 
29
 *      (flash-noinit.sh has an example)
 
30
 *
 
31
 * STARS (not used)
 
32
 *
 
33
 * VOLTAGE
 
34
 *      Resistor values for voltage divider (reference BLF-VLD README for more info)
 
35
 *      Reference voltage can be anywhere from 1.0 to 1.2, so this cannot be all that accurate
 
36
 *
 
37
 *           VCC
 
38
 *            |
 
39
 *           Vd (~.25 v drop from protection diode)
 
40
 *            |
 
41
 *          1912 (R1 19,100 ohms)
 
42
 *            |
 
43
 *            |---- PB2 from MCU
 
44
 *            |
 
45
 *          4701 (R2 4,700 ohms)
 
46
 *            |
 
47
 *           GND
 
48
 *
 
49
 *      ADC = ((V_bat - V_diode) * R2   * 255) / ((R1    + R2  ) * V_ref)
 
50
 *      125 = ((3.0   - .25    ) * 4700 * 255) / ((19100 + 4700) * 1.1  )
 
51
 *      121 = ((2.9   - .25    ) * 4700 * 255) / ((19100 + 4700) * 1.1  )
 
52
 *
 
53
 *      Well 125 and 121 were too close, so it shut off right after lowering to low mode, so I went with
 
54
 *      130 and 120
 
55
 *
 
56
 *      To find out what value to use, plug in the target voltage (V) to this equation
 
57
 *          value = (V * 4700 * 255) / (23800 * 1.1)
 
58
 *
 
59
 */
 
60
#define F_CPU 4800000UL
 
61
 
 
62
/*
 
63
 * =========================================================================
 
64
 * Settings to modify per driver
 
65
 */
 
66
 
 
67
#define VOLTAGE_MON                 // Comment out to disable
 
68
#define OWN_DELAY                   // Should we use the built-in delay or our own?
 
69
 
 
70
// Lumen measurements used a Nichia 219B at 1900mA in a Convoy S7 host
 
71
#define MODE_MOON           6       // 6: 0.14 lm (6 through 9 may be useful levels)
 
72
#define MODE_LOW            14      // 14: 7.3 lm
 
73
#define MODE_MED            39      // 39: 42 lm
 
74
#define MODE_HIGH           120     // 120: 155 lm
 
75
#define MODE_HIGHER         255     // 255: 342 lm
 
76
// If you change these, you'll probably want to change the "modes" array below
 
77
#define SOLID_MODES         5       // How many non-blinky modes will we have?
 
78
#define DUAL_BEACON_MODES   5+3     // How many beacon modes will we have (with background light on)?
 
79
#define SINGLE_BEACON_MODES 5+3+1   // How many beacon modes will we have (without background light on)?
 
80
#define FIXED_STROBE_MODES  5+3+1+3 // How many constant-speed strobe modes?
 
81
#define VARIABLE_STROBE_MODES 5+3+1+3+2 // How many variable-speed strobe modes?
 
82
#define BATT_CHECK_MODE     5+3+1+3+2+1 // battery check mode index
 
83
// Note: don't use more than 32 modes, or it will interfere with the mechanism used for mode memory
 
84
#define TOTAL_MODES         BATT_CHECK_MODE
 
85
 
 
86
//#define ADC_LOW             130     // When do we start ramping
 
87
//#define ADC_CRIT            120     // When do we shut the light off
 
88
 
 
89
#define ADC_42          185 // the ADC value we expect for 4.20 volts
 
90
#define ADC_100         185 // the ADC value for 100% full (4.2V resting)
 
91
#define ADC_75          175 // the ADC value for 75% full (4.0V resting)
 
92
#define ADC_50          164 // the ADC value for 50% full (3.8V resting)
 
93
#define ADC_25          154 // the ADC value for 25% full (3.6V resting)
 
94
#define ADC_0           139 // the ADC value for 0% full (3.3V resting)
 
95
#define ADC_LOW         123 // When do we start ramping down
 
96
#define ADC_CRIT        113 // When do we shut the light off
 
97
 
 
98
/*
 
99
 * =========================================================================
 
100
 */
 
101
 
 
102
#ifdef OWN_DELAY
 
103
#include <util/delay_basic.h>
 
104
// Having own _delay_ms() saves some bytes AND adds possibility to use variables as input
 
105
static void _delay_ms(uint16_t n)
 
106
{
 
107
    // TODO: make this take tenths of a ms instead of ms,
 
108
    // for more precise timing?
 
109
    // (would probably be better than the if/else here for a special-case
 
110
    // sub-millisecond delay)
 
111
    if (n==0) { _delay_loop_2(300); }
 
112
    else {
 
113
        while(n-- > 0)
 
114
            _delay_loop_2(890);
 
115
    }
 
116
}
 
117
#else
 
118
#include <util/delay.h>
 
119
#endif
 
120
 
 
121
#include <avr/pgmspace.h>
 
122
#include <avr/interrupt.h>
 
123
#include <avr/eeprom.h>
 
124
#include <avr/sleep.h>
 
125
 
 
126
#define STAR2_PIN   PB0
 
127
#define STAR3_PIN   PB4
 
128
#define STAR4_PIN   PB3
 
129
#define PWM_PIN     PB1
 
130
#define VOLTAGE_PIN PB2
 
131
#define ADC_CHANNEL 0x01    // MUX 01 corresponds with PB2
 
132
#define ADC_DIDR    ADC1D   // Digital input disable bit corresponding with PB2
 
133
#define ADC_PRSCL   0x06    // clk/64
 
134
 
 
135
#define PWM_LVL     OCR0B   // OCR0B is the output compare register for PB1
 
136
 
 
137
/*
 
138
 * global variables
 
139
 */
 
140
 
 
141
// Mode storage
 
142
// store in uninitialized memory so it will not be overwritten and
 
143
// can still be read at startup after short (<500ms) power off
 
144
// decay used to tell if user did a short press.
 
145
volatile uint8_t noinit_decay __attribute__ ((section (".noinit")));
 
146
volatile uint8_t noinit_mode __attribute__ ((section (".noinit")));
 
147
 
 
148
// change to 1 if you want on-time mode memory instead of "short-cycle" memory
 
149
// (actually, don't.  It's not supported any more, and doesn't work)
 
150
#define memory 0
 
151
 
 
152
// Modes (hardcoded to save space)
 
153
static uint8_t modes[TOTAL_MODES] = { // high enough to handle all
 
154
    MODE_MOON, MODE_LOW, MODE_MED, MODE_HIGH, MODE_HIGHER, // regular solid modes
 
155
    MODE_MOON, MODE_LOW, MODE_MED, // dual beacon modes (this level and this level + 2)
 
156
    MODE_HIGHER, // heartbeat beacon
 
157
    79, 41, 15, // constant-speed strobe modes (12.5 Hz, 24 Hz, 60 Hz)
 
158
    MODE_HIGHER, MODE_HIGHER, // variable-speed strobe modes
 
159
    MODE_MED, // battery check mode
 
160
};
 
161
volatile uint8_t mode_idx = 0;
 
162
// 1 or -1. Do we increase or decrease the idx when moving up to a higher mode?
 
163
// Is set by checking stars in the original STAR firmware, but that's removed to save space.
 
164
#define mode_dir 1
 
165
PROGMEM const uint8_t voltage_blinks[] = {
 
166
    ADC_0,    // 1 blink  for 0%-25%
 
167
    ADC_25,   // 2 blinks for 25%-50%
 
168
    ADC_50,   // 3 blinks for 50%-75%
 
169
    ADC_75,   // 4 blinks for 75%-100%
 
170
    ADC_100,  // 5 blinks for >100%
 
171
};
 
172
 
 
173
inline void next_mode() {
 
174
    mode_idx += mode_dir;
 
175
    if (mode_idx > (TOTAL_MODES - 1)) {
 
176
        // Wrap around
 
177
        mode_idx = 0;
 
178
    }
 
179
}
 
180
 
 
181
inline void ADC_on() {
 
182
    ADMUX  = (1 << REFS0) | (1 << ADLAR) | ADC_CHANNEL; // 1.1v reference, left-adjust, ADC1/PB2
 
183
    DIDR0 |= (1 << ADC_DIDR);                           // disable digital input on ADC pin to reduce power consumption
 
184
    ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL;   // enable, start, prescale
 
185
}
 
186
 
 
187
inline void ADC_off() {
 
188
    ADCSRA &= ~(1<<7); //ADC off
 
189
}
 
190
 
 
191
#ifdef VOLTAGE_MON
 
192
uint8_t get_voltage() {
 
193
    // Start conversion
 
194
    ADCSRA |= (1 << ADSC);
 
195
    // Wait for completion
 
196
    while (ADCSRA & (1 << ADSC));
 
197
    // See if voltage is lower than what we were looking for
 
198
    return ADCH;
 
199
}
 
200
#endif
 
201
 
 
202
int main(void)
 
203
{
 
204
    // All ports default to input, but turn pull-up resistors on for the stars
 
205
    // (not the ADC input!  Made that mistake already)
 
206
    // (stars not used)
 
207
    //PORTB = (1 << STAR2_PIN) | (1 << STAR3_PIN) | (1 << STAR4_PIN);
 
208
 
 
209
    // Set PWM pin to output
 
210
    DDRB = (1 << PWM_PIN);
 
211
 
 
212
    // Set timer to do PWM for correct output pin and set prescaler timing
 
213
    TCCR0A = 0x23; // phase corrected PWM is 0x21 for PB1, fast-PWM is 0x23
 
214
    TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
 
215
 
 
216
    // Turn features on or off as needed
 
217
    #ifdef VOLTAGE_MON
 
218
    ADC_on();
 
219
    #else
 
220
    ADC_off();
 
221
    #endif
 
222
    ACSR   |=  (1<<7); //AC off
 
223
 
 
224
    // Enable sleep mode set to Idle that will be triggered by the sleep_mode() command.
 
225
    // Will allow us to go idle between WDT interrupts (which we're not using anyway)
 
226
    set_sleep_mode(SLEEP_MODE_IDLE);
 
227
 
 
228
    // Determine what mode we should fire up
 
229
    // Read the last mode that was saved
 
230
    if (noinit_decay) // not short press, forget mode
 
231
    {
 
232
        noinit_mode = 0;
 
233
        mode_idx = 0;
 
234
    } else { // short press, advance to next mode
 
235
        mode_idx = noinit_mode;
 
236
        next_mode();
 
237
        noinit_mode = mode_idx;
 
238
    }
 
239
    // set noinit data for next boot
 
240
    noinit_decay = 0;
 
241
 
 
242
    // Now just fire up the mode
 
243
    PWM_LVL = modes[mode_idx];
 
244
 
 
245
    uint8_t i = 0;
 
246
    uint8_t j = 0;
 
247
    uint8_t strobe_len = 0;
 
248
#ifdef VOLTAGE_MON
 
249
    uint8_t lowbatt_cnt = 0;
 
250
    uint8_t voltage;
 
251
#endif
 
252
    while(1) {
 
253
        if(mode_idx < SOLID_MODES) { // Just stay on at a given brightness
 
254
            sleep_mode();
 
255
        } else if (mode_idx < DUAL_BEACON_MODES) { // two-level fast strobe pulse at about 1 Hz
 
256
            for(i=0; i<4; i++) {
 
257
                PWM_LVL = modes[mode_idx-SOLID_MODES+2];
 
258
                _delay_ms(5);
 
259
                PWM_LVL = modes[mode_idx];
 
260
                _delay_ms(65);
 
261
            }
 
262
            _delay_ms(720);
 
263
        } else if (mode_idx < SINGLE_BEACON_MODES) { // heartbeat flasher
 
264
            PWM_LVL = modes[SOLID_MODES-1];
 
265
            _delay_ms(1);
 
266
            PWM_LVL = 0;
 
267
            _delay_ms(249);
 
268
            PWM_LVL = modes[SOLID_MODES-1];
 
269
            _delay_ms(1);
 
270
            PWM_LVL = 0;
 
271
            _delay_ms(749);
 
272
        } else if (mode_idx < FIXED_STROBE_MODES) { // strobe mode, fixed-speed
 
273
            strobe_len = 1;
 
274
            if (modes[mode_idx] < 50) { strobe_len = 0; }
 
275
            PWM_LVL = modes[SOLID_MODES-1];
 
276
            _delay_ms(strobe_len);
 
277
            PWM_LVL = 0;
 
278
            _delay_ms(modes[mode_idx]);
 
279
        } else if (mode_idx == VARIABLE_STROBE_MODES-2) {
 
280
            // strobe mode, smoothly oscillating frequency ~7 Hz to ~18 Hz
 
281
            for(j=0; j<66; j++) {
 
282
                PWM_LVL = modes[SOLID_MODES-1];
 
283
                _delay_ms(1);
 
284
                PWM_LVL = 0;
 
285
                if (j<33) { strobe_len = j; }
 
286
                else { strobe_len = 66-j; }
 
287
                _delay_ms(2*(strobe_len+33-6));
 
288
            }
 
289
        } else if (mode_idx == VARIABLE_STROBE_MODES-1) {
 
290
            // strobe mode, smoothly oscillating frequency ~16 Hz to ~100 Hz
 
291
            for(j=0; j<100; j++) {
 
292
                PWM_LVL = modes[SOLID_MODES-1];
 
293
                _delay_ms(0); // less than a millisecond
 
294
                PWM_LVL = 0;
 
295
                if (j<50) { strobe_len = j; }
 
296
                else { strobe_len = 100-j; }
 
297
                _delay_ms(strobe_len+9);
 
298
            }
 
299
        } else if (mode_idx < BATT_CHECK_MODE) {
 
300
            uint8_t blinks = 0;
 
301
            // turn off and wait one second before showing the value
 
302
            // (also, ensure voltage is measured while not under load)
 
303
            PWM_LVL = 0;
 
304
            _delay_ms(1000);
 
305
            voltage = get_voltage();
 
306
            voltage = get_voltage(); // the first one is unreliable
 
307
            // division takes too much flash space
 
308
            //voltage = (voltage-ADC_LOW) / (((ADC_42 - 15) - ADC_LOW) >> 2);
 
309
            // a table uses less space than 5 logic clauses
 
310
            for (i=0; i<sizeof(voltage_blinks); i++) {
 
311
                if (voltage > pgm_read_byte(voltage_blinks + i)) {
 
312
                    blinks ++;
 
313
                }
 
314
            }
 
315
 
 
316
            // blink up to five times to show voltage
 
317
            // (~0%, ~25%, ~50%, ~75%, ~100%, >100%)
 
318
            for(i=0; i<blinks; i++) {
 
319
                PWM_LVL = MODE_MED;
 
320
                _delay_ms(100);
 
321
                PWM_LVL = 0;
 
322
                _delay_ms(400);
 
323
            }
 
324
 
 
325
            _delay_ms(1000);  // wait at least 1 second between readouts
 
326
        }
 
327
#ifdef VOLTAGE_MON
 
328
        if (ADCSRA & (1 << ADIF)) {  // if a voltage reading is ready
 
329
            voltage = get_voltage();
 
330
            // See if voltage is lower than what we were looking for
 
331
            if (voltage < ((mode_idx == 0) ? ADC_CRIT : ADC_LOW)) {
 
332
                ++lowbatt_cnt;
 
333
            } else {
 
334
                lowbatt_cnt = 0;
 
335
            }
 
336
            // See if it's been low for a while, and maybe step down
 
337
            if (lowbatt_cnt >= 3) {
 
338
                if (mode_idx > 0) {
 
339
                    mode_idx = 0;
 
340
                } else { // Already at the lowest mode
 
341
                    // Turn off the light
 
342
                    PWM_LVL = 0;
 
343
                    // Power down as many components as possible
 
344
                    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
 
345
                    sleep_mode();
 
346
                }
 
347
                lowbatt_cnt = 0;
 
348
                // Wait at least 1 second before lowering the level again
 
349
                _delay_ms(1000);  // this will interrupt blinky modes
 
350
            }
 
351
 
 
352
            // Make sure conversion is running for next time through
 
353
            ADCSRA |= (1 << ADSC);
 
354
        }
 
355
#endif
 
356
    }
 
357
}