diff --git a/Makefile b/Makefile index 15d79641..ac3d4a8b 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ include $(GOSSAMER_PATH)/make.mk SRCS += \ ./app.c \ ./watch-library/hardware/watch/watch.c \ + ./watch-library/hardware/watch/watch_adc.c \ ./watch-library/hardware/watch/watch_buzzer.c \ ./watch-library/hardware/watch/watch_extint.c \ ./watch-library/hardware/watch/watch_led.c \ diff --git a/watch-library/hardware/watch/watch_adc.c b/watch-library/hardware/watch/watch_adc.c new file mode 100644 index 00000000..11cc318c --- /dev/null +++ b/watch-library/hardware/watch/watch_adc.c @@ -0,0 +1,94 @@ +/* + * MIT License + * + * Copyright (c) 2020 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "watch_adc.h" +#include "adc.h" + +void watch_enable_adc(void) { + adc_init(); + adc_enable(); +} + +void watch_enable_analog_input(const uint16_t port_pin) { + uint8_t port = port_pin >> 8; + uint16_t pin = port_pin & 0xF; + + PORT->Group[port].DIRCLR.reg = (1 << pin); + PORT->Group[port].PINCFG[pin].reg |= PORT_PINCFG_INEN; + PORT->Group[port].PINCFG[pin].reg &= ~PORT_PINCFG_PULLEN; + PORT->Group[port].PINCFG[pin].reg |= PORT_PINCFG_PMUXEN; + if (pin & 1) { + PORT->Group[port].PMUX[pin>>1].bit.PMUXO = HAL_GPIO_PMUX_ADC; + } else { + PORT->Group[port].PMUX[pin>>1].bit.PMUXE = HAL_GPIO_PMUX_ADC; + } +} + +uint16_t watch_get_analog_pin_level(const uint16_t pin) { + return adc_get_analog_value(pin); +} + +/// TODO: put reference voltage stuff into gossamer? +void _watch_set_analog_reference_voltage(uint8_t reference); +void _watch_set_analog_reference_voltage(uint8_t reference) { + ADC->CTRLA.bit.ENABLE = 0; + + if (reference == ADC_REFCTRL_REFSEL_INTREF_Val) SUPC->VREF.bit.VREFOE = 1; + else SUPC->VREF.bit.VREFOE = 0; + + ADC->REFCTRL.bit.REFSEL = reference; + ADC->CTRLA.bit.ENABLE = 1; + while (ADC->SYNCBUSY.reg); + // throw away one measurement after reference change (the channel doesn't matter). + adc_get_analog_value_for_channel(ADC_INPUTCTRL_MUXPOS_SCALEDCOREVCC); +} + +uint16_t watch_get_vcc_voltage(void) { + // stash the previous reference so we can restore it when we're done. + uint8_t oldref = ADC->REFCTRL.bit.REFSEL; + + // if we weren't already using the internal reference voltage, select it now. + if (oldref != ADC_REFCTRL_REFSEL_INTREF_Val) _watch_set_analog_reference_voltage(ADC_REFCTRL_REFSEL_INTREF_Val); + + // get the data + uint32_t raw_val = adc_get_analog_value_for_channel(ADC_INPUTCTRL_MUXPOS_SCALEDIOVCC_Val); + + // restore the old reference, if needed. + if (oldref != ADC_REFCTRL_REFSEL_INTREF_Val) _watch_set_analog_reference_voltage(oldref); + + return (uint16_t)((raw_val * 1000) / (1024 * 1 << ADC->AVGCTRL.bit.SAMPLENUM)); +} + +inline void watch_disable_analog_input(const uint16_t port_pin) { + uint8_t port = port_pin >> 8; + uint16_t pin = port_pin & 0xF; + + PORT->Group[port].DIRSET.reg = (1 << pin); \ + PORT->Group[port].PINCFG[pin].reg &= ~(PORT_PINCFG_PULLEN | PORT_PINCFG_INEN); \ + PORT->Group[port].PINCFG[pin].reg &= ~PORT_PINCFG_PMUXEN; \ +} + +inline void watch_disable_adc(void) { + adc_disable(); +} diff --git a/watch-library/hardware/watch/watch_private.c b/watch-library/hardware/watch/watch_private.c index 71461ce2..fbba4059 100644 --- a/watch-library/hardware/watch/watch_private.c +++ b/watch-library/hardware/watch/watch_private.c @@ -23,6 +23,8 @@ */ #include "watch_private.h" +#include "watch_adc.h" +#include "adc.h" #include "tcc.h" #include "tc.h" #include "usb.h" @@ -45,7 +47,6 @@ void _watch_init(void) { SUPC->VREG.bit.STDBYPL0 = 1; while(!SUPC->STATUS.bit.VREGRDY); // wait for voltage regulator to become ready - /** TODO: check the battery voltage... watch_enable_adc(); uint16_t battery_voltage = watch_get_vcc_voltage(); watch_disable_adc(); @@ -56,7 +57,6 @@ void _watch_init(void) { } else { SUPC->VREG.bit.LPEFF = 0; } - */ // set up the brownout detector (low battery warning) NVIC_DisableIRQ(SYSTEM_IRQn); diff --git a/watch-library/shared/watch/watch_adc.h b/watch-library/shared/watch/watch_adc.h new file mode 100644 index 00000000..b2685da9 --- /dev/null +++ b/watch-library/shared/watch/watch_adc.h @@ -0,0 +1,79 @@ +/* + * MIT License + * + * Copyright (c) 2020 Joey Castillo + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#pragma once + +////< @file watch_adc.h + +#include "watch.h" + +/** @addtogroup adc Analog Input + * @brief This section covers functions related to the SAM L22's analog-to-digital converter, + * as well as configuring and reading values from the five analog-capable pins on the + * 9-pin connector. + */ +/// @{ +/** @brief Enables the ADC peripheral. You must call this before attempting to read a value + * from an analog pin. + */ +void watch_enable_adc(void); + +/** @brief Configures the selected pin for analog input. + * @param pin One of the analog pins, access using the HAL_GPIO_Ax_pin() macro. + */ +void watch_enable_analog_input(const uint16_t pin); + +/** @brief Reads an analog value from one of the pins. + * @param pin One of the analog pins, access using the HAL_GPIO_Ax_pin() macro. + * @return a 16-bit unsigned integer from 0-65535 representing the sampled value, unless you + * have changed the number of samples. @see watch_set_num_analog_samples for details + * on how that function changes the values returned from this one. + **/ +uint16_t watch_get_analog_pin_level(const uint16_t pin); + +/** @brief Returns the voltage of the VCC supply in millivolts (i.e. 3000 mV == 3.0 V). If running on + * a coin cell, this will be the battery voltage. + * @details Unlike other ADC functions, this function does not return a raw value from the ADC, but + * rather scales it to an actual number of millivolts. This is because the ADC doesn't let + * us measure VCC per se; it instead lets us measure VCC / 4, and we choose to measure it + * against the internal reference voltage of 1.024 V. In short, the ADC gives us a number + * that's complicated to deal with, so we just turn it into a useful number for you :) + * @note This function depends on INTREF being 1.024V. If you have changed it by poking at the supply + * controller's VREF.SEL bits, this function will return inaccurate values. + */ +uint16_t watch_get_vcc_voltage(void); + +/** @brief Disables the analog circuitry on the selected pin. + * @param pin One of pins A0-A4. + */ +void watch_disable_analog_input(const uint16_t pin); + +/** @brief Disables the ADC peripheral. + * @note You will need to call watch_enable_adc to re-enable the ADC peripheral. When you do, it will + * have the default settings of 16 samples and 1 measurement cycle; if you customized these + * parameters, you will need to set them up again. + **/ +void watch_disable_adc(void); + +/// @}