Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /* Compiled size: 1574 bytes
- *
- * Firmware for AtTiny25-based flashlight drivers with a clicky switch, DD FET and 1x7135
- * Features:
- * - any number of solid modes, cell voltage check, mcu temperature check
- * - visibility strobe, beacons, motion-freeze strobes
- * - does not use OTC, no memory, always start in programmed mode, a quick off-click steps through the modes
- * - double-click hidden mode accessible from start mode
- * - phase-correct PWM at 1961 Hz, compromise to accomodate 7135 switching frequency, audible noise and flicker
- * - step light down at low voltage, switch off at critical voltage
- * - optional TURBO mode time-out, optional temperature step down
- *
- * Based on original work by JonnyC and ToyKeeper / Selene Scriven
- *
- * ATTINY25 circuit:
- * ---
- * PB5:RST -| o |- VCC
- * PB3:OTC -| |- PB2:ADC1
- * PB4: -| |- PB1:PWM1 FET
- * GND -| |- PB0:PWM0 1x7135
- * ---
- * ADC4 : Temperature
- *
- * Fuses:
- * Low: 0x62 (8 MHz clock, 8* divider, 14 CK + 64 ms startup)
- * High: 0xdf (no BOD, SPI enabled)
- * Ext: 0xff (self prog disabled)
- */
- #define F_CPU 1000000UL
- /* ===============================================================================================================
- * Settings to customise driver. Also patch modes[] below to match.
- */
- #define D1 2 // Drop over reverse polarity protection diode = 0.2 V
- #define BATT_LOW 32 // Cell voltage to step light down = 3.2 V
- #define BATT_CRIT 28 // Cell voltage to shut the light off = 2.8 V
- #define SOLID_MODES 3 // 0-3: solid modes
- #define BATT_VOLT_MODE 4 // 4: diagnostics, cell voltage
- #define MCU_TEMP_MODE 5 // 5: diagnostics, mcu temperature
- #define HI_VIS_MODE 6 // 6: high-visibility strobe mode
- #define BEACON1_MODE 7 // 7: slow strobe beacon mode
- #define BEACON2_MODE 8 // 8: lighthouse beacon mode
- #define BEACON3_MODE 9 // 9: SOS beacon mode
- #define FIXED_STROBE_MODES 13 // 10-13: fixed-speed motion-freeze strobe modes
- #define LAST_MODE 13 // wrap around from here
- #define START_MODE 1 // after long press
- #define TURBO_MODE 0 // one of the solid modes
- #define TURBO_TIMEOUT 15 // x2 seconds to stay in turbo mode - comment out to disable
- #define TEMPOUT 55 // temperature to step the light down - comment out to disable
- #define STEPDOWN_MODE 1 // mode after step-down
- #define HIDDEN_MODE TURBO_MODE // activated with double-click from OFF
- #define COUNTS_100US 25 // _delay_loop_2 counts to get 100 us (1 count = 4 CLKs)
- #define DOUBLE_CLICK_TIMEOUT 4 // double-click time-outs (64 ms clicks)
- /* ===============================================================================================================
- */
- #include <util/delay_basic.h>
- #include <avr/pgmspace.h>
- #include <avr/interrupt.h>
- #include <avr/wdt.h>
- #include <avr/sleep.h>
- #define PWM0_PIN PB0 // port B, pin 0, control signal for 7135
- #define PWM1_PIN PB1 // port B, pin 1, control signal for FET
- #define PWM0_LVL OCR0A // output compare register for 7135
- #define PWM1_LVL OCR0B // output compare register for FET
- #define ADCMUX_TEMP 0b10001111 // ADCMUX register setup to read temperature
- #define ADCMUX_VCC 0b00001100 // ADCMUX register setup to read Vbg referenced to Vcc
- /* ===============================================================================================================
- * Global variables
- */
- // Levels for 7135
- PROGMEM const uint8_t levels0[] = {
- // log ramp for 4 A DD
- 0,2,2,3,3,4,4,5,5,6,7,8,9,10,11,12,14,15,17,19,21,24,27,30,34,38,43,48,54,60,67,75,85,95,106,119,133,150,168,188,211,236,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,0
- // log ramp for 10 A DD
- // 0,2,3,3,4,4,5,5,6,7,8,9,10,12,13,15,17,20,22,25,29,33,37,42,48,55,62,71,80,91,104,118,135,153,174,198,225,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255
- };
- // Levels for FET
- PROGMEM const uint8_t levels1[] = {
- // log ramp for 4 A DD
- 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,7,11,16,20,26,32,39,47,55,65,76,88,101,116,133,153,174,198,225,255
- // log ramp for 10 A DD
- // 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,4,6,8,11,14,17,20,24,29,34,40,47,55,64,74,85,98,113,130,149,170,195,223,255
- };
- // Modes
- const uint8_t modes[] = {
- 63, 42, 29, 1, // solid modes level no
- 0, // reserved, diagnostics mode 1
- 0, // reserved, diagnostics mode 2
- 0, // reserved, high-visibility strobe
- 0, // reserved, beacon 1
- 0, // reserved, beacon 2
- 0, // reserved, beacon 3
- 9, 4, 1, 0 // *10ms delay strobe modes, 2nd part. 1st part = 10 ms fixed
- };
- // Keep track of cell voltage in ISRs, 10-bit resolution required for impedance check
- volatile int16_t voltage = 0; // in volts * 10
- volatile int16_t temperature = 0; // in degC
- volatile uint8_t adc_step = 0; // steps 0, 1 read voltage, steps 2, 3 reads temperature - only use steps 1, 3 values
- // Store mode in uninitialized memory so it will not be overwritten and
- // can still be read at startup after short (<500ms) power-off decay.
- // Used to tell if user did a short press, long press or double-press.
- volatile uint8_t noinit_decay __attribute__ ((section (".noinit")));
- volatile uint8_t noinit_mode __attribute__ ((section (".noinit")));
- volatile uint8_t noinit_prev_on_time __attribute__ ((section (".noinit")));
- volatile uint8_t mode_idx;
- /* ===============================================================================================================
- * Subroutines
- */
- static void _delay_100us(uint8_t n) { // 0.1 ms to 25.5 ms timer
- do {_delay_loop_2(COUNTS_100US);} while (--n); // cost is 12 bytes per call
- }
- static void _delay_10ms(uint8_t n){ // 10 ms to 2.55 s timer
- while (n--) {_delay_100us(100);} // use this one when possible, cost is 2 bytes/call
- } // less efficient while loop, to allow 0 us time delay
- static void _delay_5s() { // wait 5 s
- _delay_10ms(250);
- _delay_10ms(250);
- }
- void flash(uint8_t n) { // flash once at full 7135 power
- PWM0_LVL = 255;
- _delay_10ms(n); // ON for n*10 ms
- PWM0_LVL = 0;
- _delay_10ms(n<<2); // OFF for n*40 ms, keep n below 64
- }
- /* ===============================================================================================================
- * 64 ms interrupt
- */
- void WDT_on() // Setup watchdog timer to interrupt every x ms
- {
- cli(); // disable interrupts
- wdt_reset(); // reset the WDT
- WDTCR |= (1<<WDCE) | (1<<WDE); // start timed sequence
- WDTCR = (1<<WDIE) | (1<<WDP1); // enable interrupt every 64 ms
- sei(); // enable interrupts
- }
- void WDT_off() // Turn off watchdog timer
- {
- cli(); // disable interrupts
- wdt_reset(); // reset the WDT
- MCUSR &= ~(1<<WDRF); // clear Watchdog reset flag
- WDTCR |= (1<<WDCE) | (1<<WDE); // start timed sequence
- WDTCR = 0x00; // disable WDT
- sei(); // enable interrupts
- }
- ISR(ADC_vect) // ADC done interrupt service routine
- {
- int16_t adcin = ADC;
- if (adc_step == 1) { // ignore first ADC value from step 0
- // Read cell voltage
- adcin = 11264/adcin + D1; // in volts * 10: 10 * 1.1 * 1024 / ADC + D1
- if (voltage > 0) {
- if (voltage < adcin) {++voltage;} // crude low pass filter
- if (voltage > adcin) {--voltage;}
- } else {voltage = adcin;} // prime on first read
- }
- if (adc_step == 3) { // ignore first ADC value from step 2
- // Read MCU temperature
- adcin -= 275; // 300 = 25 degC
- if (temperature > 0) {
- if (temperature < adcin) {++temperature;} // crude low pass filter
- if (temperature > adcin) {--temperature;}
- } else {temperature = adcin;} // prime on first read
- }
- adc_step++; if (adc_step > 3) {adc_step = 0;}
- if (adc_step < 2) { // steps 0, 1 read voltage, steps 2, 3 read temperature
- ADMUX = ADCMUX_VCC;}
- else {
- ADMUX = ADCMUX_TEMP;}
- }
- ISR(WDT_vect) // WDT interrupt service routine
- {
- // Double click timeout
- if (noinit_prev_on_time < DOUBLE_CLICK_TIMEOUT) {
- ++noinit_prev_on_time;
- }
- // start next ADC conversion and arm interrupt
- ADCSRA |= (1 << ADSC) | (1 << ADIE);
- }
- /* ===============================================================================================================
- * Main
- */
- int main(void)
- {
- // Determine what mode we should fire up, read last mode that was saved
- if (noinit_decay) { // long click, reset to start mode
- mode_idx = START_MODE;
- noinit_prev_on_time = 0; // enable double click detection on initial start
- } else if (noinit_prev_on_time >= DOUBLE_CLICK_TIMEOUT) { // short click, or first click of double click
- mode_idx = noinit_mode + 1;
- if (mode_idx > LAST_MODE) {mode_idx = START_MODE;}
- } else {mode_idx = HIDDEN_MODE;} // second click of double-click, go to hidden mode
- noinit_decay = 0; // set noinit data for next boot
- noinit_mode = mode_idx;
- // Turn ADC on (13 CLKs required for conversion, go max 200 kHz for 10-bit resolution)
- ADMUX = ADCMUX_VCC; // 1.1 V reference, not left-adjust, Vbg
- DIDR0 |= (1 << ADC1D); // disable digital input on ADC1 pin to reduce power consumption
- ACSR |= (1 << ACD); // analog comparator off to save power
- ADCSRA = (1 << ADEN ) | (1 << ADSC ) | 0b100; // enable, start, ADC clock prescale = 16 (62.5 kHz)
- // Start interrupt service routine
- WDT_on();
- // Setup PWM pins for 1960 Hz, phase-correct. F_pwm = F_clkio / N / 510, N = precale factor
- DDRB = (1<<PWM1_PIN) | (1<<PWM0_PIN); // enable PWM1, PWM0 control output pins
- if (mode_idx <= (FIXED_STROBE_MODES-4)) { // set for PWM output, otherwise regular output (needed for fast strobes)
- TCCR0B = (1 << CS00); // pre-scaler for timer = 1
- TCCR0A = (1 << WGM00); // PWM mode 1 (phase correct, TOP=xFF)
- TCCR0A |= (1 << COM0A1); // PWM0: Normal direction
- TCCR0A |= (1 << COM0B1); // PWM1: Normal direction
- }
- uint8_t i, j;
- uint8_t turbo_cnt = 0;
- PWM0_LVL = 0;
- PWM1_LVL = 0;
- // Main loop
- while(1) {
- // Manage modes
- // =======================================================================================================
- if (mode_idx <= SOLID_MODES) { // ======= SOLID MODES ===============================
- PWM0_LVL = pgm_read_byte(levels0 + modes[mode_idx]);
- PWM1_LVL = pgm_read_byte(levels1 + modes[mode_idx]);
- #ifdef TURBO_TIMEOUT
- if (mode_idx == TURBO_MODE) { // TURBO MODE time-out
- turbo_cnt++;
- _delay_10ms(200); // turbo_cnt in 2-second steps
- } else {turbo_cnt = 0;}
- if (turbo_cnt >= TURBO_TIMEOUT) { // step down from TURBO
- mode_idx = STEPDOWN_MODE;
- noinit_mode = mode_idx;
- }
- #endif
- #ifdef TEMPOUT
- if ((temperature > 0) && (mode_idx < STEPDOWN_MODE) && (temperature >= TEMPOUT )) { // step down for heat
- mode_idx = STEPDOWN_MODE;
- noinit_mode = mode_idx;
- }
- #endif
- }
- else if (mode_idx == BATT_VOLT_MODE) { // ======= CELL VOLTAGE CHECK MODE ========================
- if (voltage > 0) {
- j = voltage;
- i=10; while (j >= i) { // flash the 10's
- flash(8);
- i += 10;
- }
- _delay_10ms(62);
- i -= 10;
- j -= i;
- while (j) { // flash the 1's
- flash(8);
- j--;
- }
- _delay_10ms(250);
- }
- }
- else if (mode_idx == MCU_TEMP_MODE) { // ======= MCU TEMPERATURE CHECK MODE ========================
- if (temperature > 0) {
- j = temperature;
- i=10; while (j >= i) { // flash the 10's
- flash(8);
- i += 10;
- }
- _delay_10ms(62);
- i -= 10;
- j -= i;
- while (j) { // flash the 1's
- flash(8);
- j--;
- }
- _delay_10ms(250);
- }
- }
- else if (mode_idx <= HI_VIS_MODE) { // ======= HI-VISIBILITY MODE ========================
- PWM0_LVL = 127; // background light
- i=4; do { // flash brighter
- PWM1_LVL = 255;
- _delay_10ms(1);
- PWM1_LVL = 0;
- _delay_10ms(6);
- } while (--i);
- _delay_10ms(72);
- }
- else if (mode_idx == BEACON3_MODE) { // ======= SOS BEACON MODE =======================
- #define MORSE_T 10
- i = 3; do {
- PWM1_LVL = 255;
- _delay_10ms(MORSE_T);
- PWM1_LVL = 0;
- _delay_10ms(MORSE_T);
- } while (--i);
- i = 3; do {
- PWM1_LVL = 255;
- _delay_10ms(MORSE_T*3);
- PWM1_LVL = 0;
- _delay_10ms(MORSE_T);
- } while (--i);
- i = 3; do {
- PWM1_LVL = 255;
- _delay_10ms(MORSE_T);
- PWM1_LVL = 0;
- _delay_10ms(MORSE_T);
- } while (--i);
- _delay_5s();
- }
- else if (mode_idx == BEACON2_MODE) { // ======= LIGHTHOUSE BEACON MODE =======================
- i = 0; do {
- i++;
- PWM0_LVL = pgm_read_byte(levels0 + i);
- PWM1_LVL = pgm_read_byte(levels1 + i);
- _delay_10ms(1);
- } while (i < 63);
- do {
- PWM0_LVL = pgm_read_byte(levels0 + i);
- PWM1_LVL = pgm_read_byte(levels1 + i);
- _delay_10ms(1);
- } while (i--);
- _delay_10ms(80);
- }
- else if (mode_idx == BEACON1_MODE) { // ======= FLASH BEACON MODE =======================
- PWM1_LVL = 255;
- _delay_10ms(10);
- PWM1_LVL = 0;
- _delay_5s();
- }
- else if (mode_idx <= FIXED_STROBE_MODES) { // ======= FIXED FREQUENCY STROBE MODES ==============
- PORTB = (1 << PWM1_PIN);
- _delay_100us(1);
- PORTB = 0;
- _delay_100us(99); // 10 ms part of delay, allow 100 us for strobe on
- _delay_10ms(modes[mode_idx]); // x10 ms part of delay
- }
- // Battery protection
- // =======================================================================================================
- if ((voltage > 0) && (voltage <= BATT_LOW) && (mode_idx < 2) ) { // ignore blinkies and low power modes
- ++mode_idx; // step power down one level
- noinit_mode = mode_idx;
- _delay_10ms(200); // dont step down too fast
- }
- if ((voltage > 0) && (voltage <= BATT_CRIT)) { // flash and switch off
- PWM1_LVL = 0;
- i = 6; do {
- flash(1);
- } while (--i);
- WDT_off(); // interrupts off
- ADCSRA &= ~(1 << ADEN); // ADC off
- _delay_10ms(1); // make sure PWM is 0
- set_sleep_mode(SLEEP_MODE_PWR_DOWN); // power down as many components as possible
- sleep_mode();
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement