movement: add voltage monitor watch face
This commit is contained in:
parent
88f41b12fc
commit
0f03257ee9
@ -33,6 +33,7 @@ SRCS += \
|
|||||||
../watch_faces/thermistor/thermistor_readout_face.c \
|
../watch_faces/thermistor/thermistor_readout_face.c \
|
||||||
../watch_faces/thermistor/thermistor_logging_face.c \
|
../watch_faces/thermistor/thermistor_logging_face.c \
|
||||||
../watch_faces/demos/character_set_face.c \
|
../watch_faces/demos/character_set_face.c \
|
||||||
|
../watch_faces/demos/voltage_face.c \
|
||||||
../watch_faces/complications/beats_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.
|
# Leave this line at the bottom of the file; it has all the targets for making your project.
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
#include "thermistor_logging_face.h"
|
#include "thermistor_logging_face.h"
|
||||||
#include "character_set_face.h"
|
#include "character_set_face.h"
|
||||||
#include "beats_face.h"
|
#include "beats_face.h"
|
||||||
|
#include "voltage_face.h"
|
||||||
|
|
||||||
const watch_face_t watch_faces[] = {
|
const watch_face_t watch_faces[] = {
|
||||||
simple_clock_face,
|
simple_clock_face,
|
||||||
|
66
movement/watch_faces/demos/voltage_face.c
Normal file
66
movement/watch_faces/demos/voltage_face.c
Normal file
@ -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();
|
||||||
|
}
|
19
movement/watch_faces/demos/voltage_face.h
Normal file
19
movement/watch_faces/demos/voltage_face.h
Normal file
@ -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_
|
@ -138,6 +138,35 @@ void watch_set_analog_sampling_length(uint8_t cycles) {
|
|||||||
_watch_sync_adc();
|
_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) {
|
inline void watch_disable_analog_input(const uint8_t pin) {
|
||||||
gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF);
|
gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF);
|
||||||
}
|
}
|
||||||
|
@ -94,6 +94,53 @@ void watch_set_analog_num_samples(uint16_t samples);
|
|||||||
**/
|
**/
|
||||||
void watch_set_analog_sampling_length(uint8_t cycles);
|
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.
|
/** @brief Disables the analog circuitry on the selected pin.
|
||||||
* @param pin One of pins A0-A4.
|
* @param pin One of pins A0-A4.
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user