diff options
author | Joey Castillo <jose.castillo@gmail.com> | 2021-11-06 23:52:00 -0400 |
---|---|---|
committer | Joey Castillo <jose.castillo@gmail.com> | 2021-11-06 23:52:00 -0400 |
commit | 0f03257ee9ef46ac61e20f7201e2c50dedff01b2 (patch) | |
tree | efb1d09dc3abaaa8211d130c3c068e14cb8ca4c2 | |
parent | 88f41b12fc99542e2ace7b63651971e5907cfd1d (diff) | |
download | Sensor-Watch-0f03257ee9ef46ac61e20f7201e2c50dedff01b2.tar.gz Sensor-Watch-0f03257ee9ef46ac61e20f7201e2c50dedff01b2.tar.bz2 Sensor-Watch-0f03257ee9ef46ac61e20f7201e2c50dedff01b2.zip |
movement: add voltage monitor watch face
-rwxr-xr-x | movement/make/Makefile | 1 | ||||
-rw-r--r-- | movement/movement_config.h | 1 | ||||
-rw-r--r-- | movement/watch_faces/demos/voltage_face.c | 66 | ||||
-rw-r--r-- | movement/watch_faces/demos/voltage_face.h | 19 | ||||
-rw-r--r-- | watch-library/watch/watch_adc.c | 29 | ||||
-rw-r--r-- | watch-library/watch/watch_adc.h | 47 |
6 files changed, 163 insertions, 0 deletions
diff --git a/movement/make/Makefile b/movement/make/Makefile index a937222b..89e3161a 100755 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -33,6 +33,7 @@ SRCS += \ ../watch_faces/thermistor/thermistor_readout_face.c \ ../watch_faces/thermistor/thermistor_logging_face.c \ ../watch_faces/demos/character_set_face.c \ + ../watch_faces/demos/voltage_face.c \ ../watch_faces/complications/beats_face.c \ # Leave this line at the bottom of the file; it has all the targets for making your project. diff --git a/movement/movement_config.h b/movement/movement_config.h index 3e911477..9629a570 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -9,6 +9,7 @@ #include "thermistor_logging_face.h" #include "character_set_face.h" #include "beats_face.h" +#include "voltage_face.h" const watch_face_t watch_faces[] = { simple_clock_face, diff --git a/movement/watch_faces/demos/voltage_face.c b/movement/watch_faces/demos/voltage_face.c new file mode 100644 index 00000000..98b22c76 --- /dev/null +++ b/movement/watch_faces/demos/voltage_face.c @@ -0,0 +1,66 @@ +#include <stdlib.h> +#include <string.h> +#include "voltage_face.h" +#include "watch.h" + +void _voltage_face_update_display() { + char buf[14]; + float voltage = (float)watch_get_vcc_voltage() / 1000.0; + sprintf(buf, "BA %4.2f V", voltage); + // printf("%s\n", buf); + watch_display_string(buf, 0); +} + +void voltage_face_setup(movement_settings_t *settings, void ** context_ptr) { + (void) settings; + (void) context_ptr; +} + +void voltage_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + watch_enable_adc(); + // if we set the reference voltage here, watch_get_vcc_voltage won't do it over and over + watch_set_analog_reference_voltage(ADC_REFERENCE_INTREF); +} + +bool voltage_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + watch_date_time date_time; + switch (event.event_type) { + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + break; + case EVENT_LIGHT_BUTTON_DOWN: + movement_illuminate_led(); + break; + case EVENT_ACTIVATE: + _voltage_face_update_display(); + break; + case EVENT_TICK: + date_time = watch_rtc_get_date_time(); + if (date_time.unit.second % 5 == 4) { + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + } else if (date_time.unit.second % 5 == 0) { + _voltage_face_update_display(); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + } + break; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + default: + break; + } + + return true; +} + +void voltage_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + // make sure to restore the default in the end. + watch_set_analog_reference_voltage(ADC_REFCTRL_REFSEL_INTVCC2_Val); + watch_disable_adc(); +} diff --git a/movement/watch_faces/demos/voltage_face.h b/movement/watch_faces/demos/voltage_face.h new file mode 100644 index 00000000..da580da5 --- /dev/null +++ b/movement/watch_faces/demos/voltage_face.h @@ -0,0 +1,19 @@ +#ifndef VOLTAGE_FACE_H_ +#define VOLTAGE_FACE_H_ + +#include "movement.h" + +void voltage_face_setup(movement_settings_t *settings, void ** context_ptr); +void voltage_face_activate(movement_settings_t *settings, void *context); +bool voltage_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void voltage_face_resign(movement_settings_t *settings, void *context); + +static const watch_face_t voltage_face = { + voltage_face_setup, + voltage_face_activate, + voltage_face_loop, + voltage_face_resign, + NULL +}; + +#endif // VOLTAGE_FACE_H_
\ No newline at end of file diff --git a/watch-library/watch/watch_adc.c b/watch-library/watch/watch_adc.c index 90980a88..4aff86e6 100644 --- a/watch-library/watch/watch_adc.c +++ b/watch-library/watch/watch_adc.c @@ -138,6 +138,35 @@ void watch_set_analog_sampling_length(uint8_t cycles) { _watch_sync_adc(); } +void watch_set_analog_reference_voltage(watch_adc_reference_voltage reference) { + ADC->CTRLA.bit.ENABLE = 0; + + if (reference == ADC_REFERENCE_INTREF) SUPC->VREF.bit.VREFOE = 1; + else SUPC->VREF.bit.VREFOE = 0; + + ADC->REFCTRL.bit.REFSEL = reference; + ADC->CTRLA.bit.ENABLE = 1; + _watch_sync_adc(); + // throw away one measurement after reference change (the channel doesn't matter). + _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_SCALEDCOREVCC); +} + +uint16_t watch_get_vcc_voltage() { + // 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_REFERENCE_INTREF) watch_set_analog_reference_voltage(ADC_REFERENCE_INTREF); + + // get the data + uint32_t raw_val = _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_SCALEDIOVCC_Val); + + // restore the old reference, if needed. + if (oldref != ADC_REFERENCE_INTREF) 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 uint8_t pin) { gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF); } diff --git a/watch-library/watch/watch_adc.h b/watch-library/watch/watch_adc.h index c9b6ad2a..a4f9edad 100644 --- a/watch-library/watch/watch_adc.h +++ b/watch-library/watch/watch_adc.h @@ -94,6 +94,53 @@ void watch_set_analog_num_samples(uint16_t samples); **/ void watch_set_analog_sampling_length(uint8_t cycles); +typedef enum { + ADC_REFERENCE_INTREF = ADC_REFCTRL_REFSEL_INTREF_Val, + ADC_REFERENCE_VCC_DIV1POINT6 = ADC_REFCTRL_REFSEL_INTVCC0_Val, + ADC_REFERENCE_VCC_DIV2 = ADC_REFCTRL_REFSEL_INTVCC1_Val, + ADC_REFERENCE_VCC = ADC_REFCTRL_REFSEL_INTVCC2_Val, +} watch_adc_reference_voltage; + + +/** @brief Selects the reference voltage to use for analog readings. Default is ADC_REFERENCE_VCC. + * @param reference One of ADC_REFERENCE_VCC, ADC_REFERENCE_VCC_DIV1POINT6, ADC_REFERENCE_VCC_DIV2 + * or ADC_REFERENCE_INTREF. + * @details In order to turn an analog voltage into a 16-bit integer, the ADC needs to compare the + * measured voltage to a reference point. For example, if you were powering the watch with + * VCC == 3.0V and you had two 10K resistors connected in series from 3V to GND, you could + * expect to get 3 volts when you measure the top of the voltage divider, 0 volts at the + * bottom, and 1.5 volts in the middle. If you read these values uising a reference voltage + * of ADC_REFERENCE_VCC, the top value would be about 65535, the bottom about 0, and the + * middle about 32768. However! If we used ADC_REFERENCE_VCC_DIV2 as our reference, we would + * expect to get 65535 both at the top and the middle, because the largest value the ADC can + * measure in this configutation is 1.5V (VCC / 2). + * + * By changing the reference voltage from ADC_REFERENCE_VCC to ADC_REFERENCE_VCC_DIV1POINT6 + * or ADC_REFERENCE_VCC_DIV2, you can get more resolution when measuring small voltages (i.e. + * a phototransistor circuit in low light). + * + * There is also a special reference voltage called ADC_REFERENCE_INTREF. The SAM L22's + * Supply Controller provides a selectable voltage reference (by default, 1.024 V) that you + * can select as a reference voltage for ADC conversions. Unlike the three references we + * talked about in the last paragraph, this reference voltage does not depend on VCC, which + * makes it very useful for measuring the battery voltage (since you can't really compare + * VCC to itself). You can change the INTREF voltage to 2.048 or 4.096 V by poking at the + * supply controller's VREF register, but the watch library does not support this use case. + **/ +void watch_set_analog_reference_voltage(watch_adc_reference_voltage reference); + +/** @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(); + /** @brief Disables the analog circuitry on the selected pin. * @param pin One of pins A0-A4. */ |