diff --git a/src/charge_control.c b/src/charge_control.c index e0c8a15..cd17fbd 100644 --- a/src/charge_control.c +++ b/src/charge_control.c @@ -313,6 +313,13 @@ void charge_control_update(uint64_t uptime_ms, struct MeasurementResult *meas) } +bool charge_control_is_idle(void) +{ + return ((charge_state == CHARGE_SLEEP) + && (discharge_state == DISCHARGE_VOLTAGE_LOW)); +} + + bool charge_control_is_charge_blocked(void) { switch(charge_state) { diff --git a/src/charge_control.h b/src/charge_control.h index 5cf8b67..e943e6c 100644 --- a/src/charge_control.h +++ b/src/charge_control.h @@ -37,4 +37,6 @@ void charge_control_update(uint64_t uptime_ms, struct MeasurementResult *meas); bool charge_control_is_charge_blocked(void); bool charge_control_is_discharge_blocked(void); +bool charge_control_is_idle(void); + #endif // CHARGE_CONTROL_H diff --git a/src/config.h b/src/config.h index b9804ce..bba8724 100644 --- a/src/config.h +++ b/src/config.h @@ -52,4 +52,13 @@ /* The minimum time the load must be off before it can be switched on again (in ms). */ #define LOAD_ON_DELAY 10000 + +/* Generic configuration */ + +/* Time (in ms) to stay active in idle state before entering deep sleep. */ +#define DEEPSLEEP_DELAY 5000 + +/* Deep sleep duration (in seconds). */ +#define DEEPSLEEP_DURATION 10 + #endif // CONFIG_H diff --git a/src/deepsleep.c b/src/deepsleep.c new file mode 100644 index 0000000..402b095 --- /dev/null +++ b/src/deepsleep.c @@ -0,0 +1,140 @@ +#include +#include +#include +#include +#include + +#include "clock.h" +#include "deepsleep.h" + +void init_rtc(void) +{ + // RTC clock setup + // see libopencm3-examples::examples/stm32/l1/stm32l-discovery/button-irq-printf-lowpower/main.c + + /* turn on power block to enable unlocking */ + rcc_periph_clock_enable(RCC_PWR); + pwr_disable_backup_domain_write_protect(); + + /* reset rtc */ + RCC_BDCR |= RCC_BDCR_BDRST; + RCC_BDCR &= ~RCC_BDCR_BDRST; + + // use LSI for RTC + rcc_osc_on(RCC_LSI); + rcc_wait_for_osc_ready(RCC_LSI); + + /* Select the LSI as rtc clock */ + RCC_BDCR |= RCC_BDCR_RTCSEL_LSI; + + /* ?! Stdperiph examples don't turn this on until _afterwards_ which + * simply doesn't work. It must be on at least to be able to + * configure it */ + RCC_BDCR |= RCC_BDCR_RTCEN; + + pwr_enable_backup_domain_write_protect(); + + nvic_enable_irq(NVIC_RTC_IRQ); + + exti_set_trigger(EXTI17, EXTI_TRIGGER_RISING); + exti_enable_request(EXTI17); +} + +static void unlock_rtc_access(void) +{ + pwr_disable_backup_domain_write_protect(); + RTC_WPR = 0xCA; + RTC_WPR = 0x53; +} + +static void lock_rtc_access(void) +{ + RTC_WPR = 0xFF; + pwr_enable_backup_domain_write_protect(); +} + +void deepsleep(uint32_t duration_secs) +{ + uint32_t tmp = 0; + + // unlock RTC registers + unlock_rtc_access(); + + // enter initialization mode + RTC_ISR |= RTC_ISR_INIT; + + // wait until initialization mode has been entered + while((RTC_ISR & RTC_ISR_INITF) != RTC_ISR_INITF) { + // do nothing + } + + RTC_TR = 0; // 00:00:00 + RTC_DR = // friday, 01.01.16 + (1 << RTC_DR_YT_SHIFT) | + (6 << RTC_DR_YU_SHIFT) | + (5 << RTC_DR_WDU_SHIFT) | + (0 << RTC_DR_MT_SHIFT) | + (1 << RTC_DR_MU_SHIFT) | + (0 << RTC_DR_DT_SHIFT) | + (1 << RTC_DR_DU_SHIFT); + + // disable Alarm A + RTC_CR &= ~RTC_CR_ALRAE; + + // wait until register is writeable + while((RTC_ISR & RTC_ISR_ALRAWF) != RTC_ISR_ALRAWF) { + // do nothing + } + + tmp |= (duration_secs % 10) << RTC_ALRMXR_SU_SHIFT; + duration_secs /= 10; + + tmp |= (duration_secs % 6) << RTC_ALRMXR_ST_SHIFT; + duration_secs /= 6; + + tmp |= (duration_secs % 10) << RTC_ALRMXR_MNU_SHIFT; + duration_secs /= 10; + + tmp |= (duration_secs % 6) << RTC_ALRMXR_MNT_SHIFT; + duration_secs /= 6; + + tmp |= (duration_secs % 10) << RTC_ALRMXR_HU_SHIFT; + duration_secs /= 10; + + tmp |= (duration_secs % 2) << RTC_ALRMXR_HT_SHIFT; + // FIXME: >1d is not supported + + tmp |= RTC_ALRMXR_MSK4; // ignore day/date + + // set alarm register + RTC_ALRMAR = tmp; + + // clear Alarm A flag + RTC_ISR &= ~RTC_ISR_ALRAF; + + // enable RTC alarm interrupt for wakeup + RTC_CR |= RTC_CR_ALRAE | RTC_CR_ALRAIE; + + // leave initialization mode + RTC_ISR &= ~RTC_ISR_INIT; + + // lock registers again (using invalid key) + lock_rtc_access(); + + // enter deep sleep mode + SCB_SCR |= SCB_SCR_SLEEPDEEP; + PWR_CR |= PWR_CR_LPDS; // voltage regulator low-power mode + pwr_set_stop_mode(); + __WFI(); + SCB_SCR &= ~SCB_SCR_SLEEPDEEP; // no deepsleep except in this function + + rcc_periph_clock_disable(RCC_PWR); // no longer needed + rcc_osc_off(RCC_LSI); + init_clock(); // ensure that all clocks are running again +} + +void rtc_isr(void) +{ + exti_reset_request(EXTI17); +} + diff --git a/src/deepsleep.h b/src/deepsleep.h new file mode 100644 index 0000000..ecaf1b5 --- /dev/null +++ b/src/deepsleep.h @@ -0,0 +1,9 @@ +#ifndef DEEPSLEEP_H +#define DEEPSLEEP_H + +#include + +void init_rtc(void); +void deepsleep(uint32_t duration_secs); + +#endif // DEEPSLEEP_H diff --git a/src/main.c b/src/main.c index 77fbf99..f427a72 100644 --- a/src/main.c +++ b/src/main.c @@ -13,7 +13,9 @@ #include "charge_control.h" #include "power_switch.h" #include "measurement.h" +#include "deepsleep.h" +#include "config.h" volatile int wait_frame = 1; @@ -160,9 +162,13 @@ int main(void) bool ledtest_done = false; bool startup_done = false; + bool charge_control_was_idle = false; + uint64_t charge_control_idle_since = 0; + struct MeasurementResult meas_data; init_clock(); + init_rtc(); rs485_init(); charge_pump_init(); @@ -208,6 +214,23 @@ int main(void) // Update the charge controller immediately after the measurement. // This ensures fast reaction time to overcurrent/overvoltage. charge_control_update(timebase_ms, &meas_data); + + // deep sleep control + if(charge_control_is_idle()) { + if(!charge_control_was_idle) { + charge_control_was_idle = true; + charge_control_idle_since = timebase_ms; + } else { + // charge control already idle + if((timebase_ms - charge_control_idle_since) > DEEPSLEEP_DELAY) { + rs485_enqueue("PWR:DEEPSLEEP"); + deepsleep(DEEPSLEEP_DURATION); + charge_control_was_idle = false; + } + } + } else { + charge_control_was_idle = false; + } } timebase_ms++;