aboutsummaryrefslogtreecommitdiff
path: root/arch
diff options
context:
space:
mode:
authorSelene ToyKeeper2023-11-30 09:19:45 -0700
committerSelene ToyKeeper2023-11-30 09:19:45 -0700
commitf745e12c3bc48d8fe544893871191086cf3cccc9 (patch)
tree0e7f6c2c5f362719ac4efad9d5c2365f3ed3c159 /arch
parentadded md5sum to build-all.sh output per target (diff)
parenteliminated direct CCP register access from arch/attiny1616 (diff)
downloadanduril-f745e12c3bc48d8fe544893871191086cf3cccc9.tar.gz
anduril-f745e12c3bc48d8fe544893871191086cf3cccc9.tar.bz2
anduril-f745e12c3bc48d8fe544893871191086cf3cccc9.zip
Merge branch 'avr32dd20-devkit' into trunk
Added support for AVR DD MCUs, particularly avr32dd20. Also did a bunch of refactoring for how MCU support works, cleaned up the ADC code, switched to consistent internal formats for voltage and temperature, fixed the FW3X, and some other little things. * avr32dd20-devkit: (28 commits) eliminated direct CCP register access from arch/attiny1616 made the avr32dd20 flashing script more universal added a build target for FW3X with manually-fixed RGB aux wiring prevent future issues like the FW3X had fixed FW3X thermal regulation fixed incorrect temperature history for a few seconds after waking fsm/adc: removed dead code FW3X: fixed external temperature sensor FW3X: multiple upgrades... fw3x: fixed swapped red+blue, fixed battery measurements, added police color strobe fixed ADC on sp10-pro fixed ADC on attiny85 and related builds fixed ADC on attiny1634 and related builds more ADC / DAC / MCU progress... avr32dd20-devkit: make the defaults a bit more dev friendly (realtime voltage colors, and no simple UI by default) ADC voltage: battcheck 3 digits, fixed t1616, switched back to 8-bit internal volt unit got ADC voltage+temp working on avrdd... but broke all other builds/MCUs 1.55V AA battery should not show as "white" voltage color, only purple started refactoring fsm/adc.*, but need a checkpoint before continuing added dac-scale.py: short script to calculate avrdd DAC+Vref values from level_calc.py ramp data ...
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)
If you find this content useful please consider a small donation via bitcoin: bitcoin:1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2?amount=0.001&message=Donation Scraping git contents via cgit is very resource intensive. The continued hosting of these git repositories is entirely financed by your bitcoin contributions. For donations of a least 1 mBTC you include your IP address in the message of your bitcoin donation. This will remove this message in the future for requests from your IP, which will significantly reduce token usage. In any case, any contributions to bitcoin address 1mxKyQsHHugqRxPKgwaA7wUwJEGCNthn2 are very welcome. Thanks in advance for you contribution to a self-sustaining ecosystem.