From 3d12b7066d27b591e0283e20ed066bc66e29fbe4 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 10 Nov 2023 21:34:40 -0700 Subject: refactor checkpoint: splitting MCU-specific code into arch/$MCU.[ch] Phew, that's a lot of changes! And there's still a lot more to do... --- arch/attiny1616.c | 147 +++++++++++++++++++++++++++++++++++++++++++++++ arch/attiny1616.h | 96 +++++++++++++++++++++++++++++++ arch/attiny1634.c | 125 ++++++++++++++++++++++++++++++++++++++++ arch/attiny1634.h | 98 ++++++++++++++++++++++++++++++++ arch/attiny85.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ arch/attiny85.h | 91 +++++++++++++++++++++++++++++ arch/mcu.c | 10 ++++ arch/mcu.h | 147 ++++------------------------------------------- 8 files changed, 745 insertions(+), 136 deletions(-) create mode 100644 arch/attiny1616.c create mode 100644 arch/attiny1616.h create mode 100644 arch/attiny1634.c create mode 100644 arch/attiny1634.h create mode 100644 arch/attiny85.c create mode 100644 arch/attiny85.h create mode 100644 arch/mcu.c (limited to 'arch') diff --git a/arch/attiny1616.c b/arch/attiny1616.c new file mode 100644 index 0000000..3b170bb --- /dev/null +++ b/arch/attiny1616.c @@ -0,0 +1,147 @@ +// 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 ////////// + +///// clock dividers +// 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(); +} + +////////// ADC voltage / temperature ////////// + +inline void mcu_set_admux_therm() { + ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc; // read temperature + ADC0.CTRLC = ADC_SAMPCAP_bm + | ADC_PRESC_DIV64_gc + | ADC_REFSEL_INTREF_gc; // Internal ADC reference +} + +inline void mcu_set_admux_voltage() { + #ifdef USE_VOLTAGE_DIVIDER // 1.1V / ADC input pin + // verify that this is correct!!! untested + ADC0.MUXPOS = ADMUX_VOLTAGE_DIVIDER; // read the requested ADC pin + ADC0.CTRLC = ADC_SAMPCAP_bm + | ADC_PRESC_DIV64_gc + | ADC_REFSEL_INTREF_gc; // Use internal ADC reference + #else // VCC / 1.1V reference + ADC0.MUXPOS = ADC_MUXPOS_INTREF_gc; // read internal reference + ADC0.CTRLC = ADC_SAMPCAP_bm + | ADC_PRESC_DIV64_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; // Start the ADC conversions +} + +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() { + // Use the factory calibrated values in SIGROW.TEMPSENSE0 and + // SIGROW.TEMPSENSE1 to calculate a temperature reading in Kelvin, then + // left-align it. + int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row + uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row + uint32_t temp = ADC0.RES - sigrow_offset; + temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit) + temp += 0x80; // Add 1/2 to get correct rounding on division below + //temp >>= 8; // Divide result to get Kelvin + //return temp << 6; // left align it + return temp >> 2; // left-aligned uint16_t +} + +inline uint16_t mcu_adc_result_volts() { + // FIXME: set up ADC to use left-aligned values natively + return ADC0.RES << 6; // voltage, force left-alignment +} + +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 + RTC.PITCTRLA = RTC_PERIOD_CYC512_gc | RTC_PITEN_bm; // Period = 16ms, enable the PI Timer +} + +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 + RTC.PITCTRLA = (1<<6) | (STANDBY_TICK_SPEED<<3) | RTC_PITEN_bm; // Set period, enable the PI Timer +} + +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(); + CCP = CCP_IOREG_gc; // temporarily disable change protection + WDT.CTRLA = WDT_PERIOD_8CLK_gc; // Enable, timeout 8ms + sei(); + wdt_reset(); + while (1) {} +} + diff --git a/arch/attiny1616.h b/arch/attiny1616.h new file mode 100644 index 0000000..5989785 --- /dev/null +++ b/arch/attiny1616.h @@ -0,0 +1,96 @@ +// 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/4700) +#define DELAY_ZERO_TIME 1020 + +///// clock dividers +// this should work, but needs further validation +inline void clock_prescale_set(uint8_t n); + +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; + + +////////// 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(); + +inline void mcu_adc_on(); + +inline void mcu_adc_off(); + +#define ADC_vect ADC0_RESRDY_vect +inline void mcu_adc_vect_clear(); + +// read ADC differently for temperature and voltage +#define MCU_ADC_RESULT_PER_TYPE + +inline uint16_t mcu_adc_result_temp(); + +inline uint16_t mcu_adc_result_volts(); + +inline uint8_t mcu_adc_lsb(); + + +////////// WDT ////////// + +inline void mcu_wdt_active(); + +inline void mcu_wdt_standby(); + +inline void mcu_wdt_stop(); + +// *** 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(); + diff --git a/arch/attiny1634.c b/arch/attiny1634.c new file mode 100644 index 0000000..d4b3767 --- /dev/null +++ b/arch/attiny1634.c @@ -0,0 +1,125 @@ +// 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 ////////// + +///// clock dividers +// 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(); +} + +////////// default hw_setup() ////////// + + +////////// ADC voltage / temperature ////////// + +inline void mcu_set_admux_therm() { + ADMUX = ADMUX_THERM; +} + +inline void mcu_set_admux_voltage() { + #ifdef USE_VOLTAGE_DIVIDER // 1.1V / pin7 + ADMUX = ADMUX_VOLTAGE_DIVIDER; + #else // VCC / 1.1V reference + ADMUX = ADMUX_VCC; + #endif +} + +inline void mcu_adc_sleep_mode() { + set_sleep_mode(SLEEP_MODE_ADC); +} + +inline void mcu_adc_start_measurement() { + ADCSRA |= (1 << ADSC) | (1 << ADIE); +} + +inline void mcu_adc_on() { + hwdef_set_admux_voltage(); + #ifdef USE_VOLTAGE_DIVIDER + // disable digital input on divider pin to reduce power consumption + VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC); + #else + // 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 |= (1 << ADLAR); // left-adjust flag is here instead of ADMUX + // enable, start, auto-retrigger, prescale + ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | ADC_PRSCL; +} + +inline void mcu_adc_off() { + ADCSRA &= ~(1<> 6) + (ADCH << 2); } + + +////////// WDT ////////// + +inline void mcu_wdt_active() { + wdt_reset(); // Reset the WDT + WDTCSR = (1<= 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<> 6) + (ADCH << 2); } + + +////////// WDT ////////// + +inline void mcu_wdt_active() { + // interrupt every 16ms + //cli(); // Disable interrupts + wdt_reset(); // Reset the WDT + WDTCR |= (1< #include +#include +#include +#include +#include -/******************** 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<= 25 us * Fclk_adc (no sample delay needed) + ADC0.CTRLD = ADC_INITDLY_DLY32_gc | ADC_SAMPDLY_DLY0_gc; + // configure ADC sample length to >= 28 us * Fclk_adc + ADC0.SAMPCTRL = 32; + // set single-ended or differential + // set resolution to 12 bits + // set left- or right-adjust (right) + // set free-running mode or not (yes) + ADC0.CTRLA = ADC_CONVMODE_SINGLEENDED_gc + | ADC_RESSEL_12BIT_gc + //| ADC_LEFTADJ_bm // not in temperature mode + | ADC_FREERUN_bm; + // set number of samples (requires adjustment in formula too) + ADC0.CTRLB = ADC_SAMPNUM_NONE_gc; + // TODO: accumulate more samples for more resolution + // (and probably set the prescale faster too) + //ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result + // set a clock prescaler + ADC0.CTRLC = ADC_PRESC_DIV64_gc; + // enable the ADC + ADC0.CTRLA |= ADC_ENABLE_bm; + // actually start measuring (happens in another function) + //ADC0.COMMAND |= ADC_STCONV_bm; + // for each measurement: + // process according to sigrow data + formula +} + +inline void mcu_set_admux_voltage() { + // ADC init: Datasheet section 33.3.2 + // set Vref + VREF.ADC0REF = VREF_REFSEL_1V024_gc; + // set single-ended or differential + // set resolution to 12 bits + // set left- or right-adjust (right) + // set free-running mode or not (yes) + ADC0.CTRLA = ADC_CONVMODE_SINGLEENDED_gc + | ADC_RESSEL_12BIT_gc + | ADC_LEFTADJ_bm // has no effect when 16+ samples taken + | ADC_FREERUN_bm + | ADC_RUNSTBY_bm // allow voltage sense in standby mode + ; + // set number of samples + ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result + // set a clock prescaler + ADC0.CTRLC = ADC_PRESC_DIV16_gc; // not too fast, not too slow + // select the positive ADC input with MUXPOS + #ifdef USE_VOLTAGE_DIVIDER // external voltage divider + // ADC input pin / Vref + ADC0.MUXPOS = ADMUX_VOLTAGE_DIVIDER; // external pin + #elif defined (USE_VOLTAGE_VDDIO2) // internal voltage divider + // (Vbat / 10) / Vref + ADC0.MUXPOS = ADC_MUXPOS_VDDIO2DIV10_gc; + #else // measure directly on VDD/VCC pin + // (Vbat / 10) / Vref + ADC0.MUXPOS = ADC_MUXPOS_VDDDIV10_gc; + #endif + // enable the ADC + ADC0.CTRLA |= ADC_ENABLE_bm; + // actually start measuring (happens in another function) + //ADC0.COMMAND |= ADC_STCONV_bm; +} + +inline void mcu_adc_sleep_mode() { + set_sleep_mode(SLEEP_MODE_STANDBY); +} + +inline void mcu_adc_start_measurement() { + // FIXME: enable this after getting ADC stuff fixed + #if 0 + ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt + ADC0.COMMAND |= ADC_STCONV_bm; // actually start measuring + #endif +} + +/* +void mcu_adc_on() { + hwdef_set_admux_voltage(); + mcu_adc_start_measurement(); +} +*/ + +inline void mcu_adc_off() { + ADC0.CTRLA &= ~(ADC_ENABLE_bm); // disable the ADC +} + +inline void mcu_adc_vect_clear() { + ADC0.INTFLAGS = ADC_RESRDY_bm; // clear the interrupt +} + +inline uint16_t mcu_adc_result_temp() { + // FIXME: better math, higher precision + // Use the factory calibrated values in SIGROW.TEMPSENSE0 and + // SIGROW.TEMPSENSE1 to calculate a temperature reading in Kelvin, then + // left-align it. + int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row + uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row + uint32_t temp = ADC0.RES - sigrow_offset; + temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit) + temp += 0x80; // Add 1/2 to get correct rounding on division below + //temp >>= 8; // Divide result to get Kelvin + //return temp << 6; // left align it + return temp >> 2; // left-aligned uint16_t +} + +inline uint16_t mcu_adc_result_volts() { + // voltage is 12-bit right-aligned + 16x oversampling = 16 bits total + return ADC0.RES; +} + +inline uint8_t mcu_adc_lsb() { + // temp is right-aligned, voltage is 16-bit, both have a useful LSB + return ADC0_RESL; +} + +#ifdef USE_VOLTAGE_VDD +uint8_t calc_voltage(uint16_t measurement) { + // calculate actual voltage: volts * 10 + // FIXME + // ADC = 1.1 * 1024 / volts + // volts = 1.1 * 1024 / ADC + result = ((uint16_t)(2*1.1*1024*10)/(measurement>>6) + + VOLTAGE_FUDGE_FACTOR + #ifdef USE_VOLTAGE_CORRECTION + + VOLT_CORR - 7 + #endif + ) >> 1; + return result; +} +#elif defined(USE_VOLTAGE_VDDIO2) +#elif defined(USE_VOLTAGE_VDDIO2) +#else +// hwdef must supply its own function +#endif + +////////// WDT ////////// +// this uses the RTC PIT interrupt instead of WDT, +// as recommended on AVR 0/1-series and later: +// https://github.com/SpenceKonde/DxCore/blob/master/megaavr/extras/PowerSave.md#using-the-real-time-counter-rtcpit +// The PIT runs even in power-down mode, unlike RTC, +// and its cycles are relative to the AVR's internal 32768 Hz ULP oscillator +// AVR datasheet sections 26, 26.5, 26.9, 26.12, 26.13.12 + +// PIT tick speeds: +// 0 (none) +// 1 8192 Hz (CYC4) +// 2 4096 Hz +// 3 2048 Hz +// 4 1024 Hz (CYC32) +// 5 512 Hz +// 6 256 Hz +// 7 128 Hz +// 8 64 Hz (default) (CYC512) +inline void mcu_wdt_active() { + RTC.PITINTCTRL = RTC_PI_bm; // enable the Periodic Interrupt + while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated + // Period = 16ms (64 Hz), enable the PI Timer + RTC.PITCTRLA = RTC_PERIOD_CYC512_gc | RTC_PITEN_bm; +} + +// STANDBY_TICK_SPEED: +// 0 64 Hz +// 1 32 Hz +// 2 16 Hz +// 3 8 Hz (default) +// 4 4 Hz +// 5 2 Hz +// 6 1 Hz +inline void mcu_wdt_standby() { + RTC.PITINTCTRL = RTC_PI_bm; // enable the Periodic Interrupt + while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated + // Set period (64 Hz / STANDBY_TICK_SPEED = 8 Hz), enable the PI Timer + RTC.PITCTRLA = ((8+STANDBY_TICK_SPEED)<<3) | RTC_PITEN_bm; +} + +inline void mcu_wdt_stop() { + while (RTC.PITSTATUS > 0) {} // make sure the register is ready to be updated + RTC.PITCTRLA = 0; // Disable the PI Timer +} + +inline void mcu_wdt_vect_clear() { + RTC.PITINTFLAGS = RTC_PI_bm; // clear the PIT interrupt flag +} + + +////////// PCINT - pin change interrupt (e-switch) ////////// + +inline void mcu_switch_vect_clear() { + // Write a '1' to clear the interrupt flag + SWITCH_INTFLG |= (1 << SWITCH_PIN); +} + +inline void mcu_pcint_on() { + SWITCH_ISC_REG |= PORT_ISC_BOTHEDGES_gc; +} + +inline void mcu_pcint_off() { + SWITCH_ISC_REG &= ~(PORT_ISC_gm); +} + + +////////// misc ////////// + +void reboot() { + // request a reboot (software reset) + _PROTECTED_WRITE(RSTCTRL.SWRR, RSTCTRL_SWRST_bm); +} + +inline void prevent_reboot_loop() { + // if previous reset was a crash (WDT time-out), + // prevent it from happening again immediately + //RSTCTRL.RSTFR &= ~(RSTCTRL_WDRF_bm); // reset wdt flag only + RSTCTRL.RSTFR = 0; // reset all reset status flags (maybe unneeded?) + wdt_disable(); // from avr/wdt.h +} + diff --git a/arch/avr32dd20.h b/arch/avr32dd20.h new file mode 100644 index 0000000..7a6b8f1 --- /dev/null +++ b/arch/avr32dd20.h @@ -0,0 +1,93 @@ +// arch/avr32dd20.h: avr32dd20 support header +// Copyright (C) 2023 Selene ToyKeeper +// SPDX-License-Identifier: GPL-3.0-or-later +#pragma once + +////////// clock speed / delay stuff ////////// + +#define F_CPU 12000000UL +#define BOGOMIPS (F_CPU/3800) +#define DELAY_ZERO_TIME 1020 + +inline void mcu_clock_speed(); + +///// clock dividers +// this should work, but needs further validation +inline void clock_prescale_set(uint8_t n); + +// TODO: allow hwdef to define a base clock speed, +// and adjust these values accordingly +typedef enum +{ + // Actual clock is 12 MHz + clock_div_1 = (0), // 12 MHz + clock_div_2 = (CLKCTRL_PDIV_2X_gc | CLKCTRL_PEN_bm), // 6 MHz + clock_div_4 = (CLKCTRL_PDIV_4X_gc | CLKCTRL_PEN_bm), // 3 MHz + clock_div_8 = (CLKCTRL_PDIV_8X_gc | CLKCTRL_PEN_bm), // 1.5 MHz + clock_div_16 = (CLKCTRL_PDIV_16X_gc | CLKCTRL_PEN_bm), // 0.75 MHz + clock_div_32 = (CLKCTRL_PDIV_32X_gc | CLKCTRL_PEN_bm), // 375 kHz + clock_div_64 = (CLKCTRL_PDIV_64X_gc | CLKCTRL_PEN_bm), // 187.5 kHz +} clock_div_t; + + +////////// ADC voltage / temperature ////////// + +#define hwdef_set_admux_therm mcu_set_admux_therm +inline void mcu_set_admux_therm(); + +#define hwdef_set_admux_voltage mcu_set_admux_voltage +inline void mcu_set_admux_voltage(); + +inline void mcu_adc_sleep_mode(); + +inline void mcu_adc_start_measurement(); + +//#define mcu_adc_on hwdef_set_admux_voltage +//void mcu_adc_on(); + +inline void mcu_adc_off(); + +#define ADC_vect ADC0_RESRDY_vect +inline void mcu_adc_vect_clear(); + +// read ADC differently for temperature and voltage +#define MCU_ADC_RESULT_PER_TYPE + +inline uint16_t mcu_adc_result_temp(); + +inline uint16_t mcu_adc_result_volts(); + +inline uint8_t mcu_adc_lsb(); + + +////////// WDT ////////// + +inline void mcu_wdt_active(); + +inline void mcu_wdt_standby(); + +inline void mcu_wdt_stop(); + +#define WDT_vect RTC_PIT_vect +inline void mcu_wdt_vect_clear(); + + +////////// PCINT - pin change interrupt (e-switch) ////////// + +// set these in hwdef +//#define SWITCH_PORT VPORTD.IN +//#define SWITCH_VECT PORTD_PORT_vect + +inline void mcu_switch_vect_clear(); + +inline void mcu_pcint_on(); + +inline void mcu_pcint_off(); + + +////////// misc ////////// + +void reboot(); + +inline void prevent_reboot_loop(); + -- cgit v1.2.3 From fdb47b96e86924bf81852205b2b3078b8b21d040 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Tue, 21 Nov 2023 02:52:00 -0700 Subject: got ADC voltage+temp working on avrdd... but broke all other builds/MCUs This patch changes the ADC code to use two internal standard units, and everything else must convert to these units: - FSM Volts: centiVolts << 6 (range 0 to 10.24 V per cell) - FSM Kelvin: Kelvin << 6 (range 0 to 1024 K) UI-level voltage is still "Volts * 10", and temperature is still Celsius. FSM expects functions to be provided, to convert from the hardware's raw ADC measurements to these internal units: `voltage_raw2cooked()` and `temp_raw2cooked()`. Defaults will be provided by arch/*.[ch] for each MCU type, or the hwdef can make its own. Anyway, gotta go fix all the other MCUs and builds now. :( --- arch/avr32dd20.c | 60 ++++++++++++++++++++++++++++++++------------------------ arch/avr32dd20.h | 15 +++++++++++--- 2 files changed, 46 insertions(+), 29 deletions(-) (limited to 'arch') diff --git a/arch/avr32dd20.c b/arch/avr32dd20.c index 45923a8..998e9f6 100644 --- a/arch/avr32dd20.c +++ b/arch/avr32dd20.c @@ -52,19 +52,19 @@ inline void mcu_set_admux_therm() { ADC0.SAMPCTRL = 32; // set single-ended or differential // set resolution to 12 bits - // set left- or right-adjust (right) + // 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 // not in temperature mode + | ADC_LEFTADJ_bm | ADC_FREERUN_bm; // set number of samples (requires adjustment in formula too) ADC0.CTRLB = ADC_SAMPNUM_NONE_gc; - // TODO: accumulate more samples for more resolution - // (and probably set the prescale faster too) - //ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result + // 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; + //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) @@ -90,7 +90,7 @@ inline void mcu_set_admux_voltage() { // set number of samples ADC0.CTRLB = ADC_SAMPNUM_ACC16_gc; // 16 samples per result // set a clock prescaler - ADC0.CTRLC = ADC_PRESC_DIV16_gc; // not too fast, not too slow + 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 @@ -113,11 +113,8 @@ inline void mcu_adc_sleep_mode() { } inline void mcu_adc_start_measurement() { - // FIXME: enable this after getting ADC stuff fixed - #if 0 ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt ADC0.COMMAND |= ADC_STCONV_bm; // actually start measuring - #endif } /* @@ -135,24 +132,35 @@ inline void mcu_adc_vect_clear() { ADC0.INTFLAGS = ADC_RESRDY_bm; // clear the interrupt } -inline uint16_t mcu_adc_result_temp() { - // FIXME: better math, higher precision - // Use the factory calibrated values in SIGROW.TEMPSENSE0 and - // SIGROW.TEMPSENSE1 to calculate a temperature reading in Kelvin, then - // left-align it. - int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row - uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row - uint32_t temp = ADC0.RES - sigrow_offset; - temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit) - temp += 0x80; // Add 1/2 to get correct rounding on division below - //temp >>= 8; // Divide result to get Kelvin - //return temp << 6; // left align it - return temp >> 2; // left-aligned uint16_t +inline uint16_t mcu_adc_result() { + // value is 12-bit left-aligned + 16x oversampling = 16 bits total + return ADC0.RES; } -inline uint16_t mcu_adc_result_volts() { - // voltage is 12-bit right-aligned + 16x oversampling = 16 bits total - return ADC0.RES; +inline uint16_t mcu_vdd_raw2cooked(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; +} + +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() { diff --git a/arch/avr32dd20.h b/arch/avr32dd20.h index 7a6b8f1..3ebb05a 100644 --- a/arch/avr32dd20.h +++ b/arch/avr32dd20.h @@ -50,12 +50,21 @@ 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 +//#define MCU_ADC_RESULT_PER_TYPE +//inline uint16_t mcu_adc_result_temp(); +//inline uint16_t mcu_adc_result_volts(); -inline uint16_t mcu_adc_result_temp(); +// return (centiVolts << 6), range 0 to 10.24V +#define voltage_raw2cooked mcu_vdd_raw2cooked +inline uint16_t mcu_vdd_raw2cooked(uint16_t measurement); -inline uint16_t mcu_adc_result_volts(); +// 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(); -- cgit v1.2.3 From baaa035cf93340b8f2c626bdba47e8066cf40067 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Wed, 22 Nov 2023 05:34:26 -0700 Subject: ADC voltage: battcheck 3 digits, fixed t1616, switched back to 8-bit internal volt unit Before this branch, `voltage` was 6 bits: Volts * 10 A couple patches ago, I upgraded it to 16 bits: 65535 * Volts / 10.24 That costs too much extra ROM on attiny85 though, for extra precision it doesn't even use... so I switched back to an 8-bit value. It's still more precise than before though: Volts * 40 ... and battcheck displays an extra digit now, on devices with ROM for it. ... and battcheck waits a second to get a more accurate measurement before displaying the first value. It has *much* less variation between first and later readings now. Also: - got t1616 builds working again (tested fc13 and thefreeman-boost-fwaa) - upgraded t1616 voltage and temp to 12-bit (10 bits + 4x oversampling) - removed expensive temp conversion from t1616 ADC interrupt - recalibrated t1616 bogomips again; runs faster after interrupt fix - increased t1616 internal VDD measurement resolution by 36% (1.5V Vref, not 1.1V) - fixed sloppy setting of Vref bits I still need to test / update other t1616 builds, and fix all the t85 + t1634 code and build targets. --- arch/attiny1616.c | 125 +++++++++++++++++++++++++++++++++++++++++++----------- arch/attiny1616.h | 20 +++++++-- arch/avr32dd20.c | 37 +++++++--------- arch/avr32dd20.h | 4 +- 4 files changed, 133 insertions(+), 53 deletions(-) (limited to 'arch') diff --git a/arch/attiny1616.c b/arch/attiny1616.c index a3ead7e..330c809 100644 --- a/arch/attiny1616.c +++ b/arch/attiny1616.c @@ -7,36 +7,60 @@ ////////// 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(); - CCP = CCP_IOREG_gc; // temporarily disable clock change protection - CLKCTRL.MCLKCTRLB = n; // Set the prescaler + _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 + VREF.CTRLA = (VREF.CTRLA & (~VREF_ADC0REFSEL_gm)) + | 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_DIV64_gc + | ADC_PRESC_DIV16_gc | ADC_REFSEL_INTREF_gc; // Internal ADC reference } inline void mcu_set_admux_voltage() { - #ifdef USE_VOLTAGE_DIVIDER // 1.1V / ADC input pin - // verify that this is correct!!! untested + // 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 + VREF.CTRLA = (VREF.CTRLA & (~VREF_ADC0REFSEL_gm)) + | 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_DIV64_gc + | ADC_PRESC_DIV16_gc | ADC_REFSEL_INTREF_gc; // Use internal ADC reference - #else // VCC / 1.1V reference + #else // measure VDD pin + // result = resolution * 1.5V / Vbat + VREF.CTRLA = (VREF.CTRLA & (~VREF_ADC0REFSEL_gm)) + | 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_DIV64_gc + | ADC_PRESC_DIV16_gc | ADC_REFSEL_VDDREF_gc; // Vdd (Vcc) be ADC reference #endif } @@ -47,9 +71,10 @@ inline void mcu_adc_sleep_mode() { inline void mcu_adc_start_measurement() { ADC0.INTCTRL |= ADC_RESRDY_bm; // enable interrupt - ADC0.COMMAND |= ADC_STCONV_bm; // Start the ADC conversions + 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 @@ -59,6 +84,7 @@ inline void mcu_adc_on() { ADC0.CTRLD |= ADC_INITDLY_DLY16_gc; hwdef_set_admux_voltage(); } +*/ inline void mcu_adc_off() { ADC0.CTRLA &= ~(ADC_ENABLE_bm); // disable the ADC @@ -69,22 +95,69 @@ inline void mcu_adc_vect_clear() { } inline uint16_t mcu_adc_result_temp() { - // Use the factory calibrated values in SIGROW.TEMPSENSE0 and - // SIGROW.TEMPSENSE1 to calculate a temperature reading in Kelvin, then - // left-align it. - int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row - uint8_t sigrow_gain = SIGROW.TEMPSENSE0; // Read unsigned value from signature row - uint32_t temp = ADC0.RES - sigrow_offset; - temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit) - temp += 0x80; // Add 1/2 to get correct rounding on division below - //temp >>= 8; // Divide result to get Kelvin - //return temp << 6; // left align it - return temp >> 2; // left-aligned uint16_t + // 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() { - // FIXME: set up ADC to use left-aligned values natively - return ADC0.RES << 6; // voltage, force left-alignment + // 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() { @@ -98,13 +171,15 @@ inline uint8_t mcu_adc_lsb() { 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 - RTC.PITCTRLA = RTC_PERIOD_CYC512_gc | RTC_PITEN_bm; // Period = 16ms, enable the PI Timer + // 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 - RTC.PITCTRLA = (1<<6) | (STANDBY_TICK_SPEED<<3) | RTC_PITEN_bm; // Set period, enable the PI Timer + // 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() { @@ -148,6 +223,6 @@ void reboot() { inline void prevent_reboot_loop() { // prevent WDT from rebooting MCU again RSTCTRL.RSTFR &= ~(RSTCTRL_WDRF_bm); // reset status flag - wdt_disable(); + wdt_disable(); // from avr/wdt.h } diff --git a/arch/attiny1616.h b/arch/attiny1616.h index b4e17fc..57b0023 100644 --- a/arch/attiny1616.h +++ b/arch/attiny1616.h @@ -9,9 +9,11 @@ ////////// clock speed / delay stuff ////////// #define F_CPU 10000000UL -#define BOGOMIPS (F_CPU/4700) +#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); @@ -44,20 +46,30 @@ inline void mcu_adc_sleep_mode(); inline void mcu_adc_start_measurement(); -inline void mcu_adc_on(); +//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(); diff --git a/arch/avr32dd20.c b/arch/avr32dd20.c index 998e9f6..2ac3526 100644 --- a/arch/avr32dd20.c +++ b/arch/avr32dd20.c @@ -1,8 +1,10 @@ -// arch/avr32dd20.h: avr32dd20 support header +// 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() { @@ -137,12 +139,22 @@ inline uint16_t mcu_adc_result() { return ADC0.RES; } -inline uint16_t mcu_vdd_raw2cooked(uint16_t measurement) { +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 @@ -164,29 +176,10 @@ inline uint16_t mcu_temp_raw2cooked(uint16_t measurement) { } inline uint8_t mcu_adc_lsb() { - // temp is right-aligned, voltage is 16-bit, both have a useful LSB + // volts and temp are both 16-bit, so the LSB is useful as-is return ADC0_RESL; } -#ifdef USE_VOLTAGE_VDD -uint8_t calc_voltage(uint16_t measurement) { - // calculate actual voltage: volts * 10 - // FIXME - // ADC = 1.1 * 1024 / volts - // volts = 1.1 * 1024 / ADC - result = ((uint16_t)(2*1.1*1024*10)/(measurement>>6) - + VOLTAGE_FUDGE_FACTOR - #ifdef USE_VOLTAGE_CORRECTION - + VOLT_CORR - 7 - #endif - ) >> 1; - return result; -} -#elif defined(USE_VOLTAGE_VDDIO2) -#elif defined(USE_VOLTAGE_VDDIO2) -#else -// hwdef must supply its own function -#endif ////////// WDT ////////// // this uses the RTC PIT interrupt instead of WDT, diff --git a/arch/avr32dd20.h b/arch/avr32dd20.h index 3ebb05a..82951de 100644 --- a/arch/avr32dd20.h +++ b/arch/avr32dd20.h @@ -58,9 +58,9 @@ inline uint16_t mcu_adc_result(); //inline uint16_t mcu_adc_result_temp(); //inline uint16_t mcu_adc_result_volts(); -// return (centiVolts << 6), range 0 to 10.24V +// return Volts * 40, range 0 to 6.375V #define voltage_raw2cooked mcu_vdd_raw2cooked -inline uint16_t mcu_vdd_raw2cooked(uint16_t measurement); +inline uint8_t mcu_vdd_raw2cooked(uint16_t measurement); // return (temp in Kelvin << 6) #define temp_raw2cooked mcu_temp_raw2cooked -- cgit v1.2.3 From 8c237206aba74f9096d85f90209ac6b7dc238b1b Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Fri, 24 Nov 2023 06:29:56 -0700 Subject: more ADC / DAC / MCU progress... - fixed t1616 Vref values getting clobbered sometimes, wrapped setting those in a #define'd function for ease and consistency - moved some DAC definitions from hw/ to arch/ to reduce repetition - fixed thefreeman's other builds - switched from PWM_TOPS to PWM2_LEVELS (I'm trying to phase out _TOPS) --- arch/attiny1616.c | 9 +++------ arch/attiny1616.h | 24 ++++++++++++++++++++++++ arch/avr32dd20.h | 13 +++++++++++++ 3 files changed, 40 insertions(+), 6 deletions(-) (limited to 'arch') diff --git a/arch/attiny1616.c b/arch/attiny1616.c index 330c809..2770d06 100644 --- a/arch/attiny1616.c +++ b/arch/attiny1616.c @@ -29,8 +29,7 @@ inline void clock_prescale_set(uint8_t n) { inline void mcu_set_admux_therm() { // put the ADC in temperature mode // attiny1616 datasheet section 30.3.2.6 - VREF.CTRLA = (VREF.CTRLA & (~VREF_ADC0REFSEL_gm)) - | VREF_ADC0REFSEL_1V1_gc; // Set Vbg ref to 1.1V + 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 @@ -46,8 +45,7 @@ inline void mcu_set_admux_voltage() { ADC0.CTRLD |= ADC_INITDLY_DLY16_gc; #ifdef USE_VOLTAGE_DIVIDER // measure an arbitrary pin // result = resolution * Vdiv / 1.1V - VREF.CTRLA = (VREF.CTRLA & (~VREF_ADC0REFSEL_gm)) - | VREF_ADC0REFSEL_1V1_gc; // Set Vbg ref to 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 @@ -55,8 +53,7 @@ inline void mcu_set_admux_voltage() { | ADC_REFSEL_INTREF_gc; // Use internal ADC reference #else // measure VDD pin // result = resolution * 1.5V / Vbat - VREF.CTRLA = (VREF.CTRLA & (~VREF_ADC0REFSEL_gm)) - | VREF_ADC0REFSEL_1V5_gc; // Set Vbg ref to 1.5V + 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 diff --git a/arch/attiny1616.h b/arch/attiny1616.h index 57b0023..940973e 100644 --- a/arch/attiny1616.h +++ b/arch/attiny1616.h @@ -18,6 +18,8 @@ inline void mcu_clock_speed(); // 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 @@ -34,8 +36,30 @@ typedef enum } 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(); diff --git a/arch/avr32dd20.h b/arch/avr32dd20.h index 82951de..09b4096 100644 --- a/arch/avr32dd20.h +++ b/arch/avr32dd20.h @@ -30,6 +30,19 @@ typedef enum } 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 -- cgit v1.2.3 From 1b6f40863b4f08365e39236496d687ab7806ed11 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Mon, 27 Nov 2023 07:31:56 -0700 Subject: fixed ADC on attiny1634 and related builds --- arch/attiny1634.c | 129 ++++++++++++++++++++++++++++++++++++++++++++---------- arch/attiny1634.h | 40 +++++++++++------ 2 files changed, 132 insertions(+), 37 deletions(-) (limited to 'arch') diff --git a/arch/attiny1634.c b/arch/attiny1634.c index 0737a81..e29d1c3 100644 --- a/arch/attiny1634.c +++ b/arch/attiny1634.c @@ -7,14 +7,26 @@ ////////// 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 -// 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(); + // _PROTECTED_WRITE(CLKPR, n); CCP = 0xD8; CLKPR = n; sei(); @@ -26,15 +38,33 @@ inline void clock_prescale_set(uint8_t n) { ////////// 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() { @@ -42,31 +72,82 @@ inline void mcu_adc_sleep_mode() { } inline void mcu_adc_start_measurement() { - ADCSRA |= (1 << ADSC) | (1 << ADIE); -} - -inline void mcu_adc_on() { - hwdef_set_admux_voltage(); - #ifdef USE_VOLTAGE_DIVIDER - // disable digital input on divider pin to reduce power consumption - VOLTAGE_ADC_DIDR |= (1 << VOLTAGE_ADC); - #else - // 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 |= (1 << ADLAR); // left-adjust flag is here instead of ADMUX - // enable, start, auto-retrigger, prescale - ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | ADC_PRSCL; + // [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<> 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; +} -inline uint8_t mcu_adc_lsb() { return (ADCL >> 6) + (ADCH << 2); } + +#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 ////////// @@ -85,6 +166,7 @@ inline void mcu_wdt_stop() { cli(); // needed because CCP, below wdt_reset(); // Reset the WDT MCUSR &= ~(1<> 6) + (ADCH << 2); } +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 ////////// diff --git a/arch/attiny85.h b/arch/attiny85.h index 2753fd7..3f6ffcb 100644 --- a/arch/attiny85.h +++ b/arch/attiny85.h @@ -21,8 +21,6 @@ ////////// 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(); #endif @@ -48,8 +46,6 @@ inline void mcu_adc_sleep_mode(); inline void mcu_adc_start_measurement(); -inline void mcu_adc_on(); - inline void mcu_adc_off(); // NOP because interrupt flag clears itself @@ -57,6 +53,15 @@ inline void mcu_adc_off(); 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(); -- cgit v1.2.3 From 3fde090a8e55163288148b37e172b11cdeb3cc50 Mon Sep 17 00:00:00 2001 From: Selene ToyKeeper Date: Thu, 30 Nov 2023 09:17:46 -0700 Subject: eliminated direct CCP register access from arch/attiny1616 (the protected write macro exists for a reason, and should be used instead) --- arch/attiny1616.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'arch') diff --git a/arch/attiny1616.c b/arch/attiny1616.c index 2770d06..c5499dd 100644 --- a/arch/attiny1616.c +++ b/arch/attiny1616.c @@ -210,8 +210,8 @@ inline void mcu_pcint_off() { void reboot() { // put the WDT in hard reset mode, then trigger it cli(); - CCP = CCP_IOREG_gc; // temporarily disable change protection - WDT.CTRLA = WDT_PERIOD_8CLK_gc; // Enable, timeout 8ms + // Enable, timeout 8ms + _PROTECTED_WRITE(WDT.CTRLA, WDT_PERIOD_8CLK_gc); sei(); wdt_reset(); while (1) {} -- cgit v1.2.3