Go Down

Topic: 12-bit PWM for LED lighting (Read 23387 times) previous topic - next topic

gge5

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?

PaulS

Quote
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 art of getting good answers lies in asking good questions.

johnwasser

#2
Nov 05, 2012, 01:43 am Last Edit: Nov 05, 2012, 01:04 pm by johnwasser Reason: 1
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.

Code: [Select]

 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.
Send Bitcoin tips to: 1G2qoGwMRXx8az71DVP1E81jShxtbSh5Hp

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.

gge5


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.


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.

cjdelphi

#5
Nov 05, 2012, 05:42 am Last Edit: Nov 05, 2012, 05:45 am by cjdelphi Reason: 1
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.

johnwasser


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.
Send Bitcoin tips to: 1G2qoGwMRXx8az71DVP1E81jShxtbSh5Hp

dhenry

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.

PeterH


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?

Grumpy_Mike

Why such a fast PWM, that makes little sense.

gge5


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.

gge5


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:
Code: [Select]
//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!

gge5

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.

Code: [Select]
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};

Jervelund

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

DemoManta

@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:
http://www.compuphase.com/electronics/currentsource.htm

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!

Go Up