From b88cd0cd7e058679960adaf89818ed755f6e71a3 Mon Sep 17 00:00:00 2001 From: Joey Castillo Date: Thu, 21 Oct 2021 11:02:44 -0400 Subject: movement: add thermistor readout face --- movement/make/Makefile | 3 + movement/movement.h | 10 ++- movement/movement_config.h | 1 + .../watch_faces/thermistor/thermistor_driver.c | 76 ++++++++++++++++++++++ .../watch_faces/thermistor/thermistor_driver.h | 13 ++++ .../thermistor/thermistor_readout_face.c | 73 +++++++++++++++++++++ .../thermistor/thermistor_readout_face.h | 19 ++++++ 7 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 movement/watch_faces/thermistor/thermistor_driver.c create mode 100644 movement/watch_faces/thermistor/thermistor_driver.h create mode 100644 movement/watch_faces/thermistor/thermistor_readout_face.c create mode 100644 movement/watch_faces/thermistor/thermistor_readout_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index f17fc7e5..2ed06d96 100755 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -14,6 +14,7 @@ INCLUDES += \ -I../watch_faces/clock/ \ -I../watch_faces/settings/ \ -I../watch_faces/complications/ \ + -I../watch_faces/thermistor/ \ # If you add any other source files you wish to compile, add them after ../app.c # Note that you will need to add a backslash at the end of any line you wish to continue, i.e. @@ -27,6 +28,8 @@ SRCS += \ ../watch_faces/settings/preferences_face.c \ ../watch_faces/settings/set_time_face.c \ ../watch_faces/complications/pulsometer_face.c \ + ../watch_faces/thermistor/thermistor_driver.c \ + ../watch_faces/thermistor/thermistor_readout_face.c \ # Leave this line at the bottom of the file; it has all the targets for making your project. include $(TOP)/rules.mk diff --git a/movement/movement.h b/movement/movement.h index 41446a9b..e8bf9247 100644 --- a/movement/movement.h +++ b/movement/movement.h @@ -6,14 +6,20 @@ // TODO: none of this is implemented typedef union { struct { - uint32_t reserved : 15; - uint32_t clock_mode_24h : 1; // determines whether clock should use 12 or 24 hour mode. + uint32_t reserved : 14; uint32_t button_should_sound : 1; // if true, pressing a button emits a sound. uint32_t to_interval : 2; // an inactivity interval for asking the active face to resign. uint32_t le_interval : 3; // 0 to disable low energy mode, or an inactivity interval for going into low energy mode. uint32_t led_duration : 2; // how many seconds to shine the LED for (x2), or 0 to disable it. uint32_t led_red_color : 4; // for general purpose illumination, the red LED value (0-15) uint32_t led_green_color : 4; // for general purpose illumination, the green LED value (0-15) + + // while Movement itself doesn't implement a clock or display units, it may make sense to include some + // global settings for watch faces to check. The 12/24 hour preference could inform a clock or a + // time-oriented complication like a sunrise/sunset timer, and a simple locale preference could tell an + // altimeter to display feet or meters as easily as it tells a thermometer to display degrees in F or C. + uint32_t clock_mode_24h : 1; // indicates whether clock should use 12 or 24 hour mode. + uint32_t use_imperial_units : 1; // indicates whether to use metric units (the default) or imperial. } bit; uint32_t value; } movement_settings_t; diff --git a/movement/movement_config.h b/movement/movement_config.h index 6a7aec5a..92539a3f 100644 --- a/movement/movement_config.h +++ b/movement/movement_config.h @@ -5,6 +5,7 @@ #include "preferences_face.h" #include "set_time_face.h" #include "pulsometer_face.h" +#include "thermistor_readout_face.h" const watch_face_t watch_faces[] = { simple_clock_face, diff --git a/movement/watch_faces/thermistor/thermistor_driver.c b/movement/watch_faces/thermistor/thermistor_driver.c new file mode 100644 index 00000000..9e5d6fd7 --- /dev/null +++ b/movement/watch_faces/thermistor/thermistor_driver.c @@ -0,0 +1,76 @@ +#include "thermistor_driver.h" +#include "watch.h" + +#define THERMISTOR_B_COEFFICIENT (3950.0) +#define THERMISTOR_NOMINAL_TEMPERATURE (25.0) +#define THERMISTOR_NOMINAL_RESISTANCE (10000.0) +#define THERMISTOR_SERIES_RESISTANCE (10000.0) + +// TODO: we really need a math library. +uint32_t msb(uint32_t v); +double ln(double y); + +void thermistor_driver_enable() { + // Enable the ADC peripheral, which we'll use to read the thermistor value. + watch_enable_adc(); + // Enable analog circuitry on pin A1, which is tied to the thermistor resistor divider. + watch_enable_analog_input(A1); + // Enable digital output on A0, which is the power to the thermistor circuit. + watch_enable_digital_output(A0); +} + +void thermistor_driver_disable() { + // Enable the ADC peripheral, which we'll use to read the thermistor value. + watch_disable_adc(); + // Disable analog circuitry on pin A1 to save power. + watch_disable_analog_input(A1); + // Disable A0's output circuitry. + watch_disable_digital_output(A0); +} + +float thermistor_driver_get_temperature() { + // set A0 high to power the thermistor circuit. + watch_set_pin_level(A0, true); + // get the pin level + uint16_t val = watch_get_analog_pin_level(A1); + // and then set A0 low to power down the thermistor circuit. + watch_set_pin_level(A0, false); + + double reading = (double)val; + reading = (1023.0 * THERMISTOR_SERIES_RESISTANCE) / (reading / 64.0); + reading -= THERMISTOR_SERIES_RESISTANCE; + reading = reading / THERMISTOR_NOMINAL_RESISTANCE; + reading = ln(reading); + reading /= THERMISTOR_B_COEFFICIENT; + reading += 1.0 / (THERMISTOR_NOMINAL_TEMPERATURE + 273.15); + reading = 1.0 / reading; + reading -= 273.15; + + return reading; +} + +uint32_t msb(uint32_t v) { + static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9}; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v = (v >> 1) + 1; + return pos[(v * 0x077CB531UL) >> 27]; +} + +double ln(double y) { + int log2; + double divisor, x, result; + + log2 = msb((int)y); // See: https://stackoverflow.com/a/4970859/6630230 + divisor = (double)(1 << log2); + x = y / divisor; // normalized value between [1.0, 2.0] + + result = -1.7417939 + (2.8212026 + (-1.4699568 + (0.44717955 - 0.056570851 * x) * x) * x) * x; + result += ((double)log2) * 0.69314718; // ln(2) = 0.69314718 + + return result; +} + diff --git a/movement/watch_faces/thermistor/thermistor_driver.h b/movement/watch_faces/thermistor/thermistor_driver.h new file mode 100644 index 00000000..837eb15b --- /dev/null +++ b/movement/watch_faces/thermistor/thermistor_driver.h @@ -0,0 +1,13 @@ +#ifndef THERMISTOR_DRIVER_H_ +#define THERMISTOR_DRIVER_H_ + +// NOTE: This implementation is specific to one prototype sensor board, OSO-MISC-21-009, but both +// the sensor board design and this implementation are likely to change. Thermistor functionality +// may even end up being baked into the Sensor Watch library. This is all by way of saying this +// code is very temporary and the thermistor screens will likely get a rewrite in the future. + +void thermistor_driver_enable(); +void thermistor_driver_disable(); +float thermistor_driver_get_temperature(); + +#endif // THERMISTOR_DRIVER_H_ diff --git a/movement/watch_faces/thermistor/thermistor_readout_face.c b/movement/watch_faces/thermistor/thermistor_readout_face.c new file mode 100644 index 00000000..2a4ec3a1 --- /dev/null +++ b/movement/watch_faces/thermistor/thermistor_readout_face.c @@ -0,0 +1,73 @@ +#include +#include +#include "thermistor_readout_face.h" +#include "thermistor_driver.h" +#include "watch.h" + +void _thermistor_readout_face_update_display(bool in_fahrenheit) { + float temperature_c = thermistor_driver_get_temperature(); + char buf[14]; + if (in_fahrenheit) { + sprintf(buf, "%4.1f#F", temperature_c * 1.8 + 32.0); + } else { + sprintf(buf, "%4.1f#C", temperature_c); + } + watch_display_string(buf, 4); +} + +void thermistor_readout_face_setup(movement_settings_t *settings, void ** context_ptr) { + (void) settings; + (void) context_ptr; +} + +void thermistor_readout_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + watch_display_string("TE", 0); + thermistor_driver_enable(); +} + +bool thermistor_readout_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + (void) context; + watch_date_time date_time = watch_rtc_get_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_ALARM_BUTTON_UP: + settings->bit.use_imperial_units = !settings->bit.use_imperial_units; + _thermistor_readout_face_update_display(settings->bit.use_imperial_units); + break; + case EVENT_ACTIVATE: + // force a measurement to be taken immediately. + date_time.unit.second = 0; + // fall through + case EVENT_TICK: + if (date_time.unit.second % 5 == 4) { + // Not 100% on this, but I like the idea of using the signal indicator to indicate that we're sensing data. + // In this case we turn the indicator on a second before the reading is taken, and clear it when we're done. + // In reality the measurement takes a fraction of a second, but this is just to show something is happening. + watch_set_indicator(WATCH_INDICATOR_SIGNAL); + } else if (date_time.unit.second % 5 == 0) { + _thermistor_readout_face_update_display(settings->bit.use_imperial_units); + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); + } + break; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + default: + break; + } + + return true; +} + +void thermistor_readout_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; + thermistor_driver_disable(); +} diff --git a/movement/watch_faces/thermistor/thermistor_readout_face.h b/movement/watch_faces/thermistor/thermistor_readout_face.h new file mode 100644 index 00000000..639d00a4 --- /dev/null +++ b/movement/watch_faces/thermistor/thermistor_readout_face.h @@ -0,0 +1,19 @@ +#ifndef THERMISTOR_FACE_H_ +#define THERMISTOR_FACE_H_ + +#include "movement.h" + +void thermistor_readout_face_setup(movement_settings_t *settings, void ** context_ptr); +void thermistor_readout_face_activate(movement_settings_t *settings, void *context); +bool thermistor_readout_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void thermistor_readout_face_resign(movement_settings_t *settings, void *context); + +static const watch_face_t thermistor_readout_face = { + thermistor_readout_face_setup, + thermistor_readout_face_activate, + thermistor_readout_face_loop, + thermistor_readout_face_resign, + NULL +}; + +#endif // THERMISTOR_FACE_H_ \ No newline at end of file -- cgit v1.2.3