A really fast software PWM library

Well it's the old story: I was interfacing multiple RGB LEDs, and there's not enough built-in PWM pins.

There're libraries like ShiftPWM (http://www.elcojacobs.com/shiftpwm/) and SoftPWM (Google Code Archive - Long-term storage for Google Code Project Hosting.) which does software PWM, but they're just not ehough for me.

ShiftPWM is fast and elegant, but requires extra hardwares (the shift registers), that's around USD $0.6 each from the local retail electronics store.
But I just want 16 outputs and there are 20 pins on atmega328p (+2 more if the internal RC is used, +1 more if the reset pin is disabled), why should I spend 2 extra 74*595?

The SoftPWM library has some other problem, it's slow (high CPU usage), fat (high memory usage), and hard to configure (such as brightness levels, PWM frequencies, etc.).

So I wrote this library - just like ShiftPWM - to do PWM for all pins in background (timer interrupt).
It produces REALLY FAST code - 6 instructions per output in the ISR routine, and only use 1 byte per pin + 1 byte for the counter.
However, currently it only knows about timer1, and doesn't know about arduino pins (has to tell it the port register and bit number).

It is capable to drives 16 PWM outputs with 256 brightness levels up to 200hz on a 8Mhz ATmega328p (but this way you have almost no time loop()-ing, so you might want to use something lower like 120hz).

the code is hosted on github:

External hardware might be advisable too.
TLC5940 from ti.com. 16 channels.
WS2803 from world semi, 18 channels, but apparently only available via e-bay. I just received a tube of 25, have not tried them out yet.

WS2803-preliminary-En.pdf (437 KB)

tlc5940.pdf (1.06 MB)

CrossRoads:
External hardware might be advisable too.
TLC5940 from ti.com. 16 channels.
WS2803 from world semi, 18 channels, but apparently only available via e-bay. I just received a tube of 25, have not tried them out yet.

they're expansive.

TLC5940 is over USD $6 from local retail store, no idea about WS2803.

together with 2 ULN2803 ($0.6 each), i can drive 16 PWMs (350mA each, 2A per one ULN2803).

i'm thinking about to disable the reset pin (so it can be used as an IO pin) and enable Internal RC (free the 2 xtal pin, too) to get 23 digital IO, with 2 pins for TWI, it can drive 7 RGB LED with the rest 21 pins (and the chip can be powered with only 2.7V!).
or... maybe I should just get an ATtiny48 to do this? :stuck_out_tongue:

I'd go the other way 8) ATMega1284 with 32 IO!
Drive 10 RGBs, leave serial, reset, xtal pins free still.

CrossRoads:
I'd go the other way 8) ATMega1284 with 32 IO!
Drive 10 RGBs, leave serial, reset, xtal pins free still.

the matters is the price.

with the price of 1 ATmega1284, u can get 2 ATmega48 or 3 ATtiny48.

Depends what you want to do with them too:
ATTiny48
Core Size- Speed- Connectivity- Number of I /O- Program Memory Size- Program Memory Type- EEPROM Size- RAM Size- Data Converters
8-Bit 12MHz I²C, SPI 24 4KB (2K x 16) FLASH 64 x 8 256 x 8 A/D 6x10b
Other:
Brown-out Detect/Reset, POR, WDT

ATMega1284P
8-Bit 20MHz I²C, SPI, 32 128KB (64K x 16) FLASH 4K x 8 16K x 8 A/D 8x10b
Dual UART/USART
Other:
Brown-out Detect/Reset, POR, PWM, WDT

The limited FLASH and RAM,and lack of a UART, would be a killer for most everything I've done with 328's so far.
If you have a smaller program, and '48 fits your needs, go for it.
Lots of variations out there to meet everyone's needs.

CrossRoads:
External hardware might be advisable too.
TLC5940 from ti.com. 16 channels.
WS2803 from world semi, 18 channels, but apparently only available via e-bay. I just received a tube of 25, have not tried them out yet.

Meh, I get my 2803s directly from Shenzhen. But then, I also order them in large quantities (though not recently ... haven't had a project needing a crap load of LEDs in recent months.) Same with 2801s - 15 cents a pop.

Hi Palatis (or anyone who wants to help),

I looked at the other two libraries you mentioned before finding yours, and I think yours is better (more efficient and uses TIMER1 instead of TIMER2, which I'm clocking asynchronously for an RTC).

But! I have a problem.

I'm making a modified version of Daniel Andrade's binary clock. What he does is strip the HH:MM into its separate digits, and decide whether each LED turns on or not depending on what the digit is. An exemplary line:

if(munit == 4 || munit == 5 || munit == 6 || munit == 7) {digitalWrite(3, HIGH);} else {digitalWrite(3, LOW);}

What I'm doing is using your PWM to allow me to dim the LEDs, like so:

if(munit == 4 || munit == 5 || munit == 6 || munit == 7) {SoftPWM.set(3, brightLevel);} else {digitalWrite(3, LOW);} //brightLevel is the divided analogRead of a pot

But when I do this, the LEDs that should be off stay on, and don't respond to the pot—as if they've been written HIGH! "Huh," I think to myself. "Must be something funny about his code. I'll just do this, then:"

if(munit == 4 || munit == 5 || munit == 6 || munit == 7) {SoftPWM.set(3, brightLevel);} else {softPWM.set(3, 0);}

And when I do that, sure enough, the LEDs that should be off stay off. But the window for brightLevel shrinks almost completely: the LEDs are full-on for most of the dial, then near-instantly switch to full-off. There are only a few degrees in which they're actually dimmed.

Any ideas as to what's going on, and how I can fix it?

Thanks!

Hi Randomizer,

I've used this most excellent SoftPWM library successfully for a few projects, I too found it very fast and efficient compared to the others you and the OP mention...

I think you may need to incorporate LED gamma correction in your code as humans don't percieve PWM'ed brightness linearly. Simply put, we discern the difference in LED brighness when we set the PWM from 0 to 5 much more than when we set the PWM from 250-255.

Using a lookup table to convert linear PWM values to exp gamma corrected values is a good solution.

For example:

//------------add this include at the top of your program---------------
#include <avr/pgmspace.h> // enables you to store the gamma table into flash memory instead of Static Ram.

uint8_t const exp_gamma[256] PROGMEM=
{0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,3,3,3,3,3,
4,4,4,4,4,5,5,5,5,5,6,6,6,7,7,7,7,8,8,8,9,9,9,10,10,10,11,11,12,12,12,13,13,14,14,14,15,15,
16,16,17,17,18,18,19,19,20,20,21,21,22,23,23,24,24,25,26,26,27,28,28,29,30,30,31,32,32,33,
34,35,35,36,37,38,39,39,40,41,42,43,44,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,
61,62,63,64,65,66,67,68,70,71,72,73,74,75,77,78,79,80,82,83,84,85,87,89,91,92,93,95,96,98,
99,100,101,102,105,106,108,109,111,112,114,115,117,118,120,121,123,125,126,128,130,131,133,
135,136,138,140,142,143,145,147,149,151,152,154,156,158,160,162,164,165,167,169,171,173,175,
177,179,181,183,185,187,190,192,194,196,198,200,202,204,207,209,211,213,216,218,220,222,225,
227,229,232,234,236,239,241,244,246,249,251,253,254,255
}; // end gamma correction LUT
//-------------------------------------------------------------------------------

//----------- add this line before your SoftPWM.set call----------------------------------------

brightLevel=pgm_read_byte_near(exp_gamma+brightLevel); // gamma transform

//--------------------------------------------------------------------------------------------------------

hello, i m trying this lib but PullUp keyword seems no to be valid ?
does the actual lib works with arduino 023 or 1.0 ?

Palatis:
they're expansive.

TLC5940 is over USD $6 from local retail store

About $1 on eBay: tlc5940 for sale | eBay

This is a great library, very easy and quick. I was wondering, however, if anyone can tell me if doing SoftPWM.set( i, 0 ) and SoftPWM.set( i, SoftPWM.brightnessLevels() -1 ) is the same as setting that PIN to LOW and HIGH, or is it still doing some PWM (I wish I had a scope...) ?

randomizer:
Hi Palatis (or anyone who wants to help),

I looked at the other two libraries you mentioned before finding yours, and I think yours is better (more efficient and uses TIMER1 instead of TIMER2, which I'm clocking asynchronously for an RTC).

But! I have a problem.

I'm making a modified version of Daniel Andrade's binary clock. What he does is strip the HH:MM into its separate digits, and decide whether each LED turns on or not depending on what the digit is. An exemplary line:

if(munit == 4 || munit == 5 || munit == 6 || munit == 7) {digitalWrite(3, HIGH);} else {digitalWrite(3, LOW);}

What I'm doing is using your PWM to allow me to dim the LEDs, like so:

if(munit == 4 || munit == 5 || munit == 6 || munit == 7) {SoftPWM.set(3, brightLevel);} else {digitalWrite(3, LOW);} //brightLevel is the divided analogRead of a pot

But when I do this, the LEDs that should be off stay on, and don't respond to the pot—as if they've been written HIGH! "Huh," I think to myself. "Must be something funny about his code. I'll just do this, then:"

if(munit == 4 || munit == 5 || munit == 6 || munit == 7) {SoftPWM.set(3, brightLevel);} else {softPWM.set(3, 0);}

And when I do that, sure enough, the LEDs that should be off stay off. But the window for brightLevel shrinks almost completely: the LEDs are full-on for most of the dial, then near-instantly switch to full-off. There are only a few degrees in which they're actually dimmed.

Any ideas as to what's going on, and how I can fix it?

Thanks!

by telling the library to manage the pin:

SOFTPWM_DEFINE_CHANNEL( 6, DDRD, PORTD, PORTD6 );

it manages the pin forever.
there's no way it can tell if it's been digitalWrite()-ed. (digitalWrite() doesn't tell the lib that this pin should stay LOW).

so you have to write

SoftPWM.set(pin, 0);

as for the brightness problem you had, humen eyes are not linear to brightness level, apply gamma correction as other post mensioned.

guy1ziv2:
This is a great library, very easy and quick. I was wondering, however, if anyone can tell me if doing SoftPWM.set( i, 0 ) and SoftPWM.set( i, SoftPWM.brightnessLevels() -1 ) is the same as setting that PIN to LOW and HIGH, or is it still doing some PWM (I wish I had a scope...) ?

the pin is still managed by SoftPWM even when you do

SoftPWM.set(pin, 0);
SoftPWM.set(pin, SoftPWM.brightnessLevels() - 1);

the codes update the counter and update pin states according to the counter on each timer interrupt.
it will be inefficient to check if a pin should be managed or not.

but by setting a pin to 0 or brightnessLevels() - 1, it stays LOW or HIGH because the cbi and sbi won't modify pin state.
for example, if it was LOW, after applying cbi it's still LOW; if it was HIGH, after applying sbi it's still HIGH.