summaryrefslogtreecommitdiffstats
path: root/watch-library/hardware/watch
diff options
context:
space:
mode:
authorAlexsander Akers <me@a2.io>2022-01-25 15:03:22 -0500
committerGitHub <noreply@github.com>2022-01-25 15:03:22 -0500
commitb8de35658ffd78ad8b22f91ccbbd3d63663afda9 (patch)
tree1f265ddfcc8e5abf0316b81b15f80bf5c70fa7b7 /watch-library/hardware/watch
parent9e24f6c336773c7404139ab4db0eaab2f99504e2 (diff)
downloadSensor-Watch-b8de35658ffd78ad8b22f91ccbbd3d63663afda9.tar.gz
Sensor-Watch-b8de35658ffd78ad8b22f91ccbbd3d63663afda9.tar.bz2
Sensor-Watch-b8de35658ffd78ad8b22f91ccbbd3d63663afda9.zip
Sensor Watch Simulator (#35)
* Put something on screen * Use the 32bit watch_date_time repr to pass from JS * Implement periodic callbacks * Clear display on enabling * Hook up watch_set_led_color() to SVG (green-only) * Make debug output full-width * Remove default Emscripten canvas * Implement sleep and button clicks * Fix time zone conversion bug in beats-time app * Clean up warnings * Fix pin levels * Set time zone to browser value (if available) * Add basic backup data saving * Silence format specifier warnings in both targets * Remove unnecessary, copied files * Use RTC pointer to clear callbacks (if available) * Use preprocessor define to avoid hardcoding MOVEMENT_NUM_FACES * Change each face to const preprocessor definition * Remove Intl.DateTimeFormat usage * Update shell.html title, header * Add touch start/end event handlers on SVG buttons * Update shell.html * Update folder structure (shared, simulator, hardware under watch-library) * Tease out shared components from watch_slcd * Clean up simulator watch_slcd.c inline JS calls * Fix missing newlines at end of file * Add simulator warnings (except format, unused-paremter) * Implement remaining watch_rtc functions * Fix button bug on mouse down then drag out * Implement remaining watch_slcd functions * Link keyboard events to buttons (for keys A, L, M) * Rewrite event handling (mouse, touch, keyboard) in C * Set explicit text UTF-8 charset in shell.html * Address PR comments * Remove unused directories from include paths
Diffstat (limited to 'watch-library/hardware/watch')
-rw-r--r--watch-library/hardware/watch/tusb_config.h91
-rw-r--r--watch-library/hardware/watch/watch.c44
-rw-r--r--watch-library/hardware/watch/watch_adc.c179
-rw-r--r--watch-library/hardware/watch/watch_buzzer.c64
-rw-r--r--watch-library/hardware/watch/watch_deepsleep.c209
-rw-r--r--watch-library/hardware/watch/watch_extint.c111
-rw-r--r--watch-library/hardware/watch/watch_gpio.c61
-rw-r--r--watch-library/hardware/watch/watch_i2c.c93
-rw-r--r--watch-library/hardware/watch/watch_led.c69
-rw-r--r--watch-library/hardware/watch/watch_private.c423
-rw-r--r--watch-library/hardware/watch/watch_rtc.c195
-rw-r--r--watch-library/hardware/watch/watch_slcd.c101
-rw-r--r--watch-library/hardware/watch/watch_uart.c89
13 files changed, 1729 insertions, 0 deletions
diff --git a/watch-library/hardware/watch/tusb_config.h b/watch-library/hardware/watch/tusb_config.h
new file mode 100644
index 00000000..a22b2b99
--- /dev/null
+++ b/watch-library/hardware/watch/tusb_config.h
@@ -0,0 +1,91 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * 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 _TUSB_CONFIG_H_
+#define _TUSB_CONFIG_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+//--------------------------------------------------------------------
+// COMMON CONFIGURATION
+//--------------------------------------------------------------------
+
+// defined by board.mk
+#define CFG_TUSB_MCU OPT_MCU_SAML22
+
+#define BOARD_DEVICE_RHPORT_NUM 0
+#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED
+#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED)
+
+#define CFG_TUSB_OS OPT_OS_NONE
+
+// disable TinyUSB debug. our printf method prints stuff to the USB console, so you just get infinite noise.
+// if you need to debug tinyUSB issues, use the alternate _write function in watch_private.c to echo to the UART.
+#define CFG_TUSB_DEBUG 0
+
+/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
+ * Tinyusb use follows macros to declare transferring memory so that they can be put
+ * into those specific section.
+ * e.g
+ * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
+ * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
+ */
+#ifndef CFG_TUSB_MEM_SECTION
+#define CFG_TUSB_MEM_SECTION
+#endif
+
+#ifndef CFG_TUSB_MEM_ALIGN
+#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
+#endif
+
+//--------------------------------------------------------------------
+// DEVICE CONFIGURATION
+//--------------------------------------------------------------------
+
+#ifndef CFG_TUD_ENDPOINT0_SIZE
+#define CFG_TUD_ENDPOINT0_SIZE 64
+#endif
+
+//------------- CLASS -------------//
+#define CFG_TUD_CDC 1
+#define CFG_TUD_MSC 0
+#define CFG_TUD_HID 0
+#define CFG_TUD_MIDI 0
+#define CFG_TUD_VENDOR 0
+
+// CDC FIFO size of TX and RX
+#define CFG_TUD_CDC_RX_BUFSIZE (64)
+#define CFG_TUD_CDC_TX_BUFSIZE (64)
+
+// CDC Endpoint transfer buffer size, more is faster
+#define CFG_TUD_CDC_EP_BUFSIZE (64)
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* _TUSB_CONFIG_H_ */
diff --git a/watch-library/hardware/watch/watch.c b/watch-library/hardware/watch/watch.c
new file mode 100644
index 00000000..791fd974
--- /dev/null
+++ b/watch-library/hardware/watch/watch.c
@@ -0,0 +1,44 @@
+/*
+ * 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 "watch.h"
+
+bool battery_is_low = false;
+
+// receives interrupts from MCLK, OSC32KCTRL, OSCCTRL, PAC, PM, SUPC and TAL, whatever that is.
+void SYSTEM_Handler(void) {
+ if (SUPC->INTFLAG.bit.BOD33DET) {
+ battery_is_low = true;
+ SUPC->INTENCLR.bit.BOD33DET = 1;
+ SUPC->INTFLAG.reg &= ~SUPC_INTFLAG_BOD33DET;
+ }
+}
+
+bool watch_is_battery_low(void) {
+ return battery_is_low;
+}
+
+bool watch_is_buzzer_or_led_enabled(void){
+ return hri_mclk_get_APBCMASK_TCC0_bit(MCLK);
+}
diff --git a/watch-library/hardware/watch/watch_adc.c b/watch-library/hardware/watch/watch_adc.c
new file mode 100644
index 00000000..5ba7abdf
--- /dev/null
+++ b/watch-library/hardware/watch/watch_adc.c
@@ -0,0 +1,179 @@
+/*
+ * 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_adc.h"
+
+static void _watch_sync_adc(void) {
+ while (ADC->SYNCBUSY.reg);
+}
+
+static uint16_t _watch_get_analog_value(uint16_t channel) {
+ if (ADC->INPUTCTRL.bit.MUXPOS != channel) {
+ ADC->INPUTCTRL.bit.MUXPOS = channel;
+ _watch_sync_adc();
+ }
+
+ ADC->SWTRIG.bit.START = 1;
+ while (!ADC->INTFLAG.bit.RESRDY);
+
+ return ADC->RESULT.reg;
+}
+
+void watch_enable_adc(void) {
+ MCLK->APBCMASK.reg |= MCLK_APBCMASK_ADC;
+ GCLK->PCHCTRL[ADC_GCLK_ID].reg = GCLK_PCHCTRL_GEN_GCLK0 | GCLK_PCHCTRL_CHEN;
+
+ uint16_t calib_reg = 0;
+ calib_reg = ADC_CALIB_BIASREFBUF((*(uint32_t *)ADC_FUSES_BIASREFBUF_ADDR >> ADC_FUSES_BIASREFBUF_Pos)) |
+ ADC_CALIB_BIASCOMP((*(uint32_t *)ADC_FUSES_BIASCOMP_ADDR >> ADC_FUSES_BIASCOMP_Pos));
+
+ if (!ADC->SYNCBUSY.bit.SWRST) {
+ if (ADC->CTRLA.bit.ENABLE) {
+ ADC->CTRLA.bit.ENABLE = 0;
+ _watch_sync_adc();
+ }
+ ADC->CTRLA.bit.SWRST = 1;
+ }
+ _watch_sync_adc();
+
+ if (USB->DEVICE.CTRLA.bit.ENABLE) {
+ // if USB is enabled, we are running an 8 MHz clock.
+ // divide by 16 for a 500kHz ADC clock.
+ ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV16_Val;
+ } else {
+ // otherwise it's 4 Mhz. divide by 8 for a 500kHz ADC clock.
+ ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV8_Val;
+ }
+ ADC->CALIB.reg = calib_reg;
+ ADC->REFCTRL.bit.REFSEL = ADC_REFCTRL_REFSEL_INTVCC2_Val;
+ ADC->INPUTCTRL.bit.MUXNEG = ADC_INPUTCTRL_MUXNEG_GND_Val;
+ ADC->CTRLC.bit.RESSEL = ADC_CTRLC_RESSEL_16BIT_Val;
+ ADC->AVGCTRL.bit.SAMPLENUM = ADC_AVGCTRL_SAMPLENUM_16_Val;
+ ADC->SAMPCTRL.bit.SAMPLEN = 0;
+ ADC->INTENSET.reg = ADC_INTENSET_RESRDY;
+ ADC->CTRLA.bit.ENABLE = 1;
+ _watch_sync_adc();
+ // throw away one measurement after reference change (the channel doesn't matter).
+ _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_SCALEDCOREVCC);
+}
+
+void watch_enable_analog_input(const uint8_t pin) {
+ gpio_set_pin_direction(pin, GPIO_DIRECTION_OFF);
+ switch (pin) {
+ case A0:
+ gpio_set_pin_function(pin, PINMUX_PB04B_ADC_AIN12);
+ break;
+ case A1:
+ gpio_set_pin_function(pin, PINMUX_PB01B_ADC_AIN9);
+ break;
+ case A2:
+ gpio_set_pin_function(pin, PINMUX_PB02B_ADC_AIN10);
+ break;
+ case A3:
+ gpio_set_pin_function(pin, PINMUX_PB03B_ADC_AIN11);
+ break;
+ case A4:
+ gpio_set_pin_function(pin, PINMUX_PB00B_ADC_AIN8);
+ break;
+ default:
+ return;
+ }
+}
+
+uint16_t watch_get_analog_pin_level(const uint8_t pin) {
+ switch (pin) {
+ case A0:
+ return _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_AIN12_Val);
+ case A1:
+ return _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_AIN9_Val);
+ case A2:
+ return _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_AIN10_Val);
+ case A3:
+ return _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_AIN11_Val);
+ case A4:
+ return _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_AIN8_Val);
+ default:
+ return 0;
+ }
+}
+
+void watch_set_analog_num_samples(uint16_t samples) {
+ // ignore any input that's not a power of 2 (i.e. only one bit set)
+ if (__builtin_popcount(samples) != 1) return;
+ // if only one bit is set, counting the trailing zeroes is equivalent to log2(samples)
+ uint8_t sample_val = __builtin_ctz(samples);
+ // make sure the desired value is within range and set it, if so.
+ if (sample_val <= ADC_AVGCTRL_SAMPLENUM_1024_Val) {
+ ADC->AVGCTRL.bit.SAMPLENUM = sample_val;
+ _watch_sync_adc();
+ }
+}
+
+void watch_set_analog_sampling_length(uint8_t cycles) {
+ // for clarity the API asks the user how many cycles they want the measurement to take.
+ // but the ADC always needs at least one cycle; it just wants to know how many *extra* cycles we want.
+ // so we subtract one from the user-provided value, and clamp to the maximum.
+ ADC->SAMPCTRL.bit.SAMPLEN = (cycles - 1) & 0x3F;
+ _watch_sync_adc();
+}
+
+void watch_set_analog_reference_voltage(watch_adc_reference_voltage reference) {
+ ADC->CTRLA.bit.ENABLE = 0;
+
+ if (reference == ADC_REFERENCE_INTREF) SUPC->VREF.bit.VREFOE = 1;
+ else SUPC->VREF.bit.VREFOE = 0;
+
+ ADC->REFCTRL.bit.REFSEL = reference;
+ ADC->CTRLA.bit.ENABLE = 1;
+ _watch_sync_adc();
+ // throw away one measurement after reference change (the channel doesn't matter).
+ _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_SCALEDCOREVCC);
+}
+
+uint16_t watch_get_vcc_voltage(void) {
+ // stash the previous reference so we can restore it when we're done.
+ uint8_t oldref = ADC->REFCTRL.bit.REFSEL;
+
+ // if we weren't already using the internal reference voltage, select it now.
+ if (oldref != ADC_REFERENCE_INTREF) watch_set_analog_reference_voltage(ADC_REFERENCE_INTREF);
+
+ // get the data
+ uint32_t raw_val = _watch_get_analog_value(ADC_INPUTCTRL_MUXPOS_SCALEDIOVCC_Val);
+
+ // restore the old reference, if needed.
+ if (oldref != ADC_REFERENCE_INTREF) watch_set_analog_reference_voltage(oldref);
+
+ return (uint16_t)((raw_val * 1000) / (1024 * 1 << ADC->AVGCTRL.bit.SAMPLENUM));
+}
+
+inline void watch_disable_analog_input(const uint8_t pin) {
+ gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF);
+}
+
+inline void watch_disable_adc(void) {
+ ADC->CTRLA.bit.ENABLE = 0;
+ _watch_sync_adc();
+
+ MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_ADC;
+}
diff --git a/watch-library/hardware/watch/watch_buzzer.c b/watch-library/hardware/watch/watch_buzzer.c
new file mode 100644
index 00000000..a275b00d
--- /dev/null
+++ b/watch-library/hardware/watch/watch_buzzer.c
@@ -0,0 +1,64 @@
+/*
+ * 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_buzzer.h"
+
+ inline void watch_enable_buzzer(void) {
+ if (!hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) {
+ _watch_enable_tcc();
+ }
+}
+inline void watch_set_buzzer_period(uint32_t period) {
+ hri_tcc_write_PERBUF_reg(TCC0, period);
+}
+
+void watch_disable_buzzer(void) {
+ _watch_disable_tcc();
+}
+
+inline void watch_set_buzzer_on(void) {
+ gpio_set_pin_direction(BUZZER, GPIO_DIRECTION_OUT);
+ gpio_set_pin_function(BUZZER, WATCH_BUZZER_TCC_PINMUX);
+}
+
+inline void watch_set_buzzer_off(void) {
+ gpio_set_pin_direction(BUZZER, GPIO_DIRECTION_OFF);
+ gpio_set_pin_function(BUZZER, GPIO_PIN_FUNCTION_OFF);
+}
+
+// note: the buzzer uses a 1 MHz clock. these values were determined by dividing 1,000,000 by the target frequency.
+// i.e. for a 440 Hz tone (A4 on the piano), 1MHz/440Hz = 2273
+const uint16_t NotePeriods[108] = {18182,17161,16197,15288,14430,13620,12857,12134,11453,10811,10204,9631,9091,8581,8099,7645,7216,6811,6428,6068,5727,5405,5102,4816,4545,4290,4050,3822,3608,3405,3214,3034,2863,2703,2551,2408,2273,2145,2025,1911,1804,1703,1607,1517,1432,1351,1276,1204,1136,1073,1012,956,902,851,804,758,716,676,638,602,568,536,506,478,451,426,402,379,358,338,319,301,284,268,253,239,225,213,201,190,179,169,159,150,142,134,127};
+
+void watch_buzzer_play_note(BuzzerNote note, uint16_t duration_ms) {
+ if (note == BUZZER_NOTE_REST) {
+ watch_set_buzzer_off();
+ } else {
+ hri_tcc_write_PERBUF_reg(TCC0, NotePeriods[note]);
+ hri_tcc_write_CCBUF_reg(TCC0, WATCH_BUZZER_TCC_CHANNEL, NotePeriods[note] / 2);
+ watch_set_buzzer_on();
+ }
+ delay_ms(duration_ms);
+ watch_set_buzzer_off();
+}
diff --git a/watch-library/hardware/watch/watch_deepsleep.c b/watch-library/hardware/watch/watch_deepsleep.c
new file mode 100644
index 00000000..1813ff24
--- /dev/null
+++ b/watch-library/hardware/watch/watch_deepsleep.c
@@ -0,0 +1,209 @@
+/*
+ * 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_extint.h"
+
+// this warning only appears when you `make BOARD=OSO-SWAT-A1-02`. it's annoying,
+// but i'd rather have it warn us at build-time than fail silently at run-time.
+// besides, no one but me really has any of these boards anyway.
+#if BTN_ALARM != GPIO(GPIO_PORTA, 2)
+#warning This board revision does not support external wake on BTN_ALARM, so watch_register_extwake_callback will not work with it. Use watch_register_interrupt_callback instead.
+#endif
+
+void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool level) {
+ uint32_t pinmux;
+ hri_rtc_tampctrl_reg_t config = RTC->MODE2.TAMPCTRL.reg;
+
+ switch (pin) {
+ case A4:
+ a4_callback = callback;
+ pinmux = PINMUX_PB00G_RTC_IN0;
+ config &= ~(3 << RTC_TAMPCTRL_IN0ACT_Pos);
+ config &= ~(1 << RTC_TAMPCTRL_TAMLVL0_Pos);
+ config |= 1 << RTC_TAMPCTRL_IN0ACT_Pos;
+ config |= 1 << RTC_TAMPCTRL_DEBNC0_Pos;
+ if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL0_Pos;
+ break;
+ case A2:
+ a2_callback = callback;
+ pinmux = PINMUX_PB02G_RTC_IN1;
+ config &= ~(3 << RTC_TAMPCTRL_IN1ACT_Pos);
+ config &= ~(1 << RTC_TAMPCTRL_TAMLVL1_Pos);
+ config |= 1 << RTC_TAMPCTRL_IN1ACT_Pos;
+ config |= 1 << RTC_TAMPCTRL_DEBNC1_Pos;
+ if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL1_Pos;
+ break;
+ case BTN_ALARM:
+ gpio_set_pin_pull_mode(pin, GPIO_PULL_DOWN);
+ btn_alarm_callback = callback;
+ pinmux = PINMUX_PA02G_RTC_IN2;
+ config &= ~(3 << RTC_TAMPCTRL_IN2ACT_Pos);
+ config &= ~(1 << RTC_TAMPCTRL_TAMLVL2_Pos);
+ config |= 1 << RTC_TAMPCTRL_IN2ACT_Pos;
+ config |= 1 << RTC_TAMPCTRL_DEBNC2_Pos;
+ if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL2_Pos;
+ break;
+ default:
+ return;
+ }
+ gpio_set_pin_direction(pin, GPIO_DIRECTION_IN);
+ gpio_set_pin_function(pin, pinmux);
+
+ // disable the RTC
+ RTC->MODE2.CTRLA.bit.ENABLE = 0;
+ while (RTC->MODE2.SYNCBUSY.bit.ENABLE);
+
+ // update the configuration
+ RTC->MODE2.TAMPCTRL.reg = config;
+ // re-enable the RTC
+ RTC->MODE2.CTRLA.bit.ENABLE = 1;
+
+ NVIC_ClearPendingIRQ(RTC_IRQn);
+ NVIC_EnableIRQ(RTC_IRQn);
+ RTC->MODE2.INTENSET.reg = RTC_MODE2_INTENSET_TAMPER;
+}
+
+void watch_disable_extwake_interrupt(uint8_t pin) {
+ hri_rtc_tampctrl_reg_t config = hri_rtc_get_TAMPCTRL_reg(RTC, 0xFFFFFFFF);
+
+ switch (pin) {
+ case A4:
+ a4_callback = NULL;
+ config &= ~(3 << RTC_TAMPCTRL_IN0ACT_Pos);
+ break;
+ case A2:
+ a2_callback = NULL;
+ config &= ~(3 << RTC_TAMPCTRL_IN1ACT_Pos);
+ break;
+ case BTN_ALARM:
+ btn_alarm_callback = NULL;
+ config &= ~(3 << RTC_TAMPCTRL_IN2ACT_Pos);
+ break;
+ default:
+ return;
+ }
+
+ if (hri_rtcmode0_get_CTRLA_ENABLE_bit(RTC)) {
+ hri_rtcmode0_clear_CTRLA_ENABLE_bit(RTC);
+ hri_rtcmode0_wait_for_sync(RTC, RTC_MODE0_SYNCBUSY_ENABLE);
+ }
+ hri_rtc_write_TAMPCTRL_reg(RTC, config);
+ hri_rtcmode0_set_CTRLA_ENABLE_bit(RTC);
+}
+
+void watch_store_backup_data(uint32_t data, uint8_t reg) {
+ if (reg < 8) {
+ RTC->MODE0.BKUP[reg].reg = data;
+ }
+}
+
+uint32_t watch_get_backup_data(uint8_t reg) {
+ if (reg < 8) {
+ return RTC->MODE0.BKUP[reg].reg;
+ }
+
+ return 0;
+}
+
+static void _watch_disable_all_pins_except_rtc(void) {
+ uint32_t config = RTC->MODE0.TAMPCTRL.reg;
+ uint32_t portb_pins_to_disable = 0xFFFFFFFF;
+
+ // if there's an action set on RTC/IN[0], leave PB00 configured
+ if (config & RTC_TAMPCTRL_IN0ACT_Msk) portb_pins_to_disable &= 0xFFFFFFFE;
+ // same with RTC/IN[1] and PB02
+ if (config & RTC_TAMPCTRL_IN1ACT_Msk) portb_pins_to_disable &= 0xFFFFFFFB;
+
+ // port A: always keep PA02 configured as-is; that's our ALARM button.
+ gpio_set_port_direction(0, 0xFFFFFFFB, GPIO_DIRECTION_OFF);
+ // port B: disable all pins we didn't save above.
+ gpio_set_port_direction(1, portb_pins_to_disable, GPIO_DIRECTION_OFF);
+}
+
+static void _watch_disable_all_peripherals_except_slcd(void) {
+ _watch_disable_tcc();
+ watch_disable_adc();
+ watch_disable_external_interrupts();
+ watch_disable_i2c();
+ // TODO: replace this with a proper function when we remove the debug UART
+ SERCOM3->USART.CTRLA.reg &= ~SERCOM_USART_CTRLA_ENABLE;
+ MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_SERCOM3;
+}
+
+void watch_enter_sleep_mode(void) {
+ // disable all other peripherals
+ _watch_disable_all_peripherals_except_slcd();
+
+ // disable tick interrupt
+ watch_rtc_disable_all_periodic_callbacks();
+
+ // disable brownout detector interrupt, which could inadvertently wake us up.
+ SUPC->INTENCLR.bit.BOD33DET = 1;
+
+ // disable all pins
+ _watch_disable_all_pins_except_rtc();
+
+ // enter standby (4); we basically hang out here until an interrupt wakes us.
+ sleep(4);
+
+ // and we awake! re-enable the brownout detector
+ SUPC->INTENSET.bit.BOD33DET = 1;
+
+ // call app_setup so the app can re-enable everything we disabled.
+ app_setup();
+
+ // and call app_wake_from_standby (since main won't have a chance to do it)
+ app_wake_from_standby();
+}
+
+void watch_enter_deep_sleep_mode(void) {
+ // identical to sleep mode except we disable the LCD first.
+ slcd_sync_deinit(&SEGMENT_LCD_0);
+ hri_mclk_clear_APBCMASK_SLCD_bit(SLCD);
+
+ watch_enter_sleep_mode();
+}
+
+void watch_enter_backup_mode(void) {
+ watch_rtc_disable_all_periodic_callbacks();
+ _watch_disable_all_peripherals_except_slcd();
+ slcd_sync_deinit(&SEGMENT_LCD_0);
+ hri_mclk_clear_APBCMASK_SLCD_bit(SLCD);
+ _watch_disable_all_pins_except_rtc();
+
+ // go into backup sleep mode (5). when we exit, the reset controller will take over.
+ sleep(5);
+}
+
+// deprecated
+void watch_enter_shallow_sleep(bool display_on) {
+ if (display_on) watch_enter_sleep_mode();
+ else watch_enter_deep_sleep_mode();
+}
+
+// deprecated
+void watch_enter_deep_sleep(void) {
+ watch_register_extwake_callback(BTN_ALARM, NULL, true);
+ watch_enter_backup_mode();
+}
diff --git a/watch-library/hardware/watch/watch_extint.c b/watch-library/hardware/watch/watch_extint.c
new file mode 100644
index 00000000..5924b646
--- /dev/null
+++ b/watch-library/hardware/watch/watch_extint.c
@@ -0,0 +1,111 @@
+/*
+ * 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_extint.h"
+
+void watch_enable_external_interrupts(void) {
+ // Configure EIC to use GCLK3 (the 32.768 kHz crystal)
+ hri_gclk_write_PCHCTRL_reg(GCLK, EIC_GCLK_ID, GCLK_PCHCTRL_GEN_GCLK3_Val | (1 << GCLK_PCHCTRL_CHEN_Pos));
+ // Enable AHB clock for the EIC
+ hri_mclk_set_APBAMASK_EIC_bit(MCLK);
+ // call HAL's external interrupt init function
+ ext_irq_init();
+}
+
+void watch_disable_external_interrupts(void) {
+ ext_irq_deinit();
+ hri_mclk_clear_APBAMASK_EIC_bit(MCLK);
+}
+
+void watch_register_interrupt_callback(const uint8_t pin, ext_irq_cb_t callback, watch_interrupt_trigger trigger) {
+ uint8_t config_index;
+ uint8_t sense_pos;
+ switch (pin) {
+ case A0:
+ // for EIC channels 8-15, we need to set the SENSE value in CONFIG[1]
+ config_index = (WATCH_A0_EIC_CHANNEL > 7) ? 1 : 0;
+ // either way the index in CONFIG[n] must be 0-7
+ sense_pos = 4 * (WATCH_A0_EIC_CHANNEL % 8);
+ break;
+ case A1:
+ config_index = (WATCH_A1_EIC_CHANNEL > 7) ? 1 : 0;
+ sense_pos = 4 * (WATCH_A1_EIC_CHANNEL % 8);
+ break;
+ case A2:
+ config_index = (WATCH_A2_EIC_CHANNEL > 7) ? 1 : 0;
+ sense_pos = 4 * (WATCH_A2_EIC_CHANNEL % 8);
+ break;
+ case A3:
+ config_index = (WATCH_A3_EIC_CHANNEL > 7) ? 1 : 0;
+ sense_pos = 4 * (WATCH_A3_EIC_CHANNEL % 8);
+ break;
+ case A4:
+ config_index = (WATCH_A4_EIC_CHANNEL > 7) ? 1 : 0;
+ sense_pos = 4 * (WATCH_A4_EIC_CHANNEL % 8);
+ break;
+ case BTN_ALARM:
+ config_index = (WATCH_BTN_ALARM_EIC_CHANNEL > 7) ? 1 : 0;
+ sense_pos = 4 * (WATCH_BTN_ALARM_EIC_CHANNEL % 8);
+ break;
+ case BTN_LIGHT:
+ config_index = (WATCH_BTN_LIGHT_EIC_CHANNEL > 7) ? 1 : 0;
+ sense_pos = 4 * (WATCH_BTN_LIGHT_EIC_CHANNEL % 8);
+ break;
+ case BTN_MODE:
+ config_index = (WATCH_BTN_MODE_EIC_CHANNEL > 7) ? 1 : 0;
+ sense_pos = 4 * (WATCH_BTN_MODE_EIC_CHANNEL % 8);
+ break;
+ default:
+ return;
+ }
+
+ gpio_set_pin_direction(pin, GPIO_DIRECTION_IN);
+
+ // EIC configuration register is enable-protected, so we have to disable it first...
+ if (hri_eic_get_CTRLA_reg(EIC, EIC_CTRLA_ENABLE)) {
+ hri_eic_clear_CTRLA_ENABLE_bit(EIC);
+ // ...and wait for it to synchronize.
+ hri_eic_wait_for_sync(EIC, EIC_SYNCBUSY_ENABLE);
+ }
+ // now update the configuration...
+ hri_eic_config_reg_t config = EIC->CONFIG[config_index].reg;
+ config &= ~(7 << sense_pos);
+ config |= trigger << (sense_pos);
+ hri_eic_write_CONFIG_reg(EIC, config_index, config);
+ // ...set the pin mode...
+ gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_A);
+ if (pin == BTN_ALARM || pin == BTN_LIGHT || pin == BTN_MODE) gpio_set_pin_pull_mode(pin, GPIO_PULL_DOWN);
+ // ...and re-enable the EIC
+ hri_eic_set_CTRLA_ENABLE_bit(EIC);
+
+ ext_irq_register(pin, callback);
+}
+
+inline void watch_register_button_callback(const uint8_t pin, ext_irq_cb_t callback) {
+ watch_register_interrupt_callback(pin, callback, INTERRUPT_TRIGGER_RISING);
+}
+
+inline void watch_enable_buttons(void) {
+ watch_enable_external_interrupts();
+}
diff --git a/watch-library/hardware/watch/watch_gpio.c b/watch-library/hardware/watch/watch_gpio.c
new file mode 100644
index 00000000..b37d009f
--- /dev/null
+++ b/watch-library/hardware/watch/watch_gpio.c
@@ -0,0 +1,61 @@
+/*
+ * 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_gpio.h"
+
+ void watch_enable_digital_input(const uint8_t pin) {
+ gpio_set_pin_direction(pin, GPIO_DIRECTION_IN);
+ gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF);
+}
+
+void watch_disable_digital_input(const uint8_t pin) {
+ gpio_set_pin_direction(pin, GPIO_DIRECTION_OFF);
+ gpio_set_pin_pull_mode(pin, GPIO_PULL_OFF);
+ gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF);
+}
+
+void watch_enable_pull_up(const uint8_t pin) {
+ gpio_set_pin_pull_mode(pin, GPIO_PULL_UP);
+}
+
+void watch_enable_pull_down(const uint8_t pin) {
+ gpio_set_pin_pull_mode(pin, GPIO_PULL_DOWN);
+}
+
+bool watch_get_pin_level(const uint8_t pin) {
+ return gpio_get_pin_level(pin);
+}
+
+void watch_enable_digital_output(const uint8_t pin) {
+ gpio_set_pin_direction(pin, GPIO_DIRECTION_OUT);
+ gpio_set_pin_function(pin, GPIO_PIN_FUNCTION_OFF);
+}
+
+void watch_disable_digital_output(const uint8_t pin) {
+ gpio_set_pin_direction(pin, GPIO_DIRECTION_OFF);
+}
+
+void watch_set_pin_level(const uint8_t pin, const bool level) {
+ gpio_set_pin_level(pin, level);
+}
diff --git a/watch-library/hardware/watch/watch_i2c.c b/watch-library/hardware/watch/watch_i2c.c
new file mode 100644
index 00000000..ff20afc6
--- /dev/null
+++ b/watch-library/hardware/watch/watch_i2c.c
@@ -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.
+ */
+
+#include "watch_i2c.h"
+
+struct io_descriptor *I2C_0_io;
+
+void watch_enable_i2c(void) {
+ I2C_0_init();
+ i2c_m_sync_get_io_descriptor(&I2C_0, &I2C_0_io);
+ i2c_m_sync_enable(&I2C_0);
+}
+
+void watch_disable_i2c(void) {
+ i2c_m_sync_disable(&I2C_0);
+ hri_mclk_clear_APBCMASK_SERCOM1_bit(MCLK);
+}
+
+void watch_i2c_send(int16_t addr, uint8_t *buf, uint16_t length) {
+ i2c_m_sync_set_periphaddr(&I2C_0, addr, I2C_M_SEVEN);
+ io_write(I2C_0_io, buf, length);
+}
+
+void watch_i2c_receive(int16_t addr, uint8_t *buf, uint16_t length) {
+ i2c_m_sync_set_periphaddr(&I2C_0, addr, I2C_M_SEVEN);
+ io_read(I2C_0_io, buf, length);
+}
+
+void watch_i2c_write8(int16_t addr, uint8_t reg, uint8_t data) {
+ uint8_t buf[2];
+ buf[0] = reg;
+ buf[1] = data;
+
+ watch_i2c_send(addr, (uint8_t *)&buf, 2);
+}
+
+uint8_t watch_i2c_read8(int16_t addr, uint8_t reg) {
+ uint8_t data;
+
+ watch_i2c_send(addr, (uint8_t *)&reg, 1);
+ watch_i2c_receive(addr, (uint8_t *)&data, 1);
+
+ return data;
+}
+
+uint16_t watch_i2c_read16(int16_t addr, uint8_t reg) {
+ uint16_t data;
+
+ watch_i2c_send(addr, (uint8_t *)&reg, 1);
+ watch_i2c_receive(addr, (uint8_t *)&data, 2);
+
+ return data;
+}
+
+uint32_t watch_i2c_read24(int16_t addr, uint8_t reg) {
+ uint32_t data;
+ data = 0;
+
+ watch_i2c_send(addr, (uint8_t *)&reg, 1);
+ watch_i2c_receive(addr, (uint8_t *)&data, 3);
+
+ return data << 8;
+}
+
+uint32_t watch_i2c_read32(int16_t addr, uint8_t reg) {
+ uint32_t data;
+
+ watch_i2c_send(addr, (uint8_t *)&reg, 1);
+ watch_i2c_receive(addr, (uint8_t *)&data, 4);
+
+ return data;
+}
diff --git a/watch-library/hardware/watch/watch_led.c b/watch-library/hardware/watch/watch_led.c
new file mode 100644
index 00000000..52174b54
--- /dev/null
+++ b/watch-library/hardware/watch/watch_led.c
@@ -0,0 +1,69 @@
+/*
+ * 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_led.h"
+
+void watch_enable_leds(void) {
+ if (!hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) {
+ _watch_enable_tcc();
+ }
+}
+
+void watch_disable_leds(void) {
+ _watch_disable_tcc();
+}
+
+void watch_enable_led(bool unused) {
+ (void)unused;
+ watch_enable_leds();
+}
+
+void watch_disable_led(bool unused) {
+ (void)unused;
+ watch_disable_leds();
+}
+
+void watch_set_led_color(uint8_t red, uint8_t green) {
+ if (hri_tcc_get_CTRLA_reg(TCC0, TCC_CTRLA_ENABLE)) {
+ uint32_t period = hri_tcc_get_PER_reg(TCC0, TCC_PER_MASK);
+ hri_tcc_write_CCBUF_reg(TCC0, WATCH_RED_TCC_CHANNEL, ((period * red * 1000ull) / 255000ull));
+ hri_tcc_write_CCBUF_reg(TCC0, WATCH_GREEN_TCC_CHANNEL, ((period * green * 1000ull) / 255000ull));
+ }
+}
+
+void watch_set_led_red(void) {
+ watch_set_led_color(255, 0);
+}
+
+void watch_set_led_green(void) {
+ watch_set_led_color(0, 255);
+}
+
+void watch_set_led_yellow(void) {
+ watch_set_led_color(255, 255);
+}
+
+void watch_set_led_off(void) {
+ watch_set_led_color(0, 0);
+}
diff --git a/watch-library/hardware/watch/watch_private.c b/watch-library/hardware/watch/watch_private.c
new file mode 100644
index 00000000..ae2589e7
--- /dev/null
+++ b/watch-library/hardware/watch/watch_private.c
@@ -0,0 +1,423 @@
+/*
+ * 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_private.h"
+#include "watch_utility.h"
+#include "tusb.h"
+
+void _watch_init(void) {
+ // disable the LED pin (it may have been enabled by the bootloader)
+ watch_disable_digital_output(RED);
+
+ // RAM should be back-biased in STANDBY
+ PM->STDBYCFG.bit.BBIASHS = 1;
+
+ // Use switching regulator for lower power consumption.
+ SUPC->VREG.bit.SEL = 1;
+ while(!SUPC->STATUS.bit.VREGRDY);
+
+ // set up the brownout detector (low battery warning)
+ NVIC_DisableIRQ(SYSTEM_IRQn);
+ NVIC_ClearPendingIRQ(SYSTEM_IRQn);
+ NVIC_EnableIRQ(SYSTEM_IRQn);
+ SUPC->BOD33.bit.ENABLE = 0; // BOD33 must be disabled to change its configuration
+ SUPC->BOD33.bit.VMON = 0; // Monitor VDD in active and standby mode
+ SUPC->BOD33.bit.ACTCFG = 1; // Enable sampling mode when active
+ SUPC->BOD33.bit.RUNSTDBY = 1; // Enable sampling mode in standby
+ SUPC->BOD33.bit.STDBYCFG = 1; // Run in standby
+ SUPC->BOD33.bit.RUNBKUP = 0; // Don't run in backup mode
+ SUPC->BOD33.bit.PSEL = 0xB; // Check battery level every 4 seconds
+ SUPC->BOD33.bit.LEVEL = 31; // Detect brownout at 2.5V (1.445V + level * 34mV)
+ SUPC->BOD33.bit.ACTION = 0x2; // Generate an interrupt when BOD33 is triggered
+ SUPC->BOD33.bit.HYST = 0; // Disable hysteresis
+ while(!SUPC->STATUS.bit.B33SRDY);
+
+ // Enable interrupt on BOD33 detect
+ SUPC->INTENSET.bit.BOD33DET = 1;
+ SUPC->BOD33.bit.ENABLE = 1;
+
+ // External wake depends on RTC; calendar is a required module.
+ _watch_rtc_init();
+
+ // set up state
+ btn_alarm_callback = NULL;
+ a2_callback = NULL;
+ a4_callback = NULL;
+}
+
+static inline void _watch_wait_for_entropy() {
+ while (!hri_trng_get_INTFLAG_reg(TRNG, TRNG_INTFLAG_DATARDY));
+}
+
+// this function is called by arc4random to get entropy for random number generation.
+// let's use the SAM L22's true random number generator to seed the PRNG!
+int getentropy(void *buf, size_t buflen) {
+ hri_mclk_set_APBCMASK_TRNG_bit(MCLK);
+ hri_trng_set_CTRLA_ENABLE_bit(TRNG);
+
+ size_t i = 0;
+ while(i < buflen / 4) {
+ _watch_wait_for_entropy();
+ ((uint32_t *)buf)[i++] = hri_trng_read_DATA_reg(TRNG);
+ }
+
+ // but what if they asked for an awkward number of bytes?
+ if (buflen % 4) {
+ // all good: let's fill in one, two or three bytes at the end of the buffer.
+ _watch_wait_for_entropy();
+ uint32_t last_little_bit = hri_trng_read_DATA_reg(TRNG);
+ for(size_t j = 0; j <= (buflen % 4); j++) {
+ ((uint8_t *)buf)[i * 4 + j] = (last_little_bit >> (j * 8)) & 0xFF;
+ }
+ }
+
+ hri_trng_clear_CTRLA_ENABLE_bit(TRNG);
+ hri_mclk_clear_APBCMASK_TRNG_bit(MCLK);
+
+ return 0;
+}
+
+int _gettimeofday(struct timeval *tv, void *tzvp) {
+ (void)tzvp;
+ watch_date_time date_time = watch_rtc_get_date_time();
+
+ // FIXME: this assumes the system time is UTC! Will break for any other time zone.
+ tv->tv_sec = watch_utility_date_time_to_unix_time(date_time, 0);
+ tv->tv_usec = 0;
+
+ return 0;
+}
+
+void _watch_enable_tcc(void) {
+ // clock TCC0 with the main clock (8 MHz) and enable the peripheral clock.
+ hri_gclk_write_PCHCTRL_reg(GCLK, TCC0_GCLK_ID, GCLK_PCHCTRL_GEN_GCLK0_Val | GCLK_PCHCTRL_CHEN);
+ hri_mclk_set_APBCMASK_TCC0_bit(MCLK);
+ // disable and reset TCC0.
+ hri_tcc_clear_CTRLA_ENABLE_bit(TCC0);
+ hri_tcc_wait_for_sync(TCC0, TCC_SYNCBUSY_ENABLE);
+ hri_tcc_write_CTRLA_reg(TCC0, TCC_CTRLA_SWRST);
+ hri_tcc_wait_for_sync(TCC0, TCC_SYNCBUSY_SWRST);
+ // divide the clock down to 1 MHz
+ if (hri_usbdevice_get_CTRLA_ENABLE_bit(USB)) {
+ // if USB is enabled, we are running an 8 MHz clock.
+ hri_tcc_write_CTRLA_reg(TCC0, TCC_CTRLA_PRESCALER_DIV8);
+ } else {
+ // otherwise it's 4 Mhz.
+ hri_tcc_write_CTRLA_reg(TCC0, TCC_CTRLA_PRESCALER_DIV4);
+ }
+ // We're going to use normal PWM mode, which means period is controlled by PER, and duty cycle is controlled by
+ // each compare channel's value:
+ // * Buzzer tones are set by setting PER to the desired period for a given frequency, and CC[1] to half of that
+ // period (i.e. a square wave with a 50% duty cycle).
+ // * LEDs on CC[2] and CC[3] can be set to any value from 0 (off) to PER (fully on).
+ hri_tcc_write_WAVE_reg(TCC0, TCC_WAVE_WAVEGEN_NPWM);
+ #ifdef WATCH_INVERT_LED_POLARITY
+ // This is here for the dev board, which uses a common anode LED (instead of common cathode like the actual watch).
+ hri_tcc_set_WAVE_reg(TCC0, (1 << (TCC_WAVE_POL0_Pos + WATCH_RED_TCC_CHANNEL)) |
+ (1 << (TCC_WAVE_POL0_Pos + WATCH_GREEN_TCC_CHANNEL)));
+ #endif
+ // The buzzer will set the period depending on the tone it wants to play, but we have to set some period here to
+ // get the LED working. Almost any period will do, tho it should be below 20000 (i.e. 50 Hz) to avoid flickering.
+ hri_tcc_write_PER_reg(TCC0, 4096);
+ // Set the duty cycle of all pins to 0: LED's off, buzzer not buzzing.
+ hri_tcc_write_CC_reg(TCC0, WATCH_BUZZER_TCC_CHANNEL, 0);
+ hri_tcc_write_CC_reg(TCC0, WATCH_RED_TCC_CHANNEL, 0);
+ hri_tcc_write_CC_reg(TCC0, WATCH_GREEN_TCC_CHANNEL, 0);
+ // Enable the TCC
+ hri_tcc_set_CTRLA_ENABLE_bit(TCC0);
+ hri_tcc_wait_for_sync(TCC0, TCC_SYNCBUSY_ENABLE);
+
+ // enable LED PWM pins (the LED driver assumes if the TCC is on, the pins are enabled)
+ gpio_set_pin_direction(RED, GPIO_DIRECTION_OUT);
+ gpio_set_pin_function(RED, WATCH_RED_TCC_PINMUX);
+ gpio_set_pin_direction(GREEN, GPIO_DIRECTION_OUT);
+ gpio_set_pin_function(GREEN, WATCH_GREEN_TCC_PINMUX);
+}
+
+void _watch_disable_tcc(void) {
+ // disable all PWM pins
+ gpio_set_pin_direction(BUZZER, GPIO_DIRECTION_OFF);
+ gpio_set_pin_function(BUZZER, GPIO_PIN_FUNCTION_OFF);
+ gpio_set_pin_direction(RED, GPIO_DIRECTION_OFF);
+ gpio_set_pin_function(RED, GPIO_PIN_FUNCTION_OFF);
+ gpio_set_pin_direction(GREEN, GPIO_DIRECTION_OFF);
+ gpio_set_pin_function(GREEN, GPIO_PIN_FUNCTION_OFF);
+
+ // disable the TCC
+ hri_tcc_clear_CTRLA_ENABLE_bit(TCC0);
+ hri_mclk_clear_APBCMASK_TCC0_bit(MCLK);
+}
+
+void _watch_enable_usb(void) {
+ // disable USB, just in case.
+ hri_usb_clear_CTRLA_ENABLE_bit(USB);
+
+ // bump clock up to 8 MHz
+ hri_oscctrl_write_OSC16MCTRL_FSEL_bf(OSCCTRL, OSCCTRL_OSC16MCTRL_FSEL_8_Val);
+
+ // reset flags and disable DFLL
+ OSCCTRL->INTFLAG.reg = OSCCTRL_INTFLAG_DFLLRDY;
+ OSCCTRL->DFLLCTRL.reg = 0;
+ while (!(OSCCTRL->STATUS.reg & OSCCTRL_STATUS_DFLLRDY));
+
+ // set the coarse and fine values to speed up frequency lock.
+ uint32_t coarse =(*((uint32_t *)NVMCTRL_OTP5)) >> 26;
+ OSCCTRL->DFLLVAL.reg = OSCCTRL_DFLLVAL_COARSE(coarse) |
+ OSCCTRL_DFLLVAL_FINE(0x200);
+ // set coarse and fine steps, and multiplier (48 MHz = 32768 Hz * 1465)
+ OSCCTRL->DFLLMUL.reg = OSCCTRL_DFLLMUL_CSTEP( 1 ) |
+ OSCCTRL_DFLLMUL_FSTEP( 1 ) |
+ OSCCTRL_DFLLMUL_MUL( 1465 );
+ // set closed loop mode, chill cycle disable and USB clock recovery mode, and enable the DFLL.
+ OSCCTRL->DFLLCTRL.reg = OSCCTRL_DFLLCTRL_MODE | OSCCTRL_DFLLCTRL_CCDIS | OSCCTRL_DFLLCTRL_ONDEMAND | OSCCTRL_DFLLCTRL_RUNSTDBY | OSCCTRL_DFLLCTRL_USBCRM | OSCCTRL_DFLLCTRL_ENABLE;
+ while (!(OSCCTRL->STATUS.reg & OSCCTRL_STATUS_DFLLRDY));
+
+ // assign DFLL to GCLK1
+ GCLK->GENCTRL[1].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL48M) | GCLK_GENCTRL_DIV(1) | GCLK_GENCTRL_GENEN;// | GCLK_GENCTRL_OE;
+ while (GCLK->SYNCBUSY.bit.GENCTRL1);
+
+ // assign GCLK1 to USB
+ hri_gclk_write_PCHCTRL_reg(GCLK, USB_GCLK_ID, GCLK_PCHCTRL_GEN_GCLK1_Val | GCLK_PCHCTRL_CHEN);
+ hri_mclk_set_AHBMASK_USB_bit(MCLK);
+ hri_mclk_set_APBBMASK_USB_bit(MCLK);
+
+ // USB Pin Init
+ gpio_set_pin_direction(PIN_PA24, GPIO_DIRECTION_OUT);
+ gpio_set_pin_level(PIN_PA24, false);
+ gpio_set_pin_pull_mode(PIN_PA24, GPIO_PULL_OFF);
+ gpio_set_pin_direction(PIN_PA25, GPIO_DIRECTION_OUT);
+ gpio_set_pin_level(PIN_PA25, false);
+ gpio_set_pin_pull_mode(PIN_PA25, GPIO_PULL_OFF);
+
+ gpio_set_pin_function(PIN_PA24, PINMUX_PA24G_USB_DM);
+ gpio_set_pin_function(PIN_PA25, PINMUX_PA25G_USB_DP);
+
+ // before we init TinyUSB, we are going to need a periodic callback to handle TinyUSB tasks.
+ // TC2 and TC3 are reserved for devices on the 9-pin connector, so let's use TC0.
+ // clock TC0 with the 8 MHz clock on GCLK0.
+ hri_gclk_write_PCHCTRL_reg(GCLK, TC0_GCLK_ID, GCLK_PCHCTRL_GEN_GCLK0_Val | GCLK_PCHCTRL_CHEN);
+ // and enable the peripheral clock.
+ hri_mclk_set_APBCMASK_TC0_bit(MCLK);
+ // disable and reset TC0.
+ hri_tc_clear_CTRLA_ENABLE_bit(TC0);
+ hri_tc_wait_for_sync(TC0, TC_SYNCBUSY_ENABLE);
+ hri_tc_write_CTRLA_reg(TC0, TC_CTRLA_SWRST);
+ hri_tc_wait_for_sync(TC0, TC_SYNCBUSY_SWRST);
+ // configure the TC to overflow 1,000 times per second
+ hri_tc_write_CTRLA_reg(TC0, TC_CTRLA_PRESCALER_DIV64 | // divide the 8 MHz clock by 64 to count at 125 KHz
+ TC_CTRLA_MODE_COUNT8 | // count in 8-bit mode
+ TC_CTRLA_RUNSTDBY); // run in standby, just in case we figure that out
+ hri_tccount8_write_PER_reg(TC0, 125); // 125000 Hz / 125 = 1,000 Hz
+ // set an interrupt on overflow; this will call TC0_Handler below.
+ hri_tc_set_INTEN_OVF_bit(TC0);
+ NVIC_ClearPendingIRQ(TC0_IRQn);
+ NVIC_EnableIRQ (TC0_IRQn);
+
+ // now we can init TinyUSB
+ tusb_init();
+ // and start the timer that handles USB device tasks.
+ hri_tc_set_CTRLA_ENABLE_bit(TC0);
+}
+
+// this function ends up getting called by printf to log stuff to the USB console.
+int _write(int file, char *ptr, int len) {
+ (void)file;
+ if (hri_usbdevice_get_CTRLA_ENABLE_bit(USB)) {
+ tud_cdc_n_write(0, (void const*)ptr, len);
+ tud_cdc_n_write_flush(0);
+ return len;
+ }
+
+ return 0;
+}
+
+// this method could be overridden to read stuff from the USB console? but no need rn.
+int _read(void) {
+ return 0;
+}
+
+// Alternate function that outputs to the debug UART. useful for debugging USB issues.
+// int _write(int file, char *ptr, int len) {
+// (void)file;
+// int pos = 0;
+// while(pos < len) watch_debug_putc(ptr[pos++]);
+
+// return 0;
+// }
+
+void USB_Handler(void) {
+ tud_int_handler(0);
+}
+
+void TC0_Handler(void) {
+ tud_task();
+ TC0->COUNT8.INTFLAG.reg |= TC_INTFLAG_OVF;
+}
+
+
+// USB Descriptors and tinyUSB callbacks follow.
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * 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.
+ *
+ */
+
+//--------------------------------------------------------------------+
+// Device Descriptors
+//--------------------------------------------------------------------+
+tusb_desc_device_t const desc_device =
+{
+ .bLength = sizeof(tusb_desc_device_t),
+ .bDescriptorType = TUSB_DESC_DEVICE,
+ .bcdUSB = 0x0200,
+
+ // Use Interface Association Descriptor (IAD) for CDC
+ // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
+ .bDeviceClass = TUSB_CLASS_MISC,
+ .bDeviceSubClass = MISC_SUBCLASS_COMMON,
+ .bDeviceProtocol = MISC_PROTOCOL_IAD,
+
+ .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
+
+ .idVendor = 0x1209,
+ .idProduct = 0x2151,
+ .bcdDevice = 0x0100,
+
+ .iManufacturer = 0x01,
+ .iProduct = 0x02,
+ .iSerialNumber = 0x03,
+
+ .bNumConfigurations = 0x01
+};
+
+// Invoked when received GET DEVICE DESCRIPTOR
+// Application return pointer to descriptor
+uint8_t const * tud_descriptor_device_cb(void) {
+ return (uint8_t const *) &desc_device;
+}
+
+//--------------------------------------------------------------------+
+// Configuration Descriptor
+//--------------------------------------------------------------------+
+
+enum {
+ ITF_NUM_CDC = 0,
+ ITF_NUM_CDC_DATA,
+ ITF_NUM_TOTAL
+};
+
+#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN)
+
+#define EPNUM_CDC_NOTIF 0x81
+#define EPNUM_CDC_OUT 0x02
+#define EPNUM_CDC_IN 0x82
+
+
+uint8_t const desc_fs_configuration[] = {
+ // Config number, interface count, string index, total length, attribute, power in mA
+ TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
+
+ // Interface number, string index, EP notification address and size, EP data address (out, in) and size.
+ TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
+};
+
+// Invoked when received GET CONFIGURATION DESCRIPTOR
+// Application return pointer to descriptor
+// Descriptor contents must exist long enough for transfer to complete
+uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
+ (void) index; // for multiple configurations
+ return desc_fs_configuration;
+}
+
+//--------------------------------------------------------------------+
+// String Descriptors
+//--------------------------------------------------------------------+
+
+// array of pointer to string descriptors
+char const* string_desc_arr [] =
+{
+ (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
+ "TinyUSB", // 1: Manufacturer
+ "TinyUSB Device", // 2: Product
+ "123456", // 3: Serials, should use chip ID
+ "TinyUSB CDC", // 4: CDC Interface
+};
+
+static uint16_t _desc_str[32];
+
+// Invoked when received GET STRING DESCRIPTOR request
+// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
+uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
+{
+ (void) langid;
+
+ uint8_t chr_count;
+
+ if ( index == 0) {
+ memcpy(&_desc_str[1], string_desc_arr[0], 2);
+ chr_count = 1;
+ } else {
+ // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
+ // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
+
+ if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
+
+ const char* str = string_desc_arr[index];
+
+ // Cap at max char
+ chr_count = strlen(str);
+ if ( chr_count > 31 ) chr_count = 31;
+
+ // Convert ASCII string into UTF-16
+ for(uint8_t i=0; i<chr_count; i++)
+ {
+ _desc_str[1+i] = str[i];
+ }
+ }
+
+ // first byte is length (including header), second byte is string type
+ _desc_str[0] = (TUSB_DESC_STRING << 8 ) | (2*chr_count + 2);
+
+ return _desc_str;
+}
diff --git a/watch-library/hardware/watch/watch_rtc.c b/watch-library/hardware/watch/watch_rtc.c
new file mode 100644
index 00000000..14a968c4
--- /dev/null
+++ b/watch-library/hardware/watch/watch_rtc.c
@@ -0,0 +1,195 @@
+/*
+ * 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_rtc.h"
+
+ext_irq_cb_t tick_callbacks[8];
+ext_irq_cb_t alarm_callback;
+ext_irq_cb_t btn_alarm_callback;
+ext_irq_cb_t a2_callback;
+ext_irq_cb_t a4_callback;
+
+bool _watch_rtc_is_enabled(void) {
+ return RTC->MODE2.CTRLA.bit.ENABLE;
+}
+
+static void _sync_rtc(void) {
+ while (RTC->MODE2.SYNCBUSY.reg);
+}
+
+void _watch_rtc_init(void) {
+ MCLK->APBAMASK.reg |= MCLK_APBAMASK_RTC;
+
+ if (_watch_rtc_is_enabled()) return; // don't reset the RTC if it's already set up.
+
+ RTC->MODE2.CTRLA.bit.ENABLE = 0;
+ _sync_rtc();
+
+ RTC->MODE2.CTRLA.bit.SWRST = 1;
+ _sync_rtc();
+
+ RTC->MODE2.CTRLA.bit.MODE = RTC_MODE2_CTRLA_MODE_CLOCK_Val;
+ RTC->MODE2.CTRLA.bit.PRESCALER = RTC_MODE2_CTRLA_PRESCALER_DIV1024_Val;
+ RTC->MODE2.CTRLA.bit.CLOCKSYNC = 1;
+ RTC->MODE2.CTRLA.bit.ENABLE = 1;
+ _sync_rtc();
+}
+
+void watch_rtc_set_date_time(watch_date_time date_time) {
+ RTC->MODE2.CLOCK.reg = date_time.reg;
+ _sync_rtc();
+}
+
+watch_date_time watch_rtc_get_date_time(void) {
+ watch_date_time retval;
+
+ _sync_rtc();
+ retval.reg = RTC->MODE2.CLOCK.reg;
+
+ return retval;
+}
+
+void watch_rtc_register_tick_callback(ext_irq_cb_t callback) {
+ watch_rtc_register_periodic_callback(callback, 1);
+}
+
+void watch_rtc_disable_tick_callback(void) {
+ watch_rtc_disable_periodic_callback(1);
+}
+
+void watch_rtc_register_periodic_callback(ext_irq_cb_t callback, uint8_t frequency) {
+ // we told them, it has to be a power of 2.
+ if (__builtin_popcount(frequency) != 1) return;
+
+ // this left-justifies the period in a 32-bit integer.
+ uint32_t tmp = frequency << 24;
+ // now we can count the leading zeroes to get the value we need.
+ // 0x01 (1 Hz) will have 7 leading zeros for PER7. 0xF0 (128 Hz) will have no leading zeroes for PER0.
+ uint8_t per_n = __builtin_clz(tmp);
+
+ // this also maps nicely to an index for our list of tick callbacks.
+ tick_callbacks[per_n] = callback;
+
+ NVIC_ClearPendingIRQ(RTC_IRQn);
+ NVIC_EnableIRQ(RTC_IRQn);
+ RTC->MODE2.INTENSET.reg = 1 << per_n;
+}
+
+void watch_rtc_disable_periodic_callback(uint8_t frequency) {
+ if (__builtin_popcount(frequency) != 1) return;
+ uint8_t per_n = __builtin_clz(frequency << 24);
+ RTC->MODE2.INTENCLR.reg = 1 << per_n;
+}
+
+void watch_rtc_disable_all_periodic_callbacks(void) {
+ RTC->MODE2.INTENCLR.reg = 0xFF;
+}
+
+void watch_rtc_register_alarm_callback(ext_irq_cb_t callback, watch_date_time alarm_time, watch_rtc_alarm_match mask) {
+ RTC->MODE2.Mode2Alarm[0].ALARM.reg = alarm_time.reg;
+ RTC->MODE2.Mode2Alarm[0].MASK.reg = mask;
+ RTC->MODE2.INTENSET.reg = RTC_MODE2_INTENSET_ALARM0;
+ alarm_callback = callback;
+ NVIC_ClearPendingIRQ(RTC_IRQn);
+ NVIC_EnableIRQ(RTC_IRQn);
+ RTC->MODE2.INTENSET.reg = RTC_MODE2_INTENSET_ALARM0;
+}
+
+void watch_rtc_disable_alarm_callback(void) {
+ RTC->MODE2.INTENCLR.reg = RTC_MODE2_INTENCLR_ALARM0;
+}
+
+void RTC_Handler(void) {
+ uint16_t interrupt_status = RTC->MODE2.INTFLAG.reg;
+ uint16_t interrupt_enabled = RTC->MODE2.INTENSET.reg;
+
+ if ((interrupt_status & interrupt_enabled) & RTC_MODE2_INTFLAG_PER_Msk) {
+ // handle the tick callback first, it's what we do the most.
+ // start from PER7, the 1 Hz tick.
+ for(int8_t i = 7; i >= 0; i--) {
+ if ((interrupt_status & interrupt_enabled) & (1 << i)) {
+ if (tick_callbacks[i] != NULL) {
+ tick_callbacks[i]();
+ }
+ RTC->MODE2.INTFLAG.reg = 1 << i;
+ break;
+ }
+ }
+ } else if ((interrupt_status & interrupt_enabled) & RTC_MODE2_INTFLAG_TAMPER) {
+ // handle the extwake interrupts next.
+ uint8_t reason = RTC->MODE2.TAMPID.reg;
+ if (reason & RTC_TAMPID_TAMPID2) {
+ if (btn_alarm_callback != NULL) btn_alarm_callback();
+ } else if (reason & RTC_TAMPID_TAMPID1) {
+ if (a2_callback != NULL) a2_callback();
+ } else if (reason & RTC_TAMPID_TAMPID0) {
+ if (a4_callback != NULL) a4_callback();
+ }
+ RTC->MODE2.TAMPID.reg = reason;
+ RTC->MODE2.INTFLAG.reg = RTC_MODE2_INTFLAG_TAMPER;
+ } else if ((interrupt_status & interrupt_enabled) & RTC_MODE2_INTFLAG_ALARM0) {
+ // finally handle the alarm.
+ if (alarm_callback != NULL) {
+ alarm_callback();
+ }
+ RTC->MODE2.INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM0;
+ }
+}
+
+///////////////////////
+// Deprecated functions
+
+void watch_set_date_time(struct calendar_date_time date_time) {
+ RTC_MODE2_CLOCK_Type val;
+
+ val.bit.SECOND = date_time.time.sec;
+ val.bit.MINUTE = date_time.time.min;
+ val.bit.HOUR = date_time.time.hour;
+ val.bit.DAY = date_time.date.day;
+ val.bit.MONTH = date_time.date.month;
+ val.bit.YEAR = (uint8_t)(date_time.date.year - WATCH_RTC_REFERENCE_YEAR);
+
+ RTC->MODE2.CLOCK.reg = val.reg;
+
+ _sync_rtc();
+}
+
+void watch_get_date_time(struct calendar_date_time *date_time) {
+ _sync_rtc();
+ RTC_MODE2_CLOCK_Type val = RTC->MODE2.CLOCK;
+
+ date_time->time.sec = val.bit.SECOND;
+ date_time->time.min = val.bit.MINUTE;
+ date_time->time.hour = val.bit.HOUR;
+ date_time->date.day = val.bit.DAY;
+ date_time->date.month = val.bit.MONTH;
+ date_time->date.year = val.bit.YEAR + WATCH_RTC_REFERENCE_YEAR;
+}
+
+void watch_register_tick_callback(ext_irq_cb_t callback) {
+ tick_callbacks[7] = callback;
+ NVIC_ClearPendingIRQ(RTC_IRQn);
+ NVIC_EnableIRQ(RTC_IRQn);
+ RTC->MODE2.INTENSET.reg = RTC_MODE2_INTENSET_PER7;
+}
diff --git a/watch-library/hardware/watch/watch_slcd.c b/watch-library/hardware/watch/watch_slcd.c
new file mode 100644
index 00000000..c3bacd0d
--- /dev/null
+++ b/watch-library/hardware/watch/watch_slcd.c
@@ -0,0 +1,101 @@
+/*
+ * 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"
+#include "hpl_slcd_config.h"
+
+ //////////////////////////////////////////////////////////////////////////////////////////
+// Segmented Display
+
+static void _sync_slcd(void) {
+ while (SLCD->SYNCBUSY.reg);
+}
+
+void watch_enable_display(void) {
+ SEGMENT_LCD_0_init();
+ slcd_sync_enable(&SEGMENT_LCD_0);
+}
+
+inline void watch_set_pixel(uint8_t com, uint8_t seg) {
+ slcd_sync_seg_on(&SEGMENT_LCD_0, SLCD_SEGID(com, seg));
+}
+
+inline void watch_clear_pixel(uint8_t com, uint8_t seg) {
+ slcd_sync_seg_off(&SEGMENT_LCD_0, SLCD_SEGID(com, seg));
+}
+
+void watch_clear_display(void) {
+ SLCD->SDATAL0.reg = 0;
+ SLCD->SDATAL1.reg = 0;
+ SLCD->SDATAL2.reg = 0;
+}
+
+void watch_start_character_blink(char character, uint32_t duration) {
+ SLCD->CTRLD.bit.FC0EN = 0;
+ _sync_slcd();
+
+ if (duration <= SLCD_FC_BYPASS_MAX_MS) {
+ SLCD->FC0.reg = SLCD_FC0_PB | ((duration / (1000 / SLCD_FRAME_FREQUENCY)) - 1);
+ } else {
+ SLCD->FC0.reg = (((duration / (1000 / SLCD_FRAME_FREQUENCY)) / 8 - 1));
+ }
+ SLCD->CTRLD.bit.FC0EN = 1;
+
+ watch_display_character(character, 7);
+ watch_clear_pixel(2, 10); // clear segment B of position 7 since it can't blink
+
+ SLCD->CTRLD.bit.BLINK = 0;
+ SLCD->CTRLA.bit.ENABLE = 0;
+ _sync_slcd();
+
+ SLCD->BCFG.bit.BSS0 = 0x07;
+ SLCD->BCFG.bit.BSS1 = 0x07;
+
+ SLCD->CTRLD.bit.BLINK = 1;
+ _sync_slcd();
+ SLCD->CTRLA.bit.ENABLE = 1;
+ _sync_slcd();
+}
+
+void watch_stop_blink(void) {
+ SLCD->CTRLD.bit.FC0EN = 0;
+ SLCD->CTRLD.bit.BLINK = 0;
+}
+
+void watch_start_tick_animation(uint32_t duration) {
+ watch_display_character(' ', 8);
+ const uint32_t segs[] = { SLCD_SEGID(0, 2)};
+ slcd_sync_start_animation(&SEGMENT_LCD_0, segs, 1, duration);
+}
+
+bool watch_tick_animation_is_running(void) {
+ return hri_slcd_get_CTRLD_CSREN_bit(SLCD);
+}
+
+void watch_stop_tick_animation(void) {
+ const uint32_t segs[] = { SLCD_SEGID(0, 2)};
+ slcd_sync_stop_animation(&SEGMENT_LCD_0, segs, 1);
+ watch_display_character(' ', 8);
+}
diff --git a/watch-library/hardware/watch/watch_uart.c b/watch-library/hardware/watch/watch_uart.c
new file mode 100644
index 00000000..64b63bee
--- /dev/null
+++ b/watch-library/hardware/watch/watch_uart.c
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+ /*
+ * UART methods are Copyright (c) 2014-2017, Alex Taradov <alex@taradov.com>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "watch_uart.h"
+#include "peripheral_clk_config.h"
+
+void watch_enable_debug_uart(uint32_t baud) {
+ uint64_t br = (uint64_t)65536 * ((CONF_CPU_FREQUENCY * 4) - 16 * baud) / (CONF_CPU_FREQUENCY * 4);
+
+ gpio_set_pin_direction(A2, GPIO_DIRECTION_OUT);
+ gpio_set_pin_function(A2, PINMUX_PB02C_SERCOM3_PAD0);
+
+ MCLK->APBCMASK.reg |= MCLK_APBCMASK_SERCOM3;
+
+ GCLK->PCHCTRL[SERCOM3_GCLK_ID_CORE].reg = GCLK_PCHCTRL_GEN(0) | GCLK_PCHCTRL_CHEN;
+ while (0 == (GCLK->PCHCTRL[SERCOM3_GCLK_ID_CORE].reg & GCLK_PCHCTRL_CHEN));
+
+ SERCOM3->USART.CTRLA.reg =
+ SERCOM_USART_CTRLA_DORD | SERCOM_USART_CTRLA_MODE(1/*USART_INT_CLK*/) |
+ SERCOM_USART_CTRLA_RXPO(1/*PAD1*/) | SERCOM_USART_CTRLA_TXPO(0/*PAD0*/);
+
+ SERCOM3->USART.CTRLB.reg = SERCOM_USART_CTRLB_RXEN | SERCOM_USART_CTRLB_TXEN |
+ SERCOM_USART_CTRLB_CHSIZE(0/*8 bits*/);
+
+ SERCOM3->USART.BAUD.reg = (uint16_t)br;
+
+ SERCOM3->USART.CTRLA.reg |= SERCOM_USART_CTRLA_ENABLE;
+}
+
+void watch_debug_putc(char c) {
+ while (!(SERCOM3->USART.INTFLAG.reg & SERCOM_USART_INTFLAG_DRE));
+ SERCOM3->USART.DATA.reg = c;
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+void watch_debug_puts(char *s) {
+ while (*s) watch_debug_putc(*s++);
+}
+#pragma GCC diagnostic pop