diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 0000000..101edae --- /dev/null +++ b/.codespellrc @@ -0,0 +1,7 @@ +# See: https://github.com/codespell-project/codespell#using-a-config-file +[codespell] +# In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: +ignore-words-list = , +check-filenames = +check-hidden = +skip = ./.git diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..fa738ec --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,12 @@ +# See: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file +version: 2 + +updates: + # Configure check for outdated GitHub Actions actions in workflows. + # See: https://docs.github.com/en/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot + - package-ecosystem: github-actions + directory: / # Check the repository's workflows under /.github/workflows/ + schedule: + interval: daily + labels: + - "topic: infrastructure" diff --git a/.github/workflows/check-arduino.yml b/.github/workflows/check-arduino.yml new file mode 100644 index 0000000..e818685 --- /dev/null +++ b/.github/workflows/check-arduino.yml @@ -0,0 +1,28 @@ +name: Check Arduino + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + pull_request: + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage caused by new rules added to Arduino Lint. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Arduino Lint + uses: arduino/arduino-lint-action@v2 + with: + compliance: specification + library-manager: update + # Always use this setting for official repositories. Remove for 3rd party projects. + official: true + project-type: library diff --git a/.github/workflows/compile-examples.yml b/.github/workflows/compile-examples.yml new file mode 100644 index 0000000..eabf6be --- /dev/null +++ b/.github/workflows/compile-examples.yml @@ -0,0 +1,114 @@ +name: Compile Examples + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/compile-examples.yml" + - "examples/**" + - "src/**" + pull_request: + paths: + - ".github/workflows/compile-examples.yml" + - "examples/**" + - "src/**" + schedule: + # Run every Tuesday at 8 AM UTC to catch breakage caused by changes to external resources (libraries, platforms). + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + build: + name: ${{ matrix.board.fqbn }} + runs-on: ubuntu-latest + + env: + SKETCHES_REPORTS_PATH: sketches-reports + + strategy: + fail-fast: false + + matrix: + board: + - fqbn: arduino:samd:arduino_zero_edbg + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-arduino_zero_edbg + - fqbn: arduino:samd:mkr1000 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkr1000 + - fqbn: arduino:samd:mkrzero + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrzero + - fqbn: arduino:samd:mkrwifi1010 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrwifi1010 + - fqbn: arduino:samd:mkrfox1200 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrfox1200 + - fqbn: arduino:samd:mkrwan1300 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrwan1300 + - fqbn: arduino:samd:mkrwan1310 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrwan1310 + - fqbn: arduino:samd:mkrgsm1400 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrgsm1400 + - fqbn: arduino:samd:mkrnb1500 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrnb1500 + - fqbn: arduino:samd:mkrvidor4000 + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-mkrvidor4000 + - fqbn: arduino:samd:nano_33_iot + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-nano_33_iot + - fqbn: arduino:samd:tian + platforms: | + - name: arduino:samd + artifact-name-suffix: arduino-samd-tian + sketch-paths: | + - examples/TianStandby + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Compile examples + uses: arduino/compile-sketches@v1 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + fqbn: ${{ matrix.board.fqbn }} + platforms: ${{ matrix.board.platforms }} + libraries: | + # Install the library from the local path. + - source-path: ./ + - name: RTCZero + sketch-paths: | + # Sketches to compile for all boards + - examples/AdcWakeup + - examples/ExternalWakeup + - examples/TimedWakeup + # Board-specific sketches + ${{ matrix.board.sketch-paths }} + enable-deltas-report: true + sketches-report-path: ${{ env.SKETCHES_REPORTS_PATH }} + + - name: Save sketches report as workflow artifact + uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + path: ${{ env.SKETCHES_REPORTS_PATH }} + name: sketches-report-${{ matrix.board.artifact-name-suffix }} diff --git a/.github/workflows/report-size-deltas.yml b/.github/workflows/report-size-deltas.yml new file mode 100644 index 0000000..39e2a0a --- /dev/null +++ b/.github/workflows/report-size-deltas.yml @@ -0,0 +1,24 @@ +name: Report Size Deltas + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/report-size-deltas.yml" + schedule: + # Run at the minimum interval allowed by GitHub Actions. + # Note: GitHub Actions periodically has outages which result in workflow failures. + # In this event, the workflows will start passing again once the service recovers. + - cron: "*/5 * * * *" + workflow_dispatch: + repository_dispatch: + +jobs: + report: + runs-on: ubuntu-latest + steps: + - name: Comment size deltas reports to PRs + uses: arduino/report-size-deltas@v1 + with: + # Regex matching the names of the workflow artifacts created by the "Compile Examples" workflow + sketches-reports-source: ^sketches-report-.+ diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml new file mode 100644 index 0000000..ef7d894 --- /dev/null +++ b/.github/workflows/spell-check.yml @@ -0,0 +1,22 @@ +name: Spell Check + +# See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows +on: + push: + pull_request: + schedule: + # Run every Tuesday at 8 AM UTC to catch new misspelling detections resulting from dictionary updates. + - cron: "0 8 * * TUE" + workflow_dispatch: + repository_dispatch: + +jobs: + spellcheck: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Spell check + uses: codespell-project/actions-codespell@master diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..53a9f54 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,138 @@ +# Source: https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/sync-labels.md +name: Sync Labels + +# See: https://docs.github.com/en/actions/reference/events-that-trigger-workflows +on: + push: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + pull_request: + paths: + - ".github/workflows/sync-labels.ya?ml" + - ".github/label-configuration-files/*.ya?ml" + schedule: + # Run daily at 8 AM UTC to sync with changes to shared label configurations. + - cron: "0 8 * * *" + workflow_dispatch: + repository_dispatch: + +env: + CONFIGURATIONS_FOLDER: .github/label-configuration-files + CONFIGURATIONS_ARTIFACT: label-configuration-files + +jobs: + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download JSON schema for labels configuration file + id: download-schema + uses: carlosperate/download-file-action@v2 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/arduino-tooling-gh-label-configuration-schema.json + location: ${{ runner.temp }}/label-configuration-schema + + - name: Install JSON schema validator + run: | + sudo npm install \ + --global \ + ajv-cli \ + ajv-formats + + - name: Validate local labels configuration + run: | + # See: https://github.com/ajv-validator/ajv-cli#readme + ajv validate \ + --all-errors \ + -c ajv-formats \ + -s "${{ steps.download-schema.outputs.file-path }}" \ + -d "${{ env.CONFIGURATIONS_FOLDER }}/*.{yml,yaml}" + + download: + needs: check + runs-on: ubuntu-latest + + strategy: + matrix: + filename: + # Filenames of the shared configurations to apply to the repository in addition to the local configuration. + # https://github.com/arduino/tooling-project-assets/blob/main/workflow-templates/assets/sync-labels + - universal.yml + + steps: + - name: Download + uses: carlosperate/download-file-action@v2 + with: + file-url: https://raw.githubusercontent.com/arduino/tooling-project-assets/main/workflow-templates/assets/sync-labels/${{ matrix.filename }} + + - name: Pass configuration files to next job via workflow artifact + uses: actions/upload-artifact@v4 + with: + path: | + *.yaml + *.yml + if-no-files-found: error + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + sync: + needs: download + runs-on: ubuntu-latest + + steps: + - name: Set environment variables + run: | + # See: https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-environment-variable + echo "MERGED_CONFIGURATION_PATH=${{ runner.temp }}/labels.yml" >> "$GITHUB_ENV" + + - name: Determine whether to dry run + id: dry-run + if: > + github.event_name == 'pull_request' || + ( + ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' + ) && + github.ref != format('refs/heads/{0}', github.event.repository.default_branch) + ) + run: | + # Use of this flag in the github-label-sync command will cause it to only check the validity of the + # configuration. + echo "::set-output name=flag::--dry-run" + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download configuration files artifact + uses: actions/download-artifact@v4 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + path: ${{ env.CONFIGURATIONS_FOLDER }} + + - name: Remove unneeded artifact + uses: geekyeggo/delete-artifact@v5 + with: + name: ${{ env.CONFIGURATIONS_ARTIFACT }} + + - name: Merge label configuration files + run: | + # Merge all configuration files + shopt -s extglob + cat "${{ env.CONFIGURATIONS_FOLDER }}"/*.@(yml|yaml) > "${{ env.MERGED_CONFIGURATION_PATH }}" + + - name: Install github-label-sync + run: sudo npm install --global github-label-sync + + - name: Sync labels + env: + GITHUB_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # See: https://github.com/Financial-Times/github-label-sync + github-label-sync \ + --labels "${{ env.MERGED_CONFIGURATION_PATH }}" \ + ${{ steps.dry-run.outputs.flag }} \ + ${{ github.repository }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..55bf63e --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# Arduino Low Power library + +[![Check Arduino status](https://github.com/arduino-libraries/ArduinoLowPower/actions/workflows/check-arduino.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoLowPower/actions/workflows/check-arduino.yml) +[![Compile Examples status](https://github.com/arduino-libraries/ArduinoLowPower/actions/workflows/compile-examples.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoLowPower/actions/workflows/compile-examples.yml) +[![Spell Check status](https://github.com/arduino-libraries/ArduinoLowPower/actions/workflows/spell-check.yml/badge.svg)](https://github.com/arduino-libraries/ArduinoLowPower/actions/workflows/spell-check.yml) + +This library allows the use of the low power features of the SAMD21 MCU. This means your battery powered projects will have a longer battery life on boards like [MKRZero](https://store.arduino.cc/usa/arduino-mkrzero), [MKR1000](https://www.arduino.cc/en/Main/ArduinoMKR1000) and [MKRFox1200](https://www.arduino.cc/en/Main/ArduinoBoardMKRFox1200). + +For more information about this library please visit us at + + +# License +Copyright (c) Arduino LLC. 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 diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..d920b2b --- /dev/null +++ b/docs/api.md @@ -0,0 +1,129 @@ +# Ardunio Low Power + +## Methods + +### `LowPower.idle()` + +#### Description + +Puts the MCU in IDLE mode. This mode allows power optimization with the fastest wake-up time. The CPU is stopped. To further reduce power consumption, the user can manually disable the clocking of modules and clock sources. + + +#### Syntax + +``` +LowPower.idle(); +LowPower.idle(milliseconds); +``` + +#### Parameters + +milliseconds: the number of milliseconds to put the board in idle mode. If void the idle mode is used till a wakeup event. + +### `LowPower.sleep()` + +#### Description + +Puts the MCU in sleep mode. The sleep mode allows power optimization with a slower wakeup time. Only the chosen peripherals are on. + + +#### Syntax + +``` +LowPower.sleep(); +LowPower.sleep(milliseconds); +``` + +#### Parameters + +milliseconds: the number of milliseconds to put the board in sleep mode. If void the sleep mode is used till a wakeup event. + +### `LowPower.deepSleep()` + +#### Description + +Puts the MCU in deep sleep mode. The deep sleep mode allows power optimization with the slowest wake-up time. All but the RTC peripherals are stopped. The CPU can be wakeup only using RTC or wakeup on interrupt capable pins. + + +#### Syntax + +``` +LowPower.deepSleep(); +LowPower.deepSleep(milliseconds); +``` + +#### Parameters + +milliseconds: the number of milliseconds to put the board in deep sleep mode. If void the deep sleep mode is used till a wakeup event. + +### `LowPower.attachInterruptWakeup()` + +#### Description + +Indicates the function to call and the conditions for a wakeup event. + + +#### Syntax + +``` +LowPower.attachInterruptWakeup(pin, callback, mode); +``` + +#### Parameters + +pin: the pin used as external wakeup + +callback: the function to call on wakeup + +mode: the transitions to sense on the indicated pin. Can be one between: + +- FALLING +- RISING +- CHANGE + +### `LowPower.CompanionLowPowerCallback()` + +#### Description + +Indicates the function that the on-boad co-processor (Tian only) has to call just before going to sleep. + + +#### Syntax + +LowPower.CompanionLowPowerCallback(callback); + +#### Parameters + +callback: the function to call before going to sleep + +### `LowPower.companionSleep()` + +#### Description + +Puts the on-board co-processor (Tian only) in sleep mode + + +#### Syntax + +LowPower.companionSleep(); + +#### Parameters + +None + +### `LowPower.companionWakeup()` + +#### Description + +Forces the on board co-processor (Tian only) wakeup from sleep mode. + + +#### Syntax + +``` +LowPower.companionWakeup(); +``` + +#### Parameters + +None \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 0000000..237392c --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,17 @@ +# Arduino Low Power Library + +This library allows you to use the low power features of the SAMD21 MCU, which is used for all [MKR family boards](https://store.arduino.cc/collections/mkr-family) and the [Nano 33 IoT board](https://store.arduino.cc/products/arduino-nano-33-iot). + +In these pages, the term companion chip is used. This term refers to a board co-processor like the MIPS processor on the [Arduino Tian](https://docs.arduino.cc/retired/boards/arduino-tian). + +To use this library: + +``` +#include "" +``` + +Examples: + +- [ExternalWakeup](https://github.com/arduino-libraries/ArduinoLowPower/blob/master/examples/ExternalWakeup/ExternalWakeup.ino) : Demonstrates how to wake your board from an external source like a button. +- [TianStandby](https://github.com/arduino-libraries/ArduinoLowPower/blob/master/examples/TianStandby/TianStandby.ino) : Demonstrates how to put a Tian in standby +- [TimedWakeup](https://github.com/arduino-libraries/ArduinoLowPower/blob/master/examples/TimedWakeup/TimedWakeup.ino) : Demonstrates how to put in sleep your board for a certain amount of time \ No newline at end of file diff --git a/examples/AdcWakeup/AdcWakeup.ino b/examples/AdcWakeup/AdcWakeup.ino new file mode 100644 index 0000000..24cafe2 --- /dev/null +++ b/examples/AdcWakeup/AdcWakeup.ino @@ -0,0 +1,61 @@ +/* + AdcWakeup + + This sketch demonstrates the usage of the ADC to wakeup a chip in sleep mode. + Sleep modes allow a significant drop in the power usage of a board while it does nothing waiting for an event to happen. Battery powered application can take advantage of these modes to enhance battery life significantly. + + In this sketch, changing the voltage on pin A0 will wake up the board. You can test this by connecting a potentiometer between VCC, A0, and GND. + Please note that, if the processor is sleeping, a new sketch can't be uploaded. To overcome this, manually reset the board (usually with a single or double tap to the RESET button) + + This example code is in the public domain. +*/ + +#include "ArduinoLowPower.h" + +// Blink sequence number +// Declare it volatile since it's incremented inside an interrupt +volatile int repetitions = 1; + +// Pin used to trigger a wakeup +const int pin = A0; +// How sensitive to be to changes in voltage +const int margin = 10; + +void setup() { + pinMode(LED_BUILTIN, OUTPUT); + pinMode(pin, INPUT); +} + +void loop() { + for (int i = 0; i < repetitions; i++) { + digitalWrite(LED_BUILTIN, HIGH); + delay(500); + digitalWrite(LED_BUILTIN, LOW); + delay(500); + } + + // Read the voltage at the ADC pin + int value = analogRead(pin); + + // Define a window around that value + uint16_t lo = max(value - margin, 0); + uint16_t hi = min(value + margin, UINT16_MAX); + + // Attach an ADC interrupt on pin A0, calling repetitionsIncrease when the voltage is outside the given range. + // This should be called immediately before LowPower.sleep() because it reconfigures the ADC internally. + LowPower.attachAdcInterrupt(pin, repetitionsIncrease, ADC_INT_OUTSIDE, lo, hi); + + // Triggers an infinite sleep (the device will be woken up only by the registered wakeup sources) + // The power consumption of the chip will drop consistently + LowPower.sleep(); + + // Detach the ADC interrupt. This should be called immediately after LowPower.sleep() because it restores the ADC configuration after waking up. + LowPower.detachAdcInterrupt(); +} + +void repetitionsIncrease() { + // This function will be called once on device wakeup + // You can do some little operations here (like changing variables which will be used in the loop) + // Remember to avoid calling delay() and long running functions since this functions executes in interrupt context + repetitions ++; +} \ No newline at end of file diff --git a/examples/ExternalWakeup/ExternalWakeup.ino b/examples/ExternalWakeup/ExternalWakeup.ino index 09a7610..6151258 100644 --- a/examples/ExternalWakeup/ExternalWakeup.ino +++ b/examples/ExternalWakeup/ExternalWakeup.ino @@ -21,6 +21,8 @@ const int pin = 8; void setup() { pinMode(LED_BUILTIN, OUTPUT); + // Set pin 8 as INPUT_PULLUP to avoid spurious wakeup + pinMode(pin, INPUT_PULLUP); // Attach a wakeup interrupt on pin 8, calling repetitionsIncrease when the device is woken up LowPower.attachInterruptWakeup(pin, repetitionsIncrease, CHANGE); } diff --git a/examples/TianStandby/TianStandby.ino b/examples/TianStandby/TianStandby.ino index e4f4d56..7fc18c4 100644 --- a/examples/TianStandby/TianStandby.ino +++ b/examples/TianStandby/TianStandby.ino @@ -1,12 +1,12 @@ /* TianStandby - This sketch demonstrates the usage of SAMD chip to furtherly reduce the power usage of Tian + This sketch demonstrates the usage of SAMD chip to further reduce the power usage of the Tian board. This method can be applied to any board with companion chips which expose a method (via direct pin interrupt or via a command) to enter and exit standby. - Sleep modes allow a significant drop in the power usage of a board while it does nothing waiting for an event to happen. Battery powered application can take advantage of these modes to enhance battery life significantly. + Sleep modes allow a significant drop in the power usage of a board while it does nothing waiting for an event to happen. Battery powered applications can take advantage of these modes to enhance battery life significantly. - In this sketch, the internal RTC of SAMD chip will wake up the processor every 20 seconds. + In this sketch, the internal RTC of the SAMD chip will wake up the processor every 20 seconds. Before going to sleep, the SAMD chip tells the MIPS CPU to standby too. Please note that, if the processor is sleeping, a new sketch can't be uploaded. To overcome this, manually reset the board (usually with a single or double tap to the RESET button) @@ -17,14 +17,14 @@ #define MIPS_PIN 32 -void MIPS_PM(bool sleep) { +void MipsPM(bool sleep) { pinMode(MIPS_PIN, OUTPUT); digitalWrite(MIPS_PIN, sleep ? LOW: HIGH); } void setup() { pinMode(LED_BUILTIN, OUTPUT); - LowPower.companionLowPowerCallback(MIPS_PM); + LowPower.companionLowPowerCallback(MipsPM); // Uncomment this function if you wish to attach function dummy when RTC wakes up the chip LowPower.attachInterruptWakeup(RTC_ALARM_WAKEUP, onWakeup, CHANGE); } diff --git a/keywords.txt b/keywords.txt index 3a02803..95fa6c9 100644 --- a/keywords.txt +++ b/keywords.txt @@ -1,5 +1,5 @@ ####################################### -# Syntax Coloring Map For Energy Saving +# Syntax Coloring Map For Arduino Low Power ####################################### ####################################### @@ -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 diff --git a/library.properties b/library.properties index aabaec8..a585142 100644 --- a/library.properties +++ b/library.properties @@ -1,9 +1,10 @@ name=Arduino Low Power -version=1.0.0 +version=1.2.2 author=Arduino -maintainer=Arduino LLC -sentence=Power save primitives features for SAMD 32bit boards +maintainer=Arduino +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 +url=https://www.arduino.cc/libraries/ArduinoLowPower architectures=samd +depends=RTCZero diff --git a/src/ArduinoLowPower.cpp b/src/ArduinoLowPower.cpp deleted file mode 100644 index 466d763..0000000 --- a/src/ArduinoLowPower.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "ArduinoLowPower.h" -#include "WInterrupts.h" - -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() { - USBDevice.standby(); - SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; - __DSB(); - __WFI(); -} - -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, 0); - } - - uint32_t now = rtc.getEpoch(); - rtc.setAlarmEpoch(now + millis/1000); - rtc.enableAlarm(rtc.MATCH_HHMMSS); -} - -void ArduinoLowPowerClass::attachInterruptWakeup(uint32_t pin, voidFuncPtr callback, uint32_t 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); - - // 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); - - // Enable wakeup capability on pin in case being used during sleep - EIC->WAKEUP.reg |= (1 << in); - - /* 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; -} - -ArduinoLowPowerClass LowPower; \ No newline at end of file diff --git a/src/ArduinoLowPower.h b/src/ArduinoLowPower.h index 06f4a4e..8ac559a 100644 --- a/src/ArduinoLowPower.h +++ b/src/ArduinoLowPower.h @@ -11,16 +11,40 @@ #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 #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); @@ -41,7 +65,7 @@ class ArduinoLowPowerClass { deepSleep((uint32_t)millis); } - void attachInterruptWakeup(uint32_t pin, voidFuncPtr callback, uint32_t mode); + void attachInterruptWakeup(uint32_t pin, voidFuncPtr callback, irq_mode mode); #ifdef BOARD_HAS_COMPANION_CHIP void companionLowPowerCallback(onOffFuncPtr callback) { @@ -55,9 +79,23 @@ 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 + + #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 diff --git a/src/samd/ArduinoLowPower.cpp b/src/samd/ArduinoLowPower.cpp new file mode 100644 index 0000000..1ba0f7c --- /dev/null +++ b/src/samd/ArduinoLowPower.cpp @@ -0,0 +1,218 @@ +#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; + } + // 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