Initial seemingly stable version of movement using the RTC COUNTER32 mode
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user