Firmware (Beta Version) for Caving light (Spot & Flood)

7 posts / 0 new
Last post
YuvalS
YuvalS's picture
Offline
Last seen: 14 hours 3 min ago
Joined: 09/26/2018 - 10:39
Posts: 444
Firmware (Beta Version) for Caving light (Spot & Flood)

Hi all,
I am developing Special firmware for caving lights.
This is only beta version but I want to share it with the community to get some feedback, since it is my first flashlight firmware.

It will be running on BLF A6 driver but I replaced the 7135 chip with another FET and removed the OTC.

Single momentary switch is controlling both channels and all other modes.

 

Basic specification of the firmare:

  1. 3 flood modes (L/M/H)
  2. 2 spot modes (L/H)
  3. 1 Moon mode
  4. Battery check mode (by request)
  5. Low battery level warning 
  6. Cut off for very low voltage
  7. Step down timer for flood_h and spot_h
  8. Locking option
  9. Auto locking timer 

I know some of this features may seem unusual and  are not common in regular flashlights, but in my opinion are really important for caving lights (actually the reason I wrote this FW is because I couldn't find FW that have these features)

 UI

/*
* Project:Or V0.1 (Beta) firmware
*
* 2 channels (Spot & Flood) caving light firmware.
*
* This light uses a modified BLF A6 driver whith 2 FETs for each LED PWM channel
*
* The firmware is based on:
* 1. MiniMo firmware by DrJones
* 2. BLF A6 firmware by ToyKeeper
* 3. Battcheck firmware by ToyKeeper
* (Thank You)
*
* Copyright (C) 2019 Yuval Soboliev
*
* 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
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* Modified BLF A6 FET*2 driver diagram:
*                   ---
*                  -| |- VCC
*                  -| |- Voltage ADC
* Momentary Switch -| |- PWM (FET) Spot
*              GND -| |- PWM (FET) Food
* ---
*
* FUSES
* I use these fuse settings
* Low: 0x75 (4.8MHz CPU without 8x divider, 9.4kHz phase-correct PWM or 18.75kHz fast-PWM)
* High: 0xfd (to enable brownout detection)
*
* VOLTAGE
* Resistor values for voltage divider (reference BLF-VLD README for more info)
* Reference voltage can be anywhere from 1.0 to 1.2, so this cannot be all that accurate
*
* VCC
* |
* Vd (~.25 v drop from protection diode)
* |
* 1912 (R1 19,100 ohms)
* |
* |---- PB2 from MCU
* |
* 4701 (R2 4,700 ohms)
* |
* GND
*
* To find out what values to use, flash the driver with battcheck.hex
* and hook the light up to each voltage you need a value for. This is
* much more reliable than attempting to calculate the values from a
* theoretical formula.
*/

#define F_CPU 4800000 // use fuses low:0x75 high:0xfd
#include <avr/io.h>
#include <util/delay.h>

// For battery check
#define ADC_CHANNEL 0x01 // MUX 01 corresponds with PB2
#define ADC_DIDR ADC1D // Digital input disable bit corresponding with PB2
#define ADC_PRSCL 0x06 // clk/64

// Define value for Battery Check
// These valued measured using TotKeeper battcheck program
#define V40 168 // ADC reading for V=4.0v
#define V38 159 // ADC reading for V=3.8v
#define V35 147 // ADC reading for V=3.5v
#define V30 126 // ADC reading for V=3.5v

// Define values for LED output: 0-OFF , 255-brightest
#define SPOT_H 60
#define SPOT_L 30

#define FLOOD_H 60
#define FLOOD_M 30
#define FLOOD_L 10

#define MOON 1
#define OFF 0

// Define numbers (index) for different modes
#define OFF_MODE 0

#define FLOOD_MODE_1 1
#define FLOOD_MODE_2 2
#define FLOOD_MODE_3 3

#define SPOT_MODE_1 4
#define SPOT_MODE_2 5

#define MOON_MODE 6

// Function that check battery level and return number between 4 to 0
uint8_t get_batt_lvl() {
  uint16_t voltage = 0;
  uint8_t i;

  // ADC on
  ADMUX = (1 << REFS0) | (1 << ADLAR) | ADC_CHANNEL; // 1.1v reference, left-adjust, ADC1/PB2
  DIDR0 |= (1 << ADC_DIDR); // disable digital input on ADC pin to reduce power consumption
  ADCSRA = (1 << ADEN ) | (1 << ADSC ) | ADC_PRSCL; // enable, start, prescale

  ACSR |= (1<<7); //AC off

  for (i=0; i<8; i++) {
    // Start conversion
    ADCSRA |= (1 << ADSC);
    // Wait for completion
    while (ADCSRA & (1 << ADSC));
    // See if voltage is lower than what we were looking for
    voltage += ADCH;
    _delay_ms(50);
  }

  // ADC off
  ADCSRA &= ~(1<<7); //ADC off
  voltage = voltage >> 3;

  if (voltage > V40) return 4; // Almost full
  if (voltage > V38) return 3;
  if (voltage > V35) return 2;
  if (voltage > V30) return 1;
  return 0; // Almost empty
}

// Function to blink LED (used for battery level indicator and button press feedback)
// input for fonction is number of blinks
void blink(uint8_t i) {
  OCR0B = 0;
  OCR0A = 0;

  while (i>0) {
    _delay_ms(400);
    OCR0B = 10;
    _delay_ms(100);
    OCR0B = 0;
    i--;
  }
  _delay_ms(400);
}

int main() {
  // Pins configuration
  // set pin 5 (7135) (flood) to output
  DDRB |= (1 << DDB0);

  // set pin 6 (FET) (spot) to output
  DDRB |= (1 << DDB1);

  // set pin 2 (momentary switch) to pull up (it is input by reset value)
  PORTB |= (1 << PB3);

  // PWN configuration
  TCCR0A=0b10100001;
  TCCR0B=0b00000001; //PWM setup, 9kHz

  // Variables
  uint8_t press_len=0; // used to measure press length (short/medium/long)
  uint8_t press_flag=0; // indicates that button was pressed
  uint8_t lock=0; // indicates that lamp is in lock mode
  uint8_t press_num=0; // count click in order to unlock the lamp (need 4 clicks to unlock)
  uint8_t time_from_last_press=0; // measure the time from last click in order to unlock the lamp (if time is too long stay in lock)

  uint16_t cycle_cnt = 0; // counts cycles of main loop in order to calculate time
  uint8_t ten_sec_cnt = 0; // counts time of 10 seconds

  uint8_t batt_lvl = get_batt_lvl(); // stores the level of the battery
  uint8_t warn_num[]={255,3,0,0,0}; // number of times to warn for each battery level (currently only warns for low and cutoff)

  // set LED levels for each mode
  uint8_t spot_output[]= {OFF, OFF, OFF, OFF, SPOT_L, SPOT_H, MOON};
  uint8_t flood_output[]={OFF, FLOOD_L, FLOOD_M, FLOOD_H, OFF, OFF, OFF};
  uint8_t mode=OFF_MODE; // used to set lamp output


  while(1) { //endless loop
    // Locked Mode
    //////////////////////
    if (lock == 1) {
      // count time from last press
      if (press_num>0) {
        time_from_last_press++; // more 25 ms passed since last press
        if (time_from_last_press==32) { 
          // if too long time has passed reset count
          press_num=0;
          // blink to indicate timeout
          OCR0B=50;
          _delay_ms(25);
          OCR0B=0;
        }
      }
      if ((PINB&8)==0) { // when the button is pressed (PB3 pulled down)
        press_flag=1; // remember that the button was pressed, see below
        press_len++; // length of button press
      }
      else { // button not pressed
        if (press_flag) { // but it was pressed, so it has just been released!
          if (press_len<16) {
            press_num++; // count the number of short clicks
            if (press_num==4){ // if 4 fast clics - release the locking
              lock=0;
              press_num=0;
              mode=FLOOD_MODE_2; // turn on light
            }
          }
          // reset all counters and flags
          press_flag=0;
          press_len=0;
          time_from_last_press=0;
        }
      }
    } // if lock

    // Operational mode
    /////////////////////////
    else {
      if ((PINB&8)==0) { // when the button is pressed (PB3 pulled down)
        press_flag=1; // remember that the button was pressed, see below
        press_len++; // press_len length of button press
        cycle_cnt = 0; // any button press reset timers
        ten_sec_cnt = 0; // any button press reset timers
      }
      else { // button not pressed
        if (press_flag) { // but it was pressed, so it has just been released!
          if (press_len<16) {
            // Short press - cycle between flood modes - always start at medium flood
            switch(mode) {
              case FLOOD_MODE_2 : mode=FLOOD_MODE_3; break;
              case FLOOD_MODE_3 : mode=FLOOD_MODE_1; break;

              default : mode=FLOOD_MODE_2;
            }
          } // if short press
          else {
            if (press_len<40) {
              // Medium Press
              // if off check battery
              if (mode==OFF_MODE) {
                // Battery check
                blink(get_batt_lvl());
                mode=MOON_MODE;
              }
              // if not off cycle between spot modes - always start at high spot
              else {
                if (mode == SPOT_MODE_2) {
                  mode = SPOT_MODE_1;
                }
                else {
                  mode = SPOT_MODE_2;
                }
              }
            } // if medium press
            else {
              // Long Press
              if (mode == OFF_MODE) { // lock the lamp if already in off mode
                lock=1;
                blink(2);
              }
              // turn off all LEDs
              mode = OFF_MODE;
              // Reset warning numbers on turn off
              warn_num[0]=255;
              warn_num[1]=3;
            }
          }
          // reset all flags and timers
          press_flag=0;
          press_len=0;
        }
      }
    }

    // Set output of LEDs according to the mode
    OCR0B=spot_output[mode];
    OCR0A=flood_output[mode];

    _delay_ms(25); //wait a bit before checking again, important for press_lening

    // Before start the loop again measure time and act according to time passed
    cycle_cnt++;
    if (cycle_cnt == 347) { // every 347 iterations is about 10 sec
      cycle_cnt = 0;
      ten_sec_cnt++;

      // Timer to step down from high mode after 30 sec
      if (ten_sec_cnt == 3) {
        if (mode == SPOT_MODE_2 || mode == FLOOD_MODE_3) {
          mode = FLOOD_MODE_2;
        }
      }

      // Timer of 5 minutes
      if (ten_sec_cnt == 30) { // 30 * 10 sec = 5 min
        ten_sec_cnt = 0;

        // automatically lock lamp after 5 min in off mode
        if (mode == OFF_MODE) {
          if (lock == 0) {
            blink(2); // blink twice to indicate locking
            lock = 1;
          }
        }
        else {
          // Every 5 minutes check battery
          batt_lvl = get_batt_lvl();
          if (warn_num[batt_lvl] > 0) { // Only warn warn_num (=3 for level 0 and level 1) times for each level
            warn_num[batt_lvl]--;
            blink(4-batt_lvl);
          }
          // Below cut off - turn light off (can turned on again)
          if (batt_lvl == 0) {
            mode = OFF_MODE;
          }
        }
      } // 5 min timer
    } // 10 sec timer
  } // endless loop
} // main

 
I would appreciate any input regarding the UI or the code.
I still didn't test it in cave but at home it works as expected so if any caver wants to try test it underground it will be great.

Thanks you all 
And special thanks to DrJones and ToyKeeper 

My FW repository

My BLF/OL contest entries: 7th 8th 9th 

Edited by: YuvalS on 01/01/2019 - 06:50
Lightbringer
Lightbringer's picture
Offline
Last seen: 2 hours 50 min ago
Joined: 08/30/2016 - 14:12
Posts: 15566
Location: nyc

Sounds interesting… like to see how it turns out.

09 F9 11 02 9D 74 E3 5B D8 41 56 C5 63 56 88 C0

chadvone
chadvone's picture
Offline
Last seen: 15 hours 28 min ago
Joined: 08/28/2015 - 23:48
Posts: 1998
Location: Iowa

What are you gonna use for a host?

YuvalS
YuvalS's picture
Offline
Last seen: 14 hours 3 min ago
Joined: 09/26/2018 - 10:39
Posts: 444

This driver is for headlamp, I am building the host by myself.
It will be similar to the last version:


 

My FW repository

My BLF/OL contest entries: 7th 8th 9th 

Mike C
Mike C's picture
Offline
Last seen: 6 days 22 hours ago
Joined: 01/22/2014 - 08:03
Posts: 2578
Location: Sweden

Looks good. Is it a single cell driver? If so you can measure MCU voltage by using MCC VCC as voltage reference and then measuring the internal 1.1V. Then you wouldn’t need the voltage divider and you can minimize parasitic drain if cell is not physically locked out.

I’m not up to date with BLF drivers or firmware, I only use my own, so I don’t have much input on how to do that with ATtiny85 (or what ever MCU the BLF A6 uses). The old code I have for those MCUs also uses voltage divider.

YuvalS
YuvalS's picture
Offline
Last seen: 14 hours 3 min ago
Joined: 09/26/2018 - 10:39
Posts: 444

Mike C wrote:
Looks good. Is it a single cell driver? If so you can measure MCU voltage by using MCC VCC as voltage reference and then measuring the internal 1.1V. Then you wouldn't need the voltage divider and you can minimize parasitic drain if cell is not physically locked out. I'm not up to date with BLF drivers or firmware, I only use my own, so I don't have much input on how to do that with ATtiny85 (or what ever MCU the BLF A6 uses). The old code I have for those MCUs also uses voltage divider.


Yes it is single cell,
Do you mean something like this

My FW repository

My BLF/OL contest entries: 7th 8th 9th 

Mike C
Mike C's picture
Offline
Last seen: 6 days 22 hours ago
Joined: 01/22/2014 - 08:03
Posts: 2578
Location: Sweden

YuvalS wrote:
Do you mean something like this

Yes, that’s exactly what I mean.