aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSelene ToyKeeper2024-08-19 13:10:04 -0600
committerSelene ToyKeeper2024-08-19 13:10:04 -0600
commit4c2bbf92194ff5f41a87baa95c5388b325a27d2d (patch)
tree2d2b51fc3ef800e55009bc5f01ef7119d4c32d6e
parentts25-boost: reduced visible PWM, made party strobe less blurry (diff)
parentremoved "Off -> 3H" strobe/mood mode access from Extended Simple UI (diff)
downloadanduril-4c2bbf92194ff5f41a87baa95c5388b325a27d2d.tar.gz
anduril-4c2bbf92194ff5f41a87baa95c5388b325a27d2d.tar.bz2
anduril-4c2bbf92194ff5f41a87baa95c5388b325a27d2d.zip
Merge branch 'trunk' into wurkkos-ts25-boost
# By Selene ToyKeeper (20) and others * trunk: (25 commits) removed "Off -> 3H" strobe/mood mode access from Extended Simple UI include hardware-specific readme files in the release .zip added change log for 2024-04-20 release Forgot to update model count after the last couple additions use low aux for chan-aux level 0 MODELS: added emisar-d3aa make-release.sh should use version-string.sh instead of duplicating code d3aa: fixed voltage calculation to use 0.02V units instead of 0.025V d3aa weak battery test: blink 3x instead of 2x, and omit number readout use smooth steps in lockout mode, if enabled fixed Tactical Mode's strobes when Momentary Mode not enabled increased voltage precision from 0.025V to 0.02V (so 0 to 255 now goes from 0.00V to 5.10V) weak battery detection: use different thresholds for AA and Li-Ion (also, fixed bug where a totally empty li-ion didn't get limited) d3aa: got weak battery detection actually working, and not letting the magic smoke out of updi adapters any more (probably) dammit, got alkaline detection half working and then my flashing adapter died (saving progress here so I can work on a different branch) fixed inaccurate comment (thanks to xikteny for spotting it) d3aa fine-tuning: - new ramp - production style config defaults (simple mode, Hank config) - candle tuning - fixed way-too-fast thermal regulation (might still be a bit fast, but it's a lot better) d3aa: fixed voltage measurement Allow manually running GitHub actions workflows Bugfix: Prevent switching channel modes when in tactical mode ... # Conflicts: # arch/attiny1616.c (fixed)
-rw-r--r--.github/workflows/compile.yml2
-rw-r--r--ChangeLog.md33
-rw-r--r--MODELS4
-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/make-release.sh10
-rw-r--r--docs/anduril-manual.md12
-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/ramping.c5
-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.c202
-rw-r--r--hw/hank/emisar-d3aa/hwdef.h227
-rw-r--r--hw/hank/emisar-d3aa/model1
-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.h4
-rw-r--r--hw/thefreeman/boost-fwaa-mp3432-hdr-dac-rgb/hwdef.h4
-rw-r--r--ui/anduril/anduril.c24
-rw-r--r--ui/anduril/aux-leds.c39
-rw-r--r--ui/anduril/lockout-mode.c4
-rw-r--r--ui/anduril/off-mode.c26
-rw-r--r--ui/anduril/strobe-modes.c2
-rw-r--r--ui/anduril/tactical-mode.c5
36 files changed, 752 insertions, 130 deletions
diff --git a/.github/workflows/compile.yml b/.github/workflows/compile.yml
index 2ba771f..c77ac85 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" ]
diff --git a/ChangeLog.md b/ChangeLog.md
index a70a398..4a71e4e 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -14,6 +14,39 @@ formats:
# Next
+# 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..2a6e114 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
@@ -55,6 +56,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
diff --git a/arch/attiny1616.c b/arch/attiny1616.c
index 317552d..5d46111 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) << 4
- // 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));
const uint16_t adc_offset = (ADC_22 - (ADC_44 - ADC_22)) << 4;
uint8_t result = (measurement - adc_offset) / 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/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 68c577c..cdf0dd1 100644
--- a/docs/anduril-manual.md
+++ b/docs/anduril-manual.md
@@ -142,17 +142,13 @@ manufacturer's request. This typically includes:
- `Ramp -> 3C`: Toggle smooth or stepped ramp shape.
- `Ramp -> 5H`: Sunset timer.
- - `Off -> 3H`: Access the strobe/mood modes.
- `Off -> 7C/7H`: Change the aux LED pattern.
- `Lockout -> 7C/7H`: Change the aux LED pattern.
-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
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/ramping.c b/fsm/ramping.c
index adc8acb..743e619 100644
--- a/fsm/ramping.c
+++ b/fsm/ramping.c
@@ -57,6 +57,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..be673e1
--- /dev/null
+++ b/hw/hank/emisar-d3aa/hwdef.c
@@ -0,0 +1,202 @@
+// 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
+};
+
+
+void set_level_zero() {
+ DAC_LVL = 0; // DAC off
+ DAC_VREF = V10; // low Vref
+ HDR_ENABLE_PORT &= ~(1 << HDR_ENABLE_PIN); // HDR off
+
+ // prevent post-off flash
+ IN_NFET_ENABLE_PORT |= (1 << IN_NFET_ENABLE_PIN);
+ delay_4ms(IN_NFET_DELAY_TIME/4);
+ 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);
+ }
+
+ // BST on first, to give it a few extra microseconds to spin up
+ BST_ENABLE_PORT |= (1 << BST_ENABLE_PIN);
+
+ // 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 (noflash) {
+ // wait for flash prevention to finish
+ delay_4ms(IN_NFET_DELAY_TIME/4);
+ 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..87740ba
--- /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 12 // (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/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 38b508d..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
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/ui/anduril/anduril.c b/ui/anduril/anduril.c
index 1cdb8d0..a7e4199 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())
@@ -293,10 +301,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
diff --git a/ui/anduril/aux-leds.c b/ui/anduril/aux-leds.c
index fd184fc..7356666 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;
@@ -151,11 +151,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/lockout-mode.c b/ui/anduril/lockout-mode.c
index 6f85ca9..c3f82ea 100644
--- a/ui/anduril/lockout-mode.c
+++ b/ui/anduril/lockout-mode.c
@@ -26,11 +26,11 @@ uint8_t lockout_state(Event event, uint16_t arg) {
if (cfg.manual_memory) lvl = cfg.manual_memory;
#endif
}
- set_level(lvl);
+ off_state_set_level(lvl);
}
// button was released
else if ((event & (B_CLICK | B_PRESS)) == (B_CLICK)) {
- set_level(0);
+ 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();