rampinglight.c 13.2 KB
Newer Older
Sven Greiner's avatar
Sven Greiner committed
1 2 3 4 5 6 7 8 9 10
/*
 * Copyright (c) 2018 Sven Karsten Greiner
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Sven Greiner's avatar
Sven Greiner committed
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Sven Greiner's avatar
Sven Greiner committed
12 13 14
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
Sven Greiner's avatar
Sven Greiner committed
15
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
Sven Greiner's avatar
Sven Greiner committed
16 17
 */

18
// Optional features
Sven Greiner's avatar
Sven Greiner committed
19
#define BATTCHECK
Sven Greiner's avatar
Sven Greiner committed
20
#define LOW_VOLTAGE_PROTECTION
Sven Greiner's avatar
Sven Greiner committed
21
#define DELAY_EVENT
22

23 24
#include <avr/io.h>
#include <util/delay.h>
Sven Greiner's avatar
Sven Greiner committed
25
#include <avr/interrupt.h>
Sven Greiner's avatar
Sven Greiner committed
26
#include <avr/eeprom.h>
27
#include <util/atomic.h>
28 29

#define PWM_PIN PB1
Sven Greiner's avatar
Sven Greiner committed
30 31
#define VOLTAGE_ADC_CHANNEL 0x01  // ADC1/PB2

32 33
#define FLASH_TIME 20
#define FLICKER_TIME 2
Sven Greiner's avatar
Sven Greiner committed
34 35

#define BAT_LOW  141  // ~3.2 V
Sven Greiner's avatar
Sven Greiner committed
36 37 38 39
#define BAT_CRIT 120  // ~2.7 V
#define BAT_75P  175  // ~4.0 V
#define BAT_50P  167  // ~3.8 V
#define BAT_25P  154  // ~3.5 V
40 41 42 43

#define RAMP_TIME 3
#define RAMP_SIZE sizeof(ramp_values)
#define RAMP_VALUES 5,5,5,5,5,6,6,6,6,7,7,8,8,9,10,11,12,13,14,15,17,18,20,22,23,25,28,30,32,35,38,41,44,47,51,55,59,63,67,71,76,81,86,92,97,103,109,116,122,129,136,144,151,159,167,176,185,194,203,213,223,233,244,255
Sven Greiner's avatar
Sven Greiner committed
44
#define TURBO_PWM 255
45

Sven Greiner's avatar
Sven Greiner committed
46
#define FIXED_SIZE sizeof(fixed_values)
47
#define FIXED_VALUES 5,35,118,255
Sven Greiner's avatar
Sven Greiner committed
48 49

#define CBD_BYTES 4
50 51
#define CBD_PATTERN 0xAA

Sven Greiner's avatar
Sven Greiner committed
52 53
#define EEPROM_SIZE 64
#define EEPROM_OPTIONS (EEPROM_SIZE-1)
54
#define EEPROM_OUTPUT_WL_BYTES 16
Sven Greiner's avatar
Sven Greiner committed
55

56
/**
Sven Greiner's avatar
Sven Greiner committed
57 58 59
 * States of the state machine.
 */
enum State {
Sven Greiner's avatar
Sven Greiner committed
60
  kDefault,    // Special decision state
Sven Greiner's avatar
Sven Greiner committed
61 62 63
  kRamping,    // Ramping up and down
  kFrozen,     // Frozen ramping level
  kTurbo,      // Full power
Sven Greiner's avatar
Sven Greiner committed
64
  kFixed,      // Fixed mode
Sven Greiner's avatar
Sven Greiner committed
65
  kConfig,     // Config menu
66 67 68
#ifdef BATTCHECK
  kBattcheck,  // Battery level
#endif  // ifdef BATTCHECK
Sven Greiner's avatar
Sven Greiner committed
69 70 71
};

/**
72
 * Persistent configuration of the flashlight
73
 */
74 75 76 77
typedef union {
  uint8_t raw;
  struct {
    unsigned fixed_mode : 1;
Sven Greiner's avatar
Sven Greiner committed
78
    unsigned mode_memory : 1;
79 80 81 82
    unsigned freeze_on_high : 1;
    unsigned start_high : 1;
  };
} Options;
83 84

const uint8_t __flash ramp_values[] = { RAMP_VALUES };
Sven Greiner's avatar
Sven Greiner committed
85
const uint8_t __flash fixed_values[] = { FIXED_VALUES };
Sven Greiner's avatar
Sven Greiner committed
86
const uint8_t __flash voltage_table[] = { 0, BAT_25P, BAT_50P, BAT_75P };
87

Sven Greiner's avatar
Sven Greiner committed
88 89 90 91
uint8_t cold_boot_detect[CBD_BYTES] __attribute__((section(".noinit")));
enum State state __attribute__((section(".noinit")));
uint8_t output __attribute__((section(".noinit")));
uint8_t fast_presses __attribute__((section(".noinit")));
Sven Greiner's avatar
Sven Greiner committed
92
uint8_t ramping_up __attribute__((section(".noinit")));
Sven Greiner's avatar
Sven Greiner committed
93 94

register Options options asm("r7");
95 96
register uint8_t output_eeprom asm("r6");
register uint8_t output_eeprom_pos asm("r5");
Sven Greiner's avatar
Sven Greiner committed
97
register uint8_t microticks asm("r4");
Sven Greiner's avatar
Sven Greiner committed
98
register uint8_t ticks asm("r3");
99 100

/**
101
 * Busy wait delay with 10 ms resolution. This function allows to choose the
Sven Greiner's avatar
Sven Greiner committed
102
 * duration during runtime.
103
 *
104
 * @param duration Wait duration in n*10 ms.
105
 */
106
void delay_10ms(uint8_t duration) {
107
  while (duration--) {
108
    _delay_ms(10);
109 110 111
  }
}

Sven Greiner's avatar
Sven Greiner committed
112 113 114 115 116
/**
 * Busy wait one second. Saves some space because call does not require setup
 * of arguments.
 */
void delay_s(void) {
117
  delay_10ms(100);
Sven Greiner's avatar
Sven Greiner committed
118 119
}

120 121 122 123 124
/**
 * Set output, i.e. compare register.
 *
 * @param pwm Raw PWM level.
 */
125
static inline void set_pwm(uint8_t pwm) {
126
  OCR0B = pwm;
Sven Greiner's avatar
Sven Greiner committed
127 128 129 130 131
  if (!pwm) {
    TCCR0A &= ~(1 << COM0B1);
  } else {
    TCCR0A |= (1 << COM0B1);
  }
132 133 134 135 136 137
}

/**
 * Set output to a level as defined in the ramp table. The table is indexed
 * starting from 1 so that level 0 can be used to disable the output.
 *
Sven Greiner's avatar
Sven Greiner committed
138
 * @param level Index in ramp_values or fixed_values depending on fixed_mode
139 140 141 142 143
 */
void set_level(uint8_t level) {
  if (level == 0) {
    set_pwm(0);
  } else {
Sven Greiner's avatar
Sven Greiner committed
144 145 146 147 148
    if (options.fixed_mode) {
      set_pwm(fixed_values[level - 1]);
    } else {
      set_pwm(ramp_values[level - 1]);
    }
149 150 151 152 153 154 155
  }
}

/**
 * Blink or strobe the LED on low intensity.
 *
 * @param count Number of flashes.
156
 * @param speed Duration of a single flash in n*10 ms.
157
 */
158
void blink(uint8_t count, const uint8_t speed) {
Sven Greiner's avatar
Sven Greiner committed
159
  const uint8_t old_pwm = OCR0B;
160 161
  while (count--) {
    set_pwm(40);
162
    delay_10ms(speed);
163
    set_pwm(0);
164 165
    delay_10ms(speed);
    delay_10ms(speed);
166
  }
Sven Greiner's avatar
Sven Greiner committed
167
  set_pwm(old_pwm);
Sven Greiner's avatar
Sven Greiner committed
168 169
}

170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
/**
 * Internal function to erase or dirty write a byte to EEPROM.
 *
 * @param address Address in EEPROM
 * @param data    Data that should be written to address
 * @param eecr    Write (EEPM1) or erase (EEPM0) operation
 */
static void eeprom_erase_or_write_byte(const uint8_t address, const uint8_t data, const uint8_t eecr) {
  while (EECR & (1 << EEPE));
  EECR = eecr;
  EEARL = address;
  EEDR = data;
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    EECR |= (1<<EEMPE);
    EECR |= (1<<EEPE);
  }
}

/**
 * Dirty write a byte to EEPROM without erase cycle.
 *
 * @param address Address in EEPROM
 * @param data    Data that should be written to address
 */
void eeprom_onlywrite_byte(const uint8_t address, const uint8_t data) {
  eeprom_erase_or_write_byte(address, data, (1 << EEPM1));
}

/**
 * Erase a byte in EEPROM without writing anything to it.
 *
 * @param address Address in EEPROM
 */
void eeprom_erase_byte(const uint8_t address) {
  eeprom_erase_or_write_byte(address, 0, (1 << EEPM0));
}

207 208 209 210 211 212 213 214 215 216 217 218 219
/**
 * Write current options to EEPROM.
 */
void save_options(void) {
  // Not necessary to use eeprom_update_byte() because options will usually be
  // written only if they have changed.
  // Using non-atomic erase and write to save some bytes by not using avr-libc
  // method eeprom_write_byte().
  // Store inverted so that a read from uninitialized EEPROM results in 0.
  eeprom_erase_byte(EEPROM_OPTIONS);
  eeprom_onlywrite_byte(EEPROM_OPTIONS, ~options.raw);
}

220 221 222 223 224 225
/**
 * Write current output level to EEPROM with wear leveling.
 */
void save_output(void) {
  if (!options.mode_memory) return;

Sven Greiner's avatar
Sven Greiner committed
226 227 228 229 230 231 232
  if (!output_eeprom_pos) {
    uint8_t i = EEPROM_OUTPUT_WL_BYTES;
    do {
      --i;
      eeprom_erase_byte(i);
    } while (i);
  }
233 234 235

  // Store inverted so that an output of 0 (invalid in this code) can be used
  // to detect unused bytes in the EEPROM (0xFF)
Sven Greiner's avatar
Sven Greiner committed
236
  eeprom_onlywrite_byte(output_eeprom_pos, ~output);
237 238
}

Sven Greiner's avatar
Sven Greiner committed
239 240 241 242 243
/**
 * Restore state from EEPROM.
 */
void restore_state(void) {
  options.raw = ~eeprom_read_byte((uint8_t *)EEPROM_OPTIONS);
244 245 246 247 248 249 250 251 252 253 254 255
  if (!options.mode_memory) return;

  // From back to front find the first byte that is not uninitialized EEPROM
  output_eeprom_pos = EEPROM_OUTPUT_WL_BYTES - 1;
  while (output_eeprom_pos && !(output_eeprom = ~eeprom_read_byte((uint8_t *)output_eeprom_pos))) {
    --output_eeprom_pos;
  }
  if (output_eeprom_pos == EEPROM_OUTPUT_WL_BYTES - 1) {
    output_eeprom_pos = 0;
  } else {
    ++output_eeprom_pos;
  }
Sven Greiner's avatar
Sven Greiner committed
256 257
}

258 259 260 261 262 263 264
/**
 * User interface to toggle an option.
 *
 * @param new_opts New options
 * @param flashes  Number of flashes
 */
void toggle_option(uint8_t new_opts, uint8_t flashes) {
Sven Greiner's avatar
Sven Greiner committed
265
  blink(flashes, FLASH_TIME);
266 267
  uint8_t old_options = options.raw;
  options.raw = new_opts;
Sven Greiner's avatar
Sven Greiner committed
268
  save_options();
Sven Greiner's avatar
Sven Greiner committed
269
  blink(24, FLICKER_TIME);
270
  options.raw = old_options;
Sven Greiner's avatar
Sven Greiner committed
271
  save_options();
Sven Greiner's avatar
Sven Greiner committed
272
  delay_s();
273 274
}

Sven Greiner's avatar
Sven Greiner committed
275
#if defined(LOW_VOLTAGE_PROTECTION) || defined(BATTCHECK)
Sven Greiner's avatar
Sven Greiner committed
276 277 278 279 280
/**
 * Measure battery voltage.
 *
 * @return 8 bit battery voltage as ADC value
 */
Sven Greiner's avatar
Sven Greiner committed
281 282 283 284 285 286 287
static uint8_t battery_voltage(void) {
  ADCSRA |= (1 << ADSC);
  while (ADCSRA & (1 << ADSC));
  return ADCH;
}
#endif  // if defined(LOW_VOLTAGE_PROTECTION) || defined(BATTCHECK)

Sven Greiner's avatar
Sven Greiner committed
288
/**
289 290 291 292 293
 * Timer0 overflow interrupt handler.
 * Frequency will be F_CPU/(8*256) = 2343.75 Hz.
 *
 * One microtick: ~0.427 ms
 * One tick (256 microticks): ~0.109227 s
Sven Greiner's avatar
Sven Greiner committed
294 295 296 297 298 299
 */
ISR(TIM0_OVF_vect) {
  if (!--microticks) {
    ++ticks;
  }

Sven Greiner's avatar
Sven Greiner committed
300
#ifndef DELAY_EVENT
301
  if (ticks == 4) {  // ~440 ms
Sven Greiner's avatar
Sven Greiner committed
302 303
    fast_presses = 0;
  }
Sven Greiner's avatar
Sven Greiner committed
304
#endif  // ifndef DELAY_EVENT
305 306 307 308 309 310
}

/**
 * Entry point.
 */
int main(void) {
311
  microticks = 0;
Sven Greiner's avatar
Sven Greiner committed
312
  ticks = 0;
313

314 315 316 317
  // Fast PWM, system clock with /8 prescaler
  // Frequency will be F_CPU/(8*256) = 2343.75 Hz
  TCCR0A = (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
  TCCR0B = (1 << CS01);
318

Sven Greiner's avatar
Sven Greiner committed
319
  // Enable timer overflow interrupt
320
  TIMSK0 |= (1 << TOIE0);  // TODO Optimization: no or
Sven Greiner's avatar
Sven Greiner committed
321

322
  // Set PWM pin to output
323 324
  DDRB |= (1 << PWM_PIN);  // TODO Optimization: no or

Sven Greiner's avatar
Sven Greiner committed
325 326 327 328 329 330 331 332 333 334 335 336
#if defined(LOW_VOLTAGE_PROTECTION) || defined(BATTCHECK)
  ADMUX =
    (1 << REFS0) |        // Internal 1.1 V reference
    (1 << ADLAR) |        // Left adjust
    VOLTAGE_ADC_CHANNEL;  // ADC MUX channel

  ADCSRA =
    (1 << ADEN) |                 // Enable ADC
    (1 << ADSC) |                 // Start conversion
    (1 << ADPS2) | (1 << ADPS1);  // Set /64 prescaler
#endif  // if defined(LOW_VOLTAGE_PROTECTION) || defined(BATTCHECK)

337
  restore_state();
338

339
  sei();  // Restore state before enabling interrupts (EEPROM read)!
Sven Greiner's avatar
Sven Greiner committed
340

341
  // Cold boot detection
342
  uint8_t coldboot = 0;
343 344 345 346 347 348 349
  for (int i = CBD_BYTES - 1; i >= 0; --i) {
    if (cold_boot_detect[i] != CBD_PATTERN) {
      coldboot = 1;
    }
    cold_boot_detect[i] = CBD_PATTERN;
  }

Sven Greiner's avatar
Sven Greiner committed
350
  if (coldboot) {  // Initialize state after the flashlight was switched off for some time
Sven Greiner's avatar
Sven Greiner committed
351
    state = kDefault;
352
    fast_presses = 0;
Sven Greiner's avatar
Sven Greiner committed
353
    ramping_up = 1;
Sven Greiner's avatar
Sven Greiner committed
354

355 356
    if (options.mode_memory && output_eeprom) {
      output = output_eeprom;
Sven Greiner's avatar
Sven Greiner committed
357
    } else {
358 359 360 361 362
      if (options.start_high) {
        output = options.fixed_mode ? FIXED_SIZE : RAMP_SIZE;
      } else {
        output = 1;
      }
Sven Greiner's avatar
Sven Greiner committed
363
    }
Sven Greiner's avatar
Sven Greiner committed
364
  } else {  // User has tapped the power button
365 366 367 368 369 370
    ++fast_presses;

    // TODO Optimize overflow handling
    if (fast_presses > 10) {
      fast_presses = 10;
    }
371

Sven Greiner's avatar
Sven Greiner committed
372 373 374 375 376 377
#ifdef DELAY_EVENT
    set_level(output);
    delay_10ms(40);
    // TODO Delaying here prevents the light from turning off when entering config mode
#endif  // ifdef DELAY_EVENT

Sven Greiner's avatar
Sven Greiner committed
378
    // Input handling
Sven Greiner's avatar
Sven Greiner committed
379 380
    if (options.fixed_mode) {
      switch (fast_presses) {
381
#ifdef BATTCHECK
Sven Greiner's avatar
Sven Greiner committed
382 383 384
        case FIXED_SIZE + 1:
          state = kBattcheck;
          break;
385
#endif  // ifdef BATTCHECK
386

Sven Greiner's avatar
Sven Greiner committed
387 388 389
        case 10:
          state = kConfig;
          break;
390

Sven Greiner's avatar
Sven Greiner committed
391 392 393
        default:
          output = (output % FIXED_SIZE) + 1;
          state = kFixed;
394
          save_output();
Sven Greiner's avatar
Sven Greiner committed
395 396 397 398 399 400 401
          break;
      }
    } else {
      switch (fast_presses) {
        case 2:
          state = kTurbo;
          break;
402

403
#ifdef BATTCHECK
Sven Greiner's avatar
Sven Greiner committed
404
        case FIXED_SIZE + 1:
Sven Greiner's avatar
Sven Greiner committed
405 406
          state = kBattcheck;
          break;
407
#endif  // ifdef BATTCHECK
Sven Greiner's avatar
Sven Greiner committed
408

Sven Greiner's avatar
Sven Greiner committed
409 410 411 412 413 414 415 416
        case 10:
          state = kConfig;
          break;

        default:
          switch (state) {
            case kRamping:
              state = kFrozen;
417
              save_output();
Sven Greiner's avatar
Sven Greiner committed
418 419 420 421 422 423 424 425
              break;

            default:
              state = kRamping;
              break;
          }
          break;
      }
Sven Greiner's avatar
Sven Greiner committed
426 427
    }
  }
428

Sven Greiner's avatar
Sven Greiner committed
429
  set_level(output);
430

Sven Greiner's avatar
Sven Greiner committed
431 432 433 434
#ifdef DELAY_EVENT
  fast_presses = 0;
#endif  // ifdef DELAY_EVENT

Sven Greiner's avatar
Sven Greiner committed
435 436
  while (1) {
    switch (state) {
Sven Greiner's avatar
Sven Greiner committed
437 438 439
      case kDefault:
        // This is a special state that does nothing but deciding which is the
        // correct state for the current mode (ramping vs fixed)
440 441 442 443 444 445 446 447 448
        if (options.fixed_mode) {
          state = kFixed;
        } else {
          if (options.mode_memory) {
            state = kFrozen;
          } else {
            state = kRamping;
          }
        }
Sven Greiner's avatar
Sven Greiner committed
449 450
        continue;  // Skip main loop cycle and continue with correct mode

Sven Greiner's avatar
Sven Greiner committed
451
      case kRamping:
Sven Greiner's avatar
Sven Greiner committed
452 453 454
        ramping_up =
            (ramping_up && output < RAMP_SIZE) ||
            (!ramping_up && output == 1);
Sven Greiner's avatar
Sven Greiner committed
455

Sven Greiner's avatar
Sven Greiner committed
456
        output += ramping_up ? 1 : -1;
457 458 459
        set_level(output);

        if (options.freeze_on_high && output == RAMP_SIZE) {
Sven Greiner's avatar
Sven Greiner committed
460 461 462 463
          state = kFrozen;
          break;
        }

464
        if (output == RAMP_SIZE) {
Sven Greiner's avatar
Sven Greiner committed
465
          delay_s();
Sven Greiner's avatar
Sven Greiner committed
466
        } else {
467
          delay_10ms(RAMP_TIME*100/RAMP_SIZE);
Sven Greiner's avatar
Sven Greiner committed
468 469 470 471 472 473 474 475
        }

        break;

      case kFrozen:
        break;

      case kTurbo:
Sven Greiner's avatar
Sven Greiner committed
476
        set_pwm(TURBO_PWM);
Sven Greiner's avatar
Sven Greiner committed
477 478
        break;

Sven Greiner's avatar
Sven Greiner committed
479 480 481
      case kFixed:
        break;

482
#ifdef BATTCHECK
Sven Greiner's avatar
Sven Greiner committed
483
      case kBattcheck:
Sven Greiner's avatar
Sven Greiner committed
484 485 486
        set_pwm(0);

        const uint8_t voltage = battery_voltage();
Sven Greiner's avatar
Sven Greiner committed
487

Sven Greiner's avatar
Sven Greiner committed
488
        uint8_t i = sizeof(voltage_table) - 1;
Sven Greiner's avatar
Sven Greiner committed
489 490
        while (voltage < voltage_table[i]) {
          --i;
Sven Greiner's avatar
Sven Greiner committed
491 492
        }

Sven Greiner's avatar
Sven Greiner committed
493
        blink(i+1, FLASH_TIME);
Sven Greiner's avatar
Sven Greiner committed
494
        delay_s();
Sven Greiner's avatar
Sven Greiner committed
495
        break;
496
#endif  // ifdef BATTCHECK
Sven Greiner's avatar
Sven Greiner committed
497

Sven Greiner's avatar
Sven Greiner committed
498
      case kConfig:
499
        set_level(0);
Sven Greiner's avatar
Sven Greiner committed
500
        delay_s();
Sven Greiner's avatar
Sven Greiner committed
501 502 503

        state = kDefault;

Sven Greiner's avatar
Sven Greiner committed
504
        // This assumes that the bit field starts at the least significant bit
505 506 507 508 509
        uint8_t opts = options.raw;
        toggle_option(opts ^ 0b00000001, 1);  // Fixed mode
        toggle_option(opts ^ 0b00000010, 2);  // Mode memory
        toggle_option(opts ^ 0b00000100, 3);  // Freeze on high
        toggle_option(opts ^ 0b00001000, 4);  // Start with high
Sven Greiner's avatar
Sven Greiner committed
510 511

        break;
512
    }
Sven Greiner's avatar
Sven Greiner committed
513

Sven Greiner's avatar
Sven Greiner committed
514 515 516 517 518 519 520 521 522 523 524
#ifdef LOW_VOLTAGE_PROTECTION
    if ((ticks & 0x7F) == 14) {  // Every ~14 s starting after ~1.5 s
      // TODO Take several measurements for noise filtering?
      const uint8_t voltage = battery_voltage();
      if (voltage <= BAT_CRIT) {
        set_pwm(0);
        while (1) {
          // Do not go to sleep, but flash every few seconds to notify the user
          // that the flashlight is still turned on but the battery is dying.
          // TODO If free space in flash, disable as many components as possible
          blink(3, FLASH_TIME/2);
525 526 527
          delay_s();
          delay_s();
          delay_s();
Sven Greiner's avatar
Sven Greiner committed
528 529 530 531 532 533
        }
      } else if (voltage <= BAT_LOW) {
        blink(16, FLICKER_TIME);
      }
    }
#endif  // ifdef LOW_VOLTAGE_PROTECTION
Sven Greiner's avatar
Sven Greiner committed
534
  }
535
}