12-bit PWM for LED lighting

I need to set up Timer1 on Pin9 to smoothly control the brightness of an LED.

I would like 12-bit accuracy (4096 levels), as fast as possible (no prescaling). I need Fast PWM.

I've read through many articles and tutorials, but my mind is still boggled on how to set it up.

I don't need any interrupts, just the PWM signal. Can anyone provide the setup code?

I need to set up Timer1 on Pin9 to smoothly control the brightness of an LED.

I would like 12-bit accuracy (4096 levels)

Do you think you'll be able to see 4096 levels of brightness? Most people can't see even the normal 255 levels that the 8 bit PWM output can define.

The Timer/Counter 1 hardware has built-in constants for 8-bit, 9-bit, and 10-bit PWM. To make it do 12-bit you need to use a register to hold the value for TOP. You can use WGM 14 with ICR1 as TOP or WGM 15 with OCR1A as TOP. Since you want to use Pin 9 (OC1A) for your PWM you can't use the OCR1A register for TOP so you have to use the WGM 14 and the ICR1 register.

  TCCR1A = (1 << COM1A1) | (1 << COM1A0) | (1 << WGM11);   // Enable Fast PWM on OC1A (Pin 9)
  TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);         // Mode 14 (TOP = ICR1), pre-scale = 1
  TCCR4C = 0;

  // Set the TOP value for 12-bit PWM
  ICR1H = 4095 >> 8;
  ICR1L = 4095;

  //  Set the PWM output to full off.
  OCR1AH = 0;   
  OCR1AL = 0;

  TCNT1H = 0;   // Initialize the timer count
  TCNT1L = 0;

With no pre-scale the output frequency will be 16 MHz / 4096: about 3.9 kHz.

Before you embark on such a task, ask yourself if you really have those out-of-this-world kind of eyes that require 12-bit pwm.

If you really do, you can set up the timer with its top at dc / 4096 - dc and use an interrupt to flip pins.

PaulS:
Do you think you'll be able to see 4096 levels of brightness? Most people can't see even the normal 255 levels that the 8 bit PWM output can define.

dhenry:
Before you embark on such a task, ask yourself if you really have those out-of-this-world kind of eyes that require 12-bit pwm.

If you really do, you can set up the timer with its top at dc / 4096 - dc and use an interrupt to flip pins.

That is a valid question. Yes, for my project I would like 4096 levels at least. It is for a dawn-simulator lightbox. It needs to be able to go as low as 5 lumens and as high as 10,000 lumens.

The eye's perception of light is logarithmic, but the PWM is linear. I need enough resolution so that the dim end can fade smoothly. The resolution is not so much of a problem at the high end where the eye can't tell minute differences. Perhaps if there was such thing as a logarithmic PWM then I could get by with 256 levels no problem.

My LED driver is based off the LM3404HV which can accept pulse-width modulation up to 1MHz. I would ideally like to reach 200KHz while maintaining resolution so that I can keep my PCB components small (such as the inductor and output capacitor). However, I might have to settle for only 10's of KHz.

You'd be surprised, 255 steps is quite a lot, i'm using an old 800-1000 lumens SSC LED which draws around 800ma at 3v or so, i could increase it to about 2amps, but the heat is not worth it.

But anyway how about this for an idea (never tried, might be way off but..)

Use a 300 ohm resistor from a PWM pin, and use it to charge a Capacitor (from 0v to 5v) via analogWrite, now you can slowly fill that capacitor (analogWrite(pwmpin,20)) charge it even faster with 50 or 100 with a few careful tweaks, you should be able to control the cap's voltage, maybe a 1 - 10k bleed resistor (higher the slower to discharge) have that resistor fit your needs.

last but not least, connect the + of the cap to your the base/gate, control the voltage of the cap and you have an output voltage anywhere between 0.0 - 5v this should significantly give you more than 255 levels of brightness.

GGE5:
I would ideally like to reach 200KHz while maintaining resolution so that I can keep my PCB components small (such as the inductor and output capacitor). However, I might have to settle for only 10's of KHz.

200 kHz and 4096 levels requires an 819.2 MHz clock rate.

20 kHz would require 81.92 MHz.

With the Arduino's 16 MHz clock you get less than 4 kHz PWM with 4096 levels.

I think you will need external hardware like the TLC5940. It has 16 channels of 12-bit PWM. Unfortunately the greyscale clock can only go up to 30 MHz so you won't get even 10 kHz PWM out of it. Similarly the LT8500 can only handle a 25 MHz clock.

You might need to design custom hardware with a 1 GHz clock. Emitter Coupled Logic should be fast enough.

ON Semiconductor makes the MC10E016: 5V ECL 8-Bit Synchronous Binary Up Counter. Hook two together to get a 16-bit counter. Then use a 12-bit compare for the PWM output and a register you can load. ON Semi also makes the MC10E166: 5V ECL 9-Bit Magnitude Comparator. Two of those and some external logic should allow you to do a 12-bit comparison.

Depending on your hardware set-up, you can overlay two pwm pulse trains together: a 50% dc pulse train ANDing a 25% dc pulse train produces a 12.5% pulse train.

So you can use your avr to produce a 10bit pwm, and then another time to produce a 2bit pwm, ANDing them will produce a 12-bit pwm.

How to AND them will be design specific.

GGE5:
The eye's perception of light is logarithmic, but the PWM is linear. I need enough resolution so that the dim end can fade smoothly. The resolution is not so much of a problem at the high end where the eye can't tell minute differences. Perhaps if there was such thing as a logarithmic PWM then I could get by with 256 levels no problem.

Is it feasible to power the LEDs via two circuits configured to provide different currents, to extend the range of brightness that can be achieved with boring old 8-bit PWM?

Why such a fast PWM, that makes little sense.

Grumpy_Mike:
Why such a fast PWM, that makes little sense.

It turns out I was misinterpreting the DS1404 datasheet. The 200KHz frequency is the rate at which it implements it's own feedback loop to maintain a steady output current.

The input PWM dimming signal should be somewhere around 10KHz, which this solution will provide.

johnwasser:
The Timer/Counter 1 hardware has built-in constants for 8-bit, 9-bit, and 10-bit PWM. To make it do 12-bit you need to use a register to hold the value for TOP. You can use WGM 14 with ICR1 as TOP or WGM 15 with OCR1A as TOP. Since you want to use Pin 9 (OC1A) for your PWM you can't use the OCR1A register for TOP so you have to use the WGM 14 and the ICR1 register.

I modified your code a bit to work. I had to set the pinMode of 9 to output before anything else. I had to erase your line setting TCCR4C because there is no Timer4 on my ATMEGA328 (and it was throwing an error), but also because TCCRxC is only active in non-PWM modes.

Also, when using C code, you don't have to set the HIGH and LOW registers separately, as the compiler takes care of that for you.

Here is the working code:

//12-Bit PWM on PIN9 using direct access to Timer1
//Fade in and out an LED connected to that pin

#define LEDValue OCR1A

const int PWMMax = 4095;
int value = 1;
int direction = 1;

void setup() { 
  pinMode(9,OUTPUT);
  
  TCCR1A = (1 << COM1A1) | (1 << WGM11);                // Enable Fast PWM on OC1A (Pin 9)
  TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);   // Mode 14 Fast PWM/ (TOP = ICR1), pre-scale = 1
  
  ICR1 = PWMMax;  //Set the TOP value for 12-bit PWM
  LEDValue = 0;      //Set the PWM output to full off.
}

void loop() {
  //Fade the LED between 0 and PWMMax and then back to 0
  value += direction;
  if (value <=0){
    direction = 1;
  }
  else if (value >= PWMMax){
    direction = -1;
  }
  LEDValue = value;
  delay(1);
}

Thank you so much for your help!

Just a quick update. I wrote a program that let me visually map the apparent brightness of the LED vs its PWM value. Sure enough, the eye is logarithmic, so the PWM needs to be exponential. I was able to determine about 85 apparent brightness levels from the 12-bit (4096 level) PWM. I made a lookup table so I don't have to mess with equations.

const unsigned int PWMTable[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13,
  16, 19, 23, 26, 29, 32, 35, 39, 43, 47, 51, 55, 60, 66,
  71, 77, 84, 91, 98, 106, 114, 123, 133, 143, 154, 166,
  179, 192, 207, 222, 239, 257, 276, 296, 317, 341, 366,
  392, 421, 451, 483, 518, 555, 595, 638, 684, 732, 784,
  840, 900, 964, 1032, 1105, 1184, 1267, 1357, 1453, 1555,
  1665, 1782, 1907, 2042, 2185, 2339, 2503, 2679, 2867, 3069,
  3284, 3514, 3761, 4024, 4096};

Did you manage to get clear transitions in the lowest levels with the 12-bit PWM? My experience is the default PWM steps in the low range (0-8, as you are also using) are clearly visible

@GGE5,
Thanks for posting this code. I modified it to test out controlling a current control LED. I needed a voltage reference produced by treating the PWM output as a DAC. I did this by using this circuit modified a bit:

I changed out the diode for a 10uF capacitor and a resistor in parallel to each other. I also buffered the input to the transistor with a NPN signal transistor (2N2222). The NPN got the 2K resistor on its collector. The Emitter went to the base of the original transistor. So basically the NPN was setup as input stage for the power resistor. Then I fed a 10K ohm resistor into the signal transistor base. That is where I feed in the PWM output of the Arduino. An opamp "might" be simpler for this.

The result is none of the PWM frequency is getting to the LED. The LED is still being current controlled and has no flicker or surge current. I needed that because my LEDs are pulling 200mA at 12VDC. The current surges were messing up my camera power and image. So I needed a digitally controlled current controller.

Your code allowed me to control the range of voltage to a finer degree. I am for testing feeding it a 10-bit analog signal bit shifted to 9 bits and sent to the PWM. This gives me 9 bits over a range that fits the light control perfectly. The next step is make all this smaller and giving myself more channels. Woot!

GGE5:
The input PWM dimming signal should be somewhere around 10KHz, which this solution will provide.

Is it possible to have these 4096 steps with a lower frequency? ( <1khz )
I am asking because I have a led driver which is PWM dimmable but the specifications say the frequency has to be between 100 Hz and 1 KHz. :confused:

Old thread rivived.
I use the PCA9685 in my home lighting system.
The PCA9685 has 16 channels of 12-bit (4096 steps) dimming.
Default frequency is 200hz, but can be set between 40hz and 1Khz.
I use this library.
It uses a lookup table for smooth lineair (for the eye) dimming.
The IC is controlled with a two wire interface (I2C).
Picture of my home made 16 channel LED driver attached.

Adafruit has a PCA9685 breakout board (without LED drivers).
And their own library.
You should be able to connect your LED driver to one of the outputs.
And have 15 outputs spare.
Leo..

thank you very much for you answer!

So, I bought the Adafruit board you mentioned above, and since I couldn t find any Adafruit library concerning Leds and dimming (their library is only concerned with servos ( GitHub - adafruit/Adafruit-PWM-Servo-Driver-Library: Adafruit PWM Servo Driver Library ) I am trying to use the library you linked above, since they are both using the PCA9685. (It would be great if I could use it cause it uses the look up table for smooth (to the eye) dimming)
When I am trying to upload the example code, it gives me this error message " error: 'class PCA9685' has no member named 'setPWMFrequency' ".

What am I missing here?

(print screen image attached)

Add #include <Wire.h> in the code
Edit the <Wire/Wire.H> inside the PCA9685.h file to <Wire.h> with e.g. "Notepad++" (!not windows notepad!)
Or replace the PCA9685.h file with the one attached.
Works with IDE 1.6.1

The Adafruit library should work for servos and LEDs.
Don't know. Happy with this one.
Leo..

PCA9685.zip (3.46 KB)