Sensor Watch Simulator (#35)

* Put something on screen

* Use the 32bit watch_date_time repr to pass from JS

* Implement periodic callbacks

* Clear display on enabling

* Hook up watch_set_led_color() to SVG (green-only)

* Make debug output full-width

* Remove default Emscripten canvas

* Implement sleep and button clicks

* Fix time zone conversion bug in beats-time app

* Clean up warnings

* Fix pin levels

* Set time zone to browser value (if available)

* Add basic backup data saving

* Silence format specifier warnings in both targets

* Remove unnecessary, copied files

* Use RTC pointer to clear callbacks (if available)

* Use preprocessor define to avoid hardcoding MOVEMENT_NUM_FACES

* Change each face to const preprocessor definition

* Remove Intl.DateTimeFormat usage

* Update shell.html title, header

* Add touch start/end event handlers on SVG buttons

* Update shell.html

* Update folder structure (shared, simulator, hardware under watch-library)

* Tease out shared components from watch_slcd

* Clean up simulator watch_slcd.c inline JS calls

* Fix missing newlines at end of file

* Add simulator warnings (except format, unused-paremter)

* Implement remaining watch_rtc functions

* Fix button bug on mouse down then drag out

* Implement remaining watch_slcd functions

* Link keyboard events to buttons (for keys A, L, M)

* Rewrite event handling (mouse, touch, keyboard) in C

* Set explicit text UTF-8 charset in shell.html

* Address PR comments

* Remove unused directories from include paths
This commit is contained in:
Alexsander Akers
2022-01-25 15:03:22 -05:00
committed by GitHub
parent 9e24f6c336
commit b8de35658f
327 changed files with 2303 additions and 570 deletions

View File

@@ -0,0 +1,83 @@
/*
* 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.
*/
/// @file watch.h
#ifndef WATCH_H_
#define WATCH_H_
#include <stdint.h>
#include <stdbool.h>
#include "driver_init.h"
/** @mainpage Sensor Watch Documentation
* @brief This documentation covers most of the functions you will use to interact with the Sensor Watch
hardware. It is divided into the following sections:
- @ref app - This section covers the functions that you will implement in your app.c file when designing a
Sensor Watch app.
- @ref rtc - This section covers functions related to the SAM L22's real-time clock peripheral, including
date, time and alarm functions.
- @ref slcd - This section covers functions related to the Segment LCD display driver, which is responsible
for displaying strings of characters and indicators on the main watch display.
- @ref buttons - This section covers functions related to the three buttons: Light, Mode and Alarm.
- @ref led - This section covers functions related to the bi-color red/green LED mounted behind the LCD.
- @ref buzzer - This section covers functions related to the piezo buzzer.
- @ref adc - 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.
- @ref gpio - This section covers functions related to general-purpose input and output signals.
- @ref i2c - This section covers functions related to the SAM L22's built-I2C driver, including configuring
the I2C bus, putting values directly on the bus and reading data from registers on I2C devices.
- @ref debug - This section covers functions related to the debug UART, available on pin D1 of the 9-pin connector.
- @ref deepsleep - This section covers functions related to preparing for and entering BACKUP mode, the
deepest sleep mode available on the SAM L22.
*/
#include "watch_app.h"
#include "watch_rtc.h"
#include "watch_slcd.h"
#include "watch_extint.h"
#include "watch_led.h"
#include "watch_buzzer.h"
#include "watch_adc.h"
#include "watch_gpio.h"
#include "watch_i2c.h"
#include "watch_uart.h"
#include "watch_deepsleep.h"
#include "watch_private.h"
/** @brief Returns true when the battery voltage dips below 2.5V.
* @details A CR2016 battery will have a nominal voltage between 2.9 and 3 volts for most of its lifespan. Once the battery
* discharges to about 60%, the voltage will drift slightly lower; this may manifest as a dimmer LED. By the time
* the battery voltage has fallen to 2.5 volts, it will have probably less than 10% of its capacity remaining, and
* you can expect the voltage to drop relatively quickly as the battery dies.
*/
bool watch_is_battery_low(void);
/** @brief Returns true if either the buzzer or the LED driver is enabled.
* @details Both the buzzer and the LED use the TCC peripheral to drive their behavior. This function returns true if that
* peripheral is enabled. You can use this function to determine whether you need to call the watch_disable_leds or
* or watch_enable_buzzer functions before using these peripherals.
*/
bool watch_is_buzzer_or_led_enabled(void);
#endif /* WATCH_H_ */

View File

@@ -0,0 +1,174 @@
/*
* 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.
*/
#ifndef _WATCH_ADC_H_INCLUDED
#define _WATCH_ADC_H_INCLUDED
////< @file watch_adc.h
#include "watch.h"
// matches adc.h
#ifndef ADC_REFCTRL_REFSEL_INTREF_Val
#define ADC_REFCTRL_REFSEL_INTREF_Val 0x0
#endif
#ifndef ADC_REFCTRL_REFSEL_INTVCC0_Val
#define ADC_REFCTRL_REFSEL_INTVCC0_Val 0x1
#endif
#ifndef ADC_REFCTRL_REFSEL_INTVCC1_Val
#define ADC_REFCTRL_REFSEL_INTVCC1_Val 0x2
#endif
#ifndef ADC_REFCTRL_REFSEL_INTVCC2_Val
#define ADC_REFCTRL_REFSEL_INTVCC2_Val 0x5
#endif
/** @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 pins A0-A4.
*/
void watch_enable_analog_input(const uint8_t pin);
/** @brief Reads an analog value from one of the pins.
* @param pin One of pins A0-A4.
* @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 uint8_t pin);
/** @brief Sets the number of samples to accumulate when measuring a pin level. Default is 16.
* @param samples A power of 2 <= 1024. Specifically: 1, 2, 4, 8, 16, 32, 64, 128, 256, 512
or 1024. Any other value will be ignored.
* @details The SAM L22's ADC has a resolution of 12 bits. By default, the watch configures
* the ADC to take 16 samples of the analog input and accumulate them in the result
* register; this effectively gives us a 16-bit resolution, at the cost of taking 16
* ADC cycles to complete a measurement. If you are measuring a slowly changing signal
* like a thermistor output or an ambient light sensor this is probably fine, even
* desirable. If you are measuring something a bit more fast-paced, like an analog
* accelerometer, you may wish to exchange precision for speed. In this case you may
* call this function to configure the ADC to accumulate fewer samples. HOWEVER! Note
* that this may change the range of values returned from watch_get_analog_pin_level:
* - For watch_set_num_analog_samples(1), the returned value will be 12 bits (0-4095).
* - For watch_set_num_analog_samples(2), the returned value will be 13 bits (0-8191).
* - For watch_set_num_analog_samples(4), the returned value will be 14 bits (0-16383).
* - For watch_set_num_analog_samples(8), the returned value will be 15 bits (0-32767).
* For sampling values over 16, the returned value will still be 16 bits (0-65535); the
* ADC will automatically divide the measured value by whatever factor is necessary to fit
* the result in 16 bits.
* @see watch_get_analog_pin_level
**/
void watch_set_analog_num_samples(uint16_t samples);
/** @brief Sets the length of time spent sampling, which allows measurement of higher impedance inputs.
* Default is 1.
* @param cycles The number of ADC cycles to sample, between 1 and 64.
* @see this article by Thea Flowers: https://blog.thea.codes/getting-the-most-out-of-the-samd21-adc/
* which is where I learned all of this.
* @details To measure an analog value, the SAM L22 must charge a capacitor to the analog voltage
* presented at the input. This takes time. Importantly, the higher the input impedance,
* the more time this takes. As a basic example: if you are using a thermistor tied to
* VCC to measure temperature, the capacitor has to charge through the thermistor. The
* higher the resistor value, the higher the input impedance, and the more time we need
* to allow for the measurement. By default, the ADC is configured to run on a 500 kHz
* clock with a sample time of one cycle. This is appropriate for an input impedance up
* to about 28kΩ. Setting the sampling time to 4 cycles allows for an input impedance up
* to 123kΩ. Setting the sampling time to the maximum of 64 cycles theoretically allows
* for input impedance up to 2 MΩ. (I based these numbers on the calculator in the linked
* blog post; it also has a ton of great info on the SAM D21 ADC, which is similar to the
* SAM L22's).
**/
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(void);
/** @brief Disables the analog circuitry on the selected pin.
* @param pin One of pins A0-A4.
*/
void watch_disable_analog_input(const uint8_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);
/// @}
#endif

View File

@@ -0,0 +1,108 @@
/*
* 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.
*/
#ifndef _WATCH_APP_H_INCLUDED
#define _WATCH_APP_H_INCLUDED
////< @file watch_app.h
/** @addtogroup app Application Framework
* @brief This section covers the functions that you will implement in your app.c file when designing a Sensor Watch app.
* @details You should be able to write a watch app by simply implementing these functions and declaring callbacks for
* various GPIO and peripheral interrupts. The main.c file takes care of calling these functions for you. The
* general flow:
*
* 1. Your app_init() function is called.
* - This method should only be used to set your initial application state.
* 2. If your app is waking from BACKUP, app_wake_from_backup() is called.
* - If you saved state in the RTC's backup registers, you can restore it here.
* 3. Your app_setup() method is called.
* - You may wish to enable some functionality and peripherals here.
* - You should definitely set up some interrupts here.
* 4. The main run loop begins: your app_loop() function is called.
* - Run code and update your UI here.
* - Return true if your app is prepared to enter STANDBY mode.
* 5. This step differs depending on the value returned by app_loop:
* - If you returned false, execution resumes at (4).
* - If you returned true, app_prepare_for_standby() is called; execution moves on to (6).
* 6. The microcontroller enters STANDBY mode.
* - No user code will run, and the watch will enter a low power mode.
* - The watch will remain in this state until an interrupt wakes it.
* 7. Once woken from STANDBY, your app_wake_from_standby() function is called.
* - After this, execution resumes at (4).
*/
/// @{
/** @brief A function you will implement to initialize your application state. The app_init function is called before
* anything else. Use it to set up any internal data structures or application state required by your app,
* but don't configure any peripherals just yet.
*/
void app_init(void);
/** @brief A function you will implement to wake from BACKUP mode, which wipes the system's RAM, and with it, your
* application's state. You may have chosen to store some important application state in the RTC's backup
* registers prior to entering this mode. You may restore that state here.
*/
void app_wake_from_backup(void);
/** @brief A function you will implement to set up your application. The app_setup function is like setup() in Arduino.
* It is called once when the program begins. You should set pin modes and enable any peripherals you want to
* set up (real-time clock, I2C, etc.) Depending on your application, you may or may not want to configure
* sensors on your sensor board here. For example, a low-power accelerometer that will run at all times should
* be configured here, whereas you may want to enable a more power-hungry sensor only when you need it.
* @note If your app enters the ultra-low power BACKUP sleep mode, this function will be called again when it wakes
* from that deep sleep state. In this state, the RTC will still be configured with the correct date and time.
*/
void app_setup(void);
/** @brief A function you will implement to serve as the app's main run loop. This method will be called repeatedly,
or if you enter STANDBY mode, as soon as the device wakes from sleep.
* @return You should return true if your app is prepared to enter STANDBY mode. If you return false, your app's
* app_loop method will be called again immediately. Note that in STANDBY mode, the watch will consume only
* about 95 microamperes of power, whereas if you return false and keep the app awake, it will consume about
* 355 microamperes. This is the difference between months of battery life and days. As much as possible,
* you should limit the amount of time your app spends awake.
* @note Only the RTC, the segment LCD controller and the external interrupt controller run in STANDBY mode. If you
* are using, e.g. the PWM function to set a custom LED color, you should return false here until you are
* finished with that operation. Note however that the peripherals will continue running after waking up,
* so e.g. the I2C controller, if configured, will sleep in STANDBY. But you can use it again as soon as your
* app wakes up.
*/
bool app_loop(void);
/** @brief A function you will implement to prepare to enter STANDBY mode. The app_prepare_for_standby function is
* called after your app_loop function returns true, and just before the watch enters STANDBY mode. In this
* mode most peripherals are shut down, and no code will run until the watch receives an interrupt (generally
* either the 1Hz tick or a press on one of the buttons).
* @note If you are PWM'ing the LED or playing a sound on the buzzer, the TC/TCC peripherals that drive those operations
* will not run in STANDBY. BUT! the output pins will retain the state they had when entering standby. This means
* you could end up entering standby with an LED on and draining power, or with a DC potential across the piezo
* buzzer that could damage it if left in this state. If your app_loop does not prevent sleep during these
* activities, you should make sure to disable these outputs in app_prepare_for_standby.
*/
void app_prepare_for_standby(void);
/** @brief A method you will implement to configure the app after waking from STANDBY mode.
*/
void app_wake_from_standby(void);
/// @}
#endif

View File

@@ -0,0 +1,164 @@
/*
* 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.
*/
#ifndef _WATCH_BUZZER_H_INCLUDED
#define _WATCH_BUZZER_H_INCLUDED
////< @file watch_buzzer.h
#include "watch.h"
/** @addtogroup buzzer Buzzer
* @brief This section covers functions related to the piezo buzzer embedded in the F-91W's back plate.
*/
/// @{
/** @brief Enables the TCC peripheral, which drives the buzzer.
*/
void watch_enable_buzzer(void);
/** @brief Sets the period of the buzzer.
* @param period The period of a single cycle for the TCC peripheral. You can determine the period for
* a desired frequency with the following formula: period = 1000000 / freq
*/
void watch_set_buzzer_period(uint32_t period);
/** @brief Disables the TCC peripheral that drives the buzzer.
* @note If you are using PWM to set custom LED colors, this method will also disable the LED PWM driver,
* since the buzzer and LED both make use of the same peripheral to drive their PWM behavior.
*/
void watch_disable_buzzer(void);
/** @brief Turns the buzzer output on. It will emit a continuous sound at the given frequency.
* @note The TCC peripheral that drives the buzzer does not run in standby mode; if you wish for buzzer
* output to continue, you should prevent your app from going to sleep.
*/
void watch_set_buzzer_on(void);
/** @brief Turns the buzzer output off.
*/
void watch_set_buzzer_off(void);
/// @brief 87 notes for use with watch_buzzer_play_note
typedef enum BuzzerNote {
BUZZER_NOTE_A1, ///< 55.00 Hz
BUZZER_NOTE_A1SHARP_B1FLAT, ///< 58.27 Hz
BUZZER_NOTE_B1, ///< 61.74 Hz
BUZZER_NOTE_C2, ///< 65.41 Hz
BUZZER_NOTE_C2SHARP_D2FLAT, ///< 69.30 Hz
BUZZER_NOTE_D2, ///< 73.42 Hz
BUZZER_NOTE_D2SHARP_E2FLAT, ///< 77.78 Hz
BUZZER_NOTE_E2, ///< 82.41 Hz
BUZZER_NOTE_F2, ///< 87.31 Hz
BUZZER_NOTE_F2SHARP_G2FLAT, ///< 92.50 Hz
BUZZER_NOTE_G2, ///< 98.00 Hz
BUZZER_NOTE_G2SHARP_A2FLAT, ///< 103.83 Hz
BUZZER_NOTE_A2, ///< 110.00 Hz
BUZZER_NOTE_A2SHARP_B2FLAT, ///< 116.54 Hz
BUZZER_NOTE_B2, ///< 123.47 Hz
BUZZER_NOTE_C3, ///< 130.81 Hz
BUZZER_NOTE_C3SHARP_D3FLAT, ///< 138.59 Hz
BUZZER_NOTE_D3, ///< 146.83 Hz
BUZZER_NOTE_D3SHARP_E3FLAT, ///< 155.56 Hz
BUZZER_NOTE_E3, ///< 164.81 Hz
BUZZER_NOTE_F3, ///< 174.61 Hz
BUZZER_NOTE_F3SHARP_G3FLAT, ///< 185.00 Hz
BUZZER_NOTE_G3, ///< 196.00 Hz
BUZZER_NOTE_G3SHARP_A3FLAT, ///< 207.65 Hz
BUZZER_NOTE_A3, ///< 220.00 Hz
BUZZER_NOTE_A3SHARP_B3FLAT, ///< 233.08 Hz
BUZZER_NOTE_B3, ///< 246.94 Hz
BUZZER_NOTE_C4, ///< 261.63 Hz
BUZZER_NOTE_C4SHARP_D4FLAT, ///< 277.18 Hz
BUZZER_NOTE_D4, ///< 293.66 Hz
BUZZER_NOTE_D4SHARP_E4FLAT, ///< 311.13 Hz
BUZZER_NOTE_E4, ///< 329.63 Hz
BUZZER_NOTE_F4, ///< 349.23 Hz
BUZZER_NOTE_F4SHARP_G4FLAT, ///< 369.99 Hz
BUZZER_NOTE_G4, ///< 392.00 Hz
BUZZER_NOTE_G4SHARP_A4FLAT, ///< 415.30 Hz
BUZZER_NOTE_A4, ///< 440.00 Hz
BUZZER_NOTE_A4SHARP_B4FLAT, ///< 466.16 Hz
BUZZER_NOTE_B4, ///< 493.88 Hz
BUZZER_NOTE_C5, ///< 523.25 Hz
BUZZER_NOTE_C5SHARP_D5FLAT, ///< 554.37 Hz
BUZZER_NOTE_D5, ///< 587.33 Hz
BUZZER_NOTE_D5SHARP_E5FLAT, ///< 622.25 Hz
BUZZER_NOTE_E5, ///< 659.25 Hz
BUZZER_NOTE_F5, ///< 698.46 Hz
BUZZER_NOTE_F5SHARP_G5FLAT, ///< 739.99 Hz
BUZZER_NOTE_G5, ///< 783.99 Hz
BUZZER_NOTE_G5SHARP_A5FLAT, ///< 830.61 Hz
BUZZER_NOTE_A5, ///< 880.00 Hz
BUZZER_NOTE_A5SHARP_B5FLAT, ///< 932.33 Hz
BUZZER_NOTE_B5, ///< 987.77 Hz
BUZZER_NOTE_C6, ///< 1046.50 Hz
BUZZER_NOTE_C6SHARP_D6FLAT, ///< 1108.73 Hz
BUZZER_NOTE_D6, ///< 1174.66 Hz
BUZZER_NOTE_D6SHARP_E6FLAT, ///< 1244.51 Hz
BUZZER_NOTE_E6, ///< 1318.51 Hz
BUZZER_NOTE_F6, ///< 1396.91 Hz
BUZZER_NOTE_F6SHARP_G6FLAT, ///< 1479.98 Hz
BUZZER_NOTE_G6, ///< 1567.98 Hz
BUZZER_NOTE_G6SHARP_A6FLAT, ///< 1661.22 Hz
BUZZER_NOTE_A6, ///< 1760.00 Hz
BUZZER_NOTE_A6SHARP_B6FLAT, ///< 1864.66 Hz
BUZZER_NOTE_B6, ///< 1975.53 Hz
BUZZER_NOTE_C7, ///< 2093.00 Hz
BUZZER_NOTE_C7SHARP_D7FLAT, ///< 2217.46 Hz
BUZZER_NOTE_D7, ///< 2349.32 Hz
BUZZER_NOTE_D7SHARP_E7FLAT, ///< 2489.02 Hz
BUZZER_NOTE_E7, ///< 2637.02 Hz
BUZZER_NOTE_F7, ///< 2793.83 Hz
BUZZER_NOTE_F7SHARP_G7FLAT, ///< 2959.96 Hz
BUZZER_NOTE_G7, ///< 3135.96 Hz
BUZZER_NOTE_G7SHARP_A7FLAT, ///< 3322.44 Hz
BUZZER_NOTE_A7, ///< 3520.00 Hz
BUZZER_NOTE_A7SHARP_B7FLAT, ///< 3729.31 Hz
BUZZER_NOTE_B7, ///< 3951.07 Hz
BUZZER_NOTE_C8, ///< 4186.01 Hz
BUZZER_NOTE_C8SHARP_D8FLAT, ///< 4434.92 Hz
BUZZER_NOTE_D8, ///< 4698.63 Hz
BUZZER_NOTE_D8SHARP_E8FLAT, ///< 4978.03 Hz
BUZZER_NOTE_E8, ///< 5274.04 Hz
BUZZER_NOTE_F8, ///< 5587.65 Hz
BUZZER_NOTE_F8SHARP_G8FLAT, ///< 5919.91 Hz
BUZZER_NOTE_G8, ///< 6271.93 Hz
BUZZER_NOTE_G8SHARP_A8FLAT, ///< 6644.88 Hz
BUZZER_NOTE_A8, ///< 7040.00 Hz
BUZZER_NOTE_A8SHARP_B8FLAT, ///< 7458.62 Hz
BUZZER_NOTE_B8, ///< 7902.13 Hz
BUZZER_NOTE_REST ///< no sound
} BuzzerNote;
/** @brief Plays the given note for a set duration.
* @param note The note you wish to play, or BUZZER_NOTE_REST to disable output for the given duration.
* @param duration_ms The duration of the note.
* @note Note that this will block your UI for the duration of the note's play time, and it will
* after this call, the buzzer period will be set to the period of this note.
*/
void watch_buzzer_play_note(BuzzerNote note, uint16_t duration_ms);
/// @brief An array of periods for all the notes on a piano, corresponding to the names in BuzzerNote.
extern const uint16_t NotePeriods[108];
/// @}
#endif

View File

@@ -0,0 +1,159 @@
/*
* 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.
*/
#ifndef _WATCH_DEEPSLEEP_H_INCLUDED
#define _WATCH_DEEPSLEEP_H_INCLUDED
////< @file watch_deepsleep.h
#include "watch.h"
// These are declared in watch_rtc.c.
extern ext_irq_cb_t btn_alarm_callback;
extern ext_irq_cb_t a2_callback;
extern ext_irq_cb_t a4_callback;
/** @addtogroup deepsleep Sleep Control
* @brief This section covers functions related to the various sleep modes available to the watch,
* including Sleep, Deep Sleep, and BACKUP mode.
* @details These terms changed meaning a bit over the course of development; if you are coming
* to this documentation after having worked with an earlier version of the library,
* these definitions should clarify the terminology. Terms in all caps are modes of the
* SAM L22; terms in Title Case are specific implementations in this library.
* - ACTIVE mode is the mode the SAM L22 is in when both the main clock and the CPU are
* running. It is the most power-hungry mode. If you ever call delay_ms to wait a beat,
* the watch will remain in ACTIVE mode while taking that delay. In addition, whenever
* your `app_loop` function returns false, the device will remain in ACTIVE mode and
* call your `app_loop` function again.
* - STANDBY mode turns off the main clock and halts the CPU. Since the PWM driver is
* run from the main clock, it also stops the buzzer and any dimming of the LEDs.
* In this mode, the watch can wake from any interrupt source. Whenever your `app_loop`
* function returns true, the watch enters STANDBY mode until the next tick or other
* interrupt. This mode uses much less power than ACTIVE mode.
* - Sleep Mode is a special case of STANDBY mode. In this mode, the watch turns off
* almost all peripherals (including the external interrupt controller), and disables
* all pins except for the external wake pins. In this mode the watch can only wake
* from the RTC alarm interrupt or an external wake pin (A2, A4 or the alarm button),
* but the display remains on and your app's state is retained. You can enter this
* mode by calling `watch_enter_sleep_mode`. It consumes an order of magnitude less
* power than STANDBY mode.
* - Deep Sleep Mode is identical to sleep mode, but it also turns off the LCD to save
* a bit more power. You can enter this mode by calling `watch_enter_deep_sleep_mode`.
* - BACKUP mode is the lowest possible power mode on the SAM L22. It turns off all pins
* and peripherals except for the RTC. It also turns off the RAM, obliterating your
* application's state. The only way to wake from this mode is by setting an external
* wake interrupt on pin A2 or pin A4, and when you do wake it will be much like a
* wake from reset. You can enter this mode by calling `watch_enter_backup_mode`.
*/
/// @{
/** @brief Registers a callback on one of the RTC's external wake pins, which can wake the device
* from Sleep, Deep Sleep and BACKUP modes (but see warning re: BACKUP mode).
* @param pin Either pin BTN_ALARM, A2, or A4. These are the three external wake pins. If the pin
* is BTN_ALARM, this function also enables an internal pull down on that pin.
* @param callback The callback to be called if this pin triggers outside of BACKUP mode. If this is
* NULL, no callback will be called even in normal modes, but the interrupt will
* still be enabled so that it can wake the device.
* @param level The level you wish to scan for: true for rising, false for falling. Note that you
* cannot scan for both rising and falling edges like you can with the external interrupt
* pins; with the external wake interrupt, you can only get one or the other.
* @note When in ACTIVE, STANDBY and Sleep / Deep sleep modes, this will function much like a standard
* external interrupt situation: these pins will wake the device, and your callback will be
* called. However, if the device enters BACKUP mode and one of these pins wakes the device, your
* callback WILL NOT be called, as the device is basically waking from reset at that point.
* @warning As of the current SAM L22 silicon revision (rev B), the BTN_ALARM pin cannot wake the
* device from BACKUP mode. You can still use this function to register a BTN_ALARM interrupt
* in normal or deep sleep mode, but to wake from BACKUP, you will need to use pin A2 or A4.
*/
void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool level);
/** @brief Unregisters the RTC interrupt on one of the EXTWAKE pins. This will prevent a value change on
* one of these pins from waking the device.
* @param pin Either pin BTN_ALARM, A2, or A4. If the pin is BTN_ALARM, this function DOES NOT disable
* the internal pull down on that pin.
*/
void watch_disable_extwake_interrupt(uint8_t pin);
/** @brief Stores data in one of the RTC's backup registers, which retain their data in BACKUP mode.
* @param data An unsigned 32 bit integer with the data you wish to store.
* @param reg A register from 0-7.
*/
void watch_store_backup_data(uint32_t data, uint8_t reg);
/** @brief Gets 32 bits of data from the RTC's BACKUP register.
* @param reg A register from 0-7.
* @return An unsigned 32 bit integer with the from the backup register.
*/
uint32_t watch_get_backup_data(uint8_t reg);
/** @brief enters Sleep Mode by disabling all pins and peripherals except the RTC and the LCD.
* @details This sleep mode is not the lowest power mode available, but it has the benefit of allowing you
* to display a message to the user while asleep. You can also set an alarm interrupt to wake at a
* configfurable interval (every minute, hour or day) to update the display. You can wake from this
* mode by pressing the ALARM button, if you registered an extwake callback on the ALARM button.
* Also note that when your app wakes from this sleep mode, your app_setup method will be called
* again, since this function will have disabled things you set up there.
*
* Note that to wake from either the ALARM button, the A2 interrupt or the A4 interrupt, you
* must first configure this by calling watch_register_extwake_callback.
*
* You can estimate the power consumption of this mode to be on the order of 30 microwatts
* (about 10 µA at 3 V).
*/
void watch_enter_sleep_mode(void);
/** @brief enters Deep Sleep Mode by disabling all pins and peripherals except the RTC.
* @details Short of BACKUP mode, this is the lowest power mode you can enter while retaining your
* application state (and the ability to wake with the alarm button). Just note that the display
* will be completely off, so you should document to the user of your application that they will
* need to press the alarm button to wake the device, or use a sensor board with support for
* an external wake pin.
*
* All notes from watch_enter_sleep_mode apply here, except for power consumption. You can estimate
* the power consumption of this mode to be on the order of 12 microwatts (about 4µA at 3 V).
*/
void watch_enter_deep_sleep_mode(void);
/** @brief Enters the SAM L22's lowest-power mode, BACKUP.
* @details This function does some housekeeping before entering BACKUP mode. It first disables all pins
* and peripherals except for the RTC, and disables the tick interrupt (since that would wake
* us up from BACKUP mode). Once again, if you wish to wake from the A2 or the A4 interrupt,
* you must first configure this by calling watch_register_extwake_callback.
* @note If you have a callback set for an external wake interrupt, it will be called if triggered while
* in ACTIVE, STANDBY, Sleep and Deep Sleep modes, but it *will not be called* when waking from
* BACKUP mode. Waking from backup is effectively like waking from reset, except that your
* @ref app_wake_from_backup function will be called.
* @warning On current revisions of the SAM L22 silicon, the ALARM_BTN pin (PA02 RTC/IN2) cannot wake
* the device from deep sleep mode. There is an errata note (Reference: 15010) that says that
* due to a silicon bug, RTC/IN2 is not functional in BACKUP. As a result, you should not call
* this function unless you have a device on the nine-pin connector with an external interrupt
* on pin A2 or A4 (i.e. an accelerometer with an interrupt pin).
*/
void watch_enter_backup_mode(void);
__attribute__((deprecated("Use watch_enter_sleep_mode or watch_enter_deep_sleep_mode instead")))
void watch_enter_shallow_sleep(bool display_on);
__attribute__((deprecated("Use watch_enter_backup_mode instead")))
void watch_enter_deep_sleep(void);
/// @}
#endif

View File

@@ -0,0 +1,85 @@
/*
* 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.
*/
#ifndef _WATCH_EXTINT_H_INCLUDED
#define _WATCH_EXTINT_H_INCLUDED
////< @file watch_extint.h
#include "watch.h"
#include "hal_ext_irq.h"
/** @addtogroup buttons Buttons & External Interrupts
* @brief This section covers functions related to the three buttons: Light, Mode and Alarm, as well as
* external interrupts from devices on the nine-pin connector.
* @details The buttons are the core input UI of the watch, and the way the user will interact with
* your application. They are active high, pulled down by the microcontroller, and triggered
* when one of the "pushers" brings a tab from the metal frame into contact with the edge
* of the board. Note that the buttons can only wake the watch from STANDBY mode, at least as
* of the current SAM L22 silicon revision. The external interrupt controller runs in STANDBY
* mode, but it does not run in BACKUP mode; to wake from BACKUP, buttons will not cut it.
*/
/// @{
///@brief An enum defining the types of interrupt trigger you wish to scan for.
typedef enum watch_interrupt_trigger {
INTERRUPT_TRIGGER_NONE = 0,
INTERRUPT_TRIGGER_RISING,
INTERRUPT_TRIGGER_FALLING,
INTERRUPT_TRIGGER_BOTH,
} watch_interrupt_trigger;
/// @brief Enables the external interrupt controller.
void watch_enable_external_interrupts(void);
/// @brief Disables the external interrupt controller.
void watch_disable_external_interrupts(void);
/** @brief Configures an external interrupt callback on one of the external interrupt pins.
* @details You can set one interrupt callback per pin, and you can monitor for a rising condition,
* a falling condition, or both. If you just want to detect a button press, register your
* interrupt with INTERRUPT_TRIGGER_RISING; if you want to detect an active-low interrupt
* signal from a device on the nine-pin connector, use INTERRUPT_TRIGGER_FALLING. If you
* want to detect both rising and falling conditions (i.e. button down and button up), use
* INTERRUPT_TRIGGER_BOTH and use watch_get_pin_level to check the pin level in your callback
* to determine which condition caused the interrupt.
* @param pin One of BTN_LIGHT, BTN_MODE, BTN_ALARM, A0, A1, A3 or A4. If the pin parameter matches one of
* the three button pins, this function will also enable an internal pull-down resistor. If
* the pin parameter is A0-A4, you are responsible for setting any required pull configuration
* using watch_enable_pull_up or watch_enable_pull_down.
* @param callback The function you wish to have called when the button is pressed.
* @param trigger The condition on which you wish to trigger: rising, falling or both.
* @note Pins A2 and A4 can also generate interrupts via the watch_register_extwake_callback function, which
* will allow them to trigger even when the watch is in deep sleep mode.
* @warning As of now, A2 is not usable via the watch_register_interrupt_callback function. To enable an
* external interrupt on pin A2, use the watch_register_extwake_callback function. This issue will be
* addressed in a future revision of the watch library.
*/
void watch_register_interrupt_callback(const uint8_t pin, ext_irq_cb_t callback, watch_interrupt_trigger trigger);
__attribute__((deprecated("Use watch_register_interrupt_callback or watch_register_extwake_callback instead")))
void watch_register_button_callback(const uint8_t pin, ext_irq_cb_t callback);
__attribute__((deprecated("Use watch_enable_external_interrupts instead")))
void watch_enable_buttons(void);
/// @}
#endif

View File

@@ -0,0 +1,76 @@
/*
* 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.
*/
#ifndef _WATCH_GPIO_H_INCLUDED
#define _WATCH_GPIO_H_INCLUDED
////< @file watch_gpio.h
#include "watch.h"
/** @addtogroup gpio Digital Input and Output
* @brief This section covers functions related to general-purpose input and output signals.
*/
/// @{
/** @brief Configures the selected pin for digital input.
* @param pin The pin that you wish to act as an input.
*/
void watch_enable_digital_input(const uint8_t pin);
/** @brief Disables any digital input, along with any pull-up or pull-down configuration.
* @param pin The pin that you wish to disable.
*/
void watch_disable_digital_input(const uint8_t pin);
/** @brief Enables a pull-up resistor on the selected pin.
* @param pin The pin that you wish to configure.
*/
void watch_enable_pull_up(const uint8_t pin);
/** @brief Enables a pull-down resistor on the selected pin.
* @param pin The pin that you wish to configure.
*/
void watch_enable_pull_down(const uint8_t pin);
/** @brief Gets the level of the selected pin.
* @param pin The pin whose value you wish to read.
* @return true if the pin was logic high; otherwise, false.
*/
bool watch_get_pin_level(const uint8_t pin);
/** @brief Configures the selected pin for digital output.
* @param pin The pin that you wish to act as an output.
*/
void watch_enable_digital_output(const uint8_t pin);
/** @brief Disables digital output on the selected pin.
* @param pin The pin that you wish disable.
*/
void watch_disable_digital_output(const uint8_t pin);
/** @brief Sets the level of the selected pin.
* @param pin The pin whose value you wish to set.
* @param level The level you wish to set: true for high, false for low.
*/
void watch_set_pin_level(const uint8_t pin, const bool level);
/// @}
#endif

View File

@@ -0,0 +1,106 @@
/*
* 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.
*/
#ifndef _WATCH_I2C_H_INCLUDED
#define _WATCH_I2C_H_INCLUDED
////< @file watch_i2c.h
#include "watch.h"
/** @addtogroup i2c I2C Controller Driver
* @brief This section covers functions related to the SAM L22's built-I2C driver, including
* configuring the I2C bus, putting values directly on the bus and reading data from
* registers on I2C devices.
*/
/// @{
/** @brief Enables the I2C peripheral. Call this before attempting to interface with I2C devices.
*/
void watch_enable_i2c(void);
/** @brief Disables the I2C peripheral.
*/
void watch_disable_i2c(void);
/** @brief Sends a series of values to a device on the I2C bus.
* @param addr The address of the device you wish to talk to.
* @param buf A series of unsigned bytes; the data you wish to transmit.
* @param length The number of bytes in buf that you wish to send.
*/
void watch_i2c_send(int16_t addr, uint8_t *buf, uint16_t length);
/** @brief Receives a series of values from a device on the I2C bus.
* @param addr The address of the device you wish to hear from.
* @param buf Storage for the incoming bytes; on return, it will contain the received data.
* @param length The number of bytes that you wish to receive.
*/
void watch_i2c_receive(int16_t addr, uint8_t *buf, uint16_t length);
/** @brief Writes a byte to a register in an I2C device.
* @param addr The address of the device you wish to address.
* @param reg The register on the device that you wish to set.
* @param data The value that you wish to set the register to.
*/
void watch_i2c_write8(int16_t addr, uint8_t reg, uint8_t data);
/** @brief Reads a byte from a register in an I2C device.
* @param addr The address of the device you wish to address.
* @param reg The register on the device that you wish to read.
* @return An unsigned byte representing the value of the register that was read.
*/
uint8_t watch_i2c_read8(int16_t addr, uint8_t reg);
/** @brief Reads an unsigned little-endian word from a register in an I2C device.
* @param addr The address of the device you wish to address.
* @param reg The register on the device that you wish to read.
* @return An unsigned word representing the value of the register that was read.
* @note This reads two bytes into the word in bus order. If the device returns
the LSB first and then the MSB, you can use this value as returned.
If the device returns the data in big-endian order or uses some other
kind of fancy bit packing, you may need to shuffle some bits around.
*/
uint16_t watch_i2c_read16(int16_t addr, uint8_t reg);
/** @brief Reads three bytes as an unsigned little-endian int from a register in an I2C device.
* @param addr The address of the device you wish to address.
* @param reg The register on the device that you wish to read.
* @return An unsigned word representing the value of the register that was read.
* @note This reads three bytes into the word in bus order. If the device returns
these bytes LSB first, you can use this value as returned. If there is a
sign bit, the device returns the data in big-endian order, or it uses some
other kind of fancy bit packing, you may need to shuffle some bits around.
*/
uint32_t watch_i2c_read24(int16_t addr, uint8_t reg);
/** @brief Reads an unsigned little-endian int from a register in an I2C device.
* @param addr The address of the device you wish to address.
* @param reg The register on the device that you wish to read.
* @return An unsigned word representing the value of the register that was read.
* @note This reads three bytes into the word in bus order. If the device returns
these bytes LSB first, you can use this value as returned. If the device
returns the data in big-endian order, or it uses some other kind of fancy
bit packing, you may need to shuffle some bits around.
*/
uint32_t watch_i2c_read32(int16_t addr, uint8_t reg);
/// @}
#endif

View File

@@ -0,0 +1,93 @@
/*
* 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.
*/
#ifndef _WATCH_LED_H_INCLUDED
#define _WATCH_LED_H_INCLUDED
////< @file watch_led.h
#include "watch.h"
/** @addtogroup led LED Control
* @brief This section covers functions related to the bi-color red/green LED mounted behind the LCD.
* @details The SAM L22 is an exceedingly power efficient chip, whereas the LED's are relatively power-
* hungry. The green LED, at full power, consumes more power than the whole chip in active mode,
* and the red LED consumes about twelve times as much power! The LED's should thus be used only
* sparingly in order to preserve battery life.
* @note Some watches use a red/blue LED instead of a red/green LED. You will be able to determine this
* easily when you double tap the reset button: if the pulsing bootloader LED is red, you have a
* red/green edition; if it is blue, you have a red/blue edition. For red/blue watches, build your
* project with the command `make LED=BLUE`, and the watch library will automatically swap the pins
* so that watch_set_led_red sets the red LED, and watch_set_led_green sets the blue one.
*/
/// @{
/** @brief Enables the bi-color LED.
* @note The TCC peripheral that drives the LEDs does not run in STANDBY mode — but the outputs do! This
* means that if you set either red, green or both LEDs to full power, they will shine even when
* your app is asleep. If, however, you set a custom color using watch_set_led_color, the color will
* not display correctly in STANDBY mode. You will need to keep your app running while the LED is on.
*/
void watch_enable_leds(void);
/** @brief Disables the LEDs.
* @note This method will also disable the buzzer, since the buzzer and LED both make use of the same
* peripheral to drive their PWM behavior.
*/
void watch_disable_leds(void);
/** @brief Sets the LED to a custom color by modulating each output's duty cycle.
* @param red The red value from 0-255.
* @param green The green value from 0-255. If your watch has a red/blue LED, this will be the blue value.
* @note If you are displaying a custom color, you will need to prevent your app from going to sleep
* while the LED is on; otherwise, the color will not display correctly. You can do this by
* returning false in your app_loop method.
*/
void watch_set_led_color(uint8_t red, uint8_t green);
/** @brief Sets the red LED to full brightness, and turns the green LED off.
* @details Of the two LED's in the RG bi-color LED, the red LED is the less power-efficient one (~4.5 mA).
*/
void watch_set_led_red(void);
/** @brief Sets the green LED to full brightness, and turns the red LED off.
* @details Of the two LED's in the RG bi-color LED, the green LED is the more power-efficient one (~0.44 mA).
* @note If your watch has a red/blue LED, this method will set the LED to blue.
*/
void watch_set_led_green(void);
/** @brief Sets both red and green LEDs to full brightness.
* @details The total current draw between the two LED's in this mode will be ~5 mA, which is more than the
* watch draws in any other mode. Take care not to drain the battery.
* @note If your watch has a red/blue LED, this method will set the LED to pink.
*/
void watch_set_led_yellow(void);
/** @brief Turns both the red and the green LEDs off. */
void watch_set_led_off(void);
__attribute__((deprecated("Use watch_enable_leds instead")))
void watch_enable_led(bool unused);
__attribute__((deprecated("Use watch_disable_leds instead")))
void watch_disable_led(bool unused);
/// @}
#endif

View File

@@ -0,0 +1,50 @@
/*
* MIT License
*
* Copyright (c) 2021 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.
*/
#ifndef _WATCH_PRIVATE_H_INCLUDED
#define _WATCH_PRIVATE_H_INCLUDED
#include "watch.h"
/// Called by main.c while setting up the app. You should not call this from your app.
void _watch_init(void);
/// Initializes the real-time clock peripheral.
void _watch_rtc_init(void);
/// Called by buzzer and LED setup functions. You should not call this from your app.
void _watch_enable_tcc(void);
/// Called by buzzer and LED teardown functions. You should not call this from your app.
void _watch_disable_tcc(void);
/// Called by main.c if plugged in to USB. You should not call this from your app.
void _watch_enable_usb(void);
// this function ends up getting called by printf to log stuff to the USB console.
int _write(int file, char *ptr, int len);
// this method could be overridden to read stuff from the USB console? but no need rn.
int _read(void);
#endif

View File

@@ -0,0 +1,137 @@
/*
* 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_slcd.h"
#include "watch_private_display.h"
static const uint32_t IndicatorSegments[] = {
SLCD_SEGID(0, 17), // WATCH_INDICATOR_SIGNAL
SLCD_SEGID(0, 16), // WATCH_INDICATOR_BELL
SLCD_SEGID(2, 17), // WATCH_INDICATOR_PM
SLCD_SEGID(2, 16), // WATCH_INDICATOR_24H
SLCD_SEGID(1, 10), // WATCH_INDICATOR_LAP
};
void watch_display_character(uint8_t character, uint8_t position) {
// special cases for positions 4 and 6
if (position == 4 || position == 6) {
if (character == '7') character = '&'; // "lowercase" 7
else if (character == 'A') character = 'a'; // A needs to be lowercase
else if (character == 'o') character = 'O'; // O needs to be uppercase
else if (character == 'L') character = '!'; // L needs to be in top half
else if (character == 'M' || character == 'm' || character == 'N') character = 'n'; // M and uppercase N need to be lowercase n
else if (character == 'c') character = 'C'; // C needs to be uppercase
else if (character == 'J') character = 'j'; // same
else if (character == 'v' || character == 'V' || character == 'U' || character == 'W' || character == 'w') character = 'u'; // bottom segment duplicated, so show in top half
} else {
if (character == 'u') character = 'v'; // we can use the bottom segment; move to lower half
else if (character == 'j') character = 'J'; // same but just display a normal J
}
if (position > 1) {
if (character == 'T') character = 't'; // uppercase T only works in positions 0 and 1
}
if (position == 1) {
if (character == 'o') character = 'O'; // O needs to be uppercase
if (character == 'i') character = 'l'; // I needs to be uppercase (use an l, it looks the same)
if (character == 'n') character = 'N'; // N needs to be uppercase
if (character == 'r') character = 'R'; // R needs to be uppercase
if (character == 'd') character = 'D'; // D needs to be uppercase
if (character == 'v' || character == 'V' || character == 'u') character = 'U'; // side segments shared, make uppercase
if (character == 'b') character = 'B'; // B needs to be uppercase
if (character == 'c') character = 'C'; // C needs to be uppercase
} else {
if (character == 'R') character = 'r'; // R needs to be lowercase almost everywhere
}
if (position == 0) {
watch_clear_pixel(0, 15); // clear funky ninth segment
} else {
if (character == 'I') character = 'l'; // uppercase I only works in position 0
}
uint64_t segmap = Segment_Map[position];
uint64_t segdata = Character_Set[character - 0x20];
for (int i = 0; i < 8; i++) {
uint8_t com = (segmap & 0xFF) >> 6;
if (com > 2) {
// COM3 means no segment exists; skip it.
segmap = segmap >> 8;
segdata = segdata >> 1;
continue;
}
uint8_t seg = segmap & 0x3F;
watch_clear_pixel(com, seg);
if (segdata & 1) watch_set_pixel(com, seg);
segmap = segmap >> 8;
segdata = segdata >> 1;
}
if (character == 'T' && position == 1) watch_set_pixel(1, 12); // add descender
else if (position == 0 && (character == 'B' || character == 'D')) watch_set_pixel(0, 15); // add funky ninth segment
else if (position == 1 && (character == 'B' || character == 'D' || character == '@')) watch_set_pixel(0, 12); // add funky ninth segment
}
void watch_display_string(char *string, uint8_t position) {
size_t i = 0;
while(string[i] != 0) {
watch_display_character(string[i], position + i);
i++;
if (position + i >= Num_Chars) break;
}
// uncomment this line to see screen output on terminal, i.e.
// FR 29
// 11 50 23
// note that for partial displays (positon > 0) it will only show the characters that were updated.
// printf("________\n %c%c %c%c\n%c%c %c%c %c%c\n--------\n", (position > 0) ? ' ' : string[0], (position > 1) ? ' ' : string[1 - position], (position > 2) ? ' ' : string[2 - position], (position > 3) ? ' ' : string[3 - position], (position > 4) ? ' ' : string[4 - position], (position > 5) ? ' ' : string[5 - position], (position > 6) ? ' ' : string[6 - position], (position > 7) ? ' ' : string[7 - position], (position > 8) ? ' ' : string[8 - position], (position > 9) ? ' ' : string[9 - position]);
}
void watch_set_colon(void) {
watch_set_pixel(1, 16);
}
void watch_clear_colon(void) {
watch_clear_pixel(1, 16);
}
void watch_set_indicator(WatchIndicatorSegment indicator) {
uint32_t value = IndicatorSegments[indicator];
uint8_t com = SLCD_COMNUM(value);
uint8_t seg = SLCD_SEGNUM(value);
watch_set_pixel(com, seg);
}
void watch_clear_indicator(WatchIndicatorSegment indicator) {
uint32_t value = IndicatorSegments[indicator];
uint8_t com = SLCD_COMNUM(value);
uint8_t seg = SLCD_SEGNUM(value);
watch_clear_pixel(com, seg);
}
void watch_clear_all_indicators(void) {
watch_clear_pixel(2, 17);
watch_clear_pixel(2, 16);
watch_clear_pixel(0, 17);
watch_clear_pixel(0, 16);
watch_clear_pixel(1, 10);
}

View File

@@ -0,0 +1,146 @@
/*
* 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.
*/
#ifndef _WATCH_PRIVATE_DISPLAY_H_INCLUDED
#define _WATCH_PRIVATE_DISPLAY_H_INCLUDED
#include "hpl_slcd_config.h"
#include "driver_init.h"
static const uint8_t Character_Set[] =
{
0b00000000, //
0b01100000, // ! (L in the top half for positions 4 and 6)
0b00100010, // "
0b01100011, // # (degree symbol, hash mark doesn't fit)
0b00000000, // $ (unused)
0b00000000, // % (unused)
0b01000100, // & ("lowercase 7" for positions 4 and 6)
0b00100000, // '
0b00111001, // (
0b00001111, // )
0b00000000, // * (unused)
0b11000000, // + (only works in position 0)
0b00000100, // ,
0b01000000, // -
0b01000000, // . (same as -, semantically most useful)
0b00010010, // /
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
0b00000000, // : (unused)
0b00000000, // ; (unused)
0b01011000, // <
0b01001000, // =
0b01001100, // >
0b01010011, // ?
0b11111111, // @ (all segments on)
0b01110111, // A
0b01111111, // B
0b00111001, // C
0b00111111, // D
0b01111001, // E
0b01110001, // F
0b00111101, // G
0b01110110, // H
0b10001001, // I (only works in position 0)
0b00001110, // J
0b01110101, // K
0b00111000, // L
0b10110111, // M (only works in position 0)
0b00110111, // N
0b00111111, // O
0b01110011, // P
0b01100111, // Q
0b11110111, // R (only works in position 1)
0b01101101, // S
0b10000001, // T (only works in position 0; set (1, 12) to make it work in position 1)
0b00111110, // U
0b00111110, // V
0b10111110, // W (only works in position 0)
0b01111110, // X
0b01101110, // Y
0b00011011, // Z
0b00111001, // [
0b00100100, // backslash
0b00001111, // ]
0b00100011, // ^
0b00001000, // _
0b00000010, // `
0b01011111, // a
0b01111100, // b
0b01011000, // c
0b01011110, // d
0b01111011, // e
0b01110001, // f
0b01101111, // g
0b01110100, // h
0b00010000, // i
0b01000010, // j (appears as superscript to work in more positions)
0b01110101, // k
0b00110000, // l
0b10110111, // m (only works in position 0)
0b01010100, // n
0b01011100, // o
0b01110011, // p
0b01100111, // q
0b01010000, // r
0b01101101, // s
0b01111000, // t
0b01100010, // u (appears in (u)pper half to work in more positions)
0b00011100, // v (looks like u but in the lower half)
0b10111110, // w (only works in position 0)
0b01111110, // x
0b01101110, // y
0b00011011, // z
0b00111001, // {
0b00110000, // |
0b00001111, // }
0b00000001, // ~
};
static const uint64_t Segment_Map[] = {
0x4e4f0e8e8f8d4d0d, // Position 0, mode
0xc8c4c4c8b4b4b0b, // Position 1, mode (Segments B and C shared, as are segments E and F)
0xc049c00a49890949, // Position 2, day of month (Segments A, D, G shared; missing segment F)
0xc048088886874707, // Position 3, day of month
0xc053921252139352, // Position 4, clock hours (Segments A and D shared)
0xc054511415559594, // Position 5, clock hours
0xc057965616179716, // Position 6, clock minutes (Segments A and D shared)
0xc041804000018a81, // Position 7, clock minutes
0xc043420203048382, // Position 8, clock seconds
0xc045440506468584, // Position 9, clock seconds
};
static const uint8_t Num_Chars = 10;
void watch_display_character(uint8_t character, uint8_t position);
#endif

View File

@@ -0,0 +1,164 @@
/*
* 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.
*/
#ifndef _WATCH_RTC_H_INCLUDED
#define _WATCH_RTC_H_INCLUDED
////< @file watch_rtc.h
#include "watch.h"
#include "hpl_calendar.h"
/** @addtogroup rtc Real-Time Clock
* @brief This section covers functions related to the SAM L22's real-time clock peripheral, including
* date, time and alarm functions.
* @details The real-time clock is the only peripheral that main.c enables for you. It is the cornerstone
* of low power operation on the watch, and it is required for several key functions that we
* assume will be available, namely the wake from BACKUP mode and the callback on the ALARM button.
* It is also required for the operation of the 1 Hz tick interrupt, which you will most likely use
* to wake from STANDBY mode.
*/
/// @{
#define WATCH_RTC_REFERENCE_YEAR (2020)
typedef union {
struct {
uint32_t second : 6; // 0-59
uint32_t minute : 6; // 0-59
uint32_t hour : 5; // 0-23
uint32_t day : 5; // 1-31
uint32_t month : 4; // 1-12
uint32_t year : 6; // 0-63 (representing 2020-2083)
} unit;
uint32_t reg; // the bit-packed value as expected by the RTC peripheral's CLOCK register.
} watch_date_time;
typedef enum watch_rtc_alarm_match {
ALARM_MATCH_DISABLED = 0,
ALARM_MATCH_SS,
ALARM_MATCH_MMSS,
ALARM_MATCH_HHMMSS,
} watch_rtc_alarm_match;
/** @brief Called by main.c to check if the RTC is enabled.
* You may call this function, but outside of app_init, it should always return true.
*/
bool _watch_rtc_is_enabled(void);
/** @brief Sets the date and time.
* @param date_time The date and time you wish to set, with a year value from 0-63 representing 2020-2083.
* @note The SAM L22 stores the year as six bits representing a value from 0 to 63. It treats this as a year
* offset from a reference year, which must be a leap year. Since 2020 was a leap year, and it allows
* useful dates through 2083, it is assumed that watch apps will use 2020 as the reference year; thus
* 1 means 2021, 2 means 2022, etc. **You will be responsible for handling this offset in your code**,
* if the calendar year is needed for timestamp calculation logic or display purposes.
*/
void watch_rtc_set_date_time(watch_date_time date_time);
/** @brief Returns the date and time.
* @return A watch_date_time with the current date and time, with a year value from 0-63 representing 2020-2083.
* @see watch_rtc_set_date_time for notes about how the year is stored.
*/
watch_date_time watch_rtc_get_date_time(void);
/** @brief Registers an alarm callback that will be called when the RTC time matches the target time, as masked
* by the provided mask.
* @param callback The function you wish to have called when the alarm fires. If this value is NULL, the alarm
* interrupt will still be enabled, but no callback function will be called.
* @param alarm_time The time that you wish to match. The date is currently ignored.
* @param mask One of the values in watch_rtc_alarm_match indicating which values to check.
* @details The alarm interrupt is a versatile tool for scheduling events in the future, especially since it can
* wake the device from all sleep modes. The key to its versatility is the mask parameter.
* Suppose we set an alarm for midnight, 00:00:00.
* * if mask is ALARM_MATCH_SS, the alarm will fire every minute when the clock ticks to seconds == 0.
* * with ALARM_MATCH_MMSS, the alarm will once an hour, at the top of each hour.
* * with ALARM_MATCH_HHMMSS, the alarm will fire at midnight every day.
* In theory the SAM L22's alarm function can match on days, months and even years, but I have not had
* success with this yet; as such, I am omitting these options for now.
*/
void watch_rtc_register_alarm_callback(ext_irq_cb_t callback, watch_date_time alarm_time, watch_rtc_alarm_match mask);
/** @brief Disables the alarm callback.
*/
void watch_rtc_disable_alarm_callback(void);
/** @brief Registers a "tick" callback that will be called once per second.
* @param callback The function you wish to have called when the clock ticks. If you pass in NULL, the tick
* interrupt will still be enabled, but no callback function will be called.
* @note this is equivalent to calling watch_rtc_register_periodic_callback with a frequency of 1. It can be
* disabled with either watch_rtc_disable_tick_callback() or watch_rtc_disable_periodic_callback(1),
* and will also be disabled when watch_rtc_disable_all_periodic_callbacks is called.
*/
void watch_rtc_register_tick_callback(ext_irq_cb_t callback);
/** @brief Disables the tick callback for the given period.
*/
void watch_rtc_disable_tick_callback(void);
/** @brief Registers a callback that will be called at a configurable period.
* @param callback The function you wish to have called at the specified period. If you pass in NULL, the periodic
* interrupt will still be enabled, but no callback function will be called.
* @param frequency The frequency of the tick in Hz. **Must be a power of 2**, from 1 to 128 inclusive.
* @note A 1 Hz tick (@see watch_rtc_register_tick_callback) is suitable for most applications, in that it gives you a
* chance to update the display once a second — an ideal update rate for a watch! If however you are displaying
* a value (such as an accelerometer output) that updates more frequently than once per second, you may want to
* tick at 16 or 32 Hz to update the screen more quickly. Just remember that the more frequent the tick, the more
* power your app will consume. Ideally you should enable the fast tick only when the user requires it (i.e. in
* response to an input event), and move back to the slow tick after some time.
*
* Also note that the RTC peripheral does not have sub-second resolution, so even if you set a 2 or 4 Hz interval,
* the system will not have any way of telling you where you are within a given second; watch_rtc_get_date_time
* will return the exact same timestamp until the second ticks over.
*/
void watch_rtc_register_periodic_callback(ext_irq_cb_t callback, uint8_t frequency);
/** @brief Disables the tick callback for the given period.
* @param frequency The frequency of the tick you wish to disable, in Hz. **Must be a power of 2**, from 1 to 128.
*/
void watch_rtc_disable_periodic_callback(uint8_t frequency);
/** @brief Disables all periodic callbacks, including the once-per-second tick callback.
*/
void watch_rtc_disable_all_periodic_callbacks(void);
/** @brief Sets the system date and time.
* @param date_time A struct representing the date and time you wish to set.
*/
__attribute__((deprecated("Use watch_rtc_set_date_time function instead")))
void watch_set_date_time(struct calendar_date_time date_time);
/** @brief Returns the system date and time in the provided struct.
* @param date_time A pointer to a calendar_date_time struct. It will have with the correct date and time on return.
*/
__attribute__((deprecated("Use the watch_rtc_get_date_time function instead")))
void watch_get_date_time(struct calendar_date_time *date_time);
/** @brief Registers a "tick" callback that will be called once per second.
* @param callback The function you wish to have called when the clock ticks. If you pass in NULL, the tick
* interrupt will still be enabled, but no callback function will be called.
*/
__attribute__((deprecated("Use the watch_rtc_register_tick_callback function instead")))
void watch_register_tick_callback(ext_irq_cb_t callback);
/// @}
#endif

View File

@@ -0,0 +1,151 @@
/*
* 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.
*/
#ifndef _WATCH_SLCD_H_INCLUDED
#define _WATCH_SLCD_H_INCLUDED
////< @file watch_slcd.h
#include "watch.h"
/** @addtogroup slcd Segment LCD Display
* @brief This section covers functions related to the Segment LCD display driver, which is responsible
* for displaying strings of characters and indicators on the main watch display.
* @details The segment LCD controller consumes about 3 microamperes of power with no segments on, and
* about 4 microamperes with all segments on. There is also a slight power impact associated
* with updating the screen (about 1 microampere to update at 1 Hz). For the absolute lowest
* power operation, update the display only when its contents have changed, and disable the
* SLCD peripheral when the screen is not in use.
* For a map of all common and segment pins, see <a href="segmap.html">segmap.html</a>. You can
* hover over any segment in that diagram to view the common and segment pins associated with
* each segment of the display.
*/
/// @{
/// An enum listing the icons and indicators available on the watch.
typedef enum WatchIndicatorSegment {
WATCH_INDICATOR_SIGNAL = 0, ///< The hourly signal indicator; also useful for indicating that sensors are on.
WATCH_INDICATOR_BELL, ///< The small bell indicating that an alarm is set.
WATCH_INDICATOR_PM, ///< The PM indicator, indicating that a time is in the afternoon.
WATCH_INDICATOR_24H, ///< The 24H indicator, indicating that the watch is in a 24-hour mode.
WATCH_INDICATOR_LAP ///< The LAP indicator; the F-91W uses this in its stopwatch UI.
} WatchIndicatorSegment;
/** @brief Enables the Segment LCD display.
* Call this before attempting to set pixels or display strings.
*/
void watch_enable_display(void);
/** @brief Sets a pixel. Use this to manually set a pixel with a given common and segment number.
* See <a href="segmap.html">segmap.html</a>.
* @param com the common pin, numbered from 0-2.
* @param seg the segment pin, numbered from 0-23.
*/
void watch_set_pixel(uint8_t com, uint8_t seg);
/** @brief Clears a pixel. Use this to manually clear a pixel with a given common and segment number.
* See <a href="segmap.html">segmap.html</a>.
* @param com the common pin, numbered from 0-2.
* @param seg the segment pin, numbered from 0-23.
*/
void watch_clear_pixel(uint8_t com, uint8_t seg);
/** @brief Clears all segments of the display, including incicators and the colon.
*/
void watch_clear_display(void);
/** @brief Displays a string at the given position, starting from the top left. There are ten digits.
A space in any position will clear that digit.
* @param string A null-terminated string.
* @param position The position where you wish to start displaying the string. The day of week digits
* are positions 0 and 1; the day of month digits are positions 2 and 3, and the main
* clock line occupies positions 4-9.
* @note This method does not clear the display; if for example you display a two-character string at
position 0, positions 2-9 will retain whatever state they were previously displaying.
*/
void watch_display_string(char *string, uint8_t position);
/** @brief Turns the colon segment on.
*/
void watch_set_colon(void);
/** @brief Turns the colon segment off.
*/
void watch_clear_colon(void);
/** @brief Sets an indicator on the LCD. Use this to turn on one of the indicator segments.
* @param indicator One of the indicator segments from the enum. @see WatchIndicatorSegment
*/
void watch_set_indicator(WatchIndicatorSegment indicator);
/** @brief Clears an indicator on the LCD. Use this to turn off one of the indicator segments.
* @param indicator One of the indicator segments from the enum. @see WatchIndicatorSegment
*/
void watch_clear_indicator(WatchIndicatorSegment indicator);
/** @brief Clears all indicator segments.
* @see WatchIndicatorSegment
*/
void watch_clear_all_indicators(void);
/** @brief Blinks a single character in position 7. Does not affect other positions.
* @details Six of the seven segments in position 7 (and only position 7) are capable of autonomous
* blinking. This blinking does not require any CPU resources, and will continue even in
* STANDBY and Sleep mode (but not Deep Sleep mode, since that mode turns off the LCD).
* @param character The character you wish to blink.
* @param duration The duration of the on/off cycle in milliseconds, from 50 to ~4250 ms.
* @note Segment B of position 7 cannot blink autonomously, so not all characters will work well.
* Supported characters for blinking:
* * Punctuation: underscore, apostrophe, comma, hyphen, equals sign, tilde (top segment only)
* * Numbers: 5, 6, ampersand (lowercase 7)
* * Letters: b, C, c, E, F, h, i, L, l, n, o, S, t
*/
void watch_start_character_blink(char character, uint32_t duration);
/** @brief Stops and clears all blinking segments.
* @details This will stop all blinking in position 7, and clear all segments in that digit.
*/
void watch_stop_blink(void);
/** @brief Begins a two-segment "tick-tock" animation in position 8.
* @details Six of the seven segments in position 8 (and only position 8) are capable of autonomous
* animation. This animation is very basic, and consists of moving a bit pattern forward
* or backward in a shift register whose positions map to fixed segments on the LCD. Given
* this constraint, an animation across all six segments does not make sense; so the watch
* library offers only a simple "tick/tock" in segments D and E. This animation does not
* require any CPU resources, and will continue even in STANDBY and Sleep mode (but not Deep
* Sleep mode, since that mode turns off the LCD).
* @param duration The duration of each frame in ms. 500 milliseconds produces a classic tick/tock.
*/
void watch_start_tick_animation(uint32_t duration);
/** @brief Checks if the tick animation is currently running.
* @return true if the animation is running; false otherwise.
*/
bool watch_tick_animation_is_running(void);
/** @brief Stops the tick/tock animation and clears all animating segments.
* @details This will stop the animation and clear all segments in position 8.
*/
void watch_stop_tick_animation(void);
/// @}
#endif

View File

@@ -0,0 +1,58 @@
/*
* 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.
*/
#ifndef _WATCH_UART_H_INCLUDED
#define _WATCH_UART_H_INCLUDED
////< @file watch_uart.h
#include "watch.h"
/** @addtogroup debug Debug UART
* @brief This section covers functions related to the debug UART, available on
* pin D1 of the 9-pin connector.
* @warning These functions were used early on in development, before the TinyUSB
* CDC was implemented. You can now print debug messages to the USB console
* using printf, rendering this bit irrelevant. These methods will likely
* be refactored out in the future, in favor of a more full-featured UART
* on the nine-pin connector.
**/
/// @{
/** @brief Initializes the debug UART.
* @param baud The baud rate
*/
__attribute__((deprecated("Use printf to log debug messages over USB.")))
void watch_enable_debug_uart(uint32_t baud);
/** @brief Outputs a single character on the debug UART.
* @param c The character you wish to output.
*/
__attribute__((deprecated("Use printf to log debug messages over USB.")))
void watch_debug_putc(char c);
/** @brief Outputs a string on the debug UART.
* @param s A null-terminated string.
*/
__attribute__((deprecated("Use printf to log debug messages over USB.")))
void watch_debug_puts(char *s);
/// @}
#endif

View File

@@ -0,0 +1,172 @@
/*
* MIT License
*
* Copyright (c) 2021 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 <math.h>
#include "watch_utility.h"
const char * watch_utility_get_weekday(watch_date_time date_time) {
static const char weekdays[7][3] = {"SA", "SU", "MO", "TU", "WE", "TH", "FR"};
date_time.unit.year += 20;
if (date_time.unit.month <= 2) {
date_time.unit.month += 12;
date_time.unit.year--;
}
return weekdays[(date_time.unit.day + 13 * (date_time.unit.month + 1) / 5 + date_time.unit.year + date_time.unit.year / 4 + 525) % 7];
}
uint32_t watch_utility_convert_to_unix_time(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, uint32_t utc_offset) {
uint16_t DAYS_SO_FAR[] = {
0, // Jan
31, // Feb
59, // March
90, // April
120, // May
151, // June
181, // July
212, // August
243, // September
273, // October
304, // November
334 // December
};
uint32_t year_adj = year + 4800;
uint32_t febs = year_adj - (month <= 2 ? 1 : 0); /* Februaries since base. */
uint32_t leap_days = 1 + (febs / 4) - (febs / 100) + (febs / 400);
uint32_t days = 365 * year_adj + leap_days + DAYS_SO_FAR[month - 1] + day - 1;
days -= 2472692; /* Adjust to Unix epoch. */
uint32_t timestamp = days * 86400;
timestamp += hour * 3600;
timestamp += minute * 60;
timestamp += second;
timestamp -= utc_offset;
return timestamp;
}
uint32_t watch_utility_date_time_to_unix_time(watch_date_time date_time, uint32_t utc_offset) {
return watch_utility_convert_to_unix_time(date_time.unit.year + WATCH_RTC_REFERENCE_YEAR, date_time.unit.month, date_time.unit.day, date_time.unit.hour, date_time.unit.minute, date_time.unit.second, utc_offset);
}
#define LEAPOCH (946684800LL + 86400*(31+29))
#define DAYS_PER_400Y (365*400 + 97)
#define DAYS_PER_100Y (365*100 + 24)
#define DAYS_PER_4Y (365*4 + 1)
watch_date_time watch_utility_date_time_from_unix_time(uint32_t timestamp, uint32_t utc_offset) {
watch_date_time retval;
retval.reg = 0;
int32_t days, secs;
int32_t remdays, remsecs, remyears;
int32_t qc_cycles, c_cycles, q_cycles;
int32_t years, months;
int32_t wday, yday, leap;
static const int8_t days_in_month[] = {31,30,31,30,31,31,30,31,30,31,31,29};
timestamp += utc_offset;
secs = timestamp - LEAPOCH;
days = secs / 86400;
remsecs = secs % 86400;
if (remsecs < 0) {
remsecs += 86400;
days--;
}
wday = (3+days)%7;
if (wday < 0) wday += 7;
qc_cycles = (int)(days / DAYS_PER_400Y);
remdays = days % DAYS_PER_400Y;
if (remdays < 0) {
remdays += DAYS_PER_400Y;
qc_cycles--;
}
c_cycles = remdays / DAYS_PER_100Y;
if (c_cycles == 4) c_cycles--;
remdays -= c_cycles * DAYS_PER_100Y;
q_cycles = remdays / DAYS_PER_4Y;
if (q_cycles == 25) q_cycles--;
remdays -= q_cycles * DAYS_PER_4Y;
remyears = remdays / 365;
if (remyears == 4) remyears--;
remdays -= remyears * 365;
leap = !remyears && (q_cycles || !c_cycles);
yday = remdays + 31 + 28 + leap;
if (yday >= 365+leap) yday -= 365+leap;
years = remyears + 4*q_cycles + 100*c_cycles + 400*qc_cycles;
for (months=0; days_in_month[months] <= remdays; months++)
remdays -= days_in_month[months];
years += 2000;
months += 2;
if (months >= 12) {
months -=12;
years++;
}
if (years < 2020 || years > 2083) return retval;
retval.unit.year = years - WATCH_RTC_REFERENCE_YEAR;
retval.unit.month = months + 1;
retval.unit.day = remdays + 1;
retval.unit.hour = remsecs / 3600;
retval.unit.minute = remsecs / 60 % 60;
retval.unit.second = remsecs % 60;
return retval;
}
watch_date_time watch_utility_date_time_convert_zone(watch_date_time date_time, uint32_t origin_utc_offset, uint32_t destination_utc_offset) {
uint32_t timestamp = watch_utility_date_time_to_unix_time(date_time, origin_utc_offset);
return watch_utility_date_time_from_unix_time(timestamp, destination_utc_offset);
}
float watch_utility_thermistor_temperature(uint16_t value, bool highside, float b_coefficient, float nominal_temperature, float nominal_resistance, float series_resistance) {
float reading = (float)value;
if (highside) {
reading = (1023.0 * series_resistance) / (reading / 64.0);
reading -= series_resistance;
} else {
reading = series_resistance / (65535.0 / value - 1.0);
}
reading = reading / nominal_resistance;
reading = log(reading);
reading /= b_coefficient;
reading += 1.0 / (nominal_temperature + 273.15);
reading = 1.0 / reading;
reading -= 273.15;
return reading;
}

View File

@@ -0,0 +1,100 @@
/*
* MIT License
*
* Copyright (c) 2021 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.
*/
#ifndef _WATCH_UTILITY_H_INCLUDED
#define _WATCH_UTILITY_H_INCLUDED
////< @file watch_utility.h
#include "watch.h"
/** @addtogroup utility Utility Functions
* @brief This section covers various useful functions that don't fit anywhere else.
**/
/// @{
/** @brief Returns a two-letter weekday for the given timestamp, suitable for display
* in positions 0-1 of the watch face
* @param date_time The watch_date_time whose weekday you want.
*/
const char * watch_utility_get_weekday(watch_date_time date_time);
/** @brief Returns the UNIX time (seconds since 1970) for a given date/time in UTC.
* @param date_time The watch_date_time that you wish to convert.
* @param year The year of the date you wish to convert.
* @param month The month of the date you wish to convert.
* @param day The day of the date you wish to convert.
* @param hour The hour of the date you wish to convert.
* @param minute The minute of the date you wish to convert.
* @param second The second of the date you wish to convert.
* @param utc_offset The number of seconds that date_time is offset from UTC, or 0 if the time is UTC.
* @return A UNIX timestamp for the given date/time and UTC offset.
* @note Implemented by Wesley Ellis (tahnok) and based on BSD-licensed code by Josh Haberman:
* https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html
*/
uint32_t watch_utility_convert_to_unix_time(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute, uint8_t second, uint32_t utc_offset);
/** @brief Returns the UNIX time (seconds since 1970) for a given watch_date_time struct.
* @param date_time The watch_date_time that you wish to convert.
* @param utc_offset The number of seconds that date_time is offset from UTC, or 0 if the time is UTC.
* @return A UNIX timestamp for the given watch_date_time and UTC offset.
*/
uint32_t watch_utility_date_time_to_unix_time(watch_date_time date_time, uint32_t utc_offset);
/** @brief Returns the UNIX time (seconds since 1970) for a given watch_date_time struct.
* @param timestamp The UNIX timestamp that you wish to convert.
* @param utc_offset The number of seconds that you wish date_time to be offset from UTC.
* @return A watch_date_time for the given UNIX timestamp and UTC offset, or if outside the range that
* watch_date_time can represent, a watch_date_time with all fields set to 0.
* @note Adapted from MIT-licensed code from musl, Copyright © 2005-2014 Rich Felker, et al.:
* https://github.com/esmil/musl/blob/1cc81f5cb0df2b66a795ff0c26d7bbc4d16e13c6/src/time/__secs_to_tm.c
*/
watch_date_time watch_utility_date_time_from_unix_time(uint32_t timestamp, uint32_t utc_offset);
/** @brief Converts a time from a given time zone to another time zone.
* @param date_time The watch_date_time that you wish to convert
* @param origin_utc_offset The number of seconds from UTC in the origin time zone
* @param destination_utc_offset The number of seconds from UTC in the destination time zone
* @return A watch_date_time for the given UNIX timestamp and UTC offset, or if outside the range that
* watch_date_time can represent, a watch_date_time with all fields set to 0.
* @note Adapted from MIT-licensed code from musl, Copyright © 2005-2014 Rich Felker, et al.:
* https://github.com/esmil/musl/blob/1cc81f5cb0df2b66a795ff0c26d7bbc4d16e13c6/src/time/__secs_to_tm.c
*/
watch_date_time watch_utility_date_time_convert_zone(watch_date_time date_time, uint32_t origin_utc_offset, uint32_t destination_utc_offset);
/** @brief Returns a temperature in degrees Celsius for a given thermistor voltage divider circuit.
* @param value The raw analog reading from the thermistor pin (0-65535)
* @param highside True if the thermistor is connected to VCC and the series resistor is connected
* to GND; false if the thermistor is connected to GND and the series resistor is
* connected to VCC.
* @param b_coefficient From your thermistor's data sheet, the B25/85 coefficient. A typical value
* will be between 2000 and 5000.
* @param nominal_temperature From your thermistor's data sheet, the temperature (in Celsius) at
* which the thermistor's resistance is at its nominal value.
* @param nominal_resistance The thermistor's resistance at the nominal temperature.
* @param series_resistance The value of the other resistor in the voltage divider.
* @note Ported from Adafruit's MIT-licensed CircuitPython thermistor code, (c) 2017 Scott Shawcroft:
* https://github.com/adafruit/Adafruit_CircuitPython_Thermistor/blob/main/adafruit_thermistor.py
*/
float watch_utility_thermistor_temperature(uint16_t value, bool highside, float b_coefficient, float nominal_temperature, float nominal_resistance, float series_resistance);
#endif