final deep sleep refactor: retain RAM, call it shallow sleep mode

This commit is contained in:
Joey Castillo 2021-09-23 14:57:30 -04:00
parent fd5e8046d0
commit a23901f843
3 changed files with 80 additions and 63 deletions

View File

@ -21,7 +21,7 @@ typedef struct ApplicationState {
LightColor color; LightColor color;
bool light_on; bool light_on;
uint8_t wake_count; uint8_t wake_count;
bool enter_deep_sleep; bool enter_sleep_mode;
} ApplicationState; } ApplicationState;
ApplicationState application_state; ApplicationState application_state;
@ -57,13 +57,7 @@ void app_init() {
* @see watch_enter_deep_sleep() * @see watch_enter_deep_sleep()
*/ */
void app_wake_from_deep_sleep() { void app_wake_from_deep_sleep() {
// retrieve our application state from the backup registers // this app only supports shallow sleep mode.
application_state.mode = (ApplicationMode)watch_get_backup_data(0);
application_state.color = (LightColor)watch_get_backup_data(1);
application_state.wake_count = (uint8_t)watch_get_backup_data(2) + 1;
// wait a moment for the user's finger to be off the button
delay_ms(250);
} }
/** /**
@ -151,22 +145,21 @@ bool app_loop() {
break; break;
} }
if (application_state.enter_deep_sleep) { if (application_state.enter_sleep_mode) {
application_state.enter_deep_sleep = false;
// stash our application state in the backup registers
watch_store_backup_data((uint32_t)application_state.mode, 0);
watch_store_backup_data((uint32_t)application_state.color, 1);
watch_store_backup_data((uint32_t)application_state.wake_count, 2);
// turn off the LED
watch_set_led_off();
// wait a moment for the user's finger to be off the button // wait a moment for the user's finger to be off the button
delay_ms(250); delay_ms(250);
// nap time :) // nap time :)
watch_enter_deep_sleep(NULL); watch_enter_shallow_sleep(NULL);
// we just woke up; wait a moment again for the user's finger to be off the button...
delay_ms(250);
// and prevent ourselves from going right back to sleep.
application_state.enter_sleep_mode = false;
// finally, after sleep, return false so that our app loop runs again and updates the display.
return false;
} }
return true; return true;
@ -191,5 +184,5 @@ void cb_mode_pressed() {
} }
void cb_alarm_pressed() { void cb_alarm_pressed() {
application_state.enter_deep_sleep = true; application_state.enter_sleep_mode = true;
} }

View File

@ -89,6 +89,34 @@ void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool le
_extwake_register_callback(&CALENDAR_0.device, extwake_callback); _extwake_register_callback(&CALENDAR_0.device, extwake_callback);
} }
void watch_disable_extwake_interrupt(uint8_t pin) {
hri_rtc_tampctrl_reg_t config = hri_rtc_get_TAMPCTRL_reg(RTC, 0xFFFFFFFF);
switch (pin) {
case A4:
a4_callback = NULL;
config &= ~(3 << RTC_TAMPCTRL_IN0ACT_Pos);
break;
case A2:
a2_callback = NULL;
config &= ~(3 << RTC_TAMPCTRL_IN1ACT_Pos);
break;
case BTN_ALARM:
btn_alarm_callback = NULL;
config &= ~(3 << RTC_TAMPCTRL_IN2ACT_Pos);
break;
default:
return;
}
if (hri_rtcmode0_get_CTRLA_ENABLE_bit(RTC)) {
hri_rtcmode0_clear_CTRLA_ENABLE_bit(RTC);
hri_rtcmode0_wait_for_sync(RTC, RTC_MODE0_SYNCBUSY_ENABLE);
}
hri_rtc_write_TAMPCTRL_reg(RTC, config);
hri_rtcmode0_set_CTRLA_ENABLE_bit(RTC);
}
void watch_store_backup_data(uint32_t data, uint8_t reg) { void watch_store_backup_data(uint32_t data, uint8_t reg) {
if (reg < 8) { if (reg < 8) {
RTC->MODE0.BKUP[reg].reg = data; RTC->MODE0.BKUP[reg].reg = data;
@ -128,10 +156,7 @@ void _watch_disable_all_peripherals_except_slcd() {
MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_SERCOM3; MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_SERCOM3;
} }
void watch_enter_deep_sleep(char *message) { void watch_enter_shallow_sleep(char *message) {
// configure the ALARM interrupt (the callback doesn't matter)
watch_register_extwake_callback(BTN_ALARM, NULL, true);
if (message != NULL) { if (message != NULL) {
watch_display_string(" ", 0); watch_display_string(" ", 0);
watch_display_string(message, 0); watch_display_string(message, 0);
@ -152,16 +177,20 @@ void watch_enter_deep_sleep(char *message) {
// disable all pins // disable all pins
_watch_disable_all_pins_except_rtc(); _watch_disable_all_pins_except_rtc();
// turn off RAM completely. // enter standby (4); we basically hang out here until an interrupt wakes us.
PM->STDBYCFG.bit.BBIASHS = 3;
// enter standby (4); we basically hang out here until an interrupt forces us to reset.
sleep(4); sleep(4);
NVIC_SystemReset(); // and we awake! re-enable the brownout detector
SUPC->INTENSET.bit.BOD33DET = 1;
// call app_setup so the app can re-enable everything we disabled.
app_setup();
// and call app_wake_from_sleep (since main won't have a chance to do it)
app_wake_from_sleep();
} }
void watch_enter_backup_mode() { void watch_enter_deep_sleep() {
// this will not work on the current silicon revision, but I said in the documentation that we do it. // this will not work on the current silicon revision, but I said in the documentation that we do it.
// so let's do it! // so let's do it!
watch_register_extwake_callback(BTN_ALARM, NULL, true); watch_register_extwake_callback(BTN_ALARM, NULL, true);

View File

@ -49,15 +49,14 @@
*/ */
void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool level); void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool level);
/** @brief Unregisters the interrupt on one of the EXTWAKE pins. This will prevent a value change on /** @brief Unregisters the RTC interrupt on one of the EXTWAKE pins. This will prevent a value change on
* one of these pins from waking the device from deep sleep or BACKUP modes. * one of these pins from waking the device from shallow and deep sleep modes.
* @param pin Either pin BTN_ALARM, A2, or A4. If the pin is BTN_ALARM, this function DOES NOT disable * @param pin Either pin BTN_ALARM, A2, or A4. If the pin is BTN_ALARM, this function DOES NOT disable
* the internal pull down on that pin. * the internal pull down on that pin.
*/ */
void watch_disable_extwake_interrupt(uint8_t pin, ext_irq_cb_t callback, bool level); void watch_disable_extwake_interrupt(uint8_t pin);
/** @brief Stores data in one of the RTC's backup registers, which retain their data in the deep sleep /** @brief Stores data in one of the RTC's backup registers, which retain their data in deep sleep mode.
and backup modes.
* @param data An unsigned 32 bit integer with the data you wish to store. * @param data An unsigned 32 bit integer with the data you wish to store.
* @param reg A register from 0-7. * @param reg A register from 0-7.
*/ */
@ -69,33 +68,29 @@ void watch_store_backup_data(uint32_t data, uint8_t reg);
*/ */
uint32_t watch_get_backup_data(uint8_t reg); uint32_t watch_get_backup_data(uint8_t reg);
/** @brief Enters a deep sleep mode by disabling RAM retention and all peripherals except the RTC and /** @brief Enters a shallow sleep mode by disabling all pins and peripherals except the RTC and (optionally)
* (optionally) the LCD. You can wake from this mode by pressing the ALARM button. * the LCD. You can wake from this mode by pressing the ALARM button, if you have an registered an
* @param message Either NULL, or a string representing a message to display while in deep sleep mode. The * external wake callback on the ALARM button. When your app wakes from this shallow sleep mode, your
* message will be displayed at position 0, so you should pad out the beginning of the string * app_setup method will be called, since this function will have disabled things you set up.
* with spaces if you wish for the message to appear on line 2, i.e. " SLEEP". If this * @param message Either NULL, or a string representing a message to display while in shallow sleep mode. If
* parameter is NULL, the screen will be blanked, and this function will disable the SLCD * this parameter is NULL, the screen will be blanked out, and this function will disable the
* peripheral for additional power savings. (also note that while the message will replace any * SLCD peripheral for additional power savings. If the message is non-NULL, it will replace
* text on the display, this function will not clear any indicators you have set. This is by * any text on the screen, and will be displayed at position 0 (so you should pad out the beginning
* design, in case you wish to leave an indicator lit in sleep mode.) * of the string with spaces if you wish for the message to appear on line 2, i.e. " SLEEP").
* @details This deep sleep mode is not the lowest power mode available (see watch_enter_backup_mode), but * Also note that this function will NOT clear any indicator segments that you have set. This is
* it has the benefit of being able to wake with a press of the ALARM button, and provides an option * by design, in case you wish to leave an indicator lit in sleep mode.
* for displaying a message to the user when asleep. The only way to wake from this mode is by * @details This shallow sleep mode is not the lowest power mode available (see watch_enter_deep_sleep), but
* pressing the ALARM button, or receiving an interrupt on pin A2 or A4 of the nine-pin connector. * it has the benefit of retaining your application state and being able to wake from the ALARM button.
* (An alarm interrupt would also work, but this has not yet been implemented.) This function enables * It also provides an option for displaying a message to the user when asleep. Note that whether you
* the ALARM button interrupt for you, but if you wish to wake from the A2 or A4 RTC interrupt, you * want to wake from the ALARM button, the A2 RTC interrupt or the A4 interrupt, you must configure
* must configure them by calling watch_register_extwake_callback. Note however that your callback * this by calling watch_register_extwake_callback first.
* will not be called in this case. *
* Power consumption in deep sleep mode varies a bit with the battery voltage and the temperature, * Power consumption in shallow sleep mode varies a bit with the battery voltage and the temperature,
* but at 3 V and ~25° C you can eoughly estimate: * but at 3 V and 25~30° C you can roughly estimate:
* * ~12µA current draw with the LCD controller on (message != NULL) * * < 12µA current draw with the LCD controller on (message != NULL)
* * ~6.5µA current draw with the LCD controller off (message == NULL) * * < 6µA current draw with the LCD controller off (message == NULL)
* @note With RAM powered off, your application state will be cleared as soon as you call this function, and
* when the user wakes up the watch, your app will effectively be waking from reset. Your app's @ref
* app_wake_from_deep_sleep function will be called to give your app a chance to restore any state that
* you stored using @ref watch_store_backup_data.
*/ */
void watch_enter_deep_sleep(char *message); void watch_enter_shallow_sleep(char *message);
/** @brief Enters the SAM L22's lowest-power mode, BACKUP. /** @brief Enters the SAM L22's lowest-power mode, BACKUP.
* @details This function does some housekeeping before entering BACKUP mode. It first disables all * @details This function does some housekeeping before entering BACKUP mode. It first disables all
@ -113,5 +108,5 @@ void watch_enter_deep_sleep(char *message);
* this function unless you have a device on the nine-pin connector with an external interrupt * this function unless you have a device on the nine-pin connector with an external interrupt
* on pin A2 or A4 (i.e. an accelerometer with an interrupt pin). * on pin A2 or A4 (i.e. an accelerometer with an interrupt pin).
*/ */
void watch_enter_backup_mode(); void watch_enter_deep_sleep();
/// @} /// @}