stm32f1xx_HBL/drv/bkp.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);
}