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

Sounds interesting… like to see how it turns out.

What are you gonna use for a host?

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


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

Yes, that’s exactly what I mean.