/** @file bkp.c * Module handling the Backup (BKP) domain functionalities. * * This module includes management of the Real Time Clock (RTC), Tamper * protection system and a limited space (20 bytes) for persistent data storage * * All features present in this module stay running in standby mode / when * no-longer powered, so long as VBAT is maintained */ //--includes-------------------------------------------------------------------- #include "bkp.h" #include "bkp_regs.h" #include "rcc.h" #include "nvic.h" //--local definitions----------------------------------------------------------- static uint32_t compute_prescaler(uint32_t period_ms, enum BkpRtcClockSrc clock_src); static void lsi_calib_callback(enum TimIRQSource src); //--local variables------------------------------------------------------------- static volatile struct BKP* bkp_regs = (struct BKP*)BKP_BASE_ADDRESS; static volatile struct RCC* rcc_regs = (struct RCC*)RCC_BASE_ADDRESS; static volatile struct RTC* rtc_regs = (struct RTC*)RTC_BASE_ADDRESS; static BkpRtcCallback rtc_callback; //--public functions------------------------------------------------------------ void bkp_configure_rtc(uint32_t period_ms, enum BkpRtcClockSrc clock_src, enum BkpRtcIrq irq_mask, BkpRtcCallback callback) { rcc_enable(RCC_AHB_NONE, RCC_APB1_BKP, RCC_APB2_NONE); //start RTC rcc_regs->BDCR.RTCSEL = clock_src + 1; rcc_regs->BDCR.RTCEN = 1; uint32_t prescaler = compute_prescaler(period_ms, clock_src); //wait for registers to synchronize rtc_regs->CRL.RSF = 0; while (rtc_regs->CRL.RSF != 1) {} //wait for last operation to finish while (rtc_regs->CRL.RTOFF != 1) {} //enable core configuration rtc_regs->CRL.CNF = 1; //configure core registers rtc_regs->PRLH.PRL = prescaler >> 16; rtc_regs->PRLL.PRL = prescaler; //apply irq config rtc_regs->CRH.word |= irq_mask & 0x7; //disable/apply core configuration rtc_regs->CRL.CNF = 0; //wait for last operation to finish while (rtc_regs->CRL.RTOFF != 1) {} if (callback) { rtc_callback = callback; nvic_enable(NVIC_IRQ_RTC); } } void bkp_set_rtc_alam(uint32_t offset) { uint32_t alarm = bkp_read_rtc() + offset; //wait for last operation to finish while (rtc_regs->CRL.RTOFF != 1) {} //enable core configuration rtc_regs->CRL.CNF = 1; //write alarm value rtc_regs->ALRH.RTC_ALR = alarm >> 16; rtc_regs->ALRL.RTC_ALR = alarm; //disable/apply core configuration rtc_regs->CRL.CNF = 0; //wait for last operation to finish while (rtc_regs->CRL.RTOFF != 1) {} } uint32_t bkp_read_rtc(void) { //wait for core registers to be synchronized, immediate most of the time while (rtc_regs->CRL.RSF != 1) {} uint32_t time = rtc_regs->CNTH.RTC_CNT << 16; time |= rtc_regs->CNTL.RTC_CNT << 0; return time; } uint32_t bkp_read_rtc_div(void) { //wait for core registers to be synchronized, immediate most of the time while (rtc_regs->CRL.RSF != 1) {} uint32_t div = rtc_regs->DIVH.RTC_DIV << 16; div |= rtc_regs->DIVL.RTC_DIV << 0; return div; } void bkp_reset(void) { rcc_regs->BDCR.BDRST = 1; rcc_regs->BDCR.BDRST = 0; } void bkp_write_data(enum BkpData data_index, uint16_t data) { bkp_regs->DR[data_index].D = data; } uint16_t bkp_read_data(enum BkpData data_index) { return bkp_regs->DR[data_index].D; } void bkp_calibrate_lsi(enum TimPeriph timer) { rcc_enable(RCC_AHB_NONE, RCC_APB1_BKP, RCC_APB2_NONE); //do not calibrate if already calibrated if (bkp_read_data(BKP_DATA_LSI_CALIB) != 0) { return; } //configure timer to count 1s exactly struct RccClocks clocks; rcc_get_clocks(&clocks); tim_set_prescaler(timer, (clocks.tim_freq / 10000)); //10000 to avoid //prescaler overflow tim_set_auto_reload(timer, 10000); tim_configure_master(timer, TIM_CONFIG_ONE_SHOT | TIM_CONFIG_DIR_UP, TIM_MASTER_CONFIG_MODE_RESET, lsi_calib_callback); //configure rtc to tick every 2s so the div value isn't reset during the //timer's delay if the clock is too fast bkp_configure_rtc(2000, BKP_RTC_CLOCK_SRC_LSI, BKP_RTC_IRQ_NONE, nullptr); tim_start(timer); while (bkp_read_data(BKP_DATA_LSI_CALIB) == 0) {} //TODO reset timer } //--local functions------------------------------------------------------------- /** * Computes the prescaler value based on the clock source and the required * period */ static uint32_t compute_prescaler(uint32_t period_ms, enum BkpRtcClockSrc clock_src) { uint32_t prescaler; switch (clock_src) { case BKP_RTC_CLOCK_SRC_LSE: prescaler = 32768; //32.768kHz break; case BKP_RTC_CLOCK_SRC_LSI: prescaler = bkp_read_data(BKP_DATA_LSI_CALIB); if (prescaler == 0) { prescaler = 40000; //40khz } break; case BKP_RTC_CLOCK_SRC_HSE: prescaler = 62500; //8Mhz / 128 break; default: return 0; } return (period_ms * prescaler) / 1000; } static void lsi_calib_callback(enum TimIRQSource src) { if (src == TIM_IRQ_SOURCE_UPDATE) { //div is decremented from 40kHz * programmed delay (in s) uint32_t lsi_freq = (40000 * 2) - bkp_read_rtc_div(); bkp_write_data(BKP_DATA_LSI_CALIB, lsi_freq); } } //--ISRs------------------------------------------------------------------------ void hdr_rtc(void) { nvic_clear_pending(NVIC_IRQ_RTC); //copy and clear and pass along src flags enum BkpRtcIrq src = rtc_regs->CRL.word & 0x7; rtc_regs->CRL.word &= ~(0x7); rtc_callback(src); }