STAR Firmware by JonnyC - Source Code and Explanation

Thanks for the help everyone, I’ll try defining the step-down level & adding it in there.

I’ll be flashing a couple this weekend if all goes well.

It should work to simply make the step-down reduce the PWM level without changing the mode index. Like, if the PWM_LVL is less than some minimum (10, maybe?) blink and shut off. Otherwise, tell it to cut the PWM level in half — “PWM_LVL = PWM_LVL >> 1;”.

If there’s only one level defined, any quick tap should return the light to its maximum level… and then it’ll step down when the voltage has been low for a while.

I’m assuming this is a clicky switch.

I think the clicky firmwares already behave in exactly this way (only the momentary firmware actually steps down through the modes IIRC). The issue is that with normal operation the “some minimum” is the lowest defined mode rather than an arbitrary value. After swapping in an arbitrary minimum (10, whatever) the behavior is almost exactly as you described, except that after we reach the (10) or whatever the firmware will maintain a constant output level until it hits the ADC_CRIT value, then it shuts down.

OK, I've defined the stepdown value in this section of the code;

=========================================================================
* Settings to modify per driver
*/

#define VOLTAGE_MON // Comment out to disable
#define STEPDOWN_MINIMUM 50 // Hidden mode level for low voltage step down
// #define MODE_MOON 6 // Can comment out to remove mode, but should be set through soldering stars
// #define MODE_LOW 12 // Can comment out to remove mode
// #define MODE_MED 39 // Can comment out to remove mode
// #define MODE_HIGH_W_TURBO 110 // MODE_HIGH value when turbo is enabled
#define MODE_HIGH 255 // Can comment out to remove mode
// #define MODE_TURBO 255 // Can comment out to remove mode
// #define TURBO_TIMEOUT 240 // How many WTD ticks before before dropping down (.5 sec each)

#define WDT_TIMEOUT 2 // Number of WTD ticks before mode is saved (.5 sec each)

#define ADC_LOW 130 // When do we start ramping
#define ADC_CRIT 120 // When do we shut the light off

/*
* =========================================================================

And altered the code at the end where the stepdown routine is;

// Flash 3 times before lowering
hold_pwm = PWM_LVL;
i = 0;
while (i++<3) {
PWM_LVL = 0;
_delay_ms(250);
PWM_LVL = hold_pwm;
_delay_ms(500);
}
// Lower the mode by half, but don't go below lowest level
if ((PWM_LVL >> 1) < STEPDOWN_MINIMUM) {
PWM_LVL = modes[0];
mode_idx = 0;
} else {
PWM_LVL = (PWM_LVL >> 1);
}
// See if we should change the current mode level if we've gone under the current mode.
if (PWM_LVL < modes[mode_idx]) {
// Lower our recorded mode
mode_idx--;

I'm bench testing this with a partially charged cell, starting around 3.2v, & letting it run down.

I don't get the three flashes + stepdown to the defined value (50.)

Instead, it goes straight to the 10 flashes for critical voltage, but does not power off.

At no point does it step down to the predefined value (50).

What have I done wrong?

I was thinking about that also a few weeks ago, It should be fairly easy to make a voltage divider with the pot, and put it to ADC.

In order to maintain use of the full physical throw of the pot and have the full desired range of PWM levels stay available a voltage divider is no good by itself - it’s not regulated so the maximum input will go down as battery voltage declines. Instead you need a regulated voltage source, probably either an LDO or on a Zener mod driver just add a divider off of the Zener. That way you have fixed target for both high and low voltages when looking at the ADC values.

Oops, my mistake. :zipper_mouth_face:

Also look here:

if (mode_idx == 0 && PWM_LVL <= modes[mode_idx]) {

Looks like it should be:

if (mode_idx == 0 && PWM_LVL <= STEPDOWN_MINIMUM) {

i see 3 options

1. use regulated voltage reference
2. design the circuit so that you are comparing voltage of the battery with the voltage drop of the divider. Did not think this one through thoroughly though.
3. not use the full physical range of the pot, so you have some headroom to make up for the voltage drop (i.e with full battery you need to turn the pot only to 60% of it’s range to get 100% light, and with low battery you need to turn it to 100% to get 100% of the output

also the pot solution might be used with constant DC power supply, so perhaps battery is not the power source in this one at all.

Yes, all correct. (Based on who’s asking, I assume it’s a flashlight. VoB is probably looking for something like a new Lambda VaraPower.)

The ATtiny can be set to use Vcc as the reference voltage, so your #2 option is a good one assuming that don’t need to do battery monitoring. If we do need to do battery monitoring then we have to switch the Vref back and forth. I’ve already written (and posted in this thread) simple/hacky code to do that - it didn’t work correctly. I think we probably need to take two readings every time we switch the Vref, but I haven’t tried that yet.

Thanks wight, I’ll try that out tonight. :beer:

Thanks - that worked, in so far as stepping down to the STEPDOWN_MINIMUM value.

However, it continues to give the 3 periodic warning flashes, until the voltage gets down to around the 2.6 mark, where it seems to eventually peter-out.

It does not appear to be getting to the Critical routine (10 flashes & power down).

As it is now, I'm happy with the operation. I ended up increasing the time between warning flashes, as they were annoyingly frequent.

For educational purposes, I'm still interested as to why it's not getting to the Critical/Shutdown routine.

#ifdef VOLTAGE_MON
if (low_voltage(ADC_LOW)) {
// We need to go to a lower level
if (mode_idx == 0 && PWM_LVL <= STEPDOWN_MINIMUM) {
// Can't go any lower than the lowest mode
// Wait until we hit the critical level before flashing 10 times and turning off
while (!low_voltage(ADC_CRIT));
i = 0;
while (i++<10) {
PWM_LVL = 0;
_delay_ms(250);
PWM_LVL = modes[0];
_delay_ms(500);
}
// Turn off the light
PWM_LVL = 0;
// Disable WDT so it doesn't wake us up
WDT_off();
// Power down as many components as possible
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_mode();
} else {
// Flash 3 times before lowering
hold_pwm = PWM_LVL;
i = 0;
while (i++<3) {
PWM_LVL = 0;
_delay_ms(250);
PWM_LVL = hold_pwm;
_delay_ms(500);
}
// Lower the mode by half, but don't go below lowest level
if ((PWM_LVL >> 1) < STEPDOWN_MINIMUM) {
PWM_LVL = modes[0];
mode_idx = 0;
} else {
PWM_LVL = (PWM_LVL >> 1);
}
// See if we should change the current mode level if we've gone under the current mode.
if (PWM_LVL < modes[mode_idx]) {
// Lower our recorded mode
mode_idx--;
}
}
// Wait 10 seconds before lowering the level again
_delay_ms(10000);
}
#endif

Probably due to an additional oversight on my part. Let’s see where I went wrong.

Hmm. Are you _certain_ that it's stepping down? To me it [now] looks pretty clear that the stepdown process is broken by the changes I suggested. It seems that I missed was this line that will continue to reset the driver to modes[0] (100% duty cycle, 255/255):

// Lower the mode by half, but don't go below lowest level
if ((PWM_LVL >> 1) < STEPDOWN_MINIMUM) {
PWM_LVL = modes[0];
mode_idx = 0;
} else {
PWM_LVL = (PWM_LVL >> 1);
}

As you can see, the line highlighted in red above should continue to reset the mode to mode zero every time we get PWM_LVL down to the point where halving it will take it below STEPDOWN_MINIMUM. Instead we should set it to STEPDOWN_MINIMUM there! Otherwise this conditional never becomes true: "if (mode_idx == 0 && PWM_LVL <= STEPDOWN_MINIMUM)" because we keep stepping down from modes[0] towards STEPDOWN_MINIMUM over and over without ever actually reaching <= STEPDOWN_MINIMUM.

Depending on what you set STEPDOWN_MINIMUM to we should be stepping down several times and then going back up, then doing it again - over and over. At least that's what I see at the moment...

Thanks wight for taking the time to explain.

I've spent a few hours with this today, but I'm not quite there yet....

I was certain it was stepping down visually, but to be sure, I charged my test cell back up to about 3.4v & hooked up an ammeter in series;

Starts out at 0.6A, ---> 3*flash/stepdown to 0.3A, then ~6 mins later, at 0.12A, another 3*flash/stepdown to 0.05A, then a few seconds later, another 3*flashes, & back up to 0.14A, then 3*flash & down to 0.02A ......./end of test...

Yep, just like you described above.

-

I replaced PWM_LVL = modes[0]; with PWM_LVL = STEPDOWN_MINIMUM , but we still don't see the Critical routine.

It now steps down as before, but doesn't jump back up & start over, just continues with the 3 flashes, maintaining the lowest current, in a loop.

Without a Babel fish, I'm still unable to properly follow what's happening to some of the variables :(

I took a couple of wild stabs ; PWM_LVL = STEPDOWN_MINIMUM -1 which done nothing, & also tried removing the line under that mode_idx = 0; which again achieved nothing.


Hmm. Please go ahead and post up your full code in some unmolested way (upload a TXT file some where or use Pastbin or a similar website). Some sites like pastebin also have a highlighter, which is nice. :slight_smile:

Here’s the latest test code, minus the couple of “stabs” I took at it.

:beer:

It appears that our current problem is "wrapping" the unsigned variable 'modes_idx' from zero up to it's maximum value. With what I think we have currently I guess the problem manifests immediately after the first stepdown. Here is the problem section:

// See if we should change the current mode level if we've gone under the current mode.
if (PWM_LVL < modes[mode_idx]) {
// Lower our recorded mode
mode_idx--;
}

Do you see the problem? Of course the conditional is true, PWM_LVL is now below modes[0]. So we subtract 1 from mode_idx. Since it's already zero and it's unsigned (can't be negative), the value wraps to the maximum value, I guess that's 255? After that we probably subtract one from that value every 3 seconds or so... so eventually maybe "if (mode_idx == 0 && PWM_LVL <= modes[mode_idx])" will be true, but not any time soon (13 minutes maybe?).

I think deleting that whole section is fine.

That's the problem on my end; I don't know enough/forgotten too much to follow what's going on.

I taught myself Basic on a CPC464 by reading through the user manual - even got to the stage where I was able to write up some games to give to friends to try out..... but that was over 30 years ago now....

...back to now, & I've re-flashed with the updated code, so far, so good - just waiting for the battery to run down....

Oops, I meant to include this link in my post: c - Is unsigned integer subtraction defined behavior? - Stack Overflow

I had to re-run the test, as I was interrupted…

It now does the stepdown sequence four times, then gives the Critical warning(10 flashes), but does not power down???

Current code.

Well, I can't figure out why the power-down sequence isn't working....

We're reaching the critical voltage routine, getting the 10 flashes, but no power-down?

while (!low_voltage(ADC_CRIT));

i = 0;
while (i++<10) {
PWM_LVL = 0;
_delay_ms(250);
PWM_LVL = modes[0];
_delay_ms(500);
}
// Turn off the light
PWM_LVL = 0;
// Disable WDT so it doesn't wake us up
WDT_off();
// Power down as many components as possible
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_mode();