Initial seemingly stable version of movement using the RTC COUNTER32 mode
This commit is contained in:
@@ -20,6 +20,9 @@ TINYUSB_CDC=1
|
||||
# Now we're all set to include gossamer's make rules.
|
||||
include $(GOSSAMER_PATH)/make.mk
|
||||
|
||||
# Don't add gossamer's rtc.c since we are using our own rtc32.c
|
||||
SRCS := $(filter-out $(GOSSAMER_PATH)/peripherals/rtc.c,$(SRCS))
|
||||
|
||||
CFLAGS+=-D_POSIX_C_SOURCE=200112L
|
||||
|
||||
define n
|
||||
@@ -136,6 +139,7 @@ INCLUDES += \
|
||||
-I./watch-library/hardware/watch \
|
||||
|
||||
SRCS += \
|
||||
./watch-library/hardware/watch/rtc32.c \
|
||||
./watch-library/hardware/watch/watch.c \
|
||||
./watch-library/hardware/watch/watch_adc.c \
|
||||
./watch-library/hardware/watch/watch_deepsleep.c \
|
||||
|
||||
+495
-272
File diff suppressed because it is too large
Load Diff
+16
-23
@@ -134,6 +134,17 @@ typedef enum {
|
||||
EVENT_DOUBLE_TAP, // Accelerometer detected a double tap. This event is not yet implemented.
|
||||
} movement_event_type_t;
|
||||
|
||||
// Each different timeout type will use a different index when invoking watch_rtc_register_comp_callback
|
||||
typedef enum {
|
||||
LIGHT_BUTTON_TIMEOUT = 0, // Light button longpress timeout
|
||||
MODE_BUTTON_TIMEOUT, // Mode button longpress timeout
|
||||
ALARM_BUTTON_TIMEOUT, // Alarm button longpress timeout
|
||||
LED_TIMEOUT, // LED off timeout
|
||||
RESIGN_TIMEOUT, // Resign active face timeout
|
||||
SLEEP_TIMEOUT, // Low-energy begin timeout
|
||||
MINUTE_TIMEOUT, // Top of the Minute timeout
|
||||
} movement_timeout_index_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t event_type;
|
||||
uint8_t subsecond;
|
||||
@@ -249,37 +260,16 @@ typedef struct {
|
||||
int16_t current_face_idx;
|
||||
int16_t next_face_idx;
|
||||
bool watch_face_changed;
|
||||
bool fast_tick_enabled;
|
||||
int16_t fast_ticks;
|
||||
|
||||
// LED stuff
|
||||
int16_t light_ticks;
|
||||
|
||||
// alarm stuff
|
||||
int16_t alarm_ticks;
|
||||
bool is_buzzing;
|
||||
watch_buzzer_note_t alarm_note;
|
||||
|
||||
// button tracking for long press
|
||||
uint16_t light_down_timestamp;
|
||||
uint16_t mode_down_timestamp;
|
||||
uint16_t alarm_down_timestamp;
|
||||
bool light_on;
|
||||
|
||||
// background task handling
|
||||
bool woke_from_alarm_handler;
|
||||
bool has_scheduled_background_task;
|
||||
bool needs_wake;
|
||||
|
||||
// low energy mode countdown
|
||||
int32_t le_mode_ticks;
|
||||
|
||||
// app resignation countdown (TODO: consolidate with LE countdown?)
|
||||
int16_t timeout_ticks;
|
||||
|
||||
// stuff for subsecond tracking
|
||||
uint8_t tick_frequency;
|
||||
uint8_t last_second;
|
||||
uint8_t subsecond;
|
||||
uint8_t tick_pern;
|
||||
|
||||
// backup register stuff
|
||||
uint8_t next_available_backup_register;
|
||||
@@ -324,9 +314,11 @@ void movement_cancel_background_task_for_face(uint8_t watch_face_index);
|
||||
void movement_request_sleep(void);
|
||||
void movement_request_wake(void);
|
||||
|
||||
void movement_play_note(watch_buzzer_note_t note, uint16_t duration_ms);
|
||||
void movement_play_signal(void);
|
||||
void movement_play_alarm(void);
|
||||
void movement_play_alarm_beeps(uint8_t rounds, watch_buzzer_note_t alarm_note);
|
||||
void movement_play_sequence(int8_t *note_sequence, uint8_t priority);
|
||||
|
||||
uint8_t movement_claim_backup_register(void);
|
||||
|
||||
@@ -340,6 +332,7 @@ watch_date_time_t movement_get_utc_date_time(void);
|
||||
watch_date_time_t movement_get_local_date_time(void);
|
||||
watch_date_time_t movement_get_date_time_in_zone(uint8_t zone_index);
|
||||
|
||||
void movement_set_utc_date_time(watch_date_time_t date_time);
|
||||
void movement_set_local_date_time(watch_date_time_t date_time);
|
||||
|
||||
bool movement_button_should_sound(void);
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2022 Joey Castillo
|
||||
* Copyright (c) 2025 Alessandro Genova
|
||||
*
|
||||
* 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 <stddef.h>
|
||||
#include "rtc32.h"
|
||||
#include "sam.h"
|
||||
|
||||
rtc_cb_t _rtc_callback = NULL;
|
||||
|
||||
#if defined(_SAMD21_) || defined(_SAMD11_)
|
||||
#define CTRLREG (RTC->MODE0.CTRL)
|
||||
#define MODE_SETTING (RTC_MODE0_CTRL_MODE_COUNT32_Val) // Mode 0 Count32
|
||||
#define PRESCALER_SETTING (RTC_MODE0_CTRL_PRESCALER_DIV8_Val)
|
||||
#else
|
||||
#define CTRLREG (RTC->MODE0.CTRLA)
|
||||
#define MODE_SETTING (RTC_MODE0_CTRLA_MODE_COUNT32_Val) // Mode 0 Count32
|
||||
#define PRESCALER_SETTING (RTC_MODE0_CTRLA_PRESCALER_DIV8_Val)
|
||||
#endif
|
||||
|
||||
bool rtc_is_enabled(void) {
|
||||
return CTRLREG.bit.ENABLE;
|
||||
}
|
||||
|
||||
static void _rtc_sync(void) {
|
||||
#if defined(_SAMD21_) || defined(_SAMD11_)
|
||||
while (RTC->MODE0.STATUS.bit.SYNCBUSY);
|
||||
#else
|
||||
while (RTC->MODE0.SYNCBUSY.reg & RTC_MODE0_SYNCBUSY_MASK);
|
||||
#endif
|
||||
}
|
||||
|
||||
void rtc_init(void) {
|
||||
#if defined(_SAMD21_) || defined(_SAMD11_)
|
||||
// enable the RTC
|
||||
PM->APBAMASK.reg |= PM_APBAMASK_RTC;
|
||||
// clock RTC with GCLK3 (prescaled 1024 Hz output from the external crystal)
|
||||
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_GEN(3) | GCLK_CLKCTRL_ID(RTC_GCLK_ID) | GCLK_CLKCTRL_CLKEN;
|
||||
#else
|
||||
MCLK->APBAMASK.reg |= MCLK_APBAMASK_RTC;
|
||||
#endif
|
||||
|
||||
// if (rtc_is_enabled()) return; // don't reset the RTC if it's already set up.
|
||||
// Reset everything, once things are stabilized we can think about preserving some state
|
||||
CTRLREG.bit.ENABLE = 0;
|
||||
|
||||
_rtc_sync();
|
||||
CTRLREG.bit.SWRST = 1;
|
||||
_rtc_sync();
|
||||
|
||||
CTRLREG.bit.MODE = MODE_SETTING;
|
||||
CTRLREG.bit.PRESCALER = PRESCALER_SETTING;
|
||||
|
||||
#if defined(_SAML21_) || defined(_SAML22_) || defined(_SAMD51_)
|
||||
CTRLREG.bit.COUNTSYNC = 1;
|
||||
#endif
|
||||
|
||||
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_OVF;
|
||||
}
|
||||
|
||||
void rtc_enable(void) {
|
||||
if (rtc_is_enabled()) return;
|
||||
CTRLREG.bit.ENABLE = 1;
|
||||
_rtc_sync();
|
||||
}
|
||||
|
||||
void rtc_set_counter(rtc_counter_t counter) {
|
||||
// // syncing before and after was found to increase reliability on Sensor Watch
|
||||
_rtc_sync();
|
||||
RTC->MODE0.COUNT.reg = counter;
|
||||
_rtc_sync();
|
||||
}
|
||||
|
||||
rtc_counter_t rtc_get_counter(void) {
|
||||
rtc_counter_t counter;
|
||||
|
||||
#if defined(_SAML21_) || defined(_SAML22_) || defined(_SAMD51_)
|
||||
CTRLREG.bit.COUNTSYNC = 1;
|
||||
#endif
|
||||
_rtc_sync();
|
||||
counter = RTC->MODE0.COUNT.reg;
|
||||
|
||||
return counter;
|
||||
}
|
||||
|
||||
void rtc_enable_compare_interrupt(uint32_t compare_time) {
|
||||
RTC->MODE0.COMP[0].reg = compare_time;
|
||||
_rtc_sync();
|
||||
RTC->MODE0.INTENSET.reg = RTC_MODE0_INTENSET_CMP0;
|
||||
// NVIC_ClearPendingIRQ(RTC_IRQn);
|
||||
// NVIC_EnableIRQ(RTC_IRQn);
|
||||
}
|
||||
|
||||
void rtc_configure_callback(rtc_cb_t callback) {
|
||||
_rtc_callback = callback;
|
||||
}
|
||||
|
||||
void rtc_disable_compare_interrupt(void){
|
||||
RTC->MODE0.INTENCLR.reg = RTC_MODE0_INTENCLR_CMP0;
|
||||
// NVIC_ClearPendingIRQ(RTC_IRQn);
|
||||
// NVIC_DisableIRQ(RTC_IRQn);
|
||||
}
|
||||
|
||||
void irq_handler_rtc(void);
|
||||
|
||||
void irq_handler_rtc(void) {
|
||||
uint16_t int_cause = (uint16_t)RTC->MODE0.INTFLAG.reg;
|
||||
RTC->MODE0.INTFLAG.reg = RTC_MODE0_INTFLAG_MASK;
|
||||
(void)RTC->MODE0.INTFLAG.reg;
|
||||
|
||||
/* Invoke registered Callback function */
|
||||
if (_rtc_callback != NULL) {
|
||||
_rtc_callback(int_cause);
|
||||
}
|
||||
|
||||
// NVIC_ClearPendingIRQ(RTC_IRQn);
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
////< @file rtc32.h
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Joey Castillo
|
||||
* Copyright (c) 2025 Alessandro Genova
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @addtogroup rtc Real-Time Clock
|
||||
* @brief Functions for configuring and using the Real-Time Clock peripheral.
|
||||
* @details This is the rtc implementation for MODE0 (counter32)
|
||||
* @{
|
||||
*/
|
||||
|
||||
#define 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.
|
||||
} rtc_date_time_t;
|
||||
|
||||
typedef enum rtc_alarm_match_t {
|
||||
ALARM_MATCH_DISABLED = 0,
|
||||
ALARM_MATCH_SS,
|
||||
ALARM_MATCH_MMSS,
|
||||
ALARM_MATCH_HHMMSS,
|
||||
} rtc_alarm_match_t;
|
||||
|
||||
typedef uint32_t rtc_counter_t;
|
||||
|
||||
typedef void (*rtc_cb_t)(uint16_t intflag);
|
||||
|
||||
/** @brief Initializes the RTC.
|
||||
* @details Configures the RTC for COUNT32 mode, with a 1 Hz
|
||||
* tick derived from the 1024 Hz clock on GCLK3 (for SAM D devices)
|
||||
* or OSC32KCTRL's most accurate 1024 Hz output (for SAM L devices).
|
||||
*/
|
||||
void rtc_init(void);
|
||||
|
||||
/** @brief Enables the RTC.
|
||||
*/
|
||||
void rtc_enable(void);
|
||||
|
||||
/** @brief Checks if the RTC is enabled.
|
||||
* @return true if the RTC is enabled; false if not.
|
||||
*/
|
||||
bool rtc_is_enabled(void);
|
||||
|
||||
/** @brief Set the value of the counter register.
|
||||
*/
|
||||
void rtc_set_counter(rtc_counter_t counter);
|
||||
|
||||
/** @brief Returns the value of the counter register.
|
||||
*/
|
||||
rtc_counter_t rtc_get_counter(void);
|
||||
|
||||
/** @brief Configures the RTC alarm callback.
|
||||
* @param callback The function to call when an RTC interrupt occurs. The callback
|
||||
* will be passed a bitmask of the interrupt flags, the full contents
|
||||
* of the RTC peripheral's INTFLAG register.
|
||||
*/
|
||||
void rtc_configure_callback(rtc_cb_t callback);
|
||||
|
||||
void rtc_enable_compare_interrupt(uint32_t compare_time);
|
||||
void rtc_disable_compare_interrupt(void);
|
||||
|
||||
/** @} */
|
||||
@@ -2,6 +2,7 @@
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020 Joey Castillo
|
||||
* Copyright (c) 2025 Alessandro Genova
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -23,11 +24,32 @@
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "watch_rtc.h"
|
||||
#include "watch_private.h"
|
||||
#include "watch_utility.h"
|
||||
|
||||
static const uint32_t RTC_OSC_DIV = 10;
|
||||
static const uint32_t RTC_OSC_HZ = 1 << RTC_OSC_DIV; // 2^10 = 1024
|
||||
static const uint32_t RTC_PRESCALER_DIV = 3;
|
||||
static const uint32_t RTC_CNT_HZ = RTC_OSC_HZ >> RTC_PRESCALER_DIV; // 1024 / 2^3 = 128
|
||||
static const uint32_t RTC_CNT_DIV = RTC_OSC_DIV - RTC_PRESCALER_DIV; // 7
|
||||
static const uint32_t RTC_CNT_TICKS_PER_MINUTE = RTC_CNT_HZ * 60;
|
||||
static const uint32_t RTC_CNT_TICKS_PER_HOUR = RTC_CNT_TICKS_PER_MINUTE * 60;
|
||||
|
||||
static const int TB_BKUP_REG = 7;
|
||||
|
||||
#define WATCH_RTC_N_COMP_CB 8
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t counter;
|
||||
volatile watch_cb_t callback;
|
||||
volatile bool enabled;
|
||||
} comp_cb_t;
|
||||
|
||||
watch_cb_t tick_callbacks[8];
|
||||
comp_cb_t comp_callbacks[WATCH_RTC_N_COMP_CB];
|
||||
watch_cb_t alarm_callback;
|
||||
watch_cb_t btn_alarm_callback;
|
||||
watch_cb_t a2_callback;
|
||||
@@ -46,14 +68,49 @@ void _watch_rtc_init(void) {
|
||||
#endif
|
||||
rtc_enable();
|
||||
rtc_configure_callback(watch_rtc_callback);
|
||||
|
||||
for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) {
|
||||
comp_callbacks[index].counter = 0;
|
||||
comp_callbacks[index].callback = NULL;
|
||||
comp_callbacks[index].enabled = false;
|
||||
}
|
||||
|
||||
NVIC_ClearPendingIRQ(RTC_IRQn);
|
||||
NVIC_EnableIRQ(RTC_IRQn);
|
||||
}
|
||||
|
||||
void watch_rtc_set_date_time(rtc_date_time_t date_time) {
|
||||
rtc_set_date_time(date_time);
|
||||
watch_rtc_set_unix_time(watch_utility_date_time_to_unix_time(date_time, 0));
|
||||
}
|
||||
|
||||
rtc_date_time_t watch_rtc_get_date_time(void) {
|
||||
return rtc_get_date_time();
|
||||
return watch_utility_date_time_from_unix_time(watch_rtc_get_unix_time(), 0);
|
||||
}
|
||||
|
||||
void watch_rtc_set_unix_time(unix_timestamp_t unix_time) {
|
||||
// time_backup + counter / RTC_CNT_HZ = unix_time
|
||||
rtc_counter_t counter = rtc_get_counter();
|
||||
unix_timestamp_t tb = unix_time - (counter >> RTC_CNT_DIV);
|
||||
watch_store_backup_data(tb, TB_BKUP_REG);
|
||||
}
|
||||
|
||||
unix_timestamp_t watch_rtc_get_unix_time(void) {
|
||||
// time_backup + counter / RTC_CNT_HZ = unix_time
|
||||
rtc_counter_t counter = rtc_get_counter();
|
||||
unix_timestamp_t tb = watch_get_backup_data(TB_BKUP_REG);
|
||||
return tb + (counter >> RTC_CNT_DIV);
|
||||
}
|
||||
|
||||
rtc_counter_t watch_rtc_get_counter(void) {
|
||||
return rtc_get_counter();
|
||||
}
|
||||
|
||||
uint32_t watch_rtc_get_frequency(void) {
|
||||
return RTC_CNT_HZ;
|
||||
}
|
||||
|
||||
uint32_t watch_rtc_get_ticks_per_minute(void) {
|
||||
return RTC_CNT_TICKS_PER_MINUTE;
|
||||
}
|
||||
|
||||
rtc_date_time_t watch_get_init_date_time(void) {
|
||||
@@ -103,57 +160,98 @@ void watch_rtc_register_periodic_callback(watch_cb_t callback, uint8_t frequency
|
||||
// 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;
|
||||
// NVIC_ClearPendingIRQ(RTC_IRQn);
|
||||
// NVIC_EnableIRQ(RTC_IRQn);
|
||||
RTC->MODE0.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 & 0xFF) << 24);
|
||||
RTC->MODE2.INTENCLR.reg = 1 << per_n;
|
||||
RTC->MODE0.INTENCLR.reg = 1 << per_n;
|
||||
}
|
||||
|
||||
void watch_rtc_disable_matching_periodic_callbacks(uint8_t mask) {
|
||||
RTC->MODE2.INTENCLR.reg = mask;
|
||||
RTC->MODE0.INTENCLR.reg = mask;
|
||||
}
|
||||
|
||||
void watch_rtc_disable_all_periodic_callbacks(void) {
|
||||
watch_rtc_disable_matching_periodic_callbacks(0xFF);
|
||||
}
|
||||
|
||||
void watch_rtc_register_alarm_callback(watch_cb_t callback, rtc_date_time_t alarm_time, rtc_alarm_match_t 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;
|
||||
static void _watch_rtc_schedule_next_comp(void) {
|
||||
rtc_disable_compare_interrupt();
|
||||
|
||||
// The soonest we can schedule is the next tick
|
||||
rtc_counter_t curr_counter = watch_rtc_get_counter() + 1;
|
||||
|
||||
bool schedule_any = false;
|
||||
rtc_counter_t comp_counter;
|
||||
rtc_counter_t min_diff = UINT_MAX;
|
||||
|
||||
for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) {
|
||||
if (comp_callbacks[index].enabled) {
|
||||
rtc_counter_t diff = comp_callbacks[index].counter - curr_counter;
|
||||
if (diff <= min_diff) {
|
||||
min_diff = diff;
|
||||
comp_counter = comp_callbacks[index].counter;
|
||||
schedule_any = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (schedule_any) {
|
||||
rtc_enable_compare_interrupt(comp_counter);
|
||||
}
|
||||
}
|
||||
|
||||
void watch_rtc_disable_alarm_callback(void) {
|
||||
RTC->MODE2.INTENCLR.reg = RTC_MODE2_INTENCLR_ALARM0;
|
||||
void watch_rtc_register_comp_callback(watch_cb_t callback, rtc_counter_t counter, uint8_t index) {
|
||||
if (index >= WATCH_RTC_N_COMP_CB) {
|
||||
return;
|
||||
}
|
||||
|
||||
rtc_disable_compare_interrupt();
|
||||
|
||||
comp_callbacks[index].counter = counter;
|
||||
comp_callbacks[index].callback = callback;
|
||||
comp_callbacks[index].enabled = true;
|
||||
|
||||
_watch_rtc_schedule_next_comp();
|
||||
}
|
||||
|
||||
void watch_rtc_callback(uint16_t interrupt_status) {
|
||||
uint16_t interrupt_enabled = RTC->MODE2.INTENSET.reg;
|
||||
void watch_rtc_disable_comp_callback(uint8_t index) {
|
||||
if (index >= WATCH_RTC_N_COMP_CB) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((interrupt_status & interrupt_enabled) & RTC_MODE2_INTFLAG_PER_Msk) {
|
||||
rtc_disable_compare_interrupt();
|
||||
|
||||
comp_callbacks[index].enabled = false;
|
||||
|
||||
_watch_rtc_schedule_next_comp();
|
||||
}
|
||||
|
||||
void watch_rtc_callback(uint16_t interrupt_cause) {
|
||||
// First read all relevant registers, to ensure no changes occurr during the callbacks
|
||||
uint16_t interrupt_enabled = RTC->MODE0.INTENSET.reg;
|
||||
rtc_counter_t comp_counter = RTC->MODE0.COMP[0].reg;
|
||||
|
||||
|
||||
if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_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 ((interrupt_cause & interrupt_enabled) & (1 << i)) {
|
||||
if (tick_callbacks[i] != NULL) {
|
||||
tick_callbacks[i]();
|
||||
}
|
||||
RTC->MODE2.INTFLAG.reg = 1 << i;
|
||||
// break; Uncertain if this fix is requried. We were discussing in discord. Might slightly increase power consumption.
|
||||
}
|
||||
}
|
||||
} else if ((interrupt_status & interrupt_enabled) & RTC_MODE2_INTFLAG_TAMPER) {
|
||||
}
|
||||
|
||||
if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_TAMPER) {
|
||||
// handle the extwake interrupts next.
|
||||
uint8_t reason = RTC->MODE2.TAMPID.reg;
|
||||
uint8_t reason = RTC->MODE0.TAMPID.reg;
|
||||
if (reason & RTC_TAMPID_TAMPID2) {
|
||||
if (btn_alarm_callback != NULL) btn_alarm_callback();
|
||||
} else if (reason & RTC_TAMPID_TAMPID1) {
|
||||
@@ -161,25 +259,37 @@ void watch_rtc_callback(uint16_t interrupt_status) {
|
||||
} 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->MODE0.TAMPID.reg = reason;
|
||||
}
|
||||
|
||||
if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_CMP0) {
|
||||
// The comp interrupt is generated one tick after the matched counter
|
||||
// rtc_counter_t comp_counter = watch_rtc_get_counter() - 1;
|
||||
|
||||
for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) {
|
||||
if (comp_callbacks[index].enabled && comp_counter == comp_callbacks[index].counter) {
|
||||
comp_callbacks[index].enabled = false;
|
||||
comp_callbacks[index].callback();
|
||||
}
|
||||
}
|
||||
RTC->MODE2.INTFLAG.reg = RTC_MODE2_INTFLAG_ALARM0;
|
||||
_watch_rtc_schedule_next_comp();
|
||||
}
|
||||
|
||||
if ((interrupt_cause & interrupt_enabled) & RTC_MODE0_INTFLAG_OVF) {
|
||||
// Handle the overflow of the counter. All we need to do is reset the reference time.
|
||||
unix_timestamp_t tb = watch_get_backup_data(TB_BKUP_REG);
|
||||
watch_store_backup_data(tb + (UINT_MAX >> RTC_CNT_DIV), TB_BKUP_REG);
|
||||
}
|
||||
}
|
||||
|
||||
void watch_rtc_enable(bool en) {
|
||||
// Writing it twice - as it's quite dangerous operation.
|
||||
// If write fails - we might hang with RTC off, which means no recovery possible
|
||||
while (RTC->MODE2.SYNCBUSY.reg);
|
||||
RTC->MODE2.CTRLA.bit.ENABLE = en ? 1 : 0;
|
||||
while (RTC->MODE2.SYNCBUSY.reg);
|
||||
RTC->MODE2.CTRLA.bit.ENABLE = en ? 1 : 0;
|
||||
while (RTC->MODE2.SYNCBUSY.reg);
|
||||
while (RTC->MODE0.SYNCBUSY.reg);
|
||||
RTC->MODE0.CTRLA.bit.ENABLE = en ? 1 : 0;
|
||||
while (RTC->MODE0.SYNCBUSY.reg);
|
||||
RTC->MODE0.CTRLA.bit.ENABLE = en ? 1 : 0;
|
||||
while (RTC->MODE0.SYNCBUSY.reg);
|
||||
}
|
||||
|
||||
void watch_rtc_freqcorr_write(int16_t value, int16_t sign) {
|
||||
@@ -188,8 +298,7 @@ void watch_rtc_freqcorr_write(int16_t value, int16_t sign) {
|
||||
data.bit.VALUE = value;
|
||||
data.bit.SIGN = sign;
|
||||
|
||||
RTC->MODE2.FREQCORR.reg = data.reg; // Setting correction in single write operation
|
||||
RTC->MODE0.FREQCORR.reg = data.reg; // Setting correction in single write operation
|
||||
|
||||
// We do not sycnronize. We are not in a hurry
|
||||
}
|
||||
|
||||
|
||||
@@ -32,9 +32,13 @@ void cb_watch_buzzer_seq(void);
|
||||
|
||||
static uint16_t _seq_position;
|
||||
static int8_t _tone_ticks, _repeat_counter;
|
||||
static bool _callback_running = false;
|
||||
static int8_t *_sequence;
|
||||
static uint8_t _volume;
|
||||
static void (*_cb_finished)(void);
|
||||
static watch_cb_t _cb_start_global = NULL;
|
||||
static watch_cb_t _cb_stop_global = NULL;
|
||||
static volatile bool _led_is_active = false;
|
||||
static volatile bool _buzzer_is_active = false;
|
||||
|
||||
static void _tcc_write_RUNSTDBY(bool value) {
|
||||
// enables or disables RUNSTDBY of the tcc
|
||||
@@ -46,13 +50,11 @@ static void _tcc_write_RUNSTDBY(bool value) {
|
||||
static inline void _tc0_start() {
|
||||
// start the TC0 timer
|
||||
tc_enable(0);
|
||||
_callback_running = true;
|
||||
}
|
||||
|
||||
static inline void _tc0_stop() {
|
||||
// stop the TC0 timer
|
||||
tc_disable(0);
|
||||
_callback_running = false;
|
||||
}
|
||||
|
||||
static void _tc0_initialize() {
|
||||
@@ -68,19 +70,32 @@ static void _tc0_initialize() {
|
||||
}
|
||||
|
||||
void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(void)) {
|
||||
if (_callback_running) _tc0_stop();
|
||||
watch_buzzer_play_sequence_with_volume(note_sequence, callback_on_end, WATCH_BUZZER_VOLUME_LOUD);
|
||||
}
|
||||
|
||||
void watch_buzzer_play_sequence_with_volume(int8_t *note_sequence, void (*callback_on_end)(void), watch_buzzer_volume_t volume) {
|
||||
// Abort any previous sequence
|
||||
watch_buzzer_abort_sequence();
|
||||
|
||||
_buzzer_is_active = true;
|
||||
|
||||
if (_cb_start_global) {
|
||||
_cb_start_global();
|
||||
}
|
||||
|
||||
watch_enable_buzzer_and_leds();
|
||||
|
||||
watch_set_buzzer_off();
|
||||
_sequence = note_sequence;
|
||||
_cb_finished = callback_on_end;
|
||||
_volume = volume == WATCH_BUZZER_VOLUME_SOFT ? 5 : 25;
|
||||
_seq_position = 0;
|
||||
_tone_ticks = 0;
|
||||
_repeat_counter = -1;
|
||||
// prepare buzzer
|
||||
watch_enable_buzzer();
|
||||
|
||||
// setup TC0 timer
|
||||
_tc0_initialize();
|
||||
// TCC should run in standby mode
|
||||
_tcc_write_RUNSTDBY(true);
|
||||
// start the timer (for the 64 hz callback)
|
||||
_tc0_start();
|
||||
}
|
||||
@@ -110,7 +125,7 @@ void cb_watch_buzzer_seq(void) {
|
||||
// read note
|
||||
watch_buzzer_note_t note = _sequence[_seq_position];
|
||||
if (note != BUZZER_NOTE_REST) {
|
||||
watch_set_buzzer_period_and_duty_cycle(NotePeriods[note], 25);
|
||||
watch_set_buzzer_period_and_duty_cycle(NotePeriods[note], _volume);
|
||||
watch_set_buzzer_on();
|
||||
} else watch_set_buzzer_off();
|
||||
// set duration ticks and move to next tone
|
||||
@@ -119,17 +134,37 @@ void cb_watch_buzzer_seq(void) {
|
||||
} else {
|
||||
// end the sequence
|
||||
watch_buzzer_abort_sequence();
|
||||
if (_cb_finished) _cb_finished();
|
||||
}
|
||||
} else _tone_ticks--;
|
||||
}
|
||||
|
||||
void watch_buzzer_abort_sequence(void) {
|
||||
// ends/aborts the sequence
|
||||
if (_callback_running) _tc0_stop();
|
||||
if (!_buzzer_is_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
_buzzer_is_active = false;
|
||||
|
||||
_tc0_stop();
|
||||
|
||||
watch_set_buzzer_off();
|
||||
// disable standby mode for TCC
|
||||
_tcc_write_RUNSTDBY(false);
|
||||
|
||||
// disable TCC
|
||||
watch_maybe_disable_buzzer_and_leds();
|
||||
|
||||
if (_cb_stop_global) {
|
||||
_cb_stop_global();
|
||||
}
|
||||
|
||||
if (_cb_finished) {
|
||||
_cb_finished();
|
||||
}
|
||||
}
|
||||
|
||||
void watch_buzzer_register_global_callbacks(watch_cb_t cb_start, watch_cb_t cb_stop) {
|
||||
_cb_stop_global = cb_start;
|
||||
_cb_stop_global = cb_stop;
|
||||
}
|
||||
|
||||
void irq_handler_tc0(void) {
|
||||
@@ -142,19 +177,41 @@ bool watch_is_buzzer_or_led_enabled(void){
|
||||
return tcc_is_enabled(0);
|
||||
}
|
||||
|
||||
inline void watch_enable_buzzer(void) {
|
||||
void watch_enable_buzzer_and_leds(void) {
|
||||
if (!tcc_is_enabled(0)) {
|
||||
// tcc_set_run_in_standby(0, true);
|
||||
_watch_enable_tcc();
|
||||
// TCC should run in standby mode
|
||||
_tcc_write_RUNSTDBY(true);
|
||||
}
|
||||
}
|
||||
|
||||
void watch_disable_buzzer_and_leds(void) {
|
||||
if (tcc_is_enabled(0)) {
|
||||
_tcc_write_RUNSTDBY(false);
|
||||
_watch_disable_tcc();
|
||||
}
|
||||
}
|
||||
|
||||
void watch_maybe_disable_buzzer_and_leds(void) {
|
||||
if (_buzzer_is_active || _led_is_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
watch_disable_buzzer_and_leds();
|
||||
}
|
||||
|
||||
void watch_enable_buzzer(void) {
|
||||
watch_enable_buzzer_and_leds();
|
||||
}
|
||||
|
||||
void watch_set_buzzer_period_and_duty_cycle(uint32_t period, uint8_t duty) {
|
||||
tcc_set_period(0, period, true);
|
||||
tcc_set_cc(0, (WATCH_BUZZER_TCC_CHANNEL) % 4, period / (100 / duty), true);
|
||||
}
|
||||
|
||||
void watch_disable_buzzer(void) {
|
||||
_watch_disable_tcc();
|
||||
watch_maybe_disable_buzzer_and_leds();
|
||||
}
|
||||
|
||||
inline void watch_set_buzzer_on(void) {
|
||||
@@ -172,14 +229,17 @@ void watch_buzzer_play_note(watch_buzzer_note_t note, uint16_t duration_ms) {
|
||||
}
|
||||
|
||||
void watch_buzzer_play_note_with_volume(watch_buzzer_note_t note, uint16_t duration_ms, watch_buzzer_volume_t volume) {
|
||||
if (note == BUZZER_NOTE_REST) {
|
||||
watch_set_buzzer_off();
|
||||
} else {
|
||||
watch_set_buzzer_period_and_duty_cycle(NotePeriods[note], volume == WATCH_BUZZER_VOLUME_SOFT ? 5 : 25);
|
||||
watch_set_buzzer_on();
|
||||
}
|
||||
delay_ms(duration_ms);
|
||||
watch_set_buzzer_off();
|
||||
static int8_t single_note_sequence[3];
|
||||
|
||||
single_note_sequence[0] = note;
|
||||
// 48 ticks per second for the tc0?
|
||||
// Each tick is approximately 20ms
|
||||
uint16_t duration = duration_ms / 20;
|
||||
if (duration > 127) duration = 127;
|
||||
single_note_sequence[1] = (int8_t)duration;
|
||||
single_note_sequence[2] = 0;
|
||||
|
||||
watch_buzzer_play_sequence_with_volume(single_note_sequence, NULL, volume);
|
||||
}
|
||||
|
||||
void _watch_enable_tcc(void) {
|
||||
@@ -263,7 +323,7 @@ void watch_enable_leds(void) {
|
||||
}
|
||||
|
||||
void watch_disable_leds(void) {
|
||||
_watch_disable_tcc();
|
||||
watch_maybe_disable_buzzer_and_leds();
|
||||
}
|
||||
|
||||
void watch_set_led_color(uint8_t red, uint8_t green) {
|
||||
@@ -275,6 +335,15 @@ void watch_set_led_color(uint8_t red, uint8_t green) {
|
||||
}
|
||||
|
||||
void watch_set_led_color_rgb(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
bool turning_on = (red | green | blue) != 0;
|
||||
|
||||
if (turning_on) {
|
||||
_led_is_active = true;
|
||||
watch_enable_buzzer_and_leds();
|
||||
} else {
|
||||
_led_is_active = false;
|
||||
}
|
||||
|
||||
if (tcc_is_enabled(0)) {
|
||||
uint32_t period = tcc_get_period(0);
|
||||
tcc_set_cc(0, (WATCH_RED_TCC_CHANNEL) % 4, ((period * (uint32_t)red * 1000ull) / 255000ull), true);
|
||||
@@ -289,6 +358,10 @@ void watch_set_led_color_rgb(uint8_t red, uint8_t green, uint8_t blue) {
|
||||
(void) blue; // silence warning
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!turning_on) {
|
||||
watch_maybe_disable_buzzer_and_leds();
|
||||
}
|
||||
}
|
||||
|
||||
void watch_set_led_red(void) {
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
////< @file watch_rtc.h
|
||||
|
||||
#include "watch.h"
|
||||
#include "rtc.h"
|
||||
#include "rtc32.h"
|
||||
|
||||
/** @addtogroup rtc Real-Time Clock
|
||||
* @brief This section covers functions related to the SAM L22's real-time clock peripheral, including
|
||||
@@ -42,17 +42,20 @@
|
||||
extern watch_cb_t btn_alarm_callback;
|
||||
extern watch_cb_t a2_callback;
|
||||
extern watch_cb_t a4_callback;
|
||||
extern watch_cb_t comp_callback;
|
||||
|
||||
#define WATCH_RTC_REFERENCE_YEAR (2020)
|
||||
|
||||
#define watch_date_time_t rtc_date_time_t
|
||||
typedef rtc_counter_t watch_counter_t;
|
||||
typedef uint32_t unix_timestamp_t;
|
||||
|
||||
/** @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.
|
||||
/** @brief Sets the date and time. Calls watch_rtc_set_unix_time internally.
|
||||
* @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
|
||||
@@ -62,7 +65,7 @@ bool _watch_rtc_is_enabled(void);
|
||||
*/
|
||||
void watch_rtc_set_date_time(rtc_date_time_t date_time);
|
||||
|
||||
/** @brief Returns the date and time.
|
||||
/** @brief Returns the date and time. Calls watch_rtc_get_unix_time internally.
|
||||
* @return A rtc_date_time_t 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.
|
||||
*/
|
||||
@@ -73,26 +76,51 @@ rtc_date_time_t watch_rtc_get_date_time(void);
|
||||
*/
|
||||
rtc_date_time_t watch_get_init_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
|
||||
/** @brief Set the current UTC date and time using a unix timestamp
|
||||
*/
|
||||
void watch_rtc_set_unix_time(unix_timestamp_t unix_time);
|
||||
|
||||
/** @brief Get the current UTC date and time using a unix timestamp
|
||||
*/
|
||||
unix_timestamp_t watch_rtc_get_unix_time(void);
|
||||
|
||||
/** @brief Get the current value of the internal hardware counter
|
||||
* @details The counter starts at 0 and it increases at a 128Hz rate until it overflows and starts over.
|
||||
* We never manually set the counter. Doing so allows us to calculate absolute elapsed and more.
|
||||
* When the user sets the time, what is modified is the reference time (i.e. the date and time when
|
||||
* the counter is 0).
|
||||
*/
|
||||
rtc_counter_t watch_rtc_get_counter(void);
|
||||
|
||||
/** @brief Get the RTC counter frequency.
|
||||
*/
|
||||
uint32_t watch_rtc_get_frequency(void);
|
||||
|
||||
/** @brief Get how many counter ticks are in one minute.
|
||||
*/
|
||||
uint32_t watch_rtc_get_ticks_per_minute(void);
|
||||
|
||||
/** @brief Registers a callback that will be called when the RTC counter matches the target counter.
|
||||
* @param callback The function you wish to have called when the target counter is reached. If this value is NULL, the comp
|
||||
* 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 rtc_alarm_match_t 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.
|
||||
* @param counter The time that you wish to match. The date is currently ignored.
|
||||
* @param index We can have up to 8 active callbacks at a time. This parameter specifies which of the 8 callbacks should be set.
|
||||
* @details The hardware RTC provides us with single interrupt that fires when the RTC counter matches a target counter COMP0.
|
||||
* With a little bit of logic, we can provide multiple active compare callbacks. Every time a comp callback is
|
||||
* registered/disabled/fired we iterate over all the active comp callbacks and set the hardware COMP0 counter
|
||||
* to the next occurring one.
|
||||
* With this very simple API, movement can implement one-shot timers to turn off the led and determine button longpresses
|
||||
* as well as the inactivity timeouts for resigning and sleeping, as well as emulating the top of the minute alarm.
|
||||
*/
|
||||
void watch_rtc_register_alarm_callback(watch_cb_t callback, rtc_date_time_t alarm_time, rtc_alarm_match_t mask);
|
||||
void watch_rtc_register_comp_callback(watch_cb_t callback, rtc_counter_t counter, uint8_t index);
|
||||
|
||||
/** @brief Disables the specified comp callback.
|
||||
*/
|
||||
void watch_rtc_disable_comp_callback(uint8_t index);
|
||||
|
||||
/** @brief Disables the alarm callback.
|
||||
*/
|
||||
void watch_rtc_disable_alarm_callback(void);
|
||||
// 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
|
||||
@@ -117,10 +145,6 @@ void watch_rtc_disable_tick_callback(void);
|
||||
* 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(watch_cb_t callback, uint8_t frequency);
|
||||
|
||||
|
||||
@@ -170,8 +170,6 @@ void watch_set_buzzer_off(void);
|
||||
/** @brief Plays the given note for a set duration at the loudest possible volume.
|
||||
* @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(watch_buzzer_note_t note, uint16_t duration_ms);
|
||||
|
||||
@@ -179,8 +177,6 @@ void watch_buzzer_play_note(watch_buzzer_note_t note, uint16_t duration_ms);
|
||||
* @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.
|
||||
* @param volume either WATCH_BUZZER_VOLUME_SOFT or WATCH_BUZZER_VOLUME_LOUD
|
||||
* @note This will block your UI for the duration of the note's play time, and after this call, the
|
||||
* buzzer will stop sounding, but the TCC period will remain set to the period of this note.
|
||||
*/
|
||||
void watch_buzzer_play_note_with_volume(watch_buzzer_note_t note, uint16_t duration_ms, watch_buzzer_volume_t volume);
|
||||
|
||||
@@ -202,10 +198,36 @@ extern const uint16_t NotePeriods[108];
|
||||
*/
|
||||
void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(void));
|
||||
|
||||
/** @brief Plays the given sequence of notes in a non-blocking way.
|
||||
* @param note_sequence A pointer to the sequence of buzzer note & duration tuples, ending with a zero. A simple
|
||||
* RLE logic is implemented: a negative number instead of a buzzer note means that the sequence
|
||||
* is rewound by the given number of notes. The byte following a negative number determines the number
|
||||
* of loops. I.e. if you want to repeat the last three notes of the sequence one time, you should provide
|
||||
* the tuple -3, 1. The repeated notes must not contain any other repeat markers, or you will end up with
|
||||
* an eternal loop.
|
||||
* @param callback_on_end A pointer to a callback function to be invoked when the sequence has finished playing.
|
||||
* @param volume either WATCH_BUZZER_VOLUME_SOFT or WATCH_BUZZER_VOLUME_LOUD
|
||||
*/
|
||||
void watch_buzzer_play_sequence_with_volume(int8_t *note_sequence, void (*callback_on_end)(void), watch_buzzer_volume_t volume);
|
||||
|
||||
/** @brief Aborts a playing sequence.
|
||||
*/
|
||||
void watch_buzzer_abort_sequence(void);
|
||||
|
||||
void watch_buzzer_register_global_callbacks(watch_cb_t cb_start, watch_cb_t cb_stop);
|
||||
|
||||
/** @brief Enables the TCC peripheral, which drives the buzzer and the leds.
|
||||
*/
|
||||
void watch_enable_buzzer_and_leds(void);
|
||||
|
||||
/** @brief Disables the TCC peripheral that drives the buzzer and the leds.
|
||||
*/
|
||||
void watch_disable_buzzer_and_leds(void);
|
||||
|
||||
/** @brief Disables the TCC peripheral that drives the buzzer and the leds if neither is currently active
|
||||
*/
|
||||
void watch_maybe_disable_buzzer_and_leds(void);
|
||||
|
||||
#ifndef __EMSCRIPTEN__
|
||||
void irq_handler_tc0(void);
|
||||
#endif
|
||||
|
||||
@@ -278,6 +278,10 @@ watch_date_time_t watch_utility_date_time_convert_zone(watch_date_time_t date_ti
|
||||
return watch_utility_date_time_from_unix_time(timestamp, destination_utc_offset);
|
||||
}
|
||||
|
||||
uint32_t watch_utility_unix_time_convert_zone(uint32_t timestamp, uint32_t origin_utc_offset, uint32_t destination_utc_offset) {
|
||||
return timestamp - origin_utc_offset + destination_utc_offset;
|
||||
}
|
||||
|
||||
watch_duration_t watch_utility_seconds_to_duration(uint32_t seconds) {
|
||||
watch_duration_t retval;
|
||||
|
||||
|
||||
@@ -144,6 +144,16 @@ bool watch_utility_convert_to_12_hour(watch_date_time_t *date_time);
|
||||
*/
|
||||
watch_date_time_t watch_utility_date_time_convert_zone(watch_date_time_t date_time, uint32_t origin_utc_offset, uint32_t destination_utc_offset);
|
||||
|
||||
/** @brief Converts a unix time from a given time zone to another time zone.
|
||||
* @param timestamp The unix 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 unix time for the given UNIX timestamp and UTC offset.
|
||||
* @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
|
||||
*/
|
||||
uint32_t watch_utility_unix_time_convert_zone(uint32_t timestamp, 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
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <limits.h>
|
||||
|
||||
#include "watch_rtc.h"
|
||||
#include "watch_main_loop.h"
|
||||
@@ -29,8 +30,28 @@
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
|
||||
static const uint32_t RTC_CNT_HZ = 128;
|
||||
static const uint32_t RTC_CNT_DIV = 7;
|
||||
static const uint32_t RTC_CNT_TICKS_PER_MINUTE = RTC_CNT_HZ * 60;
|
||||
static const uint32_t RTC_CNT_TICKS_PER_HOUR = RTC_CNT_TICKS_PER_MINUTE * 60;
|
||||
|
||||
static uint32_t counter_interval;
|
||||
static uint32_t counter;
|
||||
static uint32_t reference_timestamp;
|
||||
|
||||
#define WATCH_RTC_N_COMP_CB 8
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t counter;
|
||||
volatile watch_cb_t callback;
|
||||
volatile bool enabled;
|
||||
} comp_cb_t;
|
||||
|
||||
static double time_offset = 0;
|
||||
static long tick_callbacks[8] = { -1, -1, -1, -1, -1, -1, -1, -1 };
|
||||
watch_cb_t tick_callbacks[8];
|
||||
comp_cb_t comp_callbacks[WATCH_RTC_N_COMP_CB];
|
||||
|
||||
static uint32_t scheduled_comp_counter;
|
||||
|
||||
static long alarm_interval_id = -1;
|
||||
static long alarm_timeout_id = -1;
|
||||
@@ -40,41 +61,73 @@ watch_cb_t btn_alarm_callback;
|
||||
watch_cb_t a2_callback;
|
||||
watch_cb_t a4_callback;
|
||||
|
||||
static void _watch_increase_counter(void *userData);
|
||||
static void _watch_process_periodic_callbacks(void);
|
||||
static void _watch_process_comp_callbacks(void);
|
||||
|
||||
bool _watch_rtc_is_enabled(void) {
|
||||
return true;
|
||||
return counter_interval;
|
||||
}
|
||||
|
||||
void _watch_rtc_init(void) {
|
||||
#if EMSCRIPTEN
|
||||
// Shifts the timezone so our local time is converted to UTC and set
|
||||
for (uint8_t index = 0; index < 8; ++index) {
|
||||
tick_callbacks[index] = NULL;
|
||||
}
|
||||
|
||||
for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) {
|
||||
comp_callbacks[index].counter = 0;
|
||||
comp_callbacks[index].callback = NULL;
|
||||
comp_callbacks[index].enabled = false;
|
||||
}
|
||||
|
||||
scheduled_comp_counter = 0;
|
||||
counter = 0;
|
||||
counter_interval = 0;
|
||||
|
||||
watch_rtc_set_date_time(watch_get_init_date_time());
|
||||
watch_rtc_enable(true);
|
||||
}
|
||||
|
||||
void watch_rtc_set_date_time(rtc_date_time_t date_time) {
|
||||
watch_rtc_set_unix_time(watch_utility_date_time_to_unix_time(date_time, 0));
|
||||
}
|
||||
|
||||
rtc_date_time_t watch_rtc_get_date_time(void) {
|
||||
return watch_utility_date_time_from_unix_time(watch_rtc_get_unix_time(), 0);
|
||||
}
|
||||
|
||||
void watch_rtc_set_unix_time(unix_timestamp_t unix_time) {
|
||||
// time_backup + counter / RTC_CNT_HZ = unix_time
|
||||
rtc_counter_t counter = watch_rtc_get_counter();
|
||||
reference_timestamp = unix_time - (counter >> RTC_CNT_DIV);
|
||||
}
|
||||
|
||||
unix_timestamp_t watch_rtc_get_unix_time(void) {
|
||||
// time_backup + counter / RTC_CNT_HZ = unix_time
|
||||
rtc_counter_t counter = watch_rtc_get_counter();
|
||||
return reference_timestamp + (counter >> RTC_CNT_DIV);
|
||||
}
|
||||
|
||||
rtc_counter_t watch_rtc_get_counter(void) {
|
||||
return counter;
|
||||
}
|
||||
|
||||
uint32_t watch_rtc_get_frequency(void) {
|
||||
return RTC_CNT_HZ;
|
||||
}
|
||||
|
||||
uint32_t watch_rtc_get_ticks_per_minute(void) {
|
||||
return RTC_CNT_TICKS_PER_MINUTE;
|
||||
}
|
||||
|
||||
rtc_date_time_t watch_get_init_date_time(void) {
|
||||
rtc_date_time_t date_time = {0};
|
||||
|
||||
int32_t time_zone_offset = EM_ASM_INT({
|
||||
return -new Date().getTimezoneOffset() * 60;
|
||||
return new Date().getTimezoneOffset() * 60 * 1000; // ms
|
||||
});
|
||||
#endif
|
||||
#ifdef BUILD_YEAR
|
||||
watch_date_time_t date_time = watch_get_init_date_time();
|
||||
#else
|
||||
watch_date_time_t date_time = watch_rtc_get_date_time();
|
||||
#endif
|
||||
watch_rtc_set_date_time(watch_utility_date_time_convert_zone(date_time, time_zone_offset, 0));
|
||||
}
|
||||
|
||||
void watch_rtc_set_date_time(watch_date_time_t date_time) {
|
||||
time_offset = EM_ASM_DOUBLE({
|
||||
const year = 2020 + (($0 >> 26) & 0x3f);
|
||||
const month = ($0 >> 22) & 0xf;
|
||||
const day = ($0 >> 17) & 0x1f;
|
||||
const hour = ($0 >> 12) & 0x1f;
|
||||
const minute = ($0 >> 6) & 0x3f;
|
||||
const second = $0 & 0x3f;
|
||||
const date = new Date(year, month - 1, day, hour, minute, second);
|
||||
return date - Date.now();
|
||||
}, date_time.reg);
|
||||
}
|
||||
|
||||
watch_date_time_t watch_rtc_get_date_time(void) {
|
||||
watch_date_time_t retval;
|
||||
retval.reg = EM_ASM_INT({
|
||||
date_time.reg = EM_ASM_INT({
|
||||
const date = new Date(Date.now() + $0);
|
||||
return date.getSeconds() |
|
||||
(date.getMinutes() << 6) |
|
||||
@@ -82,27 +135,16 @@ watch_date_time_t watch_rtc_get_date_time(void) {
|
||||
(date.getDate() << 17) |
|
||||
((date.getMonth() + 1) << 22) |
|
||||
((date.getFullYear() - 2020) << 26);
|
||||
}, time_offset);
|
||||
return retval;
|
||||
}
|
||||
|
||||
rtc_date_time_t watch_get_init_date_time(void) {
|
||||
rtc_date_time_t date_time = {0};
|
||||
}, time_zone_offset);
|
||||
|
||||
#ifdef BUILD_YEAR
|
||||
date_time.unit.year = BUILD_YEAR;
|
||||
#else
|
||||
date_time.unit.year = 5;
|
||||
#endif
|
||||
#ifdef BUILD_MONTH
|
||||
date_time.unit.month = BUILD_MONTH;
|
||||
#else
|
||||
date_time.unit.month = 1;
|
||||
#endif
|
||||
#ifdef BUILD_DAY
|
||||
date_time.unit.day = BUILD_DAY;
|
||||
#else
|
||||
date_time.unit.day = 1;
|
||||
#endif
|
||||
#ifdef BUILD_HOUR
|
||||
date_time.unit.hour = BUILD_HOUR;
|
||||
@@ -122,12 +164,72 @@ void watch_rtc_disable_tick_callback(void) {
|
||||
watch_rtc_disable_periodic_callback(1);
|
||||
}
|
||||
|
||||
static void watch_invoke_periodic_callback(void *userData) {
|
||||
watch_cb_t callback = userData;
|
||||
callback();
|
||||
static void _watch_increase_counter(void *userData) {
|
||||
(void) userData;
|
||||
|
||||
counter += 1;
|
||||
// Fire the periodic callbacks that match this counter
|
||||
_watch_process_periodic_callbacks();
|
||||
// Fire the comp callbacks that match this counter
|
||||
_watch_process_comp_callbacks();
|
||||
|
||||
resume_main_loop();
|
||||
}
|
||||
|
||||
static void _watch_process_periodic_callbacks(void) {
|
||||
/* It looks weird but it follows the way the hardware triggers periodic interrupts.
|
||||
* For 128hz counter periodic interrupts fire at these tick values:
|
||||
* 1Hz: 64
|
||||
* 2Hz: 32, 96
|
||||
* 4Hz: 16, 48, 80, 112
|
||||
* 8Hz: 8, 24, 40, 56, 72, 88, 104, 120
|
||||
* 16Hz: 4, 12, 20, ..., 124
|
||||
* 32Hz: 2, 6, 10, ..., 126
|
||||
* 64Hz: 1, 3, 5, ..., 127
|
||||
* 128Hz: 0, 1, 2, ..., 127
|
||||
*
|
||||
* Which means that only one periodic interrupt can fire for a given counter value
|
||||
* (except 128Hz which can always fire)
|
||||
*/
|
||||
|
||||
uint32_t freq = watch_rtc_get_frequency();
|
||||
uint32_t subsecond_mask = freq - 1;
|
||||
uint32_t subseconds = counter & subsecond_mask;
|
||||
|
||||
// Find the firs non-zero bit in the counter, which can be used to determine the appropriate period (see table above).
|
||||
uint8_t per_n = 0;
|
||||
|
||||
for (uint8_t i = 0; i < 7; i++) {
|
||||
if (subseconds & (1 << i)) {
|
||||
per_n = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tick_callbacks[per_n]) {
|
||||
tick_callbacks[per_n]();
|
||||
}
|
||||
|
||||
// 128Hz is always a match
|
||||
if (per_n != 0 && tick_callbacks[0]) {
|
||||
tick_callbacks[0]();
|
||||
}
|
||||
}
|
||||
|
||||
static void _watch_process_comp_callbacks(void) {
|
||||
// In hardware the interrupt fires one tick after the matching counter
|
||||
if (counter == (scheduled_comp_counter + 1)) {
|
||||
for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) {
|
||||
if (comp_callbacks[index].enabled && scheduled_comp_counter == comp_callbacks[index].counter) {
|
||||
comp_callbacks[index].enabled = false;
|
||||
comp_callbacks[index].callback();
|
||||
}
|
||||
}
|
||||
|
||||
_watch_rtc_schedule_next_comp();
|
||||
}
|
||||
}
|
||||
|
||||
void watch_rtc_register_periodic_callback(watch_cb_t callback, uint8_t frequency) {
|
||||
// we told them, it has to be a power of 2.
|
||||
if (__builtin_popcount(frequency) != 1) return;
|
||||
@@ -138,26 +240,19 @@ void watch_rtc_register_periodic_callback(watch_cb_t callback, uint8_t frequency
|
||||
// 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);
|
||||
|
||||
double interval = 1000.0 / frequency; // in msec
|
||||
|
||||
if (tick_callbacks[per_n] != -1) emscripten_clear_interval(tick_callbacks[per_n]);
|
||||
tick_callbacks[per_n] = emscripten_set_interval(watch_invoke_periodic_callback, interval, (void *)callback);
|
||||
tick_callbacks[per_n] = callback;
|
||||
}
|
||||
|
||||
void watch_rtc_disable_periodic_callback(uint8_t frequency) {
|
||||
if (__builtin_popcount(frequency) != 1) return;
|
||||
uint8_t per_n = __builtin_clz((frequency & 0xFF) << 24);
|
||||
if (tick_callbacks[per_n] != -1) {
|
||||
emscripten_clear_interval(tick_callbacks[per_n]);
|
||||
tick_callbacks[per_n] = -1;
|
||||
}
|
||||
tick_callbacks[per_n] = NULL;
|
||||
}
|
||||
|
||||
void watch_rtc_disable_matching_periodic_callbacks(uint8_t mask) {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
if (tick_callbacks[i] != -1 && (mask & (1 << i)) != 0) {
|
||||
emscripten_clear_interval(tick_callbacks[i]);
|
||||
tick_callbacks[i] = -1;
|
||||
if (tick_callbacks[i] && (mask & (1 << i)) != 0) {
|
||||
tick_callbacks[i] = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,81 +261,70 @@ void watch_rtc_disable_all_periodic_callbacks(void) {
|
||||
watch_rtc_disable_matching_periodic_callbacks(0xFF);
|
||||
}
|
||||
|
||||
static void watch_invoke_alarm_interval_callback(void *userData) {
|
||||
if (alarm_callback) alarm_callback();
|
||||
}
|
||||
|
||||
static void watch_invoke_alarm_callback(void *userData) {
|
||||
if (alarm_callback) alarm_callback();
|
||||
alarm_interval_id = emscripten_set_interval(watch_invoke_alarm_interval_callback, alarm_interval, NULL);
|
||||
}
|
||||
|
||||
void watch_rtc_register_alarm_callback(watch_cb_t callback, watch_date_time_t alarm_time, rtc_alarm_match_t mask) {
|
||||
watch_rtc_disable_alarm_callback();
|
||||
|
||||
switch (mask) {
|
||||
case ALARM_MATCH_DISABLED:
|
||||
return;
|
||||
case ALARM_MATCH_SS:
|
||||
alarm_interval = 60 * 1000;
|
||||
break;
|
||||
case ALARM_MATCH_MMSS:
|
||||
alarm_interval = 60 * 60 * 1000;
|
||||
break;
|
||||
case ALARM_MATCH_HHMMSS:
|
||||
alarm_interval = 60 * 60 * 60 * 1000;
|
||||
break;
|
||||
void watch_rtc_register_comp_callback(watch_cb_t callback, rtc_counter_t counter, uint8_t index) {
|
||||
if (index >= WATCH_RTC_N_COMP_CB) {
|
||||
return;
|
||||
}
|
||||
|
||||
double timeout = EM_ASM_DOUBLE({
|
||||
const now = Date.now();
|
||||
const date = new Date(now);
|
||||
comp_callbacks[index].counter = counter;
|
||||
comp_callbacks[index].callback = callback;
|
||||
comp_callbacks[index].enabled = true;
|
||||
|
||||
const hour = ($0 >> 12) & 0x1f;
|
||||
const minute = ($0 >> 6) & 0x3f;
|
||||
const second = $0 & 0x3f;
|
||||
_watch_rtc_schedule_next_comp();
|
||||
}
|
||||
|
||||
if ($1 == 1) { // SS
|
||||
if (second < date.getSeconds()) date.setMinutes(date.getMinutes() + 1);
|
||||
date.setSeconds(second);
|
||||
} else if ($1 == 2) { // MMSS
|
||||
if (second < date.getSeconds()) date.setMinutes(date.getMinutes() + 1);
|
||||
if (minute < date.getMinutes()) date.setHours(date.getHours() + 1);
|
||||
date.setMinutes(minute, second);
|
||||
} else if ($1 == 3) { // HHMMSS
|
||||
if (second < date.getSeconds()) date.setMinutes(date.getMinutes() + 1);
|
||||
if (minute < date.getMinutes()) date.setHours(date.getHours() + 1);
|
||||
if (hour < date.getHours()) date.setDate(date.getDate() + 1);
|
||||
date.setHours(hour, minute, second);
|
||||
} else {
|
||||
throw 'Invalid alarm match mask';
|
||||
void watch_rtc_disable_comp_callback(uint8_t index) {
|
||||
if (index >= WATCH_RTC_N_COMP_CB) {
|
||||
return;
|
||||
}
|
||||
|
||||
comp_callbacks[index].enabled = false;
|
||||
|
||||
_watch_rtc_schedule_next_comp();
|
||||
}
|
||||
|
||||
void _watch_rtc_schedule_next_comp(void) {
|
||||
scheduled_comp_counter = 0;
|
||||
|
||||
// The soonest we can schedule is the next tick
|
||||
rtc_counter_t curr_counter = watch_rtc_get_counter() + 1;
|
||||
|
||||
bool schedule_any = false;
|
||||
rtc_counter_t comp_counter;
|
||||
rtc_counter_t min_diff = UINT_MAX;
|
||||
|
||||
for (uint8_t index = 0; index < WATCH_RTC_N_COMP_CB; ++index) {
|
||||
// rtc_counter_t diff =
|
||||
if (comp_callbacks[index].enabled) {
|
||||
rtc_counter_t diff = comp_callbacks[index].counter - curr_counter;
|
||||
if (diff <= min_diff) {
|
||||
min_diff = diff;
|
||||
comp_counter = comp_callbacks[index].counter;
|
||||
schedule_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
return date - now;
|
||||
}, alarm_time.reg, mask);
|
||||
|
||||
alarm_callback = callback;
|
||||
alarm_timeout_id = emscripten_set_timeout(watch_invoke_alarm_callback, timeout, NULL);
|
||||
}
|
||||
|
||||
void watch_rtc_disable_alarm_callback(void) {
|
||||
alarm_callback = NULL;
|
||||
alarm_interval = 0;
|
||||
|
||||
if (alarm_timeout_id != -1) {
|
||||
emscripten_clear_timeout(alarm_timeout_id);
|
||||
alarm_timeout_id = -1;
|
||||
}
|
||||
|
||||
if (alarm_interval_id != -1) {
|
||||
emscripten_clear_interval(alarm_interval_id);
|
||||
alarm_interval_id = -1;
|
||||
if (schedule_any) {
|
||||
scheduled_comp_counter = comp_counter;
|
||||
}
|
||||
}
|
||||
|
||||
void watch_rtc_enable(bool en)
|
||||
{
|
||||
//Not simulated
|
||||
// Nothing to do cases
|
||||
if ((en && counter_interval) || (!en && !counter_interval)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (en) {
|
||||
// Very bad way to keep time, but okay way to emulates the hardware.
|
||||
double ms = 1000.0 / (double)RTC_CNT_HZ; // in msec
|
||||
counter_interval = emscripten_set_interval(_watch_increase_counter, ms, NULL);
|
||||
} else {
|
||||
emscripten_clear_interval(counter_interval);
|
||||
counter_interval = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void watch_rtc_freqcorr_write(int16_t value, int16_t sign)
|
||||
|
||||
@@ -28,16 +28,20 @@
|
||||
#include <emscripten.h>
|
||||
#include <emscripten/html5.h>
|
||||
|
||||
static bool buzzer_enabled = false;
|
||||
static volatile bool buzzer_enabled = false;
|
||||
static uint32_t buzzer_period;
|
||||
|
||||
void cb_watch_buzzer_seq(void *userData);
|
||||
|
||||
static uint16_t _seq_position;
|
||||
static int8_t _tone_ticks, _repeat_counter;
|
||||
static long _em_interval_id = 0;
|
||||
static volatile long _em_interval_id = 0;
|
||||
static int8_t *_sequence;
|
||||
static uint8_t _volume;
|
||||
static void (*_cb_finished)(void);
|
||||
static watch_cb_t _cb_start_global = NULL;
|
||||
static watch_cb_t _cb_stop_global = NULL;
|
||||
static volatile bool _buzzer_is_active = false;
|
||||
|
||||
void _watch_enable_tcc(void) {}
|
||||
|
||||
@@ -47,15 +51,27 @@ static inline void _em_interval_stop() {
|
||||
}
|
||||
|
||||
void watch_buzzer_play_sequence(int8_t *note_sequence, void (*callback_on_end)(void)) {
|
||||
if (_em_interval_id) _em_interval_stop();
|
||||
watch_set_buzzer_off();
|
||||
watch_buzzer_play_sequence_with_volume(note_sequence, callback_on_end, WATCH_BUZZER_VOLUME_LOUD);
|
||||
}
|
||||
|
||||
void watch_buzzer_play_sequence_with_volume(int8_t *note_sequence, void (*callback_on_end)(void), watch_buzzer_volume_t volume) {
|
||||
watch_buzzer_abort_sequence();
|
||||
|
||||
_buzzer_is_active = true;
|
||||
|
||||
if (_cb_start_global) {
|
||||
_cb_start_global();
|
||||
}
|
||||
|
||||
_sequence = note_sequence;
|
||||
_cb_finished = callback_on_end;
|
||||
_volume = volume == WATCH_BUZZER_VOLUME_SOFT ? 5 : 25;
|
||||
_seq_position = 0;
|
||||
_tone_ticks = 0;
|
||||
_repeat_counter = -1;
|
||||
// prepare buzzer
|
||||
watch_enable_buzzer();
|
||||
watch_set_buzzer_off();
|
||||
// initiate 64 hz callback
|
||||
_em_interval_id = emscripten_set_interval(cb_watch_buzzer_seq, (double)(1000/64), (void *)NULL);
|
||||
}
|
||||
@@ -88,7 +104,7 @@ void cb_watch_buzzer_seq(void *userData) {
|
||||
if (note == BUZZER_NOTE_REST) {
|
||||
watch_set_buzzer_off();
|
||||
} else {
|
||||
watch_set_buzzer_period_and_duty_cycle(NotePeriods[note], 25);
|
||||
watch_set_buzzer_period_and_duty_cycle(NotePeriods[note], _volume);
|
||||
watch_set_buzzer_on();
|
||||
}
|
||||
// set duration ticks and move to next tone
|
||||
@@ -97,7 +113,6 @@ void cb_watch_buzzer_seq(void *userData) {
|
||||
} else {
|
||||
// end the sequence
|
||||
watch_buzzer_abort_sequence();
|
||||
if (_cb_finished) _cb_finished();
|
||||
}
|
||||
} else _tone_ticks--;
|
||||
}
|
||||
@@ -105,10 +120,32 @@ void cb_watch_buzzer_seq(void *userData) {
|
||||
void watch_buzzer_abort_sequence(void) {
|
||||
// ends/aborts the sequence
|
||||
if (_em_interval_id) _em_interval_stop();
|
||||
|
||||
watch_set_buzzer_off();
|
||||
watch_disable_buzzer();
|
||||
|
||||
if (!_buzzer_is_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
_buzzer_is_active = false;
|
||||
|
||||
if (_cb_stop_global) {
|
||||
_cb_stop_global();
|
||||
}
|
||||
|
||||
if (_cb_finished) {
|
||||
_cb_finished();
|
||||
}
|
||||
}
|
||||
|
||||
void watch_buzzer_register_global_callbacks(watch_cb_t cb_start, watch_cb_t cb_stop) {
|
||||
_cb_stop_global = cb_start;
|
||||
_cb_stop_global = cb_stop;
|
||||
}
|
||||
|
||||
void watch_enable_buzzer(void) {
|
||||
watch_buzzer_abort_sequence();
|
||||
buzzer_enabled = true;
|
||||
buzzer_period = NotePeriods[BUZZER_NOTE_A4];
|
||||
|
||||
@@ -175,15 +212,17 @@ void watch_buzzer_play_note(watch_buzzer_note_t note, uint16_t duration_ms) {
|
||||
}
|
||||
|
||||
void watch_buzzer_play_note_with_volume(watch_buzzer_note_t note, uint16_t duration_ms, watch_buzzer_volume_t volume) {
|
||||
if (note == BUZZER_NOTE_REST) {
|
||||
watch_set_buzzer_off();
|
||||
} else {
|
||||
watch_set_buzzer_period_and_duty_cycle(NotePeriods[note], volume == WATCH_BUZZER_VOLUME_SOFT ? 5 : 25);
|
||||
watch_set_buzzer_on();
|
||||
}
|
||||
static int8_t single_note_sequence[3];
|
||||
|
||||
main_loop_sleep(duration_ms);
|
||||
watch_set_buzzer_off();
|
||||
single_note_sequence[0] = note;
|
||||
// 64 ticks per second for the tc0?
|
||||
// Each tick is approximately 15ms
|
||||
uint16_t duration = duration_ms / 15;
|
||||
if (duration > 127) duration = 127;
|
||||
single_note_sequence[1] = (int8_t)duration;
|
||||
single_note_sequence[2] = 0;
|
||||
|
||||
watch_buzzer_play_sequence_with_volume(single_note_sequence, NULL, volume);
|
||||
}
|
||||
|
||||
void watch_enable_leds(void) {}
|
||||
|
||||
Reference in New Issue
Block a user