diff --git a/Makefile b/Makefile index 50d33054..8cab5036 100644 --- a/Makefile +++ b/Makefile @@ -37,6 +37,7 @@ SRCS += \ ./watch-library/shared/watch/watch_common_display.c \ ./watch-library/hardware/watch/watch.c \ ./watch-library/hardware/watch/watch_adc.c \ + ./watch-library/hardware/watch/watch_deepsleep.c \ ./watch-library/hardware/watch/watch_extint.c \ ./watch-library/hardware/watch/watch_gpio.c \ ./watch-library/hardware/watch/watch_private.c \ diff --git a/app.c b/app.c index ab7e9dd2..29a77d9d 100644 --- a/app.c +++ b/app.c @@ -1,4 +1,3 @@ -#include #include "app.h" #include "watch.h" #include "watch_private.h" @@ -6,6 +5,15 @@ #include "usb.h" #include "tusb.h" #include "watch_usb_cdc.h" +#include "tcc.h" + +uint8_t ticks = 0; +bool beep = false; + +void cb_tick(void); +void cb_mode_btn_interrupt(void); +void cb_light_btn_interrupt(void); +void cb_alarm_btn_extwake(void); void yield(void) { tud_task(); @@ -13,7 +21,7 @@ void yield(void) { } void app_init(void) { - // perform watch initialization first! + // initialize the watch hardware. _watch_init(); // check if we are plugged into USB power. @@ -27,20 +35,68 @@ void app_init(void) { } void app_setup(void) { - watch_enable_adc(); + watch_enable_leds(); + watch_enable_external_interrupts(); + + HAL_GPIO_BTN_LIGHT_in(); + HAL_GPIO_BTN_LIGHT_pulldown(); + HAL_GPIO_BTN_LIGHT_pmuxen(HAL_GPIO_PMUX_EIC); + HAL_GPIO_BTN_MODE_in(); + HAL_GPIO_BTN_MODE_pulldown(); + HAL_GPIO_BTN_MODE_pmuxen(HAL_GPIO_PMUX_EIC); + + // Simple test sketch exercises RTC and EIC, plus LEDs, screen and buzzer. + // Periodic callback increments the tick counter. + watch_rtc_register_periodic_callback(cb_tick, 1); + // Light button turns on the LED. + watch_register_interrupt_callback(HAL_GPIO_BTN_LIGHT_pin(), cb_light_btn_interrupt, INTERRUPT_TRIGGER_RISING); + // Mode button beeps the piezo. + watch_register_interrupt_callback(HAL_GPIO_BTN_MODE_pin(), cb_mode_btn_interrupt, INTERRUPT_TRIGGER_RISING); + // Alarm buttton resets the tick counter. + watch_register_extwake_callback(HAL_GPIO_BTN_ALARM_pin(), cb_alarm_btn_extwake, true); + watch_enable_display(); + watch_display_top_left("WA"); + watch_display_top_right(" 0"); + watch_display_main_line(" test "); } bool app_loop(void) { - uint16_t vcc = watch_get_vcc_voltage(); - char buf[7]; - snprintf(buf, 7, "%6d", vcc); - watch_display_main_line(buf); - printf("VCC: %d\n", vcc); - if (usb_is_enabled()) { yield(); } - return false; + if (beep) { + watch_buzzer_play_note(BUZZER_NOTE_C8, 100); + beep = false; + } + + char buf[4]; + sprintf(buf, "%2d", ticks); + printf("ticks: %d\n", ticks); + watch_display_top_right(buf); + + if (ticks >= 30) { + watch_enter_sleep_mode(); + } + + return !usb_is_enabled(); +} + +void cb_tick(void) { + ticks = ticks + 1; + watch_set_led_off(); +} + +void cb_light_btn_interrupt(void) { + watch_set_led_green(); +} + +void cb_mode_btn_interrupt(void) { + beep = true; +} + +void cb_alarm_btn_extwake(void) { + watch_set_led_red(); + ticks = 0; } \ No newline at end of file diff --git a/watch-library/hardware/watch/watch_deepsleep.c b/watch-library/hardware/watch/watch_deepsleep.c index a25b667b..94d65488 100644 --- a/watch-library/hardware/watch/watch_deepsleep.c +++ b/watch-library/hardware/watch/watch_deepsleep.c @@ -22,52 +22,53 @@ * SOFTWARE. */ -#include "hpl_systick_config.h" +#include "watch_deepsleep.h" +#include "app.h" +#include "watch.h" +#include "watch_private.h" -#include "watch_extint.h" +void sleep(const uint8_t mode) { + PM->SLEEPCFG.bit.SLEEPMODE = mode; -// this warning only appears when you `make BOARD=OSO-SWAT-A1-02`. it's annoying, -// but i'd rather have it warn us at build-time than fail silently at run-time. -// besides, no one but me really has any of these boards anyway. -#if BTN_ALARM != GPIO(GPIO_PORTA, 2) -#warning This board revision does not support external wake on BTN_ALARM, so watch_register_extwake_callback will not work with it. Use watch_register_interrupt_callback instead. -#endif + // wait for the mode set to actually take, per SLEEPCFG note in data sheet: + // "A small latency happens between the store instruction and actual writing + // of the SLEEPCFG register due to bridges. Software has to make sure the + // SLEEPCFG register reads the wanted value before issuing WFI instruction." + while(PM->SLEEPCFG.bit.SLEEPMODE != mode); -void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool level) { - uint32_t pinmux; - hri_rtc_tampctrl_reg_t config = RTC->MODE2.TAMPCTRL.reg; + __DSB(); + __WFI(); +} - switch (pin) { - case A4: - a4_callback = callback; - pinmux = PINMUX_PB00G_RTC_IN0; - config &= ~(3 << RTC_TAMPCTRL_IN0ACT_Pos); - config &= ~(1 << RTC_TAMPCTRL_TAMLVL0_Pos); - config |= 1 << RTC_TAMPCTRL_IN0ACT_Pos; - if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL0_Pos; - break; - case A2: - a2_callback = callback; - pinmux = PINMUX_PB02G_RTC_IN1; - config &= ~(3 << RTC_TAMPCTRL_IN1ACT_Pos); - config &= ~(1 << RTC_TAMPCTRL_TAMLVL1_Pos); - config |= 1 << RTC_TAMPCTRL_IN1ACT_Pos; - if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL1_Pos; - break; - case BTN_ALARM: - gpio_set_pin_pull_mode(pin, GPIO_PULL_DOWN); - btn_alarm_callback = callback; - pinmux = PINMUX_PA02G_RTC_IN2; - config &= ~(3 << RTC_TAMPCTRL_IN2ACT_Pos); - config &= ~(1 << RTC_TAMPCTRL_TAMLVL2_Pos); - config |= 1 << RTC_TAMPCTRL_IN2ACT_Pos; - if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL2_Pos; - break; - default: - return; +void watch_register_extwake_callback(uint8_t pin, watch_cb_t callback, bool level) { + uint32_t config = RTC->MODE2.TAMPCTRL.reg; + + if (pin == HAL_GPIO_BTN_ALARM_pin()) { + HAL_GPIO_BTN_ALARM_in(); + HAL_GPIO_BTN_ALARM_pulldown(); + HAL_GPIO_BTN_ALARM_pmuxen(HAL_GPIO_PMUX_RTC); + btn_alarm_callback = callback; + config &= ~(3 << RTC_TAMPCTRL_IN2ACT_Pos); + config &= ~(1 << RTC_TAMPCTRL_TAMLVL2_Pos); + config |= 1 << RTC_TAMPCTRL_IN2ACT_Pos; + if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL2_Pos; + } else if (pin == HAL_GPIO_A2_pin()) { + HAL_GPIO_A2_in(); + HAL_GPIO_A2_pmuxen(HAL_GPIO_PMUX_RTC); + a2_callback = callback; + config &= ~(3 << RTC_TAMPCTRL_IN1ACT_Pos); + config &= ~(1 << RTC_TAMPCTRL_TAMLVL1_Pos); + config |= 1 << RTC_TAMPCTRL_IN1ACT_Pos; + if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL1_Pos; + } else if (pin == HAL_GPIO_A4_pin()) { + HAL_GPIO_A4_in(); + HAL_GPIO_A4_pmuxen(HAL_GPIO_PMUX_RTC); + a4_callback = callback; + config &= ~(3 << RTC_TAMPCTRL_IN0ACT_Pos); + config &= ~(1 << RTC_TAMPCTRL_TAMLVL0_Pos); + config |= 1 << RTC_TAMPCTRL_IN0ACT_Pos; + if (level) config |= 1 << RTC_TAMPCTRL_TAMLVL0_Pos; } - gpio_set_pin_direction(pin, GPIO_DIRECTION_IN); - gpio_set_pin_function(pin, pinmux); // disable the RTC RTC->MODE2.CTRLA.bit.ENABLE = 0; @@ -75,9 +76,9 @@ void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool le // update the configuration RTC->MODE2.TAMPCTRL.reg = config; + // re-enable the RTC RTC->MODE2.CTRLA.bit.ENABLE = 1; - while (RTC->MODE2.SYNCBUSY.bit.ENABLE); // wait for RTC to be enabled NVIC_ClearPendingIRQ(RTC_IRQn); NVIC_EnableIRQ(RTC_IRQn); @@ -85,31 +86,29 @@ void watch_register_extwake_callback(uint8_t pin, ext_irq_cb_t callback, bool le } void watch_disable_extwake_interrupt(uint8_t pin) { - hri_rtc_tampctrl_reg_t config = hri_rtc_get_TAMPCTRL_reg(RTC, 0xFFFFFFFF); + uint32_t config = RTC->MODE2.TAMPCTRL.reg; - 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 (pin == HAL_GPIO_BTN_ALARM_pin()) { + btn_alarm_callback = NULL; + config &= ~(3 << RTC_TAMPCTRL_IN2ACT_Pos); + } else if (pin == HAL_GPIO_A2_pin()) { + a2_callback = NULL; + config &= ~(3 << RTC_TAMPCTRL_IN1ACT_Pos); + } + else if (pin == HAL_GPIO_A4_pin()) { + a4_callback = NULL; + config &= ~(3 << RTC_TAMPCTRL_IN0ACT_Pos); } - 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); + // disable the RTC + RTC->MODE2.CTRLA.bit.ENABLE = 0; + while (RTC->MODE2.SYNCBUSY.bit.ENABLE); // wait for RTC to be disabled + + // update the configuration + RTC->MODE2.TAMPCTRL.reg = config; + + // re-enable the RTC + RTC->MODE2.CTRLA.bit.ENABLE = 1; } void watch_store_backup_data(uint32_t data, uint8_t reg) { @@ -135,20 +134,27 @@ static void _watch_disable_all_pins_except_rtc(void) { // 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 A: that last B is to always keep PA02 configured as-is; that's our ALARM button. + PORT->Group[0].DIRCLR.reg = 0xFFFFFFFB; + // WRCONFIG can only set half the pins at a time, so we need two writes. This sets pins 0-15. + PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_WRPINCFG | 0xFFFB; + // ...and adding the HWSEL flag configures 16-31. + PORT->Group[0].WRCONFIG.reg = PORT_WRCONFIG_HWSEL | PORT_WRCONFIG_WRPINCFG | 0xffff; + // port B: disable all pins we didn't save above. - gpio_set_port_direction(1, portb_pins_to_disable, GPIO_DIRECTION_OFF); + PORT->Group[1].DIRCLR.reg = portb_pins_to_disable; + PORT->Group[1].WRCONFIG.reg = PORT_WRCONFIG_WRPINCFG | (portb_pins_to_disable & 0xFFFF); + PORT->Group[1].WRCONFIG.reg = PORT_WRCONFIG_HWSEL | PORT_WRCONFIG_WRPINCFG | (portb_pins_to_disable >> 16); } static void _watch_disable_all_peripherals_except_slcd(void) { _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; + /// TODO: Actually disable all these peripherals! #SecondMovement + // watch_disable_i2c(); + // SERCOM3->USART.CTRLA.reg &= ~SERCOM_USART_CTRLA_ENABLE; + // MCLK->APBCMASK.reg &= ~MCLK_APBCMASK_SERCOM3; } void watch_enter_sleep_mode(void) { @@ -161,41 +167,21 @@ void watch_enter_sleep_mode(void) { // disable brownout detector interrupt, which could inadvertently wake us up. SUPC->INTENCLR.bit.BOD33DET = 1; - // per Microchip datasheet clarification DS80000782, - // work around silicon erratum 1.8.4 by disabling the SysTick interrupt, which is - // enabled as part of driver init, before going to sleep. - SysTick->CTRL = SysTick->CTRL & ~(CONF_SYSTICK_TICKINT << SysTick_CTRL_TICKINT_Pos); - // disable all pins _watch_disable_all_pins_except_rtc(); // enter standby (4); we basically hang out here until an interrupt wakes us. sleep(4); - // and we awake! re-enable the brownout detector and SysTick interrupt + // and we awake! re-enable the brownout detector SUPC->INTENSET.bit.BOD33DET = 1; - SysTick->CTRL = SysTick->CTRL | (CONF_SYSTICK_TICKINT << SysTick_CTRL_TICKINT_Pos); // call app_setup so the app can re-enable everything we disabled. app_setup(); - - // and call app_wake_from_standby (since main won't have a chance to do it) - app_wake_from_standby(); -} - -void watch_enter_deep_sleep_mode(void) { - // identical to sleep mode except we disable the LCD first. - slcd_sync_deinit(&SEGMENT_LCD_0); - hri_mclk_clear_APBCMASK_SLCD_bit(SLCD); - - watch_enter_sleep_mode(); } void watch_enter_backup_mode(void) { watch_rtc_disable_all_periodic_callbacks(); - _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(); // go into backup sleep mode (5). when we exit, the reset controller will take over. diff --git a/watch-library/shared/watch/watch.h b/watch-library/shared/watch/watch.h index 6a534482..f856f401 100644 --- a/watch-library/shared/watch/watch.h +++ b/watch-library/shared/watch/watch.h @@ -71,7 +71,7 @@ typedef void (*watch_cb_t)(void); // #include "watch_spi.h" // #include "watch_uart.h" // #include "watch_storage.h" -// #include "watch_deepsleep.h" +#include "watch_deepsleep.h" /** @brief Interrupt handler for the SYSTEM interrupt, which handles MCLK, * OSC32KCTRL, OSCCTRL, PAC, PM and SUPC. diff --git a/watch-library/shared/watch/watch_deepsleep.h b/watch-library/shared/watch/watch_deepsleep.h index 75a0a3d0..0bc7a2e0 100644 --- a/watch-library/shared/watch/watch_deepsleep.h +++ b/watch-library/shared/watch/watch_deepsleep.h @@ -21,17 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ -#ifndef _WATCH_DEEPSLEEP_H_INCLUDED -#define _WATCH_DEEPSLEEP_H_INCLUDED + +#pragma once + ////< @file watch_deepsleep.h #include "watch.h" -// These are declared in watch_rtc.c. -extern ext_irq_cb_t btn_alarm_callback; -extern ext_irq_cb_t a2_callback; -extern ext_irq_cb_t a4_callback; - /** @addtogroup deepsleep Sleep Control * @brief This section covers functions related to the various sleep modes available to the watch, * including Sleep, Deep Sleep, and BACKUP mode. @@ -56,8 +52,6 @@ extern ext_irq_cb_t a4_callback; * but the display remains on and your app's state is retained. You can enter this * mode by calling `watch_enter_sleep_mode`. It consumes an order of magnitude less * power than STANDBY mode. - * - Deep Sleep Mode is identical to sleep mode, but it also turns off the LCD to save - * a bit more power. You can enter this mode by calling `watch_enter_deep_sleep_mode`. * - BACKUP mode is the lowest possible power mode on the SAM L22. It turns off all pins * and peripherals except for the RTC. It also turns off the RAM, obliterating your * application's state. The only way to wake from this mode is by setting an external @@ -84,7 +78,7 @@ extern ext_irq_cb_t a4_callback; * device from BACKUP mode. You can still use this function to register a BTN_ALARM interrupt * 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); +void watch_register_extwake_callback(uint8_t pin, watch_cb_t callback, bool level); /** @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. @@ -122,18 +116,6 @@ uint32_t watch_get_backup_data(uint8_t reg); */ void watch_enter_sleep_mode(void); -/** @brief enters Deep Sleep Mode by disabling all pins and peripherals except the RTC. - * @details Short of BACKUP mode, this is the lowest power mode you can enter while retaining your - * application state (and the ability to wake with the alarm button). Just note that the display - * will be completely off, so you should document to the user of your application that they will - * need to press the alarm button to wake the device, or use a sensor board with support for - * an external wake pin. - * - * All notes from watch_enter_sleep_mode apply here, except for power consumption. You can estimate - * the power consumption of this mode to be on the order of 4µA at room temperature. - */ -void watch_enter_deep_sleep_mode(void); - /** @brief Enters the SAM L22's lowest-power mode, BACKUP. * @details This function does some housekeeping before entering BACKUP mode. It first disables all pins * and peripherals except for the RTC, and disables the tick interrupt (since that would wake @@ -151,5 +133,14 @@ void watch_enter_deep_sleep_mode(void); */ void watch_enter_backup_mode(void); +/** + * @brief Puts the device to sleep in the specified mode. + * @param mode The sleep mode to enter. This can be one of the following: + * * 2 - Idle: CPU, AHBx, and APBx clocks are off. + * * 4 - Standby: STANDBY ALL clocks are OFF, unless requested by sleepwalking peripheral. + * * 5 - Backup: Only Backup domain is powered on. + * * 6 - Off: All clocks and power domains are OFF until next system reset. + */ +void sleep(const uint8_t mode); + /// @} -#endif