summaryrefslogtreecommitdiffstats
path: root/watch-library/shared/watch
diff options
context:
space:
mode:
Diffstat (limited to 'watch-library/shared/watch')
-rw-r--r--watch-library/shared/watch/watch.h83
-rw-r--r--watch-library/shared/watch/watch_adc.h174
-rw-r--r--watch-library/shared/watch/watch_app.h108
-rw-r--r--watch-library/shared/watch/watch_buzzer.h164
-rw-r--r--watch-library/shared/watch/watch_deepsleep.h159
-rw-r--r--watch-library/shared/watch/watch_extint.h85
-rw-r--r--watch-library/shared/watch/watch_gpio.h76
-rw-r--r--watch-library/shared/watch/watch_i2c.h106
-rw-r--r--watch-library/shared/watch/watch_led.h93
-rw-r--r--watch-library/shared/watch/watch_private.h50
-rw-r--r--watch-library/shared/watch/watch_private_display.c137
-rw-r--r--watch-library/shared/watch/watch_private_display.h146
-rw-r--r--watch-library/shared/watch/watch_rtc.h164
-rw-r--r--watch-library/shared/watch/watch_slcd.h151
-rw-r--r--watch-library/shared/watch/watch_uart.h58
-rw-r--r--watch-library/shared/watch/watch_utility.c172
-rw-r--r--watch-library/shared/watch/watch_utility.h100
17 files changed, 2026 insertions, 0 deletions
diff --git a/watch-library/shared/watch/watch.h b/watch-library/shared/watch/watch.h
new file mode 100644
index 00000000..064f90ec
--- /dev/null
+++ b/watch-library/shared/watch/watch.h
@@ -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_ */ \ No newline at end of file
diff --git a/watch-library/shared/watch/watch_adc.h b/watch-library/shared/watch/watch_adc.h
new file mode 100644
index 00000000..d4c8586d
--- /dev/null
+++ b/watch-library/shared/watch/watch_adc.h
@@ -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
diff --git a/watch-library/shared/watch/watch_app.h b/watch-library/shared/watch/watch_app.h
new file mode 100644
index 00000000..4fa29df8
--- /dev/null
+++ b/watch-library/shared/watch/watch_app.h
@@ -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
diff --git a/watch-library/shared/watch/watch_buzzer.h b/watch-library/shared/watch/watch_buzzer.h
new file mode 100644
index 00000000..1b5d197c
--- /dev/null
+++ b/watch-library/shared/watch/watch_buzzer.h
@@ -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
diff --git a/watch-library/shared/watch/watch_deepsleep.h b/watch-library/shared/watch/watch_deepsleep.h
new file mode 100644
index 00000000..56d75478
--- /dev/null
+++ b/watch-library/shared/watch/watch_deepsleep.h
@@ -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
diff --git a/watch-library/shared/watch/watch_extint.h b/watch-library/shared/watch/watch_extint.h
new file mode 100644
index 00000000..452461b3
--- /dev/null
+++ b/watch-library/shared/watch/watch_extint.h
@@ -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
diff --git a/watch-library/shared/watch/watch_gpio.h b/watch-library/shared/watch/watch_gpio.h
new file mode 100644
index 00000000..fc43642c
--- /dev/null
+++ b/watch-library/shared/watch/watch_gpio.h
@@ -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
diff --git a/watch-library/shared/watch/watch_i2c.h b/watch-library/shared/watch/watch_i2c.h
new file mode 100644
index 00000000..fbcc1a92
--- /dev/null
+++ b/watch-library/shared/watch/watch_i2c.h
@@ -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
diff --git a/watch-library/shared/watch/watch_led.h b/watch-library/shared/watch/watch_led.h
new file mode 100644
index 00000000..9e9f5640
--- /dev/null
+++ b/watch-library/shared/watch/watch_led.h
@@ -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
diff --git a/watch-library/shared/watch/watch_private.h b/watch-library/shared/watch/watch_private.h
new file mode 100644
index 00000000..7bb91d1f
--- /dev/null
+++ b/watch-library/shared/watch/watch_private.h
@@ -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
diff --git a/watch-library/shared/watch/watch_private_display.c b/watch-library/shared/watch/watch_private_display.c
new file mode 100644
index 00000000..87e3f1ae
--- /dev/null
+++ b/watch-library/shared/watch/watch_private_display.c
@@ -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);
+}
diff --git a/watch-library/shared/watch/watch_private_display.h b/watch-library/shared/watch/watch_private_display.h
new file mode 100644
index 00000000..08597848
--- /dev/null
+++ b/watch-library/shared/watch/watch_private_display.h
@@ -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
diff --git a/watch-library/shared/watch/watch_rtc.h b/watch-library/shared/watch/watch_rtc.h
new file mode 100644
index 00000000..6dac63f5
--- /dev/null
+++ b/watch-library/shared/watch/watch_rtc.h
@@ -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
diff --git a/watch-library/shared/watch/watch_slcd.h b/watch-library/shared/watch/watch_slcd.h
new file mode 100644
index 00000000..3f550bb0
--- /dev/null
+++ b/watch-library/shared/watch/watch_slcd.h
@@ -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
diff --git a/watch-library/shared/watch/watch_uart.h b/watch-library/shared/watch/watch_uart.h
new file mode 100644
index 00000000..3e98bd35
--- /dev/null
+++ b/watch-library/shared/watch/watch_uart.h
@@ -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
diff --git a/watch-library/shared/watch/watch_utility.c b/watch-library/shared/watch/watch_utility.c
new file mode 100644
index 00000000..835076d9
--- /dev/null
+++ b/watch-library/shared/watch/watch_utility.c
@@ -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;
+}
diff --git a/watch-library/shared/watch/watch_utility.h b/watch-library/shared/watch/watch_utility.h
new file mode 100644
index 00000000..66af6ed5
--- /dev/null
+++ b/watch-library/shared/watch/watch_utility.h
@@ -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