2
* Generic clicky-switch-with-offtime-cap firmware.
3
* Expects a FET+1 style driver, supports two independent power channels.
4
* Similar to blf-a6.c but minus the end-user config options.
5
* (expects to be configured at compile-time, not runtime)
7
* Copyright (C) 2015 Selene Scriven
9
* This program is free software: you can redistribute it and/or modify
10
* it under the terms of the GNU General Public License as published by
11
* the Free Software Foundation, either version 3 of the License, or
12
* (at your option) any later version.
14
* This program is distributed in the hope that it will be useful,
15
* but WITHOUT ANY WARRANTY; without even the implied warranty of
16
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
* GNU General Public License for more details.
19
* You should have received a copy of the GNU General Public License
20
* along with this program. If not, see <http://www.gnu.org/licenses/>.
26
* OTC -| |- Voltage ADC
27
* Star 3 -| |- PWM (FET)
28
* GND -| |- PWM (1x7135)
32
* I use these fuse settings
33
* Low: 0x75 (4.8MHz CPU without 8x divider, 9.4kHz phase-correct PWM or 18.75kHz fast-PWM)
36
* For more details on these settings, visit http://github.com/JCapSolutions/blf-firmware/wiki/PWM-Frequency
39
* Star 2 = second PWM output channel
40
* Star 3 = mode memory if soldered, no memory by default
41
* Star 4 = Capacitor for off-time
44
* Resistor values for voltage divider (reference BLF-VLD README for more info)
45
* Reference voltage can be anywhere from 1.0 to 1.2, so this cannot be all that accurate
49
* Vd (~.25 v drop from protection diode)
51
* 1912 (R1 19,100 ohms)
55
* 4701 (R2 4,700 ohms)
59
* To find out what values to use, flash the driver with battcheck.hex
60
* and hook the light up to each voltage you need a value for. This is
61
* much more reliable than attempting to calculate the values from a
62
* theoretical formula.
64
* Same for off-time capacitor values. Measure, don't guess.
66
#define F_CPU 4800000UL
69
* =========================================================================
70
* Settings to modify per driver
73
//#define FAST 0x23 // fast PWM channel 1 only
74
//#define PHASE 0x21 // phase-correct PWM channel 1 only
75
#define FAST 0xA3 // fast PWM both channels
76
#define PHASE 0xA1 // phase-correct PWM both channels
78
#define VOLTAGE_MON // Comment out to disable LVP
79
#define OWN_DELAY // Should we use the built-in delay or our own?
80
// Adjust the timing per-driver, since the hardware has high variance
81
// Higher values will run slower, lower values run faster.
82
#define DELAY_TWEAK 950
84
#define OFFTIM3 // Use short/med/long off-time presses
85
// instead of just short/long
87
// output to use for blinks on battery check mode (primary PWM level, alt PWM level)
88
// Use 20,0 for a single-channel driver or 0,20 for a two-channel driver
89
#define BLINK_BRIGHTNESS 0,20
93
// PWM levels for the big circuit (FET or Nx7135)
94
#define MODESNx 0,0,0,6,56,135,255
95
// PWM levels for the small circuit (1x7135)
96
#define MODES1x 3,20,100,255,255,255,0
97
// PWM speed for each mode
98
#define MODES_PWM PHASE,FAST,FAST,FAST,FAST,FAST,PHASE
99
// Hidden modes are *before* the lowest (moon) mode, and should be specified
100
// in reverse order. So, to go backward from moon to turbo to strobe to
101
// battcheck, use BATTCHECK,STROBE,TURBO .
103
#define HIDDENMODES BIKING_STROBE,BATTCHECK,STROBE,TURBO
104
#define HIDDENMODES_PWM PHASE,PHASE,PHASE,PHASE
105
#define HIDDENMODES_ALT 0,0,0,0 // Zeroes, same length as NUM_HIDDEN
107
#define TURBO 255 // Convenience code for turbo mode
108
#define BATTCHECK 254 // Convenience code for battery check mode
109
// Uncomment to enable tactical strobe mode
110
#define STROBE 253 // Convenience code for strobe mode
111
// Uncomment to unable a 2-level stutter beacon instead of a tactical strobe
112
#define BIKING_STROBE 252 // Convenience code for biking strobe mode
113
// comment out to use minimal version instead (smaller)
114
#define FULL_BIKING_STROBE
116
#define NON_WDT_TURBO // enable turbo step-down without WDT
117
// How many timer ticks before before dropping down.
118
// Each timer tick is 500ms, so "60" would be a 30-second stepdown.
119
// Max value of 255 unless you change "ticks"
120
#define TURBO_TIMEOUT 60
122
// These values were measured using wight's "A17HYBRID-S" driver built by DBCstm.
123
// Your mileage may vary.
124
#define ADC_42 174 // the ADC value we expect for 4.20 volts
125
#define ADC_100 174 // the ADC value for 100% full (4.2V resting)
126
#define ADC_75 166 // the ADC value for 75% full (4.0V resting)
127
#define ADC_50 158 // the ADC value for 50% full (3.8V resting)
128
#define ADC_25 145 // the ADC value for 25% full (3.5V resting)
129
#define ADC_0 124 // the ADC value for 0% full (3.0V resting)
130
#define ADC_LOW 116 // When do we start ramping down (2.8V)
131
#define ADC_CRIT 112 // When do we shut the light off (2.7V)
132
// These values were copied from s7.c.
133
// Your mileage may vary.
134
//#define ADC_42 185 // the ADC value we expect for 4.20 volts
135
//#define ADC_100 185 // the ADC value for 100% full (4.2V resting)
136
//#define ADC_75 175 // the ADC value for 75% full (4.0V resting)
137
//#define ADC_50 164 // the ADC value for 50% full (3.8V resting)
138
//#define ADC_25 154 // the ADC value for 25% full (3.5V resting)
139
//#define ADC_0 139 // the ADC value for 0% full (3.0V resting)
140
//#define ADC_LOW 123 // When do we start ramping down (2.8V)
141
//#define ADC_CRIT 113 // When do we shut the light off (2.7V)
142
// Values for testing only:
143
//#define ADC_LOW 125 // When do we start ramping down (2.8V)
144
//#define ADC_CRIT 124 // When do we shut the light off (2.7V)
146
// the BLF EE A6 driver may have different offtime cap values than most other drivers
147
// Values are between 1 and 255, and can be measured with offtime-cap.c
148
// These #defines are the edge boundaries, not the center of the target.
150
#define CAP_SHORT 250 // Anything higher than this is a short press
151
#define CAP_MED 190 // Between CAP_MED and CAP_SHORT is a medium press
152
// Below CAP_MED is a long press
154
#define CAP_SHORT 190 // Anything higher than this is a short press, lower is a long press
158
* =========================================================================
161
// Ignore a spurious warning, we did the cast on purpose
162
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
165
#include <util/delay_basic.h>
166
// Having own _delay_ms() saves some bytes AND adds possibility to use variables as input
167
void _delay_ms(uint16_t n)
169
// TODO: make this take tenths of a ms instead of ms,
170
// for more precise timing?
171
while(n-- > 0) _delay_loop_2(DELAY_TWEAK);
173
void _delay_s() // because it saves a bit of ROM space to do it this way
178
#include <util/delay.h>
181
#include <avr/pgmspace.h>
182
//#include <avr/io.h>
183
//#include <avr/interrupt.h>
184
#include <avr/eeprom.h>
185
#include <avr/sleep.h>
186
//#include <avr/power.h>
188
#define STAR2_PIN PB0 // But note that there is no star 2.
189
#define STAR3_PIN PB4
191
#define CAP_CHANNEL 0x03 // MUX 03 corresponds with PB3 (Star 4)
192
#define CAP_DIDR ADC3D // Digital input disable bit corresponding with PB3
194
#define ALT_PWM_PIN PB0
195
#define VOLTAGE_PIN PB2
196
#define ADC_CHANNEL 0x01 // MUX 01 corresponds with PB2
197
#define ADC_DIDR ADC1D // Digital input disable bit corresponding with PB2
198
#define ADC_PRSCL 0x06 // clk/64
200
#define PWM_LVL OCR0B // OCR0B is the output compare register for PB1
201
#define ALT_PWM_LVL OCR0A // OCR0A is the output compare register for PB0
207
// Config / state variables
209
uint8_t memory = 0; // mode memory, or not (set via soldered star)
210
uint8_t mode_idx = 0; // current or last-used mode number
212
// NOTE: Only '1' is known to work; -1 will probably break and is untested.
213
// In other words, short press goes to the next (higher) mode,
214
// medium press goes to the previous (lower) mode.
216
// total length of current mode group's array
217
#define mode_cnt solid_modes+NUM_HIDDEN
218
// number of regular non-hidden modes in current mode group
219
#define solid_modes NUM_MODES
220
// number of hidden modes in the current mode group
221
// (hardcoded because both groups have the same hidden modes)
222
//uint8_t hidden_modes = NUM_HIDDEN; // this is never used
225
// Modes (gets set when the light starts up based on saved config values)
226
PROGMEM const uint8_t modesNx[] = { MODESNx, HIDDENMODES };
227
PROGMEM const uint8_t modes1x[] = { MODES1x, HIDDENMODES_ALT };
228
PROGMEM const uint8_t modes_pwm[] = { MODES_PWM, HIDDENMODES_PWM };
230
PROGMEM const uint8_t voltage_blinks[] = {
231
ADC_0, // 1 blink for 0%-25%
232
ADC_25, // 2 blinks for 25%-50%
233
ADC_50, // 3 blinks for 50%-75%
234
ADC_75, // 4 blinks for 75%-100%
235
ADC_100, // 5 blinks for >100%
236
255, // Ceiling, don't remove
239
void save_state() { // central method for writing (with wear leveling)
240
// a single 16-bit write uses less ROM space than two 8-bit writes
242
uint8_t oldpos=eepos;
244
eepos = (eepos+1) & 63; // wear leveling, use next cell
247
eeprom_write_byte((uint8_t *)(eepos), eep); // save current state
248
eeprom_write_byte((uint8_t *)(oldpos), 0xff); // erase old state
251
void restore_state() {
253
// find the config data
254
for(eepos=0; eepos<64; eepos++) {
255
eep = eeprom_read_byte((const uint8_t *)eepos);
256
if (eep != 0xff) break;
258
// unpack the config data
262
// unnecessary, save_state handles wrap-around
263
// (and we don't really care about it skipping cell 0 once in a while)
267
inline void next_mode() {
269
if (mode_idx >= solid_modes) {
270
// Wrap around, skipping the hidden modes
271
// (note: this also applies when going "forward" from any hidden mode)
277
inline void prev_mode() {
278
if (mode_idx == solid_modes) {
279
// If we hit the end of the hidden modes, go back to moon
281
} else if (mode_idx > 0) {
282
// Regular mode: is between 1 and TOTAL_MODES
285
// Otherwise, wrap around (this allows entering hidden modes)
286
mode_idx = mode_cnt - 1;
293
inline void ADC_on() {
294
DIDR0 |= (1 << ADC_DIDR); // disable digital input on ADC pin to reduce power consumption
295
ADMUX = (1 << REFS0) | (1 << ADLAR) | ADC_CHANNEL; // 1.1v reference, left-adjust, ADC1/PB2
296
ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL; // enable, start, prescale
299
inline void ADC_off() {
300
ADCSRA &= ~(1<<7); //ADC off
304
void set_output(uint8_t pwm1, uint8_t pwm2) {
305
// Need PHASE to properly turn off the light
306
if ((pwm1==0) && (pwm2==0)) {
313
void set_mode(uint8_t mode) {
314
TCCR0A = pgm_read_byte(modes_pwm + mode);
315
set_output(pgm_read_byte(modesNx + mode), pgm_read_byte(modes1x + mode));
317
// Only set output for solid modes
318
uint8_t out = pgm_read_byte(modesNx + mode);
319
if ((out < 250) || (out == 255)) {
320
set_output(pgm_read_byte(modesNx + mode), pgm_read_byte(modes1x + mode));
326
uint8_t get_voltage() {
328
ADCSRA |= (1 << ADSC);
329
// Wait for completion
330
while (ADCSRA & (1 << ADSC));
331
// See if voltage is lower than what we were looking for
336
void blink(uint8_t val)
340
set_output(BLINK_BRIGHTNESS);
351
// Read the off-time cap *first* to get the most accurate reading
352
// Start up ADC for capacitor pin
353
DIDR0 |= (1 << CAP_DIDR); // disable digital input on ADC pin to reduce power consumption
354
ADMUX = (1 << REFS0) | (1 << ADLAR) | CAP_CHANNEL; // 1.1v reference, left-adjust, ADC3/PB3
355
ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL; // enable, start, prescale
357
// Wait for completion
358
while (ADCSRA & (1 << ADSC));
359
// Start again as datasheet says first result is unreliable
360
ADCSRA |= (1 << ADSC);
361
// Wait for completion
362
while (ADCSRA & (1 << ADSC));
363
cap_val = ADCH; // save this for later
365
// All ports default to input, but turn pull-up resistors on for the stars (not the ADC input! Made that mistake already)
366
// only one star, because one is used for PWM channel 2
367
// and the other is used for the off-time capacitor
368
PORTB = (1 << STAR3_PIN);
370
// Set PWM pin to output
371
DDRB |= (1 << PWM_PIN); // enable main channel
372
DDRB |= (1 << ALT_PWM_PIN); // enable second channel
374
// Set timer to do PWM for correct output pin and set prescaler timing
375
//TCCR0A = 0x23; // phase corrected PWM is 0x21 for PB1, fast-PWM is 0x23
376
//TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
378
// Set timer to do PWM for correct output pin and set prescaler timing
379
TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
381
// Read config values and saved state
385
if (cap_val > CAP_SHORT) {
386
// Indicates they did a short press, go to the next mode
387
next_mode(); // Will handle wrap arounds
389
} else if (cap_val > CAP_MED) {
390
// User did a medium press, go back one mode
391
prev_mode(); // Will handle "negative" modes and wrap-arounds
394
// Long press, keep the same mode
395
// ... or reset to the first mode
397
// Reset to the first mode
406
// Charge up the capacitor by setting CAP_PIN to output
407
DDRB |= (1 << CAP_PIN); // Output
408
PORTB |= (1 << CAP_PIN); // High
410
// Turn features on or off as needed
416
//ACSR |= (1<<7); //AC off
418
// Enable sleep mode set to Idle that will be triggered by the sleep_mode() command.
419
// Will allow us to go idle between WDT interrupts
420
//set_sleep_mode(SLEEP_MODE_IDLE); // not used due to blinky modes
427
uint8_t lowbatt_cnt = 0;
430
// Make sure voltage reading is running for later
431
ADCSRA |= (1 << ADSC);
434
output = pgm_read_byte(modesNx + mode_idx);
435
// placeholder in case strobe isn't defined, should get compiled out by -Os
438
else if (output == STROBE) {
439
// 10Hz tactical strobe
445
#endif // ifdef STROBE
447
else if (output == BIKING_STROBE) {
448
// 2-level stutter beacon for biking and such
449
#ifdef FULL_BIKING_STROBE
459
// small/minimal version
466
#endif // ifdef BIKING_STROBE
468
else if (output == BATTCHECK) {
469
// turn off and wait one second before showing the value
470
// (also, ensure voltage is measured while not under load)
473
voltage = get_voltage();
474
//voltage = get_voltage(); // the first one is unreliable
475
// figure out how many times to blink
477
voltage > pgm_read_byte(voltage_blinks + i);
480
// blink up to five times to show voltage
481
// (~0%, ~25%, ~50%, ~75%, ~100%, >100%)
483
//_delay_s(); // wait at least 1 second between readouts
485
#endif // ifdef BATTCHECK
486
else { // Regular non-hidden solid mode
488
// This part of the code will mostly replace the WDT tick code.
490
// Do some magic here to handle turbo step-down
491
//if (ticks < 255) ticks++; // don't roll over
492
ticks ++; // actually, we don't care about roll-over prevention
493
if ((ticks > TURBO_TIMEOUT)
494
&& (output == TURBO)) {
495
mode_idx = solid_modes - 2; // step down to second-highest mode
500
// Otherwise, just sleep.
505
if (ADCSRA & (1 << ADIF)) { // if a voltage reading is ready
506
voltage = ADCH; // get_voltage();
507
// See if voltage is lower than what we were looking for
508
//if (voltage < ((mode_idx <= 1) ? ADC_CRIT : ADC_LOW)) {
509
if (voltage < ADC_LOW) {
514
// See if it's been low for a while, and maybe step down
515
if (lowbatt_cnt >= 8) {
516
// DEBUG: blink on step-down:
517
//set_output(0,0); _delay_ms(100);
518
i = mode_idx; // save space by not accessing mode_idx more than necessary
519
// properly track hidden vs normal modes
520
if (i >= solid_modes) {
521
// step down from blinky modes to medium
524
// step down from solid modes one at a time
526
} else { // Already at the lowest mode
528
// Turn off the light
530
// Power down as many components as possible
531
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
538
// Wait at least 2 seconds before lowering the level again
539
_delay_ms(250); // this will interrupt blinky modes
542
// Make sure conversion is running for next time through
543
ADCSRA |= (1 << ADSC);
546
#endif // ifdef VOLTAGE_MON
547
//sleep_mode(); // incompatible with blinky modes
550
//return 0; // Standard Return Code