From 21d2f7527ff4d78f66a1bd7f81829807240d1d62 Mon Sep 17 00:00:00 2001 From: chiararuggeri Date: Wed, 19 Apr 2017 11:12:15 +0200 Subject: [PATCH] Added nRF52 compatibility Waking from deepSleep() is comparable to an hard reset; the sketch won't restart from the sleep invocation but the wakeuup source can be retrieved with wakeupReason() --- examples/PrimoDeepSleep/PrimoDeepSleep.ino | 92 +++++++++++ keywords.txt | 10 ++ library.properties | 4 +- src/ArduinoLowPower.h | 17 +- src/nrf52/ArduinoLowPower.cpp | 178 +++++++++++++++++++++ src/{ => samd}/ArduinoLowPower.cpp | 6 +- 6 files changed, 303 insertions(+), 4 deletions(-) create mode 100644 examples/PrimoDeepSleep/PrimoDeepSleep.ino create mode 100644 src/nrf52/ArduinoLowPower.cpp rename src/{ => samd}/ArduinoLowPower.cpp (96%) diff --git a/examples/PrimoDeepSleep/PrimoDeepSleep.ino b/examples/PrimoDeepSleep/PrimoDeepSleep.ino new file mode 100644 index 0000000..9e7d9f8 --- /dev/null +++ b/examples/PrimoDeepSleep/PrimoDeepSleep.ino @@ -0,0 +1,92 @@ +/* + PrimoDeepSleep.ino + + Written by Chiara Ruggeri (chiara@arduino.org) + + This example for the Arduino Primo board shows how to use + low power library to enter in power off mode and save power. + This mode ensure the deepest power saving mode. If you need + a faster response from the board use standby function instead. + + Please note that once exited from the deepest sleep mode the + board will reset (so setup will be run again). + + The functions enableWakeupFrom set the peripheral that will wake up + the board. By calling it more than once you can choose more than + a wakeup source. + The board will be reset when it wakes up from power off. + You can use wakeUpCause() function to find out what signals woke up + the board if you use more than one wakeUpBy.. function. + + This example code is in the public domain. +*/ + +#include "ArduinoLowPower.h" + + +// Pin used to wakeup the board +const int digitalPin = 10; + +// Pin used in Compatarot module to wake up the board +const int analogPin = A0; + + +void StmEspPM(bool sleep){ + // enable USER1_BUTTON to turn STM32 off and on when pressed. + // note that when STM32 is off you cannot load any new sketch. + pinMode(USER1_BUTTON, STM32_IT); + + // turn ESP8266 off or on + digitalWrite(GPIO_ESP_PW, sleep ? LOW: HIGH); +} + +void setup() { + Serial.begin(9600); + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + delay(500); + + //look for what peripheral woke up the board + //reason is 0 at the first execution + wakeup_reason reason=LowPower.wakeupReason(); + if(reason==GPIO_WAKEUP) //GPIO caused the wake up + doMyStuff(); + else + if(reason==NFC_WAKEUP) //NFC caused the wake up + doMyStuffWithNFC(); + else + if(reason==ANALOG_COMPARATOR_WAKEUP) //Comparator caused the wake up + doOtherStuff(); + + Serial.println("Hi all, I return to sleep"); + + LowPower.companionLowPowerCallback(StmEspPM); + // Send sleep command to ESP and enable USER1_BUTTON to turn STM off + LowPower.companionSleep(); + + //set digital pin 10 to wake up the board when LOW level is detected + LowPower.enableWakeupFrom(GPIO_WAKEUP, digitalPin, LOW); + //let the board be woken up by any NFC field + LowPower.enableWakeupFrom(NFC_WAKEUP); + //wake up the board when the voltage on pin A0 goes below the voltage on pin AREF + LowPower.enableWakeupFrom(ANALOG_COMPARATOR_WAKEUP, analogPin, AREF, UP); + //go in low power mode. Note that the board will reset once it is woken up + LowPower.deepSleep(); +} + + +void loop() {} + +void doMyStuff(){ + //insert your code here +} + +void doMyStuffWithNFC(){ + //insert your code here +} + +void doOtherStuff(){ + //insert your code here +} diff --git a/keywords.txt b/keywords.txt index 3a02803..d0813b7 100644 --- a/keywords.txt +++ b/keywords.txt @@ -17,7 +17,17 @@ idle KEYWORD2 sleep KEYWORD2 deepSleep KEYWORD2 attachInterruptWakeup KEYWORD2 +enableWakeupFrom KEYWORD2 +companionLowPowerCallback KEYWORD2 +companionSleep KEYWORD2 +companionWakeup KEYWORD2 +wakeupReason KEYWORD2 ####################################### # Constants (LITERAL1) ####################################### + +OTHER_WAKEUP LITERAL1 +GPIO_WAKEUP LITERAL1 +NFC_WAKEUP LITERAL1 +ANALOG_COMPARATOR_WAKEUP LITERAL1 \ No newline at end of file diff --git a/library.properties b/library.properties index aabaec8..2d3fb67 100644 --- a/library.properties +++ b/library.properties @@ -2,8 +2,8 @@ name=Arduino Low Power version=1.0.0 author=Arduino maintainer=Arduino LLC -sentence=Power save primitives features for SAMD 32bit boards +sentence=Power save primitives features for SAMD and nRF52 32bit boards paragraph=With this library you can manage the low power states of newer Arduino boards category=Device Control url=http://arduino.cc/libraries/ArduinoLowPower -architectures=samd +architectures=samd,nrf52 diff --git a/src/ArduinoLowPower.h b/src/ArduinoLowPower.h index 06f4a4e..474c879 100644 --- a/src/ArduinoLowPower.h +++ b/src/ArduinoLowPower.h @@ -11,7 +11,7 @@ #include "RTCZero.h" #endif -#if defined(ARDUINO_SAMD_TIAN) +#if defined(ARDUINO_SAMD_TIAN) || defined(ARDUINO_NRF52_PRIMO) // add here any board with companion chip which can be woken up #define BOARD_HAS_COMPANION_CHIP #endif @@ -21,6 +21,14 @@ //typedef void (*voidFuncPtr)( void ) ; typedef void (*onOffFuncPtr)( bool ) ; +typedef enum{ + OTHER_WAKEUP = 0, + GPIO_WAKEUP = 1, + NFC_WAKEUP = 2, + ANALOG_COMPARATOR_WAKEUP = 3 +} wakeup_reason; + + class ArduinoLowPowerClass { public: void idle(void); @@ -55,9 +63,16 @@ class ArduinoLowPowerClass { } #endif + #ifdef ARDUINO_ARCH_NRF52 + void enableWakeupFrom(wakeup_reason peripheral, uint32_t pin = 0xFF, uint32_t event = 0xFF, uint32_t option = 0xFF); + wakeup_reason wakeupReason(); + #endif + private: void setAlarmIn(uint32_t millis); + #ifdef ARDUINO_ARCH_SAMD RTCZero rtc; + #endif #ifdef BOARD_HAS_COMPANION_CHIP void (*companionSleepCB)(bool); #endif diff --git a/src/nrf52/ArduinoLowPower.cpp b/src/nrf52/ArduinoLowPower.cpp new file mode 100644 index 0000000..75889a1 --- /dev/null +++ b/src/nrf52/ArduinoLowPower.cpp @@ -0,0 +1,178 @@ +/* + ArduinoLowPower class for nRF52. + + Written by Chiara Ruggeri (chiara@arduino.org) + + Copyright (c) 2017 Arduino AG. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#if defined(ARDUINO_ARCH_NRF52) + +#include "ArduinoLowPower.h" +#include "WInterrupts.h" +#include "nrf_rtc.h" + +volatile bool event = false; +void (*functionPointer)(void); +nrf_lpcomp_input_t aPin[]={NRF_LPCOMP_INPUT_1, NRF_LPCOMP_INPUT_2, NRF_LPCOMP_INPUT_4, NRF_LPCOMP_INPUT_5, NRF_LPCOMP_INPUT_6, NRF_LPCOMP_INPUT_7}; + +void wakeUpGpio(){ + event = true; + if(functionPointer) + functionPointer(); +} + +void ArduinoLowPowerClass::idle() { + // nRF52 has just two low power modes. Call sleep if idle is called. + sleep(); +} + +void ArduinoLowPowerClass::idle(uint32_t millis) { + setAlarmIn(millis); + idle(); +} + +void ArduinoLowPowerClass::sleep() { + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + event=false; + while(!event){ + sd_app_evt_wait(); + } +} + +void ArduinoLowPowerClass::sleep(uint32_t millis) { + setAlarmIn(millis); + sleep(); +} + +void ArduinoLowPowerClass::deepSleep() { + //Enter in systemOff mode only when no EasyDMA transfer is active + //this is achieved by disabling all peripheral that use it + NRF_UARTE0->ENABLE = UARTE_ENABLE_ENABLE_Disabled; //disable UART + NRF_SAADC ->ENABLE = (SAADC_ENABLE_ENABLE_Disabled << SAADC_ENABLE_ENABLE_Pos); //disable ADC + NRF_PWM0 ->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos); //disable all pwm instance + NRF_PWM1 ->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos); + NRF_PWM2 ->ENABLE = (PWM_ENABLE_ENABLE_Disabled << PWM_ENABLE_ENABLE_Pos); + NRF_TWIM1 ->ENABLE = (TWIM_ENABLE_ENABLE_Disabled << TWIM_ENABLE_ENABLE_Pos); //disable TWI Master + NRF_TWIS1 ->ENABLE = (TWIS_ENABLE_ENABLE_Disabled << TWIS_ENABLE_ENABLE_Pos); //disable TWI Slave + + //Enter in System OFF mode + sd_power_system_off(); + + /*Only for debugging purpose, will not be reached without connected debugger*/ + while(1); +} + +void ArduinoLowPowerClass::setAlarmIn(uint32_t millis) { + nrf_rtc_prescaler_set(NRF_RTC1, 32); + //enable interrupt + NVIC_SetPriority(RTC1_IRQn, 2); //high priority + NVIC_ClearPendingIRQ(RTC1_IRQn); + NVIC_EnableIRQ(RTC1_IRQn); + nrf_rtc_event_clear(NRF_RTC1, NRF_RTC_EVENT_COMPARE_0); + nrf_rtc_int_enable(NRF_RTC1, NRF_RTC_INT_COMPARE0_MASK); + //Tick every 1 ms + nrf_rtc_cc_set(NRF_RTC1, 0, millis); + + //start RTC + nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_START); +} + +void ArduinoLowPowerClass::attachInterruptWakeup(uint32_t pin, voidFuncPtr callback, uint32_t mode) { + functionPointer = callback; + + if(pin == RTC_ALARM_WAKEUP) + return; + + pinMode(pin, INPUT_PULLUP); + attachInterrupt(pin, wakeUpGpio, mode); +} + +void ArduinoLowPowerClass::enableWakeupFrom(wakeup_reason peripheral, uint32_t pin, uint32_t event, uint32_t option){ + if(peripheral == NFC_WAKEUP){ + NRF_NFCT->TASKS_SENSE=1; + return; + } + if(peripheral == ANALOG_COMPARATOR_WAKEUP){ + detect_mode mode; + if(option == DOWN) + mode = DOWN; + else if(option == UP) + mode = UP; + else + mode = CROSS; + nrf_lpcomp_config_t config={(nrf_lpcomp_ref_t)event, (nrf_lpcomp_detect_t)mode}; + nrf_lpcomp_configure(&config); + if(pin<14 && pin>19) + return; //no analog pin is choosen + nrf_lpcomp_input_select(aPin[pin-14]); + nrf_lpcomp_enable(); + nrf_lpcomp_task_trigger(NRF_LPCOMP_TASK_START); + while(!nrf_lpcomp_event_check(NRF_LPCOMP_EVENT_READY)); + return; + } + if(peripheral == GPIO_WAKEUP){ + if(pin > 20)// allow wake up only from digital and analog pins + return; + if(event==LOW) + nrf_gpio_cfg_sense_input(g_APinDescription[pin].ulPin, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); + else + nrf_gpio_cfg_sense_input(g_APinDescription[pin].ulPin, NRF_GPIO_PIN_PULLDOWN, NRF_GPIO_PIN_SENSE_HIGH); + } +} + +wakeup_reason ArduinoLowPowerClass::wakeupReason(){ + uint32_t guilty; + sd_power_reset_reason_get(&guilty); + if(guilty & 0x10000){ // GPIO + //RESETREAS is a cumulative register. We need to clear it by writing 1 in the relative field + sd_power_reset_reason_clr(1 << 16); + return GPIO_WAKEUP; + } + if(guilty & 0x80000){ //NFC + sd_power_reset_reason_clr(1 << 19); + return NFC_WAKEUP; + } + if(guilty & 0x20000){ //COMP + sd_power_reset_reason_clr(1 << 17); + return ANALOG_COMPARATOR_WAKEUP; + } + return OTHER_WAKEUP; +} + + +ArduinoLowPowerClass LowPower; + +#ifdef __cplusplus +extern "C"{ +#endif + +void RTC1_IRQHandler(void) +{ + event=true; + nrf_rtc_event_clear(NRF_RTC1, NRF_RTC_EVENT_COMPARE_0); + nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_CLEAR); + nrf_rtc_task_trigger(NRF_RTC1, NRF_RTC_TASK_STOP); + if(functionPointer) + functionPointer(); +} + +#ifdef __cplusplus +} +#endif + +#endif // ARDUINO_ARCH_NRF52 \ No newline at end of file diff --git a/src/ArduinoLowPower.cpp b/src/samd/ArduinoLowPower.cpp similarity index 96% rename from src/ArduinoLowPower.cpp rename to src/samd/ArduinoLowPower.cpp index 466d763..e538426 100644 --- a/src/ArduinoLowPower.cpp +++ b/src/samd/ArduinoLowPower.cpp @@ -1,3 +1,5 @@ +#if defined(ARDUINO_ARCH_SAMD) + #include "ArduinoLowPower.h" #include "WInterrupts.h" @@ -87,4 +89,6 @@ void ArduinoLowPowerClass::attachInterruptWakeup(uint32_t pin, voidFuncPtr callb NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val; } -ArduinoLowPowerClass LowPower; \ No newline at end of file +ArduinoLowPowerClass LowPower; + +#endif // ARDUINO_ARCH_SAMD \ No newline at end of file