aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSelene ToyKeeper2023-11-19 01:53:59 -0700
committerSelene ToyKeeper2023-11-19 01:53:59 -0700
commit486dd5543653b342fe64c0e5b1fcd342f0a02dbb (patch)
tree4c9638725f109135795b70934a23a35e6ec35a4c
parentthefreeman/boost*: define Vref values more explicitly without magic numbers (diff)
downloadanduril-486dd5543653b342fe64c0e5b1fcd342f0a02dbb.tar.gz
anduril-486dd5543653b342fe64c0e5b1fcd342f0a02dbb.tar.bz2
anduril-486dd5543653b342fe64c0e5b1fcd342f0a02dbb.zip
added *unfinished* support for avr32dd20, plus a build target for a dev kit
ADC doesn't work yet. No voltage, no temperature. I need to do a lot of refactoring on the ADC code. :(
Diffstat (limited to '')
-rw-r--r--arch/avr32dd20.c263
-rw-r--r--arch/avr32dd20.h93
-rwxr-xr-xbin/flash-avr32dd20.sh13
-rw-r--r--hw/thefreeman/avr32dd20-devkit/anduril.h138
-rw-r--r--hw/thefreeman/avr32dd20-devkit/arch1
-rw-r--r--hw/thefreeman/avr32dd20-devkit/hwdef.c105
-rw-r--r--hw/thefreeman/avr32dd20-devkit/hwdef.h208
-rw-r--r--hw/thefreeman/avr32dd20-devkit/model1
8 files changed, 822 insertions, 0 deletions
diff --git a/arch/avr32dd20.c b/arch/avr32dd20.c
new file mode 100644
index 0000000..45923a8
--- /dev/null
+++ b/arch/avr32dd20.c
@@ -0,0 +1,263 @@
+// arch/avr32dd20.h: avr32dd20 support header
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+////////// clock speed / delay stuff //////////
+
+inline void mcu_clock_speed() {
+ // TODO: allow hwdef to define a base clock speed
+ // run the internal clock at 12 MHz, not the default 4 MHz
+ _PROTECTED_WRITE( CLKCTRL.OSCHFCTRLA,
+ CLKCTRL_FRQSEL_12M_gc | CLKCTRL_AUTOTUNE_bm );
+ // (another option is to use 20 MHz / 2, like attiny1616 does)
+ // divide 20 MHz to run at 10 MHz to match attiny1616
+ //_PROTECTED_WRITE( CLKCTRL.MCLKCTRLB,
+ // CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm );
+}
+
+///// clock dividers
+// this should work, but needs further validation
+inline void clock_prescale_set(uint8_t n) {
+ cli();
+ _PROTECTED_WRITE(CLKCTRL.MCLKCTRLB, n); // Set the prescaler
+ while (CLKCTRL.MCLKSTATUS & CLKCTRL_SOSC_bm) {} // wait for clock change to finish
+ sei();
+}
+
+
+////////// ADC voltage / temperature //////////
+
+// ADC0.CTRLA bits:
+// RUNSTDBY, -, CONVMODE, LEFTADJ, RESSEL[1:0], FREERUN, ENABLE
+// CTRLB bits: -, -, -, -, -, SAMPNUM[2:0]
+// CTRLC bits: -, -, -, -, PRESC[3:0]
+// CTRLD bits: INITDLY[2:0], -, SAMPDLY[3:0]
+// CTRLE bits: -, -, -, -, -, WINCM[2:0]
+// SAMPCTRL: 8 bits
+// MUXPOS, MUXNEG: 7 bits each
+// COMMAND: -, -, -, -, -, -, SPCONV, STCONV
+
+inline void mcu_set_admux_therm() {
+ // ADC init: Datasheet section 33.3.2
+ // Temperature mode: Datasheet section 33.3.3.8
+ // measurements should be 12-bit right-adjusted single-ended conversion
+ // set Vref to 2.048V for temperature mode
+ VREF.ADC0REF = VREF_REFSEL_2V048_gc;
+ // set temp sensor as input
+ ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc;
+ // configure init delay to >= 25 us * Fclk_adc (no sample delay needed)
+ ADC0.CTRLD = ADC_INITDLY_DLY32_gc | ADC_SAMPDLY_DLY0_gc;
+ // configure ADC sample length to >= 28 us * Fclk_adc
+ ADC0.SAMPCTRL = 32;
+ // set single-ended or differential
+ // set resolution to 12 bits
+ // set left- or right-adjust (right)
+ // set free-running mode or not (yes)
+ ADC0.CTRLA = ADC_CONVMODE_SINGLEENDED_gc
+ | ADC_RESSEL_12BIT_gc
+ //| ADC_LEFTADJ_bm // not in temperature mode
+ | ADC_FREERUN_bm;
+ // set number of samples (requires adjustment in formula too)
+ ADC0.CTRLB = ADC_SAMPNUM_NONE_gc;
+ // TODO: accumulate more samples for more resolution
+ // (and probably set the prescale faster too)
+ //ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result
+ // set a clock prescaler
+ ADC0.CTRLC = ADC_PRESC_DIV64_gc;
+ // enable the ADC
+ ADC0.CTRLA |= ADC_ENABLE_bm;
+ // actually start measuring (happens in another function)
+ //ADC0.COMMAND |= ADC_STCONV_bm;
+ // for each measurement:
+ // process according to sigrow data + formula
+}
+
+inline void mcu_set_admux_voltage() {
+ // ADC init: Datasheet section 33.3.2
+ // set Vref
+ VREF.ADC0REF = VREF_REFSEL_1V024_gc;
+ // set single-ended or differential
+ // set resolution to 12 bits
+ // set left- or right-adjust (right)
+ // set free-running mode or not (yes)
+ ADC0.CTRLA = ADC_CONVMODE_SINGLEENDED_gc
+ | ADC_RESSEL_12BIT_gc
+ | ADC_LEFTADJ_bm // has no effect when 16+ samples taken
+ | ADC_FREERUN_bm
+ | ADC_RUNSTBY_bm // allow voltage sense in standby mode
+ ;
+ // set number of samples
+ ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result
+ // set a clock prescaler
+ ADC0.CTRLC = ADC_PRESC_DIV16_gc; // not too fast, not too slow
+ // select the positive ADC input with MUXPOS
+ #ifdef USE_VOLTAGE_DIVIDER // external voltage divider
+ // ADC input pin / Vref
+ ADC0.MUXPOS = ADMUX_VOLTAGE_DIVIDER; // external pin
+ #elif defined (USE_VOLTAGE_VDDIO2) // internal voltage divider
+ // (Vbat / 10) / Vref
+ ADC0.MUXPOS = ADC_MUXPOS_VDDIO2DIV10_gc;
+ #else // measure directly on VDD/VCC pin
+ // (Vbat / 10) / Vref
+ ADC0.MUXPOS = ADC_MUXPOS_VDDDIV10_gc;
+ #endif
+ // enable the ADC
+ ADC0.CTRLA |= ADC_ENABLE_bm;
+ // actually start measuring (happens in another function)
+ //ADC0.COMMAND |= ADC_STCONV_bm;
+}
+
+inline void mcu_adc_sleep_mode() {
+ set_sleep_mode(SLEEP_MODE_STANDBY);
+}
+
+inline void mcu_adc_start_measurement() {
+ // FIXME: enable this after getting ADC stuff fixed
+ #if 0
+ ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt
+ ADC0.COMMAND |= ADC_STCONV_bm; // actually start measuring
+ #endif
+}
+
+/*
+void mcu_adc_on() {
+ hwdef_set_admux_voltage();
+ mcu_adc_start_measurement();
+}
+*/
+
+inline void mcu_adc_off() {
+ ADC0.CTRLA &= ~(ADC_ENABLE_bm); // disable the ADC
+}
+
+inline void mcu_adc_vect_clear() {
+ ADC0.INTFLAGS = ADC_RESRDY_bm; // clear the interrupt
+}
+
+inline uint16_t mcu_adc_result_temp() {
+ // FIXME: better math, higher precision
+ // Use the factory calibrated values in SIGROW.TEMPSENSE0 and
+ // SIGROW.TEMPSENSE1 to calculate a temperature reading in Kelvin, then
+ // left-align it.
+ int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row
+ uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row
+ uint32_t temp = ADC0.RES - sigrow_offset;
+ temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit)
+ temp += 0x80; // Add 1/2 to get correct rounding on division below
+ //temp >>= 8; // Divide result to get Kelvin
+ //return temp << 6; // left align it
+ return temp >> 2; // left-aligned uint16_t
+}
+
+inline uint16_t mcu_adc_result_volts() {
+ // voltage is 12-bit right-aligned + 16x oversampling = 16 bits total
+ return ADC0.RES;
+}
+
+inline uint8_t mcu_adc_lsb() {
+ // temp is right-aligned, voltage is 16-bit, both have a useful LSB
+ return ADC0_RESL;
+}
+
+#ifdef USE_VOLTAGE_VDD
+uint8_t calc_voltage(uint16_t measurement) {
+ // calculate actual voltage: volts * 10
+ // FIXME
+ // ADC = 1.1 * 1024 / volts
+ // volts = 1.1 * 1024 / ADC
+ result = ((uint16_t)(2*1.1*1024*10)/(measurement>>6)
+ + VOLTAGE_FUDGE_FACTOR
+ #ifdef USE_VOLTAGE_CORRECTION
+ + VOLT_CORR - 7
+ #endif
+ ) >> 1;
+ return result;
+}
+#elif defined(USE_VOLTAGE_VDDIO2)
+#elif defined(USE_VOLTAGE_VDDIO2)
+#else
+// hwdef must supply its own function
+#endif
+
+////////// WDT //////////
+// this uses the RTC PIT interrupt instead of WDT,
+// as recommended on AVR 0/1-series and later:
+// https://github.com/SpenceKonde/DxCore/blob/master/megaavr/extras/PowerSave.md#using-the-real-time-counter-rtcpit
+// The PIT runs even in power-down mode, unlike RTC,
+// and its cycles are relative to the AVR's internal 32768 Hz ULP oscillator
+// AVR datasheet sections 26, 26.5, 26.9, 26.12, 26.13.12
+
+// PIT tick speeds:
+// 0 (none)
+// 1 8192 Hz (CYC4)
+// 2 4096 Hz
+// 3 2048 Hz
+// 4 1024 Hz (CYC32)
+// 5 512 Hz
+// 6 256 Hz
+// 7 128 Hz
+// 8 64 Hz (default) (CYC512)
+inline void mcu_wdt_active() {
+ RTC.PITINTCTRL = RTC_PI_bm; // enable the Periodic Interrupt
+ while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated
+ // Period = 16ms (64 Hz), enable the PI Timer
+ RTC.PITCTRLA = RTC_PERIOD_CYC512_gc | RTC_PITEN_bm;
+}
+
+// STANDBY_TICK_SPEED:
+// 0 64 Hz
+// 1 32 Hz
+// 2 16 Hz
+// 3 8 Hz (default)
+// 4 4 Hz
+// 5 2 Hz
+// 6 1 Hz
+inline void mcu_wdt_standby() {
+ RTC.PITINTCTRL = RTC_PI_bm; // enable the Periodic Interrupt
+ while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated
+ // Set period (64 Hz / STANDBY_TICK_SPEED = 8 Hz), enable the PI Timer
+ RTC.PITCTRLA = ((8+STANDBY_TICK_SPEED)<<3) | RTC_PITEN_bm;
+}
+
+inline void mcu_wdt_stop() {
+ while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated
+ RTC.PITCTRLA = 0; // Disable the PI Timer
+}
+
+inline void mcu_wdt_vect_clear() {
+ RTC.PITINTFLAGS = RTC_PI_bm; // clear the PIT interrupt flag
+}
+
+
+////////// PCINT - pin change interrupt (e-switch) //////////
+
+inline void mcu_switch_vect_clear() {
+ // Write a '1' to clear the interrupt flag
+ SWITCH_INTFLG |= (1 << SWITCH_PIN);
+}
+
+inline void mcu_pcint_on() {
+ SWITCH_ISC_REG |= PORT_ISC_BOTHEDGES_gc;
+}
+
+inline void mcu_pcint_off() {
+ SWITCH_ISC_REG &= ~(PORT_ISC_gm);
+}
+
+
+////////// misc //////////
+
+void reboot() {
+ // request a reboot (software reset)
+ _PROTECTED_WRITE(RSTCTRL.SWRR, RSTCTRL_SWRST_bm);
+}
+
+inline void prevent_reboot_loop() {
+ // if previous reset was a crash (WDT time-out),
+ // prevent it from happening again immediately
+ //RSTCTRL.RSTFR &= ~(RSTCTRL_WDRF_bm); // reset wdt flag only
+ RSTCTRL.RSTFR = 0; // reset all reset status flags (maybe unneeded?)
+ wdt_disable(); // from avr/wdt.h
+}
+
diff --git a/arch/avr32dd20.h b/arch/avr32dd20.h
new file mode 100644
index 0000000..7a6b8f1
--- /dev/null
+++ b/arch/avr32dd20.h
@@ -0,0 +1,93 @@
+// arch/avr32dd20.h: avr32dd20 support header
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+////////// clock speed / delay stuff //////////
+
+#define F_CPU 12000000UL
+#define BOGOMIPS (F_CPU/3800)
+#define DELAY_ZERO_TIME 1020
+
+inline void mcu_clock_speed();
+
+///// clock dividers
+// this should work, but needs further validation
+inline void clock_prescale_set(uint8_t n);
+
+// TODO: allow hwdef to define a base clock speed,
+// and adjust these values accordingly
+typedef enum
+{
+ // Actual clock is 12 MHz
+ clock_div_1 = (0), // 12 MHz
+ clock_div_2 = (CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm), // 6 MHz
+ clock_div_4 = (CLKCTRL_PDIV_4X_gc | CLKCTRL_PEN_bm), // 3 MHz
+ clock_div_8 = (CLKCTRL_PDIV_8X_gc | CLKCTRL_PEN_bm), // 1.5 MHz
+ clock_div_16 = (CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm), // 0.75 MHz
+ clock_div_32 = (CLKCTRL_PDIV_32X_gc | CLKCTRL_PEN_bm), // 375 kHz
+ clock_div_64 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm), // 187.5 kHz
+} clock_div_t;
+
+
+////////// ADC voltage / temperature //////////
+
+#define hwdef_set_admux_therm mcu_set_admux_therm
+inline void mcu_set_admux_therm();
+
+#define hwdef_set_admux_voltage mcu_set_admux_voltage
+inline void mcu_set_admux_voltage();
+
+inline void mcu_adc_sleep_mode();
+
+inline void mcu_adc_start_measurement();
+
+//#define mcu_adc_on hwdef_set_admux_voltage
+//void mcu_adc_on();
+
+inline void mcu_adc_off();
+
+#define ADC_vect ADC0_RESRDY_vect
+inline void mcu_adc_vect_clear();
+
+// read ADC differently for temperature and voltage
+#define MCU_ADC_RESULT_PER_TYPE
+
+inline uint16_t mcu_adc_result_temp();
+
+inline uint16_t mcu_adc_result_volts();
+
+inline uint8_t mcu_adc_lsb();
+
+
+////////// WDT //////////
+
+inline void mcu_wdt_active();
+
+inline void mcu_wdt_standby();
+
+inline void mcu_wdt_stop();
+
+#define WDT_vect RTC_PIT_vect
+inline void mcu_wdt_vect_clear();
+
+
+////////// PCINT - pin change interrupt (e-switch) //////////
+
+// set these in hwdef
+//#define SWITCH_PORT VPORTD.IN
+//#define SWITCH_VECT PORTD_PORT_vect
+
+inline void mcu_switch_vect_clear();
+
+inline void mcu_pcint_on();
+
+inline void mcu_pcint_off();
+
+
+////////// misc //////////
+
+void reboot();
+
+inline void prevent_reboot_loop();
+
diff --git a/bin/flash-avr32dd20.sh b/bin/flash-avr32dd20.sh
new file mode 100755
index 0000000..ef8b0f3
--- /dev/null
+++ b/bin/flash-avr32dd20.sh
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+
+set -e
+
+[[ -z "$1" ]] && echo "No .hex file specified." && exit 1
+HEX="$1"
+
+#pymcuprog ping -t uart -u /dev/ttyUSB2 -d avr32dd20
+#pymcuprog erase -t uart -u /dev/ttyUSB2 -d avr32dd20
+#pymcuprog write verify -t uart -u /dev/ttyUSB2 -d avr32dd20 -f "$HEX"
+#pymcuprog verify -t uart -u /dev/ttyUSB2 -d avr32dd20 -f "$HEX"
+
+pymcuprog write --erase --verify --timing -t uart -u /dev/ttyUSB2 -d avr32dd20 -f "$HEX"
diff --git a/hw/thefreeman/avr32dd20-devkit/anduril.h b/hw/thefreeman/avr32dd20-devkit/anduril.h
new file mode 100644
index 0000000..97e9e10
--- /dev/null
+++ b/hw/thefreeman/avr32dd20-devkit/anduril.h
@@ -0,0 +1,138 @@
+// thefreeman's BST21 BST20-FWxA (no button LED)
+// Copyright (C) 2023 TBD (thefreeman), Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#define HWDEF_H thefreeman/avr32dd20-devkit/hwdef.h
+
+// HPRsense : 1.7+0.3+5 = 7mR (DMN22M5UFG+trace resistance+5mR)
+// Vsense=42.46mV, R1= 191k
+// LPRsense : 1R
+// transition DAC level 8, ramp level 45
+// fifth power ramp 0.1mA to 6066mA
+
+#define RAMP_SIZE 150
+
+// 4 ramp segments:
+// - low 1.024V
+// - low 2.5 V
+// - high 1.024V
+// - high 2.5 V
+// HDR ratio: 160
+// PWM1: DAC Data
+#if 1
+// level_calc.py 4.3287 1 150 7135 5 0.01 1400 --pwm 400000
+// top level for each "gear": 30 40 120 150
+#define PWM1_LEVELS \
+ 6, 7, 9, 11, 14, 18, 23, 30, 37, 46, 56, 69, 83, 100, 120, 142, 168, 196, 229, 266, 307, 353, 403, 460, 522, 591, 667, 750, 840, 939, \
+ 428, 476, 528, 584, 645, 710, 780, 856, 937,1023, \
+ 17, 18, 20, 21, 23, 25, 27, 29, 32, 34, 37, 40, 42, 46, 49, 52, 56, 60, 64, 68, 72, 77, 81, 86, 92, 97, 103, 109, 115, 122, 128, 136, 143, 151, 159, 167, 176, 185, 194, 204, 214, 224, 235, 246, 258, 270, 283, 295, 309, 323, 337, 352, 367, 383, 399, 416, 434, 452, 470, 489, 509, 529, 550, 572, 594, 617, 640, 664, 689, 715, 741, 768, 796, 824, 854, 884, 915, 947, 979,1013, \
+ 429, 443, 458, 473, 489, 505, 521, 538, 555, 572, 590, 609, 628, 647, 667, 687, 708, 729, 751, 773, 796, 819, 843, 867, 891, 917, 943, 969, 996,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, 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
+#define MAX_1x7135 40
+#define HDR_ENABLE_LEVEL_MIN 41
+#define DEFAULT_LEVEL 50
+#else
+// level_calc.py 9.21 1 150 7135 5 0.2 1400 --pwm 400000
+// (plus dac-scale.py post-processing to get values for HDR+Vref ranges)
+// top level for each "gear": 35 48 127 150
+#define PWM1_LEVELS \
+ 5, 11, 18, 25, 33, 41, 50, 60, 71, 83, 96, 110, 125, 141, 158, 177, 198, 220, 244, 269, 297, 326, 358, 392, 429, 469, 511, 556, 605, 657, 713, 772, 836, 904, 976, \
+ 431, 465, 501, 539, 580, 624, 670, 720, 772, 828, 887, 950,1017, \
+ 16, 17, 18, 20, 21, 23, 24, 26, 27, 29, 31, 33, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 64, 68, 72, 76, 80, 85, 90, 95, 100, 106, 112, 118, 124, 131, 138, 145, 153, 161, 170, 179, 188, 198, 208, 219, 230, 242, 254, 266, 280, 294, 308, 323, 339, 355, 373, 391, 409, 429, 449, 470, 492, 515, 539, 564, 589, 616, 644, 673, 704, 735, 768, 802, 837, 874, 912, 952, 993, \
+ 424, 442, 461, 480, 501, 522, 544, 566, 590, 614, 640, 666, 693, 721, 750, 780, 811, 844, 877, 912, 948, 985,1023
+// Vref selector (V10, V20, V25, V40 = 1.024V, 2.048V, 2.5V, 4.096V)
+#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, V10, V10, V10, V10, V10, \
+ V25, V25, 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, 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
+
+#define MAX_1x7135 48
+#define DEFAULT_LEVEL 48
+#define HDR_ENABLE_LEVEL_MIN 49 // when HDR FET turns ON
+#endif
+
+// 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
+
+#define RAMP_SMOOTH_FLOOR 1
+#define RAMP_SMOOTH_CEIL 130 // 50% / 3A / 1000 lm
+// 1 22 [44] 65 87 108 130
+#define RAMP_DISCRETE_FLOOR 1
+#define RAMP_DISCRETE_CEIL 130
+#define RAMP_DISCRETE_STEPS 7
+
+// 20 [45] 70 95 120
+#define SIMPLE_UI_FLOOR 20
+#define SIMPLE_UI_CEIL 120 // ~2.25A / ~750 lm
+#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%/3A
+#define MIN_THERM_STEPDOWN MAX_1x7135
+
+
+// UI
+
+//#define SIMPLE_UI_ACTIVE 0 // advanced UI by default
+
+// allow Aux Config and Strobe Modes in Simple UI
+//#define USE_EXTENDED_SIMPLE_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
+#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 40
+
+// enable 13H factory reset so it can be used on tail e-switch lights
+#define USE_SOFT_FACTORY_RESET
+
+// TODO: disable lowpass while asleep; the MCU oversamples
+
diff --git a/hw/thefreeman/avr32dd20-devkit/arch b/hw/thefreeman/avr32dd20-devkit/arch
new file mode 100644
index 0000000..bcf4552
--- /dev/null
+++ b/hw/thefreeman/avr32dd20-devkit/arch
@@ -0,0 +1 @@
+avr32dd20
diff --git a/hw/thefreeman/avr32dd20-devkit/hwdef.c b/hw/thefreeman/avr32dd20-devkit/hwdef.c
new file mode 100644
index 0000000..e5b347f
--- /dev/null
+++ b/hw/thefreeman/avr32dd20-devkit/hwdef.c
@@ -0,0 +1,105 @@
+// thefreeman boost driver 2.1 output helper functions
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include "fsm/chan-rgbaux.c"
+
+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);
+
+ // adjust multiple times based on how far until the next level
+ // (so it adjusts faster/coarser for big steps)
+
+ int16_t diff = (dac_next - dac_now);
+ if (diff < 0) diff = -diff;
+
+ // ~70 max DAC levels per ramp step, 1 + (70 >> 3) = max 10
+ uint8_t steps;
+ steps = 1 + (diff >> 3);
+ for (uint8_t i=0; i<=steps; i++)
+ 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
+}
+
diff --git a/hw/thefreeman/avr32dd20-devkit/hwdef.h b/hw/thefreeman/avr32dd20-devkit/hwdef.h
new file mode 100644
index 0000000..7e093f9
--- /dev/null
+++ b/hw/thefreeman/avr32dd20-devkit/hwdef.h
@@ -0,0 +1,208 @@
+// hwdef for thefreeman's avr32dd20 dev kit
+// Copyright (C) 2023 thefreeman, Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+/* thefreeman’s avr32dd20 dev kit
+ * Boost driver based on MP3431(?)
+ * with high dynamic range and DAC control + aux RGB
+ *
+ * Pin Name Function
+ * 1 PA4 -
+ * 2 PA5 LVB (unused)
+ * 3 PA6 BATT LVL (voltage divider)
+ * 4 PA7 HDR: high/low Rsense range
+ * 5 PC1 -
+ * 6 PC2 -
+ * 7 PC3 -
+ * 8 VDDIO2 VCC2 (unused)
+ * 9 PD4 e-switch
+ * 10 PD5 EN: boost enable
+ * 11 PD6 DAC: control voltage out
+ * 12 PD7 IN- NFET: absorb startup flash
+ * 13 VDD VCC
+ * 14 GND GND
+ * 15 PF6 RESET
+ * 16 PF7 UPDI
+ * 17 PA0 R: aux red
+ * 18 PA1 G: aux green
+ * 19 PA2 B: aux blue
+ * 20 PA3 CH: detect charging
+ *
+ * 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
+ * CH senses the status of the onboard charger
+ * LVL : ??? (unused?)
+ * LVB is for OTSM firmware, not used here
+ */
+
+#define HWDEF_C thefreeman/avr32dd20-devkit/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
+
+
+#define PWM_CHANNELS 1 // old, remove this
+
+#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
+#define DAC_LVL DAC0_DATA // 0 to 255, for 0V to Vref
+#define DAC_VREF VREF_DAC0REF // 1.024V, 2.048V, 4.096V, or 2.5V
+//#define DAC_VREF VREF.ADC0REF // 1.024V, 2.048V, 4.096V, or 2.5V
+#define PWM_TOP_INIT 255 // highest value used in top half of ramp (unused?)
+// Vref values
+#define V10 VREF_REFSEL_1V024_gc
+#define V20 VREF_REFSEL_2V048_gc
+#define V25 VREF_REFSEL_2V500_gc
+#define V40 VREF_REFSEL_4V096_gc
+
+// BST enable
+#define BST_ENABLE_PIN PIN5_bp
+#define BST_ENABLE_PORT PORTD_OUT
+
+// HDR
+// turns on HDR FET for the high current range
+#define HDR_ENABLE_PIN PIN7_bp
+#define HDR_ENABLE_PORT PORTA_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 PIN7_bp
+#define IN_NFET_ENABLE_PORT PORTD_OUT
+
+// e-switch
+#ifndef SWITCH_PIN
+#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
+
+// TODO: define stuff for the voltage divider
+// 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
+
+// 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 PIN0_bp
+#define AUXLED_G_PIN PIN1_bp
+#define AUXLED_B_PIN PIN2_bp
+#define AUXLED_RGB_PORT PORTA
+
+// this light has three aux LED channels: R, G, B
+#define USE_AUX_RGB_LEDS
+
+
+inline void hwdef_setup() {
+
+ // TODO? for this DAC controlled-light, try to decrease the clock speed
+ mcu_clock_speed();
+
+ VPORTA.DIR = PIN0_bm // R
+ | PIN1_bm // G
+ | PIN2_bm // B
+ //| PIN3_bm // CH
+ | PIN7_bm; // HDR
+ VPORTD.DIR = PIN5_bm // EN
+ | PIN6_bm // DAC
+ | PIN7_bm; // IN- NFET
+
+ // enable pullups on the unused and input pins to reduce power
+ //PORTA.PIN0CTRL = PORT_PULLUPEN_bm; // R
+ //PORTA.PIN1CTRL = PORT_PULLUPEN_bm; // G
+ //PORTA.PIN2CTRL = PORT_PULLUPEN_bm; // B
+ //PORTA.PIN3CTRL = PORT_PULLUPEN_bm; // CH
+ PORTA.PIN4CTRL = PORT_PULLUPEN_bm;
+ PORTA.PIN5CTRL = PORT_PULLUPEN_bm;
+ PORTA.PIN6CTRL = PORT_PULLUPEN_bm;
+ //PORTA.PIN7CTRL = PORT_PULLUPEN_bm; // HDR
+
+ //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
+ | PORT_ISC_BOTHEDGES_gc; // e-switch
+ //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; // IN- NFET
+
+ // 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; // set the output voltage (off 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/thefreeman/avr32dd20-devkit/model b/hw/thefreeman/avr32dd20-devkit/model
new file mode 100644
index 0000000..216403c
--- /dev/null
+++ b/hw/thefreeman/avr32dd20-devkit/model
@@ -0,0 +1 @@
+1632==20