From b7dcbd434740cf458196dfdfd60251c50ed64393 Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Sun, 30 Jan 2022 00:53:18 -0500 Subject: [PATCH] v1.2.0 to fix `multiple-definitions` linker error ### Releases v1.2.0 1. Fix `multiple-definitions` linker error. Drop `src_cpp` and `src_h` directories 2. Add example [multiFileProject](examples/multiFileProject) to demo for multiple-file project 3. Improve accuracy by using `double`, instead of `uint32_t` for `dutycycle`, `period` 4. Optimize library code by using `reference-passing` instead of `value-passing` 5. Update examples accordingly --- CONTRIBUTING.md | 6 +- README.md | 203 ++-- changelog.md | 9 + .../ISR_8_PWMs_Array/ISR_8_PWMs_Array.ino | 9 +- .../ISR_8_PWMs_Array_Complex.ino | 51 +- .../ISR_8_PWMs_Array_Simple.ino | 7 +- .../ISR_Changing_PWM/ISR_Changing_PWM.ino | 13 +- examples/ISR_Modify_PWM/ISR_Modify_PWM.ino | 11 +- .../multiFileProject/multiFileProject.cpp | 13 + examples/multiFileProject/multiFileProject.h | 18 + .../multiFileProject/multiFileProject.ino | 54 + keywords.txt | 7 +- library.json | 7 +- library.properties | 4 +- src/AVR_Slow_PWM.h | 680 +------------ src/AVR_Slow_PWM.hpp | 937 +++++++----------- src/AVR_Slow_PWM_ISR.h | 262 +---- src/AVR_Slow_PWM_ISR.hpp | 521 +++++----- src/AVR_Slow_PWM_ISR_Impl.h | 356 +++++++ src/AVR_Slow_PWM_Impl.h | 906 +++++++++++++++++ 20 files changed, 2147 insertions(+), 1927 deletions(-) create mode 100644 examples/multiFileProject/multiFileProject.cpp create mode 100644 examples/multiFileProject/multiFileProject.h create mode 100644 examples/multiFileProject/multiFileProject.ino create mode 100644 src/AVR_Slow_PWM_ISR_Impl.h create mode 100644 src/AVR_Slow_PWM_Impl.h diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee079f6..9a62029 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ If you don't find anything, please [open a new issue](https://github.com/khoih-p Please ensure to specify the following: -* Arduino IDE version (e.g. 1.8.16) or Platform.io version +* Arduino IDE version (e.g. 1.8.19) or Platform.io version * Arduino / Adafruit / Sparkfun `AVR` Core Version (e.g. Arduino AVR core v1.8.3) * Contextual information (e.g. what you were trying to achieve) * Simplest possible steps to reproduce @@ -26,10 +26,10 @@ Please ensure to specify the following: ### Example ``` -Arduino IDE version: 1.8.16 +Arduino IDE version: 1.8.19 Arduino AVR core v1.8.3 OS: Ubuntu 20.04 LTS -Linux xy-Inspiron-3593 5.4.0-90-generic #101-Ubuntu SMP Fri Oct 15 20:00:55 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux +Linux xy-Inspiron-3593 5.4.0-96-generic #109-Ubuntu SMP Wed Jan 12 16:49:16 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux Context: I encountered a crash while trying to use the Timer Interrupt. diff --git a/README.md b/README.md index 1d2b50f..d2b8f1b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ ## Table of Contents +* [Important Change from v1.2.0](#Important-Change-from-v120) * [Why do we need this AVR_Slow_PWM library](#why-do-we-need-this-AVR_Slow_PWM-library) * [Features](#features) * [Why using ISR-based PWM is better](#why-using-isr-based-pwm-is-better) @@ -38,6 +39,7 @@ * [ 3. ISR_8_PWMs_Array_Simple](examples/ISR_8_PWMs_Array_Simple) * [ 4. ISR_Changing_PWM](examples/ISR_Changing_PWM) * [ 5. ISR_Modify_PWM](examples/ISR_Modify_PWM) + * [ 6. multiFileProject](examples/multiFileProject) **New** * [Example ISR_8_PWMs_Array_Complex](#Example-ISR_8_PWMs_Array_Complex) * [Debug Terminal Output Samples](#debug-terminal-output-samples) * [1. ISR_8_PWMs_Array_Complex on Arduino AVR Leonardo ATMega32U4](#1-ISR_8_PWMs_Array_Complex-on-Arduino-AVR-Leonardo-ATMega32U4) @@ -58,6 +60,13 @@ --- --- +### Important Change from v1.2.0 + +Please have a look at [HOWTO Fix `Multiple Definitions` Linker Error](#howto-fix-multiple-definitions-linker-error) + +--- +--- + ### Why do we need this [AVR_Slow_PWM library](https://github.com/khoih-prog/AVR_Slow_PWM) ### Features @@ -122,13 +131,15 @@ The catch is **your function is now part of an ISR (Interrupt Service Routine), ## Prerequisites - 1. [`Arduino IDE 1.8.16+` for Arduino](https://www.arduino.cc/en/Main/Software) + 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) 2. [`Arduino AVR core 1.8.3+`](https://github.com/arduino/ArduinoCore-avr) for Arduino AVR boards. Use Arduino Board Manager to install. [![Latest release](https://img.shields.io/github/release/arduino/ArduinoCore-avr.svg)](https://github.com/arduino/ArduinoCore-avr/releases/latest/) 3. [`Adafruit AVR core 1.4.14+`](https://github.com/adafruit/Adafruit_Arduino_Boards) for Adafruit AVR boards. Use Arduino Board Manager to install. 4. [`Sparkfun AVR core 1.1.13+`](https://github.com/sparkfun/Arduino_Boards) for Sparkfun AVR boards. Use Arduino Board Manager to install. 5. To use with certain example - - [`SimpleTimer library`](https://github.com/jfturcot/SimpleTimer) for [ISR_8_PWMs_Array_Complex example](examples/ISR_8_PWMs_Array_Complex). + - [`SimpleTimer library`](https://github.com/jfturcot/SimpleTimer) to use with some examples. + + --- --- @@ -161,24 +172,26 @@ Another way to install is to: ### HOWTO Fix `Multiple Definitions` Linker Error -The current library implementation, using **xyz-Impl.h instead of standard xyz.cpp**, possibly creates certain `Multiple Definitions` Linker error in certain use cases. Although it's simple to just modify several lines of code, either in the library or in the application, the library is adding 2 more source directories +The current library implementation, using `xyz-Impl.h` instead of standard `xyz.cpp`, possibly creates certain `Multiple Definitions` Linker error in certain use cases. -1. **scr_h** for new h-only files -2. **src_cpp** for standard h/cpp files +You can include this `.hpp` file -besides the standard **src** directory. +``` +// Can be included as many times as necessary, without `Multiple Definitions` Linker Error +#include "AVR_Slow_PWM.hpp" //https://github.com/khoih-prog/AVR_Slow_PWM +``` + +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 -To use the **old standard cpp** way, locate this library' directory, then just +``` +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error +#include "AVR_Slow_PWM.h" //https://github.com/khoih-prog/AVR_Slow_PWM +``` -1. **Delete the all the files in src directory.** -2. **Copy all the files in src_cpp directory into src.** -3. Close then reopen the application code in Arduino IDE, etc. to recompile from scratch. +Check the new [**multiFileProject** example](examples/multiFileProject) for a `HOWTO` demo. -To re-use the **new h-only** way, just +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) -1. **Delete the all the files in src directory.** -2. **Copy the files in src_h directory into src.** -3. Close then reopen the application code in Arduino IDE, etc. to recompile from scratch. --- --- @@ -277,6 +290,7 @@ void setup() 3. [ISR_8_PWMs_Array_Simple](examples/ISR_8_PWMs_Array_Simple) 4. [ISR_Changing_PWM](examples/ISR_Changing_PWM) 5. [ISR_Modify_PWM](examples/ISR_Modify_PWM) + 6. [**multiFileProject**](examples/multiFileProject) **New** --- @@ -305,6 +319,7 @@ void setup() #define USING_MICROS_RESOLUTION true //false +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "AVR_Slow_PWM.h" #include // https://github.com/jfturcot/SimpleTimer @@ -376,9 +391,9 @@ typedef struct irqCallback irqCallbackStartFunc; irqCallback irqCallbackStopFunc; - uint32_t PWM_Freq; + double PWM_Freq; - uint32_t PWM_DutyCycle; + double PWM_DutyCycle; uint32_t deltaMicrosStart; uint32_t previousMicrosStart; @@ -398,29 +413,29 @@ void doingSomethingStop(int index); #else // #if USE_COMPLEX_STRUCT -volatile unsigned long deltaMicrosStart [NUMBER_ISR_PWMS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -volatile unsigned long previousMicrosStart [NUMBER_ISR_PWMS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +volatile unsigned long deltaMicrosStart [] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +volatile unsigned long previousMicrosStart [] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -volatile unsigned long deltaMicrosStop [NUMBER_ISR_PWMS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -volatile unsigned long previousMicrosStop [NUMBER_ISR_PWMS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +volatile unsigned long deltaMicrosStop [] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +volatile unsigned long previousMicrosStop [] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // You can assign any interval for any timer here, in Microseconds -uint32_t PWM_Period[NUMBER_ISR_PWMS] = +double PWM_Period[] = { - 1000L, 500L, 333L, 250L, 200L, 166L, 142L, 125L + 1000.0, 500.0, 333.333, 250.0, 200.0, 166.667, 142.857, 125.0 }; // You can assign any interval for any timer here, in Hz -double PWM_Freq[NUMBER_ISR_PWMS] = +double PWM_Freq[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, }; // You can assign any interval for any timer here, in Microseconds -uint32_t PWM_DutyCycle[NUMBER_ISR_PWMS] = +double PWM_DutyCycle[] = { - 5, 10, 20, 25, 30, 35, 40, 45 + 5.0, 10.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0 }; void doingSomethingStart(int index) @@ -534,17 +549,17 @@ void doingSomethingStop7() #if USE_COMPLEX_STRUCT -ISR_PWM_Data curISR_PWM_Data[NUMBER_ISR_PWMS] = +ISR_PWM_Data curISR_PWM_Data[] = { // pin, irqCallbackStartFunc, irqCallbackStopFunc, PWM_Freq, PWM_DutyCycle, deltaMicrosStart, previousMicrosStart, deltaMicrosStop, previousMicrosStop - { LED_BUILTIN, doingSomethingStart0, doingSomethingStop0, 1, 5, 0, 0, 0, 0 }, - { PIN_D0, doingSomethingStart1, doingSomethingStop1, 2, 10, 0, 0, 0, 0 }, - { PIN_D1, doingSomethingStart2, doingSomethingStop2, 3, 20, 0, 0, 0, 0 }, - { PIN_D2, doingSomethingStart3, doingSomethingStop3, 4, 25, 0, 0, 0, 0 }, - { PIN_D3, doingSomethingStart4, doingSomethingStop4, 5, 30, 0, 0, 0, 0 }, - { PIN_D4, doingSomethingStart5, doingSomethingStop5, 6, 35, 0, 0, 0, 0 }, - { PIN_D5, doingSomethingStart6, doingSomethingStop6, 7, 40, 0, 0, 0, 0 }, - { PIN_D6, doingSomethingStart7, doingSomethingStop7, 8, 45, 0, 0, 0, 0 }, + { LED_BUILTIN, doingSomethingStart0, doingSomethingStop0, 1.0, 5.0, 0, 0, 0, 0 }, + { PIN_D0, doingSomethingStart1, doingSomethingStop1, 2.0, 10.0, 0, 0, 0, 0 }, + { PIN_D1, doingSomethingStart2, doingSomethingStop2, 3.0, 20.0, 0, 0, 0, 0 }, + { PIN_D2, doingSomethingStart3, doingSomethingStop3, 4.0, 25.0, 0, 0, 0, 0 }, + { PIN_D3, doingSomethingStart4, doingSomethingStop4, 5.0, 30.0, 0, 0, 0, 0 }, + { PIN_D4, doingSomethingStart5, doingSomethingStop5, 6.0, 35.0, 0, 0, 0, 0 }, + { PIN_D5, doingSomethingStart6, doingSomethingStop6, 7.0, 40.0, 0, 0, 0, 0 }, + { PIN_D6, doingSomethingStart7, doingSomethingStop7, 8.0, 45.0, 0, 0, 0, 0 }, }; @@ -568,13 +583,13 @@ void doingSomethingStop(int index) #else // #if USE_COMPLEX_STRUCT -irqCallback irqCallbackStartFunc[NUMBER_ISR_PWMS] = +irqCallback irqCallbackStartFunc[] = { doingSomethingStart0, doingSomethingStart1, doingSomethingStart2, doingSomethingStart3, doingSomethingStart4, doingSomethingStart5, doingSomethingStart6, doingSomethingStart7 }; -irqCallback irqCallbackStopFunc[NUMBER_ISR_PWMS] = +irqCallback irqCallbackStopFunc[] = { doingSomethingStop0, doingSomethingStop1, doingSomethingStop2, doingSomethingStop3, doingSomethingStop4, doingSomethingStop5, doingSomethingStop6, doingSomethingStop7 @@ -609,9 +624,9 @@ void simpleTimerDoingSomething2s() Serial.print(F("PWM Channel : ")); Serial.print(i); Serial.print(F(", prog Period (ms): ")); - Serial.print(1000.f / curISR_PWM_Data[i].PWM_Freq); + Serial.print(1000.0f / curISR_PWM_Data[i].PWM_Freq); - Serial.print(F(", actual : ")); Serial.print((uint32_t) curISR_PWM_Data[i].deltaMicrosStart); + Serial.print(F(", actual (uS) : ")); Serial.print(curISR_PWM_Data[i].deltaMicrosStart); Serial.print(F(", prog DutyCycle : ")); @@ -625,7 +640,7 @@ void simpleTimerDoingSomething2s() Serial.print(F("PWM Channel : ")); Serial.print(i); - Serial.print(1000 / PWM_Freq[i]); + Serial.print(1000.0f / PWM_Freq[i]); Serial.print(F(", prog. Period (us): ")); Serial.print(PWM_Period[i]); Serial.print(F(", actual : ")); Serial.print(deltaMicrosStart[i]); @@ -775,7 +790,7 @@ The following is the sample terminal output when running example [ISR_8_PWMs_Arr ``` Starting ISR_8_PWMs_Array_Complex on Arduino AVR ATMega32U4 -AVR_Slow_PWM v1.1.0 +AVR_Slow_PWM v1.2.0 CPU Frequency = 16 MHz [PWM] T3 [PWM] Freq * 1000 = 10000000.00 @@ -793,23 +808,23 @@ Channel : 5 Period : 166666 OnTime : 58333 Start_Time : 3520968 Channel : 6 Period : 142857 OnTime : 57142 Start_Time : 3520968 Channel : 7 Period : 125000 OnTime : 56250 Start_Time : 3520968 SimpleTimer (us): 2000, us : 13547548, Dus : 10026624 -PWM Channel : 0, prog Period (ms): 1000.00, actual : 1000000, prog DutyCycle : 5, actual : 5.00 -PWM Channel : 1, prog Period (ms): 500.00, actual : 500016, prog DutyCycle : 10, actual : 10.00 -PWM Channel : 2, prog Period (ms): 333.33, actual : 333396, prog DutyCycle : 20, actual : 19.98 -PWM Channel : 3, prog Period (ms): 250.00, actual : 250028, prog DutyCycle : 25, actual : 24.96 -PWM Channel : 4, prog Period (ms): 200.00, actual : 200192, prog DutyCycle : 30, actual : 29.87 -PWM Channel : 5, prog Period (ms): 166.67, actual : 166792, prog DutyCycle : 35, actual : 34.89 -PWM Channel : 6, prog Period (ms): 142.86, actual : 142992, prog DutyCycle : 40, actual : 39.87 -PWM Channel : 7, prog Period (ms): 125.00, actual : 125192, prog DutyCycle : 45, actual : 44.89 +PWM Channel : 0, prog Period (ms): 1000.00, actual (uS) : 1000000, prog DutyCycle : 5, actual : 5.00 +PWM Channel : 1, prog Period (ms): 500.00, actual (uS) : 500016, prog DutyCycle : 10, actual : 10.00 +PWM Channel : 2, prog Period (ms): 333.33, actual (uS) : 333396, prog DutyCycle : 20, actual : 19.98 +PWM Channel : 3, prog Period (ms): 250.00, actual (uS) : 250028, prog DutyCycle : 25, actual : 24.96 +PWM Channel : 4, prog Period (ms): 200.00, actual (uS) : 200192, prog DutyCycle : 30, actual : 29.87 +PWM Channel : 5, prog Period (ms): 166.67, actual (uS) : 166792, prog DutyCycle : 35, actual : 34.89 +PWM Channel : 6, prog Period (ms): 142.86, actual (uS) : 142992, prog DutyCycle : 40, actual : 39.87 +PWM Channel : 7, prog Period (ms): 125.00, actual (uS) : 125192, prog DutyCycle : 45, actual : 44.89 SimpleTimer (us): 2000, us : 23596540, Dus : 10048992 -PWM Channel : 0, prog Period (ms): 1000.00, actual : 1000000, prog DutyCycle : 5, actual : 5.00 -PWM Channel : 1, prog Period (ms): 500.00, actual : 500012, prog DutyCycle : 10, actual : 10.00 -PWM Channel : 2, prog Period (ms): 333.33, actual : 333396, prog DutyCycle : 20, actual : 19.98 -PWM Channel : 3, prog Period (ms): 250.00, actual : 250028, prog DutyCycle : 25, actual : 24.95 -PWM Channel : 4, prog Period (ms): 200.00, actual : 200192, prog DutyCycle : 30, actual : 29.88 -PWM Channel : 5, prog Period (ms): 166.67, actual : 166796, prog DutyCycle : 35, actual : 34.90 -PWM Channel : 6, prog Period (ms): 142.86, actual : 142992, prog DutyCycle : 40, actual : 39.87 -PWM Channel : 7, prog Period (ms): 125.00, actual : 125192, prog DutyCycle : 45, actual : 44.89 +PWM Channel : 0, prog Period (ms): 1000.00, actual (uS) : 1000000, prog DutyCycle : 5, actual : 5.00 +PWM Channel : 1, prog Period (ms): 500.00, actual (uS) : 500012, prog DutyCycle : 10, actual : 10.00 +PWM Channel : 2, prog Period (ms): 333.33, actual (uS) : 333396, prog DutyCycle : 20, actual : 19.98 +PWM Channel : 3, prog Period (ms): 250.00, actual (uS) : 250028, prog DutyCycle : 25, actual : 24.95 +PWM Channel : 4, prog Period (ms): 200.00, actual (uS) : 200192, prog DutyCycle : 30, actual : 29.88 +PWM Channel : 5, prog Period (ms): 166.67, actual (uS) : 166796, prog DutyCycle : 35, actual : 34.90 +PWM Channel : 6, prog Period (ms): 142.86, actual (uS) : 142992, prog DutyCycle : 40, actual : 39.87 +PWM Channel : 7, prog Period (ms): 125.00, actual (uS) : 125192, prog DutyCycle : 45, actual : 44.89 ``` --- @@ -820,7 +835,7 @@ The following is the sample terminal output when running example [**ISR_8_PWMs_A ``` Starting ISR_8_PWMs_Array_Complex on Arduino AVR Mega2560/ADK -AVR_Slow_PWM v1.1.0 +AVR_Slow_PWM v1.2.0 CPU Frequency = 16 MHz [PWM] T3 [PWM] Freq * 1000 = 10000000.00 @@ -838,23 +853,23 @@ Channel : 5 Period : 166666 OnTime : 58333 Start_Time : 2024988 Channel : 6 Period : 142857 OnTime : 57142 Start_Time : 2024988 Channel : 7 Period : 125000 OnTime : 56250 Start_Time : 2024988 SimpleTimer (us): 2000, us : 12070388, Dus : 10045444 -PWM Channel : 0, prog Period (ms): 1000.00, actual : 1000000, prog DutyCycle : 5, actual : 4.98 -PWM Channel : 1, prog Period (ms): 500.00, actual : 499996, prog DutyCycle : 10, actual : 10.00 -PWM Channel : 2, prog Period (ms): 333.33, actual : 333396, prog DutyCycle : 20, actual : 19.98 -PWM Channel : 3, prog Period (ms): 250.00, actual : 250196, prog DutyCycle : 25, actual : 24.94 -PWM Channel : 4, prog Period (ms): 200.00, actual : 200192, prog DutyCycle : 30, actual : 29.88 -PWM Channel : 5, prog Period (ms): 166.67, actual : 166792, prog DutyCycle : 35, actual : 34.90 -PWM Channel : 6, prog Period (ms): 142.86, actual : 142988, prog DutyCycle : 40, actual : 39.87 -PWM Channel : 7, prog Period (ms): 125.00, actual : 125196, prog DutyCycle : 45, actual : 44.89 +PWM Channel : 0, prog Period (ms): 1000.00, actual (uS) : 1000000, prog DutyCycle : 5, actual : 4.98 +PWM Channel : 1, prog Period (ms): 500.00, actual (uS) : 499996, prog DutyCycle : 10, actual : 10.00 +PWM Channel : 2, prog Period (ms): 333.33, actual (uS) : 333396, prog DutyCycle : 20, actual : 19.98 +PWM Channel : 3, prog Period (ms): 250.00, actual (uS) : 250196, prog DutyCycle : 25, actual : 24.94 +PWM Channel : 4, prog Period (ms): 200.00, actual (uS) : 200192, prog DutyCycle : 30, actual : 29.88 +PWM Channel : 5, prog Period (ms): 166.67, actual (uS) : 166792, prog DutyCycle : 35, actual : 34.90 +PWM Channel : 6, prog Period (ms): 142.86, actual (uS) : 142988, prog DutyCycle : 40, actual : 39.87 +PWM Channel : 7, prog Period (ms): 125.00, actual (uS) : 125196, prog DutyCycle : 45, actual : 44.89 SimpleTimer (us): 2000, us : 22144772, Dus : 10074384 -PWM Channel : 0, prog Period (ms): 1000.00, actual : 1000000, prog DutyCycle : 5, actual : 5.00 -PWM Channel : 1, prog Period (ms): 500.00, actual : 499996, prog DutyCycle : 10, actual : 10.00 -PWM Channel : 2, prog Period (ms): 333.33, actual : 333396, prog DutyCycle : 20, actual : 19.98 -PWM Channel : 3, prog Period (ms): 250.00, actual : 250196, prog DutyCycle : 25, actual : 24.94 -PWM Channel : 4, prog Period (ms): 200.00, actual : 200196, prog DutyCycle : 30, actual : 29.87 -PWM Channel : 5, prog Period (ms): 166.67, actual : 166792, prog DutyCycle : 35, actual : 34.90 -PWM Channel : 6, prog Period (ms): 142.86, actual : 143016, prog DutyCycle : 40, actual : 39.87 -PWM Channel : 7, prog Period (ms): 125.00, actual : 125008, prog DutyCycle : 45, actual : 44.96 +PWM Channel : 0, prog Period (ms): 1000.00, actual (uS) : 1000000, prog DutyCycle : 5, actual : 5.00 +PWM Channel : 1, prog Period (ms): 500.00, actual (uS) : 499996, prog DutyCycle : 10, actual : 10.00 +PWM Channel : 2, prog Period (ms): 333.33, actual (uS) : 333396, prog DutyCycle : 20, actual : 19.98 +PWM Channel : 3, prog Period (ms): 250.00, actual (uS) : 250196, prog DutyCycle : 25, actual : 24.94 +PWM Channel : 4, prog Period (ms): 200.00, actual (uS) : 200196, prog DutyCycle : 30, actual : 29.87 +PWM Channel : 5, prog Period (ms): 166.67, actual (uS) : 166792, prog DutyCycle : 35, actual : 34.90 +PWM Channel : 6, prog Period (ms): 142.86, actual (uS) : 143016, prog DutyCycle : 40, actual : 39.87 +PWM Channel : 7, prog Period (ms): 125.00, actual (uS) : 125008, prog DutyCycle : 45, actual : 44.96 ``` --- @@ -865,7 +880,7 @@ The following is the sample terminal output when running example [**ISR_8_PWMs_A ``` Starting ISR_8_PWMs_Array_Complex on Arduino AVR UNO, Nano, etc. -AVR_Slow_PWM v1.1.0 +AVR_Slow_PWM v1.2.0 CPU Frequency = 16 MHz [PWM] T1 [PWM] Freq * 1000 = 10000000.00 @@ -884,23 +899,23 @@ Channel : 5 Period : 166666 OnTime : 58333 Start_Time : 2026740 Channel : 6 Period : 142857 OnTime : 57142 Start_Time : 2026740 Channel : 7 Period : 125000 OnTime : 56250 Start_Time : 2026740 SimpleTimer (us): 2000, us : 12072128, Dus : 10045452 -PWM Channel : 0, prog Period (ms): 1000.00, actual : 1000000, prog DutyCycle : 5, actual : 5.00 -PWM Channel : 1, prog Period (ms): 500.00, actual : 500012, prog DutyCycle : 10, actual : 10.00 -PWM Channel : 2, prog Period (ms): 333.33, actual : 333396, prog DutyCycle : 20, actual : 19.98 -PWM Channel : 3, prog Period (ms): 250.00, actual : 250024, prog DutyCycle : 25, actual : 24.95 -PWM Channel : 4, prog Period (ms): 200.00, actual : 200208, prog DutyCycle : 30, actual : 29.96 -PWM Channel : 5, prog Period (ms): 166.67, actual : 166792, prog DutyCycle : 35, actual : 34.90 -PWM Channel : 6, prog Period (ms): 142.86, actual : 142992, prog DutyCycle : 40, actual : 39.87 -PWM Channel : 7, prog Period (ms): 125.00, actual : 125192, prog DutyCycle : 45, actual : 44.89 +PWM Channel : 0, prog Period (ms): 1000.00, actual (uS) : 1000000, prog DutyCycle : 5, actual : 5.00 +PWM Channel : 1, prog Period (ms): 500.00, actual (uS) : 500012, prog DutyCycle : 10, actual : 10.00 +PWM Channel : 2, prog Period (ms): 333.33, actual (uS) : 333396, prog DutyCycle : 20, actual : 19.98 +PWM Channel : 3, prog Period (ms): 250.00, actual (uS) : 250024, prog DutyCycle : 25, actual : 24.95 +PWM Channel : 4, prog Period (ms): 200.00, actual (uS) : 200208, prog DutyCycle : 30, actual : 29.96 +PWM Channel : 5, prog Period (ms): 166.67, actual (uS) : 166792, prog DutyCycle : 35, actual : 34.90 +PWM Channel : 6, prog Period (ms): 142.86, actual (uS) : 142992, prog DutyCycle : 40, actual : 39.87 +PWM Channel : 7, prog Period (ms): 125.00, actual (uS) : 125192, prog DutyCycle : 45, actual : 44.89 SimpleTimer (us): 2000, us : 22147164, Dus : 10075036 -PWM Channel : 0, prog Period (ms): 1000.00, actual : 1000000, prog DutyCycle : 5, actual : 5.00 -PWM Channel : 1, prog Period (ms): 500.00, actual : 500012, prog DutyCycle : 10, actual : 10.00 -PWM Channel : 2, prog Period (ms): 333.33, actual : 333396, prog DutyCycle : 20, actual : 19.98 -PWM Channel : 3, prog Period (ms): 250.00, actual : 250196, prog DutyCycle : 25, actual : 24.94 -PWM Channel : 4, prog Period (ms): 200.00, actual : 199996, prog DutyCycle : 30, actual : 30.00 -PWM Channel : 5, prog Period (ms): 166.67, actual : 166792, prog DutyCycle : 35, actual : 34.90 -PWM Channel : 6, prog Period (ms): 142.86, actual : 143012, prog DutyCycle : 40, actual : 39.87 -PWM Channel : 7, prog Period (ms): 125.00, actual : 125012, prog DutyCycle : 45, actual : 44.95 +PWM Channel : 0, prog Period (ms): 1000.00, actual (uS) : 1000000, prog DutyCycle : 5, actual : 5.00 +PWM Channel : 1, prog Period (ms): 500.00, actual (uS) : 500012, prog DutyCycle : 10, actual : 10.00 +PWM Channel : 2, prog Period (ms): 333.33, actual (uS) : 333396, prog DutyCycle : 20, actual : 19.98 +PWM Channel : 3, prog Period (ms): 250.00, actual (uS) : 250196, prog DutyCycle : 25, actual : 24.94 +PWM Channel : 4, prog Period (ms): 200.00, actual (uS) : 199996, prog DutyCycle : 30, actual : 30.00 +PWM Channel : 5, prog Period (ms): 166.67, actual (uS) : 166792, prog DutyCycle : 35, actual : 34.90 +PWM Channel : 6, prog Period (ms): 142.86, actual (uS) : 143012, prog DutyCycle : 40, actual : 39.87 +PWM Channel : 7, prog Period (ms): 125.00, actual (uS) : 125012, prog DutyCycle : 45, actual : 44.95 ``` --- @@ -911,7 +926,7 @@ The following is the sample terminal output when running example [ISR_Modify_PWM ``` Starting ISR_Modify_PWM on Arduino AVR Mega2560/ADK -AVR_Slow_PWM v1.1.0 +AVR_Slow_PWM v1.2.0 CPU Frequency = 16 MHz [PWM] T3 [PWM] Freq * 1000 = 10000000.00 @@ -935,7 +950,7 @@ The following is the sample terminal output when running example [ISR_Changing_P ``` Starting ISR_Changing_PWM on Arduino AVR Mega2560/ADK -AVR_Slow_PWM v1.1.0 +AVR_Slow_PWM v1.2.0 CPU Frequency = 16 MHz [PWM] T3 [PWM] Freq * 1000 = 10000000.00 @@ -998,6 +1013,10 @@ Submit issues to: [AVR_Slow_PWM issues](https://github.com/khoih-prog/AVR_Slow_P 1. Basic hardware multi-channel PWM for **AVR boards, such as Mega-2560, UNO,Nano, Leonardo, etc.** using AVR core 2. Add Table of Contents 3. Add functions to modify PWM settings on-the-fly +4. Fix `multiple-definitions` linker error. Drop `src_cpp` and `src_h` directories +5. Add example [multiFileProject](examples/multiFileProject) to demo for multiple-file project +6. Improve accuracy by using `double`, instead of `uint32_t` for `dutycycle`, `period` +7. Optimize library code by using `reference-passing` instead of `value-passing` --- --- diff --git a/changelog.md b/changelog.md index 6ca1087..e42e8e7 100644 --- a/changelog.md +++ b/changelog.md @@ -12,6 +12,7 @@ ## Table of Contents * [Changelog](#changelog) + * [Releases v1.2.0](#releases-v120) * [Releases v1.1.0](#releases-v110) * [Initial Releases v1.0.0](#Initial-Releases-v100) @@ -20,6 +21,14 @@ ## Changelog +### Releases v1.2.0 + +1. Fix `multiple-definitions` linker error. Drop `src_cpp` and `src_h` directories +2. Add example [multiFileProject](examples/multiFileProject) to demo for multiple-file project +3. Improve accuracy by using `double`, instead of `uint32_t` for `dutycycle`, `period` +4. Optimize library code by using `reference-passing` instead of `value-passing` +5. Update examples accordingly + ### Releases v1.1.0 1. Add functions to modify PWM settings on-the-fly diff --git a/examples/ISR_8_PWMs_Array/ISR_8_PWMs_Array.ino b/examples/ISR_8_PWMs_Array/ISR_8_PWMs_Array.ino index 7d70c11..2f7baad 100644 --- a/examples/ISR_8_PWMs_Array/ISR_8_PWMs_Array.ino +++ b/examples/ISR_8_PWMs_Array/ISR_8_PWMs_Array.ino @@ -39,6 +39,7 @@ #define USING_MICROS_RESOLUTION true //false +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "AVR_Slow_PWM.h" #include // https://github.com/jfturcot/SimpleTimer @@ -97,15 +98,15 @@ uint32_t PWM_Pin[] = #define NUMBER_ISR_PWMS ( sizeof(PWM_Pin) / sizeof(uint32_t) ) // You can assign any interval for any timer here, in Hz -double PWM_Freq[NUMBER_ISR_PWMS] = +double PWM_Freq[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, }; // You can assign any interval for any timer here, in Microseconds -uint32_t PWM_DutyCycle[NUMBER_ISR_PWMS] = +double PWM_DutyCycle[] = { - 5, 10, 20, 25, 30, 35, 40, 45 + 5.0, 10.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0 }; typedef void (*irqCallback) (); @@ -147,7 +148,7 @@ void doingSomething7() } -irqCallback irqCallbackStartFunc[NUMBER_ISR_PWMS] = +irqCallback irqCallbackStartFunc[] = { doingSomething0, doingSomething1, doingSomething2, doingSomething3, doingSomething4, doingSomething5, doingSomething6, doingSomething7 diff --git a/examples/ISR_8_PWMs_Array_Complex/ISR_8_PWMs_Array_Complex.ino b/examples/ISR_8_PWMs_Array_Complex/ISR_8_PWMs_Array_Complex.ino index 9348a96..6e69bbc 100644 --- a/examples/ISR_8_PWMs_Array_Complex/ISR_8_PWMs_Array_Complex.ino +++ b/examples/ISR_8_PWMs_Array_Complex/ISR_8_PWMs_Array_Complex.ino @@ -39,6 +39,7 @@ #define USING_MICROS_RESOLUTION true //false +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "AVR_Slow_PWM.h" #include // https://github.com/jfturcot/SimpleTimer @@ -110,9 +111,9 @@ typedef struct irqCallback irqCallbackStartFunc; irqCallback irqCallbackStopFunc; - uint32_t PWM_Freq; + double PWM_Freq; - uint32_t PWM_DutyCycle; + double PWM_DutyCycle; uint32_t deltaMicrosStart; uint32_t previousMicrosStart; @@ -132,29 +133,29 @@ void doingSomethingStop(int index); #else // #if USE_COMPLEX_STRUCT -volatile unsigned long deltaMicrosStart [NUMBER_ISR_PWMS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -volatile unsigned long previousMicrosStart [NUMBER_ISR_PWMS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +volatile unsigned long deltaMicrosStart [] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +volatile unsigned long previousMicrosStart [] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -volatile unsigned long deltaMicrosStop [NUMBER_ISR_PWMS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; -volatile unsigned long previousMicrosStop [NUMBER_ISR_PWMS] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +volatile unsigned long deltaMicrosStop [] = { 0, 0, 0, 0, 0, 0, 0, 0 }; +volatile unsigned long previousMicrosStop [] = { 0, 0, 0, 0, 0, 0, 0, 0 }; // You can assign any interval for any timer here, in Microseconds -uint32_t PWM_Period[NUMBER_ISR_PWMS] = +double PWM_Period[] = { - 1000L, 500L, 333L, 250L, 200L, 166L, 142L, 125L + 1000.0, 500.0, 333.333, 250.0, 200.0, 166.667, 142.857, 125.0 }; // You can assign any interval for any timer here, in Hz -double PWM_Freq[NUMBER_ISR_PWMS] = +double PWM_Freq[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, }; // You can assign any interval for any timer here, in Microseconds -uint32_t PWM_DutyCycle[NUMBER_ISR_PWMS] = +double PWM_DutyCycle[] = { - 5, 10, 20, 25, 30, 35, 40, 45 + 5.0, 10.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0 }; void doingSomethingStart(int index) @@ -268,17 +269,17 @@ void doingSomethingStop7() #if USE_COMPLEX_STRUCT -ISR_PWM_Data curISR_PWM_Data[NUMBER_ISR_PWMS] = +ISR_PWM_Data curISR_PWM_Data[] = { // pin, irqCallbackStartFunc, irqCallbackStopFunc, PWM_Freq, PWM_DutyCycle, deltaMicrosStart, previousMicrosStart, deltaMicrosStop, previousMicrosStop - { LED_BUILTIN, doingSomethingStart0, doingSomethingStop0, 1, 5, 0, 0, 0, 0 }, - { PIN_D0, doingSomethingStart1, doingSomethingStop1, 2, 10, 0, 0, 0, 0 }, - { PIN_D1, doingSomethingStart2, doingSomethingStop2, 3, 20, 0, 0, 0, 0 }, - { PIN_D2, doingSomethingStart3, doingSomethingStop3, 4, 25, 0, 0, 0, 0 }, - { PIN_D3, doingSomethingStart4, doingSomethingStop4, 5, 30, 0, 0, 0, 0 }, - { PIN_D4, doingSomethingStart5, doingSomethingStop5, 6, 35, 0, 0, 0, 0 }, - { PIN_D5, doingSomethingStart6, doingSomethingStop6, 7, 40, 0, 0, 0, 0 }, - { PIN_D6, doingSomethingStart7, doingSomethingStop7, 8, 45, 0, 0, 0, 0 }, + { LED_BUILTIN, doingSomethingStart0, doingSomethingStop0, 1.0, 5.0, 0, 0, 0, 0 }, + { PIN_D0, doingSomethingStart1, doingSomethingStop1, 2.0, 10.0, 0, 0, 0, 0 }, + { PIN_D1, doingSomethingStart2, doingSomethingStop2, 3.0, 20.0, 0, 0, 0, 0 }, + { PIN_D2, doingSomethingStart3, doingSomethingStop3, 4.0, 25.0, 0, 0, 0, 0 }, + { PIN_D3, doingSomethingStart4, doingSomethingStop4, 5.0, 30.0, 0, 0, 0, 0 }, + { PIN_D4, doingSomethingStart5, doingSomethingStop5, 6.0, 35.0, 0, 0, 0, 0 }, + { PIN_D5, doingSomethingStart6, doingSomethingStop6, 7.0, 40.0, 0, 0, 0, 0 }, + { PIN_D6, doingSomethingStart7, doingSomethingStop7, 8.0, 45.0, 0, 0, 0, 0 }, }; @@ -302,13 +303,13 @@ void doingSomethingStop(int index) #else // #if USE_COMPLEX_STRUCT -irqCallback irqCallbackStartFunc[NUMBER_ISR_PWMS] = +irqCallback irqCallbackStartFunc[] = { doingSomethingStart0, doingSomethingStart1, doingSomethingStart2, doingSomethingStart3, doingSomethingStart4, doingSomethingStart5, doingSomethingStart6, doingSomethingStart7 }; -irqCallback irqCallbackStopFunc[NUMBER_ISR_PWMS] = +irqCallback irqCallbackStopFunc[] = { doingSomethingStop0, doingSomethingStop1, doingSomethingStop2, doingSomethingStop3, doingSomethingStop4, doingSomethingStop5, doingSomethingStop6, doingSomethingStop7 @@ -343,9 +344,9 @@ void simpleTimerDoingSomething2s() Serial.print(F("PWM Channel : ")); Serial.print(i); Serial.print(F(", prog Period (ms): ")); - Serial.print(1000.f / curISR_PWM_Data[i].PWM_Freq); + Serial.print(1000.0f / curISR_PWM_Data[i].PWM_Freq); - Serial.print(F(", actual : ")); Serial.print((uint32_t) curISR_PWM_Data[i].deltaMicrosStart); + Serial.print(F(", actual (uS) : ")); Serial.print(curISR_PWM_Data[i].deltaMicrosStart); Serial.print(F(", prog DutyCycle : ")); @@ -359,7 +360,7 @@ void simpleTimerDoingSomething2s() Serial.print(F("PWM Channel : ")); Serial.print(i); - Serial.print(1000 / PWM_Freq[i]); + Serial.print(1000.0f / PWM_Freq[i]); Serial.print(F(", prog. Period (us): ")); Serial.print(PWM_Period[i]); Serial.print(F(", actual : ")); Serial.print(deltaMicrosStart[i]); diff --git a/examples/ISR_8_PWMs_Array_Simple/ISR_8_PWMs_Array_Simple.ino b/examples/ISR_8_PWMs_Array_Simple/ISR_8_PWMs_Array_Simple.ino index a7d401f..6010411 100644 --- a/examples/ISR_8_PWMs_Array_Simple/ISR_8_PWMs_Array_Simple.ino +++ b/examples/ISR_8_PWMs_Array_Simple/ISR_8_PWMs_Array_Simple.ino @@ -39,6 +39,7 @@ #define USING_MICROS_RESOLUTION true //false +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "AVR_Slow_PWM.h" #include // https://github.com/jfturcot/SimpleTimer @@ -97,15 +98,15 @@ uint32_t PWM_Pin[] = #define NUMBER_ISR_PWMS ( sizeof(PWM_Pin) / sizeof(uint32_t) ) // You can assign any interval for any timer here, in Hz -double PWM_Freq[NUMBER_ISR_PWMS] = +double PWM_Freq[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, }; // You can assign any interval for any timer here, in Microseconds -uint32_t PWM_DutyCycle[NUMBER_ISR_PWMS] = +double PWM_DutyCycle[] = { - 5, 10, 20, 25, 30, 35, 40, 45 + 5.0, 10.0, 20.0, 25.0, 30.0, 35.0, 40.0, 45.0 }; //////////////////////////////////////////////// diff --git a/examples/ISR_Changing_PWM/ISR_Changing_PWM.ino b/examples/ISR_Changing_PWM/ISR_Changing_PWM.ino index a476e69..015ec91 100644 --- a/examples/ISR_Changing_PWM/ISR_Changing_PWM.ino +++ b/examples/ISR_Changing_PWM/ISR_Changing_PWM.ino @@ -39,6 +39,7 @@ #define USING_MICROS_RESOLUTION true //false +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "AVR_Slow_PWM.h" #define LED_OFF HIGH @@ -87,14 +88,14 @@ double PWM_Freq1 = 1.0f; double PWM_Freq2 = 2.0f; // You can assign any interval for any timer here, in microseconds -uint32_t PWM_Period1 = 1000000 / PWM_Freq1; +double PWM_Period1 = 1000000.0 / PWM_Freq1; // You can assign any interval for any timer here, in microseconds -uint32_t PWM_Period2 = 1000000 / PWM_Freq2; +double PWM_Period2 = 1000000.0 / PWM_Freq2; // You can assign any duty_cycle for any PWM here, from 0-100 -uint32_t PWM_DutyCycle1 = 50; +double PWM_DutyCycle1 = 50.0; // You can assign any duty_cycle for any PWM here, from 0-100 -uint32_t PWM_DutyCycle2 = 90; +double PWM_DutyCycle2 = 90.0; // Channel number used to identify associated channel int channelNum; @@ -191,7 +192,7 @@ void loop() channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1, PWM_DutyCycle1); #else // Or using period in millisecs resolution - channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1 / 1000, PWM_DutyCycle1); + channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1 / 1000.0, PWM_DutyCycle1); #endif #endif @@ -212,7 +213,7 @@ void loop() channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period2, PWM_DutyCycle2); #else // Or using period in millisecs resolution - channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period2 / 1000, PWM_DutyCycle2); + channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period2 / 1000.0, PWM_DutyCycle2); #endif #endif diff --git a/examples/ISR_Modify_PWM/ISR_Modify_PWM.ino b/examples/ISR_Modify_PWM/ISR_Modify_PWM.ino index 50c63b7..6999f57 100644 --- a/examples/ISR_Modify_PWM/ISR_Modify_PWM.ino +++ b/examples/ISR_Modify_PWM/ISR_Modify_PWM.ino @@ -39,6 +39,7 @@ #define USING_MICROS_RESOLUTION true //false +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "AVR_Slow_PWM.h" #define LED_OFF HIGH @@ -87,14 +88,14 @@ double PWM_Freq1 = 1.0f; double PWM_Freq2 = 2.0f; // You can assign any interval for any timer here, in microseconds -uint32_t PWM_Period1 = 1000000 / PWM_Freq1; +double PWM_Period1 = 1000000.0 / PWM_Freq1; // You can assign any interval for any timer here, in microseconds -uint32_t PWM_Period2 = 1000000 / PWM_Freq2; +double PWM_Period2 = 1000000.0 / PWM_Freq2; // You can assign any duty_cycle for any PWM here, from 0-100 -uint32_t PWM_DutyCycle1 = 10; +double PWM_DutyCycle1 = 10.0; // You can assign any duty_cycle for any PWM here, from 0-100 -uint32_t PWM_DutyCycle2 = 90; +double PWM_DutyCycle2 = 90.0; // Channel number used to identify associated channel int channelNum; @@ -192,7 +193,7 @@ void setup() channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1, PWM_DutyCycle1); #else // Or using period in millisecs resolution - channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1 / 1000, PWM_DutyCycle1); + channelNum = ISR_PWM.setPWM_Period(PWM_Pin, PWM_Period1 / 1000.0, PWM_DutyCycle1); #endif #endif } diff --git a/examples/multiFileProject/multiFileProject.cpp b/examples/multiFileProject/multiFileProject.cpp new file mode 100644 index 0000000..fc353de --- /dev/null +++ b/examples/multiFileProject/multiFileProject.cpp @@ -0,0 +1,13 @@ +/**************************************************************************************************************************** + multiFileProject.cpp + + For AVR-based boards (UNO, Nano, Mega, 32U4, 16U4, etc. ) + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/AVR_Slow_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +// To demo how to include files in multi-file Projects + +#include "multiFileProject.h" diff --git a/examples/multiFileProject/multiFileProject.h b/examples/multiFileProject/multiFileProject.h new file mode 100644 index 0000000..066ecfb --- /dev/null +++ b/examples/multiFileProject/multiFileProject.h @@ -0,0 +1,18 @@ +/**************************************************************************************************************************** + multiFileProject.h + + For AVR-based boards (UNO, Nano, Mega, 32U4, 16U4, etc. ) + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/AVR_Slow_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +// To demo how to include files in multi-file Projects + +#pragma once + +#define USING_MICROS_RESOLUTION true //false + +// Can be included as many times as necessary, without `Multiple Definitions` Linker Error +#include "AVR_Slow_PWM.hpp" diff --git a/examples/multiFileProject/multiFileProject.ino b/examples/multiFileProject/multiFileProject.ino new file mode 100644 index 0000000..c3add11 --- /dev/null +++ b/examples/multiFileProject/multiFileProject.ino @@ -0,0 +1,54 @@ +/**************************************************************************************************************************** + multiFileProject.ino + + For AVR-based boards (UNO, Nano, Mega, 32U4, 16U4, etc. ) + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/AVR_Slow_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +// To demo how to include files in multi-file Projects + +#if ( defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || \ + defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_MINI) || defined(ARDUINO_AVR_ETHERNET) || \ + defined(ARDUINO_AVR_FIO) || defined(ARDUINO_AVR_BT) || defined(ARDUINO_AVR_LILYPAD) || defined(ARDUINO_AVR_PRO) || \ + defined(ARDUINO_AVR_NG) || defined(ARDUINO_AVR_UNO_WIFI_DEV_ED) || defined(ARDUINO_AVR_DUEMILANOVE) || defined(ARDUINO_AVR_FEATHER328P) || \ + defined(ARDUINO_AVR_METRO) || defined(ARDUINO_AVR_PROTRINKET5) || defined(ARDUINO_AVR_PROTRINKET3) || defined(ARDUINO_AVR_PROTRINKET5FTDI) || \ + defined(ARDUINO_AVR_PROTRINKET3FTDI) ) + #define USE_TIMER_1 true + #warning Using Timer1 +#else + #define USE_TIMER_3 true + #warning Using Timer3 +#endif + +#define AVR_SLOW_PWM_VERSION_MIN_TARGET F("AVR_Slow_PWM v1.2.0") +#define AVR_SLOW_PWM_VERSION_MIN 1002000 + +#include "multiFileProject.h" + +// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error +#include "AVR_Slow_PWM.h" + +void setup() +{ + Serial.begin(115200); + while (!Serial); + + Serial.println("\nStart multiFileProject"); + Serial.println(AVR_SLOW_PWM_VERSION); + +#if defined(AVR_SLOW_PWM_VERSION_MIN) + if (AVR_SLOW_PWM_VERSION_INT < AVR_SLOW_PWM_VERSION_MIN) + { + Serial.print("Warning. Must use this example on Version equal or later than : "); + Serial.println(AVR_SLOW_PWM_VERSION_MIN_TARGET); + } +#endif +} + +void loop() +{ + // put your main code here, to run repeatedly: +} diff --git a/keywords.txt b/keywords.txt index 16f7840..baab318 100644 --- a/keywords.txt +++ b/keywords.txt @@ -51,4 +51,9 @@ getNumAvailablePWMChannels KEYWORD2 ####################################### AVR_Slow_PWM_VERSION LITERAL1 -INVALID_NRF52_PIN LITERAL1 +AVR_Slow_PWM_VERSION_MAJOR LITERAL1 +AVR_Slow_PWM_VERSION_MINOR LITERAL1 +AVR_Slow_PWM_VERSION_PATCH LITERAL1 +AVR_Slow_PWM_VERSION_INT LITERAL1 + +INVALID_AVR_PIN LITERAL1 diff --git a/library.json b/library.json index e074106..9c0cada 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "AVR_Slow_PWM", - "version": "1.1.0", + "version": "1.2.0", "keywords": "timing, device, control, timer, interrupt, hardware, isr, isr-based, hardware-timer, mission-critical, accuracy, precise, non-blocking, avr, mega-2560, nano, uno, leonardo, 32u4, 16u4, at-mega", "description": "This library enables you to use ISR-based PWM channels on AVR-based boards, such as Mega-2560, UNO,Nano, Leonardo, etc., to create and output PWM any GPIO pin. It now supports 16 ISR-based PWM channels, while consuming only 1 Hardware Timer. PWM channel interval can be very long (ulong microsecs / millisecs). The most important feature is they're ISR-based PWM channels, supporting lower PWM frequencies with suitable accuracy. Their executions are not blocked by bad-behaving functions or tasks. This important feature is absolutely necessary for mission-critical tasks. These ISR-based PWMs, still work even if other software functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software-based PWM using millis() or micros(). That's necessary if you need to control devices requiring high precision. Now you can change the PWM settings on-the-fly", "authors": @@ -12,7 +12,7 @@ "repository": { "type": "git", - "url": "//https://github.com/khoih-prog/AVR_Slow_PWM" + "url": "https://github.com/khoih-prog/AVR_Slow_PWM.git" }, "homepage": "https://github.com/khoih-prog/AVR_Slow_PWM", "export": { @@ -22,8 +22,9 @@ "tests" ] }, + "license": "MIT", "frameworks": "*", "platforms": "avr", "examples": "examples/*/*/*.ino", - "license": "MIT" + "headers": ["AVR_Slow_PWM.h", "AVR_Slow_PWM.hpp"] } diff --git a/library.properties b/library.properties index eb430aa..d5bfbde 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=AVR_Slow_PWM -version=1.1.0 +version=1.2.0 author=Khoi Hoang maintainer=Khoi Hoang sentence=This library enables you to use ISR-based PWM channels on AVR-based boards, such as Mega-2560, UNO,Nano, Leonardo, etc., to create and output PWM any GPIO pin. @@ -9,4 +9,4 @@ url=https://github.com/khoih-prog/AVR_Slow_PWM architectures=avr repository=https://github.com/khoih-prog/AVR_Slow_PWM license=MIT -includes=AVR_Slow_PWM.h +includes=AVR_Slow_PWM.h,AVR_Slow_PWM.hpp diff --git a/src/AVR_Slow_PWM.h b/src/AVR_Slow_PWM.h index b6b607d..bfcc648 100644 --- a/src/AVR_Slow_PWM.h +++ b/src/AVR_Slow_PWM.h @@ -31,685 +31,9 @@ #ifndef AVR_SLOW_PWM_H #define AVR_SLOW_PWM_H -#if defined(BOARD_NAME) - #undef BOARD_NAME -#endif - -#if ( defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || \ - defined(__AVR_ATmega640__) || defined(__AVR_ATmega641__)) - #if defined(TIMER_INTERRUPT_USING_ATMEGA2560) - #undef TIMER_INTERRUPT_USING_ATMEGA2560 - #endif - #define TIMER_INTERRUPT_USING_ATMEGA2560 true - #define BOARD_NAME F("Arduino AVR Mega2560/ADK") - #warning Using Arduino AVR Mega, Mega640(P), Mega2560/ADK. Timer1-5 available - -#elif ( defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || \ - defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_MINI) || defined(ARDUINO_AVR_ETHERNET) || \ - defined(ARDUINO_AVR_FIO) || defined(ARDUINO_AVR_BT) || defined(ARDUINO_AVR_LILYPAD) || defined(ARDUINO_AVR_PRO) || \ - defined(ARDUINO_AVR_NG) || defined(ARDUINO_AVR_UNO_WIFI_DEV_ED) || defined(ARDUINO_AVR_DUEMILANOVE) ) - #define BOARD_NAME "Arduino AVR UNO, Nano, etc." - #warning Using Aduino AVR ATMega644(P), ATMega328(P) such as UNO, Nano. Only Timer1,2 available - -#elif ( defined(ARDUINO_AVR_FEATHER328P) || defined(ARDUINO_AVR_METRO) || defined(ARDUINO_AVR_PROTRINKET5) || defined(ARDUINO_AVR_PROTRINKET3) || \ - defined(ARDUINO_AVR_PROTRINKET5FTDI) || defined(ARDUINO_AVR_PROTRINKET3FTDI) ) - #define BOARD_NAME F("Adafruit AVR ATMega328(P)") - #warning Using Adafruit ATMega328(P), such as AVR_FEATHER328P or AVR_METRO. Only Timer1,2 available - -#elif ( defined(ARDUINO_AVR_LEONARDO) || defined(ARDUINO_AVR_LEONARDO_ETH) || defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_MICRO) || \ - defined(ARDUINO_AVR_ESPLORA) || defined(ARDUINO_AVR_LILYPAD_USB) || defined(ARDUINO_AVR_ROBOT_CONTROL) || defined(ARDUINO_AVR_ROBOT_MOTOR) || \ - defined(ARDUINO_AVR_CIRCUITPLAY) || defined(ARDUINO_AVR_YUNMINI) || defined(ARDUINO_AVR_INDUSTRIAL101) || defined(ARDUINO_AVR_LININO_ONE) ) - #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) - #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 - #endif - #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true - #define BOARD_NAME F("Arduino AVR ATMega32U4") - #warning Using Arduino ATMega32U4, such as Leonardo or Leonardo ETH. Only Timer1,3,4 available - -#elif ( defined(ARDUINO_AVR_FLORA8 ) || defined(ARDUINO_AVR_FEATHER32U4) || defined(ARDUINO_AVR_CIRCUITPLAY) || defined(ARDUINO_AVR_ITSYBITSY32U4_5V) || \ - defined(ARDUINO_AVR_ITSYBITSY32U4_3V) || defined(ARDUINO_AVR_BLUEFRUITMICRO) || defined(ARDUINO_AVR_ADAFRUIT32U4) ) - #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) - #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 - #endif - #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true - #define BOARD_NAME F("Adafruit AVR ATMega32U4") - #warning Using Adafruit ATMega32U4, such as Feather_32u4, AVR_CIRCUITPLAY, etc.. Only Timer1,3,4 available - -#elif ( defined(__AVR_ATmega32U4__) || defined(ARDUINO_AVR_MAKEYMAKEY ) || defined(ARDUINO_AVR_PROMICRO) || defined(ARDUINO_AVR_FIOV3) || \ - defined(ARDUINO_AVR_QDUINOMINI) || defined(ARDUINO_AVR_LILYPAD_ARDUINO_USB_PLUS_BOARD ) ) - #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) - #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 - #endif - #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true - #define BOARD_NAME F("Generic or Sparkfun AVR ATMega32U4") - #warning Using Generic ATMega32U4, such as Sparkfun AVR_MAKEYMAKEY, AVR_PROMICRO, etc. Only Timer1,3,4 available - -#elif ( defined(__AVR_ATmega328P__) || defined(ARDUINO_AVR_DIGITAL_SANDBOX ) || defined(ARDUINO_REDBOT) || defined(ARDUINO_AVR_SERIAL_7_SEGMENT) ) - #define BOARD_NAME F("Generic or Sparkfun AVR ATMega328P") - #warning Using Generic ATMega328P, such as Sparkfun AVR_DIGITAL_SANDBOX, REDBOT, etc. - -#elif ( defined(__AVR_ATmega128RFA1__) || defined(ARDUINO_ATMEGA128RFA1_DEV_BOARD) ) - #define BOARD_NAME F("Generic or Sparkfun AVR ATMega128RFA1") - #warning Using Generic ATMega128RFA1, such as Sparkfun ATMEGA128RFA1_DEV_BOARD, etc. - -#elif ( defined(ARDUINO_AVR_GEMMA) || defined(ARDUINO_AVR_TRINKET3) || defined(ARDUINO_AVR_TRINKET5) ) - #error These AVR boards are not supported! Please check your Tools->Board setting. - -#else - #error This is designed only for Arduino or Adafruit AVR board! Please check your Tools->Board setting. -#endif - -#ifndef AVR_SLOW_PWM_VERSION - #define AVR_SLOW_PWM_VERSION F("AVR_Slow_PWM v1.1.0") -#endif - -#ifndef _PWM_LOGLEVEL_ - #define _PWM_LOGLEVEL_ 1 -#endif - -#include -#include -#include "Arduino.h" -#include "pins_arduino.h" - -#include "PWM_Generic_Debug.h" - -#define MAX_COUNT_8BIT 255 -#define MAX_COUNT_10BIT 1023 -#define MAX_COUNT_16BIT 65535 - -#if defined(__AVR_ATmega8__) || defined(__AVR_ATmega128__) - #define TCCR2A TCCR2 - #define TCCR2B TCCR2 - #define COM2A1 COM21 - #define COM2A0 COM20 - #define OCR2A OCR2 - #define TIMSK2 TIMSK - #define OCIE2A OCIE2 - #define TIMER2_COMPA_vect TIMER2_COMP_vect - #define TIMSK1 TIMSK -#endif - -typedef void (*timer_callback)(void); -typedef void (*timer_callback_p)(void *); - -enum -{ - HW_TIMER_0 = 0, - HW_TIMER_1, - HW_TIMER_2, - HW_TIMER_3, - HW_TIMER_4, - HW_TIMER_5, - NUM_HW_TIMERS -}; - -enum -{ - NO_CLOCK_SOURCE = 0, - NO_PRESCALER, - PRESCALER_8, - PRESCALER_64, - PRESCALER_256, - PRESCALER_1024, - NUM_ITEMS -}; - -enum -{ - T2_NO_CLOCK_SOURCE = 0, - T2_NO_PRESCALER, - T2_PRESCALER_8, - T2_PRESCALER_32, - T2_PRESCALER_64, - T2_PRESCALER_128, - T2_PRESCALER_256, - T2_PRESCALER_1024, - T2_NUM_ITEMS -}; - -const unsigned int prescalerDiv [NUM_ITEMS] = { 1, 1, 8, 64, 256, 1024 }; -const unsigned int prescalerDivT2 [T2_NUM_ITEMS] = { 1, 1, 8, 32, 64, 128, 256, 1024 }; - -class TimerInterrupt -{ - private: - - bool _timerDone; - int8_t _timer; - unsigned int _prescalerIndex; - uint32_t _OCRValue; - uint32_t _OCRValueRemaining; - volatile long _toggle_count; - double _frequency; - - void* _callback; // pointer to the callback function - void* _params; // function parameter - - void set_OCR(void); - - public: - - TimerInterrupt() - { - _timer = -1; - _frequency = 0; - _callback = NULL; - _params = NULL; - _timerDone = false; - _prescalerIndex = NO_PRESCALER; - _OCRValue = 0; - _OCRValueRemaining = 0; - _toggle_count = -1; - }; - - explicit TimerInterrupt(uint8_t timerNo) - { - _timer = timerNo; - _frequency = 0; - _callback = NULL; - _params = NULL; - _timerDone = false; - _prescalerIndex = NO_PRESCALER; - _OCRValue = 0; - _OCRValueRemaining = 0; - _toggle_count = -1; - }; - - void callback() __attribute__((always_inline)) - { - if (_callback != NULL) - { - if (_params != NULL) - (*(timer_callback_p)_callback)(_params); - else - (*(timer_callback)_callback)(); - } - } - - void init(int8_t timer); - - void init() - { - init(_timer); - }; - - // frequency (in hertz) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely - bool setFrequency(float frequency, timer_callback_p callback, /* void* */ uint32_t params, unsigned long duration = 0); - - // frequency (in hertz) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely - bool setFrequency(float frequency, timer_callback callback, unsigned long duration = 0) - { - return setFrequency(frequency, reinterpret_cast(callback), /*NULL*/ 0, duration); - } - - // interval (in ms) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely - template - bool setInterval(unsigned long interval, void (*callback)(TArg), TArg params, unsigned long duration = 0) - { - static_assert(sizeof(TArg) <= sizeof(uint32_t), "setInterval() callback argument size must be <= 4 bytes"); - return setFrequency((float) (1000.0f / interval), reinterpret_cast(callback), (uint32_t) params, duration); - } - - // interval (in ms) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely - bool setInterval(unsigned long interval, timer_callback callback, unsigned long duration = 0) - { - return setFrequency((float) (1000.0f / interval), reinterpret_cast(callback), /*NULL*/ 0, duration); - } - - template - bool attachInterrupt(float frequency, void (*callback)(TArg), TArg params, unsigned long duration = 0) - { - static_assert(sizeof(TArg) <= sizeof(uint32_t), "attachInterrupt() callback argument size must be <= 4 bytes"); - return setFrequency(frequency, reinterpret_cast(callback), (uint32_t) params, duration); - } - - bool attachInterrupt(float frequency, timer_callback callback, unsigned long duration = 0) - { - return setFrequency(frequency, reinterpret_cast(callback), /*NULL*/ 0, duration); - } - - // Interval (in ms) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely - template - bool attachInterruptInterval(float interval, void (*callback)(TArg), TArg params, unsigned long duration = 0) - { - static_assert(sizeof(TArg) <= sizeof(uint32_t), "attachInterruptInterval() callback argument size must be <= 4 bytes"); - return setFrequency( (float) ( 1000.0f / interval), reinterpret_cast(callback), (uint32_t) params, duration); - } - - // Interval (in ms) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely - bool attachInterruptInterval(float interval, timer_callback callback, unsigned long duration = 0) - { - return setFrequency( (float) ( 1000.0f / interval), reinterpret_cast (callback), /*NULL*/ 0, duration); - } - - void detachInterrupt(); - - void disableTimer(void) - { - detachInterrupt(); - } - - // Duration (in milliseconds). Duration = 0 or not specified => run indefinitely - void reattachInterrupt(unsigned long duration = 0); - - // Duration (in milliseconds). Duration = 0 or not specified => run indefinitely - void enableTimer(unsigned long duration = 0) __attribute__((always_inline)) - { - reattachInterrupt(duration); - } - - // Just stop clock source, still keep the count - void pauseTimer(void); - - // Just reconnect clock source, continue from the current count - void resumeTimer(void); - - // Just stop clock source, clear the count - void stopTimer(void) - { - detachInterrupt(); - } - - // Just reconnect clock source, start current count from 0 - void restartTimer(unsigned long duration = 0) - { - reattachInterrupt(duration); - } - - int8_t getTimer() __attribute__((always_inline)) - { - return _timer; - }; - - long getCount() __attribute__((always_inline)) - { - return _toggle_count; - }; - - void setCount(long countInput) __attribute__((always_inline)) - { - //cli();//stop interrupts - //noInterrupts(); - - _toggle_count = countInput; - - //sei();//enable interrupts - //interrupts(); - }; - - long get_OCRValue() __attribute__((always_inline)) - { - return _OCRValue; - }; - - long get_OCRValueRemaining() __attribute__((always_inline)) - { - return _OCRValueRemaining; - }; - - void adjust_OCRValue() //__attribute__((always_inline)) - { - //cli();//stop interrupts - noInterrupts(); - - if (_timer != 2) - { -#if TIMER_INTERRUPT_USING_ATMEGA_32U4 - if (_timer == 4) - { - _OCRValueRemaining -= min(MAX_COUNT_8BIT, _OCRValueRemaining); - } - else - { - _OCRValueRemaining -= min(MAX_COUNT_16BIT, _OCRValueRemaining); - } -#else - _OCRValueRemaining -= min(MAX_COUNT_16BIT, _OCRValueRemaining); -#endif - } - else - _OCRValueRemaining -= min(MAX_COUNT_8BIT, _OCRValueRemaining); - - if (_OCRValueRemaining == 0) - { - // Reset value for next cycle - _OCRValueRemaining = _OCRValue; - _timerDone = true; - } - else - _timerDone = false; - - //sei();//enable interrupts - interrupts(); - }; - - void reload_OCRValue() //__attribute__((always_inline)) - { - //cli();//stop interrupts - noInterrupts(); - - // Reset value for next cycle, have to deduct the value already loaded to OCR register - - if (_timer != 2) - { -#if TIMER_INTERRUPT_USING_ATMEGA_32U4 - if (_timer == 4) - { - _OCRValueRemaining = _OCRValue - min(MAX_COUNT_8BIT, _OCRValueRemaining); - } - else - { - _OCRValueRemaining = _OCRValue - min(MAX_COUNT_16BIT, _OCRValueRemaining); - } -#else - _OCRValueRemaining = _OCRValue - min(MAX_COUNT_16BIT, _OCRValueRemaining); -#endif - } - else - _OCRValueRemaining = _OCRValue - min(MAX_COUNT_8BIT, _OCRValueRemaining); - - _timerDone = false; - - //sei();//enable interrupts - interrupts(); - }; - - bool checkTimerDone(void) //__attribute__((always_inline)) - { - return _timerDone; - }; - -}; // class TimerInterrupt - -////////////////////////////////////////////// - -// To be sure not used Timers are disabled -#if !defined(USE_TIMER_1) - #define USE_TIMER_1 false -#endif - -#if !defined(USE_TIMER_2) - #define USE_TIMER_2 false -#elif ( USE_TIMER_2 && TIMER_INTERRUPT_USING_ATMEGA_32U4 ) - #error Timer2 is disabled for ATMEGA_32U4, only available for ATMEGA_328(P) and Mega -#endif - -#if !defined(USE_TIMER_3) - #define USE_TIMER_3 false -#elif ( USE_TIMER_3 && ( TIMER_INTERRUPT_USING_ATMEGA_32U4 || TIMER_INTERRUPT_USING_ATMEGA2560 ) ) - #warning Timer3 (16-bit) is OK to use for ATMEGA_32U4 and Mega -#elif USE_TIMER_3 - #error Timer3 is only available for ATMEGA_32U4 and Mega -#endif - -#if !defined(USE_TIMER_4) - #define USE_TIMER_4 false -#elif ( USE_TIMER_4 && ( TIMER_INTERRUPT_USING_ATMEGA_32U4 || TIMER_INTERRUPT_USING_ATMEGA2560 ) ) - #warning Timer4 is OK to use for ATMEGA_32U4 (10-bit but using as 8-bit) and Mega (16-bit) -#elif USE_TIMER_4 - #error Timer4 is only available for ATMEGA_32U4 and Mega -#endif - -#if !defined(USE_TIMER_5) - #define USE_TIMER_5 false -#elif ( USE_TIMER_5 && TIMER_INTERRUPT_USING_ATMEGA2560 ) - #warning Timer5 is OK to use for Mega -#elif USE_TIMER_5 - #error Timer5 is only available for Mega -#endif - -////////////////////////////////////////////// - -#if USE_TIMER_1 - #ifndef TIMER1_INSTANTIATED - // To force pre-instatiate only once - #define TIMER1_INSTANTIATED - TimerInterrupt ITimer1(HW_TIMER_1); - - // Timer0 is used for micros(), millis(), delay(), etc and can't be used - // Pre-instatiate - - ISR(TIMER1_COMPA_vect) - { - long countLocal = ITimer1.getCount(); - - if (ITimer1.getTimer() == 1) - { - if (countLocal != 0) - { - if (ITimer1.checkTimerDone()) - { - PWM_LOGDEBUG3(("T1 callback, _OCRValueRemaining ="), ITimer1.get_OCRValueRemaining(), (", millis ="), millis()); - - ITimer1.callback(); - - // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_16BIT if _OCRValueRemaining > MAX_COUNT_16BIT - ITimer1.reload_OCRValue(); - - if (countLocal > 0) - ITimer1.setCount(countLocal - 1); - } - else - { - //Deduct _OCRValue by min(MAX_COUNT_16BIT, _OCRValue) - // If _OCRValue == 0, flag _timerDone for next cycle - // If last one (_OCRValueRemaining < MAX_COUNT_16BIT) => load _OCR register _OCRValueRemaining - ITimer1.adjust_OCRValue(); - } - } - else - { - PWM_LOGWARN(("T1 done")); - - ITimer1.detachInterrupt(); - } - } - } - - #endif //#ifndef TIMER1_INSTANTIATED -#endif //#if USE_TIMER_1 - -#if USE_TIMER_2 - #ifndef TIMER2_INSTANTIATED - #define TIMER2_INSTANTIATED - TimerInterrupt ITimer2(HW_TIMER_2); - - ISR(TIMER2_COMPA_vect) - { - long countLocal = ITimer2.getCount(); - - if (ITimer2.getTimer() == 2) - { - if (countLocal != 0) - { - if (ITimer2.checkTimerDone()) - { - PWM_LOGDEBUG3(("T2 callback, _OCRValueRemaining ="), ITimer2.get_OCRValueRemaining(), (", millis ="), millis()); - - ITimer2.callback(); - // To reload _OCRValue - ITimer2.reload_OCRValue(); - - if (countLocal > 0) - ITimer2.setCount(countLocal - 1); - - } - else - { - //Deduct _OCRValue by min(MAX_COUNT_8BIT, _OCRValue) - // If _OCRValue == 0, flag _timerDone for next cycle - ITimer2.adjust_OCRValue(); - } - } - else - { - PWM_LOGWARN(("T2 done")); - - ITimer2.detachInterrupt(); - } - } - } - #endif //#ifndef TIMER2_INSTANTIATED -#endif //#if USE_TIMER_2 - -#if (TIMER_INTERRUPT_USING_ATMEGA2560 || TIMER_INTERRUPT_USING_ATMEGA_32U4) - - // Pre-instatiate - #if USE_TIMER_3 - #ifndef TIMER3_INSTANTIATED - // To force pre-instatiate only once - #define TIMER3_INSTANTIATED - TimerInterrupt ITimer3(HW_TIMER_3); - - ISR(TIMER3_COMPA_vect) - { - long countLocal = ITimer3.getCount(); - - if (ITimer3.getTimer() == 3) - { - if (countLocal != 0) - { - if (ITimer3.checkTimerDone()) - { - PWM_LOGDEBUG3(("T3 callback, _OCRValueRemaining ="), ITimer3.get_OCRValueRemaining(), (", millis ="), millis()); - - ITimer3.callback(); - - // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_16BIT - ITimer3.reload_OCRValue(); - - if (countLocal > 0) - ITimer3.setCount(countLocal - 1); - } - else - { - //Deduct _OCRValue by min(MAX_COUNT_16BIT, _OCRValue) - // If _OCRValue == 0, flag _timerDone for next cycle - // If last one (_OCRValueRemaining < MAX_COUNT_16BIT) => load _OCR register _OCRValueRemaining - ITimer3.adjust_OCRValue(); - } - } - else - { - PWM_LOGWARN(("T3 done")); - - ITimer3.detachInterrupt(); - } - } - } - - #endif //#ifndef TIMER3_INSTANTIATED - #endif //#if USE_TIMER_3 - -#endif //#if (TIMER_INTERRUPT_USING_ATMEGA2560 || TIMER_INTERRUPT_USING_ATMEGA_32U4) - -#if (TIMER_INTERRUPT_USING_ATMEGA2560 || TIMER_INTERRUPT_USING_ATMEGA_32U4) - - // Even 32u4 Timer4 has 10-bit counter, we use only 8-bit to simplify by not using 2-bit High Byte Register (TC4H) - // Check 15.2.2 Accuracy, page 141 of ATmega16U4/32U4 [DATASHEET] - - #if USE_TIMER_4 - #ifndef TIMER4_INSTANTIATED - // To force pre-instatiate only once - #define TIMER4_INSTANTIATED - TimerInterrupt ITimer4(HW_TIMER_4); - - ISR(TIMER4_COMPA_vect) - { - long countLocal = ITimer4.getCount(); - - if (ITimer4.getTimer() == 4) - { - if (countLocal != 0) - { - if (ITimer4.checkTimerDone()) - { - PWM_LOGDEBUG3(("T4 callback, _OCRValueRemaining ="), ITimer4.get_OCRValueRemaining(), (", millis ="), millis()); - - ITimer4.callback(); - - // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_16BIT (Mega2560) or MAX_COUNT_8BIT (32u4) - ITimer4.reload_OCRValue(); - - if (countLocal > 0) - ITimer4.setCount(countLocal - 1); - } - else - { - //Deduct _OCRValue by min(MAX_COUNT_16BIT, _OCRValue) or min(MAX_COUNT_8BIT, _OCRValue) - // If _OCRValue == 0, flag _timerDone for next cycle - // If last one (_OCRValueRemaining < MAX_COUNT_16BIT / MAX_COUNT_8BIT) => load _OCR register _OCRValueRemaining - ITimer4.adjust_OCRValue(); - } - } - else - { - PWM_LOGWARN(("T4 done")); - - ITimer4.detachInterrupt(); - } - } - } - - - #endif //#ifndef TIMER4_INSTANTIATED - #endif //#if USE_TIMER_4 - -#endif //#if (TIMER_INTERRUPT_USING_ATMEGA2560 || TIMER_INTERRUPT_USING_ATMEGA_32U4) - -#if TIMER_INTERRUPT_USING_ATMEGA2560 - - #if USE_TIMER_5 - #ifndef TIMER5_INSTANTIATED - // To force pre-instatiate only once - #define TIMER5_INSTANTIATED - TimerInterrupt ITimer5(HW_TIMER_5); - - ISR(TIMER5_COMPA_vect) - { - long countLocal = ITimer5.getCount(); - - if (ITimer5.getTimer() == 5) - { - if (countLocal != 0) - { - if (ITimer5.checkTimerDone()) - { - PWM_LOGDEBUG3(("T5 callback, _OCRValueRemaining ="), ITimer5.get_OCRValueRemaining(), (", millis ="), millis()); - - ITimer5.callback(); - - // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_16BIT - ITimer5.reload_OCRValue(); - - if (countLocal > 0) - ITimer5.setCount(countLocal - 1); - } - else - { - //Deduct _OCRValue by min(MAX_COUNT_16BIT, _OCRValue) - // If _OCRValue == 0, flag _timerDone for next cycle - // If last one (_OCRValueRemaining < MAX_COUNT_16BIT) => load _OCR register _OCRValueRemaining - ITimer5.adjust_OCRValue(); - } - } - else - { - PWM_LOGWARN(("T5 done")); - - ITimer5.detachInterrupt(); - } - } - } - - #endif //#ifndef TIMER5_INSTANTIATED - #endif //#if USE_TIMER_5 - -#endif //#if TIMER_INTERRUPT_USING_ATMEGA2560 - - #include "AVR_Slow_PWM.hpp" +#include "AVR_Slow_PWM_Impl.h" + #include "AVR_Slow_PWM_ISR.h" diff --git a/src/AVR_Slow_PWM.hpp b/src/AVR_Slow_PWM.hpp index b6b05f5..283118c 100644 --- a/src/AVR_Slow_PWM.hpp +++ b/src/AVR_Slow_PWM.hpp @@ -24,617 +24,446 @@ ------- ----------- ---------- ----------- 1.0.0 K.Hoang 27/09/2021 Initial coding for AVR-based boards (UNO, Nano, Mega, 32U4, 16U4, etc. ) 1.1.0 K Hoang 10/11/2021 Add functions to modify PWM settings on-the-fly -****************************************************************************************************************************/ +*****************************************************************************************************************************/ #pragma once #ifndef AVR_SLOW_PWM_HPP #define AVR_SLOW_PWM_HPP -#ifndef _PWM_LOGLEVEL_ - #define _PWM_LOGLEVEL_ 0 +#if defined(BOARD_NAME) + #undef BOARD_NAME #endif -void TimerInterrupt::init(int8_t timer) -{ - // Set timer specific stuff - // All timers in CTC mode - // 8 bit timers will require changing prescalar values, - // whereas 16 bit timers are set to either ck/1 or ck/64 prescalar - - //cli();//stop interrupts - noInterrupts(); +#if ( defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || \ + defined(__AVR_ATmega640__) || defined(__AVR_ATmega641__)) + #if defined(TIMER_INTERRUPT_USING_ATMEGA2560) + #undef TIMER_INTERRUPT_USING_ATMEGA2560 + #endif + #define TIMER_INTERRUPT_USING_ATMEGA2560 true + #define BOARD_NAME F("Arduino AVR Mega2560/ADK") + #warning Using Arduino AVR Mega, Mega640(P), Mega2560/ADK. Timer1-5 available - switch (timer) - { - #if defined(TCCR1A) && defined(TCCR1B) && defined(WGM12) - case 1: - // 16 bit timer - TCCR1A = 0; - TCCR1B = 0; - // Page 172-173. ATmega 328/328P or Page 145-146 of ATmega 640/1280/2560 - // Mode 4 => Clear Timer on Compare match (CTC) using OCR1A for counter value - bitWrite(TCCR1B, WGM12, 1); - // No scaling now - bitWrite(TCCR1B, CS10, 1); - - PWM_LOGWARN(F("T1")); - - break; - #endif - - #if defined(TCCR2A) && defined(TCCR2B) - case 2: - // 8 bit timer - TCCR2A = 0; - TCCR2B = 0; - // Page 205-206. ATmegal328, Page 184-185 ATmega 640/1280/2560 - // Mode 2 => Clear Timer on Compare match (CTC) using OCR2A for counter value - bitWrite(TCCR2A, WGM21, 1); - // No scaling now - bitWrite(TCCR2B, CS20, 1); - - PWM_LOGWARN(F("T2")); - - break; - #endif - - #if defined(TCCR3A) && defined(TCCR3B) && defined(TIMSK3) - case 3: - // 16 bit timer - TCCR3A = 0; - TCCR3B = 0; - bitWrite(TCCR3B, WGM32, 1); - bitWrite(TCCR3B, CS30, 1); - - PWM_LOGWARN(F("T3")); - - break; - #endif - - #if defined(TCCR4A) && defined(TCCR4B) && defined(TIMSK4) - case 4: - // 16 bit timer - TCCR4A = 0; - TCCR4B = 0; - #if defined(WGM42) - bitWrite(TCCR4B, WGM42, 1); - #elif defined(CS43) - // TODO this may not be correct - // atmega32u4 - bitWrite(TCCR4B, CS43, 1); - #endif - bitWrite(TCCR4B, CS40, 1); - - PWM_LOGWARN(F("T4")); - - break; - #endif - - #if defined(TCCR5A) && defined(TCCR5B) && defined(TIMSK5) - case 5: - // 16 bit timer - TCCR5A = 0; - TCCR5B = 0; - bitWrite(TCCR5B, WGM52, 1); - bitWrite(TCCR5B, CS50, 1); - - PWM_LOGWARN(F("T5")); - - break; - #endif - } - - _timer = timer; - - //sei();//enable interrupts - interrupts(); +#elif ( defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || \ + defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_MINI) || defined(ARDUINO_AVR_ETHERNET) || \ + defined(ARDUINO_AVR_FIO) || defined(ARDUINO_AVR_BT) || defined(ARDUINO_AVR_LILYPAD) || defined(ARDUINO_AVR_PRO) || \ + defined(ARDUINO_AVR_NG) || defined(ARDUINO_AVR_UNO_WIFI_DEV_ED) || defined(ARDUINO_AVR_DUEMILANOVE) ) + #define BOARD_NAME "Arduino AVR UNO, Nano, etc." + #warning Using Aduino AVR ATMega644(P), ATMega328(P) such as UNO, Nano. Only Timer1,2 available + +#elif ( defined(ARDUINO_AVR_FEATHER328P) || defined(ARDUINO_AVR_METRO) || defined(ARDUINO_AVR_PROTRINKET5) || defined(ARDUINO_AVR_PROTRINKET3) || \ + defined(ARDUINO_AVR_PROTRINKET5FTDI) || defined(ARDUINO_AVR_PROTRINKET3FTDI) ) + #define BOARD_NAME F("Adafruit AVR ATMega328(P)") + #warning Using Adafruit ATMega328(P), such as AVR_FEATHER328P or AVR_METRO. Only Timer1,2 available + +#elif ( defined(ARDUINO_AVR_LEONARDO) || defined(ARDUINO_AVR_LEONARDO_ETH) || defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_MICRO) || \ + defined(ARDUINO_AVR_ESPLORA) || defined(ARDUINO_AVR_LILYPAD_USB) || defined(ARDUINO_AVR_ROBOT_CONTROL) || defined(ARDUINO_AVR_ROBOT_MOTOR) || \ + defined(ARDUINO_AVR_CIRCUITPLAY) || defined(ARDUINO_AVR_YUNMINI) || defined(ARDUINO_AVR_INDUSTRIAL101) || defined(ARDUINO_AVR_LININO_ONE) ) + #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) + #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 + #endif + #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true + #define BOARD_NAME F("Arduino AVR ATMega32U4") + #warning Using Arduino ATMega32U4, such as Leonardo or Leonardo ETH. Only Timer1,3,4 available -} - -void TimerInterrupt::set_OCR() -{ - // Run with noInterrupt() - // Set the OCR for the given timer, - // set the toggle count, - // then turn on the interrupts - uint32_t _OCRValueToUse; - - switch (_timer) - { - case 1: - _OCRValueToUse = min(MAX_COUNT_16BIT, _OCRValueRemaining); - OCR1A = _OCRValueToUse; - _OCRValueRemaining -= _OCRValueToUse; - -#if defined(OCR1A) && defined(TIMSK1) && defined(OCIE1A) - // Bit 1 – OCIEA: Output Compare A Match Interrupt Enable - // When this bit is written to '1', and the I-flag in the Status Register is set (interrupts globally enabled), the - // Timer/Counter Output Compare A Match interrupt is enabled. The corresponding Interrupt Vector is - // executed when the OCFA Flag, located in TIFR1, is set. - bitWrite(TIMSK1, OCIE1A, 1); -#elif defined(OCR1A) && defined(TIMSK) && defined(OCIE1A) - // this combination is for at least the ATmega32 - bitWrite(TIMSK, OCIE1A, 1); -#endif - break; +#elif ( defined(ARDUINO_AVR_FLORA8 ) || defined(ARDUINO_AVR_FEATHER32U4) || defined(ARDUINO_AVR_CIRCUITPLAY) || defined(ARDUINO_AVR_ITSYBITSY32U4_5V) || \ + defined(ARDUINO_AVR_ITSYBITSY32U4_3V) || defined(ARDUINO_AVR_BLUEFRUITMICRO) || defined(ARDUINO_AVR_ADAFRUIT32U4) ) + #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) + #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 + #endif + #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true + #define BOARD_NAME F("Adafruit AVR ATMega32U4") + #warning Using Adafruit ATMega32U4, such as Feather_32u4, AVR_CIRCUITPLAY, etc.. Only Timer1,3,4 available + +#elif ( defined(__AVR_ATmega32U4__) || defined(ARDUINO_AVR_MAKEYMAKEY ) || defined(ARDUINO_AVR_PROMICRO) || defined(ARDUINO_AVR_FIOV3) || \ + defined(ARDUINO_AVR_QDUINOMINI) || defined(ARDUINO_AVR_LILYPAD_ARDUINO_USB_PLUS_BOARD ) ) + #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) + #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 + #endif + #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true + #define BOARD_NAME F("Generic or Sparkfun AVR ATMega32U4") + #warning Using Generic ATMega32U4, such as Sparkfun AVR_MAKEYMAKEY, AVR_PROMICRO, etc. Only Timer1,3,4 available -#if defined(OCR2A) && defined(TIMSK2) && defined(OCIE2A) - case 2: - _OCRValueToUse = min(MAX_COUNT_8BIT, _OCRValueRemaining); - OCR2A = _OCRValueToUse; - _OCRValueRemaining -= _OCRValueToUse; +#elif ( defined(__AVR_ATmega328P__) || defined(ARDUINO_AVR_DIGITAL_SANDBOX ) || defined(ARDUINO_REDBOT) || defined(ARDUINO_AVR_SERIAL_7_SEGMENT) ) + #define BOARD_NAME F("Generic or Sparkfun AVR ATMega328P") + #warning Using Generic ATMega328P, such as Sparkfun AVR_DIGITAL_SANDBOX, REDBOT, etc. - bitWrite(TIMSK2, OCIE2A, 1); - break; +#elif ( defined(__AVR_ATmega128RFA1__) || defined(ARDUINO_ATMEGA128RFA1_DEV_BOARD) ) + #define BOARD_NAME F("Generic or Sparkfun AVR ATMega128RFA1") + #warning Using Generic ATMega128RFA1, such as Sparkfun ATMEGA128RFA1_DEV_BOARD, etc. + +#elif ( defined(ARDUINO_AVR_GEMMA) || defined(ARDUINO_AVR_TRINKET3) || defined(ARDUINO_AVR_TRINKET5) ) + #error These AVR boards are not supported! Please check your Tools->Board setting. + +#else + #error This is designed only for Arduino or Adafruit AVR board! Please check your Tools->Board setting. #endif -#if defined(OCR3A) && defined(TIMSK3) && defined(OCIE3A) - case 3: - _OCRValueToUse = min(MAX_COUNT_16BIT, _OCRValueRemaining); - OCR3A = _OCRValueToUse; - _OCRValueRemaining -= _OCRValueToUse; +#ifndef AVR_SLOW_PWM_VERSION + #define AVR_SLOW_PWM_VERSION F("AVR_Slow_PWM v1.2.0") + + #define AVR_SLOW_PWM_VERSION_MAJOR 1 + #define AVR_SLOW_PWM_VERSION_MINOR 2 + #define AVR_SLOW_PWM_VERSION_PATCH 0 - bitWrite(TIMSK3, OCIE3A, 1); - break; + #define AVR_SLOW_PWM_VERSION_INT 1002000 #endif -#if defined(OCR4A) && defined(TIMSK4) && defined(OCIE4A) - case 4: - -#if TIMER_INTERRUPT_USING_ATMEGA_32U4 - _OCRValueToUse = min(MAX_COUNT_8BIT, _OCRValueRemaining); -#else - _OCRValueToUse = min(MAX_COUNT_16BIT, _OCRValueRemaining); +#ifndef _PWM_LOGLEVEL_ + #define _PWM_LOGLEVEL_ 1 #endif - - OCR4A = _OCRValueToUse; - _OCRValueRemaining -= _OCRValueToUse; - bitWrite(TIMSK4, OCIE4A, 1); - break; +#include +#include +#include "Arduino.h" +#include "pins_arduino.h" + +#include "PWM_Generic_Debug.h" + +#define MAX_COUNT_8BIT 255 +#define MAX_COUNT_10BIT 1023 +#define MAX_COUNT_16BIT 65535 + +#if defined(__AVR_ATmega8__) || defined(__AVR_ATmega128__) + #define TCCR2A TCCR2 + #define TCCR2B TCCR2 + #define COM2A1 COM21 + #define COM2A0 COM20 + #define OCR2A OCR2 + #define TIMSK2 TIMSK + #define OCIE2A OCIE2 + #define TIMER2_COMPA_vect TIMER2_COMP_vect + #define TIMSK1 TIMSK #endif -#if defined(OCR5A) && defined(TIMSK5) && defined(OCIE5A) - case 5: - _OCRValueToUse = min(MAX_COUNT_16BIT, _OCRValueRemaining); - OCR5A = _OCRValueToUse; - _OCRValueRemaining -= _OCRValueToUse; +typedef void (*timer_callback)(); +typedef void (*timer_callback_p)(void *); - bitWrite(TIMSK5, OCIE5A, 1); - break; -#endif - } +enum +{ + HW_TIMER_0 = 0, + HW_TIMER_1, + HW_TIMER_2, + HW_TIMER_3, + HW_TIMER_4, + HW_TIMER_5, + NUM_HW_TIMERS +}; + +enum +{ + NO_CLOCK_SOURCE = 0, + NO_PRESCALER, + PRESCALER_8, + PRESCALER_64, + PRESCALER_256, + PRESCALER_1024, + NUM_ITEMS +}; + +enum +{ + T2_NO_CLOCK_SOURCE = 0, + T2_NO_PRESCALER, + T2_PRESCALER_8, + T2_PRESCALER_32, + T2_PRESCALER_64, + T2_PRESCALER_128, + T2_PRESCALER_256, + T2_PRESCALER_1024, + T2_NUM_ITEMS +}; + +const unsigned int prescalerDiv [NUM_ITEMS] = { 1, 1, 8, 64, 256, 1024 }; +const unsigned int prescalerDivT2 [T2_NUM_ITEMS] = { 1, 1, 8, 32, 64, 128, 256, 1024 }; + +class TimerInterrupt +{ + private: - // Flag _OCRValue == 0 => end of long timer - if (_OCRValueRemaining == 0) - _timerDone = true; + bool _timerDone; + int8_t _timer; + unsigned int _prescalerIndex; + uint32_t _OCRValue; + uint32_t _OCRValueRemaining; + volatile long _toggle_count; + double _frequency; -} + void* _callback; // pointer to the callback function + void* _params; // function parameter -// frequency (in hertz) and duration (in milliseconds). -// Return true if frequency is OK with selected timer (OCRValue is in range) -bool TimerInterrupt::setFrequency(float frequency, timer_callback_p callback, uint32_t params, unsigned long duration) -{ - uint8_t andMask = 0b11111000; - unsigned long OCRValue; - bool isSuccess = false; - - //frequencyLimit must > 1 - float frequencyLimit = frequency * 17179.840; - - // Limit frequency to larger than (0.00372529 / 64) Hz or interval 17179.840s / 17179840 ms to avoid uint32_t overflow - if ((_timer <= 0) || (callback == NULL) || ((frequencyLimit) < 1) ) - { - return false; - } - else - { - // Calculate the toggle count. Duration must be at least longer then one cycle - if (duration > 0) - { - _toggle_count = frequency * duration / 1000; - - PWM_LOGWARN1(F("setFrequency => _toggle_count ="), _toggle_count); - PWM_LOGWARN3(F("Frequency ="), frequency, F(", duration ="), duration); - - if (_toggle_count < 1) + void set_OCR(); + + public: + + TimerInterrupt() + { + _timer = -1; + _frequency = 0; + _callback = NULL; + _params = NULL; + _timerDone = false; + _prescalerIndex = NO_PRESCALER; + _OCRValue = 0; + _OCRValueRemaining = 0; + _toggle_count = -1; + }; + + explicit TimerInterrupt(uint8_t timerNo) + { + _timer = timerNo; + _frequency = 0; + _callback = NULL; + _params = NULL; + _timerDone = false; + _prescalerIndex = NO_PRESCALER; + _OCRValue = 0; + _OCRValueRemaining = 0; + _toggle_count = -1; + }; + + void callback() __attribute__((always_inline)) + { + if (_callback != NULL) { - return false; + if (_params != NULL) + (*(timer_callback_p)_callback)(_params); + else + (*(timer_callback)_callback)(); } } - else + + void init(int8_t timer); + + void init() { - _toggle_count = -1; + init(_timer); + }; + + // frequency (in hertz) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely + bool setFrequency(const float& frequency, timer_callback_p callback, /* void* */ const uint32_t& params, const unsigned long& duration = 0); + + // frequency (in hertz) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely + bool setFrequency(const float& frequency, timer_callback callback, const unsigned long& duration = 0) + { + return setFrequency(frequency, reinterpret_cast(callback), /*NULL*/ 0, duration); } - - //Timer0 and timer2 are 8 bit timers, meaning they can store a maximum counter value of 255. - //Timer2 does not have the option of 1024 prescaler, only 1, 8, 32, 64 - //Timer1 is a 16 bit timer, meaning it can store a maximum counter value of 65535. - int prescalerIndexStart; - - //Use smallest prescaler first, then increase until fits (<255) - if (_timer != 2) - { - if (frequencyLimit > 64) - prescalerIndexStart = NO_PRESCALER; - else if (frequencyLimit > 8) - prescalerIndexStart = PRESCALER_8; - else - prescalerIndexStart = PRESCALER_64; - - for (int prescalerIndex = prescalerIndexStart; prescalerIndex <= PRESCALER_1024; prescalerIndex++) - { - OCRValue = F_CPU / (frequency * prescalerDiv[prescalerIndex]) - 1; - - PWM_LOGWARN1(F("Freq * 1000 ="), frequency * 1000); - PWM_LOGWARN3(F("F_CPU ="), F_CPU, F(", preScalerDiv ="), prescalerDiv[prescalerIndex]); - PWM_LOGWARN3(F("OCR ="), OCRValue, F(", preScalerIndex ="), prescalerIndex); + // interval (in ms) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely + template + bool setInterval(const unsigned long& interval, void (*callback)(TArg), TArg params, const unsigned long& duration = 0) + { + static_assert(sizeof(TArg) <= sizeof(uint32_t), "setInterval() callback argument size must be <= 4 bytes"); + return setFrequency((float) (1000.0f / interval), reinterpret_cast(callback), (uint32_t) params, duration); + } - // We use very large _OCRValue now, and every time timer ISR activates, we deduct min(MAX_COUNT_16BIT, _OCRValueRemaining) from _OCRValueRemaining - // So that we can create very long timer, even if the counter is only 16-bit. - // Use very high frequency (OCRValue / MAX_COUNT_16BIT) around 16 * 1024 to achieve higher accuracy -#if TIMER_INTERRUPT_USING_ATMEGA_32U4 - uint16_t MAX_COUNT_32U4 = (_timer == 4) ? MAX_COUNT_8BIT : MAX_COUNT_16BIT; - - if ( (OCRValue / MAX_COUNT_32U4) < 16384 ) -#else - if ( (OCRValue / MAX_COUNT_16BIT) < 16384 ) -#endif - { - _OCRValue = OCRValue; - _OCRValueRemaining = OCRValue; - _prescalerIndex = prescalerIndex; - - PWM_LOGWARN1(F("OK in loop => _OCR ="), _OCRValue); - PWM_LOGWARN3(F("_preScalerIndex ="), _prescalerIndex, F(", preScalerDiv ="), prescalerDiv[_prescalerIndex]); - - isSuccess = true; - - break; - } - } + // interval (in ms) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely + bool setInterval(const unsigned long& interval, timer_callback callback, const unsigned long& duration = 0) + { + return setFrequency((float) (1000.0f / interval), reinterpret_cast(callback), /*NULL*/ 0, duration); + } - if (!isSuccess) - { - // Always do this - _OCRValue = OCRValue; - _OCRValueRemaining = OCRValue; - _prescalerIndex = PRESCALER_1024; - - PWM_LOGWARN1(F("OK out loop => _OCR ="), _OCRValue); - PWM_LOGWARN3(F("_preScalerIndex ="), _prescalerIndex, F(", preScalerDiv ="), prescalerDiv[_prescalerIndex]); - } + template + bool attachInterrupt(const float& frequency, void (*callback)(TArg), TArg params, const unsigned long& duration = 0) + { + static_assert(sizeof(TArg) <= sizeof(uint32_t), "attachInterrupt() callback argument size must be <= 4 bytes"); + return setFrequency(frequency, reinterpret_cast(callback), (uint32_t) params, duration); } - else + + bool attachInterrupt(const float& frequency, timer_callback callback, const unsigned long& duration = 0) { - if (frequencyLimit > 64) - prescalerIndexStart = T2_NO_PRESCALER; - else if (frequencyLimit > 8) - prescalerIndexStart = T2_PRESCALER_8; - else if (frequencyLimit > 2) - prescalerIndexStart = T2_PRESCALER_32; - else - prescalerIndexStart = T2_PRESCALER_64; - - // Page 206-207. ATmegal328 - //8-bit Timer2 has more options up to 1024 prescaler, from 1, 8, 32, 64, 128, 256 and 1024 - for (int prescalerIndex = prescalerIndexStart; prescalerIndex <= T2_PRESCALER_1024; prescalerIndex++) - { - OCRValue = F_CPU / (frequency * prescalerDivT2[prescalerIndex]) - 1; - - PWM_LOGWARN3(F("F_CPU ="), F_CPU, F(", preScalerDiv ="), prescalerDivT2[prescalerIndex]); - PWM_LOGWARN3(F("OCR2 ="), OCRValue, F(", preScalerIndex ="), prescalerIndex); + return setFrequency(frequency, reinterpret_cast(callback), /*NULL*/ 0, duration); + } - // We use very large _OCRValue now, and every time timer ISR activates, we deduct min(MAX_COUNT_8BIT, _OCRValue) from _OCRValue - // to create very long timer, even if the counter is only 16-bit. - // Use very high frequency (OCRValue / MAX_COUNT_8BIT) around 16 * 1024 to achieve higher accuracy - if ( (OCRValue / MAX_COUNT_8BIT) < 16384 ) - { - _OCRValue = OCRValue; - _OCRValueRemaining = OCRValue; - // same as prescalarbits - _prescalerIndex = prescalerIndex; - - PWM_LOGWARN1(F("OK in loop => _OCR ="), _OCRValue); - PWM_LOGWARN3(F("_preScalerIndex ="), _prescalerIndex, F(", preScalerDiv ="), prescalerDivT2[_prescalerIndex]); - - isSuccess = true; - - break; - } - } + // Interval (in ms) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely + template + bool attachInterruptInterval(const float& interval, void (*callback)(TArg), TArg params, const unsigned long& duration = 0) + { + static_assert(sizeof(TArg) <= sizeof(uint32_t), "attachInterruptInterval() callback argument size must be <= 4 bytes"); + return setFrequency( (float) ( 1000.0f / interval), reinterpret_cast(callback), (uint32_t) params, duration); + } - if (!isSuccess) - { - // Always do this - _OCRValue = OCRValue; - _OCRValueRemaining = OCRValue; - // same as prescalarbits - _prescalerIndex = T2_PRESCALER_1024; - - PWM_LOGWARN1(F("OK out loop => _OCR ="), _OCRValue); - PWM_LOGWARN3(F("_preScalerIndex ="), _prescalerIndex, F(", preScalerDiv ="), prescalerDivT2[_prescalerIndex]); - } + // Interval (in ms) and duration (in milliseconds). Duration = 0 or not specified => run indefinitely + bool attachInterruptInterval(const float& interval, timer_callback callback, const unsigned long& duration = 0) + { + return setFrequency( (float) ( 1000.0f / interval), reinterpret_cast (callback), /*NULL*/ 0, duration); } - //cli();//stop interrupts - noInterrupts(); + void detachInterrupt(); + + void disableTimer() + { + detachInterrupt(); + } - _frequency = frequency; - _callback = (void*) callback; - _params = reinterpret_cast(params); + // Duration (in milliseconds). Duration = 0 or not specified => run indefinitely + void reattachInterrupt(const unsigned long& duration = 0); - _timerDone = false; - - // 8 bit timers from here - #if defined(TCCR2B) - if (_timer == 2) + // Duration (in milliseconds). Duration = 0 or not specified => run indefinitely + void enableTimer(const unsigned long& duration = 0) __attribute__((always_inline)) { - TCCR2B = (TCCR2B & andMask) | _prescalerIndex; //prescalarbits; - - PWM_LOGWARN1(F("TCCR2B ="), TCCR2B); + reattachInterrupt(duration); } - #endif - - // 16 bit timers from here - #if defined(TCCR1B) - #if ( TIMER_INTERRUPT_USING_ATMEGA_32U4 ) - if (_timer == 1) - #else - else if (_timer == 1) - #endif + + // Just stop clock source, still keep the count + void pauseTimer(); + + // Just reconnect clock source, continue from the current count + void resumeTimer(); + + // Just stop clock source, clear the count + void stopTimer() { - TCCR1B = (TCCR1B & andMask) | _prescalerIndex; //prescalarbits; - - PWM_LOGWARN1(F("TCCR1B ="), TCCR1B); + detachInterrupt(); } - #endif - - #if defined(TCCR3B) - else if (_timer == 3) - TCCR3B = (TCCR3B & andMask) | _prescalerIndex; //prescalarbits; - #endif - - #if defined(TCCR4B) - else if (_timer == 4) - TCCR4B = (TCCR4B & andMask) | _prescalerIndex; //prescalarbits; - #endif - - #if defined(TCCR5B) - else if (_timer == 5) - TCCR5B = (TCCR5B & andMask) | _prescalerIndex; //prescalarbits; - #endif - - // Set the OCR for the given timer, - // set the toggle count, - // then turn on the interrupts - set_OCR(); - - //sei();//allow interrupts - interrupts(); - - return true; - } -} - -void TimerInterrupt::detachInterrupt(void) -{ - //cli();//stop interrupts - noInterrupts(); - - switch (_timer) - { - #if defined(TIMSK1) && defined(OCIE1A) - case 1: - bitWrite(TIMSK1, OCIE1A, 0); - - PWM_LOGWARN(F("Disable T1")); - - break; - #endif - - case 2: - #if defined(TIMSK2) && defined(OCIE2A) - bitWrite(TIMSK2, OCIE2A, 0); // disable interrupt - #endif - - PWM_LOGWARN(F("Disable T2")); - - break; - -#if defined(TIMSK3) && defined(OCIE3A) - case 3: - bitWrite(TIMSK3, OCIE3A, 0); - - PWM_LOGWARN(F("Disable T3")); - - break; -#endif -#if defined(TIMSK4) && defined(OCIE4A) - case 4: - bitWrite(TIMSK4, OCIE4A, 0); + // Just reconnect clock source, start current count from 0 + void restartTimer(const unsigned long& duration = 0) + { + reattachInterrupt(duration); + } - PWM_LOGWARN(F("Disable T4")); - - break; -#endif + int8_t getTimer() __attribute__((always_inline)) + { + return _timer; + }; -#if defined(TIMSK5) && defined(OCIE5A) - case 5: - bitWrite(TIMSK5, OCIE5A, 0); + long getCount() __attribute__((always_inline)) + { + return _toggle_count; + }; - PWM_LOGWARN(F("Disable T5")); - - break; -#endif - } - - //sei();//allow interrupts - interrupts(); -} + void setCount(const long& countInput) __attribute__((always_inline)) + { + //cli();//stop interrupts + //noInterrupts(); -// Duration (in milliseconds). Duration = 0 or not specified => run indefinitely -void TimerInterrupt::reattachInterrupt(unsigned long duration) -{ - //cli();//stop interrupts - noInterrupts(); - - // Calculate the toggle count - if (duration > 0) - { - _toggle_count = _frequency * duration / 1000; - } - else - { - _toggle_count = -1; - } - - switch (_timer) - { -#if defined(TIMSK1) && defined(OCIE1A) - case 1: - bitWrite(TIMSK1, OCIE1A, 1); - - PWM_LOGWARN(F("Enable T1")); - - break; -#endif + _toggle_count = countInput; - case 2: - #if defined(TIMSK2) && defined(OCIE2A) - bitWrite(TIMSK2, OCIE2A, 1); // enable interrupt - #endif - - PWM_LOGWARN(F("Enable T2")); - - break; - -#if defined(TIMSK3) && defined(OCIE3A) - case 3: - bitWrite(TIMSK3, OCIE3A, 1); - - PWM_LOGWARN(F("Enable T3")); - - break; -#endif + //sei();//enable interrupts + //interrupts(); + }; -#if defined(TIMSK4) && defined(OCIE4A) - case 4: - bitWrite(TIMSK4, OCIE4A, 1); + long get_OCRValue() __attribute__((always_inline)) + { + return _OCRValue; + }; + + long get_OCRValueRemaining() __attribute__((always_inline)) + { + return _OCRValueRemaining; + }; + + void adjust_OCRValue() //__attribute__((always_inline)) + { + //cli();//stop interrupts + noInterrupts(); - PWM_LOGWARN(F("Enable T4")); - - break; + if (_timer != 2) + { +#if TIMER_INTERRUPT_USING_ATMEGA_32U4 + if (_timer == 4) + { + _OCRValueRemaining -= min(MAX_COUNT_8BIT, _OCRValueRemaining); + } + else + { + _OCRValueRemaining -= min(MAX_COUNT_16BIT, _OCRValueRemaining); + } +#else + _OCRValueRemaining -= min(MAX_COUNT_16BIT, _OCRValueRemaining); +#endif + } + else + _OCRValueRemaining -= min(MAX_COUNT_8BIT, _OCRValueRemaining); + + if (_OCRValueRemaining == 0) + { + // Reset value for next cycle + _OCRValueRemaining = _OCRValue; + _timerDone = true; + } + else + _timerDone = false; + + //sei();//enable interrupts + interrupts(); + }; + + void reload_OCRValue() //__attribute__((always_inline)) + { + //cli();//stop interrupts + noInterrupts(); + + // Reset value for next cycle, have to deduct the value already loaded to OCR register + + if (_timer != 2) + { +#if TIMER_INTERRUPT_USING_ATMEGA_32U4 + if (_timer == 4) + { + _OCRValueRemaining = _OCRValue - min(MAX_COUNT_8BIT, _OCRValueRemaining); + } + else + { + _OCRValueRemaining = _OCRValue - min(MAX_COUNT_16BIT, _OCRValueRemaining); + } +#else + _OCRValueRemaining = _OCRValue - min(MAX_COUNT_16BIT, _OCRValueRemaining); +#endif + } + else + _OCRValueRemaining = _OCRValue - min(MAX_COUNT_8BIT, _OCRValueRemaining); + + _timerDone = false; + + //sei();//enable interrupts + interrupts(); + }; + + bool checkTimerDone() //__attribute__((always_inline)) + { + return _timerDone; + }; + +}; // class TimerInterrupt + +////////////////////////////////////////////// + +// To be sure not used Timers are disabled +#if !defined(USE_TIMER_1) + #define USE_TIMER_1 false #endif -#if defined(TIMSK5) && defined(OCIE5A) - case 5: - bitWrite(TIMSK5, OCIE5A, 1); +#if !defined(USE_TIMER_2) + #define USE_TIMER_2 false +#elif ( USE_TIMER_2 && TIMER_INTERRUPT_USING_ATMEGA_32U4 ) + #error Timer2 is disabled for ATMEGA_32U4, only available for ATMEGA_328(P) and Mega +#endif - PWM_LOGWARN(F("Enable T5")); - - break; +#if !defined(USE_TIMER_3) + #define USE_TIMER_3 false +#elif ( USE_TIMER_3 && ( TIMER_INTERRUPT_USING_ATMEGA_32U4 || TIMER_INTERRUPT_USING_ATMEGA2560 ) ) + #warning Timer3 (16-bit) is OK to use for ATMEGA_32U4 and Mega +#elif USE_TIMER_3 + #error Timer3 is only available for ATMEGA_32U4 and Mega #endif - } - - //sei();//allow interrupts - interrupts(); -} -// Just stop clock source, still keep the count -void TimerInterrupt::pauseTimer(void) -{ - uint8_t andMask = 0b11111000; - - //Just clear the CSx2-CSx0. Still keep the count in TCNT and Timer Interrupt mask TIMKSx. - - // 8 bit timers from here - #if defined(TCCR2B) - if (_timer == 2) - { - TCCR2B = (TCCR2B & andMask); - - PWM_LOGWARN1(F("TCCR2B ="), TCCR2B); - } - #endif - - // 16 bit timers from here - #if defined(TCCR1B) - #if ( TIMER_INTERRUPT_USING_ATMEGA_32U4 ) - if (_timer == 1) - #else - else if (_timer == 1) - #endif - { - TCCR1B = (TCCR1B & andMask); - - PWM_LOGWARN1(F("TCCR1B ="), TCCR1B); - } - #endif - - #if defined(TCCR3B) - else if (_timer == 3) - TCCR3B = (TCCR3B & andMask); - #endif - - #if defined(TCCR4B) - else if (_timer == 4) - TCCR4B = (TCCR4B & andMask); - #endif - - #if defined(TCCR5B) - else if (_timer == 5) - TCCR5B = (TCCR5B & andMask); - #endif -} - -// Just reconnect clock source, continue from the current count -void TimerInterrupt::resumeTimer(void) -{ - uint8_t andMask = 0b11111000; - - //Just restore the CSx2-CSx0 stored in _prescalerIndex. Still keep the count in TCNT and Timer Interrupt mask TIMKSx. - // 8 bit timers from here - #if defined(TCCR2B) - if (_timer == 2) - { - TCCR2B = (TCCR2B & andMask) | _prescalerIndex; //prescalarbits; - - PWM_LOGWARN1(F("TCCR2B ="), TCCR2B); - } - #endif +#if !defined(USE_TIMER_4) + #define USE_TIMER_4 false +#elif ( USE_TIMER_4 && ( TIMER_INTERRUPT_USING_ATMEGA_32U4 || TIMER_INTERRUPT_USING_ATMEGA2560 ) ) + #warning Timer4 is OK to use for ATMEGA_32U4 (10-bit but using as 8-bit) and Mega (16-bit) +#elif USE_TIMER_4 + #error Timer4 is only available for ATMEGA_32U4 and Mega +#endif - // 16 bit timers from here - #if defined(TCCR1B) - #if ( TIMER_INTERRUPT_USING_ATMEGA_32U4 ) - if (_timer == 1) - #else - else if (_timer == 1) - #endif - { - TCCR1B = (TCCR1B & andMask) | _prescalerIndex; //prescalarbits; - - PWM_LOGWARN1(F("TCCR1B ="), TCCR1B); - } - #endif - - #if defined(TCCR3B) - else if (_timer == 3) - TCCR3B = (TCCR3B & andMask) | _prescalerIndex; //prescalarbits; - #endif - - #if defined(TCCR4B) - else if (_timer == 4) - TCCR4B = (TCCR4B & andMask) | _prescalerIndex; //prescalarbits; - #endif - - #if defined(TCCR5B) - else if (_timer == 5) - TCCR5B = (TCCR5B & andMask) | _prescalerIndex; //prescalarbits; - #endif -} +#if !defined(USE_TIMER_5) + #define USE_TIMER_5 false +#elif ( USE_TIMER_5 && TIMER_INTERRUPT_USING_ATMEGA2560 ) + #warning Timer5 is OK to use for Mega +#elif USE_TIMER_5 + #error Timer5 is only available for Mega +#endif + +#endif // AVR_SLOW_PWM_HPP -#endif // AVR_SLOW_PWM_HPP diff --git a/src/AVR_Slow_PWM_ISR.h b/src/AVR_Slow_PWM_ISR.h index c7e4e29..008e896 100644 --- a/src/AVR_Slow_PWM_ISR.h +++ b/src/AVR_Slow_PWM_ISR.h @@ -31,268 +31,8 @@ #ifndef AVR_SLOW_PWM_ISR_H #define AVR_SLOW_PWM_ISR_H -#if defined(BOARD_NAME) - #undef BOARD_NAME -#endif - -#if ( defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || \ - defined(__AVR_ATmega640__) || defined(__AVR_ATmega641__)) - #if defined(TIMER_INTERRUPT_USING_ATMEGA2560) - #undef TIMER_INTERRUPT_USING_ATMEGA2560 - #endif - #define TIMER_INTERRUPT_USING_ATMEGA2560 true - #define BOARD_NAME F("Arduino AVR Mega2560/ADK") - #warning Using Arduino AVR Mega, Mega640(P), Mega2560/ADK. Timer1-5 available - -#elif ( defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || \ - defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_MINI) || defined(ARDUINO_AVR_ETHERNET) || \ - defined(ARDUINO_AVR_FIO) || defined(ARDUINO_AVR_BT) || defined(ARDUINO_AVR_LILYPAD) || defined(ARDUINO_AVR_PRO) || \ - defined(ARDUINO_AVR_NG) || defined(ARDUINO_AVR_UNO_WIFI_DEV_ED) || defined(ARDUINO_AVR_DUEMILANOVE) ) - #define BOARD_NAME "Arduino AVR UNO, Nano, etc." - #warning Using Aduino AVR ATMega644(P), ATMega328(P) such as UNO, Nano. Only Timer1,2 available - -#elif ( defined(ARDUINO_AVR_FEATHER328P) || defined(ARDUINO_AVR_METRO) || defined(ARDUINO_AVR_PROTRINKET5) || defined(ARDUINO_AVR_PROTRINKET3) || \ - defined(ARDUINO_AVR_PROTRINKET5FTDI) || defined(ARDUINO_AVR_PROTRINKET3FTDI) ) - #define BOARD_NAME F("Adafruit AVR ATMega328(P)") - #warning Using Adafruit ATMega328(P), such as AVR_FEATHER328P or AVR_METRO. Only Timer1,2 available - -#elif ( defined(ARDUINO_AVR_LEONARDO) || defined(ARDUINO_AVR_LEONARDO_ETH) || defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_MICRO) || \ - defined(ARDUINO_AVR_ESPLORA) || defined(ARDUINO_AVR_LILYPAD_USB) || defined(ARDUINO_AVR_ROBOT_CONTROL) || defined(ARDUINO_AVR_ROBOT_MOTOR) || \ - defined(ARDUINO_AVR_CIRCUITPLAY) || defined(ARDUINO_AVR_YUNMINI) || defined(ARDUINO_AVR_INDUSTRIAL101) || defined(ARDUINO_AVR_LININO_ONE) ) - #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) - #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 - #endif - #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true - #define BOARD_NAME F("Arduino AVR ATMega32U4") - #warning Using Arduino ATMega32U4, such as Leonardo or Leonardo ETH. Only Timer1,3,4 available - -#elif ( defined(ARDUINO_AVR_FLORA8 ) || defined(ARDUINO_AVR_FEATHER32U4) || defined(ARDUINO_AVR_CIRCUITPLAY) || defined(ARDUINO_AVR_ITSYBITSY32U4_5V) || \ - defined(ARDUINO_AVR_ITSYBITSY32U4_3V) || defined(ARDUINO_AVR_BLUEFRUITMICRO) || defined(ARDUINO_AVR_ADAFRUIT32U4) ) - #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) - #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 - #endif - #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true - #define BOARD_NAME F("Adafruit AVR ATMega32U4") - #warning Using Adafruit ATMega32U4, such as Feather_32u4, AVR_CIRCUITPLAY, etc.. Only Timer1,3,4 available - -#elif ( defined(__AVR_ATmega32U4__) || defined(ARDUINO_AVR_MAKEYMAKEY ) || defined(ARDUINO_AVR_PROMICRO) || defined(ARDUINO_AVR_FIOV3) || \ - defined(ARDUINO_AVR_QDUINOMINI) || defined(ARDUINO_AVR_LILYPAD_ARDUINO_USB_PLUS_BOARD ) ) - #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) - #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 - #endif - #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true - #define BOARD_NAME F("Generic or Sparkfun AVR ATMega32U4") - #warning Using Generic ATMega32U4, such as Sparkfun AVR_MAKEYMAKEY, AVR_PROMICRO, etc. Only Timer1,3,4 available - -#elif ( defined(__AVR_ATmega328P__) || defined(ARDUINO_AVR_DIGITAL_SANDBOX ) || defined(ARDUINO_REDBOT) || defined(ARDUINO_AVR_SERIAL_7_SEGMENT) ) - #define BOARD_NAME F("Generic or Sparkfun AVR ATMega328P") - #warning Using Generic ATMega328P, such as Sparkfun AVR_DIGITAL_SANDBOX, REDBOT, etc. - -#elif ( defined(__AVR_ATmega128RFA1__) || defined(ARDUINO_ATMEGA128RFA1_DEV_BOARD) ) - #define BOARD_NAME F("Generic or Sparkfun AVR ATMega128RFA1") - #warning Using Generic ATMega128RFA1, such as Sparkfun ATMEGA128RFA1_DEV_BOARD, etc. - -#elif ( defined(ARDUINO_AVR_GEMMA) || defined(ARDUINO_AVR_TRINKET3) || defined(ARDUINO_AVR_TRINKET5) ) - #error These AVR boards are not supported! Please check your Tools->Board setting. - -#else - #error This is designed only for Arduino or Adafruit AVR board! Please check your Tools->Board setting. -#endif - -#ifndef AVR_SLOW_PWM_VERSION - #define AVR_SLOW_PWM_VERSION F("AVR_Slow_PWM v1.1.0") -#endif - -#ifndef _PWM_LOGLEVEL_ - #define _PWM_LOGLEVEL_ 1 -#endif - -#include "PWM_Generic_Debug.h" - -#include - -#include - -#if defined(ARDUINO) - #if ARDUINO >= 100 - #include - #else - #include - #endif -#endif - -#define AVR_Slow_PWM_ISR AVR_Slow_PWM - -typedef void (*timer_callback)(); -typedef void (*timer_callback_p)(void *); - -#if !defined(USING_MICROS_RESOLUTION) - #warning Not USING_MICROS_RESOLUTION, using millis resolution - #define USING_MICROS_RESOLUTION false -#endif - -#define INVALID_AVR_PIN 255 - -////////////////////////////////////////////////////////////////// - -class AVR_Slow_PWM_ISR -{ - - public: - // maximum number of PWM channels -#define MAX_NUMBER_CHANNELS 16 - - // constructor - AVR_Slow_PWM_ISR(); - - void init(); - - // this function must be called inside loop() - void run(); - - ////////////////////////////////////////////////////////////////// - // PWM - // Return the channelNum if OK, -1 if error - int setPWM(uint32_t pin, double frequency, uint32_t dutycycle, timer_callback StartCallback = nullptr, - timer_callback StopCallback = nullptr) - { - uint32_t period = 0; - - if ( ( frequency != 0 ) && ( frequency <= 1000 ) ) - { -#if USING_MICROS_RESOLUTION - // period in us - period = 1000000.0f / frequency; -#else - // period in ms - period = 1000.0f / frequency; -#endif - } - else - { - PWM_LOGERROR("Error: Invalid frequency, max is 1000Hz"); - - return -1; - } - - return setupPWMChannel(pin, period, dutycycle, (void *) StartCallback, (void *) StopCallback); - } - - // period in us - // Return the channelNum if OK, -1 if error - int setPWM_Period(uint32_t pin, uint32_t period, uint32_t dutycycle, timer_callback StartCallback = nullptr, - timer_callback StopCallback = nullptr) - { - return setupPWMChannel(pin, period, dutycycle, (void *) StartCallback, (void *) StopCallback); - } - - ////////////////////////////////////////////////////////////////// - - // low level function to modify a PWM channel - // returns the true on success or false on failure - bool modifyPWMChannel(unsigned channelNum, uint32_t pin, double frequency, uint32_t dutycycle) - { - uint32_t period = 0; - - if ( ( frequency > 0 ) && ( frequency <= 1000 ) ) - { -#if USING_MICROS_RESOLUTION - // period in us - period = 1000000.0f / frequency; -#else - // period in ms - period = 1000.0f / frequency; -#endif - } - else - { - PWM_LOGERROR("Error: Invalid frequency, max is 1000Hz"); - return false; - } - - return modifyPWMChannel_Period(channelNum, pin, period, dutycycle); - } - - ////////////////////////////////////////////////////////////////// - - //period in us - bool modifyPWMChannel_Period(unsigned channelNum, uint32_t pin, uint32_t period, uint32_t dutycycle); - - ////////////////////////////////////////////////////////////////// - - // destroy the specified PWM channel - void deleteChannel(unsigned channelNum); - - // restart the specified PWM channel - void restartChannel(unsigned channelNum); - - // returns true if the specified PWM channel is enabled - bool isEnabled(unsigned channelNum); - - // enables the specified PWM channel - void enable(unsigned channelNum); - - // disables the specified PWM channel - void disable(unsigned channelNum); - - // enables all PWM channels - void enableAll(); - - // disables all PWM channels - void disableAll(); - - // enables the specified PWM channel if it's currently disabled, and vice-versa - void toggle(unsigned channelNum); - - // returns the number of used PWM channels - unsigned getnumChannels(); - - // returns the number of available PWM channels - unsigned getNumAvailablePWMChannels() - { - return MAX_NUMBER_CHANNELS - numChannels; - }; - - private: - - // low level function to initialize and enable a new PWM channel - // returns the PWM channel number (channelNum) on success or - // -1 on failure (f == NULL) or no free PWM channels - int setupPWMChannel(uint32_t pin, uint32_t period, uint32_t dutycycle, void* cbStartFunc = nullptr, void* cbStopFunc = nullptr); - - // find the first available slot - int findFirstFreeSlot(); - - typedef struct - { - /////////////////////////////////// - - - /////////////////////////////////// - - uint32_t prevTime; // value returned by the micros() or millis() function in the previous run() call - uint32_t period; // period value, in us / ms - uint32_t onTime; // onTime value, ( period * dutyCycle / 100 ) us / ms - - void* callbackStart; // pointer to the callback function when PWM pulse starts (HIGH) - void* callbackStop; // pointer to the callback function when PWM pulse stops (LOW) - - //////////////////////////////////////////////////////////// - - uint32_t pin; // PWM pin - bool pinHigh; // true if PWM pin is HIGH - //////////////////////////////////////////////////////////// - - bool enabled; // true if enabled - } PWM_t; - - volatile PWM_t PWM[MAX_NUMBER_CHANNELS]; - - // actual number of PWM channels in use (-1 means uninitialized) - volatile int numChannels; -}; - #include "AVR_Slow_PWM_ISR.hpp" +#include "AVR_Slow_PWM_ISR_Impl.h" #endif // AVR_SLOW_PWM_ISR_H diff --git a/src/AVR_Slow_PWM_ISR.hpp b/src/AVR_Slow_PWM_ISR.hpp index 8ca3457..a66f364 100644 --- a/src/AVR_Slow_PWM_ISR.hpp +++ b/src/AVR_Slow_PWM_ISR.hpp @@ -31,332 +31,273 @@ #ifndef AVR_SLOW_PWM_ISR_HPP #define AVR_SLOW_PWM_ISR_HPP -#include - -/////////////////////////////////////////////////// - - -uint32_t timeNow() -{ -#if USING_MICROS_RESOLUTION - return ( (uint32_t) micros() ); +#if defined(BOARD_NAME) + #undef BOARD_NAME +#endif + +#if ( defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega1281__) || \ + defined(__AVR_ATmega640__) || defined(__AVR_ATmega641__)) + #if defined(TIMER_INTERRUPT_USING_ATMEGA2560) + #undef TIMER_INTERRUPT_USING_ATMEGA2560 + #endif + #define TIMER_INTERRUPT_USING_ATMEGA2560 true + #define BOARD_NAME F("Arduino AVR Mega2560/ADK") + #warning Using Arduino AVR Mega, Mega640(P), Mega2560/ADK. Timer1-5 available + +#elif ( defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || \ + defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_MINI) || defined(ARDUINO_AVR_ETHERNET) || \ + defined(ARDUINO_AVR_FIO) || defined(ARDUINO_AVR_BT) || defined(ARDUINO_AVR_LILYPAD) || defined(ARDUINO_AVR_PRO) || \ + defined(ARDUINO_AVR_NG) || defined(ARDUINO_AVR_UNO_WIFI_DEV_ED) || defined(ARDUINO_AVR_DUEMILANOVE) ) + #define BOARD_NAME "Arduino AVR UNO, Nano, etc." + #warning Using Aduino AVR ATMega644(P), ATMega328(P) such as UNO, Nano. Only Timer1,2 available + +#elif ( defined(ARDUINO_AVR_FEATHER328P) || defined(ARDUINO_AVR_METRO) || defined(ARDUINO_AVR_PROTRINKET5) || defined(ARDUINO_AVR_PROTRINKET3) || \ + defined(ARDUINO_AVR_PROTRINKET5FTDI) || defined(ARDUINO_AVR_PROTRINKET3FTDI) ) + #define BOARD_NAME F("Adafruit AVR ATMega328(P)") + #warning Using Adafruit ATMega328(P), such as AVR_FEATHER328P or AVR_METRO. Only Timer1,2 available + +#elif ( defined(ARDUINO_AVR_LEONARDO) || defined(ARDUINO_AVR_LEONARDO_ETH) || defined(ARDUINO_AVR_YUN) || defined(ARDUINO_AVR_MICRO) || \ + defined(ARDUINO_AVR_ESPLORA) || defined(ARDUINO_AVR_LILYPAD_USB) || defined(ARDUINO_AVR_ROBOT_CONTROL) || defined(ARDUINO_AVR_ROBOT_MOTOR) || \ + defined(ARDUINO_AVR_CIRCUITPLAY) || defined(ARDUINO_AVR_YUNMINI) || defined(ARDUINO_AVR_INDUSTRIAL101) || defined(ARDUINO_AVR_LININO_ONE) ) + #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) + #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 + #endif + #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true + #define BOARD_NAME F("Arduino AVR ATMega32U4") + #warning Using Arduino ATMega32U4, such as Leonardo or Leonardo ETH. Only Timer1,3,4 available + +#elif ( defined(ARDUINO_AVR_FLORA8 ) || defined(ARDUINO_AVR_FEATHER32U4) || defined(ARDUINO_AVR_CIRCUITPLAY) || defined(ARDUINO_AVR_ITSYBITSY32U4_5V) || \ + defined(ARDUINO_AVR_ITSYBITSY32U4_3V) || defined(ARDUINO_AVR_BLUEFRUITMICRO) || defined(ARDUINO_AVR_ADAFRUIT32U4) ) + #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) + #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 + #endif + #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true + #define BOARD_NAME F("Adafruit AVR ATMega32U4") + #warning Using Adafruit ATMega32U4, such as Feather_32u4, AVR_CIRCUITPLAY, etc.. Only Timer1,3,4 available + +#elif ( defined(__AVR_ATmega32U4__) || defined(ARDUINO_AVR_MAKEYMAKEY ) || defined(ARDUINO_AVR_PROMICRO) || defined(ARDUINO_AVR_FIOV3) || \ + defined(ARDUINO_AVR_QDUINOMINI) || defined(ARDUINO_AVR_LILYPAD_ARDUINO_USB_PLUS_BOARD ) ) + #if defined(TIMER_INTERRUPT_USING_ATMEGA_32U4) + #undef TIMER_INTERRUPT_USING_ATMEGA_32U4 + #endif + #define TIMER_INTERRUPT_USING_ATMEGA_32U4 true + #define BOARD_NAME F("Generic or Sparkfun AVR ATMega32U4") + #warning Using Generic ATMega32U4, such as Sparkfun AVR_MAKEYMAKEY, AVR_PROMICRO, etc. Only Timer1,3,4 available + +#elif ( defined(__AVR_ATmega328P__) || defined(ARDUINO_AVR_DIGITAL_SANDBOX ) || defined(ARDUINO_REDBOT) || defined(ARDUINO_AVR_SERIAL_7_SEGMENT) ) + #define BOARD_NAME F("Generic or Sparkfun AVR ATMega328P") + #warning Using Generic ATMega328P, such as Sparkfun AVR_DIGITAL_SANDBOX, REDBOT, etc. + +#elif ( defined(__AVR_ATmega128RFA1__) || defined(ARDUINO_ATMEGA128RFA1_DEV_BOARD) ) + #define BOARD_NAME F("Generic or Sparkfun AVR ATMega128RFA1") + #warning Using Generic ATMega128RFA1, such as Sparkfun ATMEGA128RFA1_DEV_BOARD, etc. + +#elif ( defined(ARDUINO_AVR_GEMMA) || defined(ARDUINO_AVR_TRINKET3) || defined(ARDUINO_AVR_TRINKET5) ) + #error These AVR boards are not supported! Please check your Tools->Board setting. + #else - return ( (uint32_t) millis() ); -#endif -} - -/////////////////////////////////////////////////// - -AVR_Slow_PWM_ISR::AVR_Slow_PWM_ISR() - : numChannels (-1) -{ -} + #error This is designed only for Arduino or Adafruit AVR board! Please check your Tools->Board setting. +#endif -/////////////////////////////////////////////////// - -void AVR_Slow_PWM_ISR::init() -{ - uint32_t currentTime = timeNow(); - - for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) - { - memset((void*) &PWM[channelNum], 0, sizeof (PWM_t)); - PWM[channelNum].prevTime = currentTime; - PWM[channelNum].pin = INVALID_AVR_PIN; - } +#ifndef AVR_SLOW_PWM_VERSION + #define AVR_SLOW_PWM_VERSION F("AVR_Slow_PWM v1.2.0") - numChannels = 0; -} - -/////////////////////////////////////////////////// - -void AVR_Slow_PWM_ISR::run() -{ - //uint32_t currentTime = timeNow(); - uint32_t currentTime; - - for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) - { - currentTime = timeNow(); - - // If enabled => check - // start period / dutyCycle => digitalWrite HIGH - // end dutyCycle => digitalWrite LOW - if (PWM[channelNum].enabled) - { - if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) <= PWM[channelNum].onTime ) - { - if (!PWM[channelNum].pinHigh) - { - digitalWrite(PWM[channelNum].pin, HIGH); - PWM[channelNum].pinHigh = true; - - // callbackStart - if (PWM[channelNum].callbackStart != nullptr) - { - (*(timer_callback) PWM[channelNum].callbackStart)(); - } - } - } - else if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) < PWM[channelNum].period ) - { - if (PWM[channelNum].pinHigh) - { - digitalWrite(PWM[channelNum].pin, LOW); - PWM[channelNum].pinHigh = false; - - // callback when PWM pulse stops (LOW) - if (PWM[channelNum].callbackStop != nullptr) - { - (*(timer_callback) PWM[channelNum].callbackStop)(); - } - } - } - else - //else if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) >= PWM[channelNum].period ) - { - PWM[channelNum].prevTime = currentTime; - } - } - } -} + #define AVR_SLOW_PWM_VERSION_MAJOR 1 + #define AVR_SLOW_PWM_VERSION_MINOR 2 + #define AVR_SLOW_PWM_VERSION_PATCH 0 + #define AVR_SLOW_PWM_VERSION_INT 1002000 +#endif -/////////////////////////////////////////////////// +#ifndef _PWM_LOGLEVEL_ + #define _PWM_LOGLEVEL_ 1 +#endif -// find the first available slot -// return -1 if none found -int AVR_Slow_PWM_ISR::findFirstFreeSlot() -{ - // all slots are used - if (numChannels >= MAX_NUMBER_CHANNELS) - { - return -1; - } - - // return the first slot with no callbackStart (i.e. free) - for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) - { - if ( (PWM[channelNum].period == 0) && !PWM[channelNum].enabled ) - { - return channelNum; - } - } - - // no free slots found - return -1; -} +#include "PWM_Generic_Debug.h" -/////////////////////////////////////////////////// +#include -int AVR_Slow_PWM_ISR::setupPWMChannel(uint32_t pin, uint32_t period, uint32_t dutycycle, void* cbStartFunc, void* cbStopFunc) -{ - int channelNum; - - // Invalid input, such as period = 0, etc - if ( (period == 0) || (dutycycle > 100) ) - { - PWM_LOGERROR(F("Error: Invalid period or dutycycle")); - return -1; - } - - if (numChannels < 0) - { - init(); - } - - channelNum = findFirstFreeSlot(); - - if (channelNum < 0) - { - return -1; - } - - PWM[channelNum].pin = pin; - PWM[channelNum].period = period; - PWM[channelNum].onTime = ( period * dutycycle ) / 100; - - pinMode(pin, OUTPUT); - digitalWrite(pin, HIGH); - PWM[channelNum].pinHigh = true; - - PWM[channelNum].prevTime = timeNow(); - - PWM[channelNum].callbackStart = cbStartFunc; - PWM[channelNum].callbackStop = cbStopFunc; - - PWM_LOGINFO0(F("Channel : ")); PWM_LOGINFO0(channelNum); - PWM_LOGINFO0(F("\tPeriod : ")); PWM_LOGINFO0(PWM[channelNum].period); - PWM_LOGINFO0(F("\t\tOnTime : ")); PWM_LOGINFO0(PWM[channelNum].onTime); - PWM_LOGINFO0(F("\tStart_Time : ")); PWM_LOGINFOLN0(PWM[channelNum].prevTime); - - numChannels++; - - PWM[channelNum].enabled = true; - - return channelNum; -} +#include -/////////////////////////////////////////////////// +#if defined(ARDUINO) + #if ARDUINO >= 100 + #include + #else + #include + #endif +#endif -bool AVR_Slow_PWM_ISR::modifyPWMChannel_Period(unsigned channelNum, uint32_t pin, uint32_t period, uint32_t dutycycle) -{ - // Invalid input, such as period = 0, etc - if ( (period == 0) || (dutycycle > 100) ) - { - PWM_LOGERROR("Error: Invalid period or dutycycle"); - return false; - } - - if (channelNum > MAX_NUMBER_CHANNELS) - { - PWM_LOGERROR("Error: channelNum > MAX_NUMBER_CHANNELS"); - return false; - } - - if (PWM[channelNum].pin != pin) - { - PWM_LOGERROR("Error: channelNum and pin mismatched"); - return false; - } - - PWM[channelNum].period = period; - PWM[channelNum].onTime = ( period * dutycycle ) / 100; - - digitalWrite(pin, HIGH); - PWM[channelNum].pinHigh = true; - - PWM[channelNum].prevTime = timeNow(); - - PWM_LOGINFO0("Channel : "); PWM_LOGINFO0(channelNum); PWM_LOGINFO0("\tPeriod : "); PWM_LOGINFO0(PWM[channelNum].period); - PWM_LOGINFO0("\t\tOnTime : "); PWM_LOGINFO0(PWM[channelNum].onTime); PWM_LOGINFO0("\tStart_Time : "); PWM_LOGINFOLN0(PWM[channelNum].prevTime); - - return true; -} +#define AVR_Slow_PWM_ISR AVR_Slow_PWM +typedef void (*timer_callback)(); +typedef void (*timer_callback_p)(void *); -/////////////////////////////////////////////////// +#if !defined(USING_MICROS_RESOLUTION) + #warning Not USING_MICROS_RESOLUTION, using millis resolution + #define USING_MICROS_RESOLUTION false +#endif -void AVR_Slow_PWM_ISR::deleteChannel(unsigned channelNum) -{ - if (channelNum >= MAX_NUMBER_CHANNELS) - { - return; - } - - // nothing to delete if no timers are in use - if (numChannels == 0) - { - return; - } - - // don't decrease the number of timers if the specified slot is already empty (zero period, invalid) - if ( (PWM[channelNum].pin != INVALID_AVR_PIN) && (PWM[channelNum].period != 0) ) - { - memset((void*) &PWM[channelNum], 0, sizeof (PWM_t)); - - PWM[channelNum].pin = INVALID_AVR_PIN; - - // update number of timers - numChannels--; - } -} +#define INVALID_AVR_PIN 255 -/////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////// -void AVR_Slow_PWM_ISR::restartChannel(unsigned channelNum) +class AVR_Slow_PWM_ISR { - if (channelNum >= MAX_NUMBER_CHANNELS) - { - return; - } - PWM[channelNum].prevTime = timeNow(); -} + public: + // maximum number of PWM channels +#define MAX_NUMBER_CHANNELS 16 -/////////////////////////////////////////////////// + // constructor + AVR_Slow_PWM_ISR(); -bool AVR_Slow_PWM_ISR::isEnabled(unsigned channelNum) -{ - if (channelNum >= MAX_NUMBER_CHANNELS) - { - return false; - } + void init(); - return PWM[channelNum].enabled; -} + // this function must be called inside loop() + void run(); + + ////////////////////////////////////////////////////////////////// + // PWM + // Return the channelNum if OK, -1 if error + int8_t setPWM(const uint32_t& pin, const double& frequency, const double& dutycycle, timer_callback StartCallback = nullptr, + timer_callback StopCallback = nullptr) + { + double period = 0; + + if ( ( frequency != 0 ) && ( frequency <= 1000 ) ) + { +#if USING_MICROS_RESOLUTION + // period in us + period = 1000000.0f / frequency; +#else + // period in ms + period = 1000.0f / frequency; +#endif + } + else + { + PWM_LOGERROR("Error: Invalid frequency, max is 1000Hz"); + + return -1; + } + + return setupPWMChannel(pin, period, dutycycle, (void *) StartCallback, (void *) StopCallback); + } -/////////////////////////////////////////////////// + // period in us + // Return the channelNum if OK, -1 if error + int8_t setPWM_Period(const uint32_t& pin, const double& period, const double& dutycycle, timer_callback StartCallback = nullptr, + timer_callback StopCallback = nullptr) + { + return setupPWMChannel(pin, period, dutycycle, (void *) StartCallback, (void *) StopCallback); + } + + ////////////////////////////////////////////////////////////////// + + // low level function to modify a PWM channel + // returns the true on success or false on failure + bool modifyPWMChannel(const uint8_t& channelNum, const uint32_t& pin, const double& frequency, const double& dutycycle) + { + double period = 0; + + if ( ( frequency > 0 ) && ( frequency <= 1000 ) ) + { +#if USING_MICROS_RESOLUTION + // period in us + period = 1000000.0f / frequency; +#else + // period in ms + period = 1000.0f / frequency; +#endif + } + else + { + PWM_LOGERROR("Error: Invalid frequency, max is 1000Hz"); + return false; + } + + return modifyPWMChannel_Period(channelNum, pin, period, dutycycle); + } + + ////////////////////////////////////////////////////////////////// + + //period in us + bool modifyPWMChannel_Period(const uint8_t& channelNum, const uint32_t& pin, const double& period, const double& dutycycle); + + ////////////////////////////////////////////////////////////////// -void AVR_Slow_PWM_ISR::enable(unsigned channelNum) -{ - if (channelNum >= MAX_NUMBER_CHANNELS) - { - return; - } + // destroy the specified PWM channel + void deleteChannel(const uint8_t& channelNum); - PWM[channelNum].enabled = true; -} + // restart the specified PWM channel + void restartChannel(const uint8_t& channelNum); -/////////////////////////////////////////////////// + // returns true if the specified PWM channel is enabled + bool isEnabled(const uint8_t& channelNum); -void AVR_Slow_PWM_ISR::disable(unsigned channelNum) -{ - if (channelNum >= MAX_NUMBER_CHANNELS) - { - return; - } + // enables the specified PWM channel + void enable(const uint8_t& channelNum); - PWM[channelNum].enabled = false; -} + // disables the specified PWM channel + void disable(const uint8_t& channelNum); -/////////////////////////////////////////////////// + // enables all PWM channels + void enableAll(); -void AVR_Slow_PWM_ISR::enableAll() -{ - // Enable all timers with a callbackStart assigned (used) + // disables all PWM channels + void disableAll(); - for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) - { - if (PWM[channelNum].period != 0) - { - PWM[channelNum].enabled = true; - } - } -} + // enables the specified PWM channel if it's currently disabled, and vice-versa + void toggle(const uint8_t& channelNum); -/////////////////////////////////////////////////// + // returns the number of used PWM channels + int8_t getnumChannels(); -void AVR_Slow_PWM_ISR::disableAll() -{ - // Disable all timers with a callbackStart assigned (used) - for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) - { - if (PWM[channelNum].period != 0) + // returns the number of available PWM channels + uint8_t getNumAvailablePWMChannels() { - PWM[channelNum].enabled = false; - } - } -} + return MAX_NUMBER_CHANNELS - numChannels; + }; -/////////////////////////////////////////////////// + private: -void AVR_Slow_PWM_ISR::toggle(unsigned channelNum) -{ - if (channelNum >= MAX_NUMBER_CHANNELS) - { - return; - } + // low level function to initialize and enable a new PWM channel + // returns the PWM channel number (channelNum) on success or + // -1 on failure (f == NULL) or no free PWM channels + int8_t setupPWMChannel(const uint32_t& pin, const double& period, const double& dutycycle, void* cbStartFunc = nullptr, void* cbStopFunc = nullptr); - PWM[channelNum].enabled = !PWM[channelNum].enabled; -} + // find the first available slot + int8_t findFirstFreeSlot(); -/////////////////////////////////////////////////// - -unsigned AVR_Slow_PWM_ISR::getnumChannels() -{ - return numChannels; -} + typedef struct + { + /////////////////////////////////// + + + /////////////////////////////////// + + uint32_t prevTime; // value returned by the micros() or millis() function in the previous run() call + double period; // period value, in us / ms + uint32_t onTime; // onTime value, ( period * dutyCycle / 100 ) us / ms + + void* callbackStart; // pointer to the callback function when PWM pulse starts (HIGH) + void* callbackStop; // pointer to the callback function when PWM pulse stops (LOW) + + //////////////////////////////////////////////////////////// + + uint32_t pin; // PWM pin + bool pinHigh; // true if PWM pin is HIGH + //////////////////////////////////////////////////////////// + + bool enabled; // true if enabled + } PWM_t; + + volatile PWM_t PWM[MAX_NUMBER_CHANNELS]; + + // actual number of PWM channels in use (-1 means uninitialized) + volatile int8_t numChannels; +}; #endif // AVR_SLOW_PWM_ISR_HPP + diff --git a/src/AVR_Slow_PWM_ISR_Impl.h b/src/AVR_Slow_PWM_ISR_Impl.h new file mode 100644 index 0000000..3aeca97 --- /dev/null +++ b/src/AVR_Slow_PWM_ISR_Impl.h @@ -0,0 +1,356 @@ +/**************************************************************************************************************************** + AVR_Slow_PWM_ISR_Impl.h + For AVR-based boards (UNO, Nano, Mega, 32U4, 16U4, etc. ) + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/AVR_Slow_PWM + Licensed under MIT license + + TCNTx - Timer/Counter Register. The actual timer value is stored here. + OCRx - Output Compare Register + ICRx - Input Capture Register (only for 16bit timer) + TIMSKx - Timer/Counter Interrupt Mask Register. To enable/disable timer interrupts. + TIFRx - Timer/Counter Interrupt Flag Register. Indicates a pending timer interrupt. + + Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by + unsigned long miliseconds), you just consume only one RP2040-based timer and avoid conflicting with other cores' tasks. + The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers + Therefore, their executions are not blocked by bad-behaving functions / tasks. + This important feature is absolutely necessary for mission-critical tasks. + + Version: 1.1.0 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K.Hoang 27/09/2021 Initial coding for AVR-based boards (UNO, Nano, Mega, 32U4, 16U4, etc. ) + 1.1.0 K Hoang 10/11/2021 Add functions to modify PWM settings on-the-fly +*****************************************************************************************************************************/ + +#pragma once + +#ifndef AVR_SLOW_PWM_ISR_IMPL_H +#define AVR_SLOW_PWM_ISR_IMPL_H + +#include + +/////////////////////////////////////////////////// + + +uint32_t timeNow() +{ +#if USING_MICROS_RESOLUTION + return ( (uint32_t) micros() ); +#else + return ( (uint32_t) millis() ); +#endif +} + +/////////////////////////////////////////////////// + +AVR_Slow_PWM_ISR::AVR_Slow_PWM_ISR() + : numChannels (-1) +{ +} + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::init() +{ + uint32_t currentTime = timeNow(); + + for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) + { + memset((void*) &PWM[channelNum], 0, sizeof (PWM_t)); + PWM[channelNum].prevTime = currentTime; + PWM[channelNum].pin = INVALID_AVR_PIN; + } + + numChannels = 0; +} + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::run() +{ + //uint32_t currentTime = timeNow(); + uint32_t currentTime; + + for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) + { + currentTime = timeNow(); + + // If enabled => check + // start period / dutyCycle => digitalWrite HIGH + // end dutyCycle => digitalWrite LOW + if (PWM[channelNum].enabled) + { + if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) <= PWM[channelNum].onTime ) + { + if (!PWM[channelNum].pinHigh) + { + digitalWrite(PWM[channelNum].pin, HIGH); + PWM[channelNum].pinHigh = true; + + // callbackStart + if (PWM[channelNum].callbackStart != nullptr) + { + (*(timer_callback) PWM[channelNum].callbackStart)(); + } + } + } + else if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) < PWM[channelNum].period ) + { + if (PWM[channelNum].pinHigh) + { + digitalWrite(PWM[channelNum].pin, LOW); + PWM[channelNum].pinHigh = false; + + // callback when PWM pulse stops (LOW) + if (PWM[channelNum].callbackStop != nullptr) + { + (*(timer_callback) PWM[channelNum].callbackStop)(); + } + } + } + //else + else if ( (uint32_t) (currentTime - PWM[channelNum].prevTime) >= PWM[channelNum].period ) + { + PWM[channelNum].prevTime = currentTime; + } + } + } +} + + +/////////////////////////////////////////////////// + +// find the first available slot +// return -1 if none found +int8_t AVR_Slow_PWM_ISR::findFirstFreeSlot() +{ + // all slots are used + if (numChannels >= MAX_NUMBER_CHANNELS) + { + return -1; + } + + // return the first slot with no callbackStart (i.e. free) + for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) + { + if ( (PWM[channelNum].period == 0) && !PWM[channelNum].enabled ) + { + return channelNum; + } + } + + // no free slots found + return -1; +} + +/////////////////////////////////////////////////// + +int8_t AVR_Slow_PWM_ISR::setupPWMChannel(const uint32_t& pin, const double& period, const double& dutycycle, void* cbStartFunc, void* cbStopFunc) +{ + int channelNum; + + // Invalid input, such as period = 0, etc + if ( (period <= 0.0) || (dutycycle < 0.0) || (dutycycle > 100.0) ) + { + PWM_LOGERROR(F("Error: Invalid period or dutycycle")); + return -1; + } + + if (numChannels < 0) + { + init(); + } + + channelNum = findFirstFreeSlot(); + + if (channelNum < 0) + { + return -1; + } + + PWM[channelNum].pin = pin; + PWM[channelNum].period = period; + PWM[channelNum].onTime = ( period * dutycycle ) / 100; + + pinMode(pin, OUTPUT); + digitalWrite(pin, HIGH); + PWM[channelNum].pinHigh = true; + + PWM[channelNum].prevTime = timeNow(); + + PWM[channelNum].callbackStart = cbStartFunc; + PWM[channelNum].callbackStop = cbStopFunc; + + PWM_LOGINFO0(F("Channel : ")); PWM_LOGINFO0(channelNum); + PWM_LOGINFO0(F("\tPeriod : ")); PWM_LOGINFO0(PWM[channelNum].period); + PWM_LOGINFO0(F("\t\tOnTime : ")); PWM_LOGINFO0(PWM[channelNum].onTime); + PWM_LOGINFO0(F("\tStart_Time : ")); PWM_LOGINFOLN0(PWM[channelNum].prevTime); + + numChannels++; + + PWM[channelNum].enabled = true; + + return channelNum; +} + +/////////////////////////////////////////////////// + +bool AVR_Slow_PWM_ISR::modifyPWMChannel_Period(const uint8_t& channelNum, const uint32_t& pin, const double& period, const double& dutycycle) +{ + // Invalid input, such as period = 0, etc + if ( (period <= 0.0) || (dutycycle < 0.0) || (dutycycle > 100.0) ) + { + PWM_LOGERROR("Error: Invalid period or dutycycle"); + return false; + } + + if (channelNum > MAX_NUMBER_CHANNELS) + { + PWM_LOGERROR("Error: channelNum > MAX_NUMBER_CHANNELS"); + return false; + } + + if (PWM[channelNum].pin != pin) + { + PWM_LOGERROR("Error: channelNum and pin mismatched"); + return false; + } + + PWM[channelNum].period = period; + PWM[channelNum].onTime = ( period * dutycycle ) / 100; + + digitalWrite(pin, HIGH); + PWM[channelNum].pinHigh = true; + + PWM[channelNum].prevTime = timeNow(); + + PWM_LOGINFO0("Channel : "); PWM_LOGINFO0(channelNum); PWM_LOGINFO0("\tPeriod : "); PWM_LOGINFO0(PWM[channelNum].period); + PWM_LOGINFO0("\t\tOnTime : "); PWM_LOGINFO0(PWM[channelNum].onTime); PWM_LOGINFO0("\tStart_Time : "); PWM_LOGINFOLN0(PWM[channelNum].prevTime); + + return true; +} + + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::deleteChannel(const uint8_t& channelNum) +{ + // nothing to delete if no timers are in use + if ( (channelNum >= MAX_NUMBER_CHANNELS) || (numChannels == 0) ) + { + return; + } + + // don't decrease the number of timers if the specified slot is already empty (zero period, invalid) + if ( (PWM[channelNum].pin != INVALID_AVR_PIN) && (PWM[channelNum].period != 0) ) + { + memset((void*) &PWM[channelNum], 0, sizeof (PWM_t)); + + PWM[channelNum].pin = INVALID_AVR_PIN; + + // update number of timers + numChannels--; + } +} + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::restartChannel(const uint8_t& channelNum) +{ + if (channelNum >= MAX_NUMBER_CHANNELS) + { + return; + } + + PWM[channelNum].prevTime = timeNow(); +} + +/////////////////////////////////////////////////// + +bool AVR_Slow_PWM_ISR::isEnabled(const uint8_t& channelNum) +{ + if (channelNum >= MAX_NUMBER_CHANNELS) + { + return false; + } + + return PWM[channelNum].enabled; +} + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::enable(const uint8_t& channelNum) +{ + if (channelNum >= MAX_NUMBER_CHANNELS) + { + return; + } + + PWM[channelNum].enabled = true; +} + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::disable(const uint8_t& channelNum) +{ + if (channelNum >= MAX_NUMBER_CHANNELS) + { + return; + } + + PWM[channelNum].enabled = false; +} + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::enableAll() +{ + // Enable all timers with a callbackStart assigned (used) + + for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) + { + if (PWM[channelNum].period != 0) + { + PWM[channelNum].enabled = true; + } + } +} + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::disableAll() +{ + // Disable all timers with a callbackStart assigned (used) + for (uint8_t channelNum = 0; channelNum < MAX_NUMBER_CHANNELS; channelNum++) + { + if (PWM[channelNum].period != 0) + { + PWM[channelNum].enabled = false; + } + } +} + +/////////////////////////////////////////////////// + +void AVR_Slow_PWM_ISR::toggle(const uint8_t& channelNum) +{ + if (channelNum >= MAX_NUMBER_CHANNELS) + { + return; + } + + PWM[channelNum].enabled = !PWM[channelNum].enabled; +} + +/////////////////////////////////////////////////// + +int8_t AVR_Slow_PWM_ISR::getnumChannels() +{ + return numChannels; +} + +#endif // AVR_SLOW_PWM_ISR_IMPL_H diff --git a/src/AVR_Slow_PWM_Impl.h b/src/AVR_Slow_PWM_Impl.h new file mode 100644 index 0000000..3054844 --- /dev/null +++ b/src/AVR_Slow_PWM_Impl.h @@ -0,0 +1,906 @@ +/**************************************************************************************************************************** + AVR_Slow_PWM.hpp + For AVR-based boards (UNO, Nano, Mega, 32U4, 16U4, etc. ) + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/AVR_Slow_PWM + Licensed under MIT license + + TCNTx - Timer/Counter Register. The actual timer value is stored here. + OCRx - Output Compare Register + ICRx - Input Capture Register (only for 16bit timer) + TIMSKx - Timer/Counter Interrupt Mask Register. To enable/disable timer interrupts. + TIFRx - Timer/Counter Interrupt Flag Register. Indicates a pending timer interrupt. + + Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by + unsigned long miliseconds), you just consume only one RP2040-based timer and avoid conflicting with other cores' tasks. + The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers + Therefore, their executions are not blocked by bad-behaving functions / tasks. + This important feature is absolutely necessary for mission-critical tasks. + + Version: 1.1.0 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K.Hoang 27/09/2021 Initial coding for AVR-based boards (UNO, Nano, Mega, 32U4, 16U4, etc. ) + 1.1.0 K Hoang 10/11/2021 Add functions to modify PWM settings on-the-fly +****************************************************************************************************************************/ + +#pragma once + +#ifndef AVR_SLOW_PWM_IMPL_H +#define AVR_SLOW_PWM_IMPL_H + +#ifndef _PWM_LOGLEVEL_ + #define _PWM_LOGLEVEL_ 0 +#endif + +void TimerInterrupt::init(int8_t timer) +{ + // Set timer specific stuff + // All timers in CTC mode + // 8 bit timers will require changing prescalar values, + // whereas 16 bit timers are set to either ck/1 or ck/64 prescalar + + //cli();//stop interrupts + noInterrupts(); + + switch (timer) + { + #if defined(TCCR1A) && defined(TCCR1B) && defined(WGM12) + case 1: + // 16 bit timer + TCCR1A = 0; + TCCR1B = 0; + // Page 172-173. ATmega 328/328P or Page 145-146 of ATmega 640/1280/2560 + // Mode 4 => Clear Timer on Compare match (CTC) using OCR1A for counter value + bitWrite(TCCR1B, WGM12, 1); + // No scaling now + bitWrite(TCCR1B, CS10, 1); + + PWM_LOGWARN(F("T1")); + + break; + #endif + + #if defined(TCCR2A) && defined(TCCR2B) + case 2: + // 8 bit timer + TCCR2A = 0; + TCCR2B = 0; + // Page 205-206. ATmegal328, Page 184-185 ATmega 640/1280/2560 + // Mode 2 => Clear Timer on Compare match (CTC) using OCR2A for counter value + bitWrite(TCCR2A, WGM21, 1); + // No scaling now + bitWrite(TCCR2B, CS20, 1); + + PWM_LOGWARN(F("T2")); + + break; + #endif + + #if defined(TCCR3A) && defined(TCCR3B) && defined(TIMSK3) + case 3: + // 16 bit timer + TCCR3A = 0; + TCCR3B = 0; + bitWrite(TCCR3B, WGM32, 1); + bitWrite(TCCR3B, CS30, 1); + + PWM_LOGWARN(F("T3")); + + break; + #endif + + #if defined(TCCR4A) && defined(TCCR4B) && defined(TIMSK4) + case 4: + // 16 bit timer + TCCR4A = 0; + TCCR4B = 0; + #if defined(WGM42) + bitWrite(TCCR4B, WGM42, 1); + #elif defined(CS43) + // TODO this may not be correct + // atmega32u4 + bitWrite(TCCR4B, CS43, 1); + #endif + bitWrite(TCCR4B, CS40, 1); + + PWM_LOGWARN(F("T4")); + + break; + #endif + + #if defined(TCCR5A) && defined(TCCR5B) && defined(TIMSK5) + case 5: + // 16 bit timer + TCCR5A = 0; + TCCR5B = 0; + bitWrite(TCCR5B, WGM52, 1); + bitWrite(TCCR5B, CS50, 1); + + PWM_LOGWARN(F("T5")); + + break; + #endif + } + + _timer = timer; + + //sei();//enable interrupts + interrupts(); + +} + +void TimerInterrupt::set_OCR() +{ + // Run with noInterrupt() + // Set the OCR for the given timer, + // set the toggle count, + // then turn on the interrupts + uint32_t _OCRValueToUse; + + switch (_timer) + { + case 1: + _OCRValueToUse = min(MAX_COUNT_16BIT, _OCRValueRemaining); + OCR1A = _OCRValueToUse; + _OCRValueRemaining -= _OCRValueToUse; + +#if defined(OCR1A) && defined(TIMSK1) && defined(OCIE1A) + // Bit 1 – OCIEA: Output Compare A Match Interrupt Enable + // When this bit is written to '1', and the I-flag in the Status Register is set (interrupts globally enabled), the + // Timer/Counter Output Compare A Match interrupt is enabled. The corresponding Interrupt Vector is + // executed when the OCFA Flag, located in TIFR1, is set. + bitWrite(TIMSK1, OCIE1A, 1); +#elif defined(OCR1A) && defined(TIMSK) && defined(OCIE1A) + // this combination is for at least the ATmega32 + bitWrite(TIMSK, OCIE1A, 1); +#endif + break; + +#if defined(OCR2A) && defined(TIMSK2) && defined(OCIE2A) + case 2: + _OCRValueToUse = min(MAX_COUNT_8BIT, _OCRValueRemaining); + OCR2A = _OCRValueToUse; + _OCRValueRemaining -= _OCRValueToUse; + + bitWrite(TIMSK2, OCIE2A, 1); + break; +#endif + +#if defined(OCR3A) && defined(TIMSK3) && defined(OCIE3A) + case 3: + _OCRValueToUse = min(MAX_COUNT_16BIT, _OCRValueRemaining); + OCR3A = _OCRValueToUse; + _OCRValueRemaining -= _OCRValueToUse; + + bitWrite(TIMSK3, OCIE3A, 1); + break; +#endif + +#if defined(OCR4A) && defined(TIMSK4) && defined(OCIE4A) + case 4: + +#if TIMER_INTERRUPT_USING_ATMEGA_32U4 + _OCRValueToUse = min(MAX_COUNT_8BIT, _OCRValueRemaining); +#else + _OCRValueToUse = min(MAX_COUNT_16BIT, _OCRValueRemaining); +#endif + + OCR4A = _OCRValueToUse; + _OCRValueRemaining -= _OCRValueToUse; + + bitWrite(TIMSK4, OCIE4A, 1); + break; +#endif + +#if defined(OCR5A) && defined(TIMSK5) && defined(OCIE5A) + case 5: + _OCRValueToUse = min(MAX_COUNT_16BIT, _OCRValueRemaining); + OCR5A = _OCRValueToUse; + _OCRValueRemaining -= _OCRValueToUse; + + bitWrite(TIMSK5, OCIE5A, 1); + break; +#endif + } + + // Flag _OCRValue == 0 => end of long timer + if (_OCRValueRemaining == 0) + _timerDone = true; + +} + +// frequency (in hertz) and duration (in milliseconds). +// Return true if frequency is OK with selected timer (OCRValue is in range) +bool TimerInterrupt::setFrequency(const float& frequency, timer_callback_p callback, const uint32_t& params, const unsigned long& duration) +{ + uint8_t andMask = 0b11111000; + unsigned long OCRValue; + bool isSuccess = false; + + //frequencyLimit must > 1 + float frequencyLimit = frequency * 17179.840; + + // Limit frequency to larger than (0.00372529 / 64) Hz or interval 17179.840s / 17179840 ms to avoid uint32_t overflow + if ((_timer <= 0) || (callback == NULL) || ((frequencyLimit) < 1) ) + { + return false; + } + else + { + // Calculate the toggle count. Duration must be at least longer then one cycle + if (duration > 0) + { + _toggle_count = frequency * duration / 1000; + + PWM_LOGWARN1(F("setFrequency => _toggle_count ="), _toggle_count); + PWM_LOGWARN3(F("Frequency ="), frequency, F(", duration ="), duration); + + if (_toggle_count < 1) + { + return false; + } + } + else + { + _toggle_count = -1; + } + + //Timer0 and timer2 are 8 bit timers, meaning they can store a maximum counter value of 255. + //Timer2 does not have the option of 1024 prescaler, only 1, 8, 32, 64 + //Timer1 is a 16 bit timer, meaning it can store a maximum counter value of 65535. + int prescalerIndexStart; + + //Use smallest prescaler first, then increase until fits (<255) + if (_timer != 2) + { + if (frequencyLimit > 64) + prescalerIndexStart = NO_PRESCALER; + else if (frequencyLimit > 8) + prescalerIndexStart = PRESCALER_8; + else + prescalerIndexStart = PRESCALER_64; + + + for (int prescalerIndex = prescalerIndexStart; prescalerIndex <= PRESCALER_1024; prescalerIndex++) + { + OCRValue = F_CPU / (frequency * prescalerDiv[prescalerIndex]) - 1; + + PWM_LOGWARN1(F("Freq * 1000 ="), frequency * 1000); + PWM_LOGWARN3(F("F_CPU ="), F_CPU, F(", preScalerDiv ="), prescalerDiv[prescalerIndex]); + PWM_LOGWARN3(F("OCR ="), OCRValue, F(", preScalerIndex ="), prescalerIndex); + + // We use very large _OCRValue now, and every time timer ISR activates, we deduct min(MAX_COUNT_16BIT, _OCRValueRemaining) from _OCRValueRemaining + // So that we can create very long timer, even if the counter is only 16-bit. + // Use very high frequency (OCRValue / MAX_COUNT_16BIT) around 16 * 1024 to achieve higher accuracy +#if TIMER_INTERRUPT_USING_ATMEGA_32U4 + uint16_t MAX_COUNT_32U4 = (_timer == 4) ? MAX_COUNT_8BIT : MAX_COUNT_16BIT; + + if ( (OCRValue / MAX_COUNT_32U4) < 16384 ) +#else + if ( (OCRValue / MAX_COUNT_16BIT) < 16384 ) +#endif + { + _OCRValue = OCRValue; + _OCRValueRemaining = OCRValue; + _prescalerIndex = prescalerIndex; + + PWM_LOGWARN1(F("OK in loop => _OCR ="), _OCRValue); + PWM_LOGWARN3(F("_preScalerIndex ="), _prescalerIndex, F(", preScalerDiv ="), prescalerDiv[_prescalerIndex]); + + isSuccess = true; + + break; + } + } + + if (!isSuccess) + { + // Always do this + _OCRValue = OCRValue; + _OCRValueRemaining = OCRValue; + _prescalerIndex = PRESCALER_1024; + + PWM_LOGWARN1(F("OK out loop => _OCR ="), _OCRValue); + PWM_LOGWARN3(F("_preScalerIndex ="), _prescalerIndex, F(", preScalerDiv ="), prescalerDiv[_prescalerIndex]); + } + } + else + { + if (frequencyLimit > 64) + prescalerIndexStart = T2_NO_PRESCALER; + else if (frequencyLimit > 8) + prescalerIndexStart = T2_PRESCALER_8; + else if (frequencyLimit > 2) + prescalerIndexStart = T2_PRESCALER_32; + else + prescalerIndexStart = T2_PRESCALER_64; + + // Page 206-207. ATmegal328 + //8-bit Timer2 has more options up to 1024 prescaler, from 1, 8, 32, 64, 128, 256 and 1024 + for (int prescalerIndex = prescalerIndexStart; prescalerIndex <= T2_PRESCALER_1024; prescalerIndex++) + { + OCRValue = F_CPU / (frequency * prescalerDivT2[prescalerIndex]) - 1; + + PWM_LOGWARN3(F("F_CPU ="), F_CPU, F(", preScalerDiv ="), prescalerDivT2[prescalerIndex]); + PWM_LOGWARN3(F("OCR2 ="), OCRValue, F(", preScalerIndex ="), prescalerIndex); + + // We use very large _OCRValue now, and every time timer ISR activates, we deduct min(MAX_COUNT_8BIT, _OCRValue) from _OCRValue + // to create very long timer, even if the counter is only 16-bit. + // Use very high frequency (OCRValue / MAX_COUNT_8BIT) around 16 * 1024 to achieve higher accuracy + if ( (OCRValue / MAX_COUNT_8BIT) < 16384 ) + { + _OCRValue = OCRValue; + _OCRValueRemaining = OCRValue; + // same as prescalarbits + _prescalerIndex = prescalerIndex; + + PWM_LOGWARN1(F("OK in loop => _OCR ="), _OCRValue); + PWM_LOGWARN3(F("_preScalerIndex ="), _prescalerIndex, F(", preScalerDiv ="), prescalerDivT2[_prescalerIndex]); + + isSuccess = true; + + break; + } + } + + if (!isSuccess) + { + // Always do this + _OCRValue = OCRValue; + _OCRValueRemaining = OCRValue; + // same as prescalarbits + _prescalerIndex = T2_PRESCALER_1024; + + PWM_LOGWARN1(F("OK out loop => _OCR ="), _OCRValue); + PWM_LOGWARN3(F("_preScalerIndex ="), _prescalerIndex, F(", preScalerDiv ="), prescalerDivT2[_prescalerIndex]); + } + } + + //cli();//stop interrupts + noInterrupts(); + + _frequency = frequency; + _callback = (void*) callback; + _params = reinterpret_cast(params); + + _timerDone = false; + + // 8 bit timers from here + #if defined(TCCR2B) + if (_timer == 2) + { + TCCR2B = (TCCR2B & andMask) | _prescalerIndex; //prescalarbits; + + PWM_LOGWARN1(F("TCCR2B ="), TCCR2B); + } + #endif + + // 16 bit timers from here + #if defined(TCCR1B) + #if ( TIMER_INTERRUPT_USING_ATMEGA_32U4 ) + if (_timer == 1) + #else + else if (_timer == 1) + #endif + { + TCCR1B = (TCCR1B & andMask) | _prescalerIndex; //prescalarbits; + + PWM_LOGWARN1(F("TCCR1B ="), TCCR1B); + } + #endif + + #if defined(TCCR3B) + else if (_timer == 3) + TCCR3B = (TCCR3B & andMask) | _prescalerIndex; //prescalarbits; + #endif + + #if defined(TCCR4B) + else if (_timer == 4) + TCCR4B = (TCCR4B & andMask) | _prescalerIndex; //prescalarbits; + #endif + + #if defined(TCCR5B) + else if (_timer == 5) + TCCR5B = (TCCR5B & andMask) | _prescalerIndex; //prescalarbits; + #endif + + // Set the OCR for the given timer, + // set the toggle count, + // then turn on the interrupts + set_OCR(); + + //sei();//allow interrupts + interrupts(); + + return true; + } +} + +void TimerInterrupt::detachInterrupt() +{ + //cli();//stop interrupts + noInterrupts(); + + switch (_timer) + { + #if defined(TIMSK1) && defined(OCIE1A) + case 1: + bitWrite(TIMSK1, OCIE1A, 0); + + PWM_LOGWARN(F("Disable T1")); + + break; + #endif + + case 2: + #if defined(TIMSK2) && defined(OCIE2A) + bitWrite(TIMSK2, OCIE2A, 0); // disable interrupt + #endif + + PWM_LOGWARN(F("Disable T2")); + + break; + +#if defined(TIMSK3) && defined(OCIE3A) + case 3: + bitWrite(TIMSK3, OCIE3A, 0); + + PWM_LOGWARN(F("Disable T3")); + + break; +#endif + +#if defined(TIMSK4) && defined(OCIE4A) + case 4: + bitWrite(TIMSK4, OCIE4A, 0); + + PWM_LOGWARN(F("Disable T4")); + + break; +#endif + +#if defined(TIMSK5) && defined(OCIE5A) + case 5: + bitWrite(TIMSK5, OCIE5A, 0); + + PWM_LOGWARN(F("Disable T5")); + + break; +#endif + } + + //sei();//allow interrupts + interrupts(); +} + +// Duration (in milliseconds). Duration = 0 or not specified => run indefinitely +void TimerInterrupt::reattachInterrupt(const unsigned long& duration) +{ + //cli();//stop interrupts + noInterrupts(); + + // Calculate the toggle count + if (duration > 0) + { + _toggle_count = _frequency * duration / 1000; + } + else + { + _toggle_count = -1; + } + + switch (_timer) + { +#if defined(TIMSK1) && defined(OCIE1A) + case 1: + bitWrite(TIMSK1, OCIE1A, 1); + + PWM_LOGWARN(F("Enable T1")); + + break; +#endif + + case 2: + #if defined(TIMSK2) && defined(OCIE2A) + bitWrite(TIMSK2, OCIE2A, 1); // enable interrupt + #endif + + PWM_LOGWARN(F("Enable T2")); + + break; + +#if defined(TIMSK3) && defined(OCIE3A) + case 3: + bitWrite(TIMSK3, OCIE3A, 1); + + PWM_LOGWARN(F("Enable T3")); + + break; +#endif + +#if defined(TIMSK4) && defined(OCIE4A) + case 4: + bitWrite(TIMSK4, OCIE4A, 1); + + PWM_LOGWARN(F("Enable T4")); + + break; +#endif + +#if defined(TIMSK5) && defined(OCIE5A) + case 5: + bitWrite(TIMSK5, OCIE5A, 1); + + PWM_LOGWARN(F("Enable T5")); + + break; +#endif + } + + //sei();//allow interrupts + interrupts(); +} + +// Just stop clock source, still keep the count +void TimerInterrupt::pauseTimer() +{ + uint8_t andMask = 0b11111000; + + //Just clear the CSx2-CSx0. Still keep the count in TCNT and Timer Interrupt mask TIMKSx. + + // 8 bit timers from here + #if defined(TCCR2B) + if (_timer == 2) + { + TCCR2B = (TCCR2B & andMask); + + PWM_LOGWARN1(F("TCCR2B ="), TCCR2B); + } + #endif + + // 16 bit timers from here + #if defined(TCCR1B) + #if ( TIMER_INTERRUPT_USING_ATMEGA_32U4 ) + if (_timer == 1) + #else + else if (_timer == 1) + #endif + { + TCCR1B = (TCCR1B & andMask); + + PWM_LOGWARN1(F("TCCR1B ="), TCCR1B); + } + #endif + + #if defined(TCCR3B) + else if (_timer == 3) + TCCR3B = (TCCR3B & andMask); + #endif + + #if defined(TCCR4B) + else if (_timer == 4) + TCCR4B = (TCCR4B & andMask); + #endif + + #if defined(TCCR5B) + else if (_timer == 5) + TCCR5B = (TCCR5B & andMask); + #endif +} + +// Just reconnect clock source, continue from the current count +void TimerInterrupt::resumeTimer() +{ + uint8_t andMask = 0b11111000; + + //Just restore the CSx2-CSx0 stored in _prescalerIndex. Still keep the count in TCNT and Timer Interrupt mask TIMKSx. + // 8 bit timers from here + #if defined(TCCR2B) + if (_timer == 2) + { + TCCR2B = (TCCR2B & andMask) | _prescalerIndex; //prescalarbits; + + PWM_LOGWARN1(F("TCCR2B ="), TCCR2B); + } + #endif + + // 16 bit timers from here + #if defined(TCCR1B) + #if ( TIMER_INTERRUPT_USING_ATMEGA_32U4 ) + if (_timer == 1) + #else + else if (_timer == 1) + #endif + { + TCCR1B = (TCCR1B & andMask) | _prescalerIndex; //prescalarbits; + + PWM_LOGWARN1(F("TCCR1B ="), TCCR1B); + } + #endif + + #if defined(TCCR3B) + else if (_timer == 3) + TCCR3B = (TCCR3B & andMask) | _prescalerIndex; //prescalarbits; + #endif + + #if defined(TCCR4B) + else if (_timer == 4) + TCCR4B = (TCCR4B & andMask) | _prescalerIndex; //prescalarbits; + #endif + + #if defined(TCCR5B) + else if (_timer == 5) + TCCR5B = (TCCR5B & andMask) | _prescalerIndex; //prescalarbits; + #endif +} + +////////////////////////////////////////////// + +#if USE_TIMER_1 + #ifndef TIMER1_INSTANTIATED + // To force pre-instatiate only once + #define TIMER1_INSTANTIATED + static TimerInterrupt ITimer1(HW_TIMER_1); + + // Timer0 is used for micros(), millis(), delay(), etc and can't be used + // Pre-instatiate + + ISR(TIMER1_COMPA_vect) + { + long countLocal = ITimer1.getCount(); + + if (ITimer1.getTimer() == 1) + { + if (countLocal != 0) + { + if (ITimer1.checkTimerDone()) + { + PWM_LOGDEBUG3(("T1 callback, _OCRValueRemaining ="), ITimer1.get_OCRValueRemaining(), (", millis ="), millis()); + + ITimer1.callback(); + + if (ITimer1.get_OCRValue() > MAX_COUNT_16BIT) + { + // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_16BIT if _OCRValueRemaining > MAX_COUNT_16BIT + ITimer1.reload_OCRValue(); + } + + if (countLocal > 0) + ITimer1.setCount(countLocal - 1); + } + else + { + //Deduct _OCRValue by min(MAX_COUNT_16BIT, _OCRValue) + // If _OCRValue == 0, flag _timerDone for next cycle + // If last one (_OCRValueRemaining < MAX_COUNT_16BIT) => load _OCR register _OCRValueRemaining + ITimer1.adjust_OCRValue(); + } + } + else + { + PWM_LOGWARN(("T1 done")); + + ITimer1.detachInterrupt(); + } + } + } + + #endif //#ifndef TIMER1_INSTANTIATED +#endif //#if USE_TIMER_1 + +#if USE_TIMER_2 + #ifndef TIMER2_INSTANTIATED + #define TIMER2_INSTANTIATED + static TimerInterrupt ITimer2(HW_TIMER_2); + + ISR(TIMER2_COMPA_vect) + { + long countLocal = ITimer2.getCount(); + + if (ITimer2.getTimer() == 2) + { + if (countLocal != 0) + { + if (ITimer2.checkTimerDone()) + { + PWM_LOGDEBUG3(("T2 callback, _OCRValueRemaining ="), ITimer2.get_OCRValueRemaining(), (", millis ="), millis()); + + ITimer2.callback(); + + if (ITimer2.get_OCRValue() > MAX_COUNT_8BIT) + { + // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_8BIT if _OCRValueRemaining > MAX_COUNT_8BIT + ITimer2.reload_OCRValue(); + } + + if (countLocal > 0) + ITimer2.setCount(countLocal - 1); + + } + else + { + //Deduct _OCRValue by min(MAX_COUNT_8BIT, _OCRValue) + // If _OCRValue == 0, flag _timerDone for next cycle + ITimer2.adjust_OCRValue(); + } + } + else + { + PWM_LOGWARN(("T2 done")); + + ITimer2.detachInterrupt(); + } + } + } + #endif //#ifndef TIMER2_INSTANTIATED +#endif //#if USE_TIMER_2 + +#if (TIMER_INTERRUPT_USING_ATMEGA2560 || TIMER_INTERRUPT_USING_ATMEGA_32U4) + + // Pre-instatiate + #if USE_TIMER_3 + #ifndef TIMER3_INSTANTIATED + // To force pre-instatiate only once + #define TIMER3_INSTANTIATED + static TimerInterrupt ITimer3(HW_TIMER_3); + + ISR(TIMER3_COMPA_vect) + { + long countLocal = ITimer3.getCount(); + + if (ITimer3.getTimer() == 3) + { + if (countLocal != 0) + { + if (ITimer3.checkTimerDone()) + { + PWM_LOGDEBUG3(("T3 callback, _OCRValueRemaining ="), ITimer3.get_OCRValueRemaining(), (", millis ="), millis()); + + ITimer3.callback(); + + if (ITimer3.get_OCRValue() > MAX_COUNT_16BIT) + { + // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_16BIT if _OCRValueRemaining > MAX_COUNT_16BIT + ITimer3.reload_OCRValue(); + } + + if (countLocal > 0) + ITimer3.setCount(countLocal - 1); + } + else + { + //Deduct _OCRValue by min(MAX_COUNT_16BIT, _OCRValue) + // If _OCRValue == 0, flag _timerDone for next cycle + // If last one (_OCRValueRemaining < MAX_COUNT_16BIT) => load _OCR register _OCRValueRemaining + ITimer3.adjust_OCRValue(); + } + } + else + { + PWM_LOGWARN(("T3 done")); + + ITimer3.detachInterrupt(); + } + } + } + + #endif //#ifndef TIMER3_INSTANTIATED + #endif //#if USE_TIMER_3 + +#endif //#if (TIMER_INTERRUPT_USING_ATMEGA2560 || TIMER_INTERRUPT_USING_ATMEGA_32U4) + +#if (TIMER_INTERRUPT_USING_ATMEGA2560 || TIMER_INTERRUPT_USING_ATMEGA_32U4) + + // Even 32u4 Timer4 has 10-bit counter, we use only 8-bit to simplify by not using 2-bit High Byte Register (TC4H) + // Check 15.2.2 Accuracy, page 141 of ATmega16U4/32U4 [DATASHEET] + + #if USE_TIMER_4 + #ifndef TIMER4_INSTANTIATED + // To force pre-instatiate only once + #define TIMER4_INSTANTIATED + static TimerInterrupt ITimer4(HW_TIMER_4); + + ISR(TIMER4_COMPA_vect) + { + long countLocal = ITimer4.getCount(); + + if (ITimer4.getTimer() == 4) + { + if (countLocal != 0) + { + if (ITimer4.checkTimerDone()) + { + PWM_LOGDEBUG3(("T4 callback, _OCRValueRemaining ="), ITimer4.get_OCRValueRemaining(), (", millis ="), millis()); + + ITimer4.callback(); + + if (ITimer4.get_OCRValue() > MAX_COUNT_16BIT) + { + // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_16BIT if _OCRValueRemaining > MAX_COUNT_16BIT + ITimer4.reload_OCRValue(); + } + + if (countLocal > 0) + ITimer4.setCount(countLocal - 1); + } + else + { + //Deduct _OCRValue by min(MAX_COUNT_16BIT, _OCRValue) or min(MAX_COUNT_8BIT, _OCRValue) + // If _OCRValue == 0, flag _timerDone for next cycle + // If last one (_OCRValueRemaining < MAX_COUNT_16BIT / MAX_COUNT_8BIT) => load _OCR register _OCRValueRemaining + ITimer4.adjust_OCRValue(); + } + } + else + { + PWM_LOGWARN(("T4 done")); + + ITimer4.detachInterrupt(); + } + } + } + + + #endif //#ifndef TIMER4_INSTANTIATED + #endif //#if USE_TIMER_4 + +#endif //#if (TIMER_INTERRUPT_USING_ATMEGA2560 || TIMER_INTERRUPT_USING_ATMEGA_32U4) + +#if TIMER_INTERRUPT_USING_ATMEGA2560 + + #if USE_TIMER_5 + #ifndef TIMER5_INSTANTIATED + // To force pre-instatiate only once + #define TIMER5_INSTANTIATED + static TimerInterrupt ITimer5(HW_TIMER_5); + + ISR(TIMER5_COMPA_vect) + { + long countLocal = ITimer5.getCount(); + + if (ITimer5.getTimer() == 5) + { + if (countLocal != 0) + { + if (ITimer5.checkTimerDone()) + { + PWM_LOGDEBUG3(("T5 callback, _OCRValueRemaining ="), ITimer5.get_OCRValueRemaining(), (", millis ="), millis()); + + ITimer5.callback(); + + if (ITimer5.get_OCRValue() > MAX_COUNT_16BIT) + { + // To reload _OCRValueRemaining as well as _OCR register to MAX_COUNT_16BIT if _OCRValueRemaining > MAX_COUNT_16BIT + ITimer5.reload_OCRValue(); + } + + if (countLocal > 0) + ITimer5.setCount(countLocal - 1); + } + else + { + //Deduct _OCRValue by min(MAX_COUNT_16BIT, _OCRValue) + // If _OCRValue == 0, flag _timerDone for next cycle + // If last one (_OCRValueRemaining < MAX_COUNT_16BIT) => load _OCR register _OCRValueRemaining + ITimer5.adjust_OCRValue(); + } + } + else + { + PWM_LOGWARN(("T5 done")); + + ITimer5.detachInterrupt(); + } + } + } + + #endif //#ifndef TIMER5_INSTANTIATED + #endif //#if USE_TIMER_5 + +#endif //#if TIMER_INTERRUPT_USING_ATMEGA2560 + + +#endif // AVR_SLOW_PWM_IMPL_H