]> git.earman.xyz Git - sensor-watch.git/commitdiff
movement: add voltage monitor watch face
authorJoey Castillo <jose.castillo@gmail.com>
Sun, 7 Nov 2021 03:52:00 +0000 (23:52 -0400)
committerJoey Castillo <jose.castillo@gmail.com>
Sun, 7 Nov 2021 03:52:00 +0000 (23:52 -0400)
movement/make/Makefile
movement/movement_config.h
movement/watch_faces/demos/voltage_face.c [new file with mode: 0644]
movement/watch_faces/demos/voltage_face.h [new file with mode: 0644]
watch-library/watch/watch_adc.c
watch-library/watch/watch_adc.h

index a937222b7235d98a3794211563a4a95b36b5c5a1..89e3161a96274c108894ae431ba959b164c34ec4 100755 (executable)
@@ -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.
index 3e9114776016bf71c2979ba36912f44c795c48e8..9629a57037b60305a1edbb64ee78b283a1743d3f 100644 (file)
@@ -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 (file)
index 0000000..98b22c7
--- /dev/null
@@ -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 (file)
index 0000000..da580da
--- /dev/null
@@ -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
index 90980a88eccd5f0fabdfc39e06f9524e57e55c52..4aff86e66eefba44dd213afc85d658f7aac13f25 100644 (file)
@@ -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);
 }
index c9b6ad2a7f339a0f0f84391a5052ea8c7a596f3a..a4f9edad198e69e777d23c9af8af99027e0c4835 100644 (file)
@@ -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.
   */