diff options
-rw-r--r-- | include/arch/lora32u4ii/driver/adc.h | 18 | ||||
-rw-r--r-- | include/arch/lora32u4ii/driver/gpio.h | 11 | ||||
-rw-r--r-- | src/arch/lora32u4ii/Kconfig | 19 | ||||
-rw-r--r-- | src/arch/lora32u4ii/Makefile.inc | 8 | ||||
-rw-r--r-- | src/arch/lora32u4ii/driver/adc.cc | 121 |
5 files changed, 176 insertions, 1 deletions
diff --git a/include/arch/lora32u4ii/driver/adc.h b/include/arch/lora32u4ii/driver/adc.h new file mode 100644 index 0000000..5f9189a --- /dev/null +++ b/include/arch/lora32u4ii/driver/adc.h @@ -0,0 +1,18 @@ +#ifndef ADC_H +#define ADC_H + +class AVRADC { + private: + AVRADC(AVRADC const ©); + + public: + AVRADC() {} + + int16_t getTemp_mdegC(int16_t offset = 205); + uint16_t getVCC_mV(); + uint16_t getVBat_mV(bool controlCharger); +}; + +extern AVRADC adc; + +#endif diff --git a/include/arch/lora32u4ii/driver/gpio.h b/include/arch/lora32u4ii/driver/gpio.h index ba044d0..61dc867 100644 --- a/include/arch/lora32u4ii/driver/gpio.h +++ b/include/arch/lora32u4ii/driver/gpio.h @@ -3,6 +3,14 @@ #include <avr/io.h> +/* + * lora32u4ii v1.3 pin map: + * + * PB0 -> Charger and Vbat/2 measurement voltage divider enable + * PB5 -> User LED + * PB5 <- Vbat/2 + */ + class GPIO { private: GPIO(const GPIO ©); @@ -40,7 +48,8 @@ class GPIO { }; inline void setup() { - DDRB = _BV(PB5); + // PB5 is both output (user LED) and input (Vbat/2 to ADC). + // Leave it as input by default. } inline volatile uint8_t * pinToPort(uint8_t pin) { if (pin <= pb7) { diff --git a/src/arch/lora32u4ii/Kconfig b/src/arch/lora32u4ii/Kconfig new file mode 100644 index 0000000..eb85780 --- /dev/null +++ b/src/arch/lora32u4ii/Kconfig @@ -0,0 +1,19 @@ +# Copyright 2021 Daniel Friesel +# +# SPDX-License-Identifier: CC0-1.0 + +config arch_lora32u4ii_cpufreq +int "CPU Frequency" +#!accept [62500, 125000, 250000, 500000, 1000000, 2000000, 4000000, 8000000] +range 62500 8000000 +default 8000000 +help + Assumes an externel 8MHz crystal to be present + +config arch_lora32u4ii_driver_adc +bool "ADC (Analog-Digital-Converter)" +select meta_driver_adc + +config arch_lora32u4ii_driver_uptime +bool "Uptime Counter" +select meta_driver_uptime diff --git a/src/arch/lora32u4ii/Makefile.inc b/src/arch/lora32u4ii/Makefile.inc index 06ac1f9..3aeefa8 100644 --- a/src/arch/lora32u4ii/Makefile.inc +++ b/src/arch/lora32u4ii/Makefile.inc @@ -44,8 +44,16 @@ ifeq (${timer_s}, 1) CONFIG_arch_lora32u4ii_driver_uptime = y endif +ifneq ($(findstring adc,${arch_drivers}), ) + CONFIG_arch_lora32u4ii_driver_adc = y +endif + # Kconfig driver selection +ifdef CONFIG_arch_lora32u4ii_driver_adc + CXX_TARGETS += src/arch/lora32u4ii/driver/adc.cc +endif + ifdef CONFIG_arch_lora32u4ii_driver_uptime COMMON_FLAGS += -DTIMER_S CXX_TARGETS += src/arch/lora32u4ii/driver/uptime.cc diff --git a/src/arch/lora32u4ii/driver/adc.cc b/src/arch/lora32u4ii/driver/adc.cc new file mode 100644 index 0000000..5dd5d4d --- /dev/null +++ b/src/arch/lora32u4ii/driver/adc.cc @@ -0,0 +1,121 @@ +#include <avr/io.h> + +#include "arch.h" +#include "driver/adc.h" +#include "driver/gpio.h" + +int16_t AVRADC::getTemp_mdegC(int16_t offset) +{ + // Measure temperature probe with internal 2.56V bandgap reference + ADMUX = _BV(REFS1) | _BV(REFS0) | 0x07; + ADCSRB = _BV(MUX5); + + // Enable ADC with /64 prescaler + ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1); + + // Wait for bandgap and temperature references to stabilise + arch.delay_ms(1); + + // Start conversion + ADCSRA |= _BV(ADSC); + + // Wait for conversion to complete + while (ADCSRA & _BV(ADSC)) ; + + // TODO this is for atmega328p, not atmega32u4 + // typical values: 242 mV @ -45 degC + // typical values: 314 mV @ +25 degC + // typical values: 380 mV @ +85 degC + // slope: 0.9090.. degC / mV at 25 .. 85 degC + // -> approx. 286.5 mV @ 0 degC / approx -260.45 degC @ 0 mV + // -> T[degC] = ADC[mV] * 0.91 - 261 + // -> T[mdegC] = ADC[mV] * 91 - 26100 + // slope: 0.9722.. mV / degC at -45 .. 25 degC + // slope: 0.942 mV / degC at -45 .. 85 degC + uint8_t adcr_l = ADCL; + uint8_t adcr_h = ADCH; + uint16_t adcr = adcr_l + (adcr_h << 8); + uint16_t vadc = 1100L * adcr / 1023L; + + // adjust for chip-specific variations + vadc += offset; + + int16_t temp_mdegc = vadc * 91 - 26100L; + + // Disable ADC + ADCSRA &= ~_BV(ADEN); + + return temp_mdegc; +} + +uint16_t AVRADC::getVCC_mV() +{ + // Measure internal 1.1V bandgap using VCC as reference + ADMUX = _BV(REFS0) | 0x1e; + ADCSRB = 0; + + // Enable ADC with /64 prescaler + ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1); + + // Wait for bandgap to stabilise + arch.delay_ms(1); + + // Start conversion + ADCSRA |= _BV(ADSC); + + // Wait for conversion to complete + while (ADCSRA & _BV(ADSC)) ; + + uint8_t adcr_l = ADCL; + uint8_t adcr_h = ADCH; + uint16_t adcr = adcr_l + (adcr_h << 8); + uint16_t vcc = 1100L * 1023 / adcr; + + // Disable ADC + ADCSRA &= ~_BV(ADEN); + + return vcc; +} + +uint16_t AVRADC::getVBat_mV(bool controlCharger) +{ + // Measure VBat/2 (via voltage divider on PB5 / ADC12) using VCC as reference. + // Caution: PB5 is also connected to the white user LED. + ADMUX = _BV(REFS0) | 0x04; + ADCSRB = _BV(MUX5); + + // Enable ADC with /64 prescaler + ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADPS1); + + if (controlCharger) { + // Turn on Vbat/2 voltage divider (and charger!) + gpio.output(GPIO::pb0, 1); + } + + // Wait for things to stabilise + arch.delay_ms(1); + + // Start conversion + ADCSRA |= _BV(ADSC); + + // Wait for conversion to complete + while (ADCSRA & _BV(ADSC)) ; + + uint8_t adcr_l = ADCL; + uint8_t adcr_h = ADCH; + uint16_t adcr = adcr_l + (adcr_h << 8); + uint16_t vbat = 4200L * adcr / 1023; + + // Disable ADC + ADCSRA &= ~_BV(ADEN); + + if (controlCharger) { + // Turn off voltage divider (and charger!) + gpio.output(GPIO::pb0, 0); + } + + return vbat; +} + + +AVRADC adc; |