add a new deep sleep mode alongside backup mode
This commit is contained in:
		
							parent
							
								
									d97e74058f
								
							
						
					
					
						commit
						5ff4a88374
					
				@ -61,6 +61,9 @@ void app_wake_from_deep_sleep() {
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -160,10 +163,10 @@ bool app_loop() {
 | 
			
		||||
        watch_set_led_off();
 | 
			
		||||
 | 
			
		||||
        // wait a moment for the user's finger to be off the button
 | 
			
		||||
        delay_ms(1000);
 | 
			
		||||
        delay_ms(250);
 | 
			
		||||
 | 
			
		||||
        // nap time :)
 | 
			
		||||
        watch_enter_deep_sleep();
 | 
			
		||||
        watch_enter_deep_sleep(NULL);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
@ -188,8 +191,5 @@ void cb_mode_pressed() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void cb_alarm_pressed() {
 | 
			
		||||
    // boo: http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_L22_Family_Errata_DS80000782B.pdf
 | 
			
		||||
    // Reference 15010. doesn't say it applies to PA02 but it seems it does?
 | 
			
		||||
    // anyway can't deep sleep now :(
 | 
			
		||||
    // application_state.enter_deep_sleep = true;
 | 
			
		||||
    application_state.enter_deep_sleep = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,9 @@ int main(void) {
 | 
			
		||||
    }
 | 
			
		||||
    watch_disable_digital_input(VBUS_DET);
 | 
			
		||||
 | 
			
		||||
    // initialize the delay driver before any user code is called.
 | 
			
		||||
    delay_driver_init();
 | 
			
		||||
 | 
			
		||||
    // User code. Give the app a chance to initialize its data structures and state.
 | 
			
		||||
    app_init();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -103,21 +103,75 @@ uint32_t watch_get_backup_data(uint8_t reg) {
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void watch_enter_deep_sleep() {
 | 
			
		||||
    // enable and configure the external wake interrupt, if not already set up.
 | 
			
		||||
    if (btn_alarm_callback == NULL && a2_callback == NULL && a4_callback == NULL) {
 | 
			
		||||
        gpio_set_pin_direction(BTN_ALARM, GPIO_DIRECTION_IN);
 | 
			
		||||
        gpio_set_pin_pull_mode(BTN_ALARM, GPIO_PULL_DOWN);
 | 
			
		||||
        gpio_set_pin_function(BTN_ALARM, PINMUX_PA02G_RTC_IN2);
 | 
			
		||||
        _extwake_register_callback(&CALENDAR_0.device, extwake_callback);
 | 
			
		||||
void _watch_disable_all_pins_except_rtc() {
 | 
			
		||||
    uint32_t config = RTC->MODE0.TAMPCTRL.reg;
 | 
			
		||||
    uint32_t portb_pins_to_disable = 0xFFFFFFFF;
 | 
			
		||||
 | 
			
		||||
    // if there's an action set on RTC/IN[0], leave PB00 configured
 | 
			
		||||
    if (config & RTC_TAMPCTRL_IN0ACT_Msk) portb_pins_to_disable &= 0xFFFFFFFE;
 | 
			
		||||
    // same with RTC/IN[1] and PB02
 | 
			
		||||
    if (config & RTC_TAMPCTRL_IN1ACT_Msk) portb_pins_to_disable &= 0xFFFFFFFB;
 | 
			
		||||
 | 
			
		||||
    // port A: always keep PA02 configured as-is; that's our ALARM button.
 | 
			
		||||
    gpio_set_port_direction(0, 0xFFFFFFFB, GPIO_DIRECTION_OFF);
 | 
			
		||||
    // port B: disable all pins we didn't save above.
 | 
			
		||||
    gpio_set_port_direction(1, portb_pins_to_disable, GPIO_DIRECTION_OFF);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void _watch_disable_all_peripherals_except_slcd() {
 | 
			
		||||
    _watch_disable_tcc();
 | 
			
		||||
    watch_disable_adc();
 | 
			
		||||
    watch_disable_external_interrupts();
 | 
			
		||||
    watch_disable_i2c();
 | 
			
		||||
    // TODO: replace this with a proper function when we remove the debug UART
 | 
			
		||||
    SERCOM3->USART.CTRLA.reg &= ~SERCOM_USART_CTRLA_ENABLE;
 | 
			
		||||
    MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_SERCOM3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void watch_enter_deep_sleep(char *message) {
 | 
			
		||||
    // configure the ALARM interrupt (the callback doesn't matter)
 | 
			
		||||
    watch_register_extwake_callback(BTN_ALARM, NULL, true);
 | 
			
		||||
 | 
			
		||||
    if (message != NULL) {
 | 
			
		||||
        watch_display_string("          ", 0);
 | 
			
		||||
        watch_display_string(message, 0);
 | 
			
		||||
    } else {
 | 
			
		||||
        slcd_sync_deinit(&SEGMENT_LCD_0);
 | 
			
		||||
        hri_mclk_clear_APBCMASK_SLCD_bit(SLCD);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // disable SLCD
 | 
			
		||||
    // disable all other peripherals
 | 
			
		||||
    _watch_disable_all_peripherals_except_slcd();
 | 
			
		||||
 | 
			
		||||
    // disable tick interrupt
 | 
			
		||||
    watch_register_tick_callback(NULL);
 | 
			
		||||
 | 
			
		||||
    // disable brownout detector interrupt, which could inadvertently wake us up.
 | 
			
		||||
    SUPC->INTENCLR.bit.BOD33DET = 1;
 | 
			
		||||
 | 
			
		||||
    // disable all pins
 | 
			
		||||
    _watch_disable_all_pins_except_rtc();
 | 
			
		||||
 | 
			
		||||
    // turn off RAM completely.
 | 
			
		||||
    PM->STDBYCFG.bit.BBIASHS = 3;
 | 
			
		||||
 | 
			
		||||
    // enter standby (4); we basically hang out here until an interrupt forces us to reset.
 | 
			
		||||
    sleep(4);
 | 
			
		||||
 | 
			
		||||
    NVIC_SystemReset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void watch_enter_backup_mode() {
 | 
			
		||||
    // this will not work on the current silicon revision, but I said in the documentation that we do it.
 | 
			
		||||
    // so let's do it!
 | 
			
		||||
    watch_register_extwake_callback(BTN_ALARM, NULL, true);
 | 
			
		||||
 | 
			
		||||
    watch_register_tick_callback(NULL);
 | 
			
		||||
    _watch_disable_all_peripherals_except_slcd();
 | 
			
		||||
    slcd_sync_deinit(&SEGMENT_LCD_0);
 | 
			
		||||
    hri_mclk_clear_APBCMASK_SLCD_bit(SLCD);
 | 
			
		||||
    _watch_disable_all_pins_except_rtc();
 | 
			
		||||
 | 
			
		||||
    // TODO: disable other peripherals
 | 
			
		||||
 | 
			
		||||
    // go into backup sleep mode
 | 
			
		||||
    // go into backup sleep mode (5). when we exit, the reset controller will take over.
 | 
			
		||||
    sleep(5);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,9 @@
 | 
			
		||||
  *        from deep sleep (aka BACKUP) mode.
 | 
			
		||||
  * @param pin Either pin BTN_ALARM, A2, or A4. These are the three external wake pins. If the pin
 | 
			
		||||
  *            is BTN_ALARM, this function also enables an internal pull down on that pin.
 | 
			
		||||
  * @param callback The callback to be called if this pin triggers outside of deep sleep mode.
 | 
			
		||||
  * @param callback The callback to be called if this pin triggers outside of deep sleep mode. If
 | 
			
		||||
  *                 this is NULL, no callback will be called even in normal mode, but the interrupt
 | 
			
		||||
  *                 will still be enabled so that it can wake from deep sleep or backup mode.
 | 
			
		||||
  * @param level The level you wish to scan for: true for rising, false for falling. Note that you
 | 
			
		||||
  *              cannot scan for both rising and falling edges like you can with the external interrupt
 | 
			
		||||
  *              pins; with the external wake interrupt, you can only get one or the other.
 | 
			
		||||
@ -43,40 +45,73 @@
 | 
			
		||||
  *       WILL NOT be called, as the device is basically waking from reset at that point.
 | 
			
		||||
  * @warning As of the current SAM L22 silicon revision (rev B), the BTN_ALARM pin cannot wake the
 | 
			
		||||
  *          device from BACKUP mode. You can still use this function to register a BTN_ALARM interrupt
 | 
			
		||||
  *          in normal or STANDBY mode, but to wake from BACKUP, you will need to use pin A2 or A4.
 | 
			
		||||
  *          in normal or deep sleep mode, but to wake from BACKUP, you will need to use pin A2 or A4.
 | 
			
		||||
  */
 | 
			
		||||
void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool level);
 | 
			
		||||
 | 
			
		||||
/** @brief Stores data in one of the RTC's backup registers, which retain their data in deep sleep.
 | 
			
		||||
/** @brief Unregisters the 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.
 | 
			
		||||
  * @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.
 | 
			
		||||
  */
 | 
			
		||||
void watch_disable_extwake_interrupt(uint8_t pin, ext_irq_cb_t callback, bool level);
 | 
			
		||||
 | 
			
		||||
/** @brief Stores data in one of the RTC's backup registers, which retain their data in the deep sleep
 | 
			
		||||
           and backup modes.
 | 
			
		||||
  * @param data An unsigned 32 bit integer with the data you wish to store.
 | 
			
		||||
  * @param reg A register from 0-7.
 | 
			
		||||
  */
 | 
			
		||||
void watch_store_backup_data(uint32_t data, uint8_t reg);
 | 
			
		||||
 | 
			
		||||
/** @brief Gets 32 bits of data from the RTC's backup register, which retains its data in deep sleep.
 | 
			
		||||
/** @brief Gets 32 bits of data from the RTC's backup register.
 | 
			
		||||
  * @param reg A register from 0-7.
 | 
			
		||||
  * @return An unsigned 32 bit integer with the from the backup register.
 | 
			
		||||
  */
 | 
			
		||||
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
 | 
			
		||||
  *        (optionally) the LCD. You can wake from this mode by pressing the ALARM button.
 | 
			
		||||
  * @param message Either NULL, or a string representing a message to display while in deep sleep mode. The
 | 
			
		||||
  *                message will be displayed at position 0, so you should pad out the beginning of the string
 | 
			
		||||
  *                with spaces if you wish for the message to appear on line 2, i.e. "    SLEEP". If this
 | 
			
		||||
  *                parameter is NULL, the screen will be blanked, and this function will disable the SLCD
 | 
			
		||||
  *                peripheral for additional power savings. (also note that while the message will replace any
 | 
			
		||||
  *                text on the display, this function will not clear any indicators you have set. This is by
 | 
			
		||||
  *                design, in case you wish to leave an indicator lit in sleep mode.)
 | 
			
		||||
  * @details This deep sleep mode is not the lowest power mode available (see watch_enter_backup_mode), but
 | 
			
		||||
  *          it has the benefit of being able to wake with a press of the ALARM button, and provides an option
 | 
			
		||||
  *          for displaying a message to the user when asleep. The only way to wake from this mode is by
 | 
			
		||||
  *          pressing the ALARM button, or receiving an interrupt on pin A2 or A4 of the nine-pin connector.
 | 
			
		||||
  *          (An alarm interrupt would also work, but this has not yet been implemented.) This function enables
 | 
			
		||||
  *          the ALARM button interrupt for you, but if you wish to wake from the A2 or A4 RTC interrupt, you
 | 
			
		||||
  *          must configure them by calling watch_register_extwake_callback. Note however that your callback
 | 
			
		||||
  *          will not be called in this case.
 | 
			
		||||
  *          Power consumption in deep sleep mode varies a bit with the battery voltage and the temperature,
 | 
			
		||||
  *          but at 3 V and ~25° C you can eoughly estimate:
 | 
			
		||||
  *           * ~12µA current draw with the LCD controller on (message != NULL)
 | 
			
		||||
  *           * ~6.5µ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);
 | 
			
		||||
 | 
			
		||||
/** @brief Enters the SAM L22's lowest-power mode, BACKUP.
 | 
			
		||||
  * @details This function does some housekeeping before entering BACKUP mode. It first disables all
 | 
			
		||||
  *          peripherals except for the RTC, and disables the tick interrupt (since that would wake)
 | 
			
		||||
  *          us up from deep sleep. It also sets an external wake source on the ALARM button, if one
 | 
			
		||||
  *          peripherals except for the RTC, and disables the tick interrupt (since that would wake
 | 
			
		||||
  *          us up from deep sleep). It also sets an external wake source on the ALARM button, if one
 | 
			
		||||
  *          was not already set. If you wish to wake from another source, such as one of the external
 | 
			
		||||
  *          wake interrupt pins on the 9-pin connector, set that up prior to calling this function.
 | 
			
		||||
  * @note If you have a callback set for an external wake interrupt, it will be called if triggered while
 | 
			
		||||
  *       in ACTIVE, IDLE or STANDBY modes, but it *will not be called* when waking from BACKUP.
 | 
			
		||||
  *       Waking from backup is effectively like waking from reset, except that your @ref
 | 
			
		||||
  *       app_wake_from_deep_sleep function will be called.
 | 
			
		||||
  * @warning In initial testing, it seems like the ALARM_BTN pin (PA02 RTC/IN2) cannot wake the device
 | 
			
		||||
  *          from deep sleep mode. There is an errata note (Reference: 15010, linked) that says that
 | 
			
		||||
  *          due to a silicon bug, PB01 cannot be used as RTC/IN2. It seems though that this bug may
 | 
			
		||||
  *          also affect PA02. As a result — and I'm very bummed about this — you cannot use deep sleep
 | 
			
		||||
  *          mode unless you set up an external wake interrupt using a device on the nine-pin connector
 | 
			
		||||
  *          (i.e. an accelerometer with an interrupt pin). Otherwise your only option for waking will
 | 
			
		||||
  *          be to unscrew the watch case and press the reset button on the back of the board.
 | 
			
		||||
  *          http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_L22_Family_Errata_DS80000782B.pdf
 | 
			
		||||
  * @warning On current revisions of the SAM L22 silicon, the ALARM_BTN pin (PA02 RTC/IN2) cannot wake
 | 
			
		||||
  *          the device from deep sleep mode. There is an errata note (Reference: 15010) that says that
 | 
			
		||||
  *          due to a silicon bug, RTC/IN2 is not functional in BACKUP. As a result, you should not call
 | 
			
		||||
  *          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).
 | 
			
		||||
  */
 | 
			
		||||
void watch_enter_deep_sleep();
 | 
			
		||||
void watch_enter_backup_mode();
 | 
			
		||||
/// @}
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,11 @@ void watch_enable_i2c() {
 | 
			
		||||
    i2c_m_sync_enable(&I2C_0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void watch_disable_i2c() {
 | 
			
		||||
    i2c_m_sync_disable(&I2C_0);
 | 
			
		||||
	hri_mclk_clear_APBCMASK_SERCOM1_bit(MCLK);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void watch_i2c_send(int16_t addr, uint8_t *buf, uint16_t length) {
 | 
			
		||||
    i2c_m_sync_set_periphaddr(&I2C_0, addr, I2C_M_SEVEN);
 | 
			
		||||
    io_write(I2C_0_io, buf, length);
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,10 @@
 | 
			
		||||
  */
 | 
			
		||||
void watch_enable_i2c();
 | 
			
		||||
 | 
			
		||||
/** @brief Disables the I2C peripheral.
 | 
			
		||||
  */
 | 
			
		||||
void watch_disable_i2c();
 | 
			
		||||
 | 
			
		||||
/** @brief Sends a series of values to a device on the I2C bus.
 | 
			
		||||
  * @param addr The address of the device you wish to talk to.
 | 
			
		||||
  * @param buf A series of unsigned bytes; the data you wish to transmit.
 | 
			
		||||
 | 
			
		||||
@ -28,6 +28,9 @@ void _watch_init() {
 | 
			
		||||
    // disable the LED pin (it may have been enabled by the bootloader)
 | 
			
		||||
    watch_disable_digital_output(RED);
 | 
			
		||||
 | 
			
		||||
    // RAM should be back-biased in STANDBY
 | 
			
		||||
    PM->STDBYCFG.bit.BBIASHS = 1;
 | 
			
		||||
 | 
			
		||||
    // Use switching regulator for lower power consumption.
 | 
			
		||||
    SUPC->VREG.bit.SEL = 1;
 | 
			
		||||
    while(!SUPC->STATUS.bit.VREGRDY);
 | 
			
		||||
@ -41,10 +44,9 @@ void _watch_init() {
 | 
			
		||||
    SUPC->BOD33.bit.ACTCFG = 1;     // Enable sampling mode when active
 | 
			
		||||
    SUPC->BOD33.bit.RUNSTDBY = 1;   // Enable sampling mode in standby
 | 
			
		||||
    SUPC->BOD33.bit.STDBYCFG = 1;   // Run in standby
 | 
			
		||||
    SUPC->BOD33.bit.RUNBKUP = 1;    // Also run in backup mode
 | 
			
		||||
    SUPC->BOD33.bit.RUNBKUP = 0;    // Don't run in backup mode
 | 
			
		||||
    SUPC->BOD33.bit.PSEL = 0xB;     // Check battery level every 4 seconds
 | 
			
		||||
    SUPC->BOD33.bit.LEVEL = 31;     // Detect brownout at 2.5V (1.445V + level * 34mV)
 | 
			
		||||
    SUPC->BOD33.bit.BKUPLEVEL = 31; // Detect same level in backup mode
 | 
			
		||||
    SUPC->BOD33.bit.ACTION = 0x2;   // Generate an interrupt when BOD33 is triggered
 | 
			
		||||
    SUPC->BOD33.bit.HYST = 0;       // Disable hysteresis
 | 
			
		||||
    while(!SUPC->STATUS.bit.B33SRDY);
 | 
			
		||||
@ -57,9 +59,6 @@ void _watch_init() {
 | 
			
		||||
    CALENDAR_0_init();
 | 
			
		||||
    calendar_enable(&CALENDAR_0);
 | 
			
		||||
 | 
			
		||||
    // Not sure if this belongs in every app -- is there a power impact?
 | 
			
		||||
    delay_driver_init();
 | 
			
		||||
 | 
			
		||||
    // set up state
 | 
			
		||||
    btn_alarm_callback = NULL;
 | 
			
		||||
    a2_callback = NULL;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user