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