Initial seemingly stable version of movement using the RTC COUNTER32 mode

This commit is contained in:
Alessandro Genova
2025-07-26 21:40:22 -04:00
parent a1a255cd2a
commit eb9ec8659c
13 changed files with 1335 additions and 514 deletions
+138
View File
@@ -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);
}
+98
View File
@@ -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);
/** @} */
+148 -39
View File
@@ -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
}
+96 -23
View File
@@ -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) {