aboutsummaryrefslogtreecommitdiff
path: root/arch
diff options
context:
space:
mode:
Diffstat (limited to 'arch')
-rw-r--r--arch/attiny1616.c225
-rw-r--r--arch/attiny1616.h134
-rw-r--r--arch/attiny1634.c214
-rw-r--r--arch/attiny1634.h112
-rw-r--r--arch/attiny85.c225
-rw-r--r--arch/attiny85.h98
-rw-r--r--arch/avr32dd20.c264
-rw-r--r--arch/avr32dd20.h115
-rw-r--r--arch/mcu.c10
-rw-r--r--arch/mcu.h147
10 files changed, 1408 insertions, 136 deletions
diff --git a/arch/attiny1616.c b/arch/attiny1616.c
new file mode 100644
index 0000000..c5499dd
--- /dev/null
+++ b/arch/attiny1616.c
@@ -0,0 +1,225 @@
+// arch/attiny1616.c: attiny1616 support functions
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include "arch/attiny1616.h"
+
+////////// clock speed / delay stuff //////////
+
+inline void mcu_clock_speed() {
+ // TODO: allow hwdef to define a base clock speed
+ // set up the system clock to run at 10 MHz instead of the default 3.33 MHz
+ _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 //////////
+
+inline void mcu_set_admux_therm() {
+ // put the ADC in temperature mode
+ // attiny1616 datasheet section 30.3.2.6
+ mcu_set_adc0_vref(VREF_ADC0REFSEL_1V1_gc); // Set Vbg ref to 1.1V
+ ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc; // read temperature
+ ADC0.CTRLB = ADC_SAMPNUM_ACC4_gc; // 10-bit result + 4x oversampling
+ ADC0.CTRLC = ADC_SAMPCAP_bm
+ | ADC_PRESC_DIV16_gc
+ | ADC_REFSEL_INTREF_gc; // Internal ADC reference
+}
+
+inline void mcu_set_admux_voltage() {
+ // Enabled, free-running (aka, auto-retrigger), run in standby
+ ADC0.CTRLA = ADC_ENABLE_bm | ADC_FREERUN_bm | ADC_RUNSTBY_bm;
+ // set a INITDLY value because the AVR manual says so (section 30.3.5)
+ // (delay 1st reading until Vref is stable)
+ ADC0.CTRLD |= ADC_INITDLY_DLY16_gc;
+ #ifdef USE_VOLTAGE_DIVIDER // measure an arbitrary pin
+ // result = resolution * Vdiv / 1.1V
+ mcu_set_adc0_vref(VREF_ADC0REFSEL_1V1_gc); // Set Vbg ref to 1.1V
+ ADC0.MUXPOS = ADMUX_VOLTAGE_DIVIDER; // read the requested ADC pin
+ ADC0.CTRLB = ADC_SAMPNUM_ACC4_gc; // 12-bit result, 4x oversampling
+ ADC0.CTRLC = ADC_SAMPCAP_bm
+ | ADC_PRESC_DIV16_gc
+ | ADC_REFSEL_INTREF_gc; // Use internal ADC reference
+ #else // measure VDD pin
+ // result = resolution * 1.5V / Vbat
+ mcu_set_adc0_vref(VREF_ADC0REFSEL_1V5_gc); // Set Vbg ref to 1.5V
+ ADC0.MUXPOS = ADC_MUXPOS_INTREF_gc; // read internal reference
+ ADC0.CTRLB = ADC_SAMPNUM_ACC4_gc; // 12-bit result, 4x oversampling
+ ADC0.CTRLC = ADC_SAMPCAP_bm
+ | ADC_PRESC_DIV16_gc
+ | ADC_REFSEL_VDDREF_gc; // Vdd (Vcc) be ADC reference
+ #endif
+}
+
+inline void mcu_adc_sleep_mode() {
+ set_sleep_mode(SLEEP_MODE_STANDBY);
+}
+
+inline void mcu_adc_start_measurement() {
+ ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt
+ ADC0.COMMAND |= ADC_STCONV_bm; // actually start measuring
+}
+
+/*
+inline void mcu_adc_on() {
+ VREF.CTRLA |= VREF_ADC0REFSEL_1V1_gc; // Set Vbg ref to 1.1V
+ // Enabled, free-running (aka, auto-retrigger), run in standby
+ ADC0.CTRLA = ADC_ENABLE_bm | ADC_FREERUN_bm | ADC_RUNSTBY_bm;
+ // set a INITDLY value because the AVR manual says so (section 30.3.5)
+ // (delay 1st reading until Vref is stable)
+ ADC0.CTRLD |= ADC_INITDLY_DLY16_gc;
+ hwdef_set_admux_voltage();
+}
+*/
+
+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() {
+ // just return left-aligned ADC result, don't convert to calibrated units
+ //return ADC0.RES << 6;
+ return ADC0.RES << 4;
+}
+
+inline uint16_t mcu_adc_result_volts() {
+ // ADC has no left-aligned mode, so left-align it manually
+ return ADC0.RES << 4;
+}
+
+inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
+ // In : 65535 * 1.5 / Vbat
+ // Out: uint8_t: Vbat * 40
+ // 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);
+ #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);
+ #endif
+ return vbat40;
+}
+
+#if 0 // fine voltage, 0 to 10.24V in 1/6400th V steps
+inline uint16_t mcu_vdd_raw2fine(uint16_t measurement) {
+ // In : 65535 * 1.5 / Vbat
+ // Out: 65535 * (Vbat / 10) / 1.024V
+ uint16_t voltage = ((uint32_t)(1.5 * 4096 * 100 * 64 * 16) / measurement;
+ return voltage;
+}
+#endif
+
+#ifdef USE_VOLTAGE_DIVIDER
+inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement) {
+ // In : 4095 * Vdiv / 1.1V
+ // Out: uint8_t: Vbat * 40
+ // 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));
+ uint8_t result = measurement / adc_per_volt;
+ return result;
+}
+#endif
+
+inline uint16_t mcu_temp_raw2cooked(uint16_t measurement) {
+ // convert raw ADC values to calibrated temperature
+ // In: ADC raw temperature (16-bit, or 12-bit left-aligned)
+ // Out: Kelvin << 6
+ // Precision: 1/64th Kelvin (but noisy)
+ // attiny1616 datasheet section 30.3.2.6
+ uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // factory calibration data
+ int8_t sigrow_offset = SIGROW.TEMPSENSE1;
+ const uint32_t scaling_factor = 65536; // use all 16 bits of ADC data
+ uint32_t temp = measurement - (sigrow_offset << 6);
+ temp *= sigrow_gain; // 24-bit result
+ temp += scaling_factor / 8; // Add 1/8th K to get correct rounding on later divisions
+ temp = temp >> 8; // change (K << 14) to (K << 6)
+ return temp; // left-aligned uint16_t, 0 to 1023.98 Kelvin
+}
+
+inline uint8_t mcu_adc_lsb() {
+ //return (ADCL >> 6) + (ADCH << 2);
+ return ADC0.RESL; // right aligned, not left... so should be equivalent?
+}
+
+
+////////// WDT //////////
+
+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;
+}
+
+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 = (1<<6) | (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() {
+ // put the WDT in hard reset mode, then trigger it
+ cli();
+ // Enable, timeout 8ms
+ _PROTECTED_WRITE(WDT.CTRLA, WDT_PERIOD_8CLK_gc);
+ sei();
+ wdt_reset();
+ while (1) {}
+}
+
+inline void prevent_reboot_loop() {
+ // prevent WDT from rebooting MCU again
+ RSTCTRL.RSTFR &= ~(RSTCTRL_WDRF_bm); // reset status flag
+ wdt_disable(); // from avr/wdt.h
+}
+
diff --git a/arch/attiny1616.h b/arch/attiny1616.h
new file mode 100644
index 0000000..940973e
--- /dev/null
+++ b/arch/attiny1616.h
@@ -0,0 +1,134 @@
+// arch/attiny1616.h: attiny1616 support header
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+// FIXME: remove this
+#define AVRXMEGA3
+
+////////// clock speed / delay stuff //////////
+
+#define F_CPU 10000000UL
+#define BOGOMIPS (F_CPU/4350)
+#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 20 MHz, but assume that 10 MHz is the top speed and work from there
+ // TODO: measure PWM speed and power use at 1.25/2.5/5/10 MHz, to determine which speeds are optimal
+ clock_div_1 = (CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm), // 10 MHz
+ clock_div_2 = (CLKCTRL_PDIV_4X_gc | CLKCTRL_PEN_bm), // 5 MHz
+ clock_div_4 = (CLKCTRL_PDIV_8X_gc | CLKCTRL_PEN_bm), // 2.5 MHz
+ clock_div_8 = (CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm), // 1.25 MHz
+ clock_div_16 = (CLKCTRL_PDIV_32X_gc | CLKCTRL_PEN_bm), // 625 kHz
+ clock_div_32 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm), // 312 kHz, max without changing to the 32 kHz ULP
+ clock_div_64 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm), // 312 kHz
+ clock_div_128 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm), // 312 kHz
+ clock_div_256 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm) // 312 kHz
+} clock_div_t;
+
+
+////////// DAC controls //////////
+
+#define DAC_LVL DAC0.DATA // 0 to 255, for 0V to Vref
+#define DAC_VREF VREF.CTRLA // 0.55V, 1.1V, 1.5V, 2.5V, or 4.3V
+
+// set only the relevant bits of the Vref register
+#define mcu_set_dac_vref(x) \
+ VREF.CTRLA = x | (VREF.CTRLA & (~VREF_DAC0REFSEL_gm));
+
+// Vref values
+// (for the DAC bits, not the ADC bits)
+#define V05 V055
+#define V055 VREF_DAC0REFSEL_0V55_gc
+#define V11 VREF_DAC0REFSEL_1V1_gc
+#define V25 VREF_DAC0REFSEL_2V5_gc
+#define V43 VREF_DAC0REFSEL_4V34_gc
+#define V15 VREF_DAC0REFSEL_1V5_gc
+
+////////// ADC voltage / temperature //////////
+
+// set only the relevant bits of the Vref register
+#define mcu_set_adc0_vref(x) \
+ VREF.CTRLA = x | (VREF.CTRLA & (~VREF_ADC0REFSEL_gm));
+
+#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();
+
+//inline void mcu_adc_on();
+
+inline void mcu_adc_off();
+
+#define ADC_vect ADC0_RESRDY_vect
+inline void mcu_adc_vect_clear();
+
+//// both readings are left-aligned
+//inline uint16_t mcu_adc_result();
+
+// 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();
+
+// return Volts * 40, range 0 to 6.375V
+#define voltage_raw2cooked mcu_vdd_raw2cooked
+inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement);
+inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement);
+
+// return (temp in Kelvin << 6)
+#define temp_raw2cooked mcu_temp_raw2cooked
+inline uint16_t mcu_temp_raw2cooked(uint16_t measurement);
+
+inline uint8_t mcu_adc_lsb();
+
+
+////////// WDT //////////
+
+inline void mcu_wdt_active();
+
+inline void mcu_wdt_standby();
+
+inline void mcu_wdt_stop();
+
+// *** Note for the AVRXMEGA3 (1-Series, eg 816 and 817), the WDT
+// is not used for time-based interrupts. A new peripheral, the
+// Periodic Interrupt Timer ("PIT") is used for this purpose.
+
+#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 PINA
+//#define SWITCH_VECT PCINT0_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/arch/attiny1634.c b/arch/attiny1634.c
new file mode 100644
index 0000000..e29d1c3
--- /dev/null
+++ b/arch/attiny1634.c
@@ -0,0 +1,214 @@
+// arch/attiny1634.c: attiny85 support functions
+// Copyright (C) 2014-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include "arch/attiny1634.h"
+
+////////// clock speed / delay stuff //////////
+
+inline void mcu_clock_speed() {
+ // TODO?
+ // (or not; clock speed is set by the fuses)
+ // attiny1634 datasheet 6.5
+ // CLKSR = [OSCRDY, CSTR, CKOUT_IO, SUT, CKSEL3/2/1/0]
+ // default 8MHz calibrated internal clock: SUT=0, CKSEL=0b0010
+ #if 0
+ cli();
+ CCP = 0xD8;
+ CLKSR = 0b01000010;
+ CCP = 0xD8;
+ CLKPR = 0; // CLK / 1 = full speed, 8 MHz
+ sei();
+ #endif
+}
+
+///// clock dividers
+inline void clock_prescale_set(uint8_t n) {
+ cli();
+ // _PROTECTED_WRITE(CLKPR, n);
+ CCP = 0xD8;
+ CLKPR = n;
+ sei();
+}
+
+////////// default hw_setup() //////////
+
+
+////////// ADC voltage / temperature //////////
+
+inline void mcu_set_admux_therm() {
+ // put the ADC in temperature mode
+ // DS table 19-3, 19-4, internal sensor / 1.1V ref
+ // [refs1, refs0, refen, adc0en, mux3, mux2, mux1, mux0]
+ // refs=0b10 : internal 1.1V ref
+ // mux=0b1110 : internal temperature sensor
+ //#define ADMUX_THERM 0b10001110
+ ADMUX = ADMUX_THERM;
+ // other stuff is already set, so no need to re-set it
+}
+
+inline void mcu_set_admux_voltage() {
+ // put the ADC in battery voltage measurement mode
+ // TODO: avr datasheet references
+ #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7
+ ADMUX = ADMUX_VOLTAGE_DIVIDER;
+ // disable digital input on divider pin to reduce power consumption
+ // TODO: this should be in hwdef init, not here
+ VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC);
+ #else // VCC / 1.1V reference
+ ADMUX = ADMUX_VCC;
+ // disable digital input on VCC pin to reduce power consumption
+ //VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC); // FIXME: unsure how to handle for VCC pin
+ #endif
+ //ACSRA |= (1 << ACD); // turn off analog comparator to save power
+ // ADCSRB: [VDEN, VDPD, -, -, ADLAR, ADTS2, ADTS1, ADTS0]
+ ADCSRB = (1 << ADLAR); // left-adjust, free-running
+ //ADCSRB = 0; // right-adjust, free-running
+}
+
+inline void mcu_adc_sleep_mode() {
+ set_sleep_mode(SLEEP_MODE_ADC);
+}
+
+inline void mcu_adc_start_measurement() {
+ // [ADEN, ADSC, ADATE, adif, ADIE, ADPS2, ADPS1, ADPS0]
+ ADCSRA = (1 << ADEN) // enable
+ | (1 << ADSC) // start
+ | (1 << ADATE) // auto-retrigger
+ | (1 << ADIE) // interrupt enable
+ | ADC_PRSCL; // prescale
+}
+
+inline void mcu_adc_off() {
+ ADCSRA &= ~(1<<ADEN); //ADC off
+}
+
+// left-adjusted mode:
+inline uint16_t mcu_adc_result() { return ADC; }
+//inline uint16_t mcu_adc_result() { return (uint16_t)(ADCL | (ADCH << 8)); }
+// right-adjusted mode:
+/*
+inline uint16_t mcu_adc_result() {
+ uint16_t result = (ADCL | (ADCH << 8)) << 6;
+ return result;
+}
+*/
+
+inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
+ // In : 65535 * 1.1 / Vbat
+ // Out: uint8_t: Vbat * 40
+ // 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);
+ #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);
+ #endif
+ return vbat40;
+}
+
+
+#ifdef USE_VOLTAGE_DIVIDER
+inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement) {
+ // In : 4095 * Vdiv / 1.1V
+ // Out: uint8_t: Vbat * 40
+ // 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));
+ uint8_t result = measurement / adc_per_volt;
+ return result;
+}
+#endif
+
+inline uint16_t mcu_temp_raw2cooked(uint16_t measurement) {
+ // convert raw ADC values to calibrated temperature
+ // In: ADC raw temperature (16-bit, or left-aligned)
+ // Out: Kelvin << 6
+ // Precision: 1/64th Kelvin (but noisy)
+ // attiny1634 datasheet section 19.12
+ // nothing to do; input value is already "cooked"
+ return measurement;
+}
+
+inline uint8_t mcu_adc_lsb() {
+ // left-adjusted mode:
+ return (ADCL >> 6) + (ADCH << 2);
+ // right-adjusted mode:
+ // datasheet 19.13.3.2:
+ // "When ADCL is read, the ADC Data Register is not updated
+ // until ADCH is read. ... ADCL must be read first, then ADCH."
+ // So... gotta read it even if not needed?
+ // (the value doesn't matter here, the lower bits are only used
+ // for generating some random seed data)
+ //return ADCL + ADCH;
+}
+
+
+////////// WDT //////////
+
+inline void mcu_wdt_active() {
+ wdt_reset(); // Reset the WDT
+ WDTCSR = (1<<WDIE); // Enable interrupt every 16ms
+}
+
+inline void mcu_wdt_standby() {
+ wdt_reset(); // Reset the WDT
+ WDTCSR = (1<<WDIE) | STANDBY_TICK_SPEED;
+}
+
+inline void mcu_wdt_stop() {
+ cli(); // needed because CCP, below
+ wdt_reset(); // Reset the WDT
+ MCUSR &= ~(1<<WDRF); // clear watchdog reset flag
+ // _PROTECTED_WRITE(WDTCSR, 0);
+ CCP = 0xD8; // enable config changes
+ WDTCSR = 0; // disable and clear all WDT settings
+ sei();
+}
+
+
+////////// PCINT - pin change interrupt (e-switch) //////////
+
+inline void mcu_pcint_on() {
+ // enable pin change interrupt
+ #ifdef SWITCH2_PCIE
+ GIMSK |= ((1 << SWITCH_PCIE) | (1 << SWITCH2_PCIE));
+ #else
+ GIMSK |= (1 << SWITCH_PCIE);
+ #endif
+}
+
+inline void mcu_pcint_off() {
+ // disable all pin-change interrupts
+ GIMSK &= ~(1 << SWITCH_PCIE);
+}
+
+
+////////// misc //////////
+
+void reboot() {
+ // put the WDT in hard reset mode, then trigger it
+ cli();
+ // _PROTECTED_WRITE(WDTCSR, 0b10001000);
+ // allow protected configuration changes for next 4 clock cycles
+ CCP = 0xD8; // magic number
+ // reset (WDIF + WDE), no WDIE, fastest (16ms) timing (0000)
+ // (DS section 8.5.2 and table 8-4)
+ WDTCSR = 0b10001000;
+ sei();
+ wdt_reset();
+ while (1) {}
+}
+
+inline void prevent_reboot_loop() {
+ // prevent WDT from rebooting MCU again
+ MCUSR &= ~(1<<WDRF); // reset status flag
+ wdt_disable(); // from avr/wdt.h
+}
+
diff --git a/arch/attiny1634.h b/arch/attiny1634.h
new file mode 100644
index 0000000..559d04e
--- /dev/null
+++ b/arch/attiny1634.h
@@ -0,0 +1,112 @@
+// arch/attiny1634.h: attiny1634 support header
+// Copyright (C) 2014-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+// fill in missing values from Atmel's headers
+#define PROGMEM_SIZE 16384
+#define EEPROM_SIZE 256
+
+////////// clock speed / delay stuff //////////
+
+#define F_CPU 8000000UL
+#define BOGOMIPS (F_CPU/4000)
+#define DELAY_ZERO_TIME 1020
+
+inline void mcu_clock_speed();
+
+///// clock dividers
+inline void clock_prescale_set(uint8_t n);
+
+// TODO? allow hwdef to define a base clock speed,
+// and adjust these values accordingly
+typedef enum
+{
+ // datasheet 6.5.2, CLKPR - Clock Prescale Register
+ clock_div_1 = 0, // 8 MHz
+ clock_div_2 = 1, // 4 MHz
+ clock_div_4 = 2, // 2 MHz
+ clock_div_8 = 3, // 1 MHz
+ clock_div_16 = 4, // 500 kHz
+ clock_div_32 = 5, // 250 kHz
+ clock_div_64 = 6, // 125 kHz
+ clock_div_128 = 7, // 62.5 kHz
+ clock_div_256 = 8, // 31.75 kHz
+} clock_div_t;
+
+
+////////// ADC voltage / temperature //////////
+
+#define V_REF REFS1
+//#define VOLTAGE_ADC_DIDR DIDR0 // set this in hwdef
+
+// DS table 19-3, 19-4, 1.1V ref / VCC
+#define ADMUX_VCC 0b00001101
+// (1 << V_REF) | (THERM_CHANNEL)
+// DS table 19-3, 19-4, internal sensor / 1.1V ref
+// [refs1, refs0, refen, adc0en, mux3, mux2, mux1, mux0]
+// refs=0b10 : internal 1.1V ref
+// mux=0b1110 : internal temperature sensor
+#define ADMUX_THERM 0b10001110
+
+#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();
+
+inline void mcu_adc_off();
+
+// NOP because interrupt flag clears itself
+#define mcu_adc_vect_clear()
+
+inline uint16_t mcu_adc_result();
+
+// return Volts * 40, range 0 to 6.375V
+#define voltage_raw2cooked mcu_vdd_raw2cooked
+inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement);
+inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement);
+
+// return (temp in Kelvin << 6)
+#define temp_raw2cooked mcu_temp_raw2cooked
+inline uint16_t mcu_temp_raw2cooked(uint16_t measurement);
+
+inline uint8_t mcu_adc_lsb();
+
+
+////////// WDT //////////
+
+inline void mcu_wdt_active();
+
+inline void mcu_wdt_standby();
+
+inline void mcu_wdt_stop();
+
+// NOP because interrupt flag clears itself
+#define mcu_wdt_vect_clear()
+
+
+////////// PCINT - pin change interrupt (e-switch) //////////
+
+// set these in hwdef
+//#define SWITCH_PORT PINA
+//#define SWITCH_VECT PCINT0_vect
+
+// NOP because interrupt flag clears itself
+#define 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/arch/attiny85.c b/arch/attiny85.c
new file mode 100644
index 0000000..9e298cc
--- /dev/null
+++ b/arch/attiny85.c
@@ -0,0 +1,225 @@
+// arch/attiny85.c: attiny85 support functions
+// Copyright (C) 2014-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include "arch/attiny85.h"
+
+////////// clock speed / delay stuff //////////
+
+///// clock dividers
+
+////////// default hw_setup() //////////
+
+// FIXME: fsm/main should call hwdef_setup(), not hw_setup,
+// and this function should be hwdef_setup
+#ifdef USE_GENERIC_HWDEF_SETUP
+static inline void hwdef_setup() {
+ // configure PWM channels
+ #if PWM_CHANNELS >= 1
+ DDRB |= (1 << PWM1_PIN);
+ TCCR0B = 0x01; // pre-scaler for timer (1 => 1, 2 => 8, 3 => 64...)
+ TCCR0A = PHASE;
+ #if (PWM1_PIN == PB4) // Second PWM counter is ... weird
+ TCCR1 = _BV (CS10);
+ GTCCR = _BV (COM1B1) | _BV (PWM1B);
+ OCR1C = 255; // Set ceiling value to maximum
+ #endif
+ #endif
+ // tint ramping needs second channel enabled,
+ // despite PWM_CHANNELS being only 1
+ #if (PWM_CHANNELS >= 2) || defined(USE_TINT_RAMPING)
+ DDRB |= (1 << PWM2_PIN);
+ #if (PWM2_PIN == PB4) // Second PWM counter is ... weird
+ TCCR1 = _BV (CS10);
+ GTCCR = _BV (COM1B1) | _BV (PWM1B);
+ OCR1C = 255; // Set ceiling value to maximum
+ #endif
+ #endif
+ #if PWM_CHANNELS >= 3
+ DDRB |= (1 << PWM3_PIN);
+ #if (PWM3_PIN == PB4) // Second PWM counter is ... weird
+ TCCR1 = _BV (CS10);
+ GTCCR = _BV (COM1B1) | _BV (PWM1B);
+ OCR1C = 255; // Set ceiling value to maximum
+ #endif
+ #endif
+ #if PWM_CHANNELS >= 4
+ // 4th PWM channel is ... not actually supported in hardware :(
+ DDRB |= (1 << PWM4_PIN);
+ //OCR1C = 255; // Set ceiling value to maximum
+ TCCR1 = 1<<CTC1 | 1<<PWM1A | 3<<COM1A0 | 2<<CS10;
+ GTCCR = (2<<COM1B0) | (1<<PWM1B);
+ // set up an interrupt to control PWM4 pin
+ TIMSK |= (1<<OCIE1A) | (1<<TOIE1);
+ #endif
+
+ // configure e-switch
+ PORTB = (1 << SWITCH_PIN); // e-switch is the only input
+ PCMSK = (1 << SWITCH_PIN); // pin change interrupt uses this pin
+}
+#endif // #ifdef USE_GENERIC_HWDEF_SETUP
+
+
+////////// ADC voltage / temperature //////////
+
+inline void mcu_set_admux_therm() {
+ // put the ADC in temperature mode
+ ADMUX = ADMUX_THERM | (1 << ADLAR);
+}
+
+inline void mcu_set_admux_voltage() {
+ // put the ADC in battery voltage measurement mode
+ // TODO: avr datasheet references
+ #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7
+ ADMUX = ADMUX_VOLTAGE_DIVIDER | (1 << ADLAR);
+ // disable digital input on divider pin to reduce power consumption
+ VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC);
+ #else // VCC / 1.1V reference
+ ADMUX = ADMUX_VCC | (1 << ADLAR);
+ // disable digital input on VCC pin to reduce power consumption
+ //VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC); // FIXME: unsure how to handle for VCC pin
+ #endif
+}
+
+inline void mcu_adc_sleep_mode() {
+ set_sleep_mode(SLEEP_MODE_ADC);
+}
+
+inline void mcu_adc_start_measurement() {
+ ADCSRA = (1 << ADEN) // enable
+ | (1 << ADSC) // start
+ | (1 << ADATE) // auto-retrigger
+ | (1 << ADIE) // interrupt enable
+ | ADC_PRSCL; // prescale
+}
+
+inline void mcu_adc_off() {
+ ADCSRA &= ~(1<<ADEN); //ADC off
+}
+
+// left-adjusted mode:
+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
+ // 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;
+}
+
+
+#ifdef USE_VOLTAGE_DIVIDER
+inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement) {
+ // In : 4095 * Vdiv / 1.1V
+ // Out: uint8_t: Vbat * 40
+ // 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));
+ uint8_t result = measurement / adc_per_volt;
+ return result;
+}
+#endif
+
+inline uint16_t mcu_temp_raw2cooked(uint16_t measurement) {
+ // convert raw ADC values to calibrated temperature
+ // In: ADC raw temperature (16-bit, or left-aligned)
+ // Out: Kelvin << 6
+ // Precision: 1/64th Kelvin (but noisy)
+ // attiny1634 datasheet section 19.12
+ // nothing to do; input value is already "cooked"
+ return measurement;
+}
+
+inline uint8_t mcu_adc_lsb() {
+ // left-adjusted mode:
+ return (ADCL >> 6) + (ADCH << 2);
+}
+
+
+////////// WDT //////////
+
+inline void mcu_wdt_active() {
+ // interrupt every 16ms
+ //cli(); // Disable interrupts
+ wdt_reset(); // Reset the WDT
+ WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
+ WDTCR = (1<<WDIE); // Enable interrupt every 16ms
+ //sei(); // Enable interrupts
+}
+
+inline void mcu_wdt_standby() {
+ // interrupt slower
+ //cli(); // Disable interrupts
+ wdt_reset(); // Reset the WDT
+ WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
+ WDTCR = (1<<WDIE) | STANDBY_TICK_SPEED; // Enable interrupt every so often
+ //sei(); // Enable interrupts
+}
+
+inline void mcu_wdt_stop() {
+ //cli(); // Disable interrupts
+ wdt_reset(); // Reset the WDT
+ MCUSR &= ~(1<<WDRF); // Clear Watchdog reset flag
+ WDTCR |= (1<<WDCE) | (1<<WDE); // Start timed sequence
+ WDTCR = 0x00; // Disable WDT
+ //sei(); // Enable interrupts
+}
+
+
+////////// PCINT - pin change interrupt (e-switch) //////////
+
+inline void mcu_pcint_on() {
+ // enable pin change interrupt
+ GIMSK |= (1 << PCIE);
+ // only pay attention to the e-switch pin
+ #if 0 // this is redundant; was already done in main()
+ PCMSK = (1 << SWITCH_PCINT);
+ #endif
+ // set bits 1:0 to 0b01 (interrupt on rising *and* falling edge) (default)
+ // MCUCR &= 0b11111101; MCUCR |= 0b00000001;
+}
+
+inline void mcu_pcint_off() {
+ // disable all pin-change interrupts
+ GIMSK &= ~(1 << PCIE);
+}
+
+
+////////// misc //////////
+
+void reboot() {
+ // put the WDT in hard reset mode, then trigger it
+ cli();
+ WDTCR = 0xD8 | WDTO_15MS;
+ sei();
+ wdt_reset();
+ while (1) {}
+}
+
+inline void prevent_reboot_loop() {
+ // prevent WDT from rebooting MCU again
+ MCUSR &= ~(1<<WDRF); // reset status flag
+ wdt_disable();
+}
+
+
+#if 0 // example for one way of creating a 4th PWM channel
+// 4th PWM channel requires manually turning the pin on/off via interrupt :(
+ISR(TIMER1_OVF_vect) {
+ //bitClear(PORTB, 3);
+ PORTB &= 0b11110111;
+ //PORTB |= 0b00001000;
+}
+ISR(TIMER1_COMPA_vect) {
+ //if (!bitRead(TIFR,TOV1)) bitSet(PORTB, 3);
+ if (! (TIFR & (1<<TOV1))) PORTB |= 0b00001000;
+ //if (! (TIFR & (1<<TOV1))) PORTB &= 0b11110111;
+}
+#endif
+
diff --git a/arch/attiny85.h b/arch/attiny85.h
new file mode 100644
index 0000000..3f6ffcb
--- /dev/null
+++ b/arch/attiny85.h
@@ -0,0 +1,98 @@
+// arch/attiny85.h: attiny85 support header
+// Copyright (C) 2014-2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+// fill in missing values from Atmel's headers
+#define PROGMEM_SIZE 8192
+#define EEPROM_SIZE 512
+
+////////// clock speed / delay stuff //////////
+
+// TODO: Use 6.4 MHz instead of 8 MHz?
+#define F_CPU 8000000UL
+#define BOGOMIPS (F_CPU/4000)
+#define DELAY_ZERO_TIME 1020
+
+///// clock dividers
+// use clock_prescale_set(n) instead; it's safer
+//#define CLOCK_DIVIDER_SET(n) {CLKPR = 1<<CLKPCE; CLKPR = n;}
+
+
+////////// default hw_setup() //////////
+
+#ifdef USE_GENERIC_HWDEF_SETUP
+static inline void hwdef_setup();
+#endif
+
+
+////////// ADC voltage / temperature //////////
+
+#define V_REF REFS1
+#define VOLTAGE_ADC_DIDR DIDR0 // this MCU only has one DIDR
+
+// (1 << V_REF) | (0 << ADLAR) | (VCC_CHANNEL)
+#define ADMUX_VCC 0b00001100
+// (1 << V_REF) | (0 << ADLAR) | (THERM_CHANNEL)
+#define ADMUX_THERM 0b10001111
+
+#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();
+
+inline void mcu_adc_off();
+
+// NOP because interrupt flag clears itself
+#define mcu_adc_vect_clear()
+
+inline uint16_t mcu_adc_result();
+
+// return Volts * 40, range 0 to 6.375V
+#define voltage_raw2cooked mcu_vdd_raw2cooked
+inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement);
+inline uint8_t mcu_vdivider_raw2cooked(uint16_t measurement);
+
+// return (temp in Kelvin << 6)
+#define temp_raw2cooked mcu_temp_raw2cooked
+inline uint16_t mcu_temp_raw2cooked(uint16_t measurement);
+
+inline uint8_t mcu_adc_lsb();
+
+
+////////// WDT //////////
+
+inline void mcu_wdt_active();
+
+inline void mcu_wdt_standby();
+
+inline void mcu_wdt_stop();
+
+// NOP because interrupt flag clears itself
+#define mcu_wdt_vect_clear()
+
+
+////////// PCINT - pin change interrupt (e-switch) //////////
+
+#define SWITCH_PORT PINB // PINA or PINB or PINC
+#define SWITCH_VECT PCINT0_vect
+
+// NOP because interrupt flag clears itself
+#define 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/arch/avr32dd20.c b/arch/avr32dd20.c
new file mode 100644
index 0000000..2ac3526
--- /dev/null
+++ b/arch/avr32dd20.c
@@ -0,0 +1,264 @@
+// arch/avr32dd20.h: avr32dd20 support functions
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include "arch/avr32dd20.h"
+
+////////// 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
+ // set free-running mode or not (yes)
+ ADC0.CTRLA = ADC_CONVMODE_SINGLEENDED_gc
+ | ADC_RESSEL_12BIT_gc
+ | ADC_LEFTADJ_bm
+ | ADC_FREERUN_bm;
+ // set number of samples (requires adjustment in formula too)
+ ADC0.CTRLB = ADC_SAMPNUM_NONE_gc;
+ // accumulate more samples for more resolution
+ ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result
+ // set a clock prescaler
+ //ADC0.CTRLC = ADC_PRESC_DIV64_gc; // use this when no accumulation
+ ADC0.CTRLC = ADC_PRESC_DIV4_gc; // measure faster when oversampling
+ // 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_DIV4_gc; // measure faster when oversampling
+ // 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() {
+ ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt
+ ADC0.COMMAND |= ADC_STCONV_bm; // actually start measuring
+}
+
+/*
+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() {
+ // value is 12-bit left-aligned + 16x oversampling = 16 bits total
+ return ADC0.RES;
+}
+
+inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement) {
+ // In : 65535 * (Vbat / 10) / 1.024V
+ // Out: uint8_t: Vbat * 40
+ // (add 80 to round up near a boundary)
+ uint8_t vbat40 = (uint16_t)(measurement + 80) / 160;
+ return vbat40;
+}
+
+#if 0
+inline uint16_t mcu_vdd_raw2fine(uint16_t measurement) {
+ // In : 65535 * (Vbat / 10) / 1.024V
+ // Out: 65535 * (Vbat / 10) / 1.024V
+ // This MCU's native format is already correct
+ return measurement;
+}
+#endif
+
+inline uint16_t mcu_temp_raw2cooked(uint16_t measurement) {
+ // convert raw ADC values to calibrated temperature
+ // In: ADC raw temperature (16-bit, or 12-bit left-aligned)
+ // Out: Kelvin << 6
+ // Precision: 1/64th Kelvin (but noisy)
+ // AVR DD datasheet section 33.3.3.8
+ uint16_t sigrow_slope = SIGROW.TEMPSENSE0; // factory calibration data
+ uint16_t sigrow_offset = SIGROW.TEMPSENSE1; // 12-bit value
+ //const uint32_t scaling_factor = 4096; // use top 12 bits of ADC data
+ //uint32_t temp = sigrow_offset - (measurement >> 4);
+ const uint32_t scaling_factor = 65536; // use all 16 bits of ADC data
+ uint32_t temp = (sigrow_offset << 4) - measurement;
+ temp *= sigrow_slope; // 24-bit result
+ temp += scaling_factor / 8; // Add 1/8th K to get correct rounding on later divisions
+ //temp = temp >> 6; // change (K << 12) to (K << 6)
+ temp = temp >> 10; // change (K << 16) to (K << 6)
+ return temp; // left-aligned uint16_t, 0 to 1023.98 Kelvin
+}
+
+inline uint8_t mcu_adc_lsb() {
+ // volts and temp are both 16-bit, so the LSB is useful as-is
+ return ADC0_RESL;
+}
+
+
+////////// 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..09b4096
--- /dev/null
+++ b/arch/avr32dd20.h
@@ -0,0 +1,115 @@
+// 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;
+
+
+////////// DAC controls //////////
+
+// main LED outputs
+#define DAC_LVL DAC0_DATA // 0 to 1023, for 0V to Vref
+#define DAC_VREF VREF_DAC0REF // 1.024V, 2.048V, 4.096V, or 2.5V
+
+// Vref values (suitable for DAC and ADC0)
+#define V10 VREF_REFSEL_1V024_gc
+#define V20 VREF_REFSEL_2V048_gc
+#define V25 VREF_REFSEL_2V500_gc
+#define V40 VREF_REFSEL_4V096_gc
+
+
+////////// 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();
+
+// both readings are left-aligned
+inline uint16_t mcu_adc_result();
+
+// 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();
+
+// return Volts * 40, range 0 to 6.375V
+#define voltage_raw2cooked mcu_vdd_raw2cooked
+inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement);
+
+// return (temp in Kelvin << 6)
+#define temp_raw2cooked mcu_temp_raw2cooked
+inline uint16_t mcu_temp_raw2cooked(uint16_t measurement);
+
+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/arch/mcu.c b/arch/mcu.c
new file mode 100644
index 0000000..8881c16
--- /dev/null
+++ b/arch/mcu.c
@@ -0,0 +1,10 @@
+// arch/mcu.c: Attiny portability header.
+// Copyright (C) 2023 Selene ToyKeeper
+// SPDX-License-Identifier: GPL-3.0-or-later
+#pragma once
+
+#include "arch/mcu.h"
+
+#define MCU_C arch/MCUNAME.c
+#include incfile(MCU_C)
+
diff --git a/arch/mcu.h b/arch/mcu.h
index 3b2b974..2080ea9 100644
--- a/arch/mcu.h
+++ b/arch/mcu.h
@@ -5,145 +5,20 @@
// This helps abstract away the differences between various attiny MCUs.
-// auto-detect eeprom size from avr-libc headers
-#ifndef EEPROM_SIZE
- #ifdef E2SIZE
- #define EEPROM_SIZE E2SIZE
- #elif defined(E2END)
- #define EEPROM_SIZE (E2END+1)
- #endif
-#endif
-
-/******************** hardware-specific values **************************/
-#if (ATTINY == 13)
- #define F_CPU 4800000UL
- #define V_REF REFS0
- #define BOGOMIPS 950
- #define ADMUX_VCC 0b00001100
- #define DELAY_ZERO_TIME 252
- #define SWITCH_PORT PINB // PINA or PINB or PINC
- #define VOLTAGE_ADC_DIDR DIDR0 // this MCU only has one DIDR
-#elif (ATTINY == 25)
- // TODO: Use 6.4 MHz instead of 8 MHz?
- #define F_CPU 8000000UL
- #define V_REF REFS1
- #define BOGOMIPS (F_CPU/4000)
- #define ADMUX_VCC 0b00001100
- #define ADMUX_THERM 0b10001111
- #define DELAY_ZERO_TIME 1020
- #define SWITCH_PORT PINB // PINA or PINB or PINC
- #define VOLTAGE_ADC_DIDR DIDR0 // this MCU only has one DIDR
-#elif (ATTINY == 85)
- // TODO: Use 6.4 MHz instead of 8 MHz?
- #define F_CPU 8000000UL
- #define V_REF REFS1
- #define BOGOMIPS (F_CPU/4000)
- // (1 << V_REF) | (0 << ADLAR) | (VCC_CHANNEL)
- #define ADMUX_VCC 0b00001100
- // (1 << V_REF) | (0 << ADLAR) | (THERM_CHANNEL)
- #define ADMUX_THERM 0b10001111
- #define DELAY_ZERO_TIME 1020
- #define SWITCH_PORT PINB // PINA or PINB or PINC
- #define VOLTAGE_ADC_DIDR DIDR0 // this MCU only has one DIDR
-#elif (ATTINY == 1634)
- #define F_CPU 8000000UL
- #define V_REF REFS1
- #define BOGOMIPS (F_CPU/4000)
- // DS table 19-3, 19-4, 1.1V ref / VCC
- #define ADMUX_VCC 0b00001101
- // (1 << V_REF) | (THERM_CHANNEL)
- // DS table 19-3, 19-4, internal sensor / 1.1V ref
- #define ADMUX_THERM 0b10001110
- #define DELAY_ZERO_TIME 1020
- //#define SWITCH_PORT PINA // set this in hwdef
- //#define VOLTAGE_ADC_DIDR DIDR0 // set this in hwdef
-#elif (ATTINY == 412) || (ATTINY == 416) || (ATTINY == 417) || (ATTINY == 816) || (ATTINY == 817) || (ATTINY == 1616) || (ATTINY == 1617) || (ATTINY == 3216) || (ATTINY == 3217)
- #define AVRXMEGA3
- #define F_CPU 10000000UL
- #define BOGOMIPS (F_CPU/4700)
- #define DELAY_ZERO_TIME 1020
-#else
- #error Hey, you need to define ATTINY.
-#endif
-
-
+#include <avr/eeprom.h>
#include <avr/interrupt.h>
+#include <avr/io.h>
+#include <avr/power.h>
+#include <avr/sleep.h>
+#include <avr/wdt.h>
-/******************** I/O pin and register layout ************************/
-#ifdef HWDEFFILE
-#include "fsm/tk.h"
-#include incfile(HWDEFFILE)
-#endif
-
-#if 0 // placeholder
-
-#elif defined(NANJG_LAYOUT)
-#include "hwdef-nanjg.h"
-#elif defined(FET_7135_LAYOUT)
-#include "hwdef-FET_7135.h"
+// for consistency, ROM_SIZE + EEPROM_SIZE
+#define ROM_SIZE PROGMEM_SIZE
-#elif defined(TRIPLEDOWN_LAYOUT)
-#include "hwdef-Tripledown.h"
-
-#elif defined(FERRERO_ROCHER_LAYOUT)
-#include "hwdef-Ferrero_Rocher.h"
-
-#endif // no more recognized driver types
-
-#ifndef LAYOUT_DEFINED
-#error Hey, you need to define an I/O pin layout.
-#endif
-
-#if (ATTINY==13)
- // no changes needed
-#elif (ATTINY==25) || (ATTINY==45) || (ATTINY==85)
- // use clock_prescale_set(n) instead; it's safer
- //#define CLOCK_DIVIDER_SET(n) {CLKPR = 1<<CLKPCE; CLKPR = n;}
-#elif (ATTINY==1634)
- // make it a NOP for now
- // FIXME
- //#define clock_prescale_set(x) ((void)0)
- //#define clock_prescale_set(n) {cli(); CCP = 0xD8; CLKPR = n; sei();}
- //#define clock_prescale_set(n) {cli(); CCP = 0xD8; CLKPR = n; sei();}
- inline void clock_prescale_set(uint8_t n) {cli(); CCP = 0xD8; CLKPR = n; sei();}
- typedef enum
- {
- clock_div_1 = 0,
- clock_div_2 = 1,
- clock_div_4 = 2,
- clock_div_8 = 3,
- clock_div_16 = 4,
- clock_div_32 = 5,
- clock_div_64 = 6,
- clock_div_128 = 7,
- clock_div_256 = 8
- } clock_div_t;
+#include "fsm/tk.h"
-#elif defined(AVRXMEGA3) // ATTINY816, 817, etc
- // this should work, but needs further validation
- inline void clock_prescale_set(uint8_t n) {
- cli();
- CCP = CCP_IOREG_gc; // temporarily disable clock change protection
- CLKCTRL.MCLKCTRLB = n; // Set the prescaler
- while (CLKCTRL.MCLKSTATUS & CLKCTRL_SOSC_bm) {} // wait for clock change to finish
- sei();
- }
- typedef enum
- {
- // Actual clock is 20 MHz, but assume that 10 MHz is the top speed and work from there
- // TODO: measure PWM speed and power use at 1.25/2.5/5/10 MHz, to determine which speeds are optimal
- clock_div_1 = (CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm), // 10 MHz
- clock_div_2 = (CLKCTRL_PDIV_4X_gc | CLKCTRL_PEN_bm), // 5 MHz
- clock_div_4 = (CLKCTRL_PDIV_8X_gc | CLKCTRL_PEN_bm), // 2.5 MHz
- clock_div_8 = (CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm), // 1.25 MHz
- clock_div_16 = (CLKCTRL_PDIV_32X_gc | CLKCTRL_PEN_bm), // 625 kHz
- clock_div_32 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm), // 312 kHz, max without changing to the 32 kHz ULP
- clock_div_64 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm), // 312 kHz
- clock_div_128 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm), // 312 kHz
- clock_div_256 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm) // 312 kHz
- } clock_div_t;
-#else
-#error Unable to define MCU macros.
-#endif
+#define MCU_H arch/MCUNAME.h
+#define MCU_C arch/MCUNAME.c
+#include incfile(MCU_H)