218 lines
5.3 KiB
C
218 lines
5.3 KiB
C
/** @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);
|
|
}
|
|
|