This is a Wokwi simulation of the PWMallPins software PWM code referenced by the Secrets of Arduino PWM tutorial
It does have a one-line modification to speed up the cycling.
/******************************************************************************************************
* Title: PWMallPins.pde
* Author: Paul Badger 2007
* Formating: Seth Wolf 2015
* copied from https://playground.arduino.cc/Main/PWMallPins/
* Wokwi: https://wokwi.com/projects/371145739061239809
* modified to speed up cycles by a factor 2023-07-24
*
* Action:
* This program fades LED's on Arduino digital pins 2 through 13 in a sine-wave pattern.
* A program to illustrate one way to implement a manual PWM loop.
*
* It could be modified to also modulate pins 0 & 1 and the analog pins.
* I didn't modulate pins 0 & 1 just because of the hassle of disconnecting
* the LEDs on the RX and TX pins (0 & 1).
*
* The PWM loop, as written, operates at about 175 HZ and is flicker-free.
* The trick to this is to reduce the computation process time in between the PWM loop cycles.
* Long delays between the loops are going to show up as flicker. This is true especially of
* "Serial.print" debug statements which seem to hog the processor during
* transmission. Shorter (time-wise) statements will just dim the maximum brightness of
* the LED's. There are a couple of lines of code (commented out) that implement a
* potentiometer as a speed control for the dimming.
*
* How it works:
* The PWM loop turns on all LED's whose values are greater than 0 at the start of the PWM loop.
* value. It then turns off the LED's as the loop variable is equal to the channel's PWM modulation
* Because the values are limited to 255 (just by the table), all LED's are turned
* off at the end of the PWM loop. This has the side effect of making any extra math,
* sensor reading and will increase the "off" period of the LED's duty cycle. Consequently
* the brightest LED value (255), which is supposed to represent a "100%" duty cycle
* (on all the time), doesn't really do that. More math and sensor reading will increase
* the "off time", dimming the LED's max brightness. You can make up for
* this dimming with smaller series resistors, since LED's can be overrated if they aren't
* on all of the time.The up side of this arrangement is that the LED's stay flicker free.
*
* Note:
* This program could easily be used to modulate motor speeds with the addition of driver transistors or MOSFET's.
*
*************************************************************************************************************/
int LEDCount = 12; // Number of LEDs used in this project
int topValue = 14; // Largest Arduino pin number of the LED pin set (+1)
int pinNumber; // variable for pinNumber
int fractionRemainder; // variable for fraction Remainder
int channelCounter; // variable for channel Counter
int brightValue; // variable for LED brightness Value
int setCounter; // variable for loop set Counter
int potValue; // Variable read from the potentiometer
int pwmVal[14]; // PWM values for the LEDs (0&1 included but not used) (change this value if topValue is modified)
long time; // variable for speed debuging
float pwmSpeed[14] = { // These constants set the rate of dimming (change this value if topValue is modified)
0, 0, 1.2, 1.3, 1.4, 1.9, .9, .8, .5, 1.2, 1.37, 1.47, .3, 3.2}; // 12 values (one for each of the LEDs in the set)
float pwmFloats[14]; // Brightness values for each LED (change this value if topValue is modified)
unsigned char sinewave[] = // Preset 256 values of a binary conversions of a sine wave
{
0x80,0x83,0x86,0x89,0x8c,0x8f,0x92,0x95,0x98,0x9c,0x9f,0xa2,0xa5,0xa8,0xab,0xae,
0xb0,0xb3,0xb6,0xb9,0xbc,0xbf,0xc1,0xc4,0xc7,0xc9,0xcc,0xce,0xd1,0xd3,0xd5,0xd8,
0xda,0xdc,0xde,0xe0,0xe2,0xe4,0xe6,0xe8,0xea,0xec,0xed,0xef,0xf0,0xf2,0xf3,0xf5,
0xf6,0xf7,0xf8,0xf9,0xfa,0xfb,0xfc,0xfc,0xfd,0xfe,0xfe,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfd,0xfc,0xfc,0xfb,0xfa,0xf9,0xf8,0xf7,
0xf6,0xf5,0xf3,0xf2,0xf0,0xef,0xed,0xec,0xea,0xe8,0xe6,0xe4,0xe2,0xe0,0xde,0xdc,
0xda,0xd8,0xd5,0xd3,0xd1,0xce,0xcc,0xc9,0xc7,0xc4,0xc1,0xbf,0xbc,0xb9,0xb6,0xb3,
0xb0,0xae,0xab,0xa8,0xa5,0xa2,0x9f,0x9c,0x98,0x95,0x92,0x8f,0x8c,0x89,0x86,0x83,
0x80,0x7c,0x79,0x76,0x73,0x70,0x6d,0x6a,0x67,0x63,0x60,0x5d,0x5a,0x57,0x54,0x51,
0x4f,0x4c,0x49,0x46,0x43,0x40,0x3e,0x3b,0x38,0x36,0x33,0x31,0x2e,0x2c,0x2a,0x27,
0x25,0x23,0x21,0x1f,0x1d,0x1b,0x19,0x17,0x15,0x13,0x12,0x10,0x0f,0x0d,0x0c,0x0a,
0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x03,0x02,0x01,0x01,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x02,0x03,0x03,0x04,0x05,0x06,0x07,0x08,
0x09,0x0a,0x0c,0x0d,0x0f,0x10,0x12,0x13,0x15,0x17,0x19,0x1b,0x1d,0x1f,0x21,0x23,
0x25,0x27,0x2a,0x2c,0x2e,0x31,0x33,0x36,0x38,0x3b,0x3e,0x40,0x43,0x46,0x49,0x4c,
0x4f,0x51,0x54,0x57,0x5a,0x5d,0x60,0x63,0x67,0x6a,0x6d,0x70,0x73,0x76,0x79,0x7c
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
void setup(){
Serial.begin(9600);
DDRD=0xFC; // Arduino digital pins 0 to 7 - Configure all pins as read/write and output except for the serial pins, 0 & 1
DDRB=0xFF; // Arduino digital pins 8 to 13 - Configure all pins as read/write and output
for (auto &i : pwmSpeed) i *= 3; // speed up cycling
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
void loop()
{
/** Time = millis(); */ // This is for testing the loop speed (remove commented bracket)
/** For (setCounter=0; setCounter<1000; setCounter++){ */ // This is for testing the loop speed (remove commented bracket)
/** potValue = analogread(0); */ // Sets potValue control to manually change speed of fading (remove line below)
for (pinNumber=0; pinNumber<topValue; pinNumber++){ // Calculate a new PWM value every time through the loop (Cycle back to 0 after 11)
fractionRemainder = (fractionRemainder + 1) % LEDCount; // Calculate a new fraction Remainder every time - modulo operator makes it
channelCounter = fractionRemainder + 2; // Add 2 to the result for cycling the selected channel(pin) numbers
pwmFloats[channelCounter] = (pwmFloats[channelCounter] + pwmSpeed[channelCounter]);
/** pwmFloats[channelCounter] = (pwmFloats[channelCounter] + ((pwmSpeed[channelCounter]*15*(float)potValue)/1023)); */ // implements potentiometer control
if (pwmFloats[channelCounter] >= 256){ // Wrap around sine table values that are larger than 256
pwmFloats[channelCounter] = pwmFloats[channelCounter] - 256; //
} //
else if (pwmFloats[channelCounter] < 0){ //
pwmFloats[channelCounter] = pwmFloats[channelCounter] + 256; // Wrap around sinewave table index values that are less than 0
} //
pwmVal[channelCounter] = sinewave[(int)pwmFloats[channelCounter]]; // Convert the float value to an integer and get the value out of the sinewave index
} //
PORTD = 0xFC; // Reset all outputs except serial pins 0 & 1
PORTB = 0xFF; // Reset and turn on all pins of ports D & B
for (setCounter=0; setCounter<3; setCounter++){ // This adds more repetitions of the loop below to reduce the processing time of the loop above
// Increase this value until you start to perceive any flicker in the LEDs
// then back off and reduce the value
// Decreasing value configures for a more responsive sensor input read process
for (brightValue=0; brightValue<256; brightValue++){ // Cycle through the 256 values to find a match
for( pinNumber=2; pinNumber<topValue; pinNumber++){ // start with 2 to avoid serial pins
if (brightValue == pwmVal[pinNumber]){ // If it found a standard value matching one of the pins
if (pinNumber < 8){ // Corresponds to PORTD pin group
// bit-shift a one into the proper bit then reverse the whole byte
// equivalent to the line below but around 4 times faster
// digitalWrite(pinNumber, LOW);
PORTD = PORTD & (~(1 << pinNumber)); // Arduino digital pins 0 to 7
} //
else{ //
PORTB = PORTB & (~(1 << (pinNumber-8))); // Corresponds to PORTB (same as digitalWrite(pin, LOW);) on Port B pins
} //
}
}
}
}
/** } */ // This is for the testing loop speed (remove the commented bracket)
/** Serial.println((millis() - time), DEC); */ // speed test code
}
The simpler bit-banging software PWM example on https://docs.arduino.cc/tutorials/generic/secrets-of-arduino-pwm uses this code:
void setup()
{
pinMode(13, OUTPUT);
}
void loop()
{
digitalWrite(13, HIGH);
delayMicroseconds(100); // Approximately 10% duty cycle @ 1KHz
digitalWrite(13, LOW);
delayMicroseconds(1000 - 100);
}
The article's objections to software PWM seem a bit of a strawman:
This technique has the advantage that it can use any digital output pin. In addition, you have full control the duty cycle and frequency. One major disadvantage is that any interrupts will affect the timing, which can cause considerable jitter unless you disable interrupts. A second disadvantage is you can't leave the output running while the processor does something else. Finally, it's difficult to determine the appropriate constants for a particular duty cycle and frequency unless you either carefully count cycles, or tweak the values while watching an oscilloscope.
I thought the article's main objections of jitter due to interrupts, blocking, difficulty and expense of calculating constants were a bit of a strawman, since a rewrite with non-blocking code eliminates all three disadvantages:
// from https://docs.arduino.cc/tutorials/generic/secrets-of-arduino-pwm
// modified to be non-blocking
// https://wokwi.com/projects/371148004268653569
void setup()
{
pinMode(13, OUTPUT);
}
void loop(void) {
pwm();
}
void pwm()
{
constexpr unsigned long periodUs = 1000UL;
constexpr unsigned long onUs = periodUs * 0.10;
static int state = LOW;
static unsigned long last = -periodUs;
static unsigned long interval = periodUs - onUs;
if (micros() - last < interval ) return;
last += interval;
if (state == LOW) {
digitalWrite(13, HIGH);
state = HIGH;
interval = onUs;
} else {
digitalWrite(13, LOW);
state = LOW;
interval = periodUs - onUs;
}
}
Software PWM has some reasonable advantages, but the Secrets of Arduino PWM tutorial presents the software PWM alternative unfairly.
