├── utils ├── restyle.sh └── astyle_library.conf ├── .codespellrc ├── .github ├── workflows │ ├── auto-github-actions.yml │ ├── report-size-deltas.yml │ ├── spell-check.yml │ └── check-arduino.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── stale.yml ├── .gitignore ├── examples ├── multiFileProject │ ├── multiFileProject.cpp │ ├── multiFileProject.h │ └── multiFileProject.ino ├── ISR_Changing_PWM │ └── ISR_Changing_PWM.ino ├── ISR_Modify_PWM │ └── ISR_Modify_PWM.ino ├── ISR_16_PWMs_Array_Simple │ └── ISR_16_PWMs_Array_Simple.ino ├── ISR_16_PWMs_Array │ └── ISR_16_PWMs_Array.ino └── ISR_16_PWMs_Array_Complex │ └── ISR_16_PWMs_Array_Complex.ino ├── LICENSE ├── library.properties ├── keywords.txt ├── library.json ├── CONTRIBUTING.md ├── src ├── ESP32_PWM.h ├── ESP32_PWM_ISR.h ├── PWM_Generic_Debug.h ├── ESP32_PWM_ISR.hpp ├── ESP32_PWM_ISR_Impl.h └── ESP32_PWM.hpp ├── changelog.md ├── platformio └── platformio.ini └── README.md /utils/restyle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for dir in . ; do 4 | find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.ino" \) -exec astyle --suffix=none --options=./utils/astyle_library.conf \{\} \; 5 | done 6 | 7 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | # See: https://github.com/codespell-project/codespell#using-a-config-file 2 | [codespell] 3 | # In the event of a false positive, add the problematic word, in all lowercase, to a comma-separated list here: 4 | ignore-words-list = , 5 | check-filenames = 6 | check-hidden = 7 | skip = ./.git,./src,./examples,./Packages_Patches,./LibraryPatches 8 | -------------------------------------------------------------------------------- /.github/workflows/auto-github-actions.yml: -------------------------------------------------------------------------------- 1 | name: auto-github-actions 2 | on: [push] 3 | jobs: 4 | check-bats-version: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v3 8 | - uses: actions/setup-node@v3 9 | with: 10 | node-version: '14' 11 | - run: npm install -g bats 12 | - run: bats -v 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /.github/workflows/report-size-deltas.yml: -------------------------------------------------------------------------------- 1 | name: Report Size Deltas 2 | 3 | on: 4 | schedule: 5 | - cron: '*/5 * * * *' 6 | 7 | jobs: 8 | report: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Comment size deltas reports to PRs 13 | uses: arduino/report-size-deltas@v1 14 | with: 15 | # The name of the workflow artifact created by the "Compile Examples" workflow 16 | sketches-reports-source: sketches-reports 17 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See: https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file 2 | version: 2 3 | 4 | updates: 5 | # Configure check for outdated GitHub Actions actions in workflows. 6 | # See: https://docs.github.com/en/github/administering-a-repository/keeping-your-actions-up-to-date-with-dependabot 7 | - package-ecosystem: github-actions 8 | directory: / # Check the repository's workflows under /.github/workflows/ 9 | schedule: 10 | interval: daily 11 | -------------------------------------------------------------------------------- /.github/workflows/spell-check.yml: -------------------------------------------------------------------------------- 1 | name: Spell Check 2 | 3 | on: 4 | pull_request: 5 | push: 6 | schedule: 7 | # run every Tuesday at 3 AM UTC 8 | - cron: "0 3 * * 2" 9 | workflow_dispatch: 10 | repository_dispatch: 11 | 12 | jobs: 13 | spellcheck: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v3 19 | 20 | # See: https://github.com/codespell-project/actions-codespell/blob/master/README.md 21 | - name: Spell check 22 | uses: codespell-project/actions-codespell@master 23 | -------------------------------------------------------------------------------- /examples/multiFileProject/multiFileProject.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | multiFileProject.cpp 3 | 4 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 5 | Written by Khoi Hoang 6 | 7 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 8 | Licensed under MIT license 9 | *****************************************************************************************************************************/ 10 | 11 | // To demo how to include files in multi-file Projects 12 | 13 | #include "multiFileProject.h" 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Is your feature request related to a problem? Please describe. 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | ### Describe the solution you'd like 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | ### Describe alternatives you've considered 19 | 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | ### Additional context 23 | 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/workflows/check-arduino.yml: -------------------------------------------------------------------------------- 1 | name: Check Arduino 2 | 3 | # See: https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows 4 | on: 5 | push: 6 | pull_request: 7 | schedule: 8 | # Run every Tuesday at 8 AM UTC to catch breakage caused by new rules added to Arduino Lint. 9 | - cron: "0 8 * * TUE" 10 | workflow_dispatch: 11 | repository_dispatch: 12 | 13 | jobs: 14 | lint: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout repository 19 | uses: actions/checkout@v3 20 | 21 | - name: Arduino Lint 22 | uses: arduino/arduino-lint-action@v1 23 | with: 24 | compliance: specification 25 | library-manager: update 26 | # Always use this setting for official repositories. Remove for 3rd party projects. 27 | official: true 28 | project-type: library 29 | -------------------------------------------------------------------------------- /examples/multiFileProject/multiFileProject.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | multiFileProject.h 3 | 4 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 5 | Written by Khoi Hoang 6 | 7 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 8 | Licensed under MIT license 9 | *****************************************************************************************************************************/ 10 | 11 | // To demo how to include files in multi-file Projects 12 | 13 | #pragma once 14 | 15 | #define USING_MICROS_RESOLUTION true //false 16 | 17 | // Default is true, uncomment to false 18 | //#define CHANGING_PWM_END_OF_CYCLE false 19 | 20 | // Can be included as many times as necessary, without `Multiple Definitions` Linker Error 21 | #include "ESP32_PWM.hpp" 22 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-stale - https://github.com/probot/stale 2 | 3 | daysUntilStale: 60 4 | daysUntilClose: 14 5 | limitPerRun: 30 6 | staleLabel: stale 7 | exemptLabels: 8 | - pinned 9 | - security 10 | - "to be implemented" 11 | - "for reference" 12 | - "move to PR" 13 | - "enhancement" 14 | 15 | only: issues 16 | onlyLabels: [] 17 | exemptProjects: false 18 | exemptMilestones: false 19 | exemptAssignees: false 20 | 21 | markComment: > 22 | [STALE_SET] This issue has been automatically marked as stale because it has not had 23 | recent activity. It will be closed in 14 days if no further activity occurs. Thank you 24 | for your contributions. 25 | 26 | unmarkComment: > 27 | [STALE_CLR] This issue has been removed from the stale queue. Please ensure activity to keep it opening the future. 28 | 29 | closeComment: > 30 | [STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions. 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Khoi Hoang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=ESP32_PWM 2 | version=1.3.3 3 | author=Khoi Hoang 4 | maintainer=Khoi Hoang 5 | sentence=This library enables you to use Interrupt from Hardware Timers on an ESP32, ESP32_S2, ESP32_S3 or ESP32_C3-based board to create and output PWM to pins. 6 | paragraph=It now supports 16 ISR-based synchronized PWM channels, while consuming only 1 Hardware Timer. PWM interval can be very long (uint32_t millisecs). The most important feature is they are ISR-based PWM channels. Therefore, their executions are not blocked by bad-behaving functions or tasks. This important feature is absolutely necessary for mission-critical tasks. These hardware PWM channels, using interrupt, still work even if other functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software timers using millis() or micros(). That is necessary if you need to measure some data requiring better accuracy. Now you can change the PWM settings on-the-fly, 7 | category=Device Control 8 | url=https://github.com/khoih-prog/ESP32_PWM 9 | architectures=esp32 10 | repository=https://github.com/khoih-prog/ESP32_PWM 11 | license=MIT 12 | includes=ESP32_PWM.h,ESP32_PWM.hpp 13 | -------------------------------------------------------------------------------- /utils/astyle_library.conf: -------------------------------------------------------------------------------- 1 | # Code formatting rules for Arduino libraries, modified from for KH libraries: 2 | # 3 | # https://github.com/arduino/Arduino/blob/master/build/shared/examples_formatter.conf 4 | # 5 | 6 | # astyle --style=allman -s2 -t2 -C -S -xW -Y -M120 -f -p -xg -H -xb -c --xC120 -xL *.h *.cpp *.ino 7 | 8 | --mode=c 9 | --lineend=linux 10 | --style=allman 11 | 12 | # -r or -R 13 | #--recursive 14 | 15 | # -c => Converts tabs into spaces 16 | convert-tabs 17 | 18 | # -s2 => 2 spaces indentation 19 | --indent=spaces=2 20 | 21 | # -t2 => tab =2 spaces 22 | #--indent=tab=2 23 | 24 | # -C 25 | --indent-classes 26 | 27 | # -S 28 | --indent-switches 29 | 30 | # -xW 31 | --indent-preproc-block 32 | 33 | # -Y => indent classes, switches (and cases), comments starting at column 1 34 | --indent-col1-comments 35 | 36 | # -M120 => maximum of 120 spaces to indent a continuation line 37 | --max-continuation-indent=120 38 | 39 | # -xC120 => max‑code‑length will break a line if the code exceeds # characters 40 | --max-code-length=120 41 | 42 | # -f => 43 | --break-blocks 44 | 45 | # -p => put a space around operators 46 | --pad-oper 47 | 48 | # -xg => Insert space padding after commas 49 | --pad-comma 50 | 51 | # -H => put a space after if/for/while 52 | pad-header 53 | 54 | # -xb => Break one line headers (e.g. if/for/while) 55 | --break-one-line-headers 56 | 57 | # -c => Converts tabs into spaces 58 | #--convert-tabs 59 | 60 | # if you like one-liners, keep them 61 | #keep-one-line-statements 62 | 63 | # -xV 64 | --attach-closing-while 65 | 66 | #unpad-paren 67 | 68 | # -xp 69 | remove-comment-prefix 70 | 71 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Datatypes (KEYWORD1) 3 | ####################################### 4 | 5 | ESP32TimerInterrupt KEYWORD1 6 | ESP32Timer KEYWORD1 7 | ESP32_PWM_ISR KEYWORD1 8 | 9 | ####################################### 10 | # Methods and Functions (KEYWORD2) 11 | ####################################### 12 | 13 | ############################# 14 | # class ESP32TimerInterrupt 15 | ############################# 16 | 17 | setFrequency KEYWORD2 18 | setInterval KEYWORD2 19 | attachInterrupt KEYWORD2 20 | attachInterruptInterval KEYWORD2 21 | detachInterrupt KEYWORD2 22 | disableTimer KEYWORD2 23 | reattachInterrupt KEYWORD2 24 | enableTimer KEYWORD2 25 | stopTimer KEYWORD2 26 | restartTimer KEYWORD2 27 | 28 | ############################# 29 | # class ESP32_PWM_ISR 30 | ############################# 31 | 32 | init KEYWORD2 33 | run KEYWORD2 34 | setPWM KEYWORD2 35 | setPWM_Period KEYWORD2 36 | modifyPWMChannel KEYWORD2 37 | modifyPWMChannel_Period KEYWORD2 38 | deleteChannel KEYWORD2 39 | restartChannel KEYWORD2 40 | isEnabled KEYWORD2 41 | enable KEYWORD2 42 | disable KEYWORD2 43 | enableAll KEYWORD2 44 | disableAll KEYWORD2 45 | toggle KEYWORD2 46 | getnumChannels KEYWORD2 47 | getNumAvailablePWMChannels KEYWORD2 48 | 49 | ####################################### 50 | # Constants (LITERAL1) 51 | ####################################### 52 | 53 | ESP32_PWM_VERSION LITERAL1 54 | ESP32_PWM_VERSION_MAJOR LITERAL1 55 | ESP32_PWM_VERSION_MINOR LITERAL1 56 | ESP32_PWM_VERSION_PATCH LITERAL1 57 | ESP32_PWM_VERSION_INT LITERAL1 58 | 59 | USING_MICROS_RESOLUTION LITERAL1 60 | CHANGING_PWM_END_OF_CYCLE LITERAL1 61 | -------------------------------------------------------------------------------- /library.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ESP32_PWM", 3 | "version": "1.3.3", 4 | "keywords": "timing, device, control, pwm, timer, interrupt, hardware, isr, isr-based, hardware-timer, isr-timer, isr-based-timer, mission-critical, accuracy, precise, non-blocking, esp32, esp32-s2, esp32-c3, esp32-s3, synchronized-PWM, on-the-fly", 5 | "description": "This library enables you to use Interrupt from Hardware Timers on an ESP32, ESP32_S2, ESP32_S3 or ESP32_C3-based board to create and output PWM to pins. It now supports 16 ISR-based synchronized PWM channels, while consuming only 1 Hardware Timer. PWM interval can be very long (uint32_t millisecs). The most important feature is they're ISR-based PWM channels. Therefore, their executions are not blocked by bad-behaving functions or tasks. This important feature is absolutely necessary for mission-critical tasks. These hardware PWM channels, using interrupt, still work even if other functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software timers using millis() or micros(). That's necessary if you need to measure some data requiring better accuracy. Now you can change the PWM settings on-the-fly", 6 | "authors": 7 | { 8 | "name": "Khoi Hoang", 9 | "url": "https://github.com/khoih-prog", 10 | "maintainer": true 11 | }, 12 | "repository": 13 | { 14 | "type": "git", 15 | "url": "https://github.com/khoih-prog/ESP32_PWM.git" 16 | }, 17 | "homepage": "https://github.com/khoih-prog/ESP32_PWM", 18 | "export": { 19 | "exclude": [ 20 | "linux", 21 | "extras", 22 | "tests" 23 | ] 24 | }, 25 | "license": "MIT", 26 | "frameworks": "*", 27 | "platforms": "espressif32", 28 | "examples": "examples/*/*/*.ino", 29 | "headers": ["ESP32_PWM.h", "ESP32_PWM.hpp"] 30 | } 31 | -------------------------------------------------------------------------------- /examples/multiFileProject/multiFileProject.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | multiFileProject.ino 3 | 4 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 5 | Written by Khoi Hoang 6 | 7 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 8 | Licensed under MIT license 9 | *****************************************************************************************************************************/ 10 | 11 | // To demo how to include files in multi-file Projects 12 | 13 | #if !defined( ESP32 ) 14 | #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. 15 | #endif 16 | 17 | #define ESP32_PWM_VERSION_MIN_TARGET "ESP32_PWM v1.3.1" 18 | #define ESP32_PWM_VERSION_MIN 1003001 19 | 20 | // These define's must be placed at the beginning before #include "ESP32_PWM.h" 21 | // _PWM_LOGLEVEL_ from 0 to 4 22 | // Don't define _PWM_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system. 23 | #define _PWM_LOGLEVEL_ 3 24 | 25 | #include "multiFileProject.h" 26 | 27 | // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error 28 | #include "ESP32_PWM.h" 29 | 30 | void setup() 31 | { 32 | Serial.begin(115200); 33 | while (!Serial); 34 | 35 | delay(300); 36 | 37 | Serial.println("\nStart multiFileProject"); 38 | Serial.println(ESP32_PWM_VERSION); 39 | 40 | #if defined(ESP32_PWM_VERSION_MIN) 41 | if (ESP32_PWM_VERSION_INT < ESP32_PWM_VERSION_MIN) 42 | { 43 | Serial.print("Warning. Must use this example on Version equal or later than : "); 44 | Serial.println(ESP32_PWM_VERSION_MIN_TARGET); 45 | } 46 | #endif 47 | } 48 | 49 | void loop() 50 | { 51 | // put your main code here, to run repeatedly: 52 | } 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing to ESP32_PWM 2 | 3 | ### Reporting Bugs 4 | 5 | Please report bugs in [ESP32_PWM Issues](https://github.com/khoih-prog/ESP32_PWM/issues) if you find them. 6 | 7 | However, before reporting a bug please check through the following: 8 | 9 | * [Existing Open Issues](https://github.com/khoih-prog/ESP32_PWM/issues) - someone might have already encountered this. 10 | 11 | If you don't find anything, please [open a new issue](https://github.com/khoih-prog/ESP32_PWM/issues/new). 12 | 13 | ### How to submit a bug report 14 | 15 | Please ensure to specify the following: 16 | 17 | * Arduino IDE version (e.g. 1.8.19) or Platform.io version 18 | * `ESP32` Core Version (e.g. ESP32 core v2.0.5) 19 | * `ESP32` Board type (e.g. ESP32_DEV Module, etc.) 20 | * `ESP32-S2` Board type (e.g. ESP32S2_DEV Module, ESP32_S2_Saola, etc.) 21 | * `ESP32_S3` Board type (e.g. ESP32S3_DEV, ESP32_S3_BOX, UM TINYS3, UM PROS3, UM FEATHERS3, etc.) 22 | * `ESP32-C3` Board type (e.g. ESP32C3_DEV Module, etc.) 23 | * Contextual information (e.g. what you were trying to achieve) 24 | * Simplest possible steps to reproduce 25 | * Anything that might be relevant in your opinion, such as: 26 | * Operating system (Windows, Ubuntu, etc.) and the output of `uname -a` 27 | * Network configuration 28 | 29 | 30 | ### Example 31 | 32 | ``` 33 | Arduino IDE version: 1.8.19 34 | ESP32 core v2.0.5 35 | ESP32S3_DEV Module 36 | OS: Ubuntu 20.04 LTS 37 | Linux xy-Inspiron-3593 5.15.0-53-generic #59~20.04.1-Ubuntu SMP Thu Oct 20 15:10:22 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux 38 | 39 | Context: 40 | I encountered a crash while using this library 41 | Steps to reproduce: 42 | 1. ... 43 | 2. ... 44 | 3. ... 45 | 4. ... 46 | ``` 47 | 48 | ### Additional context 49 | 50 | Add any other context about the problem here. 51 | 52 | --- 53 | 54 | ### Sending Feature Requests 55 | 56 | Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. 57 | 58 | There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/ESP32_PWM/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. 59 | 60 | --- 61 | 62 | ### Sending Pull Requests 63 | 64 | Pull Requests with changes and fixes are also welcome! 65 | 66 | Please use the `astyle` to reformat the updated library code as follows (demo for Ubuntu Linux) 67 | 68 | 1. Change directory to the library GitHub 69 | 70 | ``` 71 | xy@xy-Inspiron-3593:~$ cd Arduino/xy/ESP32_PWM_GitHub/ 72 | xy@xy-Inspiron-3593:~/Arduino/xy/ESP32_PWM_GitHub$ 73 | ``` 74 | 75 | 2. Issue astyle command 76 | 77 | ``` 78 | xy@xy-Inspiron-3593:~/Arduino/xy/ESP32_PWM_GitHub$ bash utils/restyle.sh 79 | ``` 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/ESP32_PWM.h: -------------------------------------------------------------------------------- 1 | 2 | /**************************************************************************************************************************** 3 | ESP32_PWM.h 4 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 5 | Written by Khoi Hoang 6 | 7 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 8 | Licensed under MIT license 9 | 10 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 11 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 12 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 13 | 14 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 15 | The timer counters can be configured to count up or down and support automatic reload and software reload. 16 | They can also generate alarms when they reach a specific value, defined by the software. 17 | The value of the counter can be read by the software program. 18 | 19 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 20 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 21 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 22 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 23 | This important feature is absolutely necessary for mission-critical tasks. 24 | 25 | Version: 1.3.3 26 | 27 | Version Modified By Date Comments 28 | ------- ----------- ---------- ----------- 29 | 1.0.0 K Hoang 20/09/2021 Initial coding for ESP32, ESP32_S2, ESP32_C3 boards with ESP32 core v2.0.0+ 30 | 1.0.1 K Hoang 21/09/2021 Fix bug. Ading PWM end-of-duty-cycle callback function. Improve examples 31 | 1.1.0 K Hoang 06/11/2021 Add functions to modify PWM settings on-the-fly 32 | 1.1.1 K Hoang 09/11/2021 Fix examples to not use GPIO1/TX0 for core v2.0.1+ 33 | 1.2.0 K Hoang 29/01/2022 Fix multiple-definitions linker error. Improve accuracy. Fix bug 34 | 1.2.1 K Hoang 30/01/2022 DutyCycle to be updated at the end current PWM period 35 | 1.2.2 K Hoang 01/02/2022 Use float for DutyCycle and Freq, uint32_t for period. Optimize code 36 | 1.3.0 K Hoang 12/02/2022 Add support to new ESP32-S3 37 | 1.3.1 K Hoang 04/03/2022 Fix `DutyCycle` and `New Period` display bugs. Display warning only when debug level > 3 38 | 1.3.2 K Hoang 09/05/2022 Remove crashing PIN_D24 from examples 39 | 1.3.3 K Hoang 16/06/2022 Add support to new Adafruit boards 40 | *****************************************************************************************************************************/ 41 | 42 | #pragma once 43 | 44 | #ifndef ESP32_PWM_H 45 | #define ESP32_PWM_H 46 | 47 | #include "ESP32_PWM.hpp" 48 | #include "ESP32_PWM_ISR.h" 49 | 50 | #endif // ESP32_PWM_H 51 | 52 | -------------------------------------------------------------------------------- /src/ESP32_PWM_ISR.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | ESP32_PWM_ISR.h 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | 24 | Version: 1.3.3 25 | 26 | Version Modified By Date Comments 27 | ------- ----------- ---------- ----------- 28 | 1.0.0 K Hoang 20/09/2021 Initial coding for ESP32, ESP32_S2, ESP32_C3 boards with ESP32 core v2.0.0+ 29 | 1.0.1 K Hoang 21/09/2021 Fix bug. Ading PWM end-of-duty-cycle callback function. Improve examples 30 | 1.1.0 K Hoang 06/11/2021 Add functions to modify PWM settings on-the-fly 31 | 1.1.1 K Hoang 09/11/2021 Fix examples to not use GPIO1/TX0 for core v2.0.1+ 32 | 1.2.0 K Hoang 29/01/2022 Fix multiple-definitions linker error. Improve accuracy. Fix bug 33 | 1.2.1 K Hoang 30/01/2022 DutyCycle to be updated at the end current PWM period 34 | 1.2.2 K Hoang 01/02/2022 Use float for DutyCycle and Freq, uint32_t for period. Optimize code 35 | 1.3.0 K Hoang 12/02/2022 Add support to new ESP32-S3 36 | 1.3.1 K Hoang 04/03/2022 Fix `DutyCycle` and `New Period` display bugs. Display warning only when debug level > 3 37 | 1.3.2 K Hoang 09/05/2022 Remove crashing PIN_D24 from examples 38 | 1.3.3 K Hoang 16/06/2022 Add support to new Adafruit boards 39 | *****************************************************************************************************************************/ 40 | 41 | #pragma once 42 | 43 | #ifndef PWM_ISR_GENERIC_H 44 | #define PWM_ISR_GENERIC_H 45 | 46 | #include "ESP32_PWM_ISR.hpp" 47 | #include "ESP32_PWM_ISR_Impl.h" 48 | 49 | #endif // PWM_ISR_GENERIC_H 50 | 51 | 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ### Describe the bug 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | ### Steps to Reproduce 15 | 16 | Steps to reproduce the behavior. Including the [MRE](https://stackoverflow.com/help/minimal-reproducible-example) sketches 17 | 18 | ### Expected behavior 19 | 20 | A clear and concise description of what you expected to happen. 21 | 22 | ### Actual behavior 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | ### Debug and AT-command log (if applicable) 27 | 28 | A clear and concise description of what you expected to happen. 29 | 30 | ### Screenshots 31 | 32 | If applicable, add screenshots to help explain your problem. 33 | 34 | ### Information 35 | 36 | Please ensure to specify the following: 37 | 38 | * Arduino IDE version (e.g. 1.8.19) or Platform.io version 39 | * `ESP32` Core Version (e.g. ESP32 core v2.0.5) 40 | * `ESP32` Board type (e.g. ESP32_DEV Module, etc.) 41 | * `ESP32-S2` Board type (e.g. ESP32S2_DEV Module, ESP32_S2_Saola, etc.) 42 | * `ESP32_S3` Board type (e.g. ESP32S3_DEV, ESP32_S3_BOX, UM TINYS3, UM PROS3, UM FEATHERS3, etc.) 43 | * `ESP32-C3` Board type (e.g. ESP32C3_DEV Module, etc.) 44 | * Contextual information (e.g. what you were trying to achieve) 45 | * Simplest possible steps to reproduce 46 | * Anything that might be relevant in your opinion, such as: 47 | * Operating system (Windows, Ubuntu, etc.) and the output of `uname -a` 48 | * Network configuration 49 | 50 | 51 | ### Example 52 | 53 | ``` 54 | Arduino IDE version: 1.8.19 55 | ESP32 core v2.0.5 56 | ESP32S3_DEV Module 57 | OS: Ubuntu 20.04 LTS 58 | Linux xy-Inspiron-3593 5.15.0-53-generic #59~20.04.1-Ubuntu SMP Thu Oct 20 15:10:22 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux 59 | 60 | Context: 61 | I encountered a crash while using this library 62 | Steps to reproduce: 63 | 1. ... 64 | 2. ... 65 | 3. ... 66 | 4. ... 67 | ``` 68 | 69 | ### Additional context 70 | 71 | Add any other context about the problem here. 72 | 73 | --- 74 | 75 | ### Sending Feature Requests 76 | 77 | Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. 78 | 79 | There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/ESP32_PWM/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. 80 | 81 | --- 82 | 83 | ### Sending Pull Requests 84 | 85 | Pull Requests with changes and fixes are also welcome! 86 | 87 | Please use the `astyle` to reformat the updated library code as follows (demo for Ubuntu Linux) 88 | 89 | 1. Change directory to the library GitHub 90 | 91 | ``` 92 | xy@xy-Inspiron-3593:~$ cd Arduino/xy/ESP32_PWM_GitHub/ 93 | xy@xy-Inspiron-3593:~/Arduino/xy/ESP32_PWM_GitHub$ 94 | ``` 95 | 96 | 2. Issue astyle command 97 | 98 | ``` 99 | xy@xy-Inspiron-3593:~/Arduino/xy/ESP32_PWM_GitHub$ bash utils/restyle.sh 100 | ``` 101 | 102 | 103 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # ESP32_PWM Library 2 | 3 | [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP32_PWM.svg?)](https://www.ardu-badge.com/ESP32_PWM) 4 | [![GitHub release](https://img.shields.io/github/release/khoih-prog/ESP32_PWM.svg)](https://github.com/khoih-prog/ESP32_PWM/releases) 5 | [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/khoih-prog/ESP32_PWM/blob/main/LICENSE) 6 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) 7 | [![GitHub issues](https://img.shields.io/github/issues/khoih-prog/ESP32_PWM.svg)](http://github.com/khoih-prog/ESP32_PWM/issues) 8 | 9 | 10 | Donate to my libraries using BuyMeACoffee 11 | 12 | 13 | --- 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | * [Changelog](#changelog) 19 | * [Releases v1.3.3](#releases-v133) 20 | * [Releases v1.3.2](#releases-v132) 21 | * [Releases v1.3.1](#releases-v131) 22 | * [Releases v1.3.0](#releases-v130) 23 | * [Releases v1.2.2](#releases-v122) 24 | * [Releases v1.2.1](#releases-v121) 25 | * [Releases v1.2.0](#releases-v120) 26 | * [Releases v1.1.1](#releases-v111) 27 | * [Releases v1.1.0](#releases-v110) 28 | * [Releases v1.0.1](#releases-v101) 29 | * [Releases v1.0.0](#releases-v100) 30 | 31 | --- 32 | --- 33 | 34 | ## Changelog 35 | 36 | ### Releases v1.3.3 37 | 38 | 1. Add support to new Adafruit boards such as QTPY_ESP32S2, FEATHER_ESP32S3_NOPSRAM and QTPY_ESP32S3_NOPSRAM 39 | 40 | ### Releases v1.3.2 41 | 42 | 1. Remove crashing `PIN_D24` from examples 43 | 44 | 45 | ### Releases v1.3.1 46 | 47 | 1. Fix `DutyCycle` bug. Check [float precisison of DutyCycle only sometimes working #3](https://github.com/khoih-prog/SAMD_Slow_PWM/issues/3) 48 | 2. Fix `New Period` display bug. Check [random dropouts #4](https://github.com/khoih-prog/SAMD_Slow_PWM/issues/4) 49 | 3. Update examples 50 | 51 | ### Releases v1.3.0 52 | 53 | 1. Add support to new `ESP32-S3` (ESP32S3_DEV, ESP32_S3_BOX, UM TINYS3, UM PROS3, UM FEATHERS3, etc.) 54 | 2. Modify examples accordingly 55 | 56 | ### Releases v1.2.2 57 | 58 | 1. Use `float` for `DutyCycle` and `Freq`, `uint32_t` for `period`. 59 | 2. Optimize code by not calculation in ISR 60 | 61 | ### Releases v1.2.1 62 | 63 | 1. DutyCycle to be optionally updated at the end current PWM period instead of immediately. Check [DutyCycle to be updated at the end current PWM period #2](https://github.com/khoih-prog/ESP8266_PWM/issues/2) 64 | 65 | ### Releases v1.2.0 66 | 67 | 1. Fix `multiple-definitions` linker error. Drop `src_cpp` and `src_h` directories 68 | 2. Add support to `ESP32_C3` 69 | 3. Add example [multiFileProject](examples/multiFileProject) to demo for multiple-file project 70 | 4. Improve accuracy by using `double`, instead of `uint32_t` for `dutycycle`, `period` 71 | 5. Update examples accordingly 72 | 73 | ### Releases v1.1.1 74 | 75 | 1. Fix examples to not use GPIO1/TX0 for core v2.0.1+ 76 | 77 | ### Releases v1.1.0 78 | 79 | 1. Add functions to modify PWM settings on-the-fly 80 | 2. Fix bug 81 | 3. Add example to demo how to modify PWM settings on-the-fly 82 | 83 | ### Releases v1.0.1 84 | 85 | 1. Adding PWM end-of-duty-cycle callback function. 86 | 2. Fix bug. 87 | 3. Add end-of-duty-cycle callback feature to examples 88 | 89 | ### Releases v1.0.0 90 | 91 | 1. Initial coding for ESP32, ESP32_S2 boards using [ESP32 core v2.0.0+](https://github.com/espressif/arduino-esp32/releases/tag/2.0.0) 92 | -------------------------------------------------------------------------------- /platformio/platformio.ini: -------------------------------------------------------------------------------- 1 | ;PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | ; ============================================================ 13 | ; chose environment: 14 | ; ESP32 15 | ; esp32s2 16 | ; esp32s3 17 | ; esp32c3 18 | 19 | ; ============================================================ 20 | default_envs = ESP32 21 | 22 | [env] 23 | ; ============================================================ 24 | ; Serial configuration 25 | ; choose upload speed, serial-monitor speed 26 | ; ============================================================ 27 | upload_speed = 921600 28 | ;upload_port = COM11 29 | ;monitor_speed = 9600 30 | ;monitor_port = COM11 31 | 32 | ; Checks for the compatibility with frameworks and dev/platforms 33 | lib_compat_mode = strict 34 | lib_ldf_mode = chain+ 35 | ;lib_ldf_mode = deep+ 36 | 37 | lib_deps = 38 | 39 | build_flags = 40 | ; set your debug output (default=Serial) 41 | -D DEBUG_ESP_PORT=Serial 42 | ; comment the following line to enable WiFi debugging 43 | -D NDEBUG 44 | 45 | [env:ESP32] 46 | platform = espressif32 47 | framework = arduino 48 | 49 | ; ============================================================ 50 | ; Board configuration 51 | ; choose your board by uncommenting one of the following lines 52 | ; ============================================================ 53 | ;board = esp32cam 54 | ;board = alksesp32 55 | ;board = featheresp32 56 | ;board = espea32 57 | ;board = bpi-bit 58 | ;board = d-duino-32 59 | board = esp32doit-devkit-v1 60 | ;board = pocket_32 61 | ;board = fm-devkit 62 | ;board = pico32 63 | ;board = esp32-evb 64 | ;board = esp32-gateway 65 | ;board = esp32-pro 66 | ;board = esp32-poe 67 | ;board = oroca_edubot 68 | ;board = onehorse32dev 69 | ;board = lopy 70 | ;board = lopy4 71 | ;board = wesp32 72 | ;board = esp32thing 73 | ;board = sparkfun_lora_gateway_1-channel 74 | ;board = ttgo-lora32-v1 75 | ;board = ttgo-t-beam 76 | ;board = turta_iot_node 77 | ;board = lolin_d32 78 | ;board = lolin_d32_pro 79 | ;board = lolin32 80 | ;board = wemosbat 81 | ;board = widora-air 82 | ;board = xinabox_cw02 83 | ;board = iotbusio 84 | ;board = iotbusproteus 85 | ;board = nina_w10 86 | 87 | [env:esp32s2] 88 | platform = espressif32 89 | framework = arduino 90 | 91 | ; toolchain download links see 92 | ; refer "name": "xtensa-esp32s2-elf-gcc","version": "gcc8_4_0-esp-2021r1" section of 93 | ; https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_dev_index.json 94 | ; e.g. Windows: https://github.com/espressif/crosstool-NG/releases/download/esp-2021r1/xtensa-esp32s2-elf-gcc8_4_0-esp-2021r1-win32.zip 95 | platform_packages = 96 | toolchain-xtensa32s2@file://C:\Users\Max\Downloads\xtensa-esp32s2-elf 97 | framework-arduinoespressif32@https://github.com/espressif/arduino-esp32.git#a4118ea88987c28aac3a49bcb9cc5d6c0acc6f3f 98 | platformio/tool-esptoolpy @ ~1.30100 99 | framework = arduino 100 | board = esp32dev 101 | board_build.mcu = esp32s2 102 | board_build.partitions = huge_app.csv 103 | board_build.variant = esp32s2 104 | board_build.f_cpu = 240000000L 105 | board_build.f_flash = 80000000L 106 | board_build.flash_mode = qio 107 | board_build.arduino.ldscript = esp32s2_out.ld 108 | build_unflags = 109 | -DARDUINO_ESP32_DEV 110 | -DARDUINO_VARIANT="esp32" 111 | build_flags = 112 | -DARDUINO_ESP32S2_DEV 113 | -DARDUINO_VARIANT="esp32s2" 114 | 115 | 116 | [env:esp32s3] 117 | platform = espressif32 118 | framework = arduino 119 | 120 | board_build.mcu = esp32s3 121 | board_build.partitions = huge_app.csv 122 | board_build.variant = esp32s3 123 | board_build.f_cpu = 240000000L 124 | board_build.f_flash = 80000000L 125 | board_build.flash_mode = qio 126 | board_build.arduino.ldscript = esp32s3_out.ld 127 | build_unflags = 128 | -DARDUINO_ESP32_DEV 129 | -DARDUINO_VARIANT="esp32" 130 | build_flags = 131 | -DARDUINO_ESP32S3_DEV 132 | -DARDUINO_VARIANT="esp32s3" 133 | 134 | 135 | [env:esp32sc3] 136 | platform = espressif32 137 | framework = arduino 138 | 139 | board_build.mcu = esp32c3 140 | board_build.partitions = huge_app.csv 141 | board_build.variant = esp32c3 142 | board_build.f_cpu = 160000000L 143 | board_build.f_flash = 80000000L 144 | board_build.flash_mode = qio 145 | board_build.arduino.ldscript = esp32c3_out.ld 146 | build_unflags = 147 | -DARDUINO_ESP32_DEV 148 | -DARDUINO_VARIANT="esp32" 149 | build_flags = 150 | -DARDUINO_ESP32S3_DEV 151 | -DARDUINO_VARIANT="esp32c3" 152 | -------------------------------------------------------------------------------- /examples/ISR_Changing_PWM/ISR_Changing_PWM.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | ISR_Changing_PWM.ino 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | *****************************************************************************************************************************/ 24 | 25 | #if !defined( ESP32 ) 26 | #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. 27 | #endif 28 | 29 | // These define's must be placed at the beginning before #include "ESP32_PWM.h" 30 | // _PWM_LOGLEVEL_ from 0 to 4 31 | // Don't define _PWM_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system. 32 | #define _PWM_LOGLEVEL_ 3 33 | 34 | #define USING_MICROS_RESOLUTION true //false 35 | 36 | // Default is true, uncomment to false 37 | //#define CHANGING_PWM_END_OF_CYCLE false 38 | 39 | // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error 40 | #include "ESP32_PWM.h" 41 | 42 | #ifndef LED_BUILTIN 43 | #define LED_BUILTIN 2 44 | #endif 45 | 46 | #define HW_TIMER_INTERVAL_US 20L 47 | 48 | volatile uint32_t startMicros = 0; 49 | 50 | // Init ESP32 timer 1 51 | ESP32Timer ITimer(1); 52 | 53 | // Init ESP32_ISR_PWM 54 | ESP32_PWM ISR_PWM; 55 | 56 | bool IRAM_ATTR TimerHandler(void * timerNo) 57 | { 58 | ISR_PWM.run(); 59 | 60 | return true; 61 | } 62 | 63 | ////////////////////////////////////////////////////// 64 | 65 | #define USING_PWM_FREQUENCY true 66 | 67 | ////////////////////////////////////////////////////// 68 | 69 | // You can assign pins here. Be carefull to select good pin to use or crash 70 | uint32_t PWM_Pin = LED_BUILTIN; 71 | 72 | // You can assign any interval for any timer here, in Hz 73 | float PWM_Freq1 = 100.0f; 74 | // You can assign any interval for any timer here, in Hz 75 | float PWM_Freq2 = 200.0f; 76 | 77 | // You can assign any interval for any timer here, in microseconds 78 | uint32_t PWM_Period1 = 1000000 / PWM_Freq1; 79 | // You can assign any interval for any timer here, in microseconds 80 | uint32_t PWM_Period2 = 1000000 / PWM_Freq2; 81 | 82 | // You can assign any duty_cycle for any PWM here, from 0-100 83 | float PWM_DutyCycle1 = 50.0; 84 | // You can assign any duty_cycle for any PWM here, from 0-100 85 | float PWM_DutyCycle2 = 90.0; 86 | 87 | // Channel number used to identify associated channel 88 | int channelNum; 89 | 90 | //////////////////////////////////////////////// 91 | 92 | void setup() 93 | { 94 | Serial.begin(115200); 95 | while (!Serial); 96 | 97 | delay(2000); 98 | 99 | Serial.print(F("\nStarting ISR_Changing_PWM on ")); Serial.println(ARDUINO_BOARD); 100 | Serial.println(ESP32_PWM_VERSION); 101 | Serial.print(F("CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz")); 102 | 103 | // Interval in microsecs 104 | if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_US, TimerHandler)) 105 | { 106 | startMicros = micros(); 107 | Serial.print(F("Starting ITimer OK, micros() = ")); Serial.println(startMicros); 108 | } 109 | else 110 | Serial.println(F("Can't set ITimer. Select another freq. or timer")); 111 | 112 | // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary 113 | // You can use up to 16 timer for each ISR_PWM 114 | //void setPWM(uint32_t pin, uint32_t frequency, uint32_t dutycycle 115 | // , timer_callback_p StartCallback = nullptr, timer_callback_p StopCallback = nullptr) 116 | } 117 | 118 | void loop() 119 | { 120 | Serial.print(F("Using PWM Freq = ")); Serial.print(PWM_Freq1); Serial.print(F(", PWM DutyCycle = ")); Serial.println(PWM_DutyCycle1); 121 | 122 | #if USING_PWM_FREQUENCY 123 | 124 | // You can use this with PWM_Freq in Hz 125 | channelNum = ISR_PWM.setPWM(PWM_Pin, PWM_Freq1, PWM_DutyCycle1); 126 | 127 | #else 128 | #if USING_MICROS_RESOLUTION 129 | // Or using period in microsecs resolution 130 | channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1, PWM_DutyCycle1); 131 | #else 132 | // Or using period in millisecs resolution 133 | channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1 / 1000, PWM_DutyCycle1); 134 | #endif 135 | #endif 136 | 137 | delay(10000); 138 | 139 | ISR_PWM.deleteChannel((uint8_t) channelNum); 140 | 141 | Serial.print(F("Using PWM Freq = ")); Serial.print(PWM_Freq2); Serial.print(F(", PWM DutyCycle = ")); Serial.println(PWM_DutyCycle2); 142 | 143 | #if USING_PWM_FREQUENCY 144 | 145 | // You can use this with PWM_Freq in Hz 146 | channelNum = ISR_PWM.setPWM(PWM_Pin, PWM_Freq2, PWM_DutyCycle2); 147 | 148 | #else 149 | #if USING_MICROS_RESOLUTION 150 | // Or using period in microsecs resolution 151 | channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period2, PWM_DutyCycle2); 152 | #else 153 | // Or using period in millisecs resolution 154 | channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period2 / 1000, PWM_DutyCycle2); 155 | #endif 156 | #endif 157 | 158 | delay(10000); 159 | 160 | ISR_PWM.deleteChannel((uint8_t) channelNum); 161 | } 162 | -------------------------------------------------------------------------------- /src/PWM_Generic_Debug.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | PWM_GENERIC_Debug.h 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | 24 | Version: 1.3.3 25 | 26 | Version Modified By Date Comments 27 | ------- ----------- ---------- ----------- 28 | 1.0.0 K Hoang 20/09/2021 Initial coding for ESP32, ESP32_S2, ESP32_C3 boards with ESP32 core v2.0.0+ 29 | 1.0.1 K Hoang 21/09/2021 Fix bug. Ading PWM end-of-duty-cycle callback function. Improve examples 30 | 1.1.0 K Hoang 06/11/2021 Add functions to modify PWM settings on-the-fly 31 | 1.1.1 K Hoang 09/11/2021 Fix examples to not use GPIO1/TX0 for core v2.0.1+ 32 | 1.2.0 K Hoang 29/01/2022 Fix multiple-definitions linker error. Improve accuracy. Fix bug 33 | 1.2.1 K Hoang 30/01/2022 DutyCycle to be updated at the end current PWM period 34 | 1.2.2 K Hoang 01/02/2022 Use float for DutyCycle and Freq, uint32_t for period. Optimize code 35 | 1.3.0 K Hoang 12/02/2022 Add support to new ESP32-S3 36 | 1.3.1 K Hoang 04/03/2022 Fix `DutyCycle` and `New Period` display bugs. Display warning only when debug level > 3 37 | 1.3.2 K Hoang 09/05/2022 Remove crashing PIN_D24 from examples 38 | 1.3.3 K Hoang 16/06/2022 Add support to new Adafruit boards 39 | *****************************************************************************************************************************/ 40 | 41 | #pragma once 42 | 43 | #ifndef PWM_GENERIC_DEBUG_H 44 | #define PWM_GENERIC_DEBUG_H 45 | 46 | #ifdef PWM_GENERIC_DEBUG_PORT 47 | #define PWM_DBG_PORT PWM_GENERIC_DEBUG_PORT 48 | #else 49 | #define PWM_DBG_PORT Serial 50 | #endif 51 | 52 | // Change _PWM_LOGLEVEL_ to set tracing and logging verbosity 53 | // 0: DISABLED: no logging 54 | // 1: ERROR: errors 55 | // 2: WARN: errors and warnings 56 | // 3: INFO: errors, warnings and informational (default) 57 | // 4: DEBUG: errors, warnings, informational and debug 58 | 59 | #ifndef _PWM_LOGLEVEL_ 60 | #define _PWM_LOGLEVEL_ 1 61 | #endif 62 | 63 | /////////////////////////////////////// 64 | 65 | const char PWM_MARK[] = "[PWM] "; 66 | const char PWM_SPACE[] = " "; 67 | 68 | #define PWM_PRINT PWM_DBG_PORT.print 69 | #define PWM_PRINTLN PWM_DBG_PORT.println 70 | 71 | #define PWM_PRINT_MARK PWM_PRINT(PWM_MARK) 72 | #define PWM_PRINT_SP PWM_PRINT(PWM_SPACE) 73 | #define PWM_PRINT_LINE PWM_PRINT(PWM_LINE) 74 | 75 | /////////////////////////////////////// 76 | 77 | #define PWM_LOGERROR(x) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINTLN(x); } 78 | #define PWM_LOGERROR0(x) if(_PWM_LOGLEVEL_>0) { PWM_PRINT(x); } 79 | #define PWM_LOGERRORLN0(x) if(_PWM_LOGLEVEL_>0) { PWM_PRINTLN(x); } 80 | #define PWM_LOGERROR1(x,y) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINTLN(y); } 81 | #define PWM_LOGERROR2(x,y,z) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINTLN(z); } 82 | #define PWM_LOGERROR3(x,y,z,w) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINTLN(w); } 83 | 84 | /////////////////////////////////////// 85 | 86 | #define PWM_LOGWARN(x) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINTLN(x); } 87 | #define PWM_LOGWARN0(x) if(_PWM_LOGLEVEL_>1) { PWM_PRINT(x); } 88 | #define PWM_LOGWARNLN0(x) if(_PWM_LOGLEVEL_>1) { PWM_PRINTLN(x); } 89 | #define PWM_LOGWARN1(x,y) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINTLN(y); } 90 | #define PWM_LOGWARN2(x,y,z) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINTLN(z); } 91 | #define PWM_LOGWARN3(x,y,z,w) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINTLN(w); } 92 | 93 | /////////////////////////////////////// 94 | 95 | #define PWM_LOGINFO(x) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINTLN(x); } 96 | #define PWM_LOGINFO0(x) if(_PWM_LOGLEVEL_>2) { PWM_PRINT(x); } 97 | #define PWM_LOGINFOLN0(x) if(_PWM_LOGLEVEL_>2) { PWM_PRINTLN(x); } 98 | #define PWM_LOGINFO1(x,y) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINTLN(y); } 99 | #define PWM_LOGINFO2(x,y,z) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINTLN(z); } 100 | #define PWM_LOGINFO3(x,y,z,w) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINTLN(w); } 101 | 102 | /////////////////////////////////////// 103 | 104 | #define PWM_LOGDEBUG(x) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINTLN(x); } 105 | #define PWM_LOGDEBUG0(x) if(_PWM_LOGLEVEL_>3) { PWM_PRINT(x); } 106 | #define PWM_LOGDEBUGLN0(x) if(_PWM_LOGLEVEL_>3) { PWM_PRINTLN(x); } 107 | #define PWM_LOGDEBUG1(x,y) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINTLN(y); } 108 | #define PWM_LOGDEBUG2(x,y,z) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINTLN(z); } 109 | #define PWM_LOGDEBUG3(x,y,z,w) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINTLN(w); } 110 | 111 | /////////////////////////////////////// 112 | 113 | #endif //PWM_GENERIC_DEBUG_H 114 | -------------------------------------------------------------------------------- /examples/ISR_Modify_PWM/ISR_Modify_PWM.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | ISR_Modify_PWM.ino 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | *****************************************************************************************************************************/ 24 | 25 | #if !defined( ESP32 ) 26 | #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. 27 | #endif 28 | 29 | // These define's must be placed at the beginning before #include "ESP32_PWM.h" 30 | // _PWM_LOGLEVEL_ from 0 to 4 31 | // Don't define _PWM_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system. 32 | #define _PWM_LOGLEVEL_ 3 33 | 34 | #define USING_MICROS_RESOLUTION true //false 35 | 36 | // Default is true, uncomment to false 37 | //#define CHANGING_PWM_END_OF_CYCLE false 38 | 39 | // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error 40 | #include "ESP32_PWM.h" 41 | 42 | #ifndef LED_BUILTIN 43 | #define LED_BUILTIN 2 44 | #endif 45 | 46 | #define HW_TIMER_INTERVAL_US 20L 47 | 48 | volatile uint32_t startMicros = 0; 49 | 50 | // Init ESP32 timer 1 51 | ESP32Timer ITimer(1); 52 | 53 | // Init ESP32_ISR_PWM 54 | ESP32_PWM ISR_PWM; 55 | 56 | bool IRAM_ATTR TimerHandler(void * timerNo) 57 | { 58 | ISR_PWM.run(); 59 | 60 | return true; 61 | } 62 | 63 | ////////////////////////////////////////////////////// 64 | 65 | #define USING_PWM_FREQUENCY false //true 66 | 67 | ////////////////////////////////////////////////////// 68 | 69 | 70 | // You can assign pins here. Be carefull to select good pin to use or crash 71 | uint32_t PWM_Pin = LED_BUILTIN; 72 | 73 | // You can assign any interval for any timer here, in Hz 74 | float PWM_Freq1 = 200.0f; //1.0f; 75 | // You can assign any interval for any timer here, in Hz 76 | float PWM_Freq2 = 100.0f; //2.0f; 77 | 78 | // You can assign any interval for any timer here, in microseconds 79 | uint32_t PWM_Period1 = 1000000 / PWM_Freq1; 80 | // You can assign any interval for any timer here, in microseconds 81 | uint32_t PWM_Period2 = 1000000 / PWM_Freq2; 82 | 83 | // You can assign any duty_cycle for any PWM here, from 0-100 84 | float PWM_DutyCycle1 = 1.0f; //50.0f; 85 | // You can assign any duty_cycle for any PWM here, from 0-100 86 | float PWM_DutyCycle2 = 5.55f; //90.0f; 87 | 88 | // Channel number used to identify associated channel 89 | int channelNum; 90 | 91 | //////////////////////////////////////////////// 92 | 93 | void setup() 94 | { 95 | Serial.begin(115200); 96 | while (!Serial); 97 | 98 | delay(2000); 99 | 100 | Serial.print(F("\nStarting ISR_Modify_PWM on ")); Serial.println(ARDUINO_BOARD); 101 | Serial.println(ESP32_PWM_VERSION); 102 | Serial.print(F("CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz")); 103 | 104 | // Interval in microsecs 105 | if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_US, TimerHandler)) 106 | { 107 | startMicros = micros(); 108 | Serial.print(F("Starting ITimer OK, micros() = ")); Serial.println(startMicros); 109 | } 110 | else 111 | Serial.println(F("Can't set ITimer. Select another freq. or timer")); 112 | 113 | // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary 114 | // You can use up to 16 timer for each ISR_PWM 115 | //void setPWM(uint32_t pin, float frequency, float dutycycle 116 | // , timer_callback_p StartCallback = nullptr, timer_callback_p StopCallback = nullptr) 117 | Serial.print(F("Using PWM Freq = ")); Serial.print(PWM_Freq1); Serial.print(F(", PWM DutyCycle = ")); Serial.println(PWM_DutyCycle1); 118 | 119 | #if USING_PWM_FREQUENCY 120 | 121 | // You can use this with PWM_Freq in Hz 122 | ISR_PWM.setPWM(PWM_Pin, PWM_Freq1, PWM_DutyCycle1); 123 | 124 | #else 125 | #if USING_MICROS_RESOLUTION 126 | // Or using period in microsecs resolution 127 | channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1, PWM_DutyCycle1); 128 | #else 129 | // Or using period in millisecs resolution 130 | channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1 / 1000, PWM_DutyCycle1); 131 | #endif 132 | #endif 133 | } 134 | 135 | //////////////////////////////////////////////// 136 | 137 | void changePWM() 138 | { 139 | static uint8_t count = 1; 140 | 141 | float PWM_Freq; 142 | float PWM_DutyCycle; 143 | 144 | if (count++ % 2) 145 | { 146 | PWM_Freq = PWM_Freq2; 147 | PWM_DutyCycle = PWM_DutyCycle2; 148 | } 149 | else 150 | { 151 | PWM_Freq = PWM_Freq1; 152 | PWM_DutyCycle = PWM_DutyCycle1; 153 | } 154 | 155 | // You can use this with PWM_Freq in Hz 156 | if (!ISR_PWM.modifyPWMChannel(channelNum, PWM_Pin, PWM_Freq, PWM_DutyCycle)) 157 | { 158 | Serial.print(F("modifyPWMChannel error for PWM_Period")); 159 | } 160 | } 161 | 162 | //////////////////////////////////////////////// 163 | 164 | void changingPWM() 165 | { 166 | static ulong changingPWM_timeout = 0; 167 | 168 | static ulong current_millis; 169 | 170 | #define CHANGING_PWM_INTERVAL 10000L 171 | 172 | current_millis = millis(); 173 | 174 | // changePWM every CHANGING_PWM_INTERVAL (10) seconds. 175 | if ( (current_millis > changingPWM_timeout) ) 176 | { 177 | if (changingPWM_timeout > 0) 178 | changePWM(); 179 | 180 | changingPWM_timeout = current_millis + CHANGING_PWM_INTERVAL; 181 | } 182 | } 183 | 184 | //////////////////////////////////////////////// 185 | 186 | void loop() 187 | { 188 | changingPWM(); 189 | } 190 | -------------------------------------------------------------------------------- /examples/ISR_16_PWMs_Array_Simple/ISR_16_PWMs_Array_Simple.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | ISR_16_PWMs_Array_Simple.ino 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | *****************************************************************************************************************************/ 24 | 25 | #if !defined( ESP32 ) 26 | #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. 27 | #endif 28 | 29 | // These define's must be placed at the beginning before #include "ESP32_PWM.h" 30 | // _PWM_LOGLEVEL_ from 0 to 4 31 | // Don't define _PWM_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system. 32 | #define _PWM_LOGLEVEL_ 3 33 | 34 | #define USING_MICROS_RESOLUTION true //false 35 | 36 | // Default is true, uncomment to false 37 | //#define CHANGING_PWM_END_OF_CYCLE false 38 | 39 | // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error 40 | #include "ESP32_PWM.h" 41 | 42 | #ifndef LED_BUILTIN 43 | #define LED_BUILTIN 2 44 | #endif 45 | 46 | #ifndef LED_BLUE 47 | #define LED_BLUE 25 48 | #endif 49 | 50 | #ifndef LED_RED 51 | #define LED_RED 27 52 | #endif 53 | 54 | #define HW_TIMER_INTERVAL_US 20L 55 | 56 | uint32_t startMicros = 0; 57 | 58 | // Init ESP32 timer 1 59 | ESP32Timer ITimer(1); 60 | 61 | // Init ESP32_ISR_PWM 62 | ESP32_PWM ISR_PWM; 63 | 64 | 65 | bool IRAM_ATTR TimerHandler(void * timerNo) 66 | { 67 | ISR_PWM.run(); 68 | 69 | return true; 70 | } 71 | 72 | ////////////////////////////////////////////////////// 73 | 74 | #if ( ARDUINO_ESP32C3_DEV ) 75 | #define NUMBER_ISR_PWMS 4 76 | #elif ( ARDUINO_ESP32S3_DEV ) 77 | #define NUMBER_ISR_PWMS 16 78 | #else 79 | #define NUMBER_ISR_PWMS 16 80 | #endif 81 | 82 | #define PIN_D0 0 // Pin D0 mapped to pin GPIO0/BOOT/ADC11/TOUCH1 of ESP32 83 | #define PIN_D1 1 // Pin D1 mapped to pin GPIO1/TX0 of ESP32 84 | #define PIN_D2 2 // Pin D2 mapped to pin GPIO2/ADC12/TOUCH2 of ESP32 85 | #define PIN_D3 3 // Pin D3 mapped to pin GPIO3/RX0 of ESP32 86 | #define PIN_D4 4 // Pin D4 mapped to pin GPIO4/ADC10/TOUCH0 of ESP32 87 | #define PIN_D5 5 // Pin D5 mapped to pin GPIO5/SPISS/VSPI_SS of ESP32 88 | #define PIN_D6 6 // Pin D6 mapped to pin GPIO6 of ESP32 89 | #define PIN_D7 7 // Pin D7 mapped to pin GPIO7 of ESP32 90 | #define PIN_D8 8 // Pin D8 mapped to pin GPIO8 of ESP32 91 | #define PIN_D9 9 // Pin D9 mapped to pin GPIO9 of ESP32 92 | #define PIN_D10 10 // Pin D10 mapped to pin GPIO10 of ESP32 93 | #define PIN_D11 11 // Pin D11 mapped to pin GPIO11 of ESP32 94 | #define PIN_D12 12 // Pin D12 mapped to pin GPIO12/HSPI_MISO/ADC15/TOUCH5/TDI of ESP32 95 | #define PIN_D13 13 // Pin D13 mapped to pin GPIO13/HSPI_MOSI/ADC14/TOUCH4/TCK of ESP32 96 | #define PIN_D14 14 // Pin D14 mapped to pin GPIO14/HSPI_SCK/ADC16/TOUCH6/TMS of ESP32 97 | #define PIN_D15 15 // Pin D15 mapped to pin GPIO15/HSPI_SS/ADC13/TOUCH3/TDO of ESP32 98 | #define PIN_D16 16 // Pin D16 mapped to pin GPIO16/TX2 of ESP32 99 | #define PIN_D17 17 // Pin D17 mapped to pin GPIO17/RX2 of ESP32 100 | #define PIN_D18 18 // Pin D18 mapped to pin GPIO18/VSPI_SCK of ESP32 101 | #define PIN_D19 19 // Pin D19 mapped to pin GPIO19/VSPI_MISO of ESP32 102 | 103 | #define PIN_D21 21 // Pin D21 mapped to pin GPIO21/SDA of ESP32 104 | #define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 105 | #define PIN_D23 23 // Pin D23 mapped to pin GPIO23/VSPI_MOSI of ESP32 106 | #define PIN_D25 25 // Pin D25 mapped to pin GPIO25/ADC18/DAC1 of ESP32 107 | #define PIN_D26 26 // Pin D26 mapped to pin GPIO26/ADC19/DAC2 of ESP32 108 | #define PIN_D27 27 // Pin D27 mapped to pin GPIO27/ADC17/TOUCH7 of ESP32 109 | 110 | ////////////////////////////////////////////////////// 111 | 112 | #define USING_PWM_FREQUENCY true 113 | 114 | ////////////////////////////////////////////////////// 115 | 116 | // You can assign pins here. Be carefull to select good pin to use or crash, e.g pin 6-11 117 | // Can't use PIN_D1 for core v2.0.1+ 118 | 119 | #if ( ARDUINO_ESP32C3_DEV ) 120 | uint32_t PWM_Pin[] = 121 | // Bad pins to use: PIN_D12-PIN_D24 122 | { 123 | LED_BUILTIN, PIN_D3, PIN_D4, PIN_D5 124 | }; 125 | #elif ( ARDUINO_ESP32S3_DEV ) 126 | uint32_t PWM_Pin[] = 127 | // Bad pins to use: PIN_D24 128 | { 129 | PIN_D1, PIN_D2, PIN_D3, PIN_D4, PIN_D5, PIN_D6, PIN_D7, PIN_D8, 130 | PIN_D9, PIN_D10, PIN_D11, PIN_D12, PIN_D13, PIN_D14, PIN_D15, PIN_D16, 131 | }; 132 | #else 133 | // Bad pins to use: PIN_D24 134 | uint32_t PWM_Pin[] = 135 | { 136 | LED_BUILTIN, PIN_D25, PIN_D3, PIN_D4, PIN_D5, PIN_D12, PIN_D13, PIN_D14, 137 | PIN_D15, PIN_D16, PIN_D17, PIN_D18, PIN_D19, PIN_D21, PIN_D22, PIN_D23 138 | }; 139 | #endif 140 | 141 | // You can assign any interval for any timer here, in microseconds 142 | uint32_t PWM_Period[] = 143 | { 144 | 1000000, 500000, 333333, 250000, 200000, 166667, 142857, 125000, 145 | 111111, 100000, 66667, 50000, 40000, 33333, 25000, 20000 146 | }; 147 | 148 | // You can assign any interval for any timer here, in Hz 149 | float PWM_Freq[] = 150 | { 151 | 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 152 | 9.0f, 10.0f, 15.0f, 20.0f, 25.0f, 30.0f, 40.0f, 50.0f 153 | }; 154 | 155 | // You can assign any interval for any timer here, in milliseconds 156 | float PWM_DutyCycle[] = 157 | { 158 | 5.00, 10.00, 20.00, 30.00, 40.00, 45.00, 50.00, 55.00, 159 | 60.00, 65.00, 70.00, 75.00, 80.00, 85.00, 90.00, 95.00 160 | }; 161 | 162 | //////////////////////////////////////////////// 163 | 164 | void setup() 165 | { 166 | Serial.begin(115200); 167 | while (!Serial); 168 | 169 | delay(2000); 170 | 171 | Serial.print(F("\nStarting ISR_16_PWMs_Array_Simple on ")); Serial.println(ARDUINO_BOARD); 172 | Serial.println(ESP32_PWM_VERSION); 173 | Serial.print(F("CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz")); 174 | 175 | // Interval in microsecs 176 | if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_US, TimerHandler)) 177 | { 178 | startMicros = micros(); 179 | Serial.print(F("Starting ITimer OK, micros() = ")); Serial.println(startMicros); 180 | } 181 | else 182 | Serial.println(F("Can't set ITimer. Select another freq. or timer")); 183 | 184 | // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary 185 | // You can use up to 16 timer for each ISR_PWM 186 | for (uint16_t i = 0; i < NUMBER_ISR_PWMS; i++) 187 | //for (uint16_t i = 0; i < 1; i++) 188 | { 189 | //void setPWM(uint32_t pin, float frequency, float dutycycle 190 | // , timer_callback_p StartCallback = nullptr, timer_callback_p StopCallback = nullptr) 191 | 192 | #if USING_PWM_FREQUENCY 193 | 194 | // You can use this with PWM_Freq in Hz 195 | ISR_PWM.setPWM(PWM_Pin[i], PWM_Freq[i], PWM_DutyCycle[i]); 196 | 197 | #else 198 | #if USING_MICROS_RESOLUTION 199 | // Or using period in microsecs resolution 200 | ISR_PWM.setPWM_Period(PWM_Pin[i], PWM_Period[i], PWM_DutyCycle[i]); 201 | #else 202 | // Or using period in millisecs resolution 203 | ISR_PWM.setPWM_Period(PWM_Pin[i], PWM_Period[i] / 1000, PWM_DutyCycle[i]); 204 | #endif 205 | #endif 206 | } 207 | } 208 | 209 | void loop() 210 | { 211 | } 212 | -------------------------------------------------------------------------------- /examples/ISR_16_PWMs_Array/ISR_16_PWMs_Array.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | ISR_16_PWMs_Array.ino 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | *****************************************************************************************************************************/ 24 | 25 | #if !defined( ESP32 ) 26 | #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. 27 | 28 | #endif 29 | 30 | // These define's must be placed at the beginning before #include "ESP32_PWM.h" 31 | // _PWM_LOGLEVEL_ from 0 to 4 32 | // Don't define _PWM_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system. 33 | #define _PWM_LOGLEVEL_ 3 34 | 35 | #define USING_MICROS_RESOLUTION true //false 36 | 37 | // Default is true, uncomment to false 38 | //#define CHANGING_PWM_END_OF_CYCLE false 39 | 40 | // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error 41 | #include "ESP32_PWM.h" 42 | 43 | #ifndef LED_BUILTIN 44 | #define LED_BUILTIN 2 45 | #endif 46 | 47 | #ifndef LED_BLUE 48 | #define LED_BLUE 25 49 | #endif 50 | 51 | #ifndef LED_RED 52 | #define LED_RED 27 53 | #endif 54 | 55 | #define HW_TIMER_INTERVAL_US 20L 56 | 57 | uint32_t startMicros = 0; 58 | 59 | // Init ESP32 timer 1 60 | ESP32Timer ITimer(1); 61 | 62 | // Init ESP32_ISR_PWM 63 | ESP32_PWM ISR_PWM; 64 | 65 | bool IRAM_ATTR TimerHandler(void * timerNo) 66 | { 67 | ISR_PWM.run(); 68 | 69 | return true; 70 | } 71 | 72 | ////////////////////////////////////////////////////// 73 | 74 | #if ( ARDUINO_ESP32C3_DEV ) 75 | #define NUMBER_ISR_PWMS 4 76 | #elif ( ARDUINO_ESP32S3_DEV ) 77 | #define NUMBER_ISR_PWMS 16 78 | #else 79 | #define NUMBER_ISR_PWMS 16 80 | #endif 81 | 82 | #define PIN_D0 0 // Pin D0 mapped to pin GPIO0/BOOT/ADC11/TOUCH1 of ESP32 83 | #define PIN_D1 1 // Pin D1 mapped to pin GPIO1/TX0 of ESP32 84 | #define PIN_D2 2 // Pin D2 mapped to pin GPIO2/ADC12/TOUCH2 of ESP32 85 | #define PIN_D3 3 // Pin D3 mapped to pin GPIO3/RX0 of ESP32 86 | #define PIN_D4 4 // Pin D4 mapped to pin GPIO4/ADC10/TOUCH0 of ESP32 87 | #define PIN_D5 5 // Pin D5 mapped to pin GPIO5/SPISS/VSPI_SS of ESP32 88 | #define PIN_D6 6 // Pin D6 mapped to pin GPIO6 of ESP32 89 | #define PIN_D7 7 // Pin D7 mapped to pin GPIO7 of ESP32 90 | #define PIN_D8 8 // Pin D8 mapped to pin GPIO8 of ESP32 91 | #define PIN_D9 9 // Pin D9 mapped to pin GPIO9 of ESP32 92 | #define PIN_D10 10 // Pin D10 mapped to pin GPIO10 of ESP32 93 | #define PIN_D11 11 // Pin D11 mapped to pin GPIO11 of ESP32 94 | #define PIN_D12 12 // Pin D12 mapped to pin GPIO12/HSPI_MISO/ADC15/TOUCH5/TDI of ESP32 95 | #define PIN_D13 13 // Pin D13 mapped to pin GPIO13/HSPI_MOSI/ADC14/TOUCH4/TCK of ESP32 96 | #define PIN_D14 14 // Pin D14 mapped to pin GPIO14/HSPI_SCK/ADC16/TOUCH6/TMS of ESP32 97 | #define PIN_D15 15 // Pin D15 mapped to pin GPIO15/HSPI_SS/ADC13/TOUCH3/TDO of ESP32 98 | #define PIN_D16 16 // Pin D16 mapped to pin GPIO16/TX2 of ESP32 99 | #define PIN_D17 17 // Pin D17 mapped to pin GPIO17/RX2 of ESP32 100 | #define PIN_D18 18 // Pin D18 mapped to pin GPIO18/VSPI_SCK of ESP32 101 | #define PIN_D19 19 // Pin D19 mapped to pin GPIO19/VSPI_MISO of ESP32 102 | 103 | #define PIN_D21 21 // Pin D21 mapped to pin GPIO21/SDA of ESP32 104 | #define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 105 | #define PIN_D23 23 // Pin D23 mapped to pin GPIO23/VSPI_MOSI of ESP32 106 | #define PIN_D25 25 // Pin D25 mapped to pin GPIO25/ADC18/DAC1 of ESP32 107 | #define PIN_D26 26 // Pin D26 mapped to pin GPIO26/ADC19/DAC2 of ESP32 108 | #define PIN_D27 27 // Pin D27 mapped to pin GPIO27/ADC17/TOUCH7 of ESP32 109 | 110 | ////////////////////////////////////////////////////// 111 | 112 | #define USING_PWM_FREQUENCY true 113 | 114 | ////////////////////////////////////////////////////// 115 | 116 | // You can assign pins here. Be carefull to select good pin to use or crash, e.g pin 6-11 117 | // Can't use PIN_D1 for core v2.0.1+ 118 | 119 | #if ( ARDUINO_ESP32C3_DEV ) 120 | uint32_t PWM_Pin[] = 121 | // Bad pins to use: PIN_D12-PIN_D24 122 | { 123 | LED_BUILTIN, PIN_D3, PIN_D4, PIN_D5 124 | }; 125 | #elif ( ARDUINO_ESP32S3_DEV ) 126 | uint32_t PWM_Pin[] = 127 | // Bad pins to use: PIN_D24 128 | { 129 | PIN_D1, PIN_D2, PIN_D3, PIN_D4, PIN_D5, PIN_D6, PIN_D7, PIN_D8, 130 | PIN_D9, PIN_D10, PIN_D11, PIN_D12, PIN_D13, PIN_D14, PIN_D15, PIN_D16, 131 | }; 132 | #else 133 | // Bad pins to use: PIN_D24 134 | uint32_t PWM_Pin[] = 135 | { 136 | LED_BUILTIN, PIN_D25, PIN_D3, PIN_D4, PIN_D5, PIN_D12, PIN_D13, PIN_D14, 137 | PIN_D15, PIN_D16, PIN_D17, PIN_D18, PIN_D19, PIN_D21, PIN_D22, PIN_D23 138 | }; 139 | #endif 140 | 141 | // You can assign any interval for any timer here, in microseconds 142 | uint32_t PWM_Period[] = 143 | { 144 | 1000000, 500000, 333333, 250000, 200000, 166667, 142857, 125000, 145 | 111111, 100000, 66667, 50000, 40000, 33333, 25000, 20000 146 | }; 147 | 148 | // You can assign any interval for any timer here, in Hz 149 | float PWM_Freq[] = 150 | { 151 | 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 152 | 9.0f, 10.0f, 15.0f, 20.0f, 25.0f, 30.0f, 40.0f, 50.0f 153 | }; 154 | 155 | // You can assign any interval for any timer here, in milliseconds 156 | float PWM_DutyCycle[] = 157 | { 158 | 5.00, 10.00, 20.00, 30.00, 40.00, 45.00, 50.00, 55.00, 159 | 60.00, 65.00, 70.00, 75.00, 80.00, 85.00, 90.00, 95.00 160 | }; 161 | 162 | typedef void (*irqCallback) (); 163 | 164 | 165 | // In ESP32, avoid doing something fancy in ISR, for example complex Serial.print with String() argument 166 | // The pure simple Serial.prints here are just for demonstration and testing. Must be eliminate in working environment 167 | // Or you can get this run-time error / crash 168 | void doingSomething0() 169 | { 170 | } 171 | 172 | void doingSomething1() 173 | { 174 | } 175 | 176 | void doingSomething2() 177 | { 178 | } 179 | 180 | void doingSomething3() 181 | { 182 | } 183 | 184 | void doingSomething4() 185 | { 186 | } 187 | 188 | void doingSomething5() 189 | { 190 | } 191 | 192 | void doingSomething6() 193 | { 194 | } 195 | 196 | void doingSomething7() 197 | { 198 | } 199 | 200 | void doingSomething8() 201 | { 202 | } 203 | 204 | void doingSomething9() 205 | { 206 | } 207 | 208 | void doingSomething10() 209 | { 210 | } 211 | 212 | void doingSomething11() 213 | { 214 | } 215 | 216 | void doingSomething12() 217 | { 218 | } 219 | 220 | void doingSomething13() 221 | { 222 | } 223 | 224 | void doingSomething14() 225 | { 226 | } 227 | 228 | void doingSomething15() 229 | { 230 | } 231 | 232 | irqCallback irqCallbackStartFunc[] = 233 | { 234 | doingSomething0, doingSomething1, doingSomething2, doingSomething3, 235 | doingSomething4, doingSomething5, doingSomething6, doingSomething7, 236 | doingSomething8, doingSomething9, doingSomething10, doingSomething11, 237 | doingSomething12, doingSomething13, doingSomething14, doingSomething15 238 | }; 239 | 240 | //////////////////////////////////////////////// 241 | 242 | void setup() 243 | { 244 | pinMode(PIN_D1, OUTPUT); 245 | 246 | Serial.begin(115200); 247 | while (!Serial); 248 | 249 | delay(2000); 250 | 251 | Serial.print(F("\nStarting ISR_16_PWMs_Array on ")); Serial.println(ARDUINO_BOARD); 252 | Serial.println(ESP32_PWM_VERSION); 253 | Serial.print(F("CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz")); 254 | 255 | // Interval in microsecs 256 | if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_US, TimerHandler)) 257 | { 258 | startMicros = micros(); 259 | Serial.print(F("Starting ITimer OK, micros() = ")); Serial.println(startMicros); 260 | } 261 | else 262 | Serial.println(F("Can't set ITimer. Select another freq. or timer")); 263 | 264 | // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary 265 | // You can use up to 16 timer for each ISR_PWM 266 | for (uint16_t i = 0; i < NUMBER_ISR_PWMS; i++) 267 | { 268 | //void setPWM(uint32_t pin, float frequency, float dutycycle 269 | // , timer_callback_p StartCallback = nullptr, timer_callback_p StopCallback = nullptr) 270 | 271 | #if USING_PWM_FREQUENCY 272 | 273 | // You can use this with PWM_Freq in Hz 274 | ISR_PWM.setPWM(PWM_Pin[i], PWM_Freq[i], PWM_DutyCycle[i], irqCallbackStartFunc[i]); 275 | 276 | #else 277 | #if USING_MICROS_RESOLUTION 278 | // Or using period in microsecs resolution 279 | ISR_PWM.setPWM_Period(PWM_Pin[i], PWM_Period[i], PWM_DutyCycle[i], irqCallbackStartFunc[i]); 280 | #else 281 | // Or using period in millisecs resolution 282 | ISR_PWM.setPWM_Period(PWM_Pin[i], PWM_Period[i] / 1000, PWM_DutyCycle[i], irqCallbackStartFunc[i]); 283 | #endif 284 | #endif 285 | } 286 | } 287 | 288 | void loop() 289 | { 290 | delay(1); 291 | } 292 | -------------------------------------------------------------------------------- /src/ESP32_PWM_ISR.hpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | ESP32_PWM_ISR.hpp 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | 24 | Version: 1.3.3 25 | 26 | Version Modified By Date Comments 27 | ------- ----------- ---------- ----------- 28 | 1.0.0 K Hoang 20/09/2021 Initial coding for ESP32, ESP32_S2, ESP32_C3 boards with ESP32 core v2.0.0+ 29 | 1.0.1 K Hoang 21/09/2021 Fix bug. Ading PWM end-of-duty-cycle callback function. Improve examples 30 | 1.1.0 K Hoang 06/11/2021 Add functions to modify PWM settings on-the-fly 31 | 1.1.1 K Hoang 09/11/2021 Fix examples to not use GPIO1/TX0 for core v2.0.1+ 32 | 1.2.0 K Hoang 29/01/2022 Fix multiple-definitions linker error. Improve accuracy. Fix bug 33 | 1.2.1 K Hoang 30/01/2022 DutyCycle to be updated at the end current PWM period 34 | 1.2.2 K Hoang 01/02/2022 Use float for DutyCycle and Freq, uint32_t for period. Optimize code 35 | 1.3.0 K Hoang 12/02/2022 Add support to new ESP32-S3 36 | 1.3.1 K Hoang 04/03/2022 Fix `DutyCycle` and `New Period` display bugs. Display warning only when debug level > 3 37 | 1.3.2 K Hoang 09/05/2022 Remove crashing PIN_D24 from examples 38 | 1.3.3 K Hoang 16/06/2022 Add support to new Adafruit boards 39 | *****************************************************************************************************************************/ 40 | 41 | #pragma once 42 | 43 | #ifndef PWM_ISR_GENERIC_HPP 44 | #define PWM_ISR_GENERIC_HPP 45 | 46 | #if !defined( ESP32 ) 47 | #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. 48 | #endif 49 | 50 | #if defined(ARDUINO) 51 | #if ARDUINO >= 100 52 | #include 53 | #else 54 | #include 55 | #endif 56 | #endif 57 | 58 | #ifndef ESP32_PWM_VERSION 59 | #define ESP32_PWM_VERSION "ESP32_PWM v1.3.3" 60 | 61 | #define ESP32_PWM_VERSION_MAJOR 1 62 | #define ESP32_PWM_VERSION_MINOR 3 63 | #define ESP32_PWM_VERSION_PATCH 3 64 | 65 | #define ESP32_PWM_VERSION_INT 1003003 66 | #endif 67 | 68 | #ifndef _PWM_LOGLEVEL_ 69 | #define _PWM_LOGLEVEL_ 1 70 | #endif 71 | 72 | #include "PWM_Generic_Debug.h" 73 | 74 | #define CONFIG_ESP32_APPTRACE_ENABLE 75 | 76 | #if 0 77 | #ifndef configMINIMAL_STACK_SIZE 78 | #define configMINIMAL_STACK_SIZE 2048 79 | #else 80 | #undef configMINIMAL_STACK_SIZE 81 | #define configMINIMAL_STACK_SIZE 2048 82 | #endif 83 | #endif 84 | 85 | #include 86 | 87 | #include 88 | 89 | #define ESP32_PWM_ISR ESP32_PWM 90 | 91 | typedef void (*timer_callback)(); 92 | typedef void (*timer_callback_p)(void *); 93 | 94 | #if !defined(USING_MICROS_RESOLUTION) 95 | 96 | #if (_PWM_LOGLEVEL_ > 3) 97 | #warning Not USING_MICROS_RESOLUTION, using millis resolution 98 | #endif 99 | 100 | #define USING_MICROS_RESOLUTION false 101 | #endif 102 | 103 | #if !defined(CHANGING_PWM_END_OF_CYCLE) 104 | #if (_PWM_LOGLEVEL_ > 3) 105 | #warning Using default CHANGING_PWM_END_OF_CYCLE == true 106 | #endif 107 | 108 | #define CHANGING_PWM_END_OF_CYCLE true 109 | #endif 110 | 111 | class ESP32_PWM_ISR 112 | { 113 | 114 | public: 115 | // maximum number of PWM channels 116 | #define MAX_NUMBER_CHANNELS 16 117 | 118 | // constructor 119 | ESP32_PWM_ISR(); 120 | 121 | void init(); 122 | 123 | // this function must be called inside loop() 124 | void IRAM_ATTR run(); 125 | 126 | ////////////////////////////////////////////////////////////////// 127 | // PWM 128 | // Return the channelNum if OK, -1 if error 129 | int setPWM(const uint32_t& pin, const float& frequency, const float& dutycycle, timer_callback StartCallback = nullptr, 130 | timer_callback StopCallback = nullptr) 131 | { 132 | uint32_t period = 0; 133 | 134 | if ( ( frequency > 0.0 ) && ( frequency <= 500.0 ) ) 135 | { 136 | #if USING_MICROS_RESOLUTION 137 | // period in us 138 | period = 1000000.0f / frequency; 139 | #else 140 | // period in ms 141 | period = 1000.0f / frequency; 142 | #endif 143 | } 144 | else 145 | { 146 | PWM_LOGERROR("Error: Invalid frequency, max is 500Hz"); 147 | 148 | return -1; 149 | } 150 | 151 | return setupPWMChannel(pin, period, dutycycle, (void *) StartCallback, (void *) StopCallback); 152 | } 153 | 154 | // period in us 155 | // Return the channelNum if OK, -1 if error 156 | int setPWM_Period(const uint32_t& pin, const uint32_t& period, const float& dutycycle, 157 | timer_callback StartCallback = nullptr, timer_callback StopCallback = nullptr) 158 | { 159 | return setupPWMChannel(pin, period, dutycycle, (void *) StartCallback, (void *) StopCallback); 160 | } 161 | 162 | ////////////////////////////////////////////////////////////////// 163 | 164 | // low level function to modify a PWM channel 165 | // returns the true on success or false on failure 166 | bool modifyPWMChannel(const uint8_t& channelNum, const uint32_t& pin, const float& frequency, const float& dutycycle) 167 | { 168 | uint32_t period = 0; 169 | 170 | if ( ( frequency > 0.0 ) && ( frequency <= 500.0 ) ) 171 | { 172 | #if USING_MICROS_RESOLUTION 173 | // period in us 174 | period = 1000000.0f / frequency; 175 | #else 176 | // period in ms 177 | period = 1000.0f / frequency; 178 | #endif 179 | } 180 | else 181 | { 182 | PWM_LOGERROR("Error: Invalid frequency, max is 500Hz"); 183 | return false; 184 | } 185 | 186 | return modifyPWMChannel_Period(channelNum, pin, period, dutycycle); 187 | } 188 | 189 | //period in us 190 | bool modifyPWMChannel_Period(const uint8_t& channelNum, const uint32_t& pin, const uint32_t& period, const float& dutycycle); 191 | 192 | // destroy the specified PWM channel 193 | void deleteChannel(const uint8_t& channelNum); 194 | 195 | // restart the specified PWM channel 196 | void restartChannel(const uint8_t& channelNum); 197 | 198 | // returns true if the specified PWM channel is enabled 199 | bool isEnabled(const uint8_t& channelNum); 200 | 201 | // enables the specified PWM channel 202 | void enable(const uint8_t& channelNum); 203 | 204 | // disables the specified PWM channel 205 | void disable(const uint8_t& channelNum); 206 | 207 | // enables all PWM channels 208 | void enableAll(); 209 | 210 | // disables all PWM channels 211 | void disableAll(); 212 | 213 | // enables the specified PWM channel if it's currently disabled, and vice-versa 214 | void toggle(const uint8_t& channelNum); 215 | 216 | // returns the number of used PWM channels 217 | int8_t getnumChannels(); 218 | 219 | // returns the number of available PWM channels 220 | uint8_t getNumAvailablePWMChannels() 221 | { 222 | return MAX_NUMBER_CHANNELS - numChannels; 223 | }; 224 | 225 | private: 226 | 227 | // low level function to initialize and enable a new PWM channel 228 | // returns the PWM channel number (channelNum) on success or 229 | // -1 on failure (f == NULL) or no free PWM channels 230 | int setupPWMChannel(const uint32_t& pin, const uint32_t& period, const float& dutycycle, void* cbStartFunc = nullptr, void* cbStopFunc = nullptr); 231 | 232 | // find the first available slot 233 | int findFirstFreeSlot(); 234 | 235 | typedef struct 236 | { 237 | /////////////////////////////////// 238 | 239 | 240 | /////////////////////////////////// 241 | 242 | uint64_t prevTime; // value returned by the micros() or millis() function in the previous run() call 243 | uint32_t period; // period value, in us / ms 244 | uint32_t onTime; // onTime value, ( period * dutyCycle / 100 ) us / ms 245 | 246 | void* callbackStart; // pointer to the callback function when PWM pulse starts (HIGH) 247 | void* callbackStop; // pointer to the callback function when PWM pulse stops (LOW) 248 | 249 | //////////////////////////////////////////////////////////// 250 | 251 | uint32_t pin; // PWM pin 252 | bool pinHigh; // true if PWM pin is HIGH 253 | //////////////////////////////////////////////////////////// 254 | 255 | bool enabled; // true if enabled 256 | 257 | // New from v1.2.1 258 | uint32_t newPeriod; // period value, in us / ms 259 | uint32_t newOnTime; // onTime value, ( period * dutyCycle / 100 ) us / ms 260 | float newDutyCycle; // from 0.00 to 100.00, float precision 261 | ////// 262 | } PWM_t; 263 | 264 | volatile PWM_t PWM[MAX_NUMBER_CHANNELS]; 265 | 266 | // actual number of PWM channels in use (-1 means uninitialized) 267 | volatile int8_t numChannels; 268 | 269 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during ISR 270 | portMUX_TYPE PWM_Mux = portMUX_INITIALIZER_UNLOCKED; 271 | }; 272 | 273 | 274 | #endif // PWM_ISR_GENERIC_HPP 275 | 276 | 277 | -------------------------------------------------------------------------------- /src/ESP32_PWM_ISR_Impl.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | ESP32_PWM_ISR_Impl.h 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | 24 | Version: 1.3.3 25 | 26 | Version Modified By Date Comments 27 | ------- ----------- ---------- ----------- 28 | 1.0.0 K Hoang 20/09/2021 Initial coding for ESP32, ESP32_S2, ESP32_C3 boards with ESP32 core v2.0.0+ 29 | 1.0.1 K Hoang 21/09/2021 Fix bug. Ading PWM end-of-duty-cycle callback function. Improve examples 30 | 1.1.0 K Hoang 06/11/2021 Add functions to modify PWM settings on-the-fly 31 | 1.1.1 K Hoang 09/11/2021 Fix examples to not use GPIO1/TX0 for core v2.0.1+ 32 | 1.2.0 K Hoang 29/01/2022 Fix multiple-definitions linker error. Improve accuracy. Fix bug 33 | 1.2.1 K Hoang 30/01/2022 DutyCycle to be updated at the end current PWM period 34 | 1.2.2 K Hoang 01/02/2022 Use float for DutyCycle and Freq, uint32_t for period. Optimize code 35 | 1.3.0 K Hoang 12/02/2022 Add support to new ESP32-S3 36 | 1.3.1 K Hoang 04/03/2022 Fix `DutyCycle` and `New Period` display bugs. Display warning only when debug level > 3 37 | 1.3.2 K Hoang 09/05/2022 Remove crashing PIN_D24 from examples 38 | 1.3.3 K Hoang 16/06/2022 Add support to new Adafruit boards 39 | *****************************************************************************************************************************/ 40 | 41 | #pragma once 42 | 43 | #ifndef PWM_ISR_GENERIC_IMPL_H 44 | #define PWM_ISR_GENERIC_IMPL_H 45 | 46 | #include 47 | 48 | /////////////////////////////////////////////////// 49 | 50 | 51 | uint64_t IRAM_ATTR timeNow() 52 | { 53 | #if USING_MICROS_RESOLUTION 54 | return ( (uint64_t) micros() ); 55 | #else 56 | return ( (uint64_t) millis() ); 57 | #endif 58 | } 59 | 60 | /////////////////////////////////////////////////// 61 | 62 | ESP32_PWM_ISR::ESP32_PWM_ISR() 63 | : numChannels (-1) 64 | { 65 | } 66 | 67 | /////////////////////////////////////////////////// 68 | 69 | void ESP32_PWM_ISR::init() 70 | { 71 | uint64_t currentTime = timeNow(); 72 | 73 | for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) 74 | { 75 | memset((void*) &PWM[channelNum], 0, sizeof (PWM_t)); 76 | PWM[channelNum].prevTime = currentTime; 77 | PWM[channelNum].pin = INVALID_ESP32_PIN; 78 | } 79 | 80 | numChannels = 0; 81 | 82 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during ISR 83 | PWM_Mux = portMUX_INITIALIZER_UNLOCKED; 84 | } 85 | 86 | /////////////////////////////////////////////////// 87 | 88 | void IRAM_ATTR ESP32_PWM_ISR::run() 89 | { 90 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during ISR 91 | portENTER_CRITICAL_ISR(&PWM_Mux); 92 | 93 | uint64_t currentTime = timeNow(); 94 | 95 | for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) 96 | { 97 | // If enabled => check 98 | // start period / dutyCycle => digitalWrite HIGH 99 | // end dutyCycle => digitalWrite LOW 100 | if (PWM[channelNum].enabled) 101 | { 102 | if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) <= PWM[channelNum].onTime ) 103 | { 104 | if (!PWM[channelNum].pinHigh) 105 | { 106 | digitalWrite(PWM[channelNum].pin, HIGH); 107 | PWM[channelNum].pinHigh = true; 108 | 109 | // callbackStart 110 | if (PWM[channelNum].callbackStart != nullptr) 111 | { 112 | (*(timer_callback) PWM[channelNum].callbackStart)(); 113 | } 114 | } 115 | } 116 | else if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) < PWM[channelNum].period ) 117 | { 118 | if (PWM[channelNum].pinHigh) 119 | { 120 | digitalWrite(PWM[channelNum].pin, LOW); 121 | PWM[channelNum].pinHigh = false; 122 | 123 | // callback when PWM pulse stops (LOW) 124 | if (PWM[channelNum].callbackStop != nullptr) 125 | { 126 | (*(timer_callback) PWM[channelNum].callbackStop)(); 127 | } 128 | } 129 | } 130 | //else 131 | else if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) >= PWM[channelNum].period ) 132 | { 133 | PWM[channelNum].prevTime = currentTime; 134 | 135 | #if CHANGING_PWM_END_OF_CYCLE 136 | 137 | // Only update whenever having newPeriod 138 | if (PWM[channelNum].newPeriod != 0) 139 | { 140 | PWM[channelNum].period = PWM[channelNum].newPeriod; 141 | PWM[channelNum].newPeriod = 0; 142 | 143 | PWM[channelNum].onTime = PWM[channelNum].newOnTime; 144 | } 145 | 146 | #endif 147 | } 148 | } 149 | } 150 | 151 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during ISR 152 | portEXIT_CRITICAL_ISR(&PWM_Mux); 153 | } 154 | 155 | 156 | /////////////////////////////////////////////////// 157 | 158 | // find the first available slot 159 | // return -1 if none found 160 | int ESP32_PWM_ISR::findFirstFreeSlot() 161 | { 162 | // all slots are used 163 | if (numChannels >= MAX_NUMBER_CHANNELS) 164 | { 165 | return -1; 166 | } 167 | 168 | // return the first slot with no callbackStart (i.e. free) 169 | for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) 170 | { 171 | if ( (PWM[channelNum].period == 0) && !PWM[channelNum].enabled ) 172 | { 173 | return channelNum; 174 | } 175 | } 176 | 177 | // no free slots found 178 | return -1; 179 | } 180 | 181 | /////////////////////////////////////////////////// 182 | 183 | int ESP32_PWM_ISR::setupPWMChannel(const uint32_t& pin, const uint32_t& period, const float& dutycycle, 184 | void* cbStartFunc, void* cbStopFunc) 185 | { 186 | int channelNum; 187 | 188 | // Invalid input, such as period = 0, etc 189 | if ( (period == 0) || (dutycycle < 0.0) || (dutycycle > 100.0) ) 190 | { 191 | PWM_LOGERROR("Error: Invalid period or dutycycle"); 192 | return -1; 193 | } 194 | 195 | if (numChannels < 0) 196 | { 197 | init(); 198 | } 199 | 200 | channelNum = findFirstFreeSlot(); 201 | 202 | if (channelNum < 0) 203 | { 204 | return -1; 205 | } 206 | 207 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 208 | portENTER_CRITICAL(&PWM_Mux); 209 | 210 | PWM[channelNum].pin = pin; 211 | PWM[channelNum].period = period; 212 | 213 | // Must be 0 for new PWM channel 214 | PWM[channelNum].newPeriod = 0; 215 | 216 | PWM[channelNum].onTime = ( period * dutycycle ) / 100; 217 | 218 | pinMode(pin, OUTPUT); 219 | digitalWrite(pin, HIGH); 220 | PWM[channelNum].pinHigh = true; 221 | 222 | PWM[channelNum].prevTime = timeNow(); 223 | 224 | PWM[channelNum].callbackStart = cbStartFunc; 225 | PWM[channelNum].callbackStop = cbStopFunc; 226 | 227 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 228 | portEXIT_CRITICAL(&PWM_Mux); 229 | 230 | PWM_LOGINFO0("Channel : "); 231 | PWM_LOGINFO0(channelNum); 232 | PWM_LOGINFO0("\t Period : "); 233 | PWM_LOGINFO0(PWM[channelNum].period); 234 | PWM_LOGINFO0("\t\tOnTime : "); 235 | PWM_LOGINFO0(PWM[channelNum].onTime); 236 | PWM_LOGINFO0("\tStart_Time : "); 237 | PWM_LOGINFOLN0(PWM[channelNum].prevTime); 238 | 239 | numChannels++; 240 | 241 | PWM[channelNum].enabled = true; 242 | 243 | return channelNum; 244 | } 245 | 246 | /////////////////////////////////////////////////// 247 | 248 | bool ESP32_PWM_ISR::modifyPWMChannel_Period(const uint8_t& channelNum, const uint32_t& pin, const uint32_t& period, 249 | const float& dutycycle) 250 | { 251 | // Invalid input, such as period = 0, etc 252 | if ( (period == 0) || (dutycycle < 0.0) || (dutycycle > 100.0) ) 253 | { 254 | PWM_LOGERROR("Error: Invalid period or dutycycle"); 255 | return false; 256 | } 257 | 258 | if (channelNum > MAX_NUMBER_CHANNELS) 259 | { 260 | PWM_LOGERROR("Error: channelNum > MAX_NUMBER_CHANNELS"); 261 | return false; 262 | } 263 | 264 | if (PWM[channelNum].pin != pin) 265 | { 266 | PWM_LOGERROR("Error: channelNum and pin mismatched"); 267 | return false; 268 | } 269 | 270 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 271 | portENTER_CRITICAL(&PWM_Mux); 272 | 273 | #if CHANGING_PWM_END_OF_CYCLE 274 | 275 | PWM[channelNum].newPeriod = period; 276 | PWM[channelNum].newDutyCycle = dutycycle; 277 | PWM[channelNum].newOnTime = ( period * dutycycle ) / 100; 278 | 279 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 280 | portEXIT_CRITICAL(&PWM_Mux); 281 | 282 | PWM_LOGINFO0("Channel : "); 283 | PWM_LOGINFO0(channelNum); 284 | PWM_LOGINFO0("\t Period : "); 285 | PWM_LOGINFO0(period); 286 | PWM_LOGINFO0("\t\tOnTime : "); 287 | PWM_LOGINFO0(PWM[channelNum].newOnTime); 288 | PWM_LOGINFO0("\tStart_Time : "); 289 | PWM_LOGINFOLN0(PWM[channelNum].prevTime); 290 | 291 | #else 292 | 293 | PWM[channelNum].period = period; 294 | PWM[channelNum].onTime = ( period * dutycycle ) / 100; 295 | 296 | digitalWrite(pin, HIGH); 297 | PWM[channelNum].pinHigh = true; 298 | 299 | PWM[channelNum].prevTime = timeNow(); 300 | 301 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 302 | portEXIT_CRITICAL(&PWM_Mux); 303 | 304 | PWM_LOGINFO0("Channel : "); 305 | PWM_LOGINFO0(channelNum); 306 | PWM_LOGINFO0("\t Period : "); 307 | PWM_LOGINFO0(PWM[channelNum].period); 308 | PWM_LOGINFO0("\t\tOnTime : "); 309 | PWM_LOGINFO0(PWM[channelNum].onTime); 310 | PWM_LOGINFO0("\tStart_Time : "); 311 | PWM_LOGINFOLN0(PWM[channelNum].prevTime); 312 | 313 | #endif 314 | 315 | return true; 316 | } 317 | 318 | 319 | /////////////////////////////////////////////////// 320 | 321 | void ESP32_PWM_ISR::deleteChannel(const uint8_t& channelNum) 322 | { 323 | // nothing to delete if no timers are in use 324 | if ( (channelNum >= MAX_NUMBER_CHANNELS) || (numChannels == 0) ) 325 | { 326 | return; 327 | } 328 | 329 | // don't decrease the number of timers if the specified slot is already empty (zero period, invalid) 330 | if ( (PWM[channelNum].pin != INVALID_ESP32_PIN) && (PWM[channelNum].period != 0) ) 331 | { 332 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 333 | portENTER_CRITICAL(&PWM_Mux); 334 | 335 | memset((void*) &PWM[channelNum], 0, sizeof (PWM_t)); 336 | 337 | PWM[channelNum].pin = INVALID_ESP32_PIN; 338 | 339 | // update number of timers 340 | numChannels--; 341 | 342 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 343 | portEXIT_CRITICAL(&PWM_Mux); 344 | 345 | } 346 | } 347 | 348 | /////////////////////////////////////////////////// 349 | 350 | // function contributed by code@rowansimms.com 351 | void ESP32_PWM_ISR::restartChannel(const uint8_t& channelNum) 352 | { 353 | if (channelNum >= MAX_NUMBER_CHANNELS) 354 | { 355 | return; 356 | } 357 | 358 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 359 | portENTER_CRITICAL(&PWM_Mux); 360 | 361 | PWM[channelNum].prevTime = timeNow(); 362 | 363 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 364 | portEXIT_CRITICAL(&PWM_Mux); 365 | } 366 | 367 | /////////////////////////////////////////////////// 368 | 369 | bool ESP32_PWM_ISR::isEnabled(const uint8_t& channelNum) 370 | { 371 | if (channelNum >= MAX_NUMBER_CHANNELS) 372 | { 373 | return false; 374 | } 375 | 376 | return PWM[channelNum].enabled; 377 | } 378 | 379 | /////////////////////////////////////////////////// 380 | 381 | void ESP32_PWM_ISR::enable(const uint8_t& channelNum) 382 | { 383 | if (channelNum >= MAX_NUMBER_CHANNELS) 384 | { 385 | return; 386 | } 387 | 388 | PWM[channelNum].enabled = true; 389 | } 390 | 391 | /////////////////////////////////////////////////// 392 | 393 | void ESP32_PWM_ISR::disable(const uint8_t& channelNum) 394 | { 395 | if (channelNum >= MAX_NUMBER_CHANNELS) 396 | { 397 | return; 398 | } 399 | 400 | PWM[channelNum].enabled = false; 401 | } 402 | 403 | /////////////////////////////////////////////////// 404 | 405 | void ESP32_PWM_ISR::enableAll() 406 | { 407 | // Enable all timers with a callbackStart assigned (used) 408 | 409 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 410 | portENTER_CRITICAL(&PWM_Mux); 411 | 412 | for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) 413 | { 414 | if (PWM[channelNum].period != 0) 415 | { 416 | PWM[channelNum].enabled = true; 417 | } 418 | } 419 | 420 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 421 | portEXIT_CRITICAL(&PWM_Mux); 422 | } 423 | 424 | /////////////////////////////////////////////////// 425 | 426 | void ESP32_PWM_ISR::disableAll() 427 | { 428 | // Disable all timers with a callbackStart assigned (used) 429 | 430 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 431 | portENTER_CRITICAL(&PWM_Mux); 432 | 433 | for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) 434 | { 435 | if (PWM[channelNum].period != 0) 436 | { 437 | PWM[channelNum].enabled = false; 438 | } 439 | } 440 | 441 | // ESP32 is a multi core / multi processing chip. It is mandatory to disable task switches during modifying shared vars 442 | portEXIT_CRITICAL(&PWM_Mux); 443 | 444 | } 445 | 446 | /////////////////////////////////////////////////// 447 | 448 | void ESP32_PWM_ISR::toggle(const uint8_t& channelNum) 449 | { 450 | if (channelNum >= MAX_NUMBER_CHANNELS) 451 | { 452 | return; 453 | } 454 | 455 | PWM[channelNum].enabled = !PWM[channelNum].enabled; 456 | } 457 | 458 | /////////////////////////////////////////////////// 459 | 460 | int8_t ESP32_PWM_ISR::getnumChannels() 461 | { 462 | return numChannels; 463 | } 464 | 465 | #endif // PWM_ISR_GENERIC_IMPL_H 466 | 467 | -------------------------------------------------------------------------------- /examples/ISR_16_PWMs_Array_Complex/ISR_16_PWMs_Array_Complex.ino: -------------------------------------------------------------------------------- 1 | /**************************************************************************************************************************** 2 | ISR_16_PWMs_Array_Complex.ino 3 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 4 | Written by Khoi Hoang 5 | 6 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 7 | Licensed under MIT license 8 | 9 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 10 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 11 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 12 | 13 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 14 | The timer counters can be configured to count up or down and support automatic reload and software reload. 15 | They can also generate alarms when they reach a specific value, defined by the software. 16 | The value of the counter can be read by the software program. 17 | 18 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 19 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 20 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 21 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 22 | This important feature is absolutely necessary for mission-critical tasks. 23 | *****************************************************************************************************************************/ 24 | 25 | #if !defined( ESP32 ) 26 | #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. 27 | #endif 28 | 29 | // These define's must be placed at the beginning before #include "ESP32_PWM.h" 30 | // _PWM_LOGLEVEL_ from 0 to 4 31 | // Don't define _PWM_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system. 32 | #define _PWM_LOGLEVEL_ 3 33 | 34 | #define USING_MICROS_RESOLUTION true //false 35 | 36 | // Default is true, uncomment to false 37 | //#define CHANGING_PWM_END_OF_CYCLE false 38 | 39 | // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error 40 | #include "ESP32_PWM.h" 41 | 42 | #include // https://github.com/jfturcot/SimpleTimer 43 | 44 | #ifndef LED_BUILTIN 45 | #define LED_BUILTIN 2 46 | #endif 47 | 48 | #ifndef LED_BLUE 49 | #define LED_BLUE 25 50 | #endif 51 | 52 | #ifndef LED_RED 53 | #define LED_RED 27 54 | #endif 55 | 56 | #define HW_TIMER_INTERVAL_US 20L 57 | 58 | volatile uint32_t startMicros = 0; 59 | 60 | // Init ESP32 timer 1 61 | ESP32Timer ITimer(1); 62 | 63 | // Init ESP32_ISR_PWM 64 | ESP32_PWM ISR_PWM; 65 | 66 | bool IRAM_ATTR TimerHandler(void * timerNo) 67 | { 68 | ISR_PWM.run(); 69 | 70 | return true; 71 | } 72 | 73 | ///////////////////////////////////////////////// 74 | 75 | #if ( ARDUINO_ESP32C3_DEV ) 76 | #define NUMBER_ISR_PWMS 4 77 | #elif ( ARDUINO_ESP32S3_DEV ) 78 | #define NUMBER_ISR_PWMS 16 79 | #else 80 | #define NUMBER_ISR_PWMS 16 81 | #endif 82 | 83 | #define PIN_D0 0 // Pin D0 mapped to pin GPIO0/BOOT/ADC11/TOUCH1 of ESP32 84 | #define PIN_D1 1 // Pin D1 mapped to pin GPIO1/TX0 of ESP32 85 | #define PIN_D2 2 // Pin D2 mapped to pin GPIO2/ADC12/TOUCH2 of ESP32 86 | #define PIN_D3 3 // Pin D3 mapped to pin GPIO3/RX0 of ESP32 87 | #define PIN_D4 4 // Pin D4 mapped to pin GPIO4/ADC10/TOUCH0 of ESP32 88 | #define PIN_D5 5 // Pin D5 mapped to pin GPIO5/SPISS/VSPI_SS of ESP32 89 | #define PIN_D6 6 // Pin D6 mapped to pin GPIO6 of ESP32 90 | #define PIN_D7 7 // Pin D7 mapped to pin GPIO7 of ESP32 91 | #define PIN_D8 8 // Pin D8 mapped to pin GPIO8 of ESP32 92 | #define PIN_D9 9 // Pin D9 mapped to pin GPIO9 of ESP32 93 | #define PIN_D10 10 // Pin D10 mapped to pin GPIO10 of ESP32 94 | #define PIN_D11 11 // Pin D11 mapped to pin GPIO11 of ESP32 95 | #define PIN_D12 12 // Pin D12 mapped to pin GPIO12/HSPI_MISO/ADC15/TOUCH5/TDI of ESP32 96 | #define PIN_D13 13 // Pin D13 mapped to pin GPIO13/HSPI_MOSI/ADC14/TOUCH4/TCK of ESP32 97 | #define PIN_D14 14 // Pin D14 mapped to pin GPIO14/HSPI_SCK/ADC16/TOUCH6/TMS of ESP32 98 | #define PIN_D15 15 // Pin D15 mapped to pin GPIO15/HSPI_SS/ADC13/TOUCH3/TDO of ESP32 99 | #define PIN_D16 16 // Pin D16 mapped to pin GPIO16/TX2 of ESP32 100 | #define PIN_D17 17 // Pin D17 mapped to pin GPIO17/RX2 of ESP32 101 | #define PIN_D18 18 // Pin D18 mapped to pin GPIO18/VSPI_SCK of ESP32 102 | #define PIN_D19 19 // Pin D19 mapped to pin GPIO19/VSPI_MISO of ESP32 103 | 104 | #define PIN_D21 21 // Pin D21 mapped to pin GPIO21/SDA of ESP32 105 | #define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 106 | #define PIN_D23 23 // Pin D23 mapped to pin GPIO23/VSPI_MOSI of ESP32 107 | #define PIN_D25 25 // Pin D25 mapped to pin GPIO25/ADC18/DAC1 of ESP32 108 | #define PIN_D26 26 // Pin D26 mapped to pin GPIO26/ADC19/DAC2 of ESP32 109 | #define PIN_D27 27 // Pin D27 mapped to pin GPIO27/ADC17/TOUCH7 of ESP32 110 | 111 | typedef void (*irqCallback) (); 112 | 113 | ////////////////////////////////////////////////////// 114 | 115 | #define USING_PWM_FREQUENCY false //true 116 | 117 | ////////////////////////////////////////////////////// 118 | 119 | volatile unsigned long deltaMicrosStart [] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 120 | volatile unsigned long previousMicrosStart [] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 121 | 122 | volatile unsigned long deltaMicrosStop [] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 123 | volatile unsigned long previousMicrosStop [] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 124 | 125 | // You can assign pins here. Be carefull to select good pin to use or crash, e.g pin 6-11 126 | // Can't use PIN_D1 for core v2.0.1+ 127 | 128 | #if ( ARDUINO_ESP32C3_DEV ) 129 | uint32_t PWM_Pin[] = 130 | // Bad pins to use: PIN_D12-PIN_D24 131 | { 132 | LED_BUILTIN, PIN_D3, PIN_D4, PIN_D5 133 | }; 134 | #elif ( ARDUINO_ESP32S3_DEV ) 135 | uint32_t PWM_Pin[] = 136 | // Bad pins to use: PIN_D24 137 | { 138 | PIN_D1, PIN_D2, PIN_D3, PIN_D4, PIN_D5, PIN_D6, PIN_D7, PIN_D8, 139 | PIN_D9, PIN_D10, PIN_D11, PIN_D12, PIN_D13, PIN_D14, PIN_D15, PIN_D16, 140 | }; 141 | #else 142 | // Bad pins to use: PIN_D24 143 | uint32_t PWM_Pin[] = 144 | { 145 | LED_BUILTIN, PIN_D25, PIN_D3, PIN_D4, PIN_D5, PIN_D12, PIN_D13, PIN_D14, 146 | PIN_D15, PIN_D16, PIN_D17, PIN_D18, PIN_D19, PIN_D21, PIN_D22, PIN_D23 147 | }; 148 | #endif 149 | 150 | // You can assign any interval for any timer here, in microseconds 151 | uint32_t PWM_Period[] = 152 | { 153 | 1000000, 500000, 333333, 250000, 200000, 166667, 142857, 125000, 154 | 111111, 100000, 66667, 50000, 40000, 33333, 25000, 20000 155 | }; 156 | 157 | // You can assign any interval for any timer here, in Hz 158 | float PWM_Freq[] = 159 | { 160 | 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, 161 | 9.0f, 10.0f, 15.0f, 20.0f, 25.0f, 30.0f, 40.0f, 50.0f 162 | }; 163 | 164 | // You can assign any interval for any timer here, in milliseconds 165 | float PWM_DutyCycle[] = 166 | { 167 | 5.00, 10.00, 20.00, 30.00, 40.00, 45.00, 50.00, 55.00, 168 | 60.00, 65.00, 70.00, 75.00, 80.00, 85.00, 90.00, 95.00 169 | }; 170 | 171 | void doingSomethingStart(int index) 172 | { 173 | unsigned long currentMicros = micros(); 174 | 175 | deltaMicrosStart[index] = currentMicros - previousMicrosStart[index]; 176 | previousMicrosStart[index] = currentMicros; 177 | } 178 | 179 | void doingSomethingStop(int index) 180 | { 181 | unsigned long currentMicros = micros(); 182 | 183 | // Count from start to stop PWM pulse 184 | deltaMicrosStop[index] = currentMicros - previousMicrosStart[index]; 185 | previousMicrosStop[index] = currentMicros; 186 | } 187 | 188 | //////////////////////////////////// 189 | // Shared 190 | //////////////////////////////////// 191 | 192 | void doingSomethingStart0() 193 | { 194 | doingSomethingStart(0); 195 | } 196 | 197 | void doingSomethingStart1() 198 | { 199 | doingSomethingStart(1); 200 | } 201 | 202 | void doingSomethingStart2() 203 | { 204 | doingSomethingStart(2); 205 | } 206 | 207 | void doingSomethingStart3() 208 | { 209 | doingSomethingStart(3); 210 | } 211 | 212 | void doingSomethingStart4() 213 | { 214 | doingSomethingStart(4); 215 | } 216 | 217 | void doingSomethingStart5() 218 | { 219 | doingSomethingStart(5); 220 | } 221 | 222 | void doingSomethingStart6() 223 | { 224 | doingSomethingStart(6); 225 | } 226 | 227 | void doingSomethingStart7() 228 | { 229 | doingSomethingStart(7); 230 | } 231 | 232 | void doingSomethingStart8() 233 | { 234 | doingSomethingStart(8); 235 | } 236 | 237 | void doingSomethingStart9() 238 | { 239 | doingSomethingStart(9); 240 | } 241 | 242 | void doingSomethingStart10() 243 | { 244 | doingSomethingStart(10); 245 | } 246 | 247 | void doingSomethingStart11() 248 | { 249 | doingSomethingStart(11); 250 | } 251 | 252 | void doingSomethingStart12() 253 | { 254 | doingSomethingStart(12); 255 | } 256 | 257 | void doingSomethingStart13() 258 | { 259 | doingSomethingStart(13); 260 | } 261 | 262 | void doingSomethingStart14() 263 | { 264 | doingSomethingStart(14); 265 | } 266 | 267 | void doingSomethingStart15() 268 | { 269 | doingSomethingStart(15); 270 | } 271 | 272 | ////////////////////////////////////////////////////// 273 | 274 | void doingSomethingStop0() 275 | { 276 | doingSomethingStop(0); 277 | } 278 | 279 | void doingSomethingStop1() 280 | { 281 | doingSomethingStop(1); 282 | } 283 | 284 | void doingSomethingStop2() 285 | { 286 | doingSomethingStop(2); 287 | } 288 | 289 | void doingSomethingStop3() 290 | { 291 | doingSomethingStop(3); 292 | } 293 | 294 | void doingSomethingStop4() 295 | { 296 | doingSomethingStop(4); 297 | } 298 | 299 | void doingSomethingStop5() 300 | { 301 | doingSomethingStop(5); 302 | } 303 | 304 | void doingSomethingStop6() 305 | { 306 | doingSomethingStop(6); 307 | } 308 | 309 | void doingSomethingStop7() 310 | { 311 | doingSomethingStop(7); 312 | } 313 | 314 | void doingSomethingStop8() 315 | { 316 | doingSomethingStop(8); 317 | } 318 | 319 | void doingSomethingStop9() 320 | { 321 | doingSomethingStop(9); 322 | } 323 | 324 | void doingSomethingStop10() 325 | { 326 | doingSomethingStop(10); 327 | } 328 | 329 | void doingSomethingStop11() 330 | { 331 | doingSomethingStop(11); 332 | } 333 | 334 | void doingSomethingStop12() 335 | { 336 | doingSomethingStop(12); 337 | } 338 | 339 | void doingSomethingStop13() 340 | { 341 | doingSomethingStop(13); 342 | } 343 | 344 | void doingSomethingStop14() 345 | { 346 | doingSomethingStop(14); 347 | } 348 | 349 | void doingSomethingStop15() 350 | { 351 | doingSomethingStop(15); 352 | } 353 | 354 | ////////////////////////////////////////////////////// 355 | 356 | irqCallback irqCallbackStartFunc[] = 357 | { 358 | doingSomethingStart0, doingSomethingStart1, doingSomethingStart2, doingSomethingStart3, 359 | doingSomethingStart4, doingSomethingStart5, doingSomethingStart6, doingSomethingStart7, 360 | doingSomethingStart8, doingSomethingStart9, doingSomethingStart10, doingSomethingStart11, 361 | doingSomethingStart12, doingSomethingStart13, doingSomethingStart14, doingSomethingStart15 362 | }; 363 | 364 | irqCallback irqCallbackStopFunc[] = 365 | { 366 | doingSomethingStop0, doingSomethingStop1, doingSomethingStop2, doingSomethingStop3, 367 | doingSomethingStop4, doingSomethingStop5, doingSomethingStop6, doingSomethingStop7, 368 | doingSomethingStop8, doingSomethingStop9, doingSomethingStop10, doingSomethingStop11, 369 | doingSomethingStop12, doingSomethingStop13, doingSomethingStop14, doingSomethingStop15 370 | }; 371 | 372 | ////////////////////////////////////////////////////// 373 | 374 | #define SIMPLE_TIMER_MS 2000L 375 | 376 | // Init SimpleTimer 377 | SimpleTimer simpleTimer; 378 | 379 | // Here is software Timer, you can do somewhat fancy stuffs without many issues. 380 | // But always avoid 381 | // 1. Long delay() it just doing nothing and pain-without-gain wasting CPU power.Plan and design your code / strategy ahead 382 | // 2. Very long "do", "while", "for" loops without predetermined exit time. 383 | void simpleTimerDoingSomething2s() 384 | { 385 | static unsigned long previousMicrosStart = startMicros; 386 | 387 | unsigned long currMicros = micros(); 388 | 389 | Serial.print(F("SimpleTimer (ms): ")); Serial.print(SIMPLE_TIMER_MS); 390 | Serial.print(F(", us : ")); Serial.print(currMicros); 391 | Serial.print(F(", Dus : ")); Serial.println(currMicros - previousMicrosStart); 392 | 393 | for (uint16_t i = 0; i < NUMBER_ISR_PWMS; i++) 394 | { 395 | Serial.print(F("PWM Channel : ")); Serial.print(i); 396 | 397 | #if USING_PWM_FREQUENCY 398 | Serial.print(1000000 / PWM_Freq[i]); 399 | #else 400 | Serial.print(PWM_Period[i]); 401 | #endif 402 | 403 | Serial.print(F(", programmed Period (us): ")); Serial.print(PWM_Period[i]); 404 | Serial.print(F(", actual : ")); Serial.print(deltaMicrosStart[i]); 405 | 406 | Serial.print(F(", programmed DutyCycle : ")); 407 | 408 | Serial.print(PWM_DutyCycle[i]); 409 | 410 | Serial.print(F(", actual : ")); Serial.println( (float) deltaMicrosStop[i] * 100.0f / deltaMicrosStart[i]); 411 | } 412 | 413 | previousMicrosStart = currMicros; 414 | } 415 | 416 | void setup() 417 | { 418 | Serial.begin(115200); 419 | while (!Serial); 420 | 421 | delay(2000); 422 | 423 | Serial.print(F("\nStarting ISR_16_PWMs_Array_Complex on ")); Serial.println(ARDUINO_BOARD); 424 | Serial.println(ESP32_PWM_VERSION); 425 | Serial.print(F("CPU Frequency = ")); Serial.print(F_CPU / 1000000); Serial.println(F(" MHz")); 426 | 427 | // Interval in microsecs 428 | if (ITimer.attachInterruptInterval(HW_TIMER_INTERVAL_US, TimerHandler)) 429 | { 430 | startMicros = micros(); 431 | Serial.print(F("Starting ITimer OK, micros() = ")); Serial.println(startMicros); 432 | } 433 | else 434 | Serial.println(F("Can't set ITimer. Select another freq. or timer")); 435 | 436 | startMicros = micros(); 437 | 438 | // Just to demonstrate, don't use too many ISR Timers if not absolutely necessary 439 | // You can use up to 16 timer for each ISR_PWM 440 | for (uint16_t i = 0; i < NUMBER_ISR_PWMS; i++) 441 | { 442 | previousMicrosStart[i] = micros(); 443 | 444 | #if USING_PWM_FREQUENCY 445 | // You can use this with PWM_Freq in Hz 446 | ISR_PWM.setPWM(PWM_Pin[i], PWM_Freq[i], PWM_DutyCycle[i], irqCallbackStartFunc[i], irqCallbackStopFunc[i]); 447 | #else 448 | // Or You can use this with PWM_Period in us 449 | ISR_PWM.setPWM_Period(PWM_Pin[i], PWM_Period[i], PWM_DutyCycle[i], irqCallbackStartFunc[i], irqCallbackStopFunc[i]); 450 | #endif 451 | } 452 | 453 | 454 | // You need this timer for non-critical tasks. Avoid abusing ISR if not absolutely necessary. 455 | simpleTimer.setInterval(SIMPLE_TIMER_MS, simpleTimerDoingSomething2s); 456 | } 457 | 458 | #define BLOCKING_TIME_MS 10000L 459 | 460 | void loop() 461 | { 462 | // This unadvised blocking task is used to demonstrate the blocking effects onto the execution and accuracy to Software timer 463 | // You see the time elapse of ISR_PWM still accurate, whereas very unaccurate for Software Timer 464 | // The time elapse for 2000ms software timer now becomes 3000ms (BLOCKING_TIME_MS) 465 | // While that of ISR_PWM is still prefect. 466 | delay(BLOCKING_TIME_MS); 467 | 468 | // You need this Software timer for non-critical tasks. Avoid abusing ISR if not absolutely necessary 469 | // You don't need to and never call ISR_PWM.run() here in the loop(). It's already handled by ISR timer. 470 | simpleTimer.run(); 471 | } 472 | -------------------------------------------------------------------------------- /src/ESP32_PWM.hpp: -------------------------------------------------------------------------------- 1 | 2 | /**************************************************************************************************************************** 3 | ESP32_PWM.hpp 4 | For ESP32, ESP32_S2, ESP32_S3, ESP32_C3 boards with ESP32 core v2.0.0+ 5 | Written by Khoi Hoang 6 | 7 | Built by Khoi Hoang https://github.com/khoih-prog/ESP32_PWM 8 | Licensed under MIT license 9 | 10 | The ESP32, ESP32_S2, ESP32_S3, ESP32_C3 have two timer groups, TIMER_GROUP_0 and TIMER_GROUP_1 11 | 1) each group of ESP32, ESP32_S2, ESP32_S3 has two general purpose hardware timers, TIMER_0 and TIMER_1 12 | 2) each group of ESP32_C3 has ony one general purpose hardware timer, TIMER_0 13 | 14 | All the timers are based on 64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16 bit prescalers. 15 | The timer counters can be configured to count up or down and support automatic reload and software reload. 16 | They can also generate alarms when they reach a specific value, defined by the software. 17 | The value of the counter can be read by the software program. 18 | 19 | Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by 20 | unsigned long miliseconds), you just consume only one ESP32-S2 timer and avoid conflicting with other cores' tasks. 21 | The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers 22 | Therefore, their executions are not blocked by bad-behaving functions / tasks. 23 | This important feature is absolutely necessary for mission-critical tasks. 24 | 25 | Version: 1.3.3 26 | 27 | Version Modified By Date Comments 28 | ------- ----------- ---------- ----------- 29 | 1.0.0 K Hoang 20/09/2021 Initial coding for ESP32, ESP32_S2, ESP32_C3 boards with ESP32 core v2.0.0+ 30 | 1.0.1 K Hoang 21/09/2021 Fix bug. Ading PWM end-of-duty-cycle callback function. Improve examples 31 | 1.1.0 K Hoang 06/11/2021 Add functions to modify PWM settings on-the-fly 32 | 1.1.1 K Hoang 09/11/2021 Fix examples to not use GPIO1/TX0 for core v2.0.1+ 33 | 1.2.0 K Hoang 29/01/2022 Fix multiple-definitions linker error. Improve accuracy. Fix bug 34 | 1.2.1 K Hoang 30/01/2022 DutyCycle to be updated at the end current PWM period 35 | 1.2.2 K Hoang 01/02/2022 Use float for DutyCycle and Freq, uint32_t for period. Optimize code 36 | 1.3.0 K Hoang 12/02/2022 Add support to new ESP32-S3 37 | 1.3.1 K Hoang 04/03/2022 Fix `DutyCycle` and `New Period` display bugs. Display warning only when debug level > 3 38 | 1.3.2 K Hoang 09/05/2022 Remove crashing PIN_D24 from examples 39 | 1.3.3 K Hoang 16/06/2022 Add support to new Adafruit boards 40 | *****************************************************************************************************************************/ 41 | 42 | #pragma once 43 | 44 | #ifndef ESP32_PWM_HPP 45 | #define ESP32_PWM_HPP 46 | 47 | #if !defined( ESP32 ) 48 | #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. 49 | #endif 50 | 51 | #if ( ARDUINO_ESP32S2_DEV || ARDUINO_FEATHERS2 || ARDUINO_ESP32S2_THING_PLUS || ARDUINO_MICROS2 || \ 52 | ARDUINO_METRO_ESP32S2 || ARDUINO_MAGTAG29_ESP32S2 || ARDUINO_FUNHOUSE_ESP32S2 || \ 53 | ARDUINO_ADAFRUIT_FEATHER_ESP32S2_NOPSRAM || ARDUINO_ADAFRUIT_QTPY_ESP32S2) 54 | #define USING_ESP32_S2_PWM true 55 | #elif ( defined(ARDUINO_ESP32S3_DEV) || defined(ARDUINO_ESP32_S3_BOX) || defined(ARDUINO_TINYS3) || \ 56 | defined(ARDUINO_PROS3) || defined(ARDUINO_FEATHERS3) || defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3_NOPSRAM) || \ 57 | defined(ARDUINO_ADAFRUIT_QTPY_ESP32S3_NOPSRAM)) 58 | #define USING_ESP32_S3_PWM true 59 | #elif ( ARDUINO_ESP32C3_DEV ) 60 | #define USING_ESP32_C3_PWM true 61 | #elif defined(ESP32) 62 | #define USING_ESP32_PWM true 63 | #else 64 | #error This code is ready to run on the ESP32 platform! Please check your Tools->Board setting. 65 | #endif 66 | 67 | #if defined(ARDUINO) 68 | #if ARDUINO >= 100 69 | #include 70 | #else 71 | #include 72 | #endif 73 | #endif 74 | 75 | #ifndef ESP32_PWM_VERSION 76 | #define ESP32_PWM_VERSION "ESP32_PWM v1.3.3" 77 | 78 | #define ESP32_PWM_VERSION_MAJOR 1 79 | #define ESP32_PWM_VERSION_MINOR 3 80 | #define ESP32_PWM_VERSION_PATCH 3 81 | 82 | #define ESP32_PWM_VERSION_INT 1003003 83 | #endif 84 | 85 | #ifndef TIMER_INTERRUPT_DEBUG 86 | #define TIMER_INTERRUPT_DEBUG 0 87 | #endif 88 | 89 | #include "PWM_Generic_Debug.h" 90 | 91 | #include 92 | 93 | /* 94 | //ESP32 core v1.0.6, hw_timer_t defined in esp32/tools/sdk/include/driver/driver/timer.h: 95 | 96 | #define TIMER_BASE_CLK (APB_CLK_FREQ) //Frequency of the clock on the input of the timer groups 97 | 98 | 99 | //@brief Selects a Timer-Group out of 2 available groups 100 | 101 | typedef enum 102 | { 103 | TIMER_GROUP_0 = 0, /*! Group 0, timerNo == 1 => Group 1 262 | _timerGroup = (timer_group_t) ( (uint32_t) timerNo); 263 | 264 | #else 265 | 266 | _timerIndex = (timer_idx_t) (_timerNo % TIMER_MAX); 267 | 268 | _timerGroup = (timer_group_t) (_timerNo / TIMER_MAX); 269 | 270 | #endif 271 | } 272 | else 273 | { 274 | _timerNo = MAX_ESP32_NUM_TIMERS; 275 | } 276 | }; 277 | 278 | // frequency (in hertz) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely 279 | // No params and duration now. To be addes in the future by adding similar functions here or to esp32-hal-timer.c 280 | bool setFrequency(const float& frequency, esp32_timer_callback callback) 281 | { 282 | if (_timerNo < MAX_ESP32_NUM_TIMERS) 283 | { 284 | // select timer frequency is 1MHz for better accuracy. We don't use 16-bit prescaler for now. 285 | // Will use later if very low frequency is needed. 286 | _frequency = TIMER_BASE_CLK / TIMER_DIVIDER; 287 | _timerCount = (uint64_t) _frequency / frequency; 288 | // count up 289 | 290 | #if USING_ESP32_S2_PWM 291 | PWM_LOGWARN3(F("ESP32_S2_TimerInterrupt: _timerNo ="), _timerNo, F(", _fre ="), TIMER_BASE_CLK / TIMER_DIVIDER); 292 | #elif USING_ESP32_S3_PWM 293 | // ESP32-S3 is embedded with four 54-bit general-purpose timers, which are based on 16-bit prescalers 294 | // and 54-bit auto-reload-capable up/down-timers 295 | PWM_LOGWARN3(F("ESP32_S3_TimerInterrupt: _timerNo ="), _timerNo, F(", _fre ="), TIMER_BASE_CLK / TIMER_DIVIDER); 296 | #else 297 | PWM_LOGWARN3(F("ESP32_TimerInterrupt: _timerNo ="), _timerNo, F(", _fre ="), TIMER_BASE_CLK / TIMER_DIVIDER); 298 | #endif 299 | 300 | PWM_LOGWARN3(F("TIMER_BASE_CLK ="), TIMER_BASE_CLK, F(", TIMER_DIVIDER ="), TIMER_DIVIDER); 301 | PWM_LOGWARN3(F("_timerIndex ="), _timerIndex, F(", _timerGroup ="), _timerGroup); 302 | PWM_LOGWARN3(F("_count ="), (uint32_t) (_timerCount >> 32) , F("-"), (uint32_t) (_timerCount)); 303 | PWM_LOGWARN1(F("timer_set_alarm_value ="), TIMER_SCALE / frequency); 304 | 305 | timer_init(_timerGroup, _timerIndex, &stdConfig); 306 | 307 | // Counter value to 0 => counting up to alarm value as .counter_dir == TIMER_COUNT_UP 308 | timer_set_counter_value(_timerGroup, _timerIndex , 0x00000000ULL); 309 | 310 | timer_set_alarm_value(_timerGroup, _timerIndex, TIMER_SCALE / frequency); 311 | 312 | // enable interrupts for _timerGroup, _timerIndex 313 | timer_enable_intr(_timerGroup, _timerIndex); 314 | 315 | _callback = callback; 316 | 317 | // Register the ISR handler 318 | // If the intr_alloc_flags value ESP_INTR_FLAG_IRAM is set, the handler function must be declared with IRAM_ATTR attribute 319 | // and can only call functions in IRAM or ROM. It cannot call other timer APIs. 320 | //timer_isr_register(_timerGroup, _timerIndex, _callback, (void *) (uint32_t) _timerNo, ESP_INTR_FLAG_IRAM, NULL); 321 | timer_isr_callback_add(_timerGroup, _timerIndex, _callback, (void *) (uint32_t) _timerNo, 0); 322 | 323 | timer_start(_timerGroup, _timerIndex); 324 | 325 | return true; 326 | } 327 | else 328 | { 329 | #if USING_ESP32_C3_PWM 330 | PWM_LOGERROR(F("Error. Timer must be 0-1")); 331 | #else 332 | PWM_LOGERROR(F("Error. Timer must be 0-3")); 333 | #endif 334 | 335 | return false; 336 | } 337 | } 338 | 339 | // interval (in microseconds) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely 340 | // No params and duration now. To be addes in the future by adding similar functions here or to esp32-hal-timer.c 341 | bool setInterval(const unsigned long& interval, esp32_timer_callback callback) 342 | { 343 | return setFrequency((float) (1000000.0f / interval), callback); 344 | } 345 | 346 | bool attachInterrupt(const float& frequency, esp32_timer_callback callback) 347 | { 348 | return setFrequency(frequency, callback); 349 | } 350 | 351 | // interval (in microseconds) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely 352 | // No params and duration now. To be addes in the future by adding similar functions here or to esp32-hal-timer.c 353 | bool attachInterruptInterval(const unsigned long& interval, esp32_timer_callback callback) 354 | { 355 | return setFrequency( (float) ( 1000000.0f / interval), callback); 356 | } 357 | 358 | void detachInterrupt() 359 | { 360 | #if USING_ESP32_C3_PWM 361 | timer_group_intr_disable(_timerGroup, TIMER_INTR_T0); 362 | #else 363 | timer_group_intr_disable(_timerGroup, (_timerIndex == 0) ? TIMER_INTR_T0 : TIMER_INTR_T1); 364 | #endif 365 | } 366 | 367 | void disableTimer() 368 | { 369 | #if USING_ESP32_C3_PWM 370 | timer_group_intr_disable(_timerGroup, TIMER_INTR_T0); 371 | #else 372 | timer_group_intr_disable(_timerGroup, (_timerIndex == 0) ? TIMER_INTR_T0 : TIMER_INTR_T1); 373 | #endif 374 | } 375 | 376 | // Duration (in milliseconds). Duration = 0 or not specified => run indefinitely 377 | void reattachInterrupt() 378 | { 379 | #if USING_ESP32_C3_PWM 380 | timer_group_intr_enable(_timerGroup, TIMER_INTR_T0); 381 | #else 382 | timer_group_intr_enable(_timerGroup, (_timerIndex == 0) ? TIMER_INTR_T0 : TIMER_INTR_T1); 383 | #endif 384 | } 385 | 386 | // Duration (in milliseconds). Duration = 0 or not specified => run indefinitely 387 | void enableTimer() 388 | { 389 | #if USING_ESP32_C3_PWM 390 | timer_group_intr_enable(_timerGroup, TIMER_INTR_T0); 391 | #else 392 | timer_group_intr_enable(_timerGroup, (_timerIndex == 0) ? TIMER_INTR_T0 : TIMER_INTR_T1); 393 | #endif 394 | } 395 | 396 | // Just stop clock source, clear the count 397 | void stopTimer() 398 | { 399 | timer_pause(_timerGroup, _timerIndex); 400 | } 401 | 402 | // Just reconnect clock source, start current count from 0 403 | void restartTimer() 404 | { 405 | timer_set_counter_value(_timerGroup, _timerIndex , 0x00000000ULL); 406 | timer_start(_timerGroup, _timerIndex); 407 | } 408 | 409 | int8_t getTimer() __attribute__((always_inline)) 410 | { 411 | return _timerIndex; 412 | }; 413 | 414 | int8_t getTimerGroup() __attribute__((always_inline)) 415 | { 416 | return _timerGroup; 417 | }; 418 | 419 | }; // class ESP32TimerInterrupt 420 | 421 | #include "ESP32_PWM_ISR.hpp" 422 | 423 | #endif // ESP32_PWM_HPP 424 | 425 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32_PWM Library 2 | 3 | [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP32_PWM.svg?)](https://www.ardu-badge.com/ESP32_PWM) 4 | [![GitHub release](https://img.shields.io/github/release/khoih-prog/ESP32_PWM.svg)](https://github.com/khoih-prog/ESP32_PWM/releases) 5 | [![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/khoih-prog/ESP32_PWM/blob/main/LICENSE) 6 | [![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) 7 | [![GitHub issues](https://img.shields.io/github/issues/khoih-prog/ESP32_PWM.svg)](http://github.com/khoih-prog/ESP32_PWM/issues) 8 | 9 | 10 | Donate to my libraries using BuyMeACoffee 11 | 12 | 13 | --- 14 | --- 15 | 16 | ## Table of Contents 17 | 18 | * [Important Change from v1.2.0](#Important-Change-from-v120) 19 | * [Why do we need this ESP32_PWM library](#why-do-we-need-this-ESP32_PWM-library) 20 | * [Why using ISR-based PWM-channels is better](#Why-using-ISR-based-PWM-channels-is-better) 21 | * [Currently supported Boards](#currently-supported-boards) 22 | * [Important Notes about ISR](#important-notes-about-isr) 23 | * [Changelog](changelog.md) 24 | * [Prerequisites](#prerequisites) 25 | * [Installation](#installation) 26 | * [Use Arduino Library Manager](#use-arduino-library-manager) 27 | * [Manual Install](#manual-install) 28 | * [VS Code & PlatformIO](#vs-code--platformio) 29 | * [HOWTO Fix `Multiple Definitions` Linker Error](#howto-fix-multiple-definitions-linker-error) 30 | * [HOWTO Use analogRead() with ESP32 running WiFi and/or BlueTooth (BT/BLE)](#howto-use-analogread-with-esp32-running-wifi-andor-bluetooth-btble) 31 | * [1. ESP32 has 2 ADCs, named ADC1 and ADC2](#1--esp32-has-2-adcs-named-adc1-and-adc2) 32 | * [2. ESP32 ADCs functions](#2-esp32-adcs-functions) 33 | * [3. ESP32 WiFi uses ADC2 for WiFi functions](#3-esp32-wifi-uses-adc2-for-wifi-functions) 34 | * [More useful Information](#more-useful-information) 35 | * [How to use](#how-to-use) 36 | * [Examples](#examples) 37 | * [ 1. ISR_16_PWMs_Array](examples/ISR_16_PWMs_Array) 38 | * [ 2. ISR_16_PWMs_Array_Complex](examples/ISR_16_PWMs_Array_Complex) 39 | * [ 3. ISR_16_PWMs_Array_Simple](examples/ISR_16_PWMs_Array_Simple) 40 | * [ 4. ISR_Changing_PWM](examples/ISR_Changing_PWM) 41 | * [ 5. ISR_Modify_PWM](examples/ISR_Modify_PWM) 42 | * [ 6. multiFileProject](examples/multiFileProject) **New** 43 | * [Example ISR_16_PWMs_Array_Complex](#Example-ISR_16_PWMs_Array_Complex) 44 | * [Debug Terminal Output Samples](#debug-terminal-output-samples) 45 | * [ 1. ISR_16_PWMs_Array_Complex on ESP32_DEV](#1-ISR_16_PWMs_Array_Complex-on-ESP32_DEV) 46 | * [ 2. ISR_16_PWMs_Array on ESP32_DEV](#2-ISR_16_PWMs_Array-on-ESP32_DEV) 47 | * [ 3. ISR_16_PWMs_Array_Simple on ESP32_DEV](#3-ISR_16_PWMs_Array_Simple-on-ESP32_DEV) 48 | * [ 4. ISR_Modify_PWM on ESP32_DEV](#4-ISR_Modify_PWM-on-ESP32_DEV) 49 | * [ 5. ISR_Changing_PWM on ESP32_DEV](#5-ISR_Changing_PWM-on-ESP32_DEV) 50 | * [ 6. ISR_Modify_PWM on ESP32S2_DEV](#6-ISR_Modify_PWM-on-ESP32S2_DEV) 51 | * [ 7. ISR_Changing_PWM on ESP32S2_DEV](#7-ISR_Changing_PWM-on-ESP32S2_DEV) 52 | * [ 8. ISR_Modify_PWM on ESP32C3_DEV](#8-ISR_Modify_PWM-on-ESP32C3_DEV) 53 | * [ 9. ISR_Changing_PWM on ESP32C3_DEV](#9-ISR_Changing_PWM-on-ESP32C3_DEV) 54 | * [10. ISR_16_PWMs_Array_Complex on ESP32S3_DEV](#10-ISR_16_PWMs_Array_Complex-on-ESP32S3_DEV) **New** 55 | * [Debug](#debug) 56 | * [Troubleshooting](#troubleshooting) 57 | * [Issues](#issues) 58 | * [TO DO](#to-do) 59 | * [DONE](#done) 60 | * [Contributions and Thanks](#contributions-and-thanks) 61 | * [Contributing](#contributing) 62 | * [License](#license) 63 | * [Copyright](#copyright) 64 | 65 | --- 66 | --- 67 | 68 | ### Important Change from v1.2.0 69 | 70 | Please have a look at [HOWTO Fix `Multiple Definitions` Linker Error](#howto-fix-multiple-definitions-linker-error) 71 | 72 | As more complex calculation and check **inside ISR** are introduced from v1.2.0, there is possibly some crash depending on use-case. 73 | 74 | You can modify to use larger `HW_TIMER_INTERVAL_US`, (from current 20uS), according to your board and use-case if crash happens. 75 | 76 | 77 | ```cpp 78 | // Current 20uS 79 | #define HW_TIMER_INTERVAL_US 20L 80 | ``` 81 | 82 | --- 83 | --- 84 | 85 | ### Why do we need this [ESP32_PWM library](https://github.com/khoih-prog/ESP32_PWM) 86 | 87 | ### Features 88 | 89 | This library enables you to use Interrupt from Hardware Timers on an ESP32, ESP32_S2-based board to create and output PWM to pins. Becayse this library doesn't use the powerful hardware-controlled PWM with limitations, the maximum PWM frequency is currently limited at **500Hz**, which is suitable for many real-life applications. Now you can also modify PWM settings on-the-fly. 90 | 91 | --- 92 | 93 | This library enables you to use Interrupt from Hardware Timers on an ESP32, ESP32_S2 or ESP32_C3-based board to create and output PWM to pins. It now supports 16 ISR-based synchronized PWM channels, while consuming only 1 Hardware Timer. PWM interval can be very long (uint32_t millisecs). The most important feature is they're ISR-based PWM channels. Therefore, their executions are not blocked by bad-behaving functions or tasks. This important feature is absolutely necessary for mission-critical tasks. These hardware PWM channels, using interrupt, still work even if other functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software timers using millis() or micros(). That's necessary if you need to measure some data requiring better accuracy. 94 | 95 | As **Hardware Timers are rare, and very precious assets** of any board, this library now enables you to use up to **16 ISR-based synchronized PWM channels, while consuming only 1 Hardware Timer**. Timers' interval is very long (**ulong millisecs**). 96 | 97 | Now with these new **16 ISR-based timers**, the maximum interval is **practically unlimited** (limited only by unsigned long milliseconds) while **the accuracy is nearly perfect** compared to software timers. 98 | 99 | The most important feature is they're ISR-based PWM channels. Therefore, their executions are **not blocked by bad-behaving functions / tasks**. This important feature is absolutely necessary for mission-critical tasks. 100 | 101 | The [**ISR_16_PWMs_Array_Complex**](examples/ISR_16_PWMs_Array_Complex) example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs of each type of PWM channels. 102 | 103 | Being ISR-based timers, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet and Blynk services. You can also have many `(up to 16)` timers to use. 104 | 105 | This non-being-blocked important feature is absolutely necessary for mission-critical tasks. 106 | 107 | 108 | ### Why using ISR-based PWM-channels is better 109 | 110 | Imagine you have a system with a **mission-critical** function, measuring water level and control the sump pump or doing something much more important. You normally use a software timer to poll, or even place the function in loop(). But what if another function is **blocking** the loop() or setup(). 111 | 112 | So your function **might not be executed, and the result would be disastrous.** 113 | 114 | You'd prefer to have your function called, no matter what happening with other functions (busy loop, bug, etc.). 115 | 116 | The correct choice is to use a Hardware PWM-channels with **Interrupt** to call your function. 117 | 118 | These hardware timers, using interrupt, still work even if other functions are blocking. Moreover, they are much more **precise** (certainly depending on clock frequency accuracy) than other software timers using millis() or micros(). That's necessary if you need to measure some data requiring better accuracy. 119 | 120 | Functions using normal software timers, relying on loop() and calling millis(), won't work if the loop() or setup() is blocked by certain operation. For example, certain function is blocking while it's connecting to WiFi or some services. 121 | 122 | The catch is **your function is now part of an ISR (Interrupt Service Routine), and must be lean / mean, and follow certain rules.** More to read on: 123 | 124 | [**HOWTO Attach Interrupt**](https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/) 125 | 126 | --- 127 | 128 | ### Currently supported Boards 129 | 130 | 1. ESP32 boards, such as `ESP32_DEV`, etc. 131 | 2. ESP32_S2-based boards, such as `ESP32S2_DEV`, `ESP32_S2 Saola`, Adafruit QTPY_ESP32S2, ESP32S2 Native USB, UM FeatherS2 Neo, UM TinyS2, UM RMP, microS2, etc. 132 | 3. ESP32_C3-based boards, such as `ESP32C3_DEV`, LOLIN_C3_MINI, DFROBOT_BEETLE_ESP32_C3, ADAFRUIT_QTPY_ESP32C3, AirM2M_CORE_ESP32C3, XIAO_ESP32C3, etc. **New** 133 | 4. ESP32_S3-based boards, such as ESP32S3_DEV, ESP32_S3_BOX, UM TINYS3, UM PROS3, UM FEATHERS3, FEATHER_ESP32S3_NOPSRAM, QTPY_ESP32S3_NOPSRAM, etc. **New** 134 | 135 | 136 | --- 137 | 138 | ### Important Notes about ISR 139 | 140 | 1. Inside the attached function, **delay() won’t work and the value returned by millis() will not increment.** Serial data received while in the function may be lost. You should declare as **volatile any variables that you modify within the attached function.** 141 | 142 | 2. Typically global variables are used to pass data between an ISR and the main program. To make sure variables shared between an ISR and the main program are updated correctly, declare them as volatile. 143 | 144 | 145 | --- 146 | --- 147 | 148 | ## Prerequisites 149 | 150 | 1. [`Arduino IDE 1.8.19+` for Arduino](https://github.com/arduino/Arduino). [![GitHub release](https://img.shields.io/github/release/arduino/Arduino.svg)](https://github.com/arduino/Arduino/releases/latest) 151 | 2. [`ESP32 Core 2.0.5+`](https://github.com/espressif/arduino-esp32) for ESP32-based boards. [![Latest release](https://img.shields.io/github/release/espressif/arduino-esp32.svg)](https://github.com/espressif/arduino-esp32/releases/latest/). 152 | 3. [`SimpleTimer library`](https://github.com/jfturcot/SimpleTimer) to use with some examples. 153 | 154 | 155 | --- 156 | --- 157 | 158 | ## Installation 159 | 160 | ### Use Arduino Library Manager 161 | 162 | The best and easiest way is to use `Arduino Library Manager`. Search for [**ESP32_PWM**](https://github.com/khoih-prog/ESP32_PWM), then select / install the latest version. 163 | You can also use this link [![arduino-library-badge](https://www.ardu-badge.com/badge/ESP32_PWM.svg?)](https://www.ardu-badge.com/ESP32_PWM) for more detailed instructions. 164 | 165 | ### Manual Install 166 | 167 | Another way to install is to: 168 | 169 | 1. Navigate to [**ESP32_PWM**](https://github.com/khoih-prog/ESP32_PWM) page. 170 | 2. Download the latest release `ESP32_PWM-main.zip`. 171 | 3. Extract the zip file to `ESP32_PWM-main` directory 172 | 4. Copy whole `ESP32_PWM-main` folder to Arduino libraries' directory such as `~/Arduino/libraries/`. 173 | 174 | ### VS Code & PlatformIO 175 | 176 | 1. Install [VS Code](https://code.visualstudio.com/) 177 | 2. Install [PlatformIO](https://platformio.org/platformio-ide) 178 | 3. Install [**ESP32_PWM** library](https://registry.platformio.org/libraries/khoih-prog/ESP32_PWM) by using [Library Manager](https://registry.platformio.org/libraries/khoih-prog/ESP32_PWM/installation). Search for **ESP32_PWM** in [Platform.io Author's Libraries](https://platformio.org/lib/search?query=author:%22Khoi%20Hoang%22) 179 | 4. Use included [platformio.ini](platformio/platformio.ini) file from examples to ensure that all dependent libraries will installed automatically. Please visit documentation for the other options and examples at [Project Configuration File](https://docs.platformio.org/page/projectconf.html) 180 | 181 | 182 | --- 183 | --- 184 | 185 | 186 | ### HOWTO Fix `Multiple Definitions` Linker Error 187 | 188 | The current library implementation, using `xyz-Impl.h` instead of standard `xyz.cpp`, possibly creates certain `Multiple Definitions` Linker error in certain use cases. 189 | 190 | You can include this `.hpp` file 191 | 192 | ```cpp 193 | // Can be included as many times as necessary, without `Multiple Definitions` Linker Error 194 | #include "ESP32_PWM.hpp" //https://github.com/khoih-prog/ESP32_PWM 195 | ``` 196 | 197 | in many files. But be sure to use the following `.h` file **in just 1 `.h`, `.cpp` or `.ino` file**, which must **not be included in any other file**, to avoid `Multiple Definitions` Linker Error 198 | 199 | ```cpp 200 | // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error 201 | #include "ESP32_PWM.h" //https://github.com/khoih-prog/ESP32_PWM 202 | ``` 203 | 204 | Check the new [**multiFileProject** example](examples/multiFileProject) for a `HOWTO` demo. 205 | 206 | Have a look at the discussion in [Different behaviour using the src_cpp or src_h lib #80](https://github.com/khoih-prog/ESPAsync_WiFiManager/discussions/80) 207 | 208 | --- 209 | --- 210 | 211 | ### HOWTO Use analogRead() with ESP32 running WiFi and/or BlueTooth (BT/BLE) 212 | 213 | Please have a look at [**ESP_WiFiManager Issue 39: Not able to read analog port when using the autoconnect example**](https://github.com/khoih-prog/ESP_WiFiManager/issues/39) to have more detailed description and solution of the issue. 214 | 215 | #### 1. ESP32 has 2 ADCs, named ADC1 and ADC2 216 | 217 | #### 2. ESP32 ADCs functions 218 | 219 | - `ADC1` controls ADC function for pins **GPIO32-GPIO39** 220 | - `ADC2` controls ADC function for pins **GPIO0, 2, 4, 12-15, 25-27** 221 | 222 | #### 3.. ESP32 WiFi uses ADC2 for WiFi functions 223 | 224 | Look in file [**adc_common.c**](https://github.com/espressif/esp-idf/blob/master/components/driver/adc_common.c) 225 | 226 | > In `ADC2`, there're two locks used for different cases: 227 | > 1. lock shared with app and Wi-Fi: 228 | > ESP32: 229 | > When Wi-Fi using the `ADC2`, we assume it will never stop, so app checks the lock and returns immediately if failed. 230 | > ESP32S2: 231 | > The controller's control over the ADC is determined by the arbiter. There is no need to control by lock. 232 | > 233 | > 2. lock shared between tasks: 234 | > when several tasks sharing the `ADC2`, we want to guarantee 235 | > all the requests will be handled. 236 | > Since conversions are short (about 31us), app returns the lock very soon, 237 | > we use a spinlock to stand there waiting to do conversions one by one. 238 | > 239 | > adc2_spinlock should be acquired first, then adc2_wifi_lock or rtc_spinlock. 240 | 241 | 242 | - In order to use `ADC2` for other functions, we have to **acquire complicated firmware locks and very difficult to do** 243 | - So, it's not advisable to use `ADC2` with WiFi/BlueTooth (BT/BLE). 244 | - Use `ADC1`, and pins `GPIO32-GPIO39` 245 | - If somehow it's a must to use those pins serviced by `ADC2` (**GPIO0, 2, 4, 12, 13, 14, 15, 25, 26 and 27**), use the **fix mentioned at the end** of [**ESP_WiFiManager Issue 39: Not able to read analog port when using the autoconnect example**](https://github.com/khoih-prog/ESP_WiFiManager/issues/39) to work with ESP32 WiFi/BlueTooth (BT/BLE). 246 | 247 | --- 248 | --- 249 | 250 | ## More useful Information 251 | 252 | ### ESP32 Hardware Timers 253 | 254 | - **The ESP32, ESP32_S2 and ESP32_S3 has two timer groups, each one with two general purpose hardware timers.** 255 | - **The ESP32_C3 has two timer groups, each one with only one general purpose hardware timer.** 256 | - All the timers are based on **64-bit counters (except 54-bit counter for ESP32_S3 counter) and 16-bit prescalers.** 257 | - The timer counters can be configured to count up or down and support automatic reload and software reload. 258 | - They can also generate alarms when they reach a specific value, defined by the software. 259 | - The value of the counter can be read by the software program. 260 | 261 | --- 262 | 263 | Now with these new `16 ISR-based PWM-channels` (while consuming only **1 hardware timer**), the maximum interval is practically unlimited (limited only by unsigned long milliseconds). The accuracy is nearly perfect compared to software PWM-channels. The most important feature is they're ISR-based PWM-channels Therefore, their executions are not blocked by bad-behaving functions / tasks. 264 | This important feature is absolutely necessary for mission-critical tasks. 265 | 266 | The [**ISR_16_PWMs_Array_Complex**](examples/ISR_16_PWMs_Array_Complex) example will demonstrate the nearly perfect accuracy compared to software-based PWM-channels by printing the actual elapsed `microsecs / millisecs` of each type of PWM-channels. 267 | Being ISR-based PWM-channels, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet and Blynk services. You can also have many `(up to 16)` synchronized PWM-channels to use. 268 | This non-being-blocked important feature is absolutely necessary for mission-critical tasks. 269 | You'll see `SimpleTimer` is blocked while system is connecting to WiFi / Internet / Blynk, as well as by blocking task 270 | in `loop()`, using `delay()` function as an example. The elapsed time then is very unaccurate 271 | 272 | --- 273 | --- 274 | 275 | ## How to use 276 | 277 | Before using any Timer, you have to make sure the Timer has not been used by any other purpose. 278 | 279 | `Timer0, Timer1, Timer2 and Timer3` are supported for ESP32 280 | 281 | --- 282 | --- 283 | 284 | ### Examples: 285 | 286 | 1. [ISR_16_PWMs_Array](examples/ISR_16_PWMs_Array) 287 | 2. [ISR_16_PWMs_Array_Complex](examples/ISR_16_PWMs_Array_Complex) 288 | 3. [ISR_16_PWMs_Array_Simple](examples/ISR_16_PWMs_Array_Simple) 289 | 4. [ISR_Changing_PWM](examples/ISR_Changing_PWM) 290 | 5. [ISR_Modify_PWM](examples/ISR_Modify_PWM) 291 | 6. [**multiFileProject**](examples/multiFileProject) **New** 292 | 293 | --- 294 | --- 295 | 296 | ### Example [ISR_16_PWMs_Array_Complex](examples/ISR_16_PWMs_Array_Complex) 297 | 298 | https://github.com/khoih-prog/ESP32_PWM/blob/bf025f27e40c4b70687cd61a320a63c98bb724ba/examples/ISR_16_PWMs_Array_Complex/ISR_16_PWMs_Array_Complex.ino#L25-L471 299 | 300 | 301 | --- 302 | --- 303 | 304 | ### Debug Terminal Output Samples 305 | 306 | ### 1. ISR_16_PWMs_Array_Complex on ESP32_DEV 307 | 308 | The following is the sample terminal output when running example [ISR_16_PWMs_Array_Complex](examples/ISR_16_PWMs_Array_Complex) on **ESP32_DEV** to demonstrate the accuracy of ISR Hardware PWM-channels, **especially when system is very busy**. The ISR PWM-channels is **running exactly according to corresponding programmed periods and duty-cycles** 309 | 310 | 311 | ```cpp 312 | Starting ISR_16_PWMs_Array_Complex on ESP32_DEV 313 | ESP32_PWM v1.3.3 314 | CPU Frequency = 240 MHz 315 | [PWM] ESP32_TimerInterrupt: _timerNo = 1 , _fre = 1000000 316 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 317 | [PWM] _timerIndex = 1 , _timerGroup = 0 318 | [PWM] _count = 0 - 20 319 | [PWM] timer_set_alarm_value = 20.00 320 | Starting ITimer OK, micros() = 2058708 321 | Channel : 0 Period : 1000000 OnTime : 50000 Start_Time : 2058897 322 | Channel : 1 Period : 500000 OnTime : 50000 Start_Time : 2069539 323 | Channel : 2 Period : 333333 OnTime : 66666 Start_Time : 2069906 324 | Channel : 3 Period : 250000 OnTime : 75000 Start_Time : 2080530 325 | Channel : 4 Period : 200000 OnTime : 80000 Start_Time : 2080889 326 | Channel : 5 Period : 166667 OnTime : 75000 Start_Time : 2091451 327 | Channel : 6 Period : 142857 OnTime : 71428 Start_Time : 2102051 328 | Channel : 7 Period : 125000 OnTime : 68750 Start_Time : 2102413 329 | Channel : 8 Period : 111111 OnTime : 66666 Start_Time : 2113029 330 | Channel : 9 Period : 100000 OnTime : 65000 Start_Time : 2113401 331 | Channel : 10 Period : 66667 OnTime : 46666 Start_Time : 2124047 332 | Channel : 11 Period : 50000 OnTime : 37500 Start_Time : 2124423 333 | Channel : 12 Period : 40000 OnTime : 32000 Start_Time : 2135081 334 | Channel : 13 Period : 33333 OnTime : 28333 Start_Time : 2135450 335 | Channel : 14 Period : 25000 OnTime : 22500 Start_Time : 2146102 336 | Channel : 15 Period : 20000 OnTime : 19000 Start_Time : 2156669 337 | SimpleTimer (ms): 2000, us : 12156966, Dus : 10098123 338 | PWM Channel : 01000000, programmed Period (us): 1000000, actual : 1000000, programmed DutyCycle : 5.00, actual : 5.00 339 | PWM Channel : 1500000, programmed Period (us): 500000, actual : 500000, programmed DutyCycle : 10.00, actual : 10.00 340 | PWM Channel : 2333333, programmed Period (us): 333333, actual : 333340, programmed DutyCycle : 20.00, actual : 20.00 341 | PWM Channel : 3250000, programmed Period (us): 250000, actual : 250000, programmed DutyCycle : 30.00, actual : 30.00 342 | PWM Channel : 4200000, programmed Period (us): 200000, actual : 200000, programmed DutyCycle : 40.00, actual : 40.00 343 | PWM Channel : 5166667, programmed Period (us): 166667, actual : 166680, programmed DutyCycle : 45.00, actual : 45.00 344 | PWM Channel : 6142857, programmed Period (us): 142857, actual : 142860, programmed DutyCycle : 50.00, actual : 49.99 345 | PWM Channel : 7125000, programmed Period (us): 125000, actual : 125001, programmed DutyCycle : 55.00, actual : 54.99 346 | PWM Channel : 8111111, programmed Period (us): 111111, actual : 111120, programmed DutyCycle : 60.00, actual : 59.99 347 | PWM Channel : 9100000, programmed Period (us): 100000, actual : 100002, programmed DutyCycle : 65.00, actual : 65.00 348 | PWM Channel : 1066667, programmed Period (us): 66667, actual : 66680, programmed DutyCycle : 70.00, actual : 69.98 349 | PWM Channel : 1150000, programmed Period (us): 50000, actual : 50001, programmed DutyCycle : 75.00, actual : 74.97 350 | PWM Channel : 1240000, programmed Period (us): 40000, actual : 39999, programmed DutyCycle : 80.00, actual : 80.00 351 | PWM Channel : 1333333, programmed Period (us): 33333, actual : 33340, programmed DutyCycle : 85.00, actual : 84.94 352 | PWM Channel : 1425000, programmed Period (us): 25000, actual : 25000, programmed DutyCycle : 90.00, actual : 90.00 353 | PWM Channel : 1520000, programmed Period (us): 20000, actual : 20000, programmed DutyCycle : 95.00, actual : 95.00 354 | SimpleTimer (ms): 2000, us : 22312882, Dus : 10155916 355 | PWM Channel : 01000000, programmed Period (us): 1000000, actual : 1000000, programmed DutyCycle : 5.00, actual : 5.00 356 | PWM Channel : 1500000, programmed Period (us): 500000, actual : 500000, programmed DutyCycle : 10.00, actual : 10.00 357 | PWM Channel : 2333333, programmed Period (us): 333333, actual : 333340, programmed DutyCycle : 20.00, actual : 20.00 358 | PWM Channel : 3250000, programmed Period (us): 250000, actual : 250000, programmed DutyCycle : 30.00, actual : 30.00 359 | PWM Channel : 4200000, programmed Period (us): 200000, actual : 200000, programmed DutyCycle : 40.00, actual : 40.00 360 | PWM Channel : 5166667, programmed Period (us): 166667, actual : 166680, programmed DutyCycle : 45.00, actual : 45.00 361 | PWM Channel : 6142857, programmed Period (us): 142857, actual : 142861, programmed DutyCycle : 50.00, actual : 49.99 362 | PWM Channel : 7125000, programmed Period (us): 125000, actual : 125000, programmed DutyCycle : 55.00, actual : 54.99 363 | PWM Channel : 8111111, programmed Period (us): 111111, actual : 111120, programmed DutyCycle : 60.00, actual : 59.99 364 | PWM Channel : 9100000, programmed Period (us): 100000, actual : 100001, programmed DutyCycle : 65.00, actual : 65.00 365 | PWM Channel : 1066667, programmed Period (us): 66667, actual : 66680, programmed DutyCycle : 70.00, actual : 69.98 366 | PWM Channel : 1150000, programmed Period (us): 50000, actual : 50000, programmed DutyCycle : 75.00, actual : 75.00 367 | PWM Channel : 1240000, programmed Period (us): 40000, actual : 40000, programmed DutyCycle : 80.00, actual : 80.00 368 | PWM Channel : 1333333, programmed Period (us): 33333, actual : 33340, programmed DutyCycle : 85.00, actual : 84.94 369 | PWM Channel : 1425000, programmed Period (us): 25000, actual : 25000, programmed DutyCycle : 90.00, actual : 90.00 370 | PWM Channel : 1520000, programmed Period (us): 20000, actual : 20000, programmed DutyCycle : 95.00, actual : 95.00 371 | ``` 372 | 373 | --- 374 | 375 | ### 2. ISR_16_PWMs_Array on ESP32_DEV 376 | 377 | The following is the sample terminal output when running example [ISR_16_PWMs_Array](examples/ISR_16_PWMs_Array) on **ESP32_DEV** to demonstrate how to use multiple Hardware PWM channels. 378 | 379 | ```cpp 380 | Starting ISR_16_PWMs_Array on ESP32_DEV 381 | ESP32_PWM v1.3.3 382 | CPU Frequency = 240 MHz 383 | [PWM] ESP32_TimerInterrupt: _timerNo = 1 , _fre = 1000000 384 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 385 | [PWM] _timerIndex = 1 , _timerGroup = 0 386 | [PWM] _count = 0 - 20 387 | [PWM] timer_set_alarm_value = 20.00 388 | Starting ITimer OK, micros() = 2058746 389 | Channel : 0 Period : 1000000 OnTime : 50000 Start_Time : 2058951 390 | Channel : 1 Period : 500000 OnTime : 50000 Start_Time : 2069589 391 | Channel : 2 Period : 333333 OnTime : 66666 Start_Time : 2070006 392 | Channel : 3 Period : 250000 OnTime : 75000 Start_Time : 2080650 393 | Channel : 4 Period : 200000 OnTime : 80000 Start_Time : 2081082 394 | Channel : 5 Period : 166666 OnTime : 74999 Start_Time : 2091764 395 | Channel : 6 Period : 142857 OnTime : 71428 Start_Time : 2092203 396 | Channel : 7 Period : 125000 OnTime : 68750 Start_Time : 2102906 397 | Channel : 8 Period : 111111 OnTime : 66666 Start_Time : 2113570 398 | Channel : 9 Period : 100000 OnTime : 65000 Start_Time : 2114007 399 | Channel : 10 Period : 66666 OnTime : 46666 Start_Time : 2124648 400 | Channel : 11 Period : 50000 OnTime : 37500 Start_Time : 2125104 401 | Channel : 12 Period : 40000 OnTime : 32000 Start_Time : 2135783 402 | Channel : 13 Period : 33333 OnTime : 28333 Start_Time : 2136239 403 | Channel : 14 Period : 25000 OnTime : 22500 Start_Time : 2146919 404 | Channel : 15 Period : 20000 OnTime : 19000 Start_Time : 2147367 405 | ``` 406 | 407 | --- 408 | 409 | 410 | ### 3. ISR_16_PWMs_Array_Simple on ESP32_DEV 411 | 412 | The following is the sample terminal output when running example [ISR_16_PWMs_Array_Simple](examples/ISR_16_PWMs_Array_Simple) on **ESP32_DEV** to demonstrate how to use multiple Hardware PWM channels. 413 | 414 | ```cpp 415 | Starting ISR_16_PWMs_Array_Simple on ESP32_DEV 416 | ESP32_PWM v1.3.3 417 | CPU Frequency = 240 MHz 418 | [PWM] ESP32_TimerInterrupt: _timerNo = 1 , _fre = 1000000 419 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 420 | [PWM] _timerIndex = 1 , _timerGroup = 0 421 | [PWM] _count = 0 - 20 422 | [PWM] timer_set_alarm_value = 20.00 423 | Starting ITimer OK, micros() = 2058739 424 | Channel : 0 Period : 1000000 OnTime : 50000 Start_Time : 2058949 425 | Channel : 1 Period : 500000 OnTime : 50000 Start_Time : 2069626 426 | Channel : 2 Period : 333333 OnTime : 66666 Start_Time : 2070013 427 | Channel : 3 Period : 250000 OnTime : 75000 Start_Time : 2080640 428 | Channel : 4 Period : 200000 OnTime : 80000 Start_Time : 2081020 429 | Channel : 5 Period : 166666 OnTime : 74999 Start_Time : 2091664 430 | Channel : 6 Period : 142857 OnTime : 71428 Start_Time : 2102298 431 | Channel : 7 Period : 125000 OnTime : 68750 Start_Time : 2102684 432 | Channel : 8 Period : 111111 OnTime : 66666 Start_Time : 2113302 433 | Channel : 9 Period : 100000 OnTime : 65000 Start_Time : 2113700 434 | Channel : 10 Period : 66666 OnTime : 46666 Start_Time : 2124315 435 | Channel : 11 Period : 50000 OnTime : 37500 Start_Time : 2124701 436 | Channel : 12 Period : 40000 OnTime : 32000 Start_Time : 2135333 437 | Channel : 13 Period : 33333 OnTime : 28333 Start_Time : 2135733 438 | Channel : 14 Period : 25000 OnTime : 22500 Start_Time : 2146377 439 | Channel : 15 Period : 20000 OnTime : 19000 Start_Time : 2156993 440 | ``` 441 | 442 | --- 443 | 444 | ### 4. ISR_Modify_PWM on ESP32_DEV 445 | 446 | The following is the sample terminal output when running example [ISR_Modify_PWM](examples/ISR_Modify_PWM) on **ESP32_DEV** to demonstrate how to modify PWM settings on-the-fly without deleting the PWM channel 447 | 448 | ```cpp 449 | Starting ISR_Modify_PWM on ESP32_DEV 450 | ESP32_PWM v1.3.3 451 | CPU Frequency = 240 MHz 452 | [PWM] ESP32_TimerInterrupt: _timerNo = 1 , _fre = 1000000 453 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 454 | [PWM] _timerIndex = 1 , _timerGroup = 0 455 | [PWM] _count = 0 - 20 456 | [PWM] timer_set_alarm_value = 20.00 457 | Starting ITimer OK, micros() = 2059642 458 | Using PWM Freq = 200.00, PWM DutyCycle = 1.00 459 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 2060337 460 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 12071208 461 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 22066228 462 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 32071347 463 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 42066348 464 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 52072208 465 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 62077247 466 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 72078208 467 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 82078228 468 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 92078347 469 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 102073347 470 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 112079208 471 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 122074208 472 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 132079347 473 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 142084368 474 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 152085208 475 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 162080208 476 | ``` 477 | 478 | --- 479 | 480 | ### 5. ISR_Changing_PWM on ESP32_DEV 481 | 482 | The following is the sample terminal output when running example [ISR_Changing_PWM](examples/ISR_Changing_PWM) on **ESP32_DEV** to demonstrate how to modify PWM settings on-the-fly by deleting the PWM channel and reinit the PWM channel 483 | 484 | ```cpp 485 | Starting ISR_Changing_PWM on ESP32_DEV 486 | ESP32_PWM v1.3.3 487 | CPU Frequency = 240 MHz 488 | [PWM] ESP32_TimerInterrupt: _timerNo = 1 , _fre = 1000000 489 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 490 | [PWM] _timerIndex = 1 , _timerGroup = 0 491 | [PWM] _count = 0 - 20 492 | [PWM] timer_set_alarm_value = 20.00 493 | Starting ITimer OK, micros() = 2058761 494 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 495 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 2059443 496 | Using PWM Freq = 2.00, PWM DutyCycle = 90.00 497 | Channel : 0 Period : 500000 OnTime : 450000 Start_Time : 12070335 498 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 499 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 22070374 500 | Using PWM Freq = 2.00, PWM DutyCycle = 90.00 501 | Channel : 0 Period : 500000 OnTime : 450000 Start_Time : 32070329 502 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 503 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 42070352 504 | Using PWM Freq = 2.00, PWM DutyCycle = 90.00 505 | Channel : 0 Period : 500000 OnTime : 450000 Start_Time : 52070349 506 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 507 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 62070352 508 | Using PWM Freq = 2.00, PWM DutyCycle = 90.00 509 | Channel : 0 Period : 500000 OnTime : 450000 Start_Time : 72070329 510 | ``` 511 | 512 | --- 513 | 514 | ### 6. ISR_Modify_PWM on ESP32S2_DEV 515 | 516 | The following is the sample terminal output when running example [ISR_Modify_PWM](examples/ISR_Modify_PWM) on **ESP32S2_DEV** to demonstrate how to modify PWM settings on-the-fly without deleting the PWM channel 517 | 518 | ```cpp 519 | Starting ISR_Modify_PWM on ESP32S2_DEV 520 | ESP32_PWM v1.3.3 521 | CPU Frequency = 240 MHz 522 | [PWM] ESP32_S2_TimerInterrupt: _timerNo = 1 , _fre = 1000000 523 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 524 | [PWM] _timerIndex = 1 , _timerGroup = 0 525 | [PWM] _count = 0 - 20 526 | [PWM] timer_set_alarm_value = 20.00 527 | Starting ITimer OK, micros() = 2059642 528 | Using PWM Freq = 200.00, PWM DutyCycle = 1.00 529 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 2060337 530 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 12071208 531 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 22066228 532 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 32071347 533 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 42066348 534 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 52072208 535 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 62077247 536 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 72078208 537 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 82078228 538 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 92078347 539 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 102073347 540 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 112079208 541 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 122074208 542 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 132079347 543 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 142084368 544 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 152085208 545 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 162080208 546 | ``` 547 | 548 | --- 549 | 550 | ### 7. ISR_Changing_PWM on ESP32S2_DEV 551 | 552 | The following is the sample terminal output when running example [ISR_Changing_PWM](examples/ISR_Changing_PWM) on **ESP32S2_DEV** to demonstrate how to modify PWM settings on-the-fly by deleting the PWM channel and reinit the PWM channel 553 | 554 | ``` 555 | Starting ISR_Changing_PWM on ESP32S2_DEV 556 | ESP32_PWM v1.3.3 557 | CPU Frequency = 240 MHz 558 | [PWM] ESP32_S2_TimerInterrupt: _timerNo = 1 , _fre = 1000000 559 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 560 | [PWM] _timerIndex = 1 , _timerGroup = 0 561 | [PWM] _count = 0 - 20 562 | [PWM] timer_set_alarm_value = 20.00 563 | Starting ITimer OK, micros() = 2563689 564 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 565 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 2568686 566 | Using PWM Freq = 2.00, PWM DutyCycle = 90.00 567 | Channel : 0 Period : 500000 OnTime : 450000 Start_Time : 12578679 568 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 569 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 22583648 570 | Using PWM Freq = 2.00, PWM DutyCycle = 90.00 571 | Channel : 0 Period : 500000 OnTime : 450000 Start_Time : 32583648 572 | ``` 573 | 574 | --- 575 | 576 | ### 8. ISR_Modify_PWM on ESP32C3_DEV 577 | 578 | The following is the sample terminal output when running example [ISR_Modify_PWM](examples/ISR_Modify_PWM) on **ESP32C3_DEV** to demonstrate how to modify PWM settings on-the-fly without deleting the PWM channel 579 | 580 | ``` 581 | Starting ISR_Modify_PWM on ESP32C3_DEV 582 | ESP32_PWM v1.3.3 583 | CPU Frequency = 160 MHz 584 | [PWM] ESP32_TimerInterrupt: _timerNo = 1 , _fre = 1000000 585 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 586 | [PWM] _timerIndex = 0 , _timerGroup = 1 587 | [PWM] _count = 0 - 20 588 | [PWM] timer_set_alarm_value = 20.00 589 | Starting ITimer OK, micros() = 2059642 590 | Using PWM Freq = 200.00, PWM DutyCycle = 1.00 591 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 2060337 592 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 12071208 593 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 22066228 594 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 32071347 595 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 42066348 596 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 52072208 597 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 62077247 598 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 72078208 599 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 82078228 600 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 92078347 601 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 102073347 602 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 112079208 603 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 122074208 604 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 132079347 605 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 142084368 606 | Channel : 0 Period : 10000 OnTime : 555 Start_Time : 152085208 607 | Channel : 0 Period : 5000 OnTime : 50 Start_Time : 162080208 608 | ``` 609 | 610 | --- 611 | 612 | ### 9. ISR_Changing_PWM on ESP32C3_DEV 613 | 614 | The following is the sample terminal output when running example [ISR_Changing_PWM](examples/ISR_Changing_PWM) on **ESP32C3_DEV** to demonstrate how to modify PWM settings on-the-fly by deleting the PWM channel and reinit the PWM channel 615 | 616 | ``` 617 | Starting ISR_Changing_PWM on ESP32C3_DEV 618 | ESP32_PWM v1.3.3 619 | CPU Frequency = 160 MHz 620 | [PWM] ESP32_TimerInterrupt: _timerNo = 1 , _fre = 1000000 621 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 622 | [PWM] _timerIndex = 0 , _timerGroup = 1 623 | [PWM] _count = 0 - 20 624 | [PWM] timer_set_alarm_value = 20.00 625 | Starting ITimer OK, micros() = 2100339 626 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 627 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 2105212 628 | Using PWM Freq = 2.00, PWM DutyCycle = 90.00 629 | Channel : 0 Period : 500000 OnTime : 450000 Start_Time : 12117109 630 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 631 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 22122103 632 | Using PWM Freq = 2.00, PWM DutyCycle = 90.00 633 | Channel : 0 Period : 500000 OnTime : 450000 Start_Time : 32122107 634 | Using PWM Freq = 1.00, PWM DutyCycle = 50.00 635 | Channel : 0 Period : 1000000 OnTime : 500000 Start_Time : 42127102 636 | ``` 637 | 638 | --- 639 | 640 | ### 10. ISR_16_PWMs_Array_Complex on ESP32S3_DEV 641 | 642 | The following is the sample terminal output when running example [ISR_16_PWMs_Array_Complex](examples/ISR_16_PWMs_Array_Complex) on **ESP32S3_DEV** to demonstrate the accuracy of ISR Hardware PWM-channels, **especially when system is very busy**. The ISR PWM-channels is **running exactly according to corresponding programmed periods and duty-cycles** 643 | 644 | 645 | ``` 646 | Starting ISR_16_PWMs_Array_Complex on ESP32S3_DEV 647 | ESP32_PWM v1.3.3 648 | CPU Frequency = 240 MHz 649 | [PWM] ESP32_S3_TimerInterrupt: _timerNo = 1 , _fre = 1000000 650 | [PWM] TIMER_BASE_CLK = 80000000 , TIMER_DIVIDER = 80 651 | [PWM] _timerIndex = 1 , _timerGroup = 0 652 | [PWM] _count = 0 - 20 653 | [PWM] timer_set_alarm_value = 20.00 654 | Starting ITimer OK, micros() = 2118162 655 | Channel : 0 Period : 1000000 OnTime : 50000 Start_Time : 2118373 656 | Channel : 1 Period : 500000 OnTime : 50000 Start_Time : 2128833 657 | Channel : 2 Period : 333333 OnTime : 66666 Start_Time : 2129071 658 | Channel : 3 Period : 250000 OnTime : 75000 Start_Time : 2139568 659 | Channel : 4 Period : 200000 OnTime : 80000 Start_Time : 2150027 660 | Channel : 5 Period : 166667 OnTime : 75000 Start_Time : 2150272 661 | Channel : 6 Period : 142857 OnTime : 71428 Start_Time : 2160711 662 | Channel : 7 Period : 125000 OnTime : 68750 Start_Time : 2160971 663 | Channel : 8 Period : 111111 OnTime : 66666 Start_Time : 2171464 664 | Channel : 9 Period : 100000 OnTime : 65000 Start_Time : 2171714 665 | Channel : 10 Period : 66667 OnTime : 46666 Start_Time : 2182194 666 | Channel : 11 Period : 50000 OnTime : 37500 Start_Time : 2192697 667 | Channel : 12 Period : 40000 OnTime : 32000 Start_Time : 2192958 668 | Channel : 13 Period : 33333 OnTime : 28333 Start_Time : 2203438 669 | Channel : 14 Period : 25000 OnTime : 22500 Start_Time : 2203696 670 | Channel : 15 Period : 20000 OnTime : 19000 Start_Time : 2214155 671 | SimpleTimer (ms): 2000, us : 12214398, Dus : 10096157 672 | PWM Channel : 01000000, programmed Period (us): 1000000, actual : 1000000, programmed DutyCycle : 5.00, actual : 5.00 673 | PWM Channel : 1500000, programmed Period (us): 500000, actual : 500000, programmed DutyCycle : 10.00, actual : 10.00 674 | PWM Channel : 2333333, programmed Period (us): 333333, actual : 333340, programmed DutyCycle : 20.00, actual : 20.00 675 | PWM Channel : 3250000, programmed Period (us): 250000, actual : 249999, programmed DutyCycle : 30.00, actual : 30.00 676 | PWM Channel : 4200000, programmed Period (us): 200000, actual : 200000, programmed DutyCycle : 40.00, actual : 40.00 677 | PWM Channel : 5166667, programmed Period (us): 166667, actual : 166680, programmed DutyCycle : 45.00, actual : 45.00 678 | PWM Channel : 6142857, programmed Period (us): 142857, actual : 142860, programmed DutyCycle : 50.00, actual : 49.99 679 | PWM Channel : 7125000, programmed Period (us): 125000, actual : 125000, programmed DutyCycle : 55.00, actual : 54.99 680 | PWM Channel : 8111111, programmed Period (us): 111111, actual : 111120, programmed DutyCycle : 60.00, actual : 59.99 681 | PWM Channel : 9100000, programmed Period (us): 100000, actual : 100001, programmed DutyCycle : 65.00, actual : 65.00 682 | PWM Channel : 1066667, programmed Period (us): 66667, actual : 66680, programmed DutyCycle : 70.00, actual : 69.98 683 | PWM Channel : 1150000, programmed Period (us): 50000, actual : 50000, programmed DutyCycle : 75.00, actual : 75.00 684 | PWM Channel : 1240000, programmed Period (us): 40000, actual : 40000, programmed DutyCycle : 80.00, actual : 80.00 685 | PWM Channel : 1333333, programmed Period (us): 33333, actual : 33340, programmed DutyCycle : 85.00, actual : 84.94 686 | PWM Channel : 1425000, programmed Period (us): 25000, actual : 25000, programmed DutyCycle : 90.00, actual : 90.00 687 | PWM Channel : 1520000, programmed Period (us): 20000, actual : 19999, programmed DutyCycle : 95.00, actual : 95.00 688 | SimpleTimer (ms): 2000, us : 22375317, Dus : 10160919 689 | PWM Channel : 01000000, programmed Period (us): 1000000, actual : 1000000, programmed DutyCycle : 5.00, actual : 5.00 690 | PWM Channel : 1500000, programmed Period (us): 500000, actual : 500000, programmed DutyCycle : 10.00, actual : 10.00 691 | PWM Channel : 2333333, programmed Period (us): 333333, actual : 333340, programmed DutyCycle : 20.00, actual : 20.00 692 | PWM Channel : 3250000, programmed Period (us): 250000, actual : 250001, programmed DutyCycle : 30.00, actual : 30.00 693 | PWM Channel : 4200000, programmed Period (us): 200000, actual : 200000, programmed DutyCycle : 40.00, actual : 40.00 694 | PWM Channel : 5166667, programmed Period (us): 166667, actual : 166680, programmed DutyCycle : 45.00, actual : 45.00 695 | PWM Channel : 6142857, programmed Period (us): 142857, actual : 142860, programmed DutyCycle : 50.00, actual : 49.99 696 | PWM Channel : 7125000, programmed Period (us): 125000, actual : 125000, programmed DutyCycle : 55.00, actual : 54.99 697 | PWM Channel : 8111111, programmed Period (us): 111111, actual : 111120, programmed DutyCycle : 60.00, actual : 59.99 698 | PWM Channel : 9100000, programmed Period (us): 100000, actual : 100000, programmed DutyCycle : 65.00, actual : 65.00 699 | PWM Channel : 1066667, programmed Period (us): 66667, actual : 66679, programmed DutyCycle : 70.00, actual : 69.98 700 | PWM Channel : 1150000, programmed Period (us): 50000, actual : 50000, programmed DutyCycle : 75.00, actual : 75.00 701 | PWM Channel : 1240000, programmed Period (us): 40000, actual : 40000, programmed DutyCycle : 80.00, actual : 80.00 702 | PWM Channel : 1333333, programmed Period (us): 33333, actual : 33340, programmed DutyCycle : 85.00, actual : 84.94 703 | PWM Channel : 1425000, programmed Period (us): 25000, actual : 25000, programmed DutyCycle : 90.00, actual : 90.00 704 | PWM Channel : 1520000, programmed Period (us): 20000, actual : 20000, programmed DutyCycle : 95.00, actual : 95.00 705 | SimpleTimer (ms): 2000, us : 32536323, Dus : 10161006 706 | PWM Channel : 01000000, programmed Period (us): 1000000, actual : 1000000, programmed DutyCycle : 5.00, actual : 5.00 707 | PWM Channel : 1500000, programmed Period (us): 500000, actual : 500000, programmed DutyCycle : 10.00, actual : 10.00 708 | PWM Channel : 2333333, programmed Period (us): 333333, actual : 333340, programmed DutyCycle : 20.00, actual : 20.00 709 | PWM Channel : 3250000, programmed Period (us): 250000, actual : 250001, programmed DutyCycle : 30.00, actual : 30.00 710 | PWM Channel : 4200000, programmed Period (us): 200000, actual : 200000, programmed DutyCycle : 40.00, actual : 40.00 711 | PWM Channel : 5166667, programmed Period (us): 166667, actual : 166680, programmed DutyCycle : 45.00, actual : 45.00 712 | PWM Channel : 6142857, programmed Period (us): 142857, actual : 142861, programmed DutyCycle : 50.00, actual : 49.99 713 | PWM Channel : 7125000, programmed Period (us): 125000, actual : 125000, programmed DutyCycle : 55.00, actual : 54.99 714 | PWM Channel : 8111111, programmed Period (us): 111111, actual : 111120, programmed DutyCycle : 60.00, actual : 59.99 715 | PWM Channel : 9100000, programmed Period (us): 100000, actual : 100000, programmed DutyCycle : 65.00, actual : 64.98 716 | PWM Channel : 1066667, programmed Period (us): 66667, actual : 66680, programmed DutyCycle : 70.00, actual : 69.98 717 | PWM Channel : 1150000, programmed Period (us): 50000, actual : 50000, programmed DutyCycle : 75.00, actual : 75.00 718 | PWM Channel : 1240000, programmed Period (us): 40000, actual : 40001, programmed DutyCycle : 80.00, actual : 80.00 719 | PWM Channel : 1333333, programmed Period (us): 33333, actual : 33340, programmed DutyCycle : 85.00, actual : 84.94 720 | PWM Channel : 1425000, programmed Period (us): 25000, actual : 25000, programmed DutyCycle : 90.00, actual : 90.01 721 | PWM Channel : 1520000, programmed Period (us): 20000, actual : 20000, programmed DutyCycle : 95.00, actual : 95.00 722 | ``` 723 | 724 | --- 725 | --- 726 | 727 | ### Debug 728 | 729 | Debug is enabled by default on Serial. 730 | 731 | You can also change the debugging level `_PWM_LOGLEVEL_` from 0 to 4 732 | 733 | ```cpp 734 | // These define's must be placed at the beginning before #include "ESP32_PWM.h" 735 | // _PWM_LOGLEVEL_ from 0 to 4 736 | // Don't define _PWM_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system. 737 | #define _PWM_LOGLEVEL_ 4 738 | ``` 739 | 740 | --- 741 | 742 | ### Troubleshooting 743 | 744 | If you get compilation errors, more often than not, you may need to install a newer version of the core for Arduino boards. 745 | 746 | Sometimes, the library will only work if you update the board core to the latest version because I am using newly added functions. 747 | 748 | 749 | --- 750 | --- 751 | 752 | ### Issues 753 | 754 | Submit issues to: [ESP32_PWM issues](https://github.com/khoih-prog/ESP32_PWM/issues) 755 | 756 | --- 757 | 758 | ## TO DO 759 | 760 | 1. Search for bug and improvement. 761 | 2. Similar features for remaining Arduino boards such as SAMD21, SAMD51, SAM-DUE, nRF52, ESP8266, STM32, Portenta_H7, RP2040, etc. 762 | 763 | 764 | 765 | ## DONE 766 | 767 | 1. Basic hardware PWM-channels for ESP32, ESP32_C2 and ESP32_C3 for [ESP32 core v2.0.0+](https://github.com/espressif/arduino-esp32/releases/tag/2.0.0) 768 | 2. Longer time interval 769 | 3. Add complex examples. 770 | 4. Add functions to modify PWM settings on-the-fly 771 | 5. Fix examples to use with ESP32 core v2.0.1+ 772 | 6. Fix `multiple-definitions` linker error. Drop `src_cpp` and `src_h` directories 773 | 7. Add example [multiFileProject](examples/multiFileProject) to demo for multiple-file project 774 | 8. Improve accuracy by using `float`, instead of `uint32_t` for `dutycycle` 775 | 9. DutyCycle to be optionally updated at the end current PWM period instead of immediately. 776 | 10. Add support to `ESP32-S3` 777 | 11. Display informational warning only when `_PWM_LOGLEVEL_` > 3 778 | 12. Remove crashing `PIN_D24` from examples 779 | 780 | 781 | --- 782 | --- 783 | 784 | ### Contributions and Thanks 785 | 786 | Many thanks for everyone for bug reporting, new feature suggesting, testing and contributing to the development of this library. 787 | 788 | 789 | --- 790 | 791 | ## Contributing 792 | 793 | If you want to contribute to this project: 794 | - Report bugs and errors 795 | - Ask for enhancements 796 | - Create issues and pull requests 797 | - Tell other people about this library 798 | 799 | --- 800 | 801 | ### License 802 | 803 | - The library is licensed under [MIT](https://github.com/khoih-prog/ESP32_PWM/blob/main/LICENSE) 804 | 805 | --- 806 | 807 | ## Copyright 808 | 809 | Copyright 2021- Khoi Hoang 810 | 811 | 812 | --------------------------------------------------------------------------------