From 20177894530857b4aaf934942067009f703d2b7f Mon Sep 17 00:00:00 2001 From: Khoi Hoang <57012152+khoih-prog@users.noreply.github.com> Date: Sat, 5 Nov 2022 02:15:45 -0400 Subject: [PATCH] v1.0.0 for hardware-PWM on nRF52 boards ### Initial Releases v1.0.0 1. Initial coding to support **nRF52-based boards, such as AdaFruit Itsy-Bitsy nRF52840, Feather nRF52840 Express, Seeed XIAO nRF52840, Seeed XIAO nRF52840 Sense**, etc. using [`Adafruit nRF52 core`](https://github.com/adafruit/Adafruit_nRF52_Arduino) or [`Seeeduino nRF52 core`](https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino) --- CONTRIBUTING.md | 73 ++ changelog.md | 34 + examples/PWM_Basic/PWM_Basic.ino | 74 ++ .../PWM_DynamicDutyCycle.ino | 90 +++ .../PWM_DynamicDutyCycle_Int.ino | 96 +++ examples/PWM_DynamicFreq/PWM_DynamicFreq.ino | 89 +++ examples/PWM_Multi/PWM_Multi.ino | 102 +++ .../PWM_MultiChannel/PWM_MultiChannel.ino | 107 +++ examples/PWM_Waveform/PWM_Waveform.ino | 164 +++++ keywords.txt | 43 ++ library.json | 30 + library.properties | 12 + platformio/platformio.ini | 341 +++++++++ src/PWM_Generic_Debug.h | 99 +++ src/nRF52_PWM.h | 652 ++++++++++++++++++ utils/astyle_library.conf | 70 ++ utils/restyle.sh | 6 + 17 files changed, 2082 insertions(+) create mode 100644 CONTRIBUTING.md create mode 100644 changelog.md create mode 100644 examples/PWM_Basic/PWM_Basic.ino create mode 100644 examples/PWM_DynamicDutyCycle/PWM_DynamicDutyCycle.ino create mode 100644 examples/PWM_DynamicDutyCycle_Int/PWM_DynamicDutyCycle_Int.ino create mode 100644 examples/PWM_DynamicFreq/PWM_DynamicFreq.ino create mode 100644 examples/PWM_Multi/PWM_Multi.ino create mode 100644 examples/PWM_MultiChannel/PWM_MultiChannel.ino create mode 100644 examples/PWM_Waveform/PWM_Waveform.ino create mode 100644 keywords.txt create mode 100644 library.json create mode 100644 library.properties create mode 100644 platformio/platformio.ini create mode 100644 src/PWM_Generic_Debug.h create mode 100644 src/nRF52_PWM.h create mode 100644 utils/astyle_library.conf create mode 100644 utils/restyle.sh diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..503f2fd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,73 @@ +## Contributing to nRF52_PWM + +### Reporting Bugs + +Please report bugs in nRF52_PWM if you find them. + +However, before reporting a bug please check through the following: + +* [Existing Open Issues](https://github.com/khoih-prog/nRF52_PWM/issues) - someone might have already encountered this. + +If you don't find anything, please [open a new issue](https://github.com/khoih-prog/nRF52_PWM/issues/new). + +### How to submit a bug report + +Please ensure to specify the following: + +* Arduino IDE version (e.g. 1.8.19) or Platform.io version +* `NRF52` Core Version (e.g. Adafruit NRF52 core v1.3.0, Seeed nRF52 core v1.0.0) +* Contextual information (e.g. what you were trying to achieve) +* Simplest possible steps to reproduce +* Anything that might be relevant in your opinion, such as: + * Operating system (Windows, Ubuntu, etc.) and the output of `uname -a` + * Network configuration + + +### Example + +``` +Arduino IDE version: 1.8.19 +Adafruit NRF52 Core Version 1.3.0 +NRF52840_ITSYBITSY +OS: Ubuntu 20.04 LTS +Linux xy-Inspiron-3593 5.15.0-52-generic #58~20.04.1-Ubuntu SMP Thu Oct 13 13:09:46 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux + +Context: +I encountered a crash while using this library + +Steps to reproduce: +1. ... +2. ... +3. ... +4. ... +``` + +--- + +### Sending Feature Requests + +Feel free to post feature requests. It's helpful if you can explain exactly why the feature would be useful. + +There are usually some outstanding feature requests in the [existing issues list](https://github.com/khoih-prog/nRF52_PWM/issues?q=is%3Aopen+is%3Aissue+label%3Aenhancement), feel free to add comments to them. + +--- + +### Sending Pull Requests + +Pull Requests with changes and fixes are also welcome! + +Please use the `astyle` to reformat the updated library code as follows (demo for Ubuntu Linux) + +1. Change directory to the library GitHub + +``` +xy@xy-Inspiron-3593:~$ cd Arduino/xy/nRF52_PWM_GitHub/ +xy@xy-Inspiron-3593:~/Arduino/xy/nRF52_PWM_GitHub$ +``` + +2. Issue astyle command + +``` +xy@xy-Inspiron-3593:~/Arduino/xy/nRF52_PWM_GitHub$ bash utils/restyle.sh +``` + diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..794544b --- /dev/null +++ b/changelog.md @@ -0,0 +1,34 @@ +# nRF52_PWM Library + +[![arduino-library-badge](https://www.ardu-badge.com/badge/nRF52_PWM.svg?)](https://www.ardu-badge.com/nRF52_PWM) +[![GitHub release](https://img.shields.io/github/release/khoih-prog/nRF52_PWM.svg)](https://github.com/khoih-prog/nRF52_PWM/releases) +[![GitHub](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/khoih-prog/nRF52_PWM/blob/master/LICENSE) +[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat)](#Contributing) +[![GitHub issues](https://img.shields.io/github/issues/khoih-prog/nRF52_PWM.svg)](http://github.com/khoih-prog/nRF52_PWM/issues) + +--- +--- + +## Table of Contents + +* [Changelog](#changelog) + * [Initial Releases v1.0.0](#Initial-Releases-v100) + +--- +--- + +## Changelog + +### Initial Releases v1.0.0 + +1. Initial coding to support **nRF52-based boards, such as AdaFruit Itsy-Bitsy nRF52840, Feather nRF52840 Express, Seeed XIAO nRF52840, Seeed XIAO nRF52840 Sense**, etc. using [`Adafruit nRF52 core`](https://github.com/adafruit/Adafruit_nRF52_Arduino) or [`Seeeduino nRF52 core`](https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino) + + +--- +--- + +## Copyright + +Copyright 2022- Khoi Hoang + + diff --git a/examples/PWM_Basic/PWM_Basic.ino b/examples/PWM_Basic/PWM_Basic.ino new file mode 100644 index 0000000..b481c2c --- /dev/null +++ b/examples/PWM_Basic/PWM_Basic.ino @@ -0,0 +1,74 @@ +/**************************************************************************************************************************** + PWM_Basic.ino + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +#define _PWM_LOGLEVEL_ 4 + +// Select false to use PWM +#define USING_TIMER false //true + +#include "nRF52_PWM.h" + +// OK for Feather_nRF52840_Express (5, 6, 9-13, 14-21/A0-A7, etc.) +// OK for ItsyBitsy_nRF52840_Express (5, 7, 9-13, 14-20/A0-A6, etc.) + +#define pinToUse 5 + +//creates pwm instance +nRF52_PWM* PWM_Instance; + +float frequency = 1000.0f; + +float dutyCycle = 0.0f; + +void setup() +{ + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(500); + + Serial.print(F("\nStarting PWM_Basic using PWM on ")); + Serial.println(BOARD_NAME); + Serial.println(NRF52_PWM_VERSION); + + //assigns PWM frequency of 1.0 KHz and a duty cycle of 0% + PWM_Instance = new nRF52_PWM(pinToUse, frequency, dutyCycle); + + if ( (!PWM_Instance) || !PWM_Instance->isPWMEnabled()) + { + Serial.print(F("Stop here forever")); + + while (true) + delay(10000); + } +} + +void loop() +{ + // You can change frequency here, anytime + frequency = 2000.0f; + //frequency = 20.0f; + dutyCycle = 20.0f; + + PWM_Instance->setPWM(pinToUse, frequency, dutyCycle); + + delay(10000); + + // You can change frequency here, anytime + frequency = 5000.0f; + //frequency = 50.0f; + dutyCycle = 90.0f; + + PWM_Instance->setPWM(pinToUse, frequency, dutyCycle); + + //while (1) + delay(10000); +} diff --git a/examples/PWM_DynamicDutyCycle/PWM_DynamicDutyCycle.ino b/examples/PWM_DynamicDutyCycle/PWM_DynamicDutyCycle.ino new file mode 100644 index 0000000..e627771 --- /dev/null +++ b/examples/PWM_DynamicDutyCycle/PWM_DynamicDutyCycle.ino @@ -0,0 +1,90 @@ +/**************************************************************************************************************************** + PWM_DynamicDutyCycle.ino + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +#define _PWM_LOGLEVEL_ 4 + +// Select false to use PWM +#define USING_TIMER false //true + +#include "nRF52_PWM.h" + +// OK for Feather_nRF52840_Express (5, 6, 9, 10, 14-21/A0-A7, etc.) + +#define pinToUse 6 + +//creates pwm instance +nRF52_PWM* PWM_Instance; + +float frequency; +float dutyCycle; + +char dashLine[] = "====================================================================================="; + +void printPWMInfo(nRF52_PWM* PWM_Instance) +{ + Serial.println(dashLine); + Serial.print("Actual data: pin = "); + Serial.print(PWM_Instance->getPin()); + Serial.print(", PWM DC = "); + Serial.print(PWM_Instance->getActualDutyCycle()); + Serial.print(", PWMPeriod = "); + Serial.print(PWM_Instance->getPWMPeriod()); + Serial.print(", PWM Freq (Hz) = "); + Serial.println(PWM_Instance->getActualFreq(), 4); + Serial.println(dashLine); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(500); + + Serial.print(F("\nStarting PWM_DynamicDutyCycle on ")); + Serial.println(BOARD_NAME); + Serial.println(NRF52_PWM_VERSION); + + frequency = 5000.0f; + + PWM_Instance = new nRF52_PWM(pinToUse, frequency, 0.0f); + + if ( (!PWM_Instance) || !PWM_Instance->isPWMEnabled()) + { + Serial.print(F("Stop here forever")); + + while (true) + delay(10000); + } + + Serial.println(dashLine); +} + +void loop() +{ + dutyCycle = 90.0f; + + Serial.print(F("Change PWM DutyCycle to ")); + Serial.println(dutyCycle); + PWM_Instance->setPWM(pinToUse, frequency, dutyCycle); + + printPWMInfo(PWM_Instance); + + delay(5000); + dutyCycle = 20.0f; + + Serial.print(F("Change PWM DutyCycle to ")); + Serial.println(dutyCycle); + PWM_Instance->setPWM(pinToUse, frequency, dutyCycle); + printPWMInfo(PWM_Instance); + + delay(5000); +} diff --git a/examples/PWM_DynamicDutyCycle_Int/PWM_DynamicDutyCycle_Int.ino b/examples/PWM_DynamicDutyCycle_Int/PWM_DynamicDutyCycle_Int.ino new file mode 100644 index 0000000..1ed0236 --- /dev/null +++ b/examples/PWM_DynamicDutyCycle_Int/PWM_DynamicDutyCycle_Int.ino @@ -0,0 +1,96 @@ +/**************************************************************************************************************************** + PWM_DynamicDutyCycle_Int.ino + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +#define _PWM_LOGLEVEL_ 4 + +// Select false to use PWM +#define USING_TIMER false //true + +#include "nRF52_PWM.h" + +// OK for Feather_nRF52840_Express (5, 6, 9-13, 14-21/A0-A7, etc.) +// OK for ItsyBitsy_nRF52840_Express (5, 7, 9-13, 14-20/A0-A6, etc.) + +#define pinToUse 5 + +//creates pwm instance +nRF52_PWM* PWM_Instance; + +float frequency; +uint16_t dutyCycle; + +char dashLine[] = "====================================================================================="; + +void printPWMInfo(nRF52_PWM* PWM_Instance) +{ + Serial.println(dashLine); + Serial.print("Actual data: pin = "); + Serial.print(PWM_Instance->getPin()); + Serial.print(", PWM DC = "); + Serial.print(PWM_Instance->getActualDutyCycle()); + Serial.print(", PWMPeriod = "); + Serial.print(PWM_Instance->getPWMPeriod()); + Serial.print(", PWM Freq (Hz) = "); + Serial.println(PWM_Instance->getActualFreq(), 4); + Serial.println(dashLine); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(500); + + Serial.print(F("\nStarting PWM_DynamicDutyCycle_Int on ")); + Serial.println(BOARD_NAME); + Serial.println(NRF52_PWM_VERSION); + + frequency = 1000.0f; + + PWM_Instance = new nRF52_PWM(pinToUse, frequency, 0.0f); + + if ( (!PWM_Instance) || !PWM_Instance->isPWMEnabled()) + { + Serial.print(F("Stop here forever")); + + while (true) + delay(10000); + } + + Serial.println(dashLine); +} + +void loop() +{ + frequency = 5000.0f; + + // 50% dutyCycle = (real_dutyCycle * 65536) / 100 + dutyCycle = 32768; + + Serial.print(F("Change PWM DutyCycle to (%) ")); + Serial.println((float) dutyCycle * 100 / 65536); + PWM_Instance->setPWM_Int(pinToUse, frequency, dutyCycle); + + printPWMInfo(PWM_Instance); + + delay(5000); + + // 20% dutyCycle = (real_dutyCycle * 65536) / 100 + dutyCycle = 13107; + + Serial.print(F("Change PWM DutyCycle to (%) ")); + Serial.println((float) dutyCycle * 100 / 65536); + PWM_Instance->setPWM_Int(pinToUse, frequency, dutyCycle); + printPWMInfo(PWM_Instance); + + delay(5000); +} diff --git a/examples/PWM_DynamicFreq/PWM_DynamicFreq.ino b/examples/PWM_DynamicFreq/PWM_DynamicFreq.ino new file mode 100644 index 0000000..cc49a58 --- /dev/null +++ b/examples/PWM_DynamicFreq/PWM_DynamicFreq.ino @@ -0,0 +1,89 @@ +/**************************************************************************************************************************** + PWM_DynamicFreq.ino + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +#define _PWM_LOGLEVEL_ 4 + +// Select false to use PWM +#define USING_TIMER false //true + +#include "nRF52_PWM.h" + +// OK for Feather_nRF52840_Express (5, 6, 9-13, 14-21/A0-A7, etc.) +// OK for ItsyBitsy_nRF52840_Express (5, 7, 9-13, 14-20/A0-A6, etc.) + +#define pinToUse 5 +//creates pwm instance +nRF52_PWM* PWM_Instance; + +float frequency; + +char dashLine[] = "====================================================================================="; + +void printPWMInfo(nRF52_PWM* PWM_Instance) +{ + Serial.println(dashLine); + Serial.print("Actual data: pin = "); + Serial.print(PWM_Instance->getPin()); + Serial.print(", PWM DC = "); + Serial.print(PWM_Instance->getActualDutyCycle()); + Serial.print(", PWMPeriod = "); + Serial.print(PWM_Instance->getPWMPeriod()); + Serial.print(", PWM Freq (Hz) = "); + Serial.println(PWM_Instance->getActualFreq(), 4); + Serial.println(dashLine); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(500); + + Serial.print(F("\nStarting PWM_DynamicFreq on ")); + Serial.println(BOARD_NAME); + Serial.println(NRF52_PWM_VERSION); + + frequency = 10000.0f; + + PWM_Instance = new nRF52_PWM(pinToUse, frequency, 50.0f); + + if ( (!PWM_Instance) || !PWM_Instance->isPWMEnabled()) + { + Serial.print(F("Stop here forever")); + + while (true) + delay(10000); + } + + Serial.println(dashLine); +} + +void loop() +{ + delay(5000); + + frequency = 20000.0f; + + Serial.print(F("Change PWM Freq to ")); + Serial.println(frequency); + PWM_Instance->setPWM(pinToUse, frequency, 50.0f); + printPWMInfo(PWM_Instance); + + delay(5000); + + frequency = 10000.0f; + + Serial.print(F("Change PWM Freq to ")); + Serial.println(frequency); + PWM_Instance->setPWM(pinToUse, frequency, 50.0f); + printPWMInfo(PWM_Instance); +} diff --git a/examples/PWM_Multi/PWM_Multi.ino b/examples/PWM_Multi/PWM_Multi.ino new file mode 100644 index 0000000..64d48db --- /dev/null +++ b/examples/PWM_Multi/PWM_Multi.ino @@ -0,0 +1,102 @@ +/**************************************************************************************************************************** + PWM_Multi.ino + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +#define _PWM_LOGLEVEL_ 4 + +// Select false to use PWM +#define USING_TIMER false //true + +#include "nRF52_PWM.h" + +// OK for Feather_nRF52840_Express (5, 6, 9-13, 14-21/A0-A7, etc.) +// OK for ItsyBitsy_nRF52840_Express (5, 7, 9-13, 14-20/A0-A6, etc.) + +// To select correct pins for different frequencies +uint32_t PWM_Pins[] = { 5, 7, 9, 10 }; +float frequency[] = { 2000.0f, 3000.0f, 4000.0f, 8000.0f }; +float dutyCycle[] = { 10.0f, 30.0f, 50.0f, 90.0f }; + +#define NUM_OF_PINS ( sizeof(PWM_Pins) / sizeof(uint32_t) ) + +nRF52_PWM* PWM_Instance[NUM_OF_PINS]; + +char dashLine[] = "====================================================================================="; + +void printPWMInfo(nRF52_PWM* PWM_Instance) +{ + Serial.println(dashLine); + Serial.print("Actual data: pin = "); + Serial.print(PWM_Instance->getPin()); + Serial.print(", PWM DC = "); + Serial.print(PWM_Instance->getActualDutyCycle()); + Serial.print(", PWMPeriod = "); + Serial.print(PWM_Instance->getPWMPeriod()); + Serial.print(", PWM Freq (Hz) = "); + Serial.println(PWM_Instance->getActualFreq(), 4); + Serial.println(dashLine); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(500); + + Serial.print(F("\nStarting PWM_Multi on ")); + Serial.println(BOARD_NAME); + Serial.println(NRF52_PWM_VERSION); + + for (uint8_t index = 0; index < NUM_OF_PINS; index++) + { + PWM_Instance[index] = new nRF52_PWM(PWM_Pins[index], frequency[index], dutyCycle[index]); + + if (PWM_Instance[index]) + { + PWM_Instance[index]->setPWM(); + } + } + + Serial.println(dashLine); + Serial.println("Index\tPin\tPWM_freq\tDutyCycle\tActual Freq"); + Serial.println(dashLine); + + for (uint8_t index = 0; index < NUM_OF_PINS; index++) + { + if (PWM_Instance[index]) + { + Serial.print(index); + Serial.print("\t"); + Serial.print(PWM_Pins[index]); + Serial.print("\t"); + Serial.print(frequency[index]); + Serial.print("\t\t"); + Serial.print(dutyCycle[index]); + Serial.print("\t\t"); + Serial.println(PWM_Instance[index]->getActualFreq(), 4); + } + else + { + Serial.println(); + } + } + + for (uint8_t index = 0; index < NUM_OF_PINS; index++) + { + printPWMInfo(PWM_Instance[index]); + } +} + +void loop() +{ + //Long delay has no effect on the operation of hardware-based PWM channels + delay(1000000); +} diff --git a/examples/PWM_MultiChannel/PWM_MultiChannel.ino b/examples/PWM_MultiChannel/PWM_MultiChannel.ino new file mode 100644 index 0000000..492a2ba --- /dev/null +++ b/examples/PWM_MultiChannel/PWM_MultiChannel.ino @@ -0,0 +1,107 @@ +/**************************************************************************************************************************** + PWM_MultiChannel.ino + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +#define _PWM_LOGLEVEL_ 4 + +// Select false to use PWM +#define USING_TIMER false //true + +#include "nRF52_PWM.h" + +// OK for Feather_nRF52840_Express (5, 6, 9-13, 14-21/A0-A7, etc.) +// OK for ItsyBitsy_nRF52840_Express (5, 7, 9-13, 14-20/A0-A6, etc.) + +#define pinToUse 5 + +// To select correct pins for different frequencies +uint32_t PWM_Pins[] = { 5, 7, 9, 10 }; +float dutyCycle[] = { 10.0f, 30.0f, 50.0f, 90.0f }; + +#define NUM_OF_PINS ( sizeof(PWM_Pins) / sizeof(uint32_t) ) + +// Must be same frequency for same channel +float frequency = 2000.0f; + +//creates pwm instances +nRF52_PWM* PWM_Instance[NUM_OF_PINS]; + +char dashLine[] = "====================================================================================="; + +void printPWMInfo(nRF52_PWM* PWM_Instance) +{ + Serial.println(dashLine); + Serial.print("Actual data: pin = "); + Serial.print(PWM_Instance->getPin()); + Serial.print(", PWM DC = "); + Serial.print(PWM_Instance->getActualDutyCycle()); + Serial.print(", PWMPeriod = "); + Serial.print(PWM_Instance->getPWMPeriod()); + Serial.print(", PWM Freq (Hz) = "); + Serial.println(PWM_Instance->getActualFreq(), 4); + Serial.println(dashLine); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(500); + + Serial.print(F("\nStarting PWM_MultiChannel on ")); + Serial.println(BOARD_NAME); + Serial.println(NRF52_PWM_VERSION); + + for (uint8_t index = 0; index < NUM_OF_PINS; index++) + { + PWM_Instance[index] = new nRF52_PWM(PWM_Pins[index], frequency, dutyCycle[index]); + + if (PWM_Instance[index]) + { + PWM_Instance[index]->setPWM(); + } + } + + Serial.println(dashLine); + Serial.println("Index\tPin\tPWM_freq\tDutyCycle\tActual Freq"); + Serial.println(dashLine); + + for (uint8_t index = 0; index < NUM_OF_PINS; index++) + { + if (PWM_Instance[index]) + { + Serial.print(index); + Serial.print("\t"); + Serial.print(PWM_Pins[index]); + Serial.print("\t"); + Serial.print(frequency); + Serial.print("\t\t"); + Serial.print(dutyCycle[index]); + Serial.print("\t\t"); + Serial.println(PWM_Instance[index]->getActualFreq(), 4); + } + else + { + Serial.println(); + } + } + + for (uint8_t index = 0; index < NUM_OF_PINS; index++) + { + printPWMInfo(PWM_Instance[index]); + } +} + +void loop() +{ + //Long delay has no effect on the operation of hardware-based PWM channels + delay(1000000); +} diff --git a/examples/PWM_Waveform/PWM_Waveform.ino b/examples/PWM_Waveform/PWM_Waveform.ino new file mode 100644 index 0000000..08e5b09 --- /dev/null +++ b/examples/PWM_Waveform/PWM_Waveform.ino @@ -0,0 +1,164 @@ +/**************************************************************************************************************************** + PWM_Waveform.ino + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license +*****************************************************************************************************************************/ + +#define _PWM_LOGLEVEL_ 3 + +// Select false to use PWM +#define USING_TIMER false //true + +#include "nRF52_PWM.h" + +// OK for Feather_nRF52840_Express (5, 6, 9-13, 14-21/A0-A7, etc.) +// OK for ItsyBitsy_nRF52840_Express (5, 7, 9-13, 14-20/A0-A6, etc.) + +#define pinToUse 5 + +//creates pwm instance +nRF52_PWM* PWM_Instance; + +typedef struct +{ + uint16_t level; +} PWD_Data; + +// Data for 0-100% +PWD_Data PWM_data[] = +{ + { 0 }, + { 5 }, + { 10 }, + { 15 }, + { 20 }, + { 25 }, + { 30 }, + { 35 }, + { 40 }, + { 45 }, + { 50 }, + { 55 }, + { 60 }, + { 65 }, + { 70 }, + { 75 }, + { 80 }, + { 85 }, + { 90 }, + { 95 }, + { 100 }, + { 95 }, + { 90 }, + { 85 }, + { 80 }, + { 75 }, + { 70 }, + { 65 }, + { 60 }, + { 55 }, + { 50 }, + { 45 }, + { 40 }, + { 35 }, + { 30 }, + { 25 }, + { 20 }, + { 15 }, + { 10 }, + { 5 }, + { 0 }, +}; + +#define NUM_PWM_POINTS ( sizeof(PWM_data) / sizeof(PWD_Data) ) + +float frequency = 2000.0f; +float dutyCycle = 0.0f; + +uint8_t channel = 0; + +// You can select any value +PWD_Data PWM_data_idle = PWM_data[0]; + +char dashLine[] = "============================================================================================"; + +void printPWMInfo(nRF52_PWM* PWM_Instance) +{ + Serial.println(dashLine); + Serial.print("Actual data: pin = "); + Serial.print(PWM_Instance->getPin()); + Serial.print(", PWM DutyCycle = "); + Serial.print(PWM_Instance->getActualDutyCycle()); + Serial.print(", PWMPeriod = "); + Serial.print(PWM_Instance->getPWMPeriod()); + Serial.print(", PWM Freq (Hz) = "); + Serial.println(PWM_Instance->getActualFreq(), 4); + Serial.println(dashLine); +} + +void setup() +{ + Serial.begin(115200); + + while (!Serial && millis() < 5000); + + delay(500); + + Serial.print(F("\nStarting PWM_Waveform on ")); + Serial.println(BOARD_NAME); + Serial.println(NRF52_PWM_VERSION); + + // Create a dummy instance + PWM_Instance = new nRF52_PWM(pinToUse, frequency, dutyCycle); + + if ( (PWM_Instance) && PWM_Instance->isPWMEnabled() ) + { + // setPWM_manual(uint8_t pin, uint16_t level) + PWM_Instance->setPWM(pinToUse, frequency, 0); + + printPWMInfo(PWM_Instance); + } + else + { + Serial.print(F("Stop here forever")); + + while (true) + delay(10000); + } +} + +void updateDC() +{ + static uint16_t index = 0; + + // Mapping data to any other frequency from original data 0-100 to actual 16-bit Dutycycle + PWM_Instance->setPWM_manual(pinToUse, ( (uint32_t) PWM_data[index].level * MAX_16BIT ) / 100 ); + + // Use at low freq to check + //printPWMInfo(PWM_Instance); + + index = (index + 1) % NUM_PWM_POINTS; +} + +void check_status() +{ +#define UPDATE_INTERVAL 100L + + static unsigned long update_timeout = UPDATE_INTERVAL; + + // Update DC every UPDATE_INTERVAL (100) milliseconds + if (millis() > update_timeout) + { + updateDC(); + update_timeout = millis() + UPDATE_INTERVAL; + } +} + +void loop() +{ + check_status(); +} diff --git a/keywords.txt b/keywords.txt new file mode 100644 index 0000000..9c1c01f --- /dev/null +++ b/keywords.txt @@ -0,0 +1,43 @@ +####################################### +# Datatypes (KEYWORD1) +####################################### + +nRF52_PWM KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +################################### +# Class nRF52_PWM +################################### + +setPWM_Int KEYWORD2 +setPWM KEYWORD2 +setPWM_Period KEYWORD2 +setPWM_manual KEYWORD2 +getActualDutyCycle KEYWORD2 +getActualFreq KEYWORD2 +getPWMPeriod KEYWORD2 +get_freq_CPU KEYWORD2 +getPin KEYWORD2 +setResolution KEYWORD2 +isPWMEnabled KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +nRF52_PWM_VERSION LITERAL1 +nRF52_PWM_VERSION_MAJOR LITERAL1 +nRF52_PWM_VERSION_MINOR LITERAL1 +nRF52_PWM_VERSION_PATCH LITERAL1 +nRF52_PWM_VERSION_INT LITERAL1 + +INVALID_NRF52_PIN LITERAL1 + +MAX_COUNT_16BIT LITERAL1 +MAX_16BIT LITERAL1 +MAX_15BIT LITERAL1 + +_PWM_LOGLEVEL_ LITERAL1 diff --git a/library.json b/library.json new file mode 100644 index 0000000..d834098 --- /dev/null +++ b/library.json @@ -0,0 +1,30 @@ +{ + "name": "nRF52_PWM", + "version": "1.0.0", + "keywords": "timing, device, control, timer, pwm, pwm-driver, pwm-frequency, dynamic-pwm, duty-cycle, hardware-based-pwm, multi-channel-pwm, waveform-generator, mission-critical, accuracy, non-blocking, nrf52, nrf52840, nrf52832, itsy-bitsy-nrf52840, feather-nrf52840", + "description": "This library enables you to use Hardware-based PWM channels on nRF52-based boards to create and output PWM to pins. The most important feature is they're purely hardware-based PWM channels, supporting very high PWM frequencies. Therefore, their executions are not blocked by bad-behaving functions or tasks. This important feature is absolutely necessary for mission-critical tasks. These hardware-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 ISR, millis() or micros(). That's necessary if you need to control devices requiring high precision. New efficient setPWM_manual function to facilitate waveform creation using PWM", + "authors": + { + "name": "Khoi Hoang", + "url": "https://github.com/khoih-prog", + "maintainer": true + }, + "repository": + { + "type": "git", + "url": "https://github.com/khoih-prog/nRF52_PWM" + }, + "homepage": "https://github.com/khoih-prog/nRF52_PWM", + "export": { + "exclude": [ + "linux", + "extras", + "tests" + ] + }, + "license": "MIT", + "frameworks": "*", + "platforms": ["nordicnrf52", "nrf52"], + "examples": "examples/*/*/*.ino", + "headers": ["nRF52_PWM.h"] +} diff --git a/library.properties b/library.properties new file mode 100644 index 0000000..6eb8671 --- /dev/null +++ b/library.properties @@ -0,0 +1,12 @@ +name=nRF52_PWM +version=1.0.0 +author=Khoi Hoang +maintainer=Khoi Hoang +sentence=This library enables you to use Hardware-based PWM channels on nRF52-based boards to create and output PWM to pins. +paragraph= The most important feature is they're purely hardware-based PWM channels, supporting very high PWM frequencies. Therefore, their executions are not blocked by bad-behaving functions or tasks. This important feature is absolutely necessary for mission-critical tasks. These hardware-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 ISR, millis() or micros(). That's necessary if you need to control devices requiring high precision. New efficient setPWM_manual function to facilitate waveform creation using PWM +category=Device Control +url=https://github.com/khoih-prog/nRF52_PWM +architectures=nordicnrf52,nrf52 +repository=https://github.com/khoih-prog/nRF52_PWM +license=MIT +includes=nRF52_PWM.h diff --git a/platformio/platformio.ini b/platformio/platformio.ini new file mode 100644 index 0000000..f975754 --- /dev/null +++ b/platformio/platformio.ini @@ -0,0 +1,341 @@ +;PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + + +[platformio] +; ============================================================ +; chose environment: +; ESP8266 +; ESP32 +; SAMD +; NRF52 +; STM32 +; ============================================================ +;default_envs = ESP8266 +;default_envs = ESP32 +;default_envs = SAMD +default_envs = NRF52 +;default_envs = STM32 + +[env] +; ============================================================ +; Serial configuration +; choose upload speed, serial-monitor speed +; ============================================================ +upload_speed = 921600 +;upload_port = COM11 +;monitor_speed = 9600 +;monitor_port = COM11 + +; Checks for the compatibility with frameworks and dev/platforms +lib_compat_mode = strict +lib_ldf_mode = chain+ +;lib_ldf_mode = deep+ + +lib_deps = + +build_flags = +; set your debug output (default=Serial) +; -D DEBUG_ESP_PORT=Serial +; comment the folowing line to enable WiFi debugging +; -D NDEBUG + +[env:ESP8266] +platform = espressif8266 +framework = arduino +; ============================================================ +; Board configuration +; choose your board by uncommenting one of the following lines +; ============================================================ +;board = gen4iod +;board = huzzah +;board = oak +;board = esp_wroom_02 +;board = espduino +;board = espectro +;board = espino +;board = espresso_lite_v1 +;board = espresso_lite_v2 +;board = esp12e +;board = esp01_1m +;board = esp01 +;board = esp07 +;board = esp8285 +;board = heltec_wifi_kit_8 +;board = inventone +;board = nodemcu +board = nodemcuv2 +;board = modwifi +;board = phoenix_v1 +;board = phoenix_v2 +;board = sparkfunBlynk +;board = thing +;board = thingdev +;board = esp210 +;board = espinotee +;board = d1 +;board = d1_mini +;board = d1_mini_lite +;board = d1_mini_pro +;board = wifi_slot +;board = wifiduino +;board = wifinfo +;board = wio_link +;board = wio_node +;board = xinabox_cw01 +;board = esp32doit-devkit-v1 + +[env:ESP32] +platform = espressif32 +framework = arduino +; ============================================================ +; Board configuration +; choose your board by uncommenting one of the following lines +; ============================================================ +;board = esp32cam +;board = alksesp32 +;board = featheresp32 +;board = espea32 +;board = bpi-bit +;board = d-duino-32 +board = esp32doit-devkit-v1 +;board = pocket_32 +;board = fm-devkit +;board = pico32 +;board = esp32-evb +;board = esp32-gateway +;board = esp32-pro +;board = esp32-poe +;board = oroca_edubot +;board = onehorse32dev +;board = lopy +;board = lopy4 +;board = wesp32 +;board = esp32thing +;board = sparkfun_lora_gateway_1-channel +;board = ttgo-lora32-v1 +;board = ttgo-t-beam +;board = turta_iot_node +;board = lolin_d32 +;board = lolin_d32_pro +;board = lolin32 +;board = wemosbat +;board = widora-air +;board = xinabox_cw02 +;board = iotbusio +;board = iotbusproteus +;board = nina_w10 + +[env:SAMD] +platform = atmelsam +framework = arduino +; ============================================================ +; Choose your board by uncommenting one of the following lines +; ============================================================ +; ============================================================ +; Board configuration Adafruit SAMD +; ============================================================ + +;board = adafruit_feather_m0 +;board = adafruit_feather_m0_express +;board = adafruit_metro_m0 +;board = adafruit_circuitplayground_m0 +;board = adafruit_gemma_m0 +;board = adafruit_trinket_m0 +;board = adafruit_itsybitsy_m0 +;board = adafruit_pirkey +;board = adafruit_hallowing +;board = adafruit_crickit_m0 +;board = adafruit_metro_m4 +;board = adafruit_grandcentral_m4 +board = adafruit_itsybitsy_m4 +;board = adafruit_feather_m4 +;board = adafruit_trellis_m4 +;board = adafruit_pyportal_m4 +;board = adafruit_pyportal_m4_titano +;board = adafruit_pybadge_m4 +;board = adafruit_metro_m4_airliftlite +;board = adafruit_pygamer_m4 +;board = adafruit_pygamer_advance_m4 +;board = adafruit_pybadge_airlift_m4 +;board = adafruit_monster_m4sk +;board = adafruit_hallowing_m4 + +; ============================================================ +; Board configuration Arduino SAMD and SAM +; ============================================================ + +;board = arduino_zero_edbg +;board = arduino_zero_native +;board = mkr1000 +;board = mkrzero +;board = mkrwifi1010 +;board = nano_33_iot +;board = mkrfox1200 +;board = mkrwan1300 +;board = mkrwan1310 +;board = mkrgsm1400 +;board = mkrnb1500 +;board = mkrvidor4000 +;board = adafruit_circuitplayground_m0 +;board = mzero_pro_bl_dbg +;board = mzero_pro_bl +;board = mzero_bl +;board = tian +;board = tian_cons +;board = arduino_due_x_dbg +;board = arduino_due_x + +; ============================================================ +; Board configuration Seeeduino SAMD +; ============================================================ + +;board = seeed_wio_terminal +;board = Seeed_femto_m0 +;board = seeed_XIAO_m0 +;board = Wio_Lite_MG126 +;board = WioGPS +;board = zero +;board = rolawan +;board = seeed_grove_ui_wireless + + +[env:NRF52] +platform = nordicnrf52 +framework = arduino +; ============================================================ +; Board configuration Adafruit nRF52 +; choose your board by uncommenting one of the following lines +; ============================================================ +;board = feather52832 +board = feather52840 +;board = feather52840sense +;board = itsybitsy52840 +;board = cplaynrf52840 +;board = cluenrf52840 +;board = metro52840 +;board = pca10056 +;board = particle_xenon +;board = mdbt50qrx +;board = ninab302 +;board = ninab112 + +[env:STM32] +platform = ststm32 +framework = arduino + +; ============================================================ +; Choose your board by uncommenting one of the following lines +; ============================================================ + +; ============================================================ +; Board configuration Nucleo-144 +; ============================================================ + +;board = nucleo_f207zg +;board = nucleo_f429zi +;board = nucleo_f746zg +;board = nucleo_f756zg +;board = nucleo_f767zi +;board = nucleo_h743zi +;board = nucleo_l496zg +;board = nucleo_l496zg-p +;board = nucleo_l4r5zi +;board = nucleo_l4r5zi-p + +; ============================================================ +; Board configuration Nucleo-64 +; ============================================================ + +;board = nucleo_f030r8 +;board = nucleo_f072rb + +;board = nucleo_f091rc +;board = nucleo_f103rb +;board = nucleo_f302r8 +;board = nucleo_f303re +;board = nucleo_f401re +;board = nucleo_f411re +;board = nucleo_f446re +;board = nucleo_g071rb +;board = nucleo_g431rb +;board = nucleo_g474re +;board = nucleo_l053r8 +;board = nucleo_l073rz +;board = nucleo_l152re +;board = nucleo_l433rc_p +;board = nucleo_l452re +;board = nucleo_l452re-p +;board = nucleo_l476rg +;board = pnucleo_wb55rg + +; ============================================================ +; Board configuration Nucleo-32 +; ============================================================ + +;board = nucleo_f031k6 +;board = nucleo_l031k6 +;board = nucleo_l412kb +;board = nucleo_l432lc +;board = nucleo_f303k8 +;board = nucleo_g431kb + +; ============================================================ +; Board configuration Discovery Boards +; ============================================================ + +;board = disco_f030r8 +;board = disco_f072rb +;board = disco_f030r8 +;board = disco_f100rb +;board = disco_f407vg +;board = disco_f413zh +;board = disco_f746ng +;board = disco_g0316 +;board = disco_l475vg_iot +;board = disco_f072cz-lrwan1 + +; ============================================================ +; Board configuration STM32MP1 Boards +; ============================================================ + +;board = stm32mp157a-dk1 +;board = stm32mp157c-dk2 + +; ============================================================ +; Board configuration Generic Boards +; ============================================================ + +;board = bluepill_f103c6 +;board = bluepill_f103c8 +;board = blackpill_f103c8 +;board = stm32f103cx +;board = stm32f103rx +;board = stm32f103tx +;board = stm32f103vx +;board = stm32f103zx +;board = stm32f103zet6 +;board = maplemini_f103cb +;board = blackpill_f303cc +;board = black_f407ve +;board = black_f407vg +;board = black_f407ze +;board = black_f407zg +;board = blue_f407ve_mini +;board = blackpill_f401cc +;board = blackpill_f411ce +;board = coreboard_f401rc +;board = feather_f405 + +; ============================================================ +; Board configuration Many more Boards to be filled +; ============================================================ + + diff --git a/src/PWM_Generic_Debug.h b/src/PWM_Generic_Debug.h new file mode 100644 index 0000000..d8cb237 --- /dev/null +++ b/src/PWM_Generic_Debug.h @@ -0,0 +1,99 @@ +/**************************************************************************************************************************** + PWM_Generic_Debug.h + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license + + Version: 1.0.0 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 05/11/2022 Initial coding for nRF52-based boards + *****************************************************************************************************************************/ + +#pragma once + +#ifndef PWM_GENERIC_DEBUG_H +#define PWM_GENERIC_DEBUG_H + +#ifdef PWM_GENERIC_DEBUG_PORT + #define PWM_DBG_PORT PWM_GENERIC_DEBUG_PORT +#else + #define PWM_DBG_PORT Serial +#endif + +// Change _PWM_LOGLEVEL_ to set tracing and logging verbosity +// 0: DISABLED: no logging +// 1: ERROR: errors +// 2: WARN: errors and warnings +// 3: INFO: errors, warnings and informational (default) +// 4: DEBUG: errors, warnings, informational and debug + +#ifndef _PWM_LOGLEVEL_ + #define _PWM_LOGLEVEL_ 1 +#endif + +/////////////////////////////////////// + +const char PWM_MARK[] = "[PWM] "; +const char PWM_SPACE[] = " "; + +#define PWM_PRINT PWM_DBG_PORT.print +#define PWM_PRINTLN PWM_DBG_PORT.println + +#define PWM_PRINT_MARK PWM_PRINT(PWM_MARK) +#define PWM_PRINT_SP PWM_PRINT(PWM_SPACE) +#define PWM_PRINT_LINE PWM_PRINT(PWM_LINE) + +/////////////////////////////////////// + +#define PWM_LOGERROR0(x) if(_PWM_LOGLEVEL_>0) { PWM_PRINT(x); } +#define PWM_LOGERROR(x) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINTLN(x); } +#define PWM_LOGERROR1(x,y) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINTLN(y); } +#define PWM_HEXLOGERROR1(x,y) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT(" 0x"); PWM_PRINTLN(y, HEX); } +#define PWM_LOGERROR2(x,y,z) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINTLN(z); } +#define PWM_LOGERROR3(x,y,z,w) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINTLN(w); } +#define PWM_LOGERROR5(x,y,z,w,xx,yy) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINT(w); PWM_PRINT_SP; PWM_PRINT(xx); PWM_PRINT_SP; PWM_PRINTLN(yy); } +#define PWM_LOGERROR7(x,y,z,w,xx,yy,zz,ww) if(_PWM_LOGLEVEL_>0) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINT(w); PWM_PRINT_SP; PWM_PRINT(xx); PWM_PRINT_SP; PWM_PRINT(yy); PWM_PRINT_SP; PWM_PRINT(zz); PWM_PRINT_SP; PWM_PRINTLN(ww); } + +////////////////////////////////////////// + +#define PWM_LOGWARN0(x) if(_PWM_LOGLEVEL_>1) { PWM_PRINT(x); } +#define PWM_LOGWARN(x) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINTLN(x); } +#define PWM_LOGWARN1(x,y) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINTLN(y); } +#define PWM_HEXLOGWARN1(x,y) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT(" 0x"); PWM_PRINTLN(y, HEX); } +#define PWM_LOGWARN2(x,y,z) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINTLN(z); } +#define PWM_LOGWARN3(x,y,z,w) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINTLN(w); } +#define PWM_LOGWARN5(x,y,z,w,xx,yy) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINT(w); PWM_PRINT_SP; PWM_PRINT(xx); PWM_PRINT_SP; PWM_PRINTLN(yy); } +#define PWM_LOGWARN7(x,y,z,w,xx,yy,zz,ww) if(_PWM_LOGLEVEL_>1) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINT(w); PWM_PRINT_SP; PWM_PRINT(xx); PWM_PRINT_SP; PWM_PRINT(yy); PWM_PRINT_SP; PWM_PRINT(zz); PWM_PRINT_SP; PWM_PRINTLN(ww); } + +////////////////////////////////////////// + +#define PWM_LOGINFO0(x) if(_PWM_LOGLEVEL_>2) { PWM_PRINT(x); } +#define PWM_LOGINFO(x) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINTLN(x); } +#define PWM_LOGINFO1(x,y) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINTLN(y); } +#define PWM_HEXLOGINFO1(x,y) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT(" 0x"); PWM_PRINTLN(y, HEX); } +#define PWM_LOGINFO2(x,y,z) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINTLN(z); } +#define PWM_LOGINFO3(x,y,z,w) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINTLN(w); } +#define PWM_LOGINFO5(x,y,z,w,xx,yy) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINT(w); PWM_PRINT_SP; PWM_PRINT(xx); PWM_PRINT_SP; PWM_PRINTLN(yy); } +#define PWM_LOGINFO7(x,y,z,w,xx,yy,zz,ww) if(_PWM_LOGLEVEL_>2) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINT(w); PWM_PRINT_SP; PWM_PRINT(xx); PWM_PRINT_SP; PWM_PRINT(yy); PWM_PRINT_SP; PWM_PRINT(zz); PWM_PRINT_SP; PWM_PRINTLN(ww); } + +////////////////////////////////////////// + +#define PWM_LOGDEBUG0(x) if(_PWM_LOGLEVEL_>3) { PWM_PRINT(x); } +#define PWM_LOGDEBUG(x) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINTLN(x); } +#define PWM_LOGDEBUG1(x,y) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINTLN(y); } +#define PWM_HEXLOGDEBUG1(x,y) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT(" 0x"); PWM_PRINTLN(y, HEX); } +#define PWM_LOGDEBUG2(x,y,z) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINTLN(z); } +#define PWM_LOGDEBUG3(x,y,z,w) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINTLN(w); } +#define PWM_LOGDEBUG5(x,y,z,w,xx,yy) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINT(w); PWM_PRINT_SP; PWM_PRINT(xx); PWM_PRINT_SP; PWM_PRINTLN(yy); } +#define PWM_LOGDEBUG7(x,y,z,w,xx,yy,zz,ww) if(_PWM_LOGLEVEL_>3) { PWM_PRINT_MARK; PWM_PRINT(x); PWM_PRINT_SP; PWM_PRINT(y); PWM_PRINT_SP; PWM_PRINT(z); PWM_PRINT_SP; PWM_PRINT(w); PWM_PRINT_SP; PWM_PRINT(xx); PWM_PRINT_SP; PWM_PRINT(yy); PWM_PRINT_SP; PWM_PRINT(zz); PWM_PRINT_SP; PWM_PRINTLN(ww); } + +////////////////////////////////////////// + +/////////////////////////////////////// + +#endif //PWM_GENERIC_DEBUG_H diff --git a/src/nRF52_PWM.h b/src/nRF52_PWM.h new file mode 100644 index 0000000..dd90bf4 --- /dev/null +++ b/src/nRF52_PWM.h @@ -0,0 +1,652 @@ +/**************************************************************************************************************************** + nRF52_PWM.h + + For nRF52-based boards usinghardware-based PWM with Adafruit_nRF52_Arduino core + Written by Khoi Hoang + + Built by Khoi Hoang https://github.com/khoih-prog/nRF52_PWM + Licensed under MIT license + + Version: 1.0.0 + + Version Modified By Date Comments + ------- ----------- ---------- ----------- + 1.0.0 K Hoang 05/11/2022 Initial coding for nRF52-based boards +*****************************************************************************************************************************/ + +#pragma once + +#ifndef NRF52_PWM_H +#define NRF52_PWM_H + +//////////////////////////////////////// + +#if !(defined(NRF52840_FEATHER) || defined(NRF52832_FEATHER) || defined(NRF52_SERIES) || defined(ARDUINO_NRF52_ADAFRUIT) || \ + defined(NRF52840_FEATHER_SENSE) || defined(NRF52840_ITSYBITSY) || defined(NRF52840_CIRCUITPLAY) || \ + defined(NRF52840_CLUE) || defined(NRF52840_METRO) || defined(NRF52840_PCA10056) || defined(PARTICLE_XENON) || \ + defined(NRF52840_LED_GLASSES) || defined(MDBT50Q_RX) || defined(NINA_B302_ublox) || defined(NINA_B112_ublox) || \ + defined(ARDUINO_Seeed_XIAO_nRF52840) || defined(ARDUINO_Seeed_XIAO_nRF52840_Sense) ) +#error This code is designed to run on Adafruit or Seeed nRF52 platform! Please check your Tools->Board setting. +#endif + +//////////////////////////////////////// + +#if !defined(BOARD_NAME) + #if defined(ARDUINO_Seeed_XIAO_nRF52840) + #define BOARD_NAME "Seeed_XIAO_nRF52840" + #elif defined(ARDUINO_Seeed_XIAO_nRF52840_Sense) + #define BOARD_NAME "Seeed_XIAO_nRF52840_Sense" + #endif +#endif + +//////////////////////////////////////// + +#if (_PWM_LOGLEVEL_ > 3) + #warning Using nRF52 Hardware PWM +#endif + +//////////////////////////////////////// + +#ifndef NRF52_PWM_VERSION + #define NRF52_PWM_VERSION "nRF52_PWM v1.0.0" + + #define NRF52_PWM_VERSION_MAJOR 1 + #define NRF52_PWM_VERSION_MINOR 0 + #define NRF52_PWM_VERSION_PATCH 0 + + #define NRF52_PWM_VERSION_INT 1000000 +#endif + +//////////////////////////////////////// + +#include "Arduino.h" + +#include + +#include "PWM_Generic_Debug.h" + +//////////////////////////////////////// + +/************************************* + + // From cores/nRF5/HardwarePWM.h + #ifdef NRF52840_XXAA + #define HWPWM_MODULE_NUM 4 + #else + #define HWPWM_MODULE_NUM 3 + #endif + + //////////////////////////// + + // From cores/nRF5/nordic/nrfx/mdk/nrf52840.h + + typedef struct + { + __IOM uint32_t PTR; //!< (@ 0x00000000) Description cluster: Beginning address in RAM of this sequence + __IOM uint32_t CNT; //!< (@ 0x00000004) Description cluster: Number of values (duty cycles) in this sequence + __IOM uint32_t REFRESH; //!< (@ 0x00000008) Description cluster: Number of additional PWM + periods between samples loaded into compare register + __IOM uint32_t ENDDELAY; //!< (@ 0x0000000C) Description cluster: Time added after the sequence + __IM uint32_t RESERVED[4]; + } PWM_SEQ_Type; //!< Size = 32 (0x20) + + //////////////////////////// + + typedef struct //!< (@ 0x4001C000) PWM0 Structure + { + __IM uint32_t RESERVED; + __OM uint32_t TASKS_STOP; //!< (@ 0x00000004) Stops PWM pulse generation on all channels at + the end of current PWM period, and stops + sequence playback + __OM uint32_t TASKS_SEQSTART[2]; //!< (@ 0x00000008) Description collection: Loads the first PWM value + on all enabled channels from sequence n, + and starts playing that sequence at the + rate defined in SEQ[n]REFRESH and/or DECODER.MODE. + Causes PWM generation to start if not running. + __OM uint32_t TASKS_NEXTSTEP; //!< (@ 0x00000010) Steps by one value in the current sequence on + all enabled channels if DECODER.MODE=NextStep. + Does not cause PWM generation to start if + not running. + __IM uint32_t RESERVED1[60]; + __IOM uint32_t EVENTS_STOPPED; //!< (@ 0x00000104) Response to STOP task, emitted when PWM pulses + are no longer generated + __IOM uint32_t EVENTS_SEQSTARTED[2]; //!< (@ 0x00000108) Description collection: First PWM period started + on sequence n + __IOM uint32_t EVENTS_SEQEND[2]; //!< (@ 0x00000110) Description collection: Emitted at end of every + sequence n, when last value from RAM has + been applied to wave counter + __IOM uint32_t EVENTS_PWMPERIODEND; //!< (@ 0x00000118) Emitted at the end of each PWM period + __IOM uint32_t EVENTS_LOOPSDONE; //!< (@ 0x0000011C) Concatenated sequences have been played the amount + of times defined in LOOP.CNT + __IM uint32_t RESERVED2[56]; + __IOM uint32_t SHORTS; //!< (@ 0x00000200) Shortcuts between local events and tasks + __IM uint32_t RESERVED3[63]; + __IOM uint32_t INTEN; //!< (@ 0x00000300) Enable or disable interrupt + __IOM uint32_t INTENSET; //!< (@ 0x00000304) Enable interrupt + __IOM uint32_t INTENCLR; //!< (@ 0x00000308) Disable interrupt + __IM uint32_t RESERVED4[125]; + __IOM uint32_t ENABLE; //!< (@ 0x00000500) PWM module enable register + __IOM uint32_t MODE; //!< (@ 0x00000504) Selects operating mode of the wave counter + __IOM uint32_t COUNTERTOP; //!< (@ 0x00000508) Value up to which the pulse generator counter counts + __IOM uint32_t PRESCALER; //!< (@ 0x0000050C) Configuration for PWM_CLK + __IOM uint32_t DECODER; //!< (@ 0x00000510) Configuration of the decoder + __IOM uint32_t LOOP; //!< (@ 0x00000514) Number of playbacks of a loop + __IM uint32_t RESERVED5[2]; + __IOM PWM_SEQ_Type SEQ[2]; //!< (@ 0x00000520) Unspecified + __IOM PWM_PSEL_Type PSEL; //!< (@ 0x00000560) Unspecified + } NRF_PWM_Type; + + //////////////// + + // From cores/nRF5/nordic/nrfx/mdk/nrf52840_bitfields.h + + // Bit 0 : Selects up mode or up-and-down mode for the counter + #define PWM_MODE_UPDOWN_Pos (0UL) //!< Position of UPDOWN field. + #define PWM_MODE_UPDOWN_Msk (0x1UL << PWM_MODE_UPDOWN_Pos) //!< Bit mask of UPDOWN field. + #define PWM_MODE_UPDOWN_Up (0UL) //!< Up counter, edge-aligned PWM duty cycle + #define PWM_MODE_UPDOWN_UpAndDown (1UL) //!< Up and down counter, center-aligned PWM duty cycle + + // Register: PWM_COUNTERTOP + // Description: Value up to which the pulse generator counter counts + + // Bits 14..0 : Value up to which the pulse generator counter counts. + // This register is ignored when DECODER.MODE=WaveForm and only values from RAM are used. + #define PWM_COUNTERTOP_COUNTERTOP_Pos (0UL) //!< Position of COUNTERTOP field. + #define PWM_COUNTERTOP_COUNTERTOP_Msk (0x7FFFUL << PWM_COUNTERTOP_COUNTERTOP_Pos) //!< Bit mask of COUNTERTOP field. + + // Register: PWM_PRESCALER + // Description: Configuration for PWM_CLK + + // Bits 2..0 : Prescaler of PWM_CLK + #define PWM_PRESCALER_PRESCALER_Pos (0UL) //!< Position of PRESCALER field. + #define PWM_PRESCALER_PRESCALER_Msk (0x7UL << PWM_PRESCALER_PRESCALER_Pos) //!< Bit mask of PRESCALER field. + #define PWM_PRESCALER_PRESCALER_DIV_1 (0UL) //!< Divide by 1 (16 MHz) + #define PWM_PRESCALER_PRESCALER_DIV_2 (1UL) //!< Divide by 2 (8 MHz) + #define PWM_PRESCALER_PRESCALER_DIV_4 (2UL) //!< Divide by 4 (4 MHz) + #define PWM_PRESCALER_PRESCALER_DIV_8 (3UL) //!< Divide by 8 (2 MHz) + #define PWM_PRESCALER_PRESCALER_DIV_16 (4UL) //!< Divide by 16 (1 MHz) + #define PWM_PRESCALER_PRESCALER_DIV_32 (5UL) //!< Divide by 32 (500 kHz) + #define PWM_PRESCALER_PRESCALER_DIV_64 (6UL) //!< Divide by 64 (250 kHz) + #define PWM_PRESCALER_PRESCALER_DIV_128 (7UL) //!< Divide by 128 (125 kHz) + + // Register: PWM_DECODER + // Description: Configuration of the decoder + + // Bit 8 : Selects source for advancing the active sequence + #define PWM_DECODER_MODE_Pos (8UL) //!< Position of MODE field. + #define PWM_DECODER_MODE_Msk (0x1UL << PWM_DECODER_MODE_Pos) //!< Bit mask of MODE field. + #define PWM_DECODER_MODE_RefreshCount (0UL) //!< SEQ[n].REFRESH is used to determine loading internal compare registers + #define PWM_DECODER_MODE_NextStep (1UL) //!< NEXTSTEP task causes a new value to be loaded to internal compare registers + + // Bits 1..0 : How a sequence is read from RAM and spread to the compare register + #define PWM_DECODER_LOAD_Pos (0UL) //!< Position of LOAD field. + #define PWM_DECODER_LOAD_Msk (0x3UL << PWM_DECODER_LOAD_Pos) //!< Bit mask of LOAD field. + #define PWM_DECODER_LOAD_Common (0UL) //!< 1st half word (16-bit) used in all PWM channels 0..3 + #define PWM_DECODER_LOAD_Grouped (1UL) //!< 1st half word (16-bit) used in channel 0..1; 2nd word in channel 2..3 + #define PWM_DECODER_LOAD_Individual (2UL) //!< 1st half word (16-bit) in ch.0; 2nd in ch.1; ...; 4th in ch.3 + #define PWM_DECODER_LOAD_WaveForm (3UL) //!< 1st half word (16-bit) in ch.0; 2nd in ch.1; ...; 4th in COUNTERTOP + + // Register: PWM_LOOP + // Description: Number of playbacks of a loop + + // Bits 15..0 : Number of playbacks of pattern cycles + #define PWM_LOOP_CNT_Pos (0UL) //!< Position of CNT field. + #define PWM_LOOP_CNT_Msk (0xFFFFUL << PWM_LOOP_CNT_Pos) //!< Bit mask of CNT field. + #define PWM_LOOP_CNT_Disabled (0UL) //!< Looping disabled (stop at the end of the sequence) + + // Register: PWM_SEQ_PTR + // Description: Description cluster: Beginning address in RAM of this sequence + + // Bits 31..0 : Beginning address in RAM of this sequence + #define PWM_SEQ_PTR_PTR_Pos (0UL) //!< Position of PTR field. + #define PWM_SEQ_PTR_PTR_Msk (0xFFFFFFFFUL << PWM_SEQ_PTR_PTR_Pos) //!< Bit mask of PTR field. + + // Register: PWM_SEQ_CNT + // Description: Description cluster: Number of values (duty cycles) in this sequence + + // Bits 14..0 : Number of values (duty cycles) in this sequence + #define PWM_SEQ_CNT_CNT_Pos (0UL) //!< Position of CNT field. + #define PWM_SEQ_CNT_CNT_Msk (0x7FFFUL << PWM_SEQ_CNT_CNT_Pos) //!< Bit mask of CNT field. + #define PWM_SEQ_CNT_CNT_Disabled (0UL) //!< Sequence is disabled, and shall not be started as it is empty + + // Register: PWM_SEQ_REFRESH + // Description: Description cluster: Number of additional PWM periods between samples loaded into compare register + + // Bits 23..0 : Number of additional PWM periods between samples loaded into compare register (load every REFRESH.CNT+1 PWM periods) + #define PWM_SEQ_REFRESH_CNT_Pos (0UL) //!< Position of CNT field. + #define PWM_SEQ_REFRESH_CNT_Msk (0xFFFFFFUL << PWM_SEQ_REFRESH_CNT_Pos) //!< Bit mask of CNT field. + #define PWM_SEQ_REFRESH_CNT_Continuous (0UL) //!< Update every PWM period + + // Register: PWM_SEQ_ENDDELAY + // Description: Description cluster: Time added after the sequence + + // Bits 23..0 : Time added after the sequence in PWM periods + #define PWM_SEQ_ENDDELAY_CNT_Pos (0UL) //!< Position of CNT field. + #define PWM_SEQ_ENDDELAY_CNT_Msk (0xFFFFFFUL << PWM_SEQ_ENDDELAY_CNT_Pos) //!< Bit mask of CNT field. + + // Register: PWM_PSEL_OUT + // Description: Description collection: Output pin select for PWM channel n + + // Bit 31 : Connection + #define PWM_PSEL_OUT_CONNECT_Pos (31UL) //!< Position of CONNECT field. + #define PWM_PSEL_OUT_CONNECT_Msk (0x1UL << PWM_PSEL_OUT_CONNECT_Pos) //!< Bit mask of CONNECT field. + #define PWM_PSEL_OUT_CONNECT_Connected (0UL) //!< Connect + #define PWM_PSEL_OUT_CONNECT_Disconnected (1UL) //!< Disconnect + + // Bit 5 : Port number + #define PWM_PSEL_OUT_PORT_Pos (5UL) //!< Position of PORT field. + #define PWM_PSEL_OUT_PORT_Msk (0x1UL << PWM_PSEL_OUT_PORT_Pos) //!< Bit mask of PORT field. + + // Bits 4..0 : Pin number + #define PWM_PSEL_OUT_PIN_Pos (0UL) //!< Position of PIN field. + #define PWM_PSEL_OUT_PIN_Msk (0x1FUL << PWM_PSEL_OUT_PIN_Pos) //!< Bit mask of PIN field. + + /////////////////////////// + + // Get the mapped channel of a pin + int pin2channel(uint8_t pin) const + { + pin = g_ADigitalPinMap[pin]; + + for (int i = 0; i < MAX_CHANNELS; i++) + { + if ( _pwm->PSEL.OUT[i] == pin ) + return i; + } + + return (-1); + } + + // Check if pin is controlled by PWM + bool checkPin(uint8_t pin) const + { + return pin2channel(pin) >= 0; + } + +*************************************/ + +//////////////////////////////////////// + +#define INVALID_NRF52_PIN 255 + +//////////////////////////////////////// + +#define MAX_COUNT_16BIT 65536UL + +#define MAX_16BIT 65535UL +#define MAX_15BIT 32768UL + +//////////////////////////////////////// +//////////////////////////////////////// + +class nRF52_PWM +{ + public: + + // dutycycle = 0.0f - 100.0f => 0-65535 + nRF52_PWM(const uint32_t& pin, const float& frequency, const float& dutycycle) + { + _dutycycle = round(map(dutycycle, 0, 100.0f, 0, MAX_COUNT_16BIT)); + + // create new unique token based on micros() + NRF52_PWM_TOKEN = (uint32_t) micros(); + + PWM_HEXLOGDEBUG1("nRF52_PWM: NRF52_PWM_TOKEN =", NRF52_PWM_TOKEN); + + // input DC is 16-bit + _PWMEnabled = setupPWM(pin, (uint32_t) frequency, _dutycycle); + } + + /////////////////////////////////////////// + + ~nRF52_PWM() + { + // Release ownership. + // TODO: how to differentiate we're true owner => using unique token with micros() + for (int i = 0; i < HWPWM_MODULE_NUM; i++) + { + if (HwPWMx[i]->takeOwnership(NRF52_PWM_TOKEN)) + { + HwPWMx[i]->releaseOwnership(NRF52_PWM_TOKEN); + + return; + } + } + } + + /////////////////////////////////////////// + /////////////////////////////////////////// + + private: + + /////////////////////////////////////////// + + uint32_t calcPrescaler(const float& frequency) + { + uint32_t period = 1000000 / frequency; + + // PWM clock is 16MHz + uint32_t countTOP = 16000000 / frequency; + + //Prescaller : 1, 2, 4, 8, 16, 32, 64, 128 for min 125KHz + if ( countTOP < MAX_15BIT ) + { + // Set prescaler to 1 for 16,000KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_1; + _prescaler = 1; + } + else if ( countTOP < MAX_15BIT * 2 ) + { + // Set prescaler to 2 for 8,000KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_2; + _prescaler = 2; + } + else if ( countTOP < MAX_15BIT * 4 ) + { + // Set prescaler to 4 for 4,000KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_4; + _prescaler = 4; + } + else if ( countTOP < MAX_15BIT * 8 ) + { + // Set prescaler to 8 for 2,000KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_8; + _prescaler = 8; + } + else if ( countTOP < MAX_15BIT * 16 ) + { + // Set prescaler to 16 for 1,000KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_16; + _prescaler = 16; + } + else if ( countTOP < MAX_15BIT * 32 ) + { + // Set prescaler to 32 for 500KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_32; + _prescaler = 32; + } + else if ( countTOP < MAX_15BIT * 64 ) + { + // Set prescaler to 64 for 250KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_64; + _prescaler = 64; + } + else if ( countTOP < MAX_15BIT * 128 ) + { + // Set prescaler to 128 for 250KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_128; + _prescaler = 128; + } + else + { + // Set prescaler to 128 for 250KHz + _prescalerConfigBits = PWM_PRESCALER_PRESCALER_DIV_128; + _prescaler = 128; + } + + _compareValue = ( countTOP / _prescaler ) - 1; + + PWM_LOGDEBUG5(F("calcPrescaler: OK period ="), period, F(", _prescaler ="), _prescaler, + F(", countTOP ="), countTOP); + + PWM_LOGDEBUG7(F("calcPrescaler: _dutycycle ="), _dutycycle, F(", frequency ="), frequency, + F(", _prescalerConfigBits ="), _prescalerConfigBits, F(", _compareValue ="), _compareValue); + + return _compareValue; + } + + /////////////////////////////////////////// + + // input dutycycle is 16-bit. To be mapped to 15 bit + // TODO: set frequency by calc period + TOP + + // Bad design of HardwarePWM class + // Must call setMaxValue() to set COUNTERTOP for frequency + // Never call setResolution() after setMaxValue(), as it will reset COUNTERTOP to max resolution => wrong freq + // Calculate Prescaler according to freq. Also calculate and call setClockDiv(_prescalerConfigBits) + bool setupPWM(uint32_t pin, uint32_t frequency, uint16_t dutycycle) + { + if (_frequency != frequency) + { + // only for new Freq + calcPrescaler(frequency); + } + + // Map dutycycle to _compareValue + uint16_t newDC = round(map(dutycycle, 0, MAX_COUNT_16BIT, 0, _compareValue)); + + // Check all PWM modules if pin is the same to use + for (int i = 0; i < HWPWM_MODULE_NUM; i++) + { + if (HwPWMx[i]->isOwner(NRF52_PWM_TOKEN)) + { + int const ch = HwPWMx[i]->pin2channel(pin); + + if (ch >= 0) + { + // KH set 15-bit, then mapped DC to 15-bit + //HwPWMx[i]->setResolution(_resolution - 1); + if (_frequency != frequency) + { + HwPWMx[i]->setClockDiv(_prescalerConfigBits); + _frequency = frequency; + } + + HwPWMx[i]->setMaxValue(_compareValue); + + HwPWMx[i]->writeChannel(ch, newDC); + + PWM_LOGDEBUG5(F("setupPWM: Same _pin ="), _pin, F("to PWM module ="), i, F(", ch ="), ch); + PWM_LOGDEBUG5(F("setupPWM: dutycycle ="), dutycycle >> 1, F(", frequency ="), frequency, F(", _resolution ="), + _resolution - 1); + + return true; + } + } + } + + // Assign a new pin to currently owned PWM module + for (int i = 0; i < HWPWM_MODULE_NUM; i++) + { + if ( HwPWMx[i]->isOwner(NRF52_PWM_TOKEN) && HwPWMx[i]->addPin(pin) ) + { + // KH set 15-bit, then mapped DC to 15-bit + //HwPWMx[i]->setResolution(_resolution - 1); + if (_frequency != frequency) + { + HwPWMx[i]->setClockDiv(_prescalerConfigBits); + _frequency = frequency; + } + + HwPWMx[i]->setMaxValue(_compareValue); + + _pin = pin; + + HwPWMx[i]->writePin(pin, newDC); + + PWM_LOGDEBUG3(F("setupPWM: Added new pin ="), pin, F("to PWM module ="), i); + PWM_LOGDEBUG5(F("setupPWM: dutycycle ="), dutycycle >> 1, F(", frequency ="), frequency, F(", _resolution ="), + _resolution - 1); + + return true; + } + } + + // Try getting and takeOwnership of new HwPWMx instance of PWM module + // a) Check if no PWM channel has a pin + // b) the owner token is nullptr + for (int i = 0; i < HWPWM_MODULE_NUM; i++) + { + if (HwPWMx[i]->takeOwnership(NRF52_PWM_TOKEN)) + { + // apply the current resolution if new ownership + // KH set 15-bit, then mapped DC to 15-bit + HwPWMx[i]->setResolution(_resolution - 1); + HwPWMx[i]->setClockDiv(_prescalerConfigBits); + HwPWMx[i]->setMaxValue(_compareValue); + + _frequency = frequency; + _pin = pin; + + HwPWMx[i]->addPin(pin); + HwPWMx[i]->writePin(pin, newDC); + + PWM_LOGDEBUG3("setupPWM: pin =", pin, "assigned to new PWM module =", i); + PWM_LOGDEBUG5(F("setupPWM: dutycycle ="), dutycycle >> 1, F(", frequency ="), frequency, F(", _resolution ="), + _resolution - 1); + + return true; + } + } + + PWM_LOGDEBUG("setupPWM: No free PWM module"); + + return false; + } + + /////////////////////////////////////////// + /////////////////////////////////////////// + + public: + + // dutycycle from 0-65535 for 0%-100% + bool setPWM_Int(const uint8_t& pin, const float& frequency, const uint16_t& dutycycle) + { + if (pin != _pin) + return false; + + PWM_LOGINFO3(F("setPWM_Int: dutycycle ="), dutycycle, F(", frequency ="), frequency); + + return setupPWM(pin, (uint32_t) frequency, dutycycle); + } + + /////////////////////////////////////////// + + bool setPWM() + { + return setPWM_Int(_pin, _frequency, _dutycycle); + } + + /////////////////////////////////////////// + + bool setPWM(const uint8_t& pin, const float& frequency, const float& dutycycle) + { + _dutycycle = round(map(dutycycle, 0, 100.0f, 0, MAX_COUNT_16BIT)); + + PWM_LOGDEBUG3(F("setPWM: _dutycycle ="), _dutycycle, F(", frequency ="), frequency); + + return setPWM_Int(pin, frequency, _dutycycle); + } + + /////////////////////////////////////////// + + bool setPWM_Period(const uint8_t& pin, const float& period_us, const float& dutycycle) + { + _dutycycle = round(map(dutycycle, 0, 100.0f, 0, MAX_COUNT_16BIT)); + + PWM_LOGDEBUG3(F("setPWM_Period: _dutycycle ="), _dutycycle, F(", period_us ="), period_us); + + return setPWM_Int(pin, round(1000000.0f / period_us), _dutycycle); + } + + /////////////////////////////////////////// + + // Must have same frequency + bool setPWM_manual(const uint8_t& pin, const uint16_t& DCValue) + { + // Not the same pin or overvalue + if (_pin != pin) + return false; + + _dutycycle = DCValue; + + PWM_LOGINFO3(F("setPWM_manual: _dutycycle ="), _dutycycle, F(", frequency ="), _frequency); + + return setupPWM(pin, _frequency, DCValue); + } + + /////////////////////////////////////////// + + inline float getActualDutyCycle() + { + return ( ( (float) _dutycycle + 1 ) * 100 / MAX_16BIT ); + } + + /////////////////////////////////////////// + + inline float getActualFreq() + { + return _frequency; + } + + /////////////////////////////////////////// + + inline float getPWMPeriod() + { + return (1000000.0f / _frequency); + } + + /////////////////////////////////////////// + + inline uint32_t get_freq_CPU() + { + return F_CPU; + } + + /////////////////////////////////////////// + + inline uint32_t getPin() + { + return _pin; + } + + /////////////////////////////////////////// + + void setResolution(const uint8_t& resolution) + { + // Must be <= 16-bit and >= 8-bit + if ( (resolution <= 16) && (resolution >= 8) ) + { + _resolution = resolution; + } + } + + /////////////////////////////////////////// + + bool isPWMEnabled() + { + return _PWMEnabled; + } + + /////////////////////////////////////////////////////////////////// + + private: + + float _frequency; + + // For PWM frequency COUNTERTOP register + uint32_t _compareValue; + + uint32_t NRF52_PWM_TOKEN = 0; + + uint32_t _prescalerConfigBits; + uint16_t _prescaler = 1; + + // dutycycle from 0-65535 for 0%-100% + uint16_t _dutycycle; + + uint8_t _pin = INVALID_NRF52_PIN; + + // In number of bits + uint8_t _resolution = 16; + + bool _PWMEnabled = false; + + /////////////////////////////////////////// +}; + +/////////////////////////////////////////// + + +#endif // NRF52_PWM_H + diff --git a/utils/astyle_library.conf b/utils/astyle_library.conf new file mode 100644 index 0000000..8a73bc2 --- /dev/null +++ b/utils/astyle_library.conf @@ -0,0 +1,70 @@ +# Code formatting rules for Arduino libraries, modified from for KH libraries: +# +# https://github.com/arduino/Arduino/blob/master/build/shared/examples_formatter.conf +# + +# astyle --style=allman -s2 -t2 -C -S -xW -Y -M120 -f -p -xg -H -xb -c --xC120 -xL *.h *.cpp *.ino + +--mode=c +--lineend=linux +--style=allman + +# -r or -R +#--recursive + +# -c => Converts tabs into spaces +convert-tabs + +# -s2 => 2 spaces indentation +--indent=spaces=2 + +# -t2 => tab =2 spaces +#--indent=tab=2 + +# -C +--indent-classes + +# -S +--indent-switches + +# -xW +--indent-preproc-block + +# -Y => indent classes, switches (and cases), comments starting at column 1 +--indent-col1-comments + +# -M120 => maximum of 120 spaces to indent a continuation line +--max-continuation-indent=120 + +# -xC120 => max‑code‑length will break a line if the code exceeds # characters +--max-code-length=120 + +# -f => +--break-blocks + +# -p => put a space around operators +--pad-oper + +# -xg => Insert space padding after commas +--pad-comma + +# -H => put a space after if/for/while +pad-header + +# -xb => Break one line headers (e.g. if/for/while) +--break-one-line-headers + +# -c => Converts tabs into spaces +#--convert-tabs + +# if you like one-liners, keep them +#keep-one-line-statements + +# -xV +--attach-closing-while + +#unpad-paren + +# -xp +remove-comment-prefix + diff --git a/utils/restyle.sh b/utils/restyle.sh new file mode 100644 index 0000000..bcd846f --- /dev/null +++ b/utils/restyle.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +for dir in . ; do + find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" -o -name "*.ino" \) -exec astyle --suffix=none --options=./utils/astyle_library.conf \{\} \; +done +