/**
******************************************************************************
* @file STM32LowPower.cpp
* @author Frederic Pillon
* @brief Provides a STM32 Low Power interface with Arduino
*
******************************************************************************
* @attention
*
* <h2><center>© COPYRIGHT(c) 2020 STMicroelectronics</center></h2>
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of STMicroelectronics nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
#include "STM32LowPower.h"
STM32LowPower LowPower;
STM32LowPower::STM32LowPower()
{
_configured = false;
_serial = NULL;
_rtc_wakeup = false;
}
/**
* @brief Initializes the low power mode
* @param None
* @retval None
*/
void STM32LowPower::begin(void)
{
LowPower_init();
_configured = true;
}
/**
* @brief Enable the idle low power mode (STM32 sleep). Exit this mode on
* interrupt or in n milliseconds.
* @param ms: optional delay before leave the idle mode (default: 0).
* @retval None
*/
void STM32LowPower::idle(uint32_t ms)
{
if ((ms != 0) || _rtc_wakeup) {
programRtcWakeUp(ms, IDLE_MODE);
}
LowPower_sleep(PWR_MAINREGULATOR_ON);
}
/**
* @brief Enable the sleep low power mode (STM32 sleep). Exit this mode on
* interrupt or in n milliseconds.
* @param ms: optional delay before leave the sleep mode (default: 0).
* @retval None
*/
void STM32LowPower::sleep(uint32_t ms)
{
if ((ms != 0) || _rtc_wakeup) {
programRtcWakeUp(ms, SLEEP_MODE);
}
#if defined(PWR_LOWPOWERREGULATOR_ON)
LowPower_sleep(PWR_LOWPOWERREGULATOR_ON);
#else
LowPower_sleep(PWR_MAINREGULATOR_ON);
#endif
}
/**
* @brief Enable the deepsleep low power mode (STM32 stop). Exit this mode on
* interrupt or in n milliseconds.
* @param ms: optional delay before leave the deepSleep mode (default: 0).
* @retval None
*/
void STM32LowPower::deepSleep(uint32_t ms)
{
if ((ms != 0) || _rtc_wakeup) {
programRtcWakeUp(ms, DEEP_SLEEP_MODE);
}
LowPower_stop(_serial);
}
/**
* @brief Enable the shutdown low power mode (STM32 shutdown or standby mode).
* Exit this mode on interrupt or in n milliseconds.
* @param ms: optional delay before leave the shutdown mode (default: 0).
* @retval None
*/
void STM32LowPower::shutdown(uint32_t ms)
{
if ((ms != 0) || _rtc_wakeup) {
programRtcWakeUp(ms, SHUTDOWN_MODE);
}
/* Get the rtc object to know if it is configured */
STM32RTC &rtc = STM32RTC::getInstance();
LowPower_shutdown(rtc.isConfigured());
}
/**
* @brief Enable GPIO pin in interrupt mode. If the pin is a wakeup pin, it is
* configured as wakeup source.
* @param pin: pin number
* @param callback: pointer to callback function.
* @param mode: pin interrupt mode (HIGH, LOW, RISING, FALLING or CHANGE)
* @param LowPowerMode: Low power mode which will be used
* (IDLE_MODE, SLEEP_MODE, DEEP_SLEEP_MODE, SHUTDOWN_MODE)
* In case of SHUTDOWN_MODE only, Wakeup pin capability is activated
* @retval None
*/
void STM32LowPower::attachInterruptWakeup(uint32_t pin, voidFuncPtrVoid callback, uint32_t mode, LP_Mode LowPowerMode)
{
attachInterrupt(pin, callback, mode);
if (LowPowerMode == SHUTDOWN_MODE) {
// If Gpio is a Wake up pin activate it for shutdown (standby or shutdown stm32)
LowPower_EnableWakeUpPin(pin, mode);
}
}
/**
* @brief Enable a serial interface as a wakeup source.
* @param serial: pointer to a HardwareSerial
* @param callback: pointer to callback function called when leave the low power
* mode.
* @retval None
*/
void STM32LowPower::enableWakeupFrom(HardwareSerial *serial, voidFuncPtrVoid callback)
{
if (serial != NULL) {
_serial = &(serial->_serial);
// Reconfigure serial for low power mode (using HSI as clock source)
serial->configForLowPower();
LowPower_EnableWakeUpUart(_serial, callback);
}
}
/**
* @brief Attach a callback to a RTC alarm.
* @param rtc: pointer to a STM32RTC. Can be NULL as RTC is a Singleton.
* @param callback: pointer to callback function called when leave the low power
* mode.
* @param data: optional pointer to callback data parameters (default NULL).
* @retval None
*/
void STM32LowPower::enableWakeupFrom(STM32RTC *rtc, voidFuncPtr callback, void *data)
{
if (rtc == NULL) {
rtc = &(STM32RTC::getInstance());
}
_rtc_wakeup = true;
rtc->attachInterrupt(callback, data);
}
/**
* @brief Configure the RTC alarm
* @param ms: time of the alarm in milliseconds.
* @param lp_mode: low power mode targeted.
* @retval None
*/
void STM32LowPower::programRtcWakeUp(uint32_t ms, LP_Mode lp_mode)
{
STM32RTC &rtc = STM32RTC::getInstance();
STM32RTC::Source_Clock clkSrc = rtc.getClockSource();
switch (lp_mode) {
case IDLE_MODE:
case SLEEP_MODE:
break;
// LSI or LSE must be selected as clock source to wakeup the device.
case DEEP_SLEEP_MODE:
clkSrc = (clkSrc == STM32RTC::HSE_CLOCK) ? STM32RTC::LSI_CLOCK : clkSrc;
break;
default:
case SHUTDOWN_MODE:
#if defined(PWR_CR1_LPMS)
// For shutdown mode LSE have to be used
clkSrc = STM32RTC::LSE_CLOCK;
#else
// LSE or LSI
clkSrc = (clkSrc == STM32RTC::HSE_CLOCK) ? STM32RTC::LSI_CLOCK : clkSrc;
#endif
break;
}
rtc.configForLowPower(clkSrc);
setAlarmTime(ms, rtc);
}
static bool isLeapYear(uint8_t year2k)
{
int year = year2k + 2000;
// if year not divisible by 4 - not a leap year
// else if year divisible by 4 and not by 100 - a leap year
// else if year divisible by 400 - a leap year
return (year % 4 != 0) ? false : (year % 100 != 0) ? true : year % 400 == 0;
}
void STM32LowPower::setAlarmTime(uint32_t ms, STM32RTC &rtc)
{
if (ms != 0) {
uint16_t subSecondsToAdd = ms % 1000;
ms = ms / 1000;
uint8_t daysToAdd = ms / 86400;
uint8_t hoursToAdd = (ms - daysToAdd * 86400) / 3600;
uint8_t minutesToAdd = (ms - daysToAdd * 86400 - hoursToAdd * 3600) / 60;
uint8_t secondsToAdd = (ms - daysToAdd * 86400 - hoursToAdd * 3600 - minutesToAdd * 60);
uint8_t hrCurrent, minCurrent, secCurrent;
uint32_t subSecondsCurrent;
STM32RTC::AM_PM period;
rtc.getTime(&hrCurrent, &minCurrent, &secCurrent, &subSecondsCurrent, &period);
uint8_t weekDay, currentDay, currentMonth, currentYear;
rtc.getDate(&weekDay, ¤tDay, ¤tMonth, ¤tYear);
uint32_t ss = subSecondsCurrent + subSecondsToAdd;
if (ss >= 1000) {
ss -= 1000;
secondsToAdd++;
}
if (secondsToAdd >= 60) {
secondsToAdd = 0;
minutesToAdd++;
}
uint8_t s = secCurrent + secondsToAdd;
if (s >= 60) {
s -= 60;
minutesToAdd++;
}
if (minutesToAdd >= 60) {
minutesToAdd -= 60;
hoursToAdd++;
}
uint8_t m = minCurrent + minutesToAdd;
if (m >= 60) {
m -= 60;
hoursToAdd++;
}
if (hoursToAdd >= 24) {
hoursToAdd -= 24;
daysToAdd++;
}
uint8_t h = hrCurrent + hoursToAdd;
if (rtc._format == STM32RTC::Hour_Format::HOUR_12) {
if (h >= 24) {
h -= 24;
daysToAdd++;
} else if (h >= 12) {
if (period == STM32RTC::AM_PM::AM) {
period = STM32RTC::AM_PM::PM;
} else {
period = STM32RTC::AM_PM::AM;
daysToAdd++;
}
if (h > 12) {
h -= 12;
}
}
} else if (h >= 24) {
h -= 24;
daysToAdd++;
}
// numbers of days in each month (february is calculated based on leap year)
static uint8_t daysInMonths[] = {31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
uint8_t endDay;
if (currentMonth == 2) {
endDay = isLeapYear(currentYear) ? 29 : 28;
} else {
endDay = daysInMonths[currentMonth - 1];
}
uint8_t d = currentDay + daysToAdd;
if (d > endDay) {
d -= endDay;
}
// month-year overflow isn't handled because its not supported by RTC's alarm
rtc.setAlarmTime(h, m, s, ss, period);
rtc.setAlarmDay(d);
rtc.enableAlarm(STM32RTC::Alarm_Match::MATCH_DHHMMSS);
}
}