diff --git a/lib/Low_Power/ArduinoLowPower.cpp b/lib/Low_Power/ArduinoLowPower.cpp new file mode 100644 index 0000000..4383b0b --- /dev/null +++ b/lib/Low_Power/ArduinoLowPower.cpp @@ -0,0 +1,225 @@ +#if defined(ARDUINO_ARCH_SAMD) + +#include "ArduinoLowPower.h" + +static void configGCLK6() +{ + // enable EIC clock + GCLK->CLKCTRL.bit.CLKEN = 0; //disable GCLK module + while (GCLK->STATUS.bit.SYNCBUSY); + + GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK6 | GCLK_CLKCTRL_ID( GCM_EIC )) ; //EIC clock switched on GCLK6 + while (GCLK->STATUS.bit.SYNCBUSY); + + GCLK->GENCTRL.reg = (GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(6)); //source for GCLK6 is OSCULP32K + while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); + + GCLK->GENCTRL.bit.RUNSTDBY = 1; //GCLK6 run standby + while (GCLK->STATUS.reg & GCLK_STATUS_SYNCBUSY); + + /* Errata: Make sure that the Flash does not power all the way down + * when in sleep mode. */ + + NVMCTRL->CTRLB.bit.SLEEPPRM = NVMCTRL_CTRLB_SLEEPPRM_DISABLED_Val; +} + +void ArduinoLowPowerClass::idle() { + SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk; + PM->SLEEP.reg = 2; + __DSB(); + __WFI(); +} + +void ArduinoLowPowerClass::idle(uint32_t millis) { + setAlarmIn(millis); + idle(); +} + +void ArduinoLowPowerClass::sleep() { + bool restoreUSBDevice = false; + + if (SERIAL_PORT_USBVIRTUAL) { + USBDevice.standby(); + } else { + USBDevice.detach(); + restoreUSBDevice = true; + } + + // Ralf + //USBDevice.detach(); + //restoreUSBDevice = true; + //---------- + + // Disable systick interrupt: See https://www.avrfreaks.net/forum/samd21-samd21e16b-sporadically-locks-and-does-not-wake-standby-sleep-mode + SysTick->CTRL &= ~SysTick_CTRL_TICKINT_Msk; + SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; + __DSB(); + __WFI(); + // Enable systick interrupt + SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; + if (restoreUSBDevice) { + USBDevice.attach(); + } +} + +void ArduinoLowPowerClass::sleep(uint32_t millis) { + setAlarmIn(millis); + sleep(); +} + +void ArduinoLowPowerClass::deepSleep() { + sleep(); +} + +void ArduinoLowPowerClass::deepSleep(uint32_t millis) { + sleep(millis); +} + +void ArduinoLowPowerClass::setAlarmIn(uint32_t millis) { + + if (!rtc.isConfigured()) { + attachInterruptWakeup(RTC_ALARM_WAKEUP, NULL, (irq_mode)0); + } + + uint32_t now = rtc.getEpoch(); + rtc.setAlarmEpoch(now + millis/1000); + rtc.enableAlarm(rtc.MATCH_YYMMDDHHMMSS); +} + +void ArduinoLowPowerClass::attachInterruptWakeup(uint32_t pin, voidFuncPtr callback, irq_mode mode) { + + if (pin > PINS_COUNT) { + // check for external wakeup sources + // RTC library should call this API to enable the alarm subsystem + switch (pin) { + case RTC_ALARM_WAKEUP: + rtc.begin(false); + rtc.attachInterrupt(callback); + /*case UART_WAKEUP:*/ + } + return; + } + + EExt_Interrupts in = g_APinDescription[pin].ulExtInt; + if (in == NOT_AN_INTERRUPT || in == EXTERNAL_INT_NMI) + return; + + //pinMode(pin, INPUT_PULLUP); + attachInterrupt(pin, callback, mode); + + configGCLK6(); + + // Enable wakeup capability on pin in case being used during sleep + EIC->WAKEUP.reg |= (1 << in); +} + +void ArduinoLowPowerClass::attachAdcInterrupt(uint32_t pin, voidFuncPtr callback, adc_interrupt mode, uint16_t lo, uint16_t hi) +{ + uint8_t winmode = 0; + + switch (mode) { + case ADC_INT_BETWEEN: winmode = ADC_WINCTRL_WINMODE_MODE3; break; + case ADC_INT_OUTSIDE: winmode = ADC_WINCTRL_WINMODE_MODE4; break; + case ADC_INT_ABOVE_MIN: winmode = ADC_WINCTRL_WINMODE_MODE1; break; + case ADC_INT_BELOW_MAX: winmode = ADC_WINCTRL_WINMODE_MODE2; break; + default: return; + } + + adc_cb = callback; + + configGCLK6(); + + // Configure ADC to use GCLK6 (OSCULP32K) + while (GCLK->STATUS.bit.SYNCBUSY) {} + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_ADC + | GCLK_CLKCTRL_GEN_GCLK6 + | GCLK_CLKCTRL_CLKEN; + while (GCLK->STATUS.bit.SYNCBUSY) {} + + // Set ADC prescaler as low as possible + ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV4; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Configure window mode + ADC->WINLT.reg = lo; + ADC->WINUT.reg = hi; + ADC->WINCTRL.reg = winmode; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable window interrupt + ADC->INTENSET.bit.WINMON = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable ADC in standby mode + ADC->CTRLA.bit.RUNSTDBY = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable continuous conversions + ADC->CTRLB.bit.FREERUN = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Configure input mux + ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable the ADC + ADC->CTRLA.bit.ENABLE = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Start continuous conversions + ADC->SWTRIG.bit.START = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Enable the ADC interrupt + NVIC_EnableIRQ(ADC_IRQn); +} + +void ArduinoLowPowerClass::detachAdcInterrupt() +{ + // Disable the ADC interrupt + NVIC_DisableIRQ(ADC_IRQn); + + // Disable the ADC + ADC->CTRLA.bit.ENABLE = 0; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Disable continuous conversions + ADC->CTRLB.bit.FREERUN = 0; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Disable ADC in standby mode + ADC->CTRLA.bit.RUNSTDBY = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Disable window interrupt + ADC->INTENCLR.bit.WINMON = 1; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Disable window mode + ADC->WINCTRL.reg = ADC_WINCTRL_WINMODE_DISABLE; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Restore ADC prescaler + ADC->CTRLB.bit.PRESCALER = ADC_CTRLB_PRESCALER_DIV512_Val; + while (ADC->STATUS.bit.SYNCBUSY) {} + + // Restore ADC clock + while (GCLK->STATUS.bit.SYNCBUSY) {} + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_ADC + | GCLK_CLKCTRL_GEN_GCLK0 + | GCLK_CLKCTRL_CLKEN; + while (GCLK->STATUS.bit.SYNCBUSY) {} + + adc_cb = nullptr; +} + +void ADC_Handler() +{ + // Clear the interrupt flag + ADC->INTFLAG.bit.WINMON = 1; + LowPower.adc_cb(); +} + +ArduinoLowPowerClass LowPower; + +#endif // ARDUINO_ARCH_SAMD diff --git a/lib/Low_Power/ArduinoLowPower.h b/lib/Low_Power/ArduinoLowPower.h new file mode 100644 index 0000000..8ac559a --- /dev/null +++ b/lib/Low_Power/ArduinoLowPower.h @@ -0,0 +1,106 @@ +#ifndef _ARDUINO_LOW_POWER_H_ +#define _ARDUINO_LOW_POWER_H_ + +#include + +#ifdef ARDUINO_ARCH_AVR +#error The library is not compatible with AVR boards +#endif + +#ifdef ARDUINO_ARCH_SAMD +#include "RTCZero.h" +#endif + +#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 + +#define RTC_ALARM_WAKEUP 0xFF + +#ifdef ARDUINO_API_VERSION +using irq_mode = PinStatus; +#else +using irq_mode = uint32_t; +#endif + +//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; + +#ifdef ARDUINO_ARCH_SAMD +enum adc_interrupt +{ + ADC_INT_BETWEEN, + ADC_INT_OUTSIDE, + ADC_INT_ABOVE_MIN, + ADC_INT_BELOW_MAX, +}; +#endif + + +class ArduinoLowPowerClass { + public: + void idle(void); + void idle(uint32_t millis); + void idle(int millis) { + idle((uint32_t)millis); + } + + void sleep(void); + void sleep(uint32_t millis); + void sleep(int millis) { + sleep((uint32_t)millis); + } + + void deepSleep(void); + void deepSleep(uint32_t millis); + void deepSleep(int millis) { + deepSleep((uint32_t)millis); + } + + void attachInterruptWakeup(uint32_t pin, voidFuncPtr callback, irq_mode mode); + + #ifdef BOARD_HAS_COMPANION_CHIP + void companionLowPowerCallback(onOffFuncPtr callback) { + companionSleepCB = callback; + } + void companionSleep() { + companionSleepCB(true); + } + void companionWakeup() { + companionSleepCB(false); + } + #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 + + #ifdef ARDUINO_ARCH_SAMD + void attachAdcInterrupt(uint32_t pin, voidFuncPtr callback, adc_interrupt mode, uint16_t lo, uint16_t hi); + void detachAdcInterrupt(); + #endif + + private: + void setAlarmIn(uint32_t millis); + #ifdef ARDUINO_ARCH_SAMD + RTCZero rtc; + voidFuncPtr adc_cb; + friend void ADC_Handler(); + #endif + #ifdef BOARD_HAS_COMPANION_CHIP + void (*companionSleepCB)(bool); + #endif +}; + +extern ArduinoLowPowerClass LowPower; + +#endif diff --git a/src/main.cpp b/src/main.cpp index d788307..4719f50 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,6 +39,7 @@ #include #include "hseSensorProtocol.h" #include "MessenSensoren.h" +#include "ArduinoLowPower.h" // This EUI must be in little-endian format, so least-significant-byte // first. When copying an EUI from ttnctl output, this means to reverse @@ -226,6 +227,9 @@ void onEvent(ev_t ev) { } // Schedule next transmission os_setTimedCallback(&sendjob, os_getTime() + sec2osticks(TX_INTERVAL), do_send); + + LowPower.sleep(1000); + break; case EV_LOST_TSYNC: Serial.println(F("EV_LOST_TSYNC")); @@ -276,7 +280,7 @@ void setup() { delay(5000); //while (!Serial); Serial.begin(19200); - Serial.println(F("Starting 1.03")); + Serial.println(F("Starting 1.04")); #ifdef __SAMD21G18A__ Serial.println(F("SAMD21G18A ARM Cortex-M0+ detected")); @@ -310,6 +314,8 @@ void setup() { void loop() { os_runloop_once(); + + }