aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/compile.yml4
-rw-r--r--ChangeLog.md49
-rw-r--r--MODELS6
-rw-r--r--arch/attiny1616.c12
-rw-r--r--arch/attiny1616.h2
-rw-r--r--arch/attiny1634.c12
-rw-r--r--arch/attiny1634.h2
-rw-r--r--arch/attiny85.c10
-rw-r--r--arch/attiny85.h2
-rw-r--r--arch/avr32dd20.c6
-rw-r--r--arch/avr32dd20.h2
-rwxr-xr-xbin/build-all.sh8
-rwxr-xr-xbin/make-release.sh10
-rw-r--r--docs/anduril-manual.md102
-rw-r--r--fsm/adc.c58
-rw-r--r--fsm/adc.h10
-rw-r--r--fsm/chan-aux.c2
-rw-r--r--fsm/chan-rgbaux.c14
-rw-r--r--fsm/eeprom.c8
-rw-r--r--fsm/eeprom.h8
-rw-r--r--fsm/misc.c58
-rw-r--r--fsm/misc.h3
-rw-r--r--fsm/ramping.c35
-rw-r--r--fsm/ramping.h3
-rw-r--r--hw/fireflies/pl47g2/219/anduril.h13
-rw-r--r--hw/fireflies/pl47g2/219/model1
-rw-r--r--hw/hank/emisar-d3aa/anduril.h118
-rw-r--r--hw/hank/emisar-d3aa/arch1
-rw-r--r--hw/hank/emisar-d3aa/hwdef.c217
-rw-r--r--hw/hank/emisar-d3aa/hwdef.h227
-rw-r--r--hw/hank/emisar-d3aa/model1
-rw-r--r--hw/hank/lume-x1/anduril.h114
-rw-r--r--hw/hank/lume-x1/arch1
-rw-r--r--hw/hank/lume-x1/hwdef.c116
-rw-r--r--hw/hank/lume-x1/hwdef.h232
-rw-r--r--hw/hank/lume-x1/model1
-rw-r--r--hw/hank/lume-x1/readme.md34
-rw-r--r--hw/sofirn/sp10-pro/hwdef.h4
-rw-r--r--hw/thefreeman/avr32dd20-devkit/hwdef.c6
-rw-r--r--hw/thefreeman/avr32dd20-devkit/hwdef.h5
-rw-r--r--hw/thefreeman/boost-fwaa-mp3432-hdr-dac-rgb/hwdef.h4
-rw-r--r--hw/wurkkos/ts10/rgbaux-lowfet/README.md12
-rw-r--r--hw/wurkkos/ts10/rgbaux-lowfet/anduril.h16
-rw-r--r--hw/wurkkos/ts10/rgbaux-lowfet/model1
-rw-r--r--hw/wurkkos/ts10/rgbaux/anduril.h3
-rwxr-xr-xmake20
-rw-r--r--ui/anduril/anduril.c47
-rw-r--r--ui/anduril/aux-leds.c52
-rw-r--r--ui/anduril/battcheck-mode.c38
-rw-r--r--ui/anduril/battcheck-mode.h17
-rw-r--r--ui/anduril/config-default.h8
-rw-r--r--ui/anduril/load-save-config-fsm.h4
-rw-r--r--ui/anduril/load-save-config.h25
-rw-r--r--ui/anduril/lockout-mode.c26
-rw-r--r--ui/anduril/off-mode.c26
-rw-r--r--ui/anduril/strobe-modes.c2
-rw-r--r--ui/anduril/tactical-mode.c5
57 files changed, 1633 insertions, 190 deletions
diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml
index 2ba771f..629f7e1 100644
--- a/.github/workflows/compile.yml
+++ b/.github/workflows/compile.yml
@@ -2,7 +2,7 @@ name: build all
on:
# all branches:
- [ push, pull_request ]
+ [ push, pull_request, workflow_dispatch ]
# trunk only:
#push:
# branches: [ "trunk" ]
@@ -52,7 +52,7 @@ jobs:
echo "ARTIFACT_NAME=${GITHUB_WORKFLOW}-$(git rev-parse --short ${GITHUB_SHA})-${GITHUB_RUN_NUMBER}" >> "${GITHUB_ENV}"
- name: Store Artifacts
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: ${{ env.ARTIFACT_NAME }}
if-no-files-found: error
diff --git a/ChangeLog.md b/ChangeLog.md
index a70a398..a60b662 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -14,6 +14,55 @@ formats:
# Next
+# 2025-04-29
+
+Pretty minor release. Things have been weird lately.
+
+General:
+
+- Removed access to strobe/mood modes in Extended Simple UI, to make it more
+ safe and simple by default.
+
+New lights:
+
+- Added &hank-lume-x1 (0281): Covers all of Hank's recent Lume X1-based lights.
+- Added &wurkkos-ts10-rgbaux-lowfet (0712): Variant with lower max power,
+ created at Wurkkos's request, to stop new batches of low-Vf lights from
+ damaging themselves.
+
+# 2024-04-20
+
+General:
+
+- Smooth steps now work in Lockout Mode, if enabled.
+- Made eeprom access more reliable, by waiting for power to stabilize before
+ reads and writes.
+- Increased voltage resolution to 0.02V. It can, for example, read out 1.20V,
+ 1.22V, 1.24V, 1.26V, 1.28V, or 1.30V.
+- Added weak battery detection, to limit power on alkaline, on empty cells, and
+ while powered by a flashing adapter. Should prevent cell overload and magic
+ smoke. Weak battery mode blinks 3X at boot. (d3aa only, so far)
+- Made dark "blip"s work better on some types of regulators.
+- Fixed bug: 3C in Tactical Mode would change the channel when it shouldn't.
+- Fixed bug: Aux channels were off/off/high for levels 0/1/2. Now uses
+ off/low/high.
+- Misc improvements to the build process. Can build with Tactical Mode without
+ Momentary Mode. Can build with newer avr-libc. Version strings calculated
+ better now. Github actions can be run manually.
+- Documentation updates.
+
+New lights:
+
+- Added &hank-emisar-2ch-fet-joined, for the lighted-switch variant of the D4S.
+ It uses a 2-channel driver with only 1 channel of LEDs. (0137)
+- Added &hank-emisar-d3aa, the first "3rd generation" torch (avr32dd20,
+ thefreeman HDR driver). (0161)
+- Added &fireflies-pl47g2-219, a reduced-power version of the PL47G2.
+
+Hardware-specific changes:
+
+- &lumintop-fw3x-lume1: Reduced visible pulsing on low modes.
+
# 2023-12-03
This release is somewhat higher risk than usual, because so many large things
diff --git a/MODELS b/MODELS
index 0a8c244..17536eb 100644
--- a/MODELS
+++ b/MODELS
@@ -1,4 +1,4 @@
-Models: 72
+Models: 78
Model MCU Name
----- --- ----
@@ -23,6 +23,7 @@ Model MCU Name
0142 attiny85 hank-emisar-d18-219
0143 attiny1634 hank-noctigon-m44
0151 attiny1634 hank-emisar-d4k-3ch
+0161 avr32dd20 hank-emisar-d3aa
0211 attiny1634 hank-noctigon-kr4
0212 attiny1634 hank-noctigon-kr4-nofet
0213 attiny1634 hank-noctigon-kr4-219
@@ -42,6 +43,7 @@ Model MCU Name
0272 attiny1634 hank-noctigon-dm11-nofet
0273 attiny1634 hank-noctigon-dm11-boost
0274 attiny1634 hank-noctigon-dm11-sbt90
+0281 avr32dd20 hank-lume-x1
0311 attiny85 lumintop-fw3a
0312 attiny85 lumintop-fw3a-219
0313 attiny85 lumintop-fw3a-nofet
@@ -55,6 +57,7 @@ Model MCU Name
0421 attiny85 fireflies-pl47
0422 attiny85 fireflies-pl47-219
0423 attiny85 fireflies-pl47g2
+0424 attiny85 fireflies-pl47g2-219
0441 attiny85 fireflies-e01
0511 attiny85 mateminco-mf01s
0521 attiny85 mateminco-mf01-mini
@@ -68,6 +71,7 @@ Model MCU Name
0623 attiny1616 sofirn-lt1s-pro
0631 attiny1616 sofirn-sp10-pro
0632 attiny1616 sofirn-sc21-pro
+0712 attiny1616 wurkkos-ts10-rgbaux-lowfet
0713 attiny1616 wurkkos-ts10-rgbaux
0714 attiny1616 wurkkos-ts10
0715 attiny1616 wurkkos-ts25 (may need 0716 / 0717 on some models)
diff --git a/arch/attiny1616.c b/arch/attiny1616.c
index c5499dd..02e64b1 100644
--- a/arch/attiny1616.c
+++ b/arch/attiny1616.c
@@ -104,18 +104,18 @@ inline uint16_t mcu_adc_result_volts() {
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
// In : 65535 * 1.5 / Vbat
- // Out: uint8_t: Vbat * 40
+ // Out: uint8_t: Vbat * 50
// 1.5 = ADC Vref
#if 0
// 1024 = how much ADC resolution we're using (10 bits)
// (12 bits available, but it costs an extra 84 bytes of ROM to calculate)
- uint8_t vbat40 = (uint16_t)(40 * 1.5 * 1024) / (measurement >> 6);
+ uint8_t vbat = (uint16_t)(10 * dV * 1.5 * 1024) / (measurement >> 6);
#else
// ... spend the extra 84 bytes of ROM for better precision
// 4096 = how much ADC resolution we're using (12 bits)
- uint8_t vbat40 = (uint32_t)(40 * 1.5 * 4096) / (measurement >> 4);
+ uint8_t vbat = (uint32_t)(10 * dV * 1.5 * 4096) / (measurement >> 4);
#endif
- return vbat40;
+ return vbat;
}
#if 0 // fine voltage, 0 to 10.24V in 1/6400th V steps
@@ -130,12 +130,12 @@ inline uint16_t mcu_vdd_raw2fine(uint16_t measurement) {
#ifdef USE_VOLTAGE_DIVIDER
inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement) {
// In : 4095 * Vdiv / 1.1V
- // Out: uint8_t: Vbat * 40
+ // Out: uint8_t: Vbat * 50
// Vdiv = Vbat / 4.3 (typically)
// 1.1 = ADC Vref
const uint16_t adc_per_volt =
(((uint16_t)ADC_44 << 4) - ((uint16_t)ADC_22 << 4))
- / (4 * (44-22));
+ / (dV * (44-22));
uint8_t result = measurement / adc_per_volt;
return result;
}
diff --git a/arch/attiny1616.h b/arch/attiny1616.h
index 940973e..711452d 100644
--- a/arch/attiny1616.h
+++ b/arch/attiny1616.h
@@ -85,7 +85,7 @@ inline void mcu_adc_vect_clear();
inline uint16_t mcu_adc_result_temp();
inline uint16_t mcu_adc_result_volts();
-// return Volts * 40, range 0 to 6.375V
+// return Volts * 50, range 0 to 5.10V
#define voltage_raw2cooked mcu_vdd_raw2cooked
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement);
inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement);
diff --git a/arch/attiny1634.c b/arch/attiny1634.c
index e29d1c3..314ca52 100644
--- a/arch/attiny1634.c
+++ b/arch/attiny1634.c
@@ -97,30 +97,30 @@ inline uint16_t mcu_adc_result() {
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
// In : 65535 * 1.1 / Vbat
- // Out: uint8_t: Vbat * 40
+ // Out: uint8_t: Vbat * 50
// 1.1 = ADC Vref
#if 0
// 1024 = how much ADC resolution we're using (10 bits)
// (12 bits available, but it costs an extra 84 bytes of ROM to calculate)
- uint8_t vbat40 = (uint16_t)(40 * 1.1 * 1024) / (measurement >> 6);
+ uint8_t vbat = (uint16_t)(10 * dV * 1.1 * 1024) / (measurement >> 6);
#else
// ... spend the extra 84 bytes of ROM for better precision
// 4096 = how much ADC resolution we're using (12 bits)
- uint8_t vbat40 = (uint32_t)(40 * 1.1 * 4096) / (measurement >> 4);
+ uint8_t vbat = (uint32_t)(10 * dV * 1.1 * 4096) / (measurement >> 4);
#endif
- return vbat40;
+ return vbat;
}
#ifdef USE_VOLTAGE_DIVIDER
inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement) {
// In : 4095 * Vdiv / 1.1V
- // Out: uint8_t: Vbat * 40
+ // Out: uint8_t: Vbat * 50
// Vdiv = Vbat / 4.3 (typically)
// 1.1 = ADC Vref
const uint16_t adc_per_volt =
(((uint16_t)ADC_44 << 4) - ((uint16_t)ADC_22 << 4))
- / (4 * (44-22));
+ / (dV * (44-22));
uint8_t result = measurement / adc_per_volt;
return result;
}
diff --git a/arch/attiny1634.h b/arch/attiny1634.h
index 559d04e..19920fe 100644
--- a/arch/attiny1634.h
+++ b/arch/attiny1634.h
@@ -66,7 +66,7 @@ inline void mcu_adc_off();
inline uint16_t mcu_adc_result();
-// return Volts * 40, range 0 to 6.375V
+// return Volts * 50, range 0 to 5.10V
#define voltage_raw2cooked mcu_vdd_raw2cooked
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement);
inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement);
diff --git a/arch/attiny85.c b/arch/attiny85.c
index 9e298cc..4ca4b87 100644
--- a/arch/attiny85.c
+++ b/arch/attiny85.c
@@ -103,24 +103,24 @@ inline uint16_t mcu_adc_result() { return ADC; }
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
// In : 65535 * 1.1 / Vbat
- // Out: uint8_t: Vbat * 40
+ // Out: uint8_t: Vbat * 50
// 1.1 = ADC Vref
// 1024 = how much ADC resolution we're using (10 bits)
// (12 bits available, but it costs an extra 84 bytes of ROM to calculate)
- uint8_t vbat40 = (uint16_t)(40 * 1.1 * 1024) / (measurement >> 6);
- return vbat40;
+ uint8_t vbat = (uint16_t)(10 * dV * 1.1 * 1024) / (measurement >> 6);
+ return vbat;
}
#ifdef USE_VOLTAGE_DIVIDER
inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement) {
// In : 4095 * Vdiv / 1.1V
- // Out: uint8_t: Vbat * 40
+ // Out: uint8_t: Vbat * 50
// Vdiv = Vbat / 4.3 (typically)
// 1.1 = ADC Vref
const uint16_t adc_per_volt =
(((uint16_t)ADC_44 << 4) - ((uint16_t)ADC_22 << 4))
- / (4 * (44-22));
+ / (dV * (44-22));
uint8_t result = measurement / adc_per_volt;
return result;
}
diff --git a/arch/attiny85.h b/arch/attiny85.h
index 3f6ffcb..06a1061 100644
--- a/arch/attiny85.h
+++ b/arch/attiny85.h
@@ -53,7 +53,7 @@ inline void mcu_adc_off();
inline uint16_t mcu_adc_result();
-// return Volts * 40, range 0 to 6.375V
+// return Volts * 50, range 0 to 5.10V
#define voltage_raw2cooked mcu_vdd_raw2cooked
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement);
inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement);
diff --git a/arch/avr32dd20.c b/arch/avr32dd20.c
index 2ac3526..3ada2ee 100644
--- a/arch/avr32dd20.c
+++ b/arch/avr32dd20.c
@@ -141,10 +141,10 @@ inline uint16_t mcu_adc_result() {
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
// In : 65535 * (Vbat / 10) / 1.024V
- // Out: uint8_t: Vbat * 40
+ // Out: uint8_t: Vbat * 50
// (add 80 to round up near a boundary)
- uint8_t vbat40 = (uint16_t)(measurement + 80) / 160;
- return vbat40;
+ uint8_t vbat50 = (uint16_t)(measurement + 64) / 128;
+ return vbat50;
}
#if 0
diff --git a/arch/avr32dd20.h b/arch/avr32dd20.h
index 09b4096..7d06863 100644
--- a/arch/avr32dd20.h
+++ b/arch/avr32dd20.h
@@ -71,7 +71,7 @@ inline uint16_t mcu_adc_result();
//inline uint16_t mcu_adc_result_temp();
//inline uint16_t mcu_adc_result_volts();
-// return Volts * 40, range 0 to 6.375V
+// return Volts * 50, range 0 to 5.10V
#define voltage_raw2cooked mcu_vdd_raw2cooked
inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement);
diff --git a/bin/build-all.sh b/bin/build-all.sh
index f2420a7..c2256e5 100755
--- a/bin/build-all.sh
+++ b/bin/build-all.sh
@@ -11,12 +11,8 @@ shopt -s globstar
function main {
- if [ "$#" -gt 0 ]; then
- # multiple search terms with "AND"
- SEARCH=( "$@" )
- # memes
- [ "$1" = "me" ] && shift && shift && echo "Make your own $*." && exit 1
- fi
+ # multiple search terms with "AND"
+ [ "$#" -gt 0 ] && SEARCH=( "$@" )
# TODO: detect UI from $0 and/or $*
UI=anduril
diff --git a/bin/make-release.sh b/bin/make-release.sh
index da985d1..6618005 100755
--- a/bin/make-release.sh
+++ b/bin/make-release.sh
@@ -18,9 +18,7 @@ cd "$REPODIR"
#make
# release name
-#REV=$(date +'%Y-%m-%d')
-REV=$(git describe --tags --dirty --match='r2*')
-REV="${REV:1}" # convert 'r2023-...' to '2023-...'
+REV=$(bin/version-string.sh git)
# allow manually specifying a release name
[[ -n "$1" ]] && REV="$1"
@@ -42,6 +40,12 @@ cp -a \
docs/which-hex-file.md \
"$RELDIR"
+# add hardware-specific docs
+for f in $(find hw -name '*.md') ; do
+ d=$(echo "$f" | sed 's/^hw.//; s|/readme||i; s/^/readme./; s|/|-|g;')
+ cp -a "$f" "$RELDIR/$d"
+done
+
# add the .hex files
rename -f 's|hex/anduril.|'"$RELDIR/hex/$RELNAME"'.|;' hex/*.hex
diff --git a/docs/anduril-manual.md b/docs/anduril-manual.md
index b40b454..479289f 100644
--- a/docs/anduril-manual.md
+++ b/docs/anduril-manual.md
@@ -143,17 +143,13 @@ manufacturer's request. This typically includes:
- `Ramp -> 3C`: Toggle smooth or stepped [ramp shape](#ramping--stepped-ramping-modes).
- `Ramp -> 5H`: [Sunset timer](#sunset-timer).
- - `Off -> 3H`: Access the [strobe/mood modes](#strobe--mood-modes).
- - `Off -> 7C/7H`: Change the [aux LED brightness](#aux-leds--button-leds).
+ - `Off -> 7C/7H`: Change the [aux LED pattern](#aux-leds--button-leds).
- `Lockout -> 7C/7H`: Change the [aux LED pattern](#aux-leds--button-leds).
-If your light uses Extended Simple UI, *think twice about letting kids use it*,
-because the strobe/mood modes were not intended to be used in simple mode, and
-can reach full power with no thermal regulation.
-
-It is likely that strobe/mood modes will be removed from Extended Simple UI in
-the future, for safety reasons, or maybe have already been removed. But that
-doesn't help with older firmware, so be careful.
+Old versions (before 2024-08) also allowed access to strobe/mood modes, which
+can be dangerous, so if you have one of those, *think twice about letting kids
+use it*. Those modes were never intended to be child-safe, and can reach full
+power with no thermal regulation.
### Configuring Simple UI
@@ -178,12 +174,17 @@ and they will carry over to Simple UI:
- ramp style (smooth / stepped)
- smooth ramp speed
- ramp-after-moon style
+ - smooth steps
- memory settings
- auto-lock settings
- aux LED settings
- voltage calibration
+ - post-off voltage display
+ - aux led low and high ramp levels
- thermal regulation settings
- hardware-specific "misc menu" settings
+ - channel modes
+ - channel mode for number blinks
Advanced UI
@@ -192,6 +193,11 @@ Advanced UI
Most of the information below this is for the Advanced UI. Anything not
already noted above is blocked in the Simple UI.
+To check which UI the user is in, Simple UI or Advanced UI, enter battery check
+mode with `3C` from Off. In Simple UI, the battery voltage is displayed only
+once, but in Advanced UI, the battery voltage is checked and displayed
+repeatedly.
+
To return to Simple UI from advanced UI, enter 10C while the light is off.
@@ -258,8 +264,8 @@ While the light is on, a few actions are available:
4 = Quarter speed, ~10s.
- `10C`: Activate manual memory and save the current brightness.
-<a name="ramp-extras-config-menu">
- - `10H`: Ramp extras config menu.
+ Also saves current channel mode, on multi-channel lights.
+ - `10H`: Ramp extras config menu. <a name="ramp-extras-config-menu" />
- Item 1: Disable manual memory and go back to automatic memory.
(doesn't matter what value the user enters at the prompt)
- Item 2: Configure the manual memory timer.
@@ -422,14 +428,30 @@ In more detail, this is what each blinky / utility mode does:
### Battery check:
-Blinks out the battery voltage per cell. Full is 4.2V, empty is
-about 3.0V. The light blinks the whole-number digit first, pauses,
-then blinks out the "tenths" digit. Then a longer pause, and it
-repeats.
-So for 4.2V, it would be "blink-blink-blink-blink .. blink-blink".
+Blinks out the battery voltage per cell. Full is 4.20V, empty is
+about 3.00V. The light blinks the whole-number digit first, pauses,
+then blinks out the "tenths" digit, pauses, then blinks out the "hundredths"
+digit, in 0.02V steps. So for 4.16V, it would be "4 blinks, 1 blink,
+6 blinks". Then if it is in Advanced UI, it pauses for a bit longer and
+repeats. In Simple UI, it turns off after one readout.
A "zero" digit is represented by a very quick blink.
+The battery check format has changed a few times:
+
+ - For Anduril 2 from 2024-04 or later, the battery voltage resolution is
+ 0.02V steps (the last digit can be 0, 2, 4, 6, or 8).
+
+ - For Anduril 2 from 2023-12 or later, the battery voltage resolution is
+ 0.025V steps (the last digit can be 0, 2, 5, or 7).
+
+ - For Anduril 2 from 2023-12 or earlier, the battery voltage resolution is
+ 0.1V, so the light blinks the whole-number digit first, pauses, then blinks
+ out the "tenths" digit. Then a longer pause, and it repeats.
+
+ - On old attiny85 lights with only 8 KiB of ROM, battery voltage resolution
+ is 0.1V, even on newer versions of Anduril.
+
On lights with more than one set of LEDs, pressing `3C` during batt
check mode can select which set of LEDs (which channel mode) it uses
to blink out numbers.
@@ -454,11 +476,23 @@ The voltage config menu has these settings:
`12C`: +0.25V
`13C`: +0.30V
- 2. Post-off voltage display timeout. (only on lights with RGB aux)
+ 2. Post-off voltage display timeout. (only on lights with RGB aux)
This setting determines how many seconds the RGB aux LEDs
display the voltage color after the torch goes to sleep. Click
once per desired second, or zero times to turn this function
- off.
+ off. The default is 4 seconds.
+
+ 3. Aux low ramp level. Controls behavior of aux button LEDs while the main
+ LEDs are on. Below this ramp level, button LEDs will not be lit up while
+ the main LEDs are on. At or above this level, button LEDs light up at the
+ "low" brightness level. Setting it to 0 keeps the button LEDs off
+ completely while the main LEDs are on.
+ Also controls brightness of post-off voltage display.
+
+ 4. Aux high ramp level. At or above this ramp level, button LEDs light up at
+ the "high" brightness level. Setting it to 0 disables button's high aux
+ mode while the main LEDs are on.
+ Also controls brightness of post-off voltage display.
### Temperature check:
@@ -640,6 +674,8 @@ This assumes the light has a ramp 150 levels long. Strobe modes start
at the ramp size plus 1, so it may be different if a light has a
different ramp size.
+In Tactical Mode, the aux LED settings are inherited from Lockout Mode.
+
Configuration Menus
-------------------
@@ -956,15 +992,22 @@ This setting only applies to modes with channel ramping (i.e. tint
ramping), and only when that mode uses the default `3H` event handler.
Custom channel modes may work differently.
+On lights with channel modes, manual memory (`Ramp -> 10C`) saves the
+current brightness *and* channel mode.
+
+
FAQ
---
+
* Q: Why do the aux LEDs come on whenever I switch the light off, regardless
-of aux settings?
+ of aux settings?
* A: This is the post-off voltage display feature. It can be configured or
-disabled under [battery check](#battery-check) mode.
+ disabled under [battery check](#battery-check) mode.
+
* Q: What can I do to contribute to Anduril development?
* A: See [Contributing](https://github.com/ToyKeeper/anduril#contributing).
+
UI Reference Table
------------------
@@ -1019,6 +1062,7 @@ This is a table of all button mappings in Anduril, in one place:
| | | | 2: ceiling
| | | | 3: speed / steps
| Ramp | Full | `10C` | Turn on manual memory and save current brightness
+| | | | (and current channel mode)
| Ramp | Full | `10H` | Ramp Extras config menu:
| | | | 1: switch to automatic mem, not manual mem
| | | | 2: set manual mem timeout
@@ -1077,14 +1121,18 @@ This is a table of all button mappings in Anduril, in one place:
| Batt check | Full | `3C` | Next channel mode (for number blinks only)
| Batt check | Full | `7H` | Voltage config menu
| | | | 1: voltage correction factor
-| | | | ...
-| | | | 5: -0.10V
-| | | | 6: -0.05V
-| | | | 7: no correction
-| | | | 8: +0.05V
-| | | | 9: +0.10V
-| | | | ...
+| | | | ... 5: -0.10V
+| | | | ... 6: -0.05V
+| | | | ... 7: no correction
+| | | | ... 8: +0.05V
+| | | | ... 9: +0.10V
| | | | 2: post-off voltage display seconds
+| | | | 3: aux low ramp level
+| | | | ... 0: disabled
+| | | | ... 1+: light up at this ramp level
+| | | | 4: aux high ramp level
+| | | | ... 0: disabled
+| | | | ... 1+: brighter at this ramp level
| Mode | UI | Button | Action
| :--- | :-- | ------: | :-----
diff --git a/fsm/adc.c b/fsm/adc.c
index fbe84a1..e0bacb6 100644
--- a/fsm/adc.c
+++ b/fsm/adc.c
@@ -209,8 +209,8 @@ static void ADC_voltage_handler() {
#endif
else measurement = adc_smooth[0];
- // convert raw ADC value to FSM voltage units: Volts * 40
- // 0 .. 200 = 0.0V .. 5.0V
+ // convert raw ADC value to FSM voltage units: Volts * 50
+ // 0 .. 250 = 0.0V .. 5.0V
voltage = voltage_raw2cooked(measurement)
+ (VOLTAGE_FUDGE_FACTOR << 1)
#ifdef USE_VOLTAGE_CORRECTION
@@ -392,46 +392,46 @@ static void ADC_temperature_handler() {
#ifdef USE_BATTCHECK
#ifdef BATTCHECK_4bars
PROGMEM const uint8_t voltage_blinks[] = {
- 4*30,
- 4*35,
- 4*38,
- 4*40,
- 4*42,
- 255,
+ 30*dV,
+ 35*dV,
+ 38*dV,
+ 40*dV,
+ 42*dV,
+ 255,
};
#endif
#ifdef BATTCHECK_6bars
PROGMEM const uint8_t voltage_blinks[] = {
- 4*30,
- 4*34,
- 4*36,
- 4*38,
- 4*40,
- 4*41,
- 4*43,
- 255,
+ 30*dV,
+ 34*dV,
+ 36*dV,
+ 38*dV,
+ 40*dV,
+ 41*dV,
+ 43*dV,
+ 255,
};
#endif
#ifdef BATTCHECK_8bars
PROGMEM const uint8_t voltage_blinks[] = {
- 4*30,
- 4*33,
- 4*35,
- 4*37,
- 4*38,
- 4*39,
- 4*40,
- 4*41,
- 4*42,
- 255,
+ 30*dV,
+ 33*dV,
+ 35*dV,
+ 37*dV,
+ 38*dV,
+ 39*dV,
+ 40*dV,
+ 41*dV,
+ 42*dV,
+ 255,
};
#endif
void battcheck() {
#ifdef BATTCHECK_VpT
- blink_num(voltage / 4);
+ blink_num(voltage / dV);
#ifdef USE_EXTRA_BATTCHECK_DIGIT
- // 0 1 2 3 --> 0 2 5 7, representing x.x00 x.x25 x.x50 x.x75
- blink_num(((voltage % 4)<<1) + ((voltage % 4)>>1));
+ // 0.02V precision, 0 1 2 3 4 remainder -> .00 .02 .04 .06 .08V
+ blink_num((voltage % dV) * (10/dV));
#endif
#else
uint8_t i;
diff --git a/fsm/adc.h b/fsm/adc.h
index 02e33f8..5dec6c5 100644
--- a/fsm/adc.h
+++ b/fsm/adc.h
@@ -4,6 +4,10 @@
#pragma once
+// voltage is 0.00V to 5.10V in 0.02V steps, from 0 to 255
+// so one deci-Volt is 5 steps
+#define dV 5
+
#if defined(USE_LVP) || defined(USE_THERMAL_REGULATION)
// use raw value instead of lowpassed value for the next N measurements
// (2 = 1 for voltage + 1 for temperature)
@@ -15,13 +19,13 @@ volatile uint8_t adc_reset = 2;
#ifndef VOLTAGE_WARNING_SECONDS
#define VOLTAGE_WARNING_SECONDS 5
#endif
-// low-battery threshold in volts * 10
+// low-battery threshold in volts * 50
#ifndef VOLTAGE_LOW
-#define VOLTAGE_LOW (4*29)
+#define VOLTAGE_LOW (29*dV)
#endif
// battery is low but not critical
#ifndef VOLTAGE_RED
-#define VOLTAGE_RED (4*33)
+#define VOLTAGE_RED (33*dV)
#endif
// MCU sees voltage 0.X volts lower than actual, add X/2 to readings
#ifndef VOLTAGE_FUDGE_FACTOR
diff --git a/fsm/chan-aux.c b/fsm/chan-aux.c
index e04e6a2..239316a 100644
--- a/fsm/chan-aux.c
+++ b/fsm/chan-aux.c
@@ -4,7 +4,7 @@
#pragma once
void set_level_aux(uint8_t level) {
- indicator_led(!(!(level)) << 1); // high (or off)
+ indicator_led((!(!(level)) << 1) + 1); // high (level > 0) or low
}
bool gradual_tick_null(uint8_t gt) { return true; } // do nothing
diff --git a/fsm/chan-rgbaux.c b/fsm/chan-rgbaux.c
index 19d18a6..a66c29e 100644
--- a/fsm/chan-rgbaux.c
+++ b/fsm/chan-rgbaux.c
@@ -4,31 +4,31 @@
#pragma once
void set_level_auxred(uint8_t level) {
- rgb_led_set(!(!(level)) * 0b000010); // red, high (or off)
+ rgb_led_set(0b000001 << !(!(level))); // red, high (level > 0) or low
}
void set_level_auxyel(uint8_t level) {
- rgb_led_set(!(!(level)) * 0b001010); // red+green, high (or off)
+ rgb_led_set(0b000101 << !(!(level))); // red+green, high (level > 0) or low
}
void set_level_auxgrn(uint8_t level) {
- rgb_led_set(!(!(level)) * 0b001000); // green, high (or off)
+ rgb_led_set(0b000100 << !(!(level))); // green, high (level > 0) or low
}
void set_level_auxcyn(uint8_t level) {
- rgb_led_set(!(!(level)) * 0b101000); // green+blue, high (or off)
+ rgb_led_set(0b010100 << !(!(level))); // green+blue, high (level > 0) or low
}
void set_level_auxblu(uint8_t level) {
- rgb_led_set(!(!(level)) * 0b100000); // blue, high (or off)
+ rgb_led_set(0b010000 << !(!(level))); // blue, high (level > 0) or low
}
void set_level_auxprp(uint8_t level) {
- rgb_led_set(!(!(level)) * 0b100010); // red+blue, high (or off)
+ rgb_led_set(0b010001 << !(!(level))); // red+blue, high (level > 0) or low
}
void set_level_auxwht(uint8_t level) {
- rgb_led_set(!(!(level)) * 0b101010); // red+green+blue, high (or off)
+ rgb_led_set(0b010101 << !(!(level))); // red+green+blue, high (level > 0) or low
}
bool gradual_tick_null(uint8_t gt) { return true; } // do nothing
diff --git a/fsm/eeprom.c b/fsm/eeprom.c
index 8f8bd67..436a736 100644
--- a/fsm/eeprom.c
+++ b/fsm/eeprom.c
@@ -14,7 +14,7 @@ uint8_t eeprom[EEPROM_BYTES];
#endif
uint8_t load_eeprom() {
- #if defined(LED_ENABLE_PIN) || defined(LED2_ENABLE_PIN)
+ #ifdef USE_EEP_DELAY
delay_4ms(2); // wait for power to stabilize
#endif
@@ -32,7 +32,7 @@ uint8_t load_eeprom() {
}
void save_eeprom() {
- #if defined(LED_ENABLE_PIN) || defined(LED2_ENABLE_PIN)
+ #ifdef USE_EEP_DELAY
delay_4ms(2); // wait for power to stabilize
#endif
@@ -54,7 +54,7 @@ uint8_t eeprom_wl[EEPROM_WL_BYTES];
uint8_t * eep_wl_prev_offset;
uint8_t load_eeprom_wl() {
- #if defined(LED_ENABLE_PIN) || defined(LED2_ENABLE_PIN)
+ #ifdef USE_EEP_DELAY
delay_4ms(2); // wait for power to stabilize
#endif
@@ -83,7 +83,7 @@ uint8_t load_eeprom_wl() {
}
void save_eeprom_wl() {
- #if defined(LED_ENABLE_PIN) || defined(LED2_ENABLE_PIN)
+ #ifdef USE_EEP_DELAY
delay_4ms(2); // wait for power to stabilize
#endif
diff --git a/fsm/eeprom.h b/fsm/eeprom.h
index 1e10fd2..d5f0363 100644
--- a/fsm/eeprom.h
+++ b/fsm/eeprom.h
@@ -55,3 +55,11 @@
// if this marker isn't found, the eeprom is assumed to be blank
#define EEP_MARKER 0b10100101
+// wait a few ms before eeprom operations, to wait for power to stabilize
+// (otherwise reads or writes can get corrupt data)
+// (not necessary on some hardware,
+// but enabled by default when there's space)
+#if defined(LED_ENABLE_PIN) || defined(LED2_ENABLE_PIN) || (ROM_SIZE > 10000)
+ #define USE_EEP_DELAY
+#endif
+
diff --git a/fsm/misc.c b/fsm/misc.c
index fa8ddd7..915dbd4 100644
--- a/fsm/misc.c
+++ b/fsm/misc.c
@@ -65,6 +65,16 @@ uint8_t blink_digit(uint8_t num) {
return nice_delay_ms(BLINK_SPEED * 8 / 12);
}
+#ifdef USE_LONG_BLINK_FOR_NEGATIVE_SIGN
+void blink_negative() {
+ // "negative" symbol gets a single long blink
+ uint8_t ontime = BLINK_SPEED * 2 / 12;
+ set_level(BLINK_BRIGHTNESS);
+ nice_delay_ms(ontime * 3);
+ set_level(0);
+ nice_delay_ms(ontime * 5);
+}
+#endif
#endif
#ifdef USE_BLINK_BIG_NUM
@@ -245,6 +255,50 @@ void rgb_led_set(uint8_t value) {
// FIXME: move this logic to arch/*
#if (MCU==0x1616) || (MCU==0x32dd20) // ATTINY816, 817, etc
+ // FIXME: this *really* needs to be moved to somewhere hardware-specific
+ #ifdef AUXLED_RGB_DIFFERENT_PORTS
+
+ case 0: // LED off
+ if (i == 0) {
+ AUXLED_R_PORT.DIRSET = (1 << pin);
+ AUXLED_R_PORT.OUTCLR = (1 << pin);
+ } else if (i == 1) {
+ AUXLED_G_PORT.DIRSET = (1 << pin);
+ AUXLED_G_PORT.OUTCLR = (1 << pin);
+ } else if (i == 2) {
+ AUXLED_B_PORT.DIRSET = (1 << pin);
+ AUXLED_B_PORT.OUTCLR = (1 << pin);
+ }
+ break;
+
+ case 1: // LED low
+ if (i == 0) {
+ AUXLED_R_PORT.DIRCLR = (1 << pin);
+ *((uint8_t *)&AUXLED_R_PORT + 0x10 + pin) = PORT_PULLUPEN_bm;
+ } else if (i == 1) {
+ AUXLED_G_PORT.DIRCLR = (1 << pin);
+ *((uint8_t *)&AUXLED_G_PORT + 0x10 + pin) = PORT_PULLUPEN_bm;
+ } else if (i == 2) {
+ AUXLED_B_PORT.DIRCLR = (1 << pin);
+ *((uint8_t *)&AUXLED_B_PORT + 0x10 + pin) = PORT_PULLUPEN_bm;
+ }
+ break;
+
+ default: // LED high
+ if (i==0) {
+ AUXLED_R_PORT.DIRSET = (1 << pin);
+ AUXLED_R_PORT.OUTSET = (1 << pin);
+ } else if (i==1) {
+ AUXLED_G_PORT.DIRSET = (1 << pin);
+ AUXLED_G_PORT.OUTSET = (1 << pin);
+ } else if (i==2) {
+ AUXLED_B_PORT.DIRSET = (1 << pin);
+ AUXLED_B_PORT.OUTSET = (1 << pin);
+ }
+ break;
+
+ #else // not ifdef AUXLED_RGB_DIFFERENT_PORTS
+
case 0: // LED off
AUXLED_RGB_PORT.DIRSET = (1 << pin); // set as output
AUXLED_RGB_PORT.OUTCLR = (1 << pin); // set output low
@@ -258,8 +312,10 @@ void rgb_led_set(uint8_t value) {
AUXLED_RGB_PORT.DIRSET = (1 << pin); // set as output
AUXLED_RGB_PORT.OUTSET = (1 << pin); // set as high
break;
+
+ #endif
- #else
+ #else // not #if (MCU==0x1616) || (MCU==0x32dd20)
case 0: // LED off
AUXLED_RGB_DDR &= 0xff ^ (1 << pin);
diff --git a/fsm/misc.h b/fsm/misc.h
index ba1f8d9..0b13496 100644
--- a/fsm/misc.h
+++ b/fsm/misc.h
@@ -28,6 +28,9 @@ void auto_clock_speed();
#ifdef USE_BLINK_NUM
//#define USE_BLINK
uint8_t blink_num(uint8_t num);
+#ifdef USE_LONG_BLINK_FOR_NEGATIVE_SIGN
+void blink_negative();
+#endif
#endif
/*
diff --git a/fsm/ramping.c b/fsm/ramping.c
index adc8acb..f8ca4ec 100644
--- a/fsm/ramping.c
+++ b/fsm/ramping.c
@@ -8,14 +8,20 @@
#ifdef HAS_AUX_LEDS
inline void set_level_aux_leds(uint8_t level) {
+ #ifdef USE_AUX_THRESHOLD_CONFIG
+ #define AUX_BRIGHTNESS ((level > cfg.button_led_low_ramp_level) \
+ << (level > cfg.button_led_high_ramp_level))
+ #else
+ #define AUX_BRIGHTNESS ((level > 0) + (level > DEFAULT_LEVEL))
+ #endif
#ifdef USE_INDICATOR_LED_WHILE_RAMPING
// use side-facing aux LEDs while main LEDs are on
if (! go_to_standby) {
#ifdef USE_INDICATOR_LED
- indicator_led((level > 0) + (level > DEFAULT_LEVEL));
+ indicator_led(AUX_BRIGHTNESS);
#endif
#ifdef USE_BUTTON_LED
- button_led_set((level > 0) + (level > DEFAULT_LEVEL));
+ button_led_set(AUX_BRIGHTNESS);
#endif
}
#else // turn off front-facing aux LEDs while main LEDs are on
@@ -27,12 +33,15 @@ inline void set_level_aux_leds(uint8_t level) {
#ifdef USE_AUX_RGB_LEDS
rgb_led_set(0);
#ifdef USE_BUTTON_LED
- button_led_set((level > 0) + (level > DEFAULT_LEVEL));
+ button_led_set(AUX_BRIGHTNESS);
#endif
#endif
}
#endif
#endif
+ #ifdef AUX_BRIGHTNESS
+ #undef AUX_BRIGHTNESS
+ #endif
}
#endif // ifdef HAS_AUX_LEDS
@@ -41,15 +50,28 @@ inline void set_level_aux_leds(uint8_t level) {
#include "anduril/aux-leds.h" // for rgb_led_voltage_readout()
inline void set_level_aux_rgb_leds(uint8_t level) {
if (! go_to_standby) {
+ #ifdef USE_AUX_THRESHOLD_CONFIG
+ if (level > cfg.button_led_low_ramp_level) {
+ rgb_led_voltage_readout(level > cfg.button_led_high_ramp_level);
+ }
+ #else
if (level > 0) {
rgb_led_voltage_readout(level > USE_AUX_RGB_LEDS_WHILE_ON);
- } else {
+ }
+ #endif
+ else {
rgb_led_set(0);
}
// some drivers can be wired with RGB or single color to button
// ... so support both even though only one is connected
#ifdef USE_BUTTON_LED
+ #ifdef USE_AUX_THRESHOLD_CONFIG
+ button_led_set(
+ (level > cfg.button_led_low_ramp_level)
+ << (level > cfg.button_led_high_ramp_level));
+ #else
button_led_set((level > 0) + (level > DEFAULT_LEVEL));
+ #endif
#endif
}
}
@@ -57,6 +79,11 @@ inline void set_level_aux_rgb_leds(uint8_t level) {
void set_level(uint8_t level) {
+ #ifdef USE_RAMP_LEVEL_HARD_LIMIT
+ if (ramp_level_hard_limit && (level > ramp_level_hard_limit))
+ level = ramp_level_hard_limit;
+ #endif
+
#ifdef USE_JUMP_START
// maybe "jump start" the engine, if it's prone to slow starts
// (pulse the output high for a moment to wake up the power regulator)
diff --git a/fsm/ramping.h b/fsm/ramping.h
index c4b7d48..f542bd2 100644
--- a/fsm/ramping.h
+++ b/fsm/ramping.h
@@ -10,6 +10,9 @@
uint8_t actual_level = 0;
// the level used before actual
uint8_t prev_level = 0;
+#ifdef USE_RAMP_LEVEL_HARD_LIMIT
+uint8_t ramp_level_hard_limit = 0;
+#endif
void set_level(uint8_t level);
//void set_level_smooth(uint8_t level);
diff --git a/hw/fireflies/pl47g2/219/anduril.h b/hw/fireflies/pl47g2/219/anduril.h
new file mode 100644
index 0000000..98cd6ea
--- /dev/null
+++ b/hw/fireflies/pl47g2/219/anduril.h
@@ -0,0 +1,13 @@
+// Fireflies PL47G2-219B config options for Anduril
+// Copyright (C) 2019-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+// same as PL47G2 but with FET modes limited to 67% power
+// to avoid destroying the LEDs
+#include "fireflies/pl47g2/anduril.h"
+
+#undef PWM1_LEVELS
+#undef PWM2_LEVELS
+#define PWM1_LEVELS 1,1,2,2,3,3,4,4,5,5,6,6,7,8,8,9,10,10,11,12,13,14,15,16,17,18,19,21,22,23,25,26,27,29,31,32,34,36,38,40,42,44,46,49,51,54,56,59,62,65,68,71,74,78,81,85,89,93,97,101,106,110,115,120,125,130,136,141,147,153,160,166,173,180,187,195,202,210,219,227,236,245,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,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,255,255,255,255,255,255,255,255,255,255,255,255,255,255
+#define PWM2_LEVELS 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,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,1,2,3,4,5,6,7,8,9,10,11,12,13,15,16,17,19,20,22,23,25,27,28,30,31,33,35,37,39,41,43,45,47,50,52,55,57,60,63,65,68,71,74,77,80,83,87,90,93,97,101,105,108,112,116,121,125,129,134,139,143,148,153,159,164,169
diff --git a/hw/fireflies/pl47g2/219/model b/hw/fireflies/pl47g2/219/model
new file mode 100644
index 0000000..6e6161f
--- /dev/null
+++ b/hw/fireflies/pl47g2/219/model
@@ -0,0 +1 @@
+0424
diff --git a/hw/hank/emisar-d3aa/anduril.h b/hw/hank/emisar-d3aa/anduril.h
new file mode 100644
index 0000000..e4e4458
--- /dev/null
+++ b/hw/hank/emisar-d3aa/anduril.h
@@ -0,0 +1,118 @@
+// Emisar D3AA config options for Anduril
+// Copyright (C) 2023 thefreeman, Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#define HWDEF_H hank/emisar-d3aa/hwdef.h
+#include "hank/anduril.h"
+
+// HPRsense : 4.2+0.3+20 = 24.5mR
+// Vsense=42.46mV, R1= 165k
+// LPRsense : 3R3
+// HDR ratio: 131.5
+// transition DAC level 20, ramp level 48
+// fifth power ramp 0.02mA to 2001mA
+
+#define RAMP_SIZE 150
+
+// 4 ramp segments:
+// - low 1.024V
+// - low 2.5 V
+// - high 1.024V
+// - high 2.5 V
+// HDR ratio: 131.5
+// PWM1: DAC Data
+// 131.5 * 1024 * 2.5 = 336640 total dimming ratio
+// ./bin/dac-scale.py $( ./bin/level_calc.py 4.106 1 150 7135 3 0.01 1400 --pwm 336640 | grep PWM1 | cut -d : -f 2- )
+// top level for each "gear": 30 40 119 150
+#define PWM1_LEVELS \
+ 3, 5, 6, 9, 12, 16, 21, 28, 35, 44, 55, 68, 83, 101, 121, 144, 170, 200, 234, 271, 313, 360, 412, 470, 534, 603, 680, 764, 855, 954, \
+ 434, 482, 534, 590, 650, 715, 784, 858, 938,1023, \
+ 20, 21, 23, 25, 27, 30, 32, 35, 37, 40, 43, 46, 49, 53, 56, 60, 64, 68, 73, 77, 82, 87, 93, 98, 104, 110, 116, 123, 129, 137, 144, 152, 160, 168, 177, 185, 195, 204, 214, 225, 235, 247, 258, 270, 282, 295, 308, 322, 336, 350, 365, 381, 396, 413, 430, 447, 465, 484, 503, 522, 543, 563, 585, 607, 629, 653, 677, 701, 726, 752, 779, 806, 834, 863, 892, 923, 954, 985,1018, \
+ 430, 444, 459, 473, 488, 504, 520, 536, 552, 569, 587, 604, 622, 641, 660, 679, 699, 719, 740, 761, 782, 804, 827, 849, 873, 897, 921, 946, 971, 997,1023
+#define PWM2_LEVELS \
+ V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, \
+ V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, \
+ V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, \
+ V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25
+#define MAX_1x7135 40
+#define HDR_ENABLE_LEVEL_MIN 41
+
+#define DEFAULT_LEVEL 50
+
+// no PWM, so MCU clock speed can be slow
+#define HALFSPEED_LEVEL 41
+#define QUARTERSPEED_LEVEL 40 // seems to run fine at 10kHz/4, try reducing more?
+
+// should be about 300 mA or ~100 lm,
+// to avoid overloading firmware flashing adapters
+#define WEAK_BATTERY_TEST_MAX_LEVEL 75
+
+#define RAMP_SMOOTH_FLOOR 1
+#define RAMP_SMOOTH_CEIL 130 // 50% power
+// 10 30 [50] 70 90 110 130
+#define RAMP_DISCRETE_FLOOR 10
+#define RAMP_DISCRETE_CEIL 130
+#define RAMP_DISCRETE_STEPS 7
+
+// 10 [40] 70 100 130
+#define SIMPLE_UI_FLOOR 10
+#define SIMPLE_UI_CEIL 130
+#define SIMPLE_UI_STEPS 5
+
+// don't blink mid-ramp
+#ifdef BLINK_AT_RAMP_MIDDLE
+#undef BLINK_AT_RAMP_MIDDLE
+#endif
+
+// thermal config
+
+// temperature limit
+#define THERM_FASTER_LEVEL 130 // stop panicking at 50%/1A
+#define MIN_THERM_STEPDOWN MAX_1x7135
+
+
+// UI
+
+// Allow 3C in Simple UI for switching between smooth and stepped ramping
+#define USE_SIMPLE_UI_RAMPING_TOGGLE
+
+#define DEFAULT_2C_STYLE 1 // enable 2 click turbo
+
+
+// AUX
+
+#define USE_BUTTON_LED
+
+// this light has three aux LED channels: R, G, B
+#define USE_AUX_RGB_LEDS
+
+// show each channel while it scroll by in the menu
+#define USE_CONFIG_COLORS
+
+// blink numbers on the main LEDs by default (but allow user to change it)
+#define DEFAULT_BLINK_CHANNEL CM_MAIN
+
+// use aux red + aux blue for police strobe
+#define USE_POLICE_COLOR_STROBE_MODE
+#define POLICE_STROBE_USES_AUX
+#define POLICE_COLOR_STROBE_CH1 CM_AUXRED
+#define POLICE_COLOR_STROBE_CH2 CM_AUXBLU
+
+// the aux LEDs are front-facing, so turn them off while main LEDs are on
+#ifdef USE_INDICATOR_LED_WHILE_RAMPING
+#undef USE_INDICATOR_LED_WHILE_RAMPING
+#endif
+
+
+// Misc
+
+#define PARTY_STROBE_ONTIME 1 // slow down party strobe
+#define STROBE_OFF_LEVEL 1 // keep the regulator chip on between pulses
+
+// smoother candle mode with bigger oscillations
+#define CANDLE_AMPLITUDE 33
+
+// added for convenience
+#define USE_SOFT_FACTORY_RESET
+
diff --git a/hw/hank/emisar-d3aa/arch b/hw/hank/emisar-d3aa/arch
new file mode 100644
index 0000000..bcf4552
--- /dev/null
+++ b/hw/hank/emisar-d3aa/arch
@@ -0,0 +1 @@
+avr32dd20
diff --git a/hw/hank/emisar-d3aa/hwdef.c b/hw/hank/emisar-d3aa/hwdef.c
new file mode 100644
index 0000000..0ac0ddc
--- /dev/null
+++ b/hw/hank/emisar-d3aa/hwdef.c
@@ -0,0 +1,217 @@
+// Emisar D3AA helper functions
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include "fsm/chan-rgbaux.c"
+#include "fsm/ramping.h"
+#include "ui/anduril/misc.h"
+
+void set_level_zero();
+
+void set_level_main(uint8_t level);
+bool gradual_tick_main(uint8_t gt);
+
+
+Channel channels[] = {
+ { // main LEDs
+ .set_level = set_level_main,
+ .gradual_tick = gradual_tick_main
+ },
+ RGB_AUX_CHANNELS
+};
+
+
+inline void nfet_delay() {
+ #if IN_NFET_DELAY_TIME > 0
+ delay_4ms(IN_NFET_DELAY_TIME/4);
+ #else
+ delay_zero();
+ delay_zero();
+ #endif
+}
+
+void set_level_zero() {
+ DAC_LVL = 0; // DAC off
+ DAC_VREF = V10; // low Vref
+ HDR_ENABLE_PORT &= ~(1 << HDR_ENABLE_PIN); // HDR off
+
+ if (actual_level) {
+ // prevent post-off flash
+ IN_NFET_ENABLE_PORT |= (1 << IN_NFET_ENABLE_PIN);
+ nfet_delay();
+ IN_NFET_ENABLE_PORT &= ~(1 << IN_NFET_ENABLE_PIN);
+ }
+
+ // turn off boost last
+ BST_ENABLE_PORT &= ~(1 << BST_ENABLE_PIN); // BST off
+}
+
+// single set of LEDs with 1 regulated power channel
+// and low/high HDR plus low/high Vref as different "gears"
+void set_level_main(uint8_t level) {
+ uint8_t noflash = 0;
+
+ // when turning on from off, use IN_NFET to prevent a flash
+ if ((! actual_level) && (level < HDR_ENABLE_LEVEL_MIN)) {
+ noflash = 1;
+ IN_NFET_ENABLE_PORT |= (1 << IN_NFET_ENABLE_PIN);
+ //nfet_delay();
+ }
+
+ // pre-load ramp data so it can be assigned faster later
+ // DAC level register is left-aligned
+ PWM1_DATATYPE dac_lvl = PWM1_GET(level) << 6;
+ PWM2_DATATYPE dac_vref = PWM2_GET(level);
+
+ // enable HDR on top half of ramp
+ if (level >= (HDR_ENABLE_LEVEL_MIN-1))
+ HDR_ENABLE_PORT |= (1 << HDR_ENABLE_PIN);
+ else
+ HDR_ENABLE_PORT &= ~(1 << HDR_ENABLE_PIN);
+
+ // set these in successive clock cycles to avoid getting out of sync
+ // (minimizes ramp bumps when changing gears)
+ DAC_LVL = dac_lvl;
+ DAC_VREF = dac_vref;
+
+ // if turning on from off, let things stabilize before enabling power
+ if (noflash) { nfet_delay(); }
+
+ // BST on last, after its inputs are set and stabilized
+ BST_ENABLE_PORT |= (1 << BST_ENABLE_PIN);
+
+ if (noflash) {
+ // wait for flash prevention to finish
+ nfet_delay();
+ IN_NFET_ENABLE_PORT &= ~(1 << IN_NFET_ENABLE_PIN);
+ }
+}
+
+bool gradual_tick_main(uint8_t gt) {
+ // if HDR and Vref "engine gear" is the same, do a small adjustment...
+ // otherwise, simply jump to the next ramp level
+ // and let set_level() handle any gear changes
+
+ // different gear = full adjustment
+ PWM2_DATATYPE vref_next = PWM2_GET(gt);
+ if (vref_next != DAC_VREF) return true; // let parent set_level() for us
+
+ // same gear = small adjustment
+ PWM1_DATATYPE dac_now = DAC_LVL >> 6; // register is left-aligned
+ PWM1_DATATYPE dac_next = PWM1_GET(gt);
+
+ // only adjust 1 dac level, max is 1023
+ // (but speed it up with "#define GRADUAL_ADJUST_SPEED 4" elsewhere)
+ GRADUAL_ADJUST_SIMPLE(dac_next, dac_now);
+
+ DAC_LVL = dac_now << 6;
+
+ if (dac_next == dac_now) return true; // done
+
+ return false; // not done yet
+}
+
+
+#ifdef USE_VOLTAGE_DIVIDER
+uint8_t voltage_raw2cooked(uint16_t measurement) {
+ // In : 65535 * BATTLVL / 1.024V
+ // Out: uint8_t: Vbat * 50
+ // BATTLVL = Vbat * (100.0/(330+100)) = Vbat / 4.3
+ // So, Out = In * 4.3 / 1280
+ uint8_t result = (uint32_t)(measurement + (65535 * 4 / 1024))
+ * 43 / 12800;
+ return result;
+}
+#endif
+
+#ifdef USE_WEAK_BATTERY_PROTECTION
+uint8_t quick_volt_measurement() {
+ // wait for next hardware measurement
+ irq_adc = 0;
+ while (! irq_adc) {}
+ uint16_t m = adc_raw[0];
+ return voltage_raw2cooked(m);
+}
+
+void detect_weak_battery() {
+ // guess at the cell strength with a load test...
+ // - measure voltage with LEDs off
+ // - measure again with LEDs on
+ // - determine how much to limit power
+ // (ramp up until battery becomes unstable)
+ // - blink to indicate weak battery mode, if active
+
+ ramp_level_hard_limit = 0;
+
+ uint16_t resting, loaded;
+
+ // baseline unloaded measurement
+ set_level(0);
+ for (uint8_t i=0; i<32; i++) { delay_zero(); } // wait about 10ms
+ //resting = voltage_raw2cooked(adc_smooth[0]); // probably not settled yet
+ resting = quick_volt_measurement();
+
+ // set thresholds per cell type
+ uint8_t sag_limit, crit_voltage;
+ if (resting > DUAL_VOLTAGE_FLOOR) {
+ sag_limit = WEAK_BATTERY_SAG_THRESHOLD_LIION;
+ crit_voltage = VOLTAGE_LOW;
+ } else {
+ sag_limit = WEAK_BATTERY_SAG_THRESHOLD_AA;
+ crit_voltage = DUAL_VOLTAGE_LOW_LOW;
+ }
+
+ // progressively turn up the power until sag threshold is hit,
+ // or critical voltage, or max testing level is reached
+ for (uint8_t l=1; l<WEAK_BATTERY_TEST_MAX_LEVEL; l++) {
+ set_level(l);
+ loaded = quick_volt_measurement();
+ int16_t sag = resting - loaded;
+ if ( (loaded <= crit_voltage) || (sag > sag_limit) ) {
+ // battery empty or weak
+ ramp_level_hard_limit = l;
+ break;
+ }
+ }
+ set_level(0);
+
+ // Blink again if not in full-power mode:
+ // - 1 blink total: Strong Li-ion cell, full power enabled
+ // - 2 blinks: Strong AA cell, max AA power enabled
+ // (not used on this driver, strong AA uses mode 1)
+ // - 3 blinks: Weak battery, power severely limited
+
+ uint8_t extra_blinks = 0;
+ if (ramp_level_hard_limit) extra_blinks += 2;
+
+ for (uint8_t i=0; i<extra_blinks; i++) {
+ delay_4ms(300/4);
+ blink_once();
+ }
+
+ #ifdef USE_WEAK_BATTERY_PROTECTION_READOUT
+ // this numeric display isn't really needed by default,
+ // but the code remains in case anyone wants to use it
+ if (ramp_level_hard_limit) {
+ delay_4ms(255);
+ // not booted far enough for this to work yet
+ //blink_num(ramp_level_hard_limit);
+ uint8_t tens, ones;
+ tens = ramp_level_hard_limit / 10;
+ ones = ramp_level_hard_limit % 10;
+ for (uint8_t i=0; i<tens; i++) {
+ delay_4ms(300/4);
+ blink_once();
+ }
+ delay_4ms(600/4);
+ for (uint8_t i=0; i<ones; i++) {
+ delay_4ms(300/4);
+ blink_once();
+ }
+ }
+ #endif // ifdef USE_WEAK_BATTERY_PROTECTION_READOUT
+
+}
+#endif // ifdef USE_WEAK_BATTERY_PROTECTION
+
diff --git a/hw/hank/emisar-d3aa/hwdef.h b/hw/hank/emisar-d3aa/hwdef.h
new file mode 100644
index 0000000..889b721
--- /dev/null
+++ b/hw/hank/emisar-d3aa/hwdef.h
@@ -0,0 +1,227 @@
+// hwdef for Emisar D3AA
+// Copyright (C) 2023 thefreeman, Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+/*
+ * NiMH/li-ion 9V2A boost driver based on MP3432
+ * with high dynamic range and DAC control + front aux RGB and button LED
+ *
+ * Pin Name Function
+ * 1 PA4 e-switch
+ * 2 PA5 BATT LVL (voltage divider)
+ * 3 PA6 EN: boost enable
+ * 4 PA7 A : button LED
+ * 5 PC1 -
+ * 6 PC2 -
+ * 7 PC3 -
+ * 8 VDDIO2 (BATT+ via solder jumper)
+ * 9 PD4 IN- NFET: absorb startup flash
+ * 10 PD5 HDR: high/low Rsense range
+ * 11 PD6 DAC: control voltage out
+ * 12 PD7
+ * 13 VDD VCC
+ * 14 GND GND
+ * 15 PF6 RESET
+ * 16 PF7 UPDI
+ * 17 PA0 B: aux blue
+ * 18 PA1
+ * 19 PA2 G: aux green
+ * 20 PA3 R: aux red
+ *
+ * BST EN enable the boost regulator and Op-Amp
+ * DAC sets the current, max current depends on Vset voltage divider and Rsense
+ * HDR FET switches between high value Rsense (low current range, pin low),
+ * and low value Rsense (high current range, pin high)
+ * IN- NFET : pull up after BST enable to eliminate startup flash, pull down otherwise
+ * BATT LVL : Vbat * (100.0/(330+100))
+ * VDDIO2 : can be connected to BATT+ with a solder jumper for VDDIO2 voltage sensing
+ *
+ */
+
+#define HWDEF_C hank/emisar-d3aa/hwdef.c
+
+// allow using aux LEDs as extra channel modes
+#include "fsm/chan-rgbaux.h"
+
+// channel modes:
+// * 0. main LEDs
+// * 1+. aux RGB
+#define NUM_CHANNEL_MODES (1 + NUM_RGB_AUX_CHANNEL_MODES)
+enum CHANNEL_MODES {
+ CM_MAIN = 0,
+ RGB_AUX_ENUMS
+};
+
+#define DEFAULT_CHANNEL_MODE CM_MAIN
+
+// right-most bit first, modes are in fedcba9876543210 order
+#define CHANNEL_MODES_ENABLED 0b0000000000000001
+
+
+// DAC max is 1023, Anduril is written for 255, so regulate at 4X speed
+#undef GRADUAL_ADJUST_SPEED
+#define GRADUAL_ADJUST_SPEED 4
+
+#define PWM_BITS 16 // 10-bit DAC
+#define PWM_DATATYPE uint16_t
+#define PWM_DATATYPE2 uint32_t // only needs 32-bit if ramp values go over 255
+#define PWM1_DATATYPE uint16_t // main LED ramp
+#define PWM1_GET(l) PWM_GET16(pwm1_levels, l)
+#define PWM2_DATATYPE uint8_t // DAC Vref table
+#define PWM2_GET(l) PWM_GET8(pwm2_levels, l)
+
+// main LED outputs
+// (DAC_LVL + DAC_VREF + Vref values are defined in arch/*.h)
+
+// BST enable
+#define BST_ENABLE_PIN PIN6_bp
+#define BST_ENABLE_PORT PORTA_OUT
+
+// HDR
+// turns on HDR FET for the high current range
+#define HDR_ENABLE_PIN PIN5_bp
+#define HDR_ENABLE_PORT PORTD_OUT
+
+// IN- NFET
+// pull high to force output to zero to eliminate the startup flash
+#define IN_NFET_DELAY_TIME 0 // (ms)
+#define IN_NFET_ENABLE_PIN PIN4_bp
+#define IN_NFET_ENABLE_PORT PORTD_OUT
+
+// e-switch
+#ifndef SWITCH_PIN
+#define SWITCH_PIN PIN4_bp
+#define SWITCH_PORT VPORTA.IN
+#define SWITCH_ISC_REG PORTA.PIN4CTRL
+#define SWITCH_VECT PORTA_PORT_vect
+#define SWITCH_INTFLG VPORTA.INTFLAGS
+#endif
+
+#define DUAL_VOLTAGE_FLOOR (21*dV) // for AA/14500 boost drivers, don't indicate low voltage if below this level
+#define DUAL_VOLTAGE_LOW_LOW ( 7*dV) // the lower voltage range's danger zone 0.7 volts (NiMH)
+// comment out to use VDDIO2 instead of external voltage divider
+#define USE_VOLTAGE_DIVIDER
+#ifdef USE_VOLTAGE_DIVIDER
+ // AVR datasheet table 3.1 I/O Multiplexing, PA5 ADC0 = AIN25
+ #define ADMUX_VOLTAGE_DIVIDER ADC_MUXPOS_AIN25_gc
+ // don't use the default VDD converter
+ // convert BATT LVL pin readings to FSM volt units
+ #undef voltage_raw2cooked
+ uint8_t voltage_raw2cooked(uint16_t measurement);
+#else
+ // doesn't work on this hardware in AA mode
+ #define USE_VOLTAGE_VDDIO2
+#endif
+
+// Alkaline AA can't handle the power this light wants,
+// so try to detect it and limit the maximum power
+// (also helps protect firmware flashing adapters from overload)
+#define USE_RAMP_LEVEL_HARD_LIMIT
+#define USE_WEAK_BATTERY_PROTECTION
+// define this next to the ramp table instead
+//#define WEAK_BATTERY_TEST_MAX_LEVEL 75 // about 300 mA
+#define WEAK_BATTERY_SAG_THRESHOLD_AA (3*4) // 0.3 V
+#define WEAK_BATTERY_SAG_THRESHOLD_LIION (6*4) // 0.6 V
+
+// average drop across diode on this hardware
+#ifndef VOLTAGE_FUDGE_FACTOR
+#define VOLTAGE_FUDGE_FACTOR 0 // using a PFET so no appreciable drop
+#endif
+
+// this driver allows for aux LEDs under the optic
+#define AUXLED_R_PIN PIN3_bp
+#define AUXLED_G_PIN PIN2_bp
+#define AUXLED_B_PIN PIN0_bp
+#define AUXLED_RGB_PORT PORTA
+
+// this light has three aux LED channels: R, G, B
+#define USE_AUX_RGB_LEDS
+
+// A: button LED
+#ifndef BUTTON_LED_PIN
+#define BUTTON_LED_PIN PIN7_bp
+#define BUTTON_LED_PORT PORTA
+#endif
+
+
+inline void hwdef_setup() {
+
+ // TODO: for this DAC controlled-light, try to decrease the clock speed
+ // to reduce overall system power
+ mcu_clock_speed();
+
+ VPORTA.DIR = PIN0_bm // B
+ | PIN2_bm // G
+ | PIN3_bm // R
+ | PIN6_bm // EN
+ | PIN7_bm; // A
+ VPORTD.DIR = PIN4_bm // IN- NFET
+ | PIN5_bm // HDR
+ | PIN6_bm; // DAC
+
+ // enable pullups on the unused and input pins to reduce power
+ //PORTA.PIN0CTRL = PORT_PULLUPEN_bm; // B
+ PORTA.PIN1CTRL = PORT_PULLUPEN_bm;
+ //PORTA.PIN2CTRL = PORT_PULLUPEN_bm; // G
+ //PORTA.PIN3CTRL = PORT_PULLUPEN_bm; // R
+ PORTA.PIN4CTRL = PORT_PULLUPEN_bm
+ | PORT_ISC_BOTHEDGES_gc; // e-switch
+ //PORTA.PIN5CTRL = PORT_PULLUPEN_bm; // BATT LVL
+ //PORTA.PIN6CTRL = PORT_PULLUPEN_bm; // EN
+ //PORTA.PIN7CTRL = PORT_PULLUPEN_bm; // A
+
+ //PORTC.PIN0CTRL = PORT_PULLUPEN_bm; // doesn't exist
+ PORTC.PIN1CTRL = PORT_PULLUPEN_bm;
+ PORTC.PIN2CTRL = PORT_PULLUPEN_bm;
+ PORTC.PIN3CTRL = PORT_PULLUPEN_bm;
+ //PORTC.PIN4CTRL = PORT_PULLUPEN_bm; // doesn't exist
+ //PORTC.PIN5CTRL = PORT_PULLUPEN_bm; // doesn't exist
+
+ //PORTD.PIN0CTRL = PORT_PULLUPEN_bm; // doesn't exist
+ //PORTD.PIN1CTRL = PORT_PULLUPEN_bm; // doesn't exist
+ //PORTD.PIN2CTRL = PORT_PULLUPEN_bm; // doesn't exist
+ //PORTD.PIN3CTRL = PORT_PULLUPEN_bm; // doesn't exist
+ //PORTD.PIN4CTRL = PORT_PULLUPEN_bm; // IN- NFET
+ //PORTD.PIN5CTRL = PORT_PULLUPEN_bm; // EN
+ // AVR datasheet 34.3.1 #2, DAC pin must have input disable set
+ PORTD.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc; // DAC
+ PORTD.PIN7CTRL = PORT_PULLUPEN_bm;
+
+ // set up the DAC
+ // DAC ranges from 0V to (255 * Vref) / 256
+ DAC_VREF = V10;
+ // TODO: try DAC_RUNSTDBY_bm for extra-efficient moon
+ DAC0.CTRLA = DAC_ENABLE_bm | DAC_OUTEN_bm;
+ DAC_LVL = 0; // turn off output at boot
+ // TODO: instead of enabling the DAC at boot, pull pin down
+ // to generate a zero without spending power on the DAC
+ // (and do this in set_level_zero() too)
+
+}
+
+
+// set fuses, these carry over to the ELF file
+// we need this for enabling BOD in Active Mode from the factory.
+// settings can be verified / dumped from the ELF file using this
+// command: avr-objdump -d -S -j .fuse anduril.elf
+FUSES = {
+ .WDTCFG = FUSE_WDTCFG_DEFAULT, // Watchdog Configuration
+
+ // enable BOD (continuous) in active mode
+ .BODCFG = ACTIVE_ENABLE_gc, // BOD Configuration
+
+ .OSCCFG = FUSE_OSCCFG_DEFAULT, // Oscillator Configuration
+ .SYSCFG0 = FUSE_SYSCFG0_DEFAULT, // System Configuration 0
+
+ // enable MVIO because VDDIO2 pin isn't connected
+ // set startup time to 64ms to allow power to stabilize
+ .SYSCFG1 = MVSYSCFG_DUAL_gc | SUT_64MS_gc,
+
+ .CODESIZE = FUSE_CODESIZE_DEFAULT,
+ .BOOTSIZE = FUSE_BOOTSIZE_DEFAULT,
+};
+
+
+#define LAYOUT_DEFINED
+
diff --git a/hw/hank/emisar-d3aa/model b/hw/hank/emisar-d3aa/model
new file mode 100644
index 0000000..298b713
--- /dev/null
+++ b/hw/hank/emisar-d3aa/model
@@ -0,0 +1 @@
+0161
diff --git a/hw/hank/lume-x1/anduril.h b/hw/hank/lume-x1/anduril.h
new file mode 100644
index 0000000..c409e43
--- /dev/null
+++ b/hw/hank/lume-x1/anduril.h
@@ -0,0 +1,114 @@
+// Hank Emisar/Noctigon Lume-X1 config options for Anduril
+// Copyright (C) 2018-2024 Selene ToyKeeper, Loneoceans
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+// For flashlights using the Loneoceans Lume-X1-40W boost driver (AVR32DD20)
+// - Same firmware for 6V, 9V, or 12V configs
+// same as loneoceans lume-x1-avr32dd20 but with Hank-specific defaults
+
+#define HWDEF_H hank/lume-x1/hwdef.h
+#include "hank/anduril.h"
+
+
+
+//***************************************
+//** RAMP TABLE AND OPERATIONS **
+//***************************************
+
+#define RAMP_SIZE 150 // Change to 160 as well as table and values below if desired
+
+// PWM1: DAC Data
+// UDR x^4 curves specifically for Lume-X1 (AVR32DD20)
+
+// levels 1 to 5:
+// ./bin/level_calc.py 5.2 1 5 7135 100 0.01 0.1 --pwm 2500
+// (with the last 2 values divided to fit)
+// levels 6 to 150:
+// ./bin/dac-scale.py $( ./bin/level_calc.py 5.3 1 145 7135 17 0.2 3000 --pwm 240000 | grep PWM1 | cut -d : -f 2- )
+// top level for each "gear": 3 5 / 39 50 / 123 150
+#define PWM1_LEVELS \
+ 100, 359, 790, 588,1023, \
+ 17, 21, 24, 29, 34, 39, 46, 54, 62, 71, 82, 94, 107, 122, 138, 157, 177, 199, 223, 250, 279, 311, 346, 385, 426, 471, 520, 573, 630, 692, 758, 830, 907, 990, \
+ 441, 480, 522, 567, 615, 666, 720, 778, 840, 905, 974, \
+ 26, 28, 30, 33, 35, 37, 40, 43, 46, 49, 52, 56, 59, 63, 67, 71, 76, 80, 85, 90, 96, 101, 107, 113, 120, 126, 133, 141, 148, 156, 165, 173, 182, 192, 202, 212, 222, 234, 245, 257, 269, 282, 296, 310, 324, 339, 355, 371, 387, 405, 422, 441, 460, 480, 501, 522, 544, 567, 590, 614, 640, 665, 692, 720, 748, 778, 808, 839, 872, 905, 939, 975,1011, \
+ 429, 445, 461, 478, 495, 513, 531, 550, 570, 589, 610, 631, 653, 675, 698, 721, 745, 770, 795, 821, 848, 875, 903, 932, 962, 992,1023
+#define PWM2_LEVELS \
+ V10, V10, V10, V25, V25, \
+ V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, \
+ V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, \
+ V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, V10, \
+ V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25, V25
+
+#define LED_PATH1_PIN_LEVEL_MIN 1
+#define LED_PATH2_PIN_LEVEL_MIN 6
+#define LED_PATH3_PIN_LEVEL_MIN 51
+
+#define HALFSPEED_LEVEL 20
+#define QUARTERSPEED_LEVEL 10
+
+#define DEFAULT_LEVEL 50
+#define MAX_1x7135 49
+
+#define RAMP_SMOOTH_FLOOR 1
+#define RAMP_SMOOTH_CEIL 130
+
+#define RAMP_DISCRETE_FLOOR 10
+#define RAMP_DISCRETE_CEIL 130
+#define RAMP_DISCRETE_STEPS 7
+
+#define SIMPLE_UI_FLOOR 10
+#define SIMPLE_UI_CEIL 110 // about ~12W
+#define SIMPLE_UI_STEPS 5
+
+// don't blink mid-ramp
+#ifdef BLINK_AT_RAMP_MIDDLE
+#undef BLINK_AT_RAMP_MIDDLE
+#endif
+
+//***************************************
+//** THERMAL SETTINGS **
+//***************************************
+
+// stop panicking at 6W (not sure of this numbers yet since it depends on the host..)
+#define THERM_FASTER_LEVEL 100 // about 6W
+#define MIN_THERM_STEPDOWN 60 // similar to single amc7135 in a 3V light
+
+//***************************************
+//** AUX LEDs and MISC **
+//***************************************
+
+#define USE_BUTTON_LED
+
+// this light has three aux LED channels: R, G, B
+#define USE_AUX_RGB_LEDS
+
+// show each channel while it scroll by in the menu
+#define USE_CONFIG_COLORS
+
+// blink numbers on the main LEDs by default (but allow user to change it)
+#define DEFAULT_BLINK_CHANNEL CM_MAIN
+
+// use aux red + aux blue for police strobe
+#define USE_POLICE_COLOR_STROBE_MODE
+#define POLICE_STROBE_USES_AUX
+#define POLICE_COLOR_STROBE_CH1 CM_AUXRED
+#define POLICE_COLOR_STROBE_CH2 CM_AUXBLU
+
+// the aux LEDs are front-facing, so turn them off while main LEDs are on
+#ifdef USE_INDICATOR_LED_WHILE_RAMPING
+#undef USE_INDICATOR_LED_WHILE_RAMPING
+#endif
+
+#define PARTY_STROBE_ONTIME 1 // slow down party strobe
+#define STROBE_OFF_LEVEL 1 // keep the regulator chips on between pulses
+
+// smoother candle mode with bigger oscillations
+#define CANDLE_AMPLITUDE 30 // default 25
+
+// avr32dd20 has enough space to smooth out voltage readings
+#define USE_VOLTAGE_LOWPASS
+
+// enable long-blink as negative sign
+#define USE_LONG_BLINK_FOR_NEGATIVE_SIGN
+
diff --git a/hw/hank/lume-x1/arch b/hw/hank/lume-x1/arch
new file mode 100644
index 0000000..bcf4552
--- /dev/null
+++ b/hw/hank/lume-x1/arch
@@ -0,0 +1 @@
+avr32dd20
diff --git a/hw/hank/lume-x1/hwdef.c b/hw/hank/lume-x1/hwdef.c
new file mode 100644
index 0000000..3fe32f3
--- /dev/null
+++ b/hw/hank/lume-x1/hwdef.c
@@ -0,0 +1,116 @@
+// Copyright (C) 2017-2023 Selene ToyKeeper
+// 2021-2024 loneoceans
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+//***********************************************
+//** HELPER FUNCTIONS FOR LUME-X1-AVR32DD20 **
+//***********************************************
+
+#pragma once
+
+#include "fsm/chan-rgbaux.c"
+
+// Declare variables and functions to support UDR multiple power paths
+uint8_t is_boost_currently_on = 0; // for turn-on delay during first turn on
+
+void set_level_zero();
+void set_level_udr(uint8_t level);
+bool gradual_tick_main(uint8_t gt);
+void set_power_path(uint8_t ramp_level);
+
+Channel channels[] = {
+ { // main LEDs
+ .set_level = set_level_udr,
+ .gradual_tick = gradual_tick_main
+ },
+ RGB_AUX_CHANNELS
+};
+
+// turn off
+void set_level_zero() {
+
+ DAC_LVL = 0; // set DAC to 0
+ DAC_VREF = V10; // set DAC Vref to lowest
+
+ // turn off DC/DC converter and amplifier
+ BST_ENABLE_PORT &= ~(1 << BST_ENABLE_PIN);
+ is_boost_currently_on = 0;
+
+ // turn off all UDR paths
+ LED_PATH1_PORT &= ~LED_PATH1_PIN;
+ LED_PATH2_PORT &= ~LED_PATH2_PIN;
+ LED_PATH3_PORT &= ~LED_PATH3_PIN;
+}
+
+// UDR for set_level, which sets the led brightness based on ramp tables.
+// single set of LED(s), fully regulated boost at all levels
+void set_level_udr(uint8_t level) {
+ if (level == actual_level - 1) return; // no-op
+
+ // get the ramp data
+ PWM1_DATATYPE dac_lvl = PWM1_GET(level) << 6; // dac register is left-aligned
+ PWM2_DATATYPE dac_vref = PWM2_GET(level);
+
+ if(is_boost_currently_on != 1){
+ // boost is not on, enable buck and add boot-up delay
+ is_boost_currently_on = 1;
+ BST_ENABLE_PORT |= (1 << BST_ENABLE_PIN); // turn on buck and amplifier
+ delay_4ms(BST_ON_DELAY/4); // boot-up delay
+ }
+ // set the DAC
+ DAC_LVL = dac_lvl;
+ DAC_VREF = dac_vref;
+
+ // set the power paths
+ set_power_path(level);
+}
+
+// handles dynamic Vref used in the ramp tables
+bool gradual_tick_main(uint8_t gt) {
+ // TODO overall smoothness can be improved due to gt using linear
+ // adjustments, but ramp table is non-linear.
+
+ // if Vref is the same, make gradual adjustments.
+ // else, jump to the next ramp level and use set_level() to handle power paths.
+ PWM2_DATATYPE vref_next = PWM2_GET(gt); // DAC ramp table Vref
+
+ // if different vref level, make a ramp level adjustment..
+ if (vref_next != DAC_VREF) return true; // use set_level() to handle normally
+
+ // .. else, same vref, adjust level gradually.
+ PWM1_DATATYPE dac_next = PWM1_GET(gt); // DAC ramp table data
+ PWM1_DATATYPE dac_curr = DAC_LVL >> 6; // register is left-aligned
+
+ GRADUAL_ADJUST_SIMPLE(dac_next, dac_curr);
+ DAC_LVL = dac_curr << 6;
+
+ if (dac_next == dac_curr) return true; // done
+
+ return false;
+}
+
+// handles dynamic power pathways based on threshold levels
+void set_power_path(uint8_t ramp_level){
+
+ ramp_level ++; // convert to 1-based indexing
+
+ if (ramp_level >= LED_PATH3_PIN_LEVEL_MIN) {
+ // high mode
+ LED_PATH1_PORT |= LED_PATH1_PIN;
+ LED_PATH2_PORT |= LED_PATH2_PIN;
+ LED_PATH3_PORT |= LED_PATH3_PIN;
+ }
+ else if (ramp_level >= LED_PATH2_PIN_LEVEL_MIN) {
+ // low mode
+ LED_PATH1_PORT |= LED_PATH1_PIN;
+ LED_PATH2_PORT |= LED_PATH2_PIN;
+ LED_PATH3_PORT &= ~LED_PATH3_PIN;
+ }
+ else if (ramp_level >= LED_PATH1_PIN_LEVEL_MIN) {
+ // firefly mode
+ LED_PATH1_PORT |= LED_PATH1_PIN;
+ LED_PATH2_PORT &= ~LED_PATH2_PIN;
+ LED_PATH3_PORT &= ~LED_PATH3_PIN;
+ }
+}
+
diff --git a/hw/hank/lume-x1/hwdef.h b/hw/hank/lume-x1/hwdef.h
new file mode 100644
index 0000000..250e8ee
--- /dev/null
+++ b/hw/hank/lume-x1/hwdef.h
@@ -0,0 +1,232 @@
+// Copyright (C) 2017-2023 Selene ToyKeeper
+// 2021-2024 loneoceans
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+//**************************************************
+//** HARDWARE DEFINITIONS FOR LUME-X1-AVR32DD20 **
+//**************************************************
+
+/* Loneoceans Lume-X1 with AVR32DD20
+
+ 40W Boost Driver with Ultra Dynamic Range, RGB & SW Aux, Powerbank.
+
+ The following pins are invariant (20 QFN package 3x3 0.4mm BSC)
+ - PD6 / PP11 - DAC OUT
+ - PF6 / PP15 - nRST
+ - PF7 / PP16 - UPDI
+ - xx / PP13 - VDD (read voltage from VDD, PFET RPP, no voltage drop)
+ - xx / PP8 - VDDIO2
+ - xx / PP14 - GND
+
+ Used Pins
+ - PA0 / PP17 - FET via PWM (TCA0-WO0) - N/C for Lume X1
+ - PA5 / PP2 - PATH3 - High Range
+ - PA6 / PP3 - PATH2 - Low Range
+ - PA7 / PP4 - PATH1 - Moon Range
+ - PC1 / PP5 - Enable for Boost, Amplifier
+ - PC2 / PP6 - Enable for Microphone - N/C
+ - PC3 / PP7 - Neo Dat - N/C
+ - PD4 / PP9 - E-Switch
+ - PD5 / PP10 - Mic Output - N/C
+ - PD6 / PP11 - DAC Out
+ - PD7 / PP12 - Power Bank - N/C
+ - PA1 / PP18 - AUX R LED
+ - PA2 / PP19 - AUX G LED
+ - PA3 / PP20 - AUX B LED
+ - PA4 / PP1 - AUX SW LED
+
+*/
+
+#define HWDEF_C hank/lume-x1/hwdef.c
+
+// allow using aux LEDs as extra channel modes
+#include "fsm/chan-rgbaux.h"
+
+// channel modes:
+// * 0. main LEDs
+// * 1+. aux RGB
+#define NUM_CHANNEL_MODES (1 + NUM_RGB_AUX_CHANNEL_MODES)
+enum CHANNEL_MODES {
+ CM_MAIN = 0,
+ RGB_AUX_ENUMS
+};
+
+#define DEFAULT_CHANNEL_MODE CM_MAIN
+
+// right-most bit first, modes are in fedcba9876543210 order
+#define CHANNEL_MODES_ENABLED 0b0000000000000001
+
+//***************************************
+//** SET UP DAC AND PWM **
+//***************************************
+
+// Define DAC control
+#define PWM_BITS 16 // 10-bit DAC
+#define PWM_DATATYPE uint16_t
+#define PWM_DATATYPE2 uint32_t
+#define PWM1_DATATYPE uint16_t // main regulated ramp 10-bit
+#define PWM1_GET(x) PWM_GET16(pwm1_levels, x)
+#define PWM2_DATATYPE uint8_t // DAC Vref table (4/6 options)
+#define PWM2_GET(x) PWM_GET8(pwm2_levels, x)
+
+//***************************************
+//** PIN DEFINITIONS **
+//***************************************
+
+// Boost and Amplifier Enable (PC1)
+#define BST_ENABLE_PIN PIN1_bp
+#define BST_ENABLE_PORT PORTC_OUT
+#define BST_ON_DELAY 8 // ms delay turning on the led after enable
+
+// Ultra Dynamic Range (UDR)
+/*
+ UDR makes use of the concept of multiple power paths. 3 are used in this
+ design to achieve extremely low moonlight levels. This is combined with
+ dynamic Vref for smoother brightness level transitions that would
+ normally be limited by the 10-bit DAC resolution.
+
+ Lume drivers uses the internal DAC to generate a reference voltage for
+ a current-regulated amplifier which drives the LED. Each power path
+ routes current through different sense resistors, allowing for improved
+ current sense resolution especially at the low end. Using UDR, Lume1/X1
+ is capable of ultra-low firefly / moonlight levels with a dynamic range
+ on the order of >10-50 million : 1.
+*/
+
+// For UDR Path 1 (firefly mode) - PA7
+#define LED_PATH1_PIN PIN7_bm
+#define LED_PATH1_PORT PORTA_OUT
+
+// For UDR Path 2 (low mode) - PA6
+#define LED_PATH2_PIN PIN6_bm
+#define LED_PATH2_PORT PORTA_OUT
+
+// For UDR Path 3 (high mode) - PA5
+#define LED_PATH3_PIN PIN5_bm
+#define LED_PATH3_PORT PORTA_OUT
+
+// Define Aux LED Pins
+
+// lighted switch button aux led (PA4)
+#ifndef BUTTON_LED_PIN
+#define BUTTON_LED_PIN PIN4_bp
+#define BUTTON_LED_PORT PORTA
+#endif
+
+// this driver allows for aux LEDs under the optic
+#define AUXLED_R_PIN PIN1_bp
+#define AUXLED_G_PIN PIN2_bp
+#define AUXLED_B_PIN PIN3_bp
+
+#define AUXLED_RGB_PORT PORTA
+
+/*
+#define AUXLED_R_PORT PORTA
+#define AUXLED_G_PORT PORTA
+#define AUXLED_B_PORT PORTA
+
+// if aux leds are on different ports
+#define AUXLED_RGB_DIFFERENT_PORTS
+*/
+
+// this light has three aux LED channels: R, G, B
+#define USE_AUX_RGB_LEDS
+
+// Define e-switch Pin and ISR
+#ifndef SWITCH_PIN // PD4
+#define SWITCH_PIN PIN4_bp
+#define SWITCH_PORT VPORTD.IN
+#define SWITCH_ISC_REG PORTD.PIN4CTRL
+#define SWITCH_VECT PORTD_PORT_vect
+#define SWITCH_INTFLG VPORTD.INTFLAGS
+#endif
+
+// average drop across diode on this hardware
+#ifndef VOLTAGE_FUDGE_FACTOR
+#define VOLTAGE_FUDGE_FACTOR 1 // PFET for RRP, essentially 0 v-drop, but experimentally add 0.05V for better UX
+#endif
+
+//***************************************
+//** HARDWARE INIT **
+//***************************************
+
+inline void hwdef_setup() {
+
+ // TODO: for this DAC controlled-light, try to decrease the clock speed
+ // to reduce overall system power
+ mcu_clock_speed();
+
+ // set output pins
+ VPORTA.DIR = PIN1_bm | PIN2_bm | PIN3_bm |
+ PIN4_bm | PIN5_bm | PIN6_bm | PIN7_bm;
+ VPORTC.DIR = PIN1_bm;
+ VPORTD.DIR = PIN6_bm;
+
+ // now set pullups on input pins, and unused pins (reduce power)
+ PORTA.PIN0CTRL = PORT_PULLUPEN_bm; // FET
+ //PORTA.PIN1CTRL = PORT_PULLUPEN_bm; // AUX R
+ //PORTA.PIN2CTRL = PORT_PULLUPEN_bm; // AUX G
+ //PORTA.PIN3CTRL = PORT_PULLUPEN_bm; // AUX B
+ //PORTA.PIN4CTRL = PORT_PULLUPEN_bm; // AUX SW
+ //PORTA.PIN5CTRL = PORT_PULLUPEN_bm; // PATH3
+ //PORTA.PIN6CTRL = PORT_PULLUPEN_bm; // PATH2
+ //PORTA.PIN7CTRL = PORT_PULLUPEN_bm; // PATH1
+
+ //PORTC.PIN1CTRL = PORT_PULLUPEN_bm; // ENABLE
+ PORTC.PIN2CTRL = PORT_PULLUPEN_bm; // MIC ENABLE
+ PORTC.PIN3CTRL = PORT_PULLUPEN_bm; // NEO
+
+ //PORTD.PIN4CTRL = PORT_PULLUPEN_bm; // ESW (100kR PULLUP)
+ PORTD.PIN5CTRL = PORT_PULLUPEN_bm; // MIC OUT
+ //PORTD.PIN6CTRL = PORT_PULLUPEN_bm; // DAC OUT
+ PORTD.PIN7CTRL = PORT_PULLUPEN_bm; // PWR BNK ENABLE
+
+ // clear some pins we don't need for now or want to initialize as low
+ PORTC_OUT &= ~(1 << PIN1_bp); // ENABLE
+ PORTC_OUT &= ~(1 << PIN2_bp); // MIC ENABLE
+ PORTD_OUT &= ~(1 << PIN7_bp); // PWR BNK ENABLE
+
+ //E-Switch (now uses external pullup)
+ PORTD.DIRCLR = PIN4_bm; // set ESW as input pin
+ PORTD.PIN4CTRL = PORT_ISC_BOTHEDGES_gc;
+
+ // set up the DAC (used for the switching regulator)
+ // https://www.microchip.com/en-us/product/avr32dd20
+ // DAC ranges from 0V to (255 * Vref) / 256
+
+ // Datasheet 34.3.1-2/2: input for DAC must be disabled
+ PORTD.PIN6CTRL = PORT_ISC_INPUT_DISABLE_gc;
+ //VREF.CTRLA |= VREF_DAC0REFSEL_2V5_gc; // also VREF_DAC0REFSEL_0V55_gc and VREF_DAC0REFSEL_1V1_gc and VREF_DAC0REFSEL_2V5_gc
+ //VREF.CTRLB |= VREF_DAC0REFEN_bm; // enable vref
+ DAC_VREF = V10;
+ DAC0.CTRLA = DAC_ENABLE_bm | DAC_OUTEN_bm; // enable DAC
+ DAC_LVL = 0; // lowest output during boot
+ // TODO: instead of enabling the DAC at boot, pull pin down
+ // to generate a zero without spending power on the DAC
+ // (and do this in set_level_zero() too)
+
+}
+
+// set fuses, these carry over to the ELF file
+// we need this for enabling BOD in Active Mode from the factory.
+// settings can be verified / dumped from the ELF file using this
+// command: avr-objdump -d -S -j .fuse anduril.elf
+FUSES = {
+ .WDTCFG = FUSE_WDTCFG_DEFAULT, // Watchdog Configuration
+
+ // enable BOD (continuous) in active mode
+ .BODCFG = ACTIVE_ENABLE_gc, // BOD Configuration
+
+ .OSCCFG = FUSE_OSCCFG_DEFAULT, // Oscillator Configuration
+ .SYSCFG0 = FUSE_SYSCFG0_DEFAULT, // System Configuration 0
+
+ // enable MVIO because VDDIO2 pin isn't connected
+ // set startup time to 64ms to allow power to stabilize
+ .SYSCFG1 = MVSYSCFG_DUAL_gc | SUT_64MS_gc,
+
+ .CODESIZE = FUSE_CODESIZE_DEFAULT,
+ .BOOTSIZE = FUSE_BOOTSIZE_DEFAULT,
+};
+
+#define LAYOUT_DEFINED
diff --git a/hw/hank/lume-x1/model b/hw/hank/lume-x1/model
new file mode 100644
index 0000000..5a0d2ba
--- /dev/null
+++ b/hw/hank/lume-x1/model
@@ -0,0 +1 @@
+0281
diff --git a/hw/hank/lume-x1/readme.md b/hw/hank/lume-x1/readme.md
new file mode 100644
index 0000000..eba5949
--- /dev/null
+++ b/hw/hank/lume-x1/readme.md
@@ -0,0 +1,34 @@
+# Hank / loneoceans Lume X1 40W
+
+Regulated boost driver for Hank lights (Emisar / Noctigon) by loneoceans.
+
+Second Hank light to use the new avr32dd20 MCU. Performs better than older
+MCUs and provides more space for extra features and future firmware updates.
+
+Originally from https://github.com/loneoceans/anduril
+
+Features UDR (Ultra Dynamic Range) for smooth dimming all the way down to
+*literally zero*, meaning an effectively infinite dimming ratio:
+
+- 3 power paths or "gears" for the regulator chip's sense resistors:
+ - Firefly "gear": 0 to ~0.1 lm
+ - Low "gear": ~0.1 to ~40 lm
+ - High "gear": ~40 lm to full power (2k or 3k lm?)
+- Internal resolution of 2500 steps per "gear" allows very precise control over
+ brightness. Anduril's 150 step ramp does not fully take advantage of this.
+- Constant current, no pulsing or flicker or ripple
+- No preflash
+- No need for jump start; regulator has a quick response at all levels
+
+The firefly part of the ramp is compressed to just a few levels, to avoid
+making the rest of the ramp artificially low. It aims to be reasonably
+consistent compared to the ramp shape of Hank's other lights, but a few levels
+at the bottom are set aside for moon and firefly modes.
+
+The regulator can go much, much lower than the brightness of the button LEDs,
+but there isn't much point because the glowing button overpowers the main LEDs.
+The button and aux LEDs (on low) also use significantly less power despite
+being brighter, because they do not require the MCU to be awake. So that part
+of the ramp is mostly omitted. However, it can be added with a firmware
+modification if desired.
+
diff --git a/hw/sofirn/sp10-pro/hwdef.h b/hw/sofirn/sp10-pro/hwdef.h
index f220318..43da9d0 100644
--- a/hw/sofirn/sp10-pro/hwdef.h
+++ b/hw/sofirn/sp10-pro/hwdef.h
@@ -62,8 +62,8 @@ enum CHANNEL_MODES {
// Voltage divider battLVL
#define USE_VOLTAGE_DIVIDER // use a dedicated pin, not VCC, because VCC input is regulated
-#define DUAL_VOLTAGE_FLOOR (4*21) // for AA/14500 boost drivers, don't indicate low voltage if below this level
-#define DUAL_VOLTAGE_LOW_LOW (4*7) // the lower voltage range's danger zone 0.7 volts (NiMH)
+#define DUAL_VOLTAGE_FLOOR (21*dV) // for AA/14500 boost drivers, don't indicate low voltage if below this level
+#define DUAL_VOLTAGE_LOW_LOW ( 7*dV) // the lower voltage range's danger zone 0.7 volts (NiMH)
#define ADMUX_VOLTAGE_DIVIDER ADC_MUXPOS_AIN9_gc // which ADC channel to read
#undef voltage_raw2cooked
diff --git a/hw/thefreeman/avr32dd20-devkit/hwdef.c b/hw/thefreeman/avr32dd20-devkit/hwdef.c
index 460082f..5b534d2 100644
--- a/hw/thefreeman/avr32dd20-devkit/hwdef.c
+++ b/hw/thefreeman/avr32dd20-devkit/hwdef.c
@@ -119,13 +119,13 @@ bool gradual_tick_main(uint8_t gt) {
uint8_t voltage_raw2cooked(uint16_t measurement) {
// In : 65535 * BATTLVL / 1.024V
- // Out: uint8_t: Vbat * 40
+ // Out: uint8_t: Vbat * 50
// BATTLVL = Vbat * (100.0/(330+100)) = Vbat / 4.3
- // So, Out = In * 4.3 / 1600
+ // So, Out = In * 4.3 / 1280
// (plus a bit of fudging to fix the slope and offset,
// based on measuring actual hardware)
uint8_t result = (uint32_t)(measurement + (65535 * 4 / 1024))
- * 43 / 16128;
+ * 43 / 12880;
return result;
}
diff --git a/hw/thefreeman/avr32dd20-devkit/hwdef.h b/hw/thefreeman/avr32dd20-devkit/hwdef.h
index 231f74a..5015c24 100644
--- a/hw/thefreeman/avr32dd20-devkit/hwdef.h
+++ b/hw/thefreeman/avr32dd20-devkit/hwdef.h
@@ -109,8 +109,8 @@ enum CHANNEL_MODES {
// AVR datasheet table 3.1 I/O Multiplexing, PA6 ADC0 = AIN26
#define USE_VOLTAGE_DIVIDER // use a dedicated pin, not VCC, because VCC input is regulated
#define ADMUX_VOLTAGE_DIVIDER ADC_MUXPOS_AIN26_gc
-#define DUAL_VOLTAGE_FLOOR (4*21) // for AA/14500 boost drivers, don't indicate low voltage if below this level
-#define DUAL_VOLTAGE_LOW_LOW (4*7) // the lower voltage range's danger zone 0.7 volts (NiMH)
+#define DUAL_VOLTAGE_FLOOR (21*dV) // for AA/14500 boost drivers, don't indicate low voltage if below this level
+#define DUAL_VOLTAGE_LOW_LOW ( 7*dV) // the lower voltage range's danger zone 0.7 volts (NiMH)
// don't use the default VDD converter
// convert BATT LVL pin readings to FSM volt units
#undef voltage_raw2cooked
@@ -155,6 +155,7 @@ inline void hwdef_setup() {
//PORTA.PIN2CTRL = PORT_PULLUPEN_bm; // B
#ifdef USE_BST_BYPASS
PORTA.PIN3CTRL = PORT_PULLUPEN_bm; // BBY
+ BST_BYPASS_PORT |= (1 << BST_BYPASS_PIN);
#endif
PORTA.PIN4CTRL = PORT_PULLUPEN_bm;
PORTA.PIN5CTRL = PORT_PULLUPEN_bm;
diff --git a/hw/thefreeman/boost-fwaa-mp3432-hdr-dac-rgb/hwdef.h b/hw/thefreeman/boost-fwaa-mp3432-hdr-dac-rgb/hwdef.h
index bc1d9a7..1c6e6be 100644
--- a/hw/thefreeman/boost-fwaa-mp3432-hdr-dac-rgb/hwdef.h
+++ b/hw/thefreeman/boost-fwaa-mp3432-hdr-dac-rgb/hwdef.h
@@ -94,8 +94,8 @@ enum CHANNEL_MODES {
// Voltage divider battLVL
#define USE_VOLTAGE_DIVIDER // use a dedicated pin, not VCC, because VCC input is regulated
#define ADMUX_VOLTAGE_DIVIDER ADC_MUXPOS_AIN2_gc // which ADC channel to read
-#define DUAL_VOLTAGE_FLOOR (4*21) // for AA/14500 boost drivers, don't indicate low voltage if below this level
-#define DUAL_VOLTAGE_LOW_LOW (4*7) // the lower voltage range's danger zone 0.7 volts (NiMH)
+#define DUAL_VOLTAGE_FLOOR (21*dV) // for AA/14500 boost drivers, don't indicate low voltage if below this level
+#define DUAL_VOLTAGE_LOW_LOW ( 7*dV) // the lower voltage range's danger zone 0.7 volts (NiMH)
// don't use the default VDD converter
#undef voltage_raw2cooked
#define voltage_raw2cooked mcu_vdivider_raw2cooked
diff --git a/hw/wurkkos/ts10/rgbaux-lowfet/README.md b/hw/wurkkos/ts10/rgbaux-lowfet/README.md
new file mode 100644
index 0000000..2173723
--- /dev/null
+++ b/hw/wurkkos/ts10/rgbaux-lowfet/README.md
@@ -0,0 +1,12 @@
+# Wurkkos TS10 w/ RGB aux + reduced-power FET
+
+Starting in mid-2024, some TS10 models were built with LEDs with lower Vf, and
+they destroy themselves when operated at full power from the direct-drive FET.
+This branch runs at half power instead, to avoid hardware damage.
+
+Regardless, sustained turbo is not recommended on any TS10 models, because they
+are overpowered for their size and LED type.
+
+- https://budgetlightforum.com/t/psa-anyone-with-a-new-ts10-do-not-flash-it-with-tks-ts10-rgbaux-firmware-or-turbo-will-fry-its-leds/225124
+- https://www.reddit.com/r/flashlight/comments/1emyyq9/psa_dont_flash_wurkkosts10rgbauxhex_to_your_new/
+
diff --git a/hw/wurkkos/ts10/rgbaux-lowfet/anduril.h b/hw/wurkkos/ts10/rgbaux-lowfet/anduril.h
new file mode 100644
index 0000000..5d012a5
--- /dev/null
+++ b/hw/wurkkos/ts10/rgbaux-lowfet/anduril.h
@@ -0,0 +1,16 @@
+// Wurkkos TS10 (RGB aux version with reduced FET output) config options for Anduril
+// Copyright (C) 2024 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+// most things are the same as TS10-rgbaux
+#include "wurkkos/ts10/rgbaux/anduril.h"
+
+#undef PWM1_LEVELS
+#undef PWM2_LEVELS
+#undef PWM_TOPS
+
+#define PWM1_LEVELS 1, 1, 2, 2, 3, 3, 4, 5, 6, 6, 8, 9, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 23, 24, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 35, 36, 37, 38, 40, 41, 43, 45, 47, 50, 53, 56, 60, 63, 67, 71, 75, 79, 84, 89, 94, 99,104,110,116,122,129,136,143,150,158,166,174,183,192,202,211,222,232,243,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,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,255,255,255,255,255,255,255
+// FET half power:
+#define PWM2_LEVELS 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, 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, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 14, 15, 17, 19, 20, 22, 24, 26, 27, 29, 31, 33, 35, 37, 39, 42, 44, 46, 48, 51, 53, 55, 58, 60, 63, 65, 68, 70, 73, 75, 78, 81, 84, 86, 89, 92, 95, 98,101,103,106,109,112,116,119,122,125,128
+#define PWM_TOPS 4095,2893,3917,2806,3252,2703,2684,2660,2640,2370,3000,2900,2630,2549,2246,2193,2030,1961,1889,1716,1642,1569,1497,1428,1290,1232,1176,1122,1070,976,932,890,849,779,745,685,656,605,579,536,514,476,457,424,407,379,364,340,327,314,302,291,280,276,266,262,257,253,253,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,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,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,255,255,255,255,255,255,255,255,255,255
diff --git a/hw/wurkkos/ts10/rgbaux-lowfet/model b/hw/wurkkos/ts10/rgbaux-lowfet/model
new file mode 100644
index 0000000..ef15105
--- /dev/null
+++ b/hw/wurkkos/ts10/rgbaux-lowfet/model
@@ -0,0 +1 @@
+0712
diff --git a/hw/wurkkos/ts10/rgbaux/anduril.h b/hw/wurkkos/ts10/rgbaux/anduril.h
index a9d4f90..df0935a 100644
--- a/hw/wurkkos/ts10/rgbaux/anduril.h
+++ b/hw/wurkkos/ts10/rgbaux/anduril.h
@@ -31,7 +31,10 @@
// level_calc.py 5.7895 2 150 7135 0 0.1 125.25 FET 1 10 1200 --pwm dyn:61:4096:255:2.5 --clock 5:11:2.0
// (with heavy manual tweaks up to ~15/150)
#define PWM1_LEVELS 1, 1, 2, 2, 3, 3, 4, 5, 6, 6, 8, 9, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 23, 24, 25, 26, 26, 27, 27, 28, 28, 29, 29, 30, 30, 31, 31, 32, 32, 33, 33, 34, 35, 36, 37, 38, 40, 41, 43, 45, 47, 50, 53, 56, 60, 63, 67, 71, 75, 79, 84, 89, 94, 99,104,110,116,122,129,136,143,150,158,166,174,183,192,202,211,222,232,243,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,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,255,255,255,255,255,255,0
+// FET full power:
#define PWM2_LEVELS 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, 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, 0, 0, 0, 0, 0, 2, 3, 5, 7, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 29, 31, 34, 37, 39, 42, 45, 48, 51, 54, 58, 61, 65, 68, 72, 76, 80, 84, 88, 93, 97,102,107,112,117,122,127,133,139,145,151,157,163,170,177,183,191,198,205,213,221,229,238,246,255
+// FET limited to 200 / 255:
+//#define PWM2_LEVELS 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, 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, 0, 0, 0, 0, 0, 1, 2, 3, 5, 6, 7, 9, 10, 12, 14, 15, 17, 18, 20, 22, 24, 26, 29, 30, 32, 35, 37, 40, 42, 45, 47, 50, 53, 56, 59, 62, 65, 69, 72, 76, 80, 83, 87, 91, 95, 99,104,109,113,118,123,127,133,138,143,149,155,160,167,173,179,186,192,200
#define PWM_TOPS 4095,2893,3917,2806,3252,2703,2684,2660,2640,2370,3000,2900,2630,2549,2246,2193,2030,1961,1889,1716,1642,1569,1497,1428,1290,1232,1176,1122,1070,976,932,890,849,779,745,685,656,605,579,536,514,476,457,424,407,379,364,340,327,314,302,291,280,276,266,262,257,253,253,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,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,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,255,255,255,255,255,255,255,255,255,255
#define MAX_1x7135 90
#define MIN_THERM_STEPDOWN 60
diff --git a/make b/make
index ca3a84a..0c2d465 100755
--- a/make
+++ b/make
@@ -82,6 +82,9 @@ function main() {
todo)
grep -E 'TODO:|FIXME:' -- **/*.[ch] **/*.md
;;
+ me)
+ memes "$@"
+ ;;
*)
exec ./bin/build-all.sh "$@"
;;
@@ -96,6 +99,23 @@ function make-docs () {
done
}
+function memes () {
+ # memes
+ shift ; shift
+ if [ "$UID" = "0" ]; then
+ echo "Okay."
+ for l in . . . ' 10%' ' .' . . ' 20%' ' .' . . '' "!" '' . . . ' 35%' ' .' . . . . ' 69%' '' '' '' ' (nice)' '' '' '' '\b\b\b\b\b\b \b\b\b\b\b\b.' . . ' 95%' ' .' . . ' 99%' ' .' . . ' 99.5%' ' .' . . '' ' ?' '' ' .' . . ' 99.7%' ' .' . . '' '' ' processing...' '' ' .' . . ' 99.8%' ' .' . . '' . '' '' ' urgh!' '' ' .' . . '' '' '' ' #@*%!\a' '' '' ' .' . . '' '' ' PROCESSING!' '' ' .' '' . '' . '' . '' . ' 99.9% ' '' . '' . '' . '' '' '' ' HRRRNNGH!!' '' '' ' .' '' '' . '' '' ' 99.95%' '' '' ' .' '' '' '' . '' '' " j/k NOT!" ; do
+ t=$( printf '%.3f' $(( 500 + $RANDOM / 32 ))e-3 )
+ sleep "$t"
+ echo -ne "$l"
+ done ; echo
+ echo "Make your own $*, dammit!"
+ else
+ echo "Make your own $*."
+ fi
+ exit 1
+}
+
# go to the repo root
BASEDIR=$(dirname "$0")
cd "$BASEDIR" || (echo "Error: Can't cd to basedir." && exit 1)
diff --git a/ui/anduril/anduril.c b/ui/anduril/anduril.c
index 1cdb8d0..a17ecf2 100644
--- a/ui/anduril/anduril.c
+++ b/ui/anduril/anduril.c
@@ -121,7 +121,7 @@
#include "anduril/lockout-mode.h"
#endif
-#ifdef USE_MOMENTARY_MODE
+#if (defined(USE_MOMENTARY_MODE) || defined(USE_TACTICAL_MODE))
#include "anduril/momentary-mode.h"
#endif
@@ -189,7 +189,7 @@
#include "anduril/lockout-mode.c"
#endif
-#ifdef USE_MOMENTARY_MODE
+#if (defined(USE_MOMENTARY_MODE) || defined(USE_TACTICAL_MODE))
#include "anduril/momentary-mode.c"
#endif
@@ -225,8 +225,16 @@ void setup() {
// regular e-switch light, no hard clicky power button
- // blink at power-on to let user know power is connected
- blink_once();
+ #ifdef USE_WEAK_BATTERY_PROTECTION
+ // try to measure the battery strength
+ // (must be done *before* factory reset,
+ // because reset tries to use full power,
+ // and a weak battery can't do that)
+ detect_weak_battery();
+ #else
+ // blink at power-on to let user know power is connected
+ blink_once();
+ #endif
#ifdef USE_FACTORY_RESET
if (button_is_pressed())
@@ -278,9 +286,17 @@ void loop() {
StatePtr state = current_state;
#ifdef USE_AUX_RGB_LEDS_WHILE_ON
- // display battery charge on RGB button during use
- if (state == steady_state)
- rgb_led_voltage_readout(actual_level > USE_AUX_RGB_LEDS_WHILE_ON);
+ // display battery charge on RGB button during use
+ if (state == steady_state) {
+ #ifdef USE_AUX_THRESHOLD_CONFIG
+ // only show voltage if feature is enabled and
+ // we are above the configured minimum ramp level
+ if (actual_level > cfg.button_led_low_ramp_level)
+ rgb_led_voltage_readout(actual_level > cfg.button_led_high_ramp_level);
+ #else
+ rgb_led_voltage_readout(actual_level > USE_AUX_RGB_LEDS_WHILE_ON);
+ #endif
+ }
#endif
if (0) {} // placeholder
@@ -293,10 +309,12 @@ void loop() {
#ifdef USE_STROBE_STATE
else if ((state == strobe_state)
- #ifdef USE_MOMENTARY_MODE
+ #if defined(USE_MOMENTARY_MODE) || defined(USE_TACTICAL_MODE)
// also handle momentary strobes
- || ((
- (state == momentary_state)
+ || ((0
+ #ifdef USE_MOMENTARY_MODE
+ || (state == momentary_state)
+ #endif
#ifdef USE_TACTICAL_MODE
|| (state == tactical_state)
#endif
@@ -331,6 +349,15 @@ void loop() {
#ifdef USE_THERMAL_REGULATION
// TODO: blink out therm_ceil during thermal_config_state?
else if (state == tempcheck_state) {
+ // temperature is type int16_t
+ // but blink_num is uint8_t, so -10 will blink as 246
+ #ifdef USE_LONG_BLINK_FOR_NEGATIVE_SIGN
+ if (temperature < 0) {
+ blink_negative();
+ blink_num(-temperature);
+ }
+ else
+ #endif
blink_num(temperature);
nice_delay_ms(1000);
}
diff --git a/ui/anduril/aux-leds.c b/ui/anduril/aux-leds.c
index fd184fc..50ce5c5 100644
--- a/ui/anduril/aux-leds.c
+++ b/ui/anduril/aux-leds.c
@@ -58,27 +58,27 @@ void indicator_led_update(uint8_t mode, uint8_t tick) {
uint8_t voltage_to_rgb() {
static const uint8_t levels[] = {
// voltage, color
- 0, 0, // black
+ 0, 0, // black
#ifdef DUAL_VOLTAGE_FLOOR
// AA / NiMH voltages
- 4* 9, 1, // R
- 4*10, 2, // R+G
- 4*11, 3, // G
- 4*12, 4, // G+B
- 4*13, 5, // B
- 4*14, 6, // R + B
- 4*16, 7, // R+G+B
- 4*20, 0, // black
+ 9*dV, 1, // R
+ 10*dV, 2, // R+G
+ 11*dV, 3, // G
+ 12*dV, 4, // G+B
+ 13*dV, 5, // B
+ 14*dV, 6, // R + B
+ 16*dV, 7, // R+G+B
+ 20*dV, 0, // black
#endif
// li-ion voltages
- 4*29, 1, // R
- 4*33, 2, // R+G
- 4*35, 3, // G
- 4*37, 4, // G+B
- 4*39, 5, // B
- 4*41, 6, // R + B
- 4*44, 7, // R+G+B // skip; looks too similar to G+B
- 255, 7, // R+G+B
+ 29*dV, 1, // R
+ 33*dV, 2, // R+G
+ 35*dV, 3, // G
+ 37*dV, 4, // G+B
+ 39*dV, 5, // B
+ 41*dV, 6, // R + B
+ 44*dV, 7, // R+G+B // skip; looks too similar to G+B
+ 255, 7, // R+G+B
};
uint8_t volts = voltage;
//if (volts < VOLTAGE_LOW) return 0;
@@ -125,7 +125,18 @@ void rgb_led_update(uint8_t mode, uint16_t arg) {
&& (ticks_since_on > 0) // don't blink red on 1st frame
) {
// use high mode if regular aux level is high or prev level was high
- pattern = 1 + ((2 == pattern) | (prev_level >= POST_OFF_VOLTAGE_BRIGHTNESS));
+ #ifdef USE_AUX_THRESHOLD_CONFIG
+ // always high if configured for high aux
+ // otherwise 0/1/2 depending on recent main LED brightness
+ // (using >= makes it off by 1, but allows POVD at boot time)
+ if (pattern != 2)
+ pattern = (prev_level >= cfg.button_led_low_ramp_level)
+ << (prev_level > cfg.button_led_high_ramp_level);
+ #else
+ pattern = 1
+ + ((2 == pattern)
+ | (prev_level >= POST_OFF_VOLTAGE_BRIGHTNESS));
+ #endif
// voltage mode
color = RGB_LED_NUM_COLORS - 1;
}
@@ -151,11 +162,8 @@ void rgb_led_update(uint8_t mode, uint16_t arg) {
else { // voltage
// show actual voltage while asleep...
if (go_to_standby) {
- actual_color = voltage_to_rgb();
// choose a color based on battery voltage
- //if (volts >= 38) actual_color = pgm_read_byte(colors + 4);
- //else if (volts >= 33) actual_color = pgm_read_byte(colors + 2);
- //else actual_color = pgm_read_byte(colors + 0);
+ actual_color = voltage_to_rgb();
}
// ... but during preview, cycle colors quickly
else {
diff --git a/ui/anduril/battcheck-mode.c b/ui/anduril/battcheck-mode.c
index c7c80dd..460c58a 100644
--- a/ui/anduril/battcheck-mode.c
+++ b/ui/anduril/battcheck-mode.c
@@ -51,7 +51,7 @@ uint8_t battcheck_state(Event event, uint16_t arg) {
return EVENT_NOT_HANDLED;
}
-#ifdef USE_VOLTAGE_CORRECTION
+#if defined(USE_VOLTAGE_CORRECTION) || defined(USE_POST_OFF_VOLTAGE) || defined(USE_AUX_THRESHOLD_CONFIG)
// the user can adjust the battery measurements... on a scale of 1 to 13
// 1 = subtract 0.30V
// 2 = subtract 0.25V
@@ -61,21 +61,35 @@ uint8_t battcheck_state(Event event, uint16_t arg) {
// ...
// 13 = add 0.30V
void voltage_config_save(uint8_t step, uint8_t value) {
- #ifdef USE_POST_OFF_VOLTAGE
- if (2 == step) cfg.post_off_voltage = value;
- else
- #endif
- if (value) cfg.voltage_correction = value;
+ switch (step) {
+ #if defined(USE_AUX_THRESHOLD_CONFIG)
+ case button_led_low_ramp_level_step:
+ // 0 clicks = 255 = never turn on
+ cfg.button_led_low_ramp_level = value - 1;
+ break;
+ case button_led_high_ramp_level_step:
+ // 0 clicks = 255 = never turn on
+ cfg.button_led_high_ramp_level = value - 1;
+ break;
+ #endif
+
+ #ifdef USE_POST_OFF_VOLTAGE
+ case post_off_voltage_config_step:
+ cfg.post_off_voltage = value;
+ break;
+ #endif
+
+ #ifdef USE_VOLTAGE_CORRECTION
+ default:
+ if (value) cfg.voltage_correction = value;
+ break;
+ #endif
+ }
}
uint8_t voltage_config_state(Event event, uint16_t arg) {
- #ifdef USE_POST_OFF_VOLTAGE
- #define VOLTAGE_CONFIG_STEPS 2
- #else
- #define VOLTAGE_CONFIG_STEPS 1
- #endif
return config_state_base(event, arg,
- VOLTAGE_CONFIG_STEPS,
+ voltage_config_num_steps - 1,
voltage_config_save);
}
#endif // #ifdef USE_VOLTAGE_CORRECTION
diff --git a/ui/anduril/battcheck-mode.h b/ui/anduril/battcheck-mode.h
index b505b68..2ff7c81 100644
--- a/ui/anduril/battcheck-mode.h
+++ b/ui/anduril/battcheck-mode.h
@@ -5,8 +5,23 @@
uint8_t battcheck_state(Event event, uint16_t arg);
-#ifdef USE_VOLTAGE_CORRECTION
+#if defined(USE_VOLTAGE_CORRECTION) || defined(USE_POST_OFF_VOLTAGE) || defined(USE_AUX_THRESHOLD_CONFIG)
void voltage_config_save(uint8_t step, uint8_t value);
uint8_t voltage_config_state(Event event, uint16_t arg);
#endif
+typedef enum {
+ voltage_cfg_zero = 0,
+ #ifdef USE_VOLTAGE_CORRECTION
+ voltage_correction_config_step,
+ #endif
+ #ifdef USE_POST_OFF_VOLTAGE
+ post_off_voltage_config_step,
+ #endif
+ #if defined(USE_AUX_THRESHOLD_CONFIG)
+ button_led_low_ramp_level_step,
+ button_led_high_ramp_level_step,
+ #endif
+ voltage_config_num_steps
+} voltage_config_steps_e;
+
diff --git a/ui/anduril/config-default.h b/ui/anduril/config-default.h
index 1b34e8c..51249f6 100644
--- a/ui/anduril/config-default.h
+++ b/ui/anduril/config-default.h
@@ -199,6 +199,14 @@
#define USE_LOWPASS_WHILE_ASLEEP
#endif
+// if the light has aux LEDs and enough ROM, let the user choose whether
+// the aux LEDs should be on while the main LEDs are on
+#if (ROM_SIZE > 10000)
+// can be enabled even if no aux LEDs exist,
+// will simply do nothing in that case
+#define USE_AUX_THRESHOLD_CONFIG
+#endif
+
// if there's tint ramping, allow user to set it smooth or stepped
#define USE_STEPPED_TINT_RAMPING
#define DEFAULT_TINT_RAMP_STYLE 0 // smooth
diff --git a/ui/anduril/load-save-config-fsm.h b/ui/anduril/load-save-config-fsm.h
index d189d3a..a69d1b1 100644
--- a/ui/anduril/load-save-config-fsm.h
+++ b/ui/anduril/load-save-config-fsm.h
@@ -97,6 +97,10 @@ typedef struct Config {
uint8_t therm_ceil;
int8_t therm_cal_offset;
#endif
+ #ifdef USE_AUX_THRESHOLD_CONFIG
+ uint8_t button_led_low_ramp_level;
+ uint8_t button_led_high_ramp_level;
+ #endif
///// aux LEDs
#ifdef USE_INDICATOR_LED
diff --git a/ui/anduril/load-save-config.h b/ui/anduril/load-save-config.h
index 3ad477c..f5afb29 100644
--- a/ui/anduril/load-save-config.h
+++ b/ui/anduril/load-save-config.h
@@ -169,5 +169,30 @@ Config cfg = {
.jump_start_level = DEFAULT_JUMP_START_LEVEL,
#endif
+ #if defined(USE_AUX_THRESHOLD_CONFIG)
+ // config for RGB voltage. We need to check these here rather than
+ // setting defaults in `config-default.h` as we only know *after*
+ // defaults are loaded if `USE_AUX_RGB_LEDS_WHILE_ON` is set or unset
+ // (in `CFG_H`).
+ #ifdef USE_AUX_LEDS_WHILE_ON_INITIAL_MINIMUM_LEVEL
+ .button_led_low_ramp_level = USE_AUX_LEDS_WHILE_ON_INITIAL_MINIMUM_LEVEL,
+ #else
+ .button_led_low_ramp_level = 0, // default
+ #endif
+ #if (USE_AUX_RGB_LEDS_WHILE_ON + 0)
+ // if USE_AUX_RGB_LEDS_WHILE_ON is an int, passes. If blank (undefined
+ // or defined with no value), evaluates to `(+0)` which evaluates to
+ // false.
+ .button_led_high_ramp_level = USE_AUX_RGB_LEDS_WHILE_ON,
+ #else
+ #ifdef USE_AUX_RGB_LEDS
+ //#warning "USE_AUX_RGB_LEDS_WHILE_ON defined but has no value. Setting to default value."
+ .button_led_high_ramp_level = 25 - 1, // default
+ #else
+ .button_led_high_ramp_level = DEFAULT_LEVEL - 1, // default
+ #endif
+ #endif
+ #endif
+
};
diff --git a/ui/anduril/lockout-mode.c b/ui/anduril/lockout-mode.c
index 6f85ca9..255ab22 100644
--- a/ui/anduril/lockout-mode.c
+++ b/ui/anduril/lockout-mode.c
@@ -12,25 +12,31 @@ uint8_t lockout_state(Event event, uint16_t arg) {
// button is being held
#ifdef USE_AUX_RGB_LEDS
// don't turn on during RGB aux LED configuration
- if (event == EV_click7_hold) { set_level(0); } else
+ //if (event == EV_click7_hold) { set_level(0); } else
#endif
- if ((event & (B_CLICK | B_PRESS)) == (B_CLICK | B_PRESS)) {
- // hold: lowest floor
- // click, hold: highest floor (or manual mem level)
+ uint8_t click_num = event & B_COUNT;
+ if ( // button pressed 1st or 2nd time
+ ((B_CLICK | B_PRESS) == (event & (B_CLICK | B_PRESS)))
+ && (click_num <= 2)
+ ) {
uint8_t lvl = cfg.ramp_floors[0];
- if (1 == (event & 0x0f)) { // first click
+ // hold: lowest floor
+ if (1 == click_num) { // 1st click
if (cfg.ramp_floors[1] < lvl) lvl = cfg.ramp_floors[1];
- } else { // 2nd click or later
- if (cfg.ramp_floors[1] > lvl) lvl = cfg.ramp_floors[1];
+ }
+ // click, hold: highest floor (or manual mem level)
+ else { // 2nd click
#ifdef USE_MANUAL_MEMORY
if (cfg.manual_memory) lvl = cfg.manual_memory;
+ else
#endif
+ if (cfg.ramp_floors[1] > lvl) lvl = cfg.ramp_floors[1];
}
- set_level(lvl);
+ off_state_set_level(lvl);
}
// button was released
- else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
- set_level(0);
+ else if ((B_CLICK) == (event & (B_CLICK | B_PRESS))) {
+ off_state_set_level(0);
}
#endif // ifdef USE_MOON_DURING_LOCKOUT_MODE
diff --git a/ui/anduril/off-mode.c b/ui/anduril/off-mode.c
index 36d771c..f699c89 100644
--- a/ui/anduril/off-mode.c
+++ b/ui/anduril/off-mode.c
@@ -264,19 +264,6 @@ uint8_t off_state(Event event, uint16_t arg) {
#endif // ifndef USE_EXTENDED_SIMPLE_UI
#endif // ifdef USE_SIMPLE_UI
- // click, click, long-click: strobe mode
- #ifdef USE_STROBE_STATE
- else if (event == EV_click3_hold) {
- set_state(strobe_state, 0);
- return EVENT_HANDLED;
- }
- #elif defined(USE_BORING_STROBE_STATE)
- else if (event == EV_click3_hold) {
- set_state(boring_strobe_state, 0);
- return EVENT_HANDLED;
- }
- #endif
-
#ifdef USE_INDICATOR_LED
// 7 clicks: change indicator LED mode
else if (event == EV_7clicks) {
@@ -334,6 +321,19 @@ uint8_t off_state(Event event, uint16_t arg) {
}
#endif // ifdef USE_EXTENDED_SIMPLE_UI
+ // click, click, long-click: strobe mode
+ #ifdef USE_STROBE_STATE
+ else if (event == EV_click3_hold) {
+ set_state(strobe_state, 0);
+ return EVENT_HANDLED;
+ }
+ #elif defined(USE_BORING_STROBE_STATE)
+ else if (event == EV_click3_hold) {
+ set_state(boring_strobe_state, 0);
+ return EVENT_HANDLED;
+ }
+ #endif
+
// 10 clicks: enable simple UI
else if (event == EV_10clicks) {
blink_once();
diff --git a/ui/anduril/strobe-modes.c b/ui/anduril/strobe-modes.c
index ccc4aa0..38e4dca 100644
--- a/ui/anduril/strobe-modes.c
+++ b/ui/anduril/strobe-modes.c
@@ -13,7 +13,7 @@ uint8_t strobe_state(Event event, uint16_t arg) {
// 'st' reduces ROM size slightly
strobe_mode_te st = current_strobe_type;
- #ifdef USE_MOMENTARY_MODE
+ #if defined(USE_MOMENTARY_MODE) || defined(USE_TACTICAL_MODE)
momentary_mode = 1; // 0 = ramping, 1 = strobes
#endif
diff --git a/ui/anduril/tactical-mode.c b/ui/anduril/tactical-mode.c
index 5acd902..002f917 100644
--- a/ui/anduril/tactical-mode.c
+++ b/ui/anduril/tactical-mode.c
@@ -68,6 +68,11 @@ uint8_t tactical_state(Event event, uint16_t arg) {
return lockout_state(event, arg);
}
+ // handle 3C here to prevent changing channel modes unintentionally
+ if (event == EV_3clicks) {
+ return EVENT_HANDLED;
+ }
+
// 6 clicks: exit and turn off
else if (event == EV_6clicks) {
blink_once();