Thursday, October 13, 2011

LED fading using exponentially changing PWM duty cycle

Not wishing to make the title any longer than it is, I left out the following qualification: Although the duty cycle (pulse width for output high) varies exponentially versus time, the time interval between changes in duty cycle is fixed.

One could I suppose also achieve fading with a linearly changing duty cycle and exponentially changing time interval. Sounds more complicated though.

The problem with fading an LED using PWM is that human perception of brightness is logarithmic, just like our hearing. So changing the PWM duty cycle in a linear fashion (e.g. incrementing or decrementing the duty cycle by 1% every unit time interval) does not result in a perception of linear change in brightness.

The solution is to change the duty cycle exponentially. The following is one formula that provides a way of obtaining a series of duty cycle values with a minimum of 1 and a maximum of 2n, where n is a positive integer. The fact that the numeric base = 2 means the results are perfectly suited to computing values for 8, 16, or 32-bit registers of microcontrollers.

duty cycle = 2x/r

x = 1, 2, 3, ..., m-1, m
m = integral number of duty cycle values required.
r is chosen such that 2m/r = maximum duty cycle value

One way to understand x/r is to think of a number of rulers all of the same length--say, 12 inches. One has graduations only in inches. Another has divisions only in half inches (of course two half inches still make an inch). Yet another is marked off in quarter inches. One has one-eighth inch graduations. And another is divided into sixteenths of an inch. x corresponds to the number of graduations/divisions. Hence our 12-inch ruler with quarter-inch divisions has a total of 12 inches x 4 divisions/inch = 48 divisions. And the ruler with one-sixteenth inch graduations has 12 x 16 = 192 divisions. On the other hand, 1/r corresponds to the unit used by our ruler or the distance between graduations. So for inch-graduated ruler, 1/r = 1/1. For the one-eighth-inch-graduated ruler, 1/r = 1/8.

Let's say we are constrained to using an 8-bit register (eg. CCPR1L of the PIC12F615) for our duty cycle values. This means the maximum pulse width value we can specify is 0xFF or 255 decimal. Assume we want to make an LED fade in from zero to full brightness in a relatively short time--less than half a second. Having the LED ramp up 16 values should be sufficient for a visually smooth, seamless fade in. Going back to our ruler, if we employ the ruler with one-inch divisions, we'd have eight values and those values, as per our formula, would be 21/1, 22/1, ... 28/1. But since we'd like 16 different pulse widths, we have to use the half-inch-graduated ruler which has 16 divisions from 1/2 inch to 8 inches. To obtain the actual duty cycle values, we use the formula giving us the following series: 21/2, 22/2, 23/2, ... 215/2, 216/2, or 1.41, 2.00, 2.83, .... 181.02, 256.00. Rounding those numbers off we have the following integral series: 1, 2, 3, 4, 6, 8, 11, 16, 23, 32, 45, 64, 91, 128, 181, 255. Obviously, given our 8-bit constraint, the maximum duty cycle value has to be 255 not 256.

If we wanted 32 pulse width values--perhaps because we are fading the LED over a much longer time interval and 16 values don't provide a smooth enough fade--we'd choose the ruler with quarter-inch divisions. Using the equation we have: 21/4, 22/4, 23/4, 24/4, 25/4, ... 231/4, 232/4. Here's a graph of this example.

Bear in mind that the ruler analogy is only an aid to understanding. Once you get the hang of the equation, you can throw away these imaginary rulers. And contrary to what the ruler examples imply, we're not limited to 2n number of pulse widths (eg. 8, 16, 32, 64). Still taking our maximum duty cycle = 0xFF, we can have, for instance, 24 values: 21/3, 22/3, 23/3, 24/3, 25/3, ... 223/3, 224/3. Or, say, 40 values: 21/5, 22/5, ... 213/5, 214/5, 215/5, ... 239/5, 240/5. Or 56 values: 21/7, 22/7, ... 255/7, 256/7. Notice that for all these examples m/r is still = 8 and so maximum duty cycle value in all these series is 0xFF.

One may, if desired, also use the following slightly modified formula

duty cycle = 2x/r - 1

Using this equation forces the series to start with zero.

Given m = 16, r = 2, and thus a maximum duty cycle value of 216/2 = 0xFF, the series we end up with is: 0, 1, 2, 3, 5, 7, 10, 15, 22, 31, 44, 63, 90, 127, 180, 255. Note how the maximum is neatly the maximum possible for our 8-bit register.

Below is a sample program where an LED alternately fades in and out. The firmware employs a look-up table to change the duty cycle. PWM frequency is 244.14 Hz. This is the minimum PWM frequency obtainable in the PIC12F615 when using its internal oscillator (Fosc = 4MHz). That PWM frequency translates to a period of 4.096ms. Because 4.096ms x 16 duty cycle values is only = 65.5ms, I set timer2 postscaler to 1:10 which has the effect of holding each duty cycle value for 10 periods. With that the amount of time for the LED to go from minimum to maximum duty cycle = 4.096ms x 16 x 10 = 655ms which is slow enough for humans to actually see and appreciate. The downside is that keeping the DC value for 10 periods is noticeable at the lower DC values. The stepwise change is obvious. To remedy this, we can always increase the number of DC values and decrease the timer2 postscale value.

For the PIC12F615,

fPWM = Fosc / [(PR2+1)*4*(T2PS)]

fPWM = PWM frequency
Fosc = frequency of the oscillator
PR2 = PR2 register value
T2PS = timer2 prescale value

PWM period = 1 / fPWM

Pulse width = (CCPR1L:CCP1CON<5:4>)*(T2PS)/Fosc

CCPR1L:CCP1CON<5:4> is a 10-bit number, with the 8-bit MSB in CCPR1L and the 2-bit LSB in CCP1CON<5:4>

In the firmware below CCP1CON<5:4> is = 0. When using the pulse width equation remember to left shift CCPR1L by two places. In other words multiply CCPR1L by 4.

LED is connected to pin P1A/GP2.

LED fading using exponentially changing PWM duty cycle with fixed time interval between changes in duty cycle

October 12 2011

processor = PIC12F615
compiler = mikroC v5.0.0


// computed duty cycle values using the equation INT{[2^(x/r)] + 0.5} - 1
// x = 1, 2, ..., 16
// r = 2
// values are stored in a look-up table in flash rather than RAM
const char PW[] = {0, 1, 2, 3, 5, 7, 10, 15, 22, 31, 44, 63, 90, 127, 180, 255};  

void main()
  unsigned char i = 0;
  bit direction;
  ANSEL = 0;                 // all pins digital

  // initialize PWM registers
  // see PIC12F615 datasheet DS41302C p.96 for PWM set up procedure
  PR2 = 255;
  CCP1CON = 0b1100;          // enable PWM mode, single output (P1A only)
  CCPR1L = 0;
  PIR1.TMR2IF = 0;           // clear timer2 interrupt flag
  T2CON = 0b1001011;         // timer2 prescale = 1:16, postscale = 1:10
  T2CON.TMR2ON = 1;          // turn on timer2
  TRISIO = 0b0;

  direction = 1;
    if (PIR1.TMR2IF)          // timer2 interrupt flag is set when postscale value is reached 
      if (direction && ++i > 15)
        direction = 0;
        i = 14;
      else if (!direction && --i == 0)
        direction = 1;
      CCPR1L = PW[i];
      PIR1.TMR2IF = 0;
  } // while(1)
} // void main()



1 comment:

  1. Great article! It was hard to find a detailed explanation on this subject.