SparkFun Forums 

Where electronics enthusiasts find answers.

Have questions about a SparkFun product or board? This is the place to be.
By robodude666
#27004
Hey,

I was wondering if anyone knew of some good example codes of how to get an LED to fade via software PWM using an interrupt? I managed to write something but it doesn't work the way I need it and some examples would help me figure out my problems better. If it helps, I am using the 18F4550 PIC. I will late adopt the code to PWM multiple LEDs as well as RGB LEDs so if that is in the example it would help greatly.


Thanks,
-robodude666
By saipan59
#27025
Here's an example, although it doesn't use interrupts - it just runs in an endless loop in main().
It drives 4 LEDs (each a different color). Each one fades in and out, but each at a slightly different rate, so it gives a 'random' pattern of changing colors.
It compiles under Hi-Tech C, but with minor changes would work under any C.
Code: Select all
void main(void)
{
	ushort delay1, delay2, delay3, delay4;
	utiny leds, pwm1, pwm2, pwm3, pwm4, count;
	bool pwm1_up, pwm2_up, pwm3_up, pwm4_up;

	// Select the 8.0 MHz internal clock.
	OSCCON = 0x70;
	
	OPTION = 0b00001111;
	
	// Setup port pins.
    TRISA = 0b11111111; //
    TRISC = 0b11110000; // 
    
	//
	// Main loop.
	//
	while(1)
    {
    	CLRWDT();
    	// Do the PWM calculations for each color.
    	count++;
        if (count < pwm1)
	        leds = (leds | RED); // red off
        else
	        leds = leds & (255-RED); // red on
	    if (count < pwm2)
	        leds = (leds | YELLOW); // yellow off
        else
	        leds = leds & (255-YELLOW); // yellow on
	    if (count < pwm3)
	        leds = (leds | BLUE); // blue off
        else
	        leds = leds & (255-BLUE); // blue on
	    if (count < pwm4)
	        leds = (leds | GREEN); // green off
        else
	        leds = leds & (255-GREEN); // green on
        PORTC = leds;
        
		// Do the bright/dim cycle for each color.
        delay1++;
        if (delay1 > 165)
        {
        	if (pwm1_up)
        	{
        		pwm1++;
        		if (pwm1 == 255) pwm1_up = FALSE;
        	}
        	else
        	{
        		pwm1--;
        		if (pwm1 == 1) pwm1_up = TRUE;
        	}
        	delay1 = 0;
        }
        
        delay2++;
        if (delay2 > 161)
        {	
        	if (pwm2_up)
        	{
        		pwm2++;
        		if (pwm2 == 255) pwm2_up = FALSE;
        	}
        	else
        	{
        		pwm2--;
        		if (pwm2 == 1) pwm2_up = TRUE;
        	}
        	delay2 = 0;
        }
        
        delay3++;
        if (delay3 > 157)
        {	
        	if (pwm3_up)
        	{
        		pwm3++;
        		if (pwm3 == 255) pwm3_up = FALSE;
        	}
        	else
        	{
        		pwm3--;
        		if (pwm3 == 1) pwm3_up = TRUE;
        	}
        	delay3 = 0;
        }
        
        delay4++;
        if (delay4 > 153)
        {	
        	if (pwm4_up)
        	{
        		pwm4++;
        		if (pwm4 == 255) pwm4_up = FALSE;
        	}
        	else
        	{
        		pwm4--;
        		if (pwm4 == 1) pwm4_up = TRUE;
        	}
        	delay4 = 0;
        }
    } // end main loop
} // main()
Pete
By robodude666
#27027
Thanks a lot Pete! That is pretty helpful. How would I change the frequency of the LED and the number of PWM steps? Is that done here?

if (delay2 > 161)

and 1-255 refers to the hex value of the LED?
like RGB 0x00 00 00 to 0xFF FF FF.

I will try to adopt that code to an interrupt.

EDIT: I noticed that is for an 8MHz micro. Mine is running at 48MHz and it seems to be going too fast and just blinking. Would multiplying all values by 6 (48/8) slow it down?

EDIt2: Where is count reset? Or does it always increase do +infinity? How does it blink then? If count gets to 100000 it will never be smaller than pwm1

Thanks!
-robodude666
By saipan59
#27030
First, simply the code to work with only one LED instead of 4, until you get it working. For example, use only the code that uses delay1, leds, pwm1, count, and pwm1_up.

The '1' and '255' values specify the min and max brightness (I'm using the full range).

The 165, 161, 157, and 153 values specify how quickly the brightness changes on each LED. Since the numbers are slightly different, they quickly get 'out of sync', and appear to be changing randomly.

Pete
By robodude666
#27037
Yea I know. I have it with 1 LED. Actually, I was modifying the code and realized it is about 95% the same as mine. The only difference is you choose to have two variables as counters. count and delayx. I just have count. you also change the pwmx +/- stuff a bit different. I have it as a

LEDPWM += (holder == 0) ? 1 : -1;

but same thing really...

Speaking of which, where do you clear count? I don't see how this could possibly work if it is not cleared. How could 10000 < 255? if you let it go for 10k while loops.
By saipan59
#27059
Speaking of which, where do you clear count? I don't see how this could possibly work if it is not cleared. How could 10000 < 255? if you let it go for 10k while loops.
It's declared as an 8-bit value, so when it gets to 255 it wraps around to 0.
What you said would be true if I had declared it as an int.

Pete
By powool
#27079
One issue with PWM using a polled loop is that if you happen to do any computation in addition, if the length of that computation varies, you can get 'speckling' - momentary bright or dark spots in the PWM cycling.

I am using an MSP430 to PWM all the LED's on the Sparkfun 4x4 buttonpad, and was doing an integer divide once in awhile that was enough to flicker my LED's. So I'll change over to interrupt driven and hope it cycles fast enough to give me good intensity control over all colors.
By daemondust
#27080
Not for a PIC (written for a MSP430F169), but using interrupts:
Code: Select all
#include <msp430x16x.h>
#include <signal.h>
#include <io.h>
#include "hue.h"
#include "main.h"
#include "clock.h"

volatile int count = STEPTIME;
volatile RGB color = {.r = 0, .b = 0, .g = 0};
volatile int wake = 1;
int main(void)
{
  int i;
  init();
  for(;;)
  {
    for(i = 0; i < 256; i++)
    {
      while(!wake);
      _DINT(); // stop interrupts
      hue2rgb(i, &color); // convert color
      _EINT(); // start interrupts
      wake = 0;
    }
  }
  return 0;
}


interrupt (TIMERB0_VECTOR) Timer_B(void)
{
  static RGB c = {.r = 0, .b = 0, .g = 0};
  static int ct = 0;
  if(ct--)
  {
    if(c.r) LEDPORT |= RED;
    else LEDPORT &= ~RED;
    if(c.g) LEDPORT |= GREEN;
    else LEDPORT &= ~GREEN;
    if(c.b) LEDPORT |= BLUE;
    else LEDPORT &= ~BLUE;
    if(c.r) c.r--;
    else c.r = 0;
    if(c.g) c.g--;
    else c.g = 0;
    if(c.b) c.b--;
    else c.b = 0;
  }
  else
  {
    ct = (1 << 8) - 1;
    c.r = color.r;
    c.g = color.g;
    c.b = color.b >> 1; // blue is much too bright...
    if(!count--)
    {
      wake = 1;
      count = STEPTIME;
    }
  }
  TBCCR0 += 250; // check back in 250 clocks
}

static inline void init(void)
{
  WDTCTL = WDTPW + WDTHOLD;
  LEDDIR = RED+GREEN+BLUE;
  CalibrateDCO();
  TBCTL = TBSSEL1 + TBCLR;
  TBCCTL0 = CCIE;
  TBCCR0 = 50000;
  TBCTL |= MC1;
}
Last edited by daemondust on Mon Mar 12, 2007 11:56 am, edited 1 time in total.
By robodude666
#27084
@saipan59: Woops, sorry! I didn't notice that. I will give it another shot after I eat.



Hey daemondust, thanks for the wonderful example. I never worked with any TI chips but its thankfully C (not asm like EVERY good fader code out there >_>)

I more or less understand it and just want to clarify a few things.

The for loop inside of the endless loop goes through the 256 different color values and you use this function (hue2rgb):
Code: Select all
typedef struct { unsigned char r, g, b; } RGB;

void hue2rgb(unsigned char hue, RGB* rgb)
{
  unsigned char i,q,t;
  unsigned int h;
  h = hue;
  h <<= 8;
  h *= 3;
  h /= 128;
  i = h >> 8;

  t = h - (i << 8);
  q = 255 - t;

  switch(i)
  {
  case 0:rgb->r = 255;rgb->g = t;rgb->b = 0;break;
  case 1:rgb->r = q;rgb->g = 255;rgb->b = 0;break;
  case 2:rgb->r = 0;rgb->g = 255;rgb->b = t;break;
  case 3:rgb->r = 0;rgb->g = q;rgb->b = 255;break;
  case 4:rgb->r = t;rgb->g = 0;rgb->b = 255;break;
  case 5:rgb->r = 255;rgb->g = 0;rgb->b = q;break;
  }
}
to get the RGB values based on the hue. Then you have the interrupt PWM(?) each LED to get that color. correct?

init() is just the MSP's configure function which enables the interrupt.

What is the variable "wake" ? I am not 100% clear as to what it does. And what does the ct variable do? Is it just used to fade the LED out?

And there are two sets of RGB values? color and c? color is set by the hue2rgb function and c is for inside the interrupt, correct?

One last thing: Why the hell are there two functions called Timer_B?

interrupt (TIMERB0_VECTOR) Timer_B(void)

One of them is complete and second isn't. Is it just a copy & paste error?

Sorry for the questions. Thank you very much for the example (you too saipan ^^)!

I will play around with these ideas and let you know how it turns out.

If it doesn't work out well, does anyone know of any good I2C ICs which can fade a LED (16 LEDs or 4 RGB LEDs)? I need it to be in both DIP and SMD packages if possibly.

Thanks A LOT!!!
-robodude666
By daemondust
#27086
init() turns off the watchdog timer, sets pin directions, ramps up the clock, and sets up the timer.

MSP430's have several low-power modes. wakeup simulates going into one of these low-power modes.

ct is there to show the same color many times, to slow the color change down.

There are two copies of the color so that I don't have to re-calculate it to show it again. In the interrupt I just decrement c so it's as fast as possible.

There're two TimerB() functions because I can't copy and paste. I'll fix that.
By robodude666
#27088
Alright, so as I thought. The init function is just something used by MCPs to configure the micro.

So the variable "wake" is a register f the MSP430 and has nothing to do with the actual fading of the LED? Just used to save some power :)

Well thanks a lot man! I really appreciate your help and everyone else's. Time to open MPLAB!

EDIT: From searching I noticed that TBCCR0 sets the interrupt period and you have it set to:

TBCCR0 = 50000;

What does that equal to in Hz?

EDIt2:

Saipan, w00t! I tried your code using an interrupt and it works well :) Thanks a lot! I will try to adopt daemondust's code because it has good, easy, support for RGB LEDs and RGB values (perfect for my project).

Thank you both for your help =D
By robodude666
#27121
Sorry for double post... I haven't used struct much in C so I am a little lost. Here is what I have:
Code: Select all
struct RGB hue2rgb(int hue);

struct RGB {
	int r, g, b;
} RGBB;

struct RGB hue2rgb(int hue){
	unsigned int i,q,t;
	unsigned int h;
	h = hue;
	h <<= 8;
	h *= 3;
	h /= 128;
	i = h >> 8;
	
	t = h - (i << 8);
	q = 255 - t;
	
	switch(i){
		case 0:RGBB.r = 255; RGBB.g = t;   RGBB.b = 0;   break;
		case 1:RGBB.r = q;   RGBB.g = 255; RGBB.b = 0;   break;
		case 2:RGBB.r = 0;   RGBB.g = 255; RGBB.b = t;   break;
		case 3:RGBB.r = 0;   RGBB.g = q;   RGBB.b = 255; break;
		case 4:RGBB.r = t;   RGBB.g = 0;   RGBB.b = 255; break;
		case 5:RGBB.r = 255; RGBB.g = 0;   RGBB.b = q;   break;
	}
	return RGBB;
} 
and I have
Code: Select all
	struct RGB color = {0, 0, 0};
	color = hue2rgb(3); // convert color
in main. Is there anything wrong? :S
Last edited by robodude666 on Tue Mar 13, 2007 11:38 am, edited 1 time in total.
By daemondust
#27147
robodude666 wrote:Sorry for double post... I haven't used struct much in C so I am a little lost. Here is what I have:
...
Code: Select all
	struct RGB color = {0, 0, 0};
	color = hue2rgb(3); // convert color
in main. Is there anything wrong? :S[/code]
That should work, but there's no guarantee that your compiler supports returning structs. Why the change from void hue2rgb(char, RGB*)?
By robodude666
#27164
daemondust wrote: That should work, but there's no guarantee that your compiler supports returning structs. Why the change from void hue2rgb(char, RGB*)?
It gave me compiler errors if I kept the pointer. Don't know. I never used structs in C before and I have always had issues with pointers.
By daemondust
#27166
robodude666 wrote: It gave me compiler errors if I kept the pointer. Don't know. I never used structs in C before and I have always had issues with pointers.
What compiler are you using? Passing pointers to a struct is the most common way to do things like this.