aboutsummaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
Diffstat (limited to 'hw')
-rw-r--r--hw/fireflies/pl47g2/219/anduril.h13
-rw-r--r--hw/fireflies/pl47g2/219/model1
-rw-r--r--hw/hank/emisar-2ch/fet/joined/README.md25
-rw-r--r--hw/hank/emisar-2ch/fet/joined/anduril.h113
-rw-r--r--hw/hank/emisar-2ch/fet/joined/hwdef.c181
-rw-r--r--hw/hank/emisar-2ch/fet/joined/hwdef.h191
-rw-r--r--hw/hank/emisar-2ch/fet/joined/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/lumintop/fw3x-lume1/README.md10
-rw-r--r--hw/sofirn/sp10-pro/hwdef.h4
-rw-r--r--hw/thefreeman/avr32dd20-devkit/anduril.h1
-rw-r--r--hw/thefreeman/avr32dd20-devkit/hwdef.c19
-rw-r--r--hw/thefreeman/avr32dd20-devkit/hwdef.h27
-rw-r--r--hw/thefreeman/boost-fwaa-mp3432-hdr-dac-rgb/hwdef.h4
-rw-r--r--hw/wurkkos/fc13/README.md10
-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
-rw-r--r--hw/wurkkos/ts11/README.md8
-rw-r--r--hw/wurkkos/ts25/README.md20
31 files changed, 1708 insertions, 14 deletions
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-2ch/fet/joined/README.md b/hw/hank/emisar-2ch/fet/joined/README.md
new file mode 100644
index 0000000..a8b0456
--- /dev/null
+++ b/hw/hank/emisar-2ch/fet/joined/README.md
@@ -0,0 +1,25 @@
+# Emisar 2-channel driver connected to only 1 set of LEDs
+
+The default Emisar D4S driver doesn't support a lighted switch, so for lights
+built with one of those, Hank puts in a 2-channel driver instead... but he only
+connects 1 set of LEDs.
+
+This is an odd hardware configuration used only for special orders, I think.
+
+The driver itself has 3 power channels:
+
+ 1. Linear regulator 1
+ 2. Linear regulator 2
+ 3. Direct-drive FET
+
+Channels 1 and 3 are connected to the main LEDs, while channel 2 is not
+physically wired up to anything.
+
+Two channel modes are included:
+
+ 1. Linear only
+ 2. Linear + DD FET
+
+Channel mode 2 is default, and the only mode enabled after a factory reset.
+Channel mode 1 is included in case anyone wants to turn off the DD FET.
+
diff --git a/hw/hank/emisar-2ch/fet/joined/anduril.h b/hw/hank/emisar-2ch/fet/joined/anduril.h
new file mode 100644
index 0000000..d9b36f9
--- /dev/null
+++ b/hw/hank/emisar-2ch/fet/joined/anduril.h
@@ -0,0 +1,113 @@
+// Anduril config for Emisar 2ch+FET-joined-into-1-channel
+// Copyright (C) 2024 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+// This is mostly just used on the Emisar D4S w/ lighted switch,
+// which uses a 2-channel driver but only actually uses 1 set of LEDs
+// (so instead of the usual 2ch or 2ch-fet firmware, it needs something else)
+
+#define HWDEF_H hank/emisar-2ch/fet/joined/hwdef.h
+#include "hank/anduril.h"
+
+// this light has three aux LED channels: R, G, B
+#define USE_AUX_RGB_LEDS
+// the aux LEDs are front-facing, so turn them off while main LEDs are on
+// it also has an independent LED in the button
+#define USE_BUTTON_LED
+// enabling this option breaks the button LED
+#ifdef USE_INDICATOR_LED_WHILE_RAMPING
+#undef USE_INDICATOR_LED_WHILE_RAMPING
+#endif
+
+// channel modes...
+// CM_CH1 -- linear ch1 only
+// CM_CH1_FET -- linear ch1 + DD FET
+#define DEFAULT_CHANNEL_MODE CM_CH1_FET
+
+#define USE_CONFIG_COLORS
+
+// strobes on this light should use the same channel as the ramp
+#undef USE_CHANNEL_PER_STROBE
+
+// blink numbers on the main LEDs by default (but allow user to change it)
+#define DEFAULT_BLINK_CHANNEL CM_CH1_FET
+
+#define POLICE_COLOR_STROBE_CH1 CM_AUXRED
+#define POLICE_COLOR_STROBE_CH2 CM_AUXBLU
+
+#define RAMP_SIZE 150
+
+// Output estimates:
+// - linear ch1 only: 2500 lm
+// - ch1 + DD FET: 3500 lm
+// (tested on a D4S with Osram W2 LEDs, with a mediocre battery at 3.7V)
+// (LuxWad estimated 6000 lm (uncalibrated) on a full high-amp cell)
+// (I picked 4500 lm as a rough realistic estimate)
+// - linear ch2 only: 0 (not connected)
+// - moon: 0.7 lm at PWM 1/4096, 0.15 lm at DSM 0/32640
+
+// delta-sigma modulated PWM (0b0HHHHHHHHLLLLLLL = 0, 8xHigh, 7xLow bits)
+// (max is (255 << 7), because it's 8-bit PWM plus 7 bits of DSM)
+//
+// linear ch1 only (copied from noctigon-m44 ramp)
+// level_calc.py 5.01 1 150 7135 0 0.2 2000 --pwm 32640
+#define PWM1_LEVELS 0,1,2,3,4,5,6,7,9,10,12,14,17,19,22,25,28,32,36,41,45,50,56,62,69,76,84,92,101,110,121,132,143,156,169,184,199,215,232,251,270,291,313,336,360,386,414,442,473,505,539,574,612,651,693,736,782,829,880,932,987,1045,1105,1168,1233,1302,1374,1449,1527,1608,1693,1781,1873,1969,2068,2172,2279,2391,2507,2628,2753,2883,3018,3158,3303,3454,3609,3771,3938,4111,4289,4475,4666,4864,5068,5280,5498,5724,5957,6197,6445,6701,6965,7237,7518,7808,8106,8413,8730,9056,9392,9737,10093,10459,10835,11223,11621,12031,12452,12884,13329,13786,14255,14737,15232,15741,16262,16798,17347,17911,18489,19082,19691,20314,20954,21609,22281,22969,23674,24397,25137,25895,26671,27465,28279,29111,29963,30835,31727,32640
+//
+// linear+FET linear ch1 segment
+// level_calc.py 4.85 2 150 7135 0 0.5 2500 FET 1 10 4500 --pwm 32640
+#define PWM2_LEVELS 0,2,3,5,7,9,11,14,17,20,24,28,33,38,44,50,57,64,72,81,91,101,112,125,138,152,167,184,201,220,241,263,286,311,337,365,395,427,461,497,535,575,618,663,711,761,814,870,929,992,1057,1126,1198,1274,1353,1437,1524,1616,1712,1812,1917,2027,2141,2261,2386,2516,2652,2794,2941,3095,3255,3422,3595,3775,3962,4156,4358,4568,4785,5011,5245,5487,5738,5999,6268,6548,6837,7135,7445,7764,8095,8437,8790,9154,9531,9919,10321,10735,11162,11602,12056,12524,13006,13503,14015,14542,15085,15643,16218,16810,17419,18045,18688,19350,20031,20730,21448,22186,22944,23722,24521,25342,26184,27048,27935,28844,29777,30733,31714,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,32640,0
+//
+// linear+FET DD FET segment
+// level_calc.py 4.85 2 150 7135 0 0.5 2500 FET 1 10 4500 --pwm 255
+#define PWM3_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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,20,31,42,53,65,76,88,101,113,126,139,152,166,180,194,209,224,239,255
+
+#define DEFAULT_LEVEL 70
+#define MIN_THERM_STEPDOWN 50
+#define MAX_REGULATED 130
+#define MAX_1x7135 MAX_REGULATED
+#define MAX_Nx7135 MAX_REGULATED
+#define HALFSPEED_LEVEL 20
+#define QUARTERSPEED_LEVEL 10
+
+#define RAMP_SMOOTH_FLOOR 1
+#define RAMP_SMOOTH_CEIL 150
+// 10, 30, 50, [70], 90, 110, [130]
+#define RAMP_DISCRETE_FLOOR 10
+#define RAMP_DISCRETE_CEIL MAX_REGULATED
+#define RAMP_DISCRETE_STEPS 7
+
+// safe limit highest regulated power (no FET or turbo)
+// 10, 40, [70], 100, [130]
+#define SIMPLE_UI_FLOOR RAMP_DISCRETE_FLOOR
+#define SIMPLE_UI_CEIL RAMP_DISCRETE_CEIL
+#define SIMPLE_UI_STEPS 5
+
+// stop panicking at ~2500 lm
+#define THERM_FASTER_LEVEL MAX_REGULATED
+
+#define USE_POLICE_COLOR_STROBE_MODE
+#undef TACTICAL_LEVELS
+#define TACTICAL_LEVELS MAX_REGULATED,30,(RAMP_SIZE+3) // high, low, police strobe
+
+// use the brightest setting for strobe
+#define STROBE_BRIGHTNESS MAX_LEVEL
+// slow down party strobe; this driver can't pulse for less than 1ms
+// (in linear-only mode ... but FET mode can pulse a much shorter time)
+#define PARTY_STROBE_ONTIME 1
+
+// the default of 26 looks a bit flat, so increase it
+#define CANDLE_AMPLITUDE 33
+
+// the power regulator is a bit slow, so push it harder for a quick response from off
+#define DEFAULT_JUMP_START_LEVEL 22
+#define BLINK_BRIGHTNESS 45
+#define BLINK_ONCE_TIME 12 // longer blink, since main LEDs are slow
+
+#define THERM_CAL_OFFSET 5
+
+// don't blink while ramping
+#ifdef BLINK_AT_RAMP_MIDDLE
+#undef BLINK_AT_RAMP_MIDDLE
+#endif
+
diff --git a/hw/hank/emisar-2ch/fet/joined/hwdef.c b/hw/hank/emisar-2ch/fet/joined/hwdef.c
new file mode 100644
index 0000000..5785149
--- /dev/null
+++ b/hw/hank/emisar-2ch/fet/joined/hwdef.c
@@ -0,0 +1,181 @@
+// Emisar 2-channel-plus-FET-joined-into-1-channel
+// Copyright (C) 2024 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include "fsm/chan-rgbaux.c"
+
+
+void set_level_zero();
+
+void set_level_ch1(uint8_t level);
+void set_level_ch1_fet(uint8_t level);
+
+bool gradual_tick_ch1(uint8_t gt);
+bool gradual_tick_ch1_fet(uint8_t gt);
+
+
+Channel channels[] = {
+ { // channel 1 only
+ .set_level = set_level_ch1,
+ .gradual_tick = gradual_tick_ch1,
+ // .has_args = 0
+ },
+ { // channel 1 + DD FET
+ .set_level = set_level_ch1_fet,
+ .gradual_tick = gradual_tick_ch1_fet,
+ // .has_args = 0
+ },
+ RGB_AUX_CHANNELS
+};
+
+
+void set_level_zero() {
+ // disable timer overflow interrupt
+ // (helps improve button press handling from Off state)
+ DSM_INTCTRL &= ~DSM_OVF_bm;
+
+ // turn off all LEDs
+ ch1_dsm_lvl = 0;
+ //ch2_dsm_lvl = 0;
+ CH1_PWM = 0;
+ CH2_PWM = 0;
+ CH3_PWM = 0;
+ PWM_CNT = 0; // reset phase
+ CH1_ENABLE_PORT &= ~(1 << CH1_ENABLE_PIN); // disable opamp
+ CH2_ENABLE_PORT &= ~(1 << CH2_ENABLE_PIN); // disable opamp
+}
+
+
+// wrap setting the dsm vars, to get a faster response
+// (just setting *_dsm_lvl doesn't work well for strobes)
+// set new values for both channels,
+// handling any possible combination
+// and any before/after state
+void set_hw_levels(PWM1_DATATYPE ch1,
+ //PWM2_DATATYPE ch2,
+ PWM3_DATATYPE ch3
+ //, bool ch1_on, bool ch2_on
+ ) {
+
+ //bool was_on = (CH1_PWM>0) || (CH2_PWM>0)
+ // || ( CH1_ENABLE_PORT & (1 << CH1_ENABLE_PIN) )
+ // || ( CH2_ENABLE_PORT & (1 << CH2_ENABLE_PIN) );
+
+ //if (ch1 || ch1_on)
+ CH1_ENABLE_PORT |= (1 << CH1_ENABLE_PIN); // enable opamp
+ //else
+ // CH1_ENABLE_PORT &= ~(1 << CH1_ENABLE_PIN); // disable opamp
+
+ //if (ch2 || ch2_on)
+ // CH2_ENABLE_PORT |= (1 << CH2_ENABLE_PIN); // enable opamp
+ //else
+ // CH2_ENABLE_PORT &= ~(1 << CH2_ENABLE_PIN); // disable opamp
+
+ // set delta-sigma soft levels
+ ch1_dsm_lvl = ch1;
+ //ch2_dsm_lvl = ch2;
+
+ // set hardware PWM levels and init dsm loop
+ CH1_PWM = ch1_pwm = ch1 >> 7;
+ //CH2_PWM = ch2_pwm = ch2 >> 7;
+ CH3_PWM = ch3;
+
+ // enable timer overflow interrupt so DSM can work
+ DSM_INTCTRL |= DSM_OVF_bm;
+
+ // reset phase when turning on
+ if (! prev_level) PWM_CNT = 0;
+
+}
+
+// delta-sigma modulation of PWM outputs
+// happens on each Timer overflow (every 512 cpu clock cycles)
+// uses 8-bit pwm w/ 7-bit dsm (0b 0PPP PPPP PDDD DDDD)
+ISR(DSM_vect) {
+ // set new hardware values first,
+ // for best timing (reduce effect of interrupt jitter)
+ CH1_PWM = ch1_pwm;
+ //CH2_PWM = ch2_pwm;
+
+ // calculate next values, now that timing matters less
+
+ // accumulate error
+ ch1_dsm += (ch1_dsm_lvl & 0x007f);
+ // next PWM = base PWM value + carry bit
+ ch1_pwm = (ch1_dsm_lvl >> 7) + (ch1_dsm > 0x7f);
+ // clear carry bit
+ ch1_dsm &= 0x7f;
+
+ // repeat for other channels
+
+ //ch2_dsm += (ch2_dsm_lvl & 0x007f);
+ //ch2_pwm = (ch2_dsm_lvl >> 7) + (ch2_dsm > 0x7f);
+ //ch2_dsm &= 0x7f;
+}
+
+
+void set_level_ch1(uint8_t level) {
+ PWM1_DATATYPE pwm1 = PWM1_GET(level);
+ set_hw_levels(pwm1, 0);
+}
+
+void set_level_ch1_fet(uint8_t level) {
+ PWM2_DATATYPE pwm2 = PWM2_GET(level);
+ PWM3_DATATYPE pwm3 = PWM3_GET(level);
+ set_hw_levels(pwm2, pwm3);
+}
+
+
+///// bump each channel toward a target value /////
+bool gradual_adjust(PWM1_DATATYPE ch1,
+ //PWM2_DATATYPE ch2,
+ PWM3_DATATYPE ch3) {
+
+ // instant 0 to TOP change, and TOP to 0
+ if (((DSM_TOP == ch1_dsm_lvl) && (0 == ch1)) ||
+ ((DSM_TOP == ch1) && (0 == ch1_dsm_lvl)))
+ ch1_dsm_lvl = ch1;
+
+ // bump the DD FET multiple steps
+ // (it only has 255 steps, but the effect is small
+ // compared to the linear channel it's stacked on,
+ // so smaller steps tend to overheat on turbo)
+ for (uint8_t i=0; i<4; i++)
+ GRADUAL_ADJUST_SIMPLE(ch3, CH3_PWM);
+
+ // if DSM changed by less than 100%,
+ // adjust multiple times based on current brightness
+ // (so it adjusts faster/coarser when bright, slower/finer when dim)
+
+ // higher shift = slower/finer adjustments
+ const uint8_t shift = 9; // ((255 << 7) >> 9) = 63 max
+ uint8_t steps;
+
+ steps = ch1_dsm_lvl >> shift;
+ for (uint8_t i=0; i<=steps; i++)
+ GRADUAL_ADJUST_SIMPLE(ch1, ch1_dsm_lvl);
+
+ //steps = ch2_dsm_lvl >> shift;
+ ////for (uint8_t i=0; i<=steps; i++)
+ //// GRADUAL_ADJUST_SIMPLE(ch2, ch2_dsm_lvl);
+
+ if ((ch1 == ch1_dsm_lvl)
+ //&& (ch2 == ch2_dsm_lvl)
+ && (ch3 == CH3_PWM) ) {
+ return true; // done
+ }
+ return false; // not done yet
+}
+
+bool gradual_tick_ch1(uint8_t gt) {
+ PWM1_DATATYPE pwm1 = PWM1_GET(gt);
+ return gradual_adjust(pwm1, 0);
+}
+
+bool gradual_tick_ch1_fet(uint8_t gt) {
+ PWM2_DATATYPE pwm2 = PWM2_GET(gt);
+ PWM3_DATATYPE pwm3 = PWM3_GET(gt);
+ return gradual_adjust(pwm2, pwm3);
+}
+
diff --git a/hw/hank/emisar-2ch/fet/joined/hwdef.h b/hw/hank/emisar-2ch/fet/joined/hwdef.h
new file mode 100644
index 0000000..f260743
--- /dev/null
+++ b/hw/hank/emisar-2ch/fet/joined/hwdef.h
@@ -0,0 +1,191 @@
+// Emisar 2-channel-plus-FET-joined-into-1-channel
+// Copyright (C) 2024 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+/*
+ * Pin / Name / Function
+ * 1 PA6 ch2 LED PWM (linear) (PWM1B) (unused)
+ * 2 PA5 R: red aux LED (PWM0B)
+ * 3 PA4 G: green aux LED
+ * 4 PA3 B: blue aux LED
+ * 5 PA2 button LED
+ * 6 PA1 Opamp 2 enable (channel 2 LEDs) (unused)
+ * 7 PA0 Opamp 1 enable (channel 1 LEDs)
+ * 8 GND GND
+ * 9 VCC VCC
+ * 10 PC5 (none)
+ * 11 PC4 (none)
+ * 12 PC3 RESET
+ * 13 PC2 (none)
+ * 14 PC1 SCK
+ * 15 PC0 ch1 LED PWM (FET) (PWM0A, 8-bit)
+ * 16 PB3 ch1 LED PWM (linear) (PWM1A)
+ * 17 PB2 MISO
+ * 18 PB1 MOSI / battery voltage (ADC6)
+ * 19 PB0 (none)
+ * 20 PA7 e-switch (PCINT7)
+ * ADC12 thermal sensor
+ *
+ * This is used for lights with only 1 set of main LEDs, when using the
+ * 2-channel driver internally. This allows the D4S to have a lighted button.
+ * Hank typically connects only linear ch1 and sometimes the DD FET
+ * to the main LEDs, leaving linear ch2 completely unused. Support is
+ * included in an extra channel mode in case modders decide to use it.
+ * It can raise the maximum regulated level.
+ */
+
+#define HWDEF_C hank/emisar-2ch/fet/joined/hwdef.c
+
+// allow using aux LEDs as extra channel modes
+#include "fsm/chan-rgbaux.h"
+
+// channel modes:
+// * 0. linear (ch1) only
+// * 1. linear (ch1) + DD FET <-- Hank's primary config, as far as I can tell
+// x 2. linear (both) + DD FET (not used)
+#define NUM_CHANNEL_MODES (2 + NUM_RGB_AUX_CHANNEL_MODES)
+enum channel_modes_e {
+ CM_CH1 = 0,
+ CM_CH1_FET, // <-- default
+ //CM_BOTH_FET,
+ RGB_AUX_ENUMS
+};
+
+// right-most bit first, modes are in fedcba9876543210 order
+#define CHANNEL_MODES_ENABLED 0b0000000000000010
+//#define USE_CHANNEL_MODE_ARGS
+// _, _, _, 128=middle CCT, 0=warm-to-cool
+//#define CHANNEL_MODE_ARGS 0,0,0,RGB_AUX_CM_ARGS
+
+#define PWM_CHANNELS 3 // old, remove this
+
+#define PWM_BITS 16 // 0 to 32640 (0 to 255 PWM + 0 to 127 DSM)
+#define PWM_GET PWM_GET16
+#define PWM_DATATYPE uint16_t
+// linear-only ramp
+#define PWM1_DATATYPE uint16_t
+#define PWM1_GET(l) PWM_GET16(pwm1_levels, l)
+// linear part of linear+FET ramp
+#define PWM2_DATATYPE uint16_t
+#define PWM2_GET(l) PWM_GET16(pwm2_levels, l)
+// DD FET part of linear+FET ramp
+#define PWM3_DATATYPE uint8_t
+#define PWM3_GET(l) PWM_GET8(pwm3_levels, l)
+
+// PWM parameters of both channels are tied together because they share a counter
+#define PWM_TOP ICR1 // holds the TOP value for for variable-resolution PWM
+#define PWM_TOP_INIT 255
+#define PWM_CNT TCNT1 // for checking / resetting phase
+// (max is (255 << 7), because it's 8-bit PWM plus 7 bits of DSM)
+#define DSM_TOP (255<<7) // 15-bit resolution leaves 1 bit for carry
+
+// timer interrupt for DSM
+#define DSM_vect TIMER1_OVF_vect
+#define DSM_INTCTRL TIMSK
+#define DSM_OVF_bm (1<<TOIE1)
+
+#define DELAY_FACTOR 90 // less time in delay() because more time spent in interrupts
+
+// main LEDs, linear
+uint16_t ch1_dsm_lvl;
+uint8_t ch1_pwm, ch1_dsm;
+#define CH1_PIN PB3 // pin 16, Opamp reference
+#define CH1_PWM OCR1A // OCR1A is the output compare register for PB3
+#define CH1_ENABLE_PIN PA0 // pin 7, Opamp power
+#define CH1_ENABLE_PORT PORTA // control port for PA0
+
+// 2nd LEDs, linear (unused)
+#define CH2_PIN PA6 // pin 1, 2nd LED Opamp reference
+#define CH2_PWM OCR1B // OCR1B is the output compare register for PA6
+#define CH2_ENABLE_PIN PA1 // pin 6, Opamp power
+#define CH2_ENABLE_PORT PORTA // control port for PA1
+
+// main LEDs, DD FET
+#define CH3_PIN PC0 // pin 15, DD FET PWM
+#define CH3_PWM OCR0A // OCR0A is the output compare register for PC0
+
+// e-switch
+#ifndef SWITCH_PIN
+#define SWITCH_PIN PA7 // pin 20
+#define SWITCH_PCINT PCINT7 // pin 20 pin change interrupt
+#define SWITCH_PCIE PCIE0 // PCIE1 is for PCINT[7:0]
+#define SWITCH_PCMSK PCMSK0 // PCMSK1 is for PCINT[7:0]
+#define SWITCH_PORT PINA // PINA or PINB or PINC
+#define SWITCH_PUE PUEA // pullup group A
+#define SWITCH_VECT PCINT0_vect // ISR for PCINT[7:0]
+#endif
+
+#include "hank/vdivider-1634.h"
+
+// this light has aux LEDs under the optic
+#define AUXLED_R_PIN PA5 // pin 2
+#define AUXLED_G_PIN PA4 // pin 3
+#define AUXLED_B_PIN PA3 // pin 4
+#define AUXLED_RGB_PORT PORTA // PORTA or PORTB or PORTC
+#define AUXLED_RGB_DDR DDRA // DDRA or DDRB or DDRC
+#define AUXLED_RGB_PUE PUEA // PUEA or PUEB or PUEC
+
+#define BUTTON_LED_PIN PA2 // pin 5
+#define BUTTON_LED_PORT PORTA // for all "PA" pins
+#define BUTTON_LED_DDR DDRA // for all "PA" pins
+#define BUTTON_LED_PUE PUEA // for all "PA" pins
+
+
+inline void hwdef_setup() {
+ // enable output ports
+ DDRC = (1 << CH3_PIN);
+ DDRB = (1 << CH1_PIN);
+ DDRA = (1 << CH2_PIN)
+ | (1 << AUXLED_R_PIN)
+ | (1 << AUXLED_G_PIN)
+ | (1 << AUXLED_B_PIN)
+ | (1 << BUTTON_LED_PIN)
+ | (1 << CH1_ENABLE_PIN)
+ | (1 << CH2_ENABLE_PIN)
+ ;
+
+ // configure PWM
+ // Setup PWM. F_pwm = F_clkio / 2 / N / TOP, where N = prescale factor, TOP = top of counter
+ // pre-scale for timer: N = 1
+ // Linear opamp PWM for both main and 2nd LEDs (10-bit)
+ // WGM1[3:0]: 1,0,1,0: PWM, Phase Correct, adjustable (DS table 12-5)
+ // CS1[2:0]: 0,0,1: clk/1 (No prescaling) (DS table 12-6)
+ // COM1A[1:0]: 1,0: PWM OC1A in the normal direction (DS table 12-4)
+ // COM1B[1:0]: 1,0: PWM OC1B in the normal direction (DS table 12-4)
+ TCCR1A = (1<<WGM11) | (0<<WGM10) // adjustable PWM (TOP=ICR1) (DS table 12-5)
+ | (1<<COM1A1) | (0<<COM1A0) // PWM 1A in normal direction (DS table 12-4)
+ | (1<<COM1B1) | (0<<COM1B0) // PWM 1B in normal direction (DS table 12-4)
+ ;
+ TCCR1B = (0<<CS12) | (0<<CS11) | (1<<CS10) // clk/1 (no prescaling) (DS table 12-6)
+ | (1<<WGM13) | (0<<WGM12) // phase-correct adjustable PWM (DS table 12-5)
+ ;
+
+ // FET PWM (8-bit; this channel can't do 10-bit)
+ // WGM0[2:0]: 0,0,1: PWM, Phase Correct, 8-bit (DS table 11-8)
+ // CS0[2:0]: 0,0,1: clk/1 (No prescaling) (DS table 11-9)
+ // COM0A[1:0]: 1,0: PWM OC0A in the normal direction (DS table 11-4)
+ // COM0B[1:0]: 1,0: PWM OC0B in the normal direction (DS table 11-7)
+ TCCR0A = (0<<WGM01) | (1<<WGM00) // 8-bit (TOP=0xFF) (DS table 11-8)
+ | (1<<COM0A1) | (0<<COM0A0) // PWM 0A in normal direction (DS table 11-4)
+ //| (1<<COM0B1) | (0<<COM0B0) // PWM 0B in normal direction (DS table 11-7)
+ ;
+ TCCR0B = (0<<CS02) | (0<<CS01) | (1<<CS00) // clk/1 (no prescaling) (DS table 11-9)
+ | (0<<WGM02) // phase-correct PWM (DS table 11-8)
+ ;
+
+ // set PWM resolution
+ PWM_TOP = PWM_TOP_INIT;
+
+ // set up interrupt for delta-sigma modulation
+ // (moved to hwdef.c functions so it can be enabled/disabled based on ramp level)
+ //DSM_INTCTRL |= DSM_OVF_bm; // interrupt once for each timer cycle
+
+ // set up e-switch
+ SWITCH_PUE = (1 << SWITCH_PIN); // pull-up for e-switch
+ SWITCH_PCMSK = (1 << SWITCH_PCINT); // enable pin change interrupt
+}
+
+
+#define LAYOUT_DEFINED
+
diff --git a/hw/hank/emisar-2ch/fet/joined/model b/hw/hank/emisar-2ch/fet/joined/model
new file mode 100644
index 0000000..5e8eba0
--- /dev/null
+++ b/hw/hank/emisar-2ch/fet/joined/model
@@ -0,0 +1 @@
+0137
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/lumintop/fw3x-lume1/README.md b/hw/lumintop/fw3x-lume1/README.md
index 190cd0e..d155eba 100644
--- a/hw/lumintop/fw3x-lume1/README.md
+++ b/hw/lumintop/fw3x-lume1/README.md
@@ -6,9 +6,10 @@ sensor.
## Notes of interest
-**Flashing firmware**: The MOSI and MISO pin are swapped, compared to a Hanklight.
-LoneOceans sent a fixed driver design to Lumintop, but the new design didn't
-get produced. So to flash firmware, swap the wires for those two pins first.
+**Flashing firmware**: The MOSI and MISO pin are swapped on PCBs dated 01/20,
+compared to a Hanklight. LoneOceans sent a fixed driver design (dated 06/20)
+to Lumintop, but the new design didn't get produced. So to flash firmware,
+swap the wires for those two pins first.
**Fuses**: Lumintop seems to have shipped the FW3X with the wrong fuse values,
so it has the MCU severely underclocked and everything runs much slower than it
@@ -29,3 +30,6 @@ output is unstable so there is very visible ripple. The user can either raise
the ramp floor to a level high enough to avoid these issues, or learn to live
with the ripple and preflash.
+LoneOceans has some extra notes here, which mostly apply only to older versions
+of the code: https://budgetlightforum.com/t/anduril-2/62656/531
+
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/anduril.h b/hw/thefreeman/avr32dd20-devkit/anduril.h
index 02d5de3..3152183 100644
--- a/hw/thefreeman/avr32dd20-devkit/anduril.h
+++ b/hw/thefreeman/avr32dd20-devkit/anduril.h
@@ -110,6 +110,7 @@
#define PARTY_STROBE_ONTIME 1 // slow down party strobe
#define STROBE_OFF_LEVEL 1 // keep the regulator chip on between pulses
+#define BLIP_LEVEL 1 // same
// smoother candle mode with bigger oscillations
#define CANDLE_AMPLITUDE 40
diff --git a/hw/thefreeman/avr32dd20-devkit/hwdef.c b/hw/thefreeman/avr32dd20-devkit/hwdef.c
index 3e5dd79..5b534d2 100644
--- a/hw/thefreeman/avr32dd20-devkit/hwdef.c
+++ b/hw/thefreeman/avr32dd20-devkit/hwdef.c
@@ -30,6 +30,11 @@ void set_level_zero() {
delay_4ms(IN_NFET_DELAY_TIME/4);
IN_NFET_ENABLE_PORT &= ~(1 << IN_NFET_ENABLE_PIN);
+ #ifdef USE_BST_BYPASS
+ // turn off bypass
+ BST_BYPASS_PORT |= (1 << BST_BYPASS_PIN);
+ #endif
+
// turn off boost last
BST_ENABLE_PORT &= ~(1 << BST_ENABLE_PIN); // BST off
}
@@ -48,6 +53,14 @@ void set_level_main(uint8_t level) {
// BST on first, to give it a few extra microseconds to spin up
BST_ENABLE_PORT |= (1 << BST_ENABLE_PIN);
+ #ifdef USE_BST_BYPASS
+ // turn on bypass in li-ion mode
+ if (voltage > DUAL_VOLTAGE_FLOOR)
+ BST_BYPASS_PORT &= ~(1 << BST_BYPASS_PIN); // low = bypass
+ else // turn off bypass in AA/NiMH mode
+ BST_BYPASS_PORT |= (1 << BST_BYPASS_PIN); // high = boost
+ #endif
+
// 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;
@@ -106,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 f1b6095..5015c24 100644
--- a/hw/thefreeman/avr32dd20-devkit/hwdef.h
+++ b/hw/thefreeman/avr32dd20-devkit/hwdef.h
@@ -28,6 +28,7 @@
* 18 PA1 G: aux green
* 19 PA2 B: aux blue
* 20 PA3 CH: detect charging
+ * or BBY: boost bypass PFET
*
* BST EN enable the boost regulator and Op-Amp
* DAC sets the current, max current depends on Vset voltage divider and Rsense
@@ -35,6 +36,8 @@
* and low value Rsense (high current range, pin high)
* IN- NFET : pull up after BST enable to eliminate startup flash, pull down otherwise
* CH senses the status of the onboard charger
+ * BBY routes power around the boost converter in li-ion 3V mode
+ * (pin low = bypass, pin high = boost)
* BATT LVL : Vbat * (100.0/(330+100))
* LVB is for OTSM firmware, not used here
*/
@@ -77,6 +80,11 @@ enum CHANNEL_MODES {
#define BST_ENABLE_PIN PIN5_bp
#define BST_ENABLE_PORT PORTD_OUT
+// BST bypass
+#define USE_BST_BYPASS
+#define BST_BYPASS_PIN PIN3_bp
+#define BST_BYPASS_PORT PORTA_OUT
+
// HDR
// turns on HDR FET for the high current range
#define HDR_ENABLE_PIN PIN7_bp
@@ -101,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
@@ -133,7 +141,9 @@ inline void hwdef_setup() {
VPORTA.DIR = PIN0_bm // R
| PIN1_bm // G
| PIN2_bm // B
- //| PIN3_bm // CH
+ #ifdef USE_BST_BYPASS
+ | PIN3_bm // BBY
+ #endif
| PIN7_bm; // HDR
VPORTD.DIR = PIN5_bm // EN
| PIN6_bm // DAC
@@ -143,7 +153,10 @@ inline void hwdef_setup() {
//PORTA.PIN0CTRL = PORT_PULLUPEN_bm; // R
//PORTA.PIN1CTRL = PORT_PULLUPEN_bm; // G
//PORTA.PIN2CTRL = PORT_PULLUPEN_bm; // B
- //PORTA.PIN3CTRL = PORT_PULLUPEN_bm; // CH
+ #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;
//PORTA.PIN6CTRL = PORT_PULLUPEN_bm; // BATT LVL
@@ -177,6 +190,12 @@ inline void hwdef_setup() {
// to generate a zero without spending power on the DAC
// (and do this in set_level_zero() too)
+ // TCA/TCB/TCD aren't being used, so turn them off
+ TCA0.SINGLE.CTRLA = 0;
+ TCB0.CTRLA = 0;
+ TCB1.CTRLA = 0;
+ TCD0.CTRLA = 0;
+
}
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/fc13/README.md b/hw/wurkkos/fc13/README.md
new file mode 100644
index 0000000..ab9b87f
--- /dev/null
+++ b/hw/wurkkos/fc13/README.md
@@ -0,0 +1,10 @@
+# Wurkkos FC13
+
+Single LED 18650 torch with FET+1 driver and RGB button. Includes a built-in
+charger / powerbank too, separate from the main torch circuit.
+
+The Wurkkos SP30S Pro also uses this firmware.
+
+Either torch may ship with the `0715` (wurkkos-ts25) firmware installed, but
+the RGB button does not work fully until upgrading to this FC13 firmware.
+
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/hw/wurkkos/ts11/README.md b/hw/wurkkos/ts11/README.md
new file mode 100644
index 0000000..e53e038
--- /dev/null
+++ b/hw/wurkkos/ts11/README.md
@@ -0,0 +1,8 @@
+# Wurkkos TS11
+
+Single LED 18350 / 18650 torch with FET+1 driver, RGB button, and RGB front
+aux. Includes a built-in charger too, separate from the main torch circuit.
+
+May ship with the `0715` (wurkkos-ts25) firmware installed, but the RGB button
+does not work fully until upgrading to this TS11 firmware.
+
diff --git a/hw/wurkkos/ts25/README.md b/hw/wurkkos/ts25/README.md
new file mode 100644
index 0000000..b3c33af
--- /dev/null
+++ b/hw/wurkkos/ts25/README.md
@@ -0,0 +1,20 @@
+# Wurkkos TS25
+
+A quad LED 21700 light with FET+1 driver and RGB front aux. A charge port is
+built in too, but operates independently and is not visible to the main
+firmware.
+
+Some other Wurkkos models may also ship with this firmware, and should be
+upgraded to use a more specific build:
+
+ - FC13: use wurkkos-fc13
+ - TS11: use wurkkos-ts11
+ - SP30S Pro: use wurkkos-fc13
+
+These builds have different aux LED behavior. The difference is whether the
+RGB aux is on the front, the side (button), or both.
+
+ - Front: TS25
+ - Side: FC13
+ - Both: TS11
+