321 строка
8.4 KiB
C
321 строка
8.4 KiB
C
#include "jdstm.h"
|
|
|
|
#define US_TICK_SCALE 16
|
|
#define TICK_US_SCALE 10
|
|
#define US_TO_TICKS(us) (((uint32_t)(us)*ctx->ticksPerUs) >> US_TICK_SCALE)
|
|
#define TICKS_TO_US(t) (((uint32_t)(t)*ctx->usPerTick) >> TICK_US_SCALE)
|
|
|
|
STATIC_ASSERT(10000 <= RTC_SECOND_IN_US);
|
|
STATIC_ASSERT(RTC_SECOND_IN_US <= 200000);
|
|
|
|
typedef struct rtc_state {
|
|
uint64_t microsecondBase;
|
|
uint32_t ticksPerUs, usPerTick;
|
|
uint16_t presc;
|
|
cb_t cb;
|
|
uint8_t lastSecond, needsSync;
|
|
} ctx_t;
|
|
|
|
static ctx_t ctx_;
|
|
|
|
#if 0
|
|
#define PIN_PWR_STATE PIN_P0
|
|
#define PIN_PWR_LOG PIN_P1
|
|
#else
|
|
#define PIN_PWR_STATE -1
|
|
#define PIN_PWR_LOG -1
|
|
#endif
|
|
|
|
#ifdef STM32G0
|
|
#define RTC_IRQn RTC_TAMP_IRQn
|
|
#define RTC_IRQHandler RTC_TAMP_IRQHandler
|
|
#define EXTI_LINE LL_EXTI_LINE_19
|
|
#else
|
|
#define EXTI_LINE LL_EXTI_LINE_17
|
|
#define LL_EXTI_ClearRisingFlag_0_31 LL_EXTI_ClearFlag_0_31
|
|
#endif
|
|
|
|
#define BCD(t, h) (((t)&0xf) + 10 * (((t) >> 4) & ((1 << h) - 1)))
|
|
uint32_t rtc_get_seconds(void) {
|
|
uint32_t t = RTC->TR;
|
|
uint32_t d = RTC->DR;
|
|
DMESG("rtc %x %x", d, t);
|
|
uint32_t d0 = (BCD(d >> 0, 2) - 1) + 30 * (BCD(d >> 8, 1) - 1) + 365 * BCD(d >> 16, 4);
|
|
uint32_t r = BCD(t >> 0, 3) + 60 * (BCD(t >> 8, 3) + 60 * (BCD(t >> 16, 2) + 24 * d0));
|
|
return r;
|
|
}
|
|
|
|
// ~20us at 8MHz
|
|
void rtc_sync_time() {
|
|
ctx_t *ctx = &ctx_;
|
|
|
|
if (!ctx->needsSync)
|
|
return;
|
|
|
|
target_disable_irq();
|
|
uint16_t subsecond;
|
|
uint8_t newSecond;
|
|
for (;;) {
|
|
subsecond = LL_RTC_TIME_GetSubSecond(RTC); // this locks TR/DR
|
|
newSecond = RTC->TR & 0x7f;
|
|
if (subsecond == LL_RTC_TIME_GetSubSecond(RTC) && (RTC->TR & 0x7f) == newSecond) {
|
|
(void)RTC->DR; // unlock DR/TR
|
|
break;
|
|
}
|
|
}
|
|
subsecond = ctx->presc - subsecond;
|
|
if (newSecond != ctx->lastSecond) {
|
|
// this branch adds ~4us
|
|
int diff = 60 + BCD(newSecond, 3) - BCD(ctx->lastSecond, 3);
|
|
if (diff >= 60)
|
|
diff -= 60;
|
|
ctx->lastSecond = newSecond;
|
|
ctx->microsecondBase += diff * RTC_SECOND_IN_US;
|
|
}
|
|
tim_set_micros(ctx->microsecondBase + TICKS_TO_US(subsecond));
|
|
ctx->needsSync = false;
|
|
target_enable_irq();
|
|
}
|
|
|
|
static void schedule_sync(ctx_t *ctx) {
|
|
ctx->needsSync = true;
|
|
uint64_t now = tim_get_micros();
|
|
uint64_t delta = now - ctx->microsecondBase;
|
|
if (delta > RTC_SECOND_IN_US * 50) {
|
|
// we've been running for too long on regular timer - we lost sync with RTC
|
|
uint16_t subsecond;
|
|
uint8_t newSecond;
|
|
for (;;) {
|
|
subsecond = LL_RTC_TIME_GetSubSecond(RTC); // this locks TR/DR
|
|
newSecond = RTC->TR & 0x7f;
|
|
if (subsecond == LL_RTC_TIME_GetSubSecond(RTC) && (RTC->TR & 0x7f) == newSecond) {
|
|
(void)RTC->DR; // unlock DR/TR
|
|
break;
|
|
}
|
|
}
|
|
subsecond = ctx->presc - subsecond;
|
|
ctx->microsecondBase = tim_get_micros() - TICKS_TO_US(subsecond);
|
|
ctx->lastSecond = newSecond;
|
|
DMESG("RTC sync %d", (int)(delta));
|
|
}
|
|
}
|
|
|
|
static void rtc_set(ctx_t *ctx, uint32_t delta_us, cb_t f) {
|
|
if (delta_us > RTC_SECOND_IN_US)
|
|
jd_panic();
|
|
if (delta_us < RTC_MIN_TIME_US)
|
|
delta_us = RTC_MIN_TIME_US;
|
|
uint32_t delta_tick = US_TO_TICKS(delta_us);
|
|
|
|
ctx->cb = f;
|
|
|
|
LL_RTC_ALMA_Disable(RTC);
|
|
while (!LL_RTC_IsActiveFlag_ALRAW(RTC))
|
|
;
|
|
|
|
uint32_t v = LL_RTC_TIME_GetSubSecond(RTC);
|
|
(void)RTC->DR; // unlock DR/TR
|
|
uint32_t nv = v + ctx->presc - delta_tick; // the timer counts back; also don't underflow
|
|
if (nv >= ctx->presc)
|
|
nv -= ctx->presc; // make sure it's in range
|
|
|
|
LL_RTC_ALMA_SetSubSecond(RTC, nv);
|
|
LL_RTC_ClearFlag_ALRA(RTC);
|
|
LL_EXTI_ClearRisingFlag_0_31(EXTI_LINE);
|
|
NVIC_ClearPendingIRQ(RTC_IRQn);
|
|
LL_RTC_ALMA_Enable(RTC);
|
|
// pin_pulse(PIN_PWR_LOG, 1);
|
|
}
|
|
|
|
void rtc_cancel_cb() {
|
|
ctx_.cb = NULL;
|
|
}
|
|
|
|
void RTC_IRQHandler(void) {
|
|
rtc_sync_time();
|
|
|
|
if (LL_RTC_IsActiveFlag_ALRA(RTC) != 0) {
|
|
LL_RTC_ClearFlag_ALRA(RTC);
|
|
|
|
target_disable_irq();
|
|
cb_t f = ctx_.cb;
|
|
ctx_.cb = NULL;
|
|
target_enable_irq();
|
|
|
|
if (f)
|
|
f();
|
|
}
|
|
|
|
// Clear the EXTI's Flag for RTC Alarm
|
|
LL_EXTI_ClearRisingFlag_0_31(EXTI_LINE);
|
|
}
|
|
|
|
static void rtc_config(uint8_t p0, uint16_t p1) {
|
|
#ifdef STM32G0
|
|
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR | LL_APB1_GRP1_PERIPH_RTC);
|
|
#else
|
|
LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
|
|
#endif
|
|
|
|
LL_PWR_EnableBkUpAccess();
|
|
|
|
LL_RCC_LSI_Enable();
|
|
|
|
while (LL_RCC_LSI_IsReady() != 1)
|
|
;
|
|
|
|
LL_RCC_ForceBackupDomainReset();
|
|
LL_RCC_ReleaseBackupDomainReset();
|
|
LL_RCC_SetRTCClockSource(LL_RCC_RTC_CLKSOURCE_LSI);
|
|
|
|
LL_RCC_EnableRTC();
|
|
|
|
LL_RTC_DisableWriteProtection(RTC);
|
|
|
|
LL_RTC_EnableInitMode(RTC);
|
|
while (LL_RTC_IsActiveFlag_INIT(RTC) != 1)
|
|
;
|
|
|
|
LL_RTC_EnableShadowRegBypass(RTC);
|
|
|
|
LL_RTC_SetAsynchPrescaler(RTC, p0 - 1);
|
|
LL_RTC_SetSynchPrescaler(RTC, p1 - 1);
|
|
|
|
RTC->TR = 0;
|
|
RTC->DR = 0x2101; // BCD! and one-based!
|
|
RTC->SSR = 0;
|
|
|
|
// set alarm
|
|
LL_RTC_ALMA_SetMask(RTC, LL_RTC_ALMA_MASK_ALL); // ignore all
|
|
|
|
LL_RTC_ALMA_SetSubSecond(RTC, 0);
|
|
LL_RTC_ALMA_SetSubSecondMask(RTC, 15); // compare entire sub-second reg
|
|
|
|
LL_RTC_ALMA_Enable(RTC); // only enabled alarm when requested
|
|
LL_RTC_ClearFlag_ALRA(RTC);
|
|
LL_RTC_EnableIT_ALRA(RTC);
|
|
|
|
LL_EXTI_EnableIT_0_31(EXTI_LINE);
|
|
LL_EXTI_EnableRisingTrig_0_31(EXTI_LINE);
|
|
|
|
NVIC_SetPriority(RTC_IRQn, 2); // match tim.c
|
|
NVIC_EnableIRQ(RTC_IRQn);
|
|
|
|
LL_RTC_DisableInitMode(RTC);
|
|
}
|
|
|
|
// with more than 4000 it overflows!
|
|
#define CALIB_CYCLES 1024
|
|
|
|
void rtc_init() {
|
|
ctx_t *ctx = &ctx_;
|
|
|
|
pin_setup_output(PIN_PWR_STATE);
|
|
pin_setup_output(PIN_PWR_LOG);
|
|
|
|
target_disable_irq();
|
|
rtc_config(1, CALIB_CYCLES);
|
|
uint64_t t0 = tim_get_micros();
|
|
while (LL_RTC_IsActiveFlag_ALRA(RTC) == 0)
|
|
;
|
|
uint32_t d = (tim_get_micros() - t0) + 20;
|
|
target_enable_irq();
|
|
|
|
ctx->ticksPerUs = (1 << US_TICK_SCALE) * CALIB_CYCLES / d;
|
|
ctx->usPerTick = d * CALIB_CYCLES / (1 << TICK_US_SCALE);
|
|
|
|
int tmp = US_TO_TICKS(RTC_SECOND_IN_US);
|
|
uint32_t h_ms = US_TO_TICKS(100000);
|
|
DMESG("rtc: 100ms=%d ticks; presc=%d", h_ms, tmp);
|
|
// we're expecting around 4000, but there's large drift possible
|
|
if (!(3000 <= h_ms && h_ms <= 5000))
|
|
jd_panic();
|
|
if (tmp > 0x7f00)
|
|
jd_panic();
|
|
ctx->presc = tmp;
|
|
rtc_config(1, ctx->presc);
|
|
LL_RTC_ALMA_Disable(RTC);
|
|
ctx->needsSync = true;
|
|
rtc_sync_time();
|
|
}
|
|
|
|
// standby stuff not currently used
|
|
void rtc_set_to_seconds_and_standby() {
|
|
ctx_t *ctx = &ctx_;
|
|
|
|
if (ctx->ticksPerUs) {
|
|
int pr = US_TO_TICKS(1000000);
|
|
int async_pr = 3;
|
|
DMESG("pr=%d * %d", pr >> async_pr, 1 << async_pr);
|
|
rtc_config(1 << async_pr, pr >> async_pr);
|
|
LL_RTC_ALMA_Disable(RTC);
|
|
LL_RTC_EnableWriteProtection(RTC);
|
|
}
|
|
|
|
// pin_setup_input(PA_0, -1);
|
|
LL_PWR_EnableWakeUpPin(LL_PWR_WAKEUP_PIN1);
|
|
LL_PWR_ClearFlag_SB();
|
|
LL_PWR_ClearFlag_WU();
|
|
LL_PWR_SetPowerMode(LL_PWR_MODE_STANDBY);
|
|
LL_LPM_EnableDeepSleep();
|
|
__enable_irq(); // just in case, otherwise WFI will do nothing
|
|
__WFI();
|
|
}
|
|
|
|
bool rtc_check_standby(void) {
|
|
if (LL_PWR_IsActiveFlag_SB()) {
|
|
LL_PWR_ClearFlag_SB();
|
|
LL_PWR_ClearFlag_WU();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void rtc_sleep(bool forceShallow) {
|
|
if (forceShallow) {
|
|
pin_pulse(PIN_PWR_LOG, 1);
|
|
LL_RTC_ALMA_Disable(RTC);
|
|
__WFI();
|
|
return;
|
|
}
|
|
|
|
target_disable_irq();
|
|
uint32_t usec;
|
|
cb_t f = tim_steal_callback(&usec);
|
|
if (!f) {
|
|
target_enable_irq();
|
|
pin_pulse(PIN_PWR_LOG, 2);
|
|
LL_RTC_ALMA_Disable(RTC);
|
|
__WFI();
|
|
return;
|
|
}
|
|
|
|
rtc_set(&ctx_, usec, f);
|
|
#ifdef STM32G0
|
|
LL_PWR_SetPowerMode(LL_PWR_MODE_STOP1);
|
|
#else
|
|
LL_PWR_SetPowerMode(LL_PWR_MODE_STOP_LPREGU);
|
|
#endif
|
|
|
|
// enable to test power in stand by; should be around 3.2uA
|
|
#if 0
|
|
LL_RTC_ALMA_Disable(RTC);
|
|
pin_pulse(PIN_P1, 5);
|
|
LL_PWR_ClearFlag_SB();
|
|
LL_PWR_ClearFlag_WU();
|
|
LL_PWR_SetPowerMode(LL_PWR_MODE_STANDBY);
|
|
#endif
|
|
|
|
rtc_deepsleep();
|
|
}
|
|
|
|
void rtc_deepsleep(void) {
|
|
LL_LPM_EnableDeepSleep();
|
|
pin_set(PIN_PWR_STATE, 0);
|
|
schedule_sync(&ctx_);
|
|
__WFI();
|
|
target_enable_irq();
|
|
rtc_sync_time(); // likely already happened in ISR, but doesn't hurt to check again
|
|
LL_LPM_EnableSleep(); // i.e., no deep
|
|
}
|