Skip to content
This repository has been archived by the owner on Feb 4, 2023. It is now read-only.
/ nRF52_MBED_PWM Public archive

This library enables you to use Hardware Timers on nRF52840-based Nano_33_BLE or Nano_33_BLE_Sense board to create and output PWM to pins. These PWM channels, using nRF52840 Hardware Timers, still work even if other functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software …

License

Notifications You must be signed in to change notification settings

khoih-prog/nRF52_MBED_PWM

Repository files navigation

nRF52_MBED_PWM Library

arduino-library-badge GitHub release GitHub contributions welcome GitHub issues

Donate to my libraries using BuyMeACoffee



Table of Contents



Why do we need this nRF52_MBED_PWM library

Features

This library enables you to use Hardware-based PWM to create and output PWM to pins on an nRF52840-based Nano_33_BLE board.

This library is using the same or similar functions as other FastPWM libraries, as follows, to enable you to port your PWM code easily between platforms

  1. RP2040_PWM
  2. AVR_PWM
  3. megaAVR_PWM
  4. ESP32_FastPWM
  5. SAMD_PWM
  6. SAMDUE_PWM
  7. nRF52_PWM
  8. Teensy_PWM
  9. ATtiny_PWM
  10. Dx_PWM
  11. Portenta_H7_PWM
  12. MBED_RP2040_PWM
  13. nRF52_MBED_PWM
  14. STM32_PWM

The most important feature is they're purely hardware-based PWM channels. Therefore, their executions are not blocked by bad-behaving functions / tasks. This important feature is absolutely necessary for mission-critical tasks.

This important feature is absolutely necessary for mission-critical tasks. These hardware timers, using interrupt, still work even if other functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software timers using millis() or micros(). That's necessary if you need to measure some data requiring better accuracy.

The PWM_Multi will demonstrate the usage of multichannel PWM using multiple Hardware Timers. The 4 independent Hardware Timers are used to control 4 different PWM outputs, with totally independent frequencies and dutycycles. You can start, stop, change and restore the settings of any PWM channel on-the-fly.

Being hardware-based PWM, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet or Blynk services.

This non-being-blocked important feature is absolutely necessary for mission-critical tasks.


Currently supported Boards

  1. MBED nRF52840-based boards such as Nano_33_BLE, Nano_33_BLE_Sense, etc. using Arduino-mbed mbed_nano core
  2. Seeeduino nRF52840-based boards such as SEEED_XIAO_NRF52840 and SEEED_XIAO_NRF52840_SENSE, etc. using Seeeduino mbed core


Prerequisites

  1. Arduino IDE 1.8.19+ for Arduino. GitHub release
  2. Arduino mbed_nano core 3.4.1+ for Arduino (Use Arduino Board Manager) MBED nRF52840-based boards such as Nano_33_BLE, Nano_33_BLE_Sense. GitHub release
  3. Seeeduino mbed core 2.9.0+ for Seeed nRF52840-based boards such as SEEED_XIAO_NRF52840 and SEEED_XIAO_NRF52840_SENSE. GitHub release


Installation

Use Arduino Library Manager

The best and easiest way is to use Arduino Library Manager. Search for nRF52_MBED_PWM, then select / install the latest version. You can also use this link arduino-library-badge for more detailed instructions.

Manual Install

Another way to install is to:

  1. Navigate to nRF52_MBED_PWM page.
  2. Download the latest release nRF52_MBED_PWM-main.zip.
  3. Extract the zip file to nRF52_MBED_PWM-main directory
  4. Copy whole nRF52_MBED_PWM-main folder to Arduino libraries' directory such as ~/Arduino/libraries/.

VS Code & PlatformIO

  1. Install VS Code
  2. Install PlatformIO
  3. Install nRF52_MBED_PWM library by using Library Manager. Search for nRF52_MBED_PWM in Platform.io Author's Libraries
  4. Use included platformio.ini file from examples to ensure that all dependent libraries will installed automatically. Please visit documentation for the other options and examples at Project Configuration File


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.

You can include this .hpp file

// Can be included as many times as necessary, without `Multiple Definitions` Linker Error
#include "nRF52_MBED_PWM.hpp"    //https://github.com/khoih-prog/nRF52_MBED_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 be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error
#include "nRF52_MBED_PWM.h"      //https://github.com/khoih-prog/nRF52_MBED_PWM

Check the new multiFileProject example for a HOWTO demo.



Nano_33_BLE PWM-enables pins

All the digital pins on Arduino Nano 33 BLE sense are PWM-enabled pins which are numbered from D0 to D13

Check Nano_33_BLE Specs



Usage

Before using any Timer for a PWM channel, you have to make sure the Timer has not been used by any other purpose.

1. Prepare PWM settings

// All the digital pins on Arduino Nano 33 BLE sense are PWM-enabled pins which are numbered from D0 to D13
uint32_t myPin  = D2;

float dutyCycle = 50.0f;

float freq      = 5000.0f;

mbed::PwmOut* pwm   = NULL;

2. Set PWM Frequency and dutycycle

void setup()
{
  ....
  
  setPWM(pwm, myPin, freq, dutyCycle);
}  

3. Read actual PWM Parameters

Use these functions

float getFreq(mbed::PwmOut* &pwm);

float getDutyCycle(mbed::PwmOut* &pwm);

float getPulseWidth_uS(mbed::PwmOut* &pwm);

float getPeriod_uS(mbed::PwmOut* &pwm);

For example

// All the digital pins on Arduino Nano 33 BLE sense are PWM-enabled pins which are numbered from D0 to D13
uint32_t myPin  = D2;

float dutyCycle = 50.0f;

float freq      = 5000.0f;

mbed::PwmOut* pwm   = NULL;

setPWM(pwm, myPin, freq, dutyCycle);

if (pwm)
{
  Serial.print(getPulseWidth_uS(pwm)); Serial.print(F("\t\t"));
  Serial.print(getDutyCycle(pwm)); Serial.print(F("\t\t"));
  Serial.print(getPeriod_uS(pwm));
}


Examples:

  1. PWM_Multi
  2. PWM_Single
  3. multiFileProject
  4. PWM_StepperControl New


Example PWM_Multi

/****************************************************************************************************************************
PWM_Multi.ino
For Nano_33_BLE or Nano_33_BLE_Sense boards
Written by Khoi Hoang
Built by Khoi Hoang https://github.com/khoih-prog/nRF52_MBED_PWM
Licensed under MIT license
*****************************************************************************************************************************/
#if !( ARDUINO_ARCH_NRF52840 && TARGET_NAME == ARDUINO_NANO33BLE )
#error This code is designed to run on nRF52-based Nano-33-BLE boards using mbed-RTOS platform! Please check your Tools->Board setting.
#endif
#define _PWM_LOGLEVEL_ 1
// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error
#include "nRF52_MBED_PWM.h"
#define LED_ON LOW
#define LED_OFF HIGH
// All the digital pins on Arduino Nano 33 BLE sense are PWM-enabled pins which are numbered from D0 to D13
uint32_t pins[] = { D2, D3, D4, D5 };
#define NUM_OF_PINS ( sizeof(pins) / sizeof(uint32_t) )
float dutyCycle[] = { 50.0f, 50.0f, 50.0f, 50.0f };
float freq[] = { 1000.0f, 2500.0f, 4000.0f, 5000.0f };
float curDutyCycle[] = { 50.0f, 50.0f, 50.0f, 50.0f };
float curFreq[] = { 1000.0f, 2500.0f, 4000.0f, 5000.0f };
mbed::PwmOut* pwm[] = { NULL, NULL, NULL, NULL };
void startAllPWM()
{
digitalWrite(LEDG, LED_ON);
digitalWrite(LEDB, LED_OFF);
digitalWrite(LEDR, LED_OFF);
for (uint8_t index = 0; index < NUM_OF_PINS; index++)
{
PWM_LOGERROR7("Freq = ", freq[index], ", \tDutyCycle % = ", dutyCycle[index], ", \tDutyCycle = ",
dutyCycle[index] / 100, ", \tPin = ", pins[index]);
// setPWM(mbed::PwmOut* &pwm, pin_size_t pin, float frequency, float dutyCycle)
setPWM(pwm[index], pins[index], freq[index], dutyCycle[index]);
}
}
void restoreAllPWM()
{
digitalWrite(LEDG, LED_ON);
digitalWrite(LEDB, LED_OFF);
digitalWrite(LEDR, LED_OFF);
for (uint8_t index = 0; index < NUM_OF_PINS; index++)
{
curFreq[index] = freq[index];
curDutyCycle[index] = dutyCycle[index];
// setPWM(mbed::PwmOut* &pwm, pin_size_t pin, float frequency, float dutyCycle)
setPWM(pwm[index], pins[index], freq[index], dutyCycle[index]);
}
}
void changeAllPWM()
{
digitalWrite(LEDG, LED_OFF);
digitalWrite(LEDB, LED_ON);
digitalWrite(LEDR, LED_OFF);
for (uint8_t index = 0; index < NUM_OF_PINS; index++)
{
curFreq[index] = freq[index] * 2;
curDutyCycle[index] = dutyCycle[index] / 2;
// setPWM(mbed::PwmOut* &pwm, pin_size_t pin, float frequency, float dutyCycle)
setPWM(pwm[index], pins[index], curFreq[index], curDutyCycle[index]);
}
}
void stopAllPWM()
{
digitalWrite(LEDG, LED_OFF);
digitalWrite(LEDB, LED_OFF);
digitalWrite(LEDR, LED_ON);
for (uint8_t index = 0; index < NUM_OF_PINS; index++)
{
curFreq[index] = 1000.0f;
curDutyCycle[index] = 0.0f;
//stopPWM(mbed::PwmOut* &pwm, pin_size_t pin)
stopPWM(pwm[index], pins[index]);
}
}
void printLine()
{
Serial.println(F("\n========================================================="));
}
void printPulseWidth()
{
static uint32_t num = 0;
if (num++ % 50 == 0)
{
printLine();
for (uint8_t index = 0; index < NUM_OF_PINS; index++)
{
Serial.print(F("PW (us) "));
Serial.print(index);
Serial.print(F("\t"));
}
printLine();
}
if (num > 1)
{
for (uint8_t index = 0; index < NUM_OF_PINS; index++)
{
if (pwm[index])
{
Serial.print(getPulseWidth_uS(pwm[index]));
Serial.print(F("\t\t"));
}
}
Serial.println();
}
}
#define PRINT_INTERVAL 10000L
#define CHANGE_INTERVAL 20000L
void check_status()
{
static unsigned long checkstatus_timeout = 0;
static unsigned long changePWM_timeout = 0;
static bool PWM_orig = true;
static uint32_t count = 0;
// Print every PRINT_INTERVAL (10) seconds.
if ((millis() > checkstatus_timeout) || (checkstatus_timeout == 0))
{
printPulseWidth();
checkstatus_timeout = millis() + PRINT_INTERVAL;
}
if ( (millis() > changePWM_timeout) && (millis() > CHANGE_INTERVAL) )
{
if (PWM_orig)
{
if (count++ % 2 == 0)
{
Serial.println("Stop all PWM");
stopAllPWM();
}
else
{
Serial.println("Change all PWM");
changeAllPWM();
PWM_orig = !PWM_orig;
}
}
else
{
Serial.println("Restore all PWM");
restoreAllPWM();
PWM_orig = !PWM_orig;
}
changePWM_timeout = millis() + CHANGE_INTERVAL;
}
}
void setup()
{
pinMode(LEDG, OUTPUT);
pinMode(LEDB, OUTPUT);
pinMode(LEDR, OUTPUT);
digitalWrite(LEDG, LED_OFF);
digitalWrite(LEDB, LED_OFF);
digitalWrite(LEDR, LED_OFF);
for (uint8_t index = 0; index < NUM_OF_PINS; index++)
{
pinMode(pins[index], OUTPUT);
digitalWrite(pins[index], LOW);
}
Serial.begin(115200);
while (!Serial && millis() < 5000);
delay(100);
Serial.print(F("\nStarting PWM_Multi on "));
Serial.println(BOARD_NAME);
Serial.println(nRF52_MBED_PWM_VERSION);
startAllPWM();
}
void loop()
{
check_status();
}



Debug Terminal Output Samples

1. PWM_Single on Nano_33_BLE

The following is the sample terminal output when running example PWM_Single on Nano_33_BLE to demonstrate how to start a single PWM channel, then stop, change, restore the PWM settings on-the-fly.

Starting PWM_Single on Nano_33_BLE
nRF52_MBED_PWM v1.0.3
[PWM] Freq = 5000.00, DutyCycle % = 50.00, DutyCycle = 0.50, Pin = 2

============================================
PW (us)		DutyCycle	Period (uS)
============================================
100.00		50.00		200.00
Stop PWM
0.00		0.00		1000.00
0.00		0.00		1000.00
Change PWM
25.00		25.00		100.00
25.00		25.00		100.00
Restore PWM
100.00		50.00		200.00
100.00		50.00		200.00
Stop PWM
0.00		0.00		1000.00
0.00		0.00		1000.00
Change PWM
25.00		25.00		100.00
25.00		25.00		100.00

2. PWM_Multi on Nano_33_BLE

The following is the sample terminal output when running example PWM_Multi on Nano_33_BLE to demonstrate how to start multiple PWM channels, then stop, change, restore the PWM settings on-the-fly.

Starting PWM_Multi on Nano_33_BLE
nRF52_MBED_PWM v1.0.3
[PWM] Freq = 1000.00, 	DutyCycle % = 50.00, 	DutyCycle = 0.50, 	Pin = 2
[PWM] Freq = 2500.00, 	DutyCycle % = 50.00, 	DutyCycle = 0.50, 	Pin = 3
[PWM] Freq = 4000.00, 	DutyCycle % = 50.00, 	DutyCycle = 0.50, 	Pin = 4
[PWM] Freq = 5000.00, 	DutyCycle % = 50.00, 	DutyCycle = 0.50, 	Pin = 5

=========================================================
PW (us) 0	PW (us) 1	PW (us) 2	PW (us) 3	
=========================================================
500.00		200.00		125.00		100.00		
Stop all PWM
0.00		0.00		0.00		0.00		
0.00		0.00		0.00		0.00		
Change all PWM
125.00		50.00		31.25		25.00		
125.00		50.00		31.25		25.00		
Restore all PWM
500.00		200.00		125.00		100.00		
500.00		200.00		125.00		100.00		
Stop all PWM
0.00		0.00		0.00		0.00		
0.00		0.00		0.00		0.00		
Change all PWM
125.00		50.00		31.25		25.00		
125.00		50.00		31.25		25.00		
Restore all PWM
500.00		200.00		125.00		100.00		
500.00		200.00		125.00		100.00


Debug

Debug is enabled by default on Serial.

You can also change the debugging level _PWM_LOGLEVEL_ from 0 to 4

// Don't define _PWM_LOGLEVEL_ > 0. Only for special ISR debugging only. Can hang the system.
#define _PWM_LOGLEVEL_     0

Troubleshooting

If you get compilation errors, more often than not, you may need to install a newer version of the core for Arduino boards.

Sometimes, the library will only work if you update the board core to the latest version because I am using newly added functions.



Issues

Submit issues to: nRF52_MBED_PWM issues



TO DO

  1. Search for bug and improvement.
  2. Similar features for remaining Arduino boards

DONE

  1. Basic hardware multi-channel PWM for Nano_33_BLE.
  2. Add Table of Contents
  3. Permit to start, stop, modify, restore PWM settings on-the-fly
  4. Optimize library code by using reference-passing instead of value-passing
  5. Use h-only style
  6. Add functions to read PWM parameters.
  7. Add support to Seeeduino nRF52840-based boards such as SEEED_XIAO_NRF52840 and SEEED_XIAO_NRF52840_SENSE, etc. using Seeeduino mbed core
  8. Add astyle using allman style. Restyle the library
  9. Add example PWM_StepperControl to demo how to control Stepper Motor using PWM


Contributions and Thanks

Many thanks for everyone for bug reporting, new feature suggesting, testing and contributing to the development of this library.

  1. Thanks to Paul van Dinther for proposing new way to use PWM to drive Stepper-Motor in Using PWM to step a stepper driver #16, leading to v2.0.3
dinther
Paul van Dinther


Contributing

If you want to contribute to this project:

  • Report bugs and errors
  • Ask for enhancements
  • Create issues and pull requests
  • Tell other people about this library

License

  • The library is licensed under MIT

Copyright

Copyright (c) 2022- Khoi Hoang

About

This library enables you to use Hardware Timers on nRF52840-based Nano_33_BLE or Nano_33_BLE_Sense board to create and output PWM to pins. These PWM channels, using nRF52840 Hardware Timers, still work even if other functions are blocking. Moreover, they are much more precise (certainly depending on clock frequency accuracy) than other software …

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published