LED fader

Andrew Cowan

Senior Member
My current project - a four channel RGBY LED fader.

I'm using a 28X2 at 1MHz in order to get four PWM channels at 98Hz.

The finished project will be much more complex and controlled via a radio link, but to start, I'm trying to write a fading subroutine.

I'm not worried about the fading rate at the moment - that's just a matter of adding a pause in the loop.

The idea is to step through a loop, slowly adjusting the PWM rate until it is desired. The tricky part is to adjust all four PWM channels at the correct rate - eg if red needs to increase by 200, and green needs to increase by 50, the red channel should increase at four times the rate of the green channel.

I've written this code several times in C before for RGB faders - with signed, floating point variables, it's pretty simple:

Code:
void Fade (byte ActualR, byte ActualG, byte ActualB, byte TargetR, byte TargetG, byte TargetB, byte rate)
{  
  int RDiff = TargetR - ActualR;    //signed int showing change and direction
  int GDiff = TargetG - ActualG;
  int BDiff = TargetB - ActualB;
   
  int NoOfSteps = max(abs(RDiff),max(abs(GDiff), abs(BDiff)));
 
  
   for (int i = 0; i <= NoOfSteps; i++)
   {
   int NewR = ActualR + (i * RDiff / NoOfSteps);
   int NewG = ActualG + (i * GDiff / NoOfSteps);   
   int NewB = ActualB + (i * BDiff / NoOfSteps);   
   
   delay(4 * rate);
   
   setnewPWMrate(NewR, NewG, NewB);
     
   } 
}
However, as PICAXE doesn't have floating point or signed varibles, I'm a bit stuck.

The basic functionality should be a subroutine called 'set' that fits into code such as:

Code:
setfreq m1

Symbol oRe = C.1
Symbol oGr = C.2
Symbol oBl = B.0
Symbol oYe = B.5

Symbol ActualR = b0
Symbol ActualG = b1
Symbol ActualB = b2
Symbol ActualY = b3

Symbol TargetR = b4
Symbol TargetG = b5
Symbol TargetB = b6
Symbol TargetY = b7

init:
	low oRe
	low oGr
	low oBl
	low oYe	
	
	pwmout pwmdiv16, oRe, 158, 0
	pwmout pwmdiv16, oGr, 158, 0
	pwmout pwmdiv16, oBl, 158, 0
	pwmout pwmdiv16, oYe, 158, 0

main:
	gosub red
	gosub set
	gosub orange
	gosub set
	goto main
	
set:
	'start loop
	pwmduty oRe, ActualR
	pwmduty oGr, ActualG
	pwmduty oBl, ActualB
	pwmduty oYe, ActualY
	'carry on round loop
	return
	
	
red:
	TargetR = 255
	TargetG = 0
	TargetB = 0
	targetY = 0
	return
	
orange:
	TargetR = 255
	TargetG = 0
	TargetB = 0
	targetY = 255
	return
Any thoughts or inspiration on designing this would be very appreciated!

I think that Actual and Target PWM duty should be a 0-255 scale; this can then easily be scaled by about 2.5 to the 0-636 required by a 98hz PWM.

One thing to bare in mind is that I'm running the chip at 1MHz, so a complex loop might take quite a while to go through!

Many thanks

Andrew

Note: don't worry about small issues such as the required duty cycle (0-636) not fitting in a byte variable - that's fine!).
 

geoff07

Senior Member
Interesting. Another reason for having floating point. Which I think would be a good next step after having the extra memory and clock speed of the new M2 parts.

But is there not some scope for using word integers and scaling them? By which I mean treat the upper byte as the integer value and the lower byte as the fractional part? I haven't worked out your example but perhaps there is a possibility there?
 

Andrew Cowan

Senior Member
The problem with scaling words is the degree of scaling required.

For example, if the red LED needs to increase by 255 steps, and the green by 253 steps, then the green needs to increase every 1.0079051383 steps.

For a chip supporting floating points, it will do the two steps that the green LED doesn't get brighter on evenly thoughout the process - that's a lot harder to do with PICAXE.

In addition, running the chip at 1MHz makes it really slow to do a 255 long loop - I gave it a go, but found it wasn't really working.

Solution: I think this is one task for a raw PIC programmed in C - that raw PIC can then be controlled by the PICAXE. I programmed the raw PIC this afternoon - about 50 lines of code are needed for full 8 bit PWM control of four channels, with adjustable fading.

A
 

geoff07

Senior Member
But how much variation can the eye detect? For hearing it is about a 1db step. Possibly this level of precision is a bit more than absolutely necessary? Unless you are doing colour mixing for image displays I suppose.
 

Andrew Cowan

Senior Member
While it's true you really don't need 8 bit precision, it's the 'standard' I've used for all the RGB lights in my setup.

I did try a 0-100 range with the 28X2, but it was still far too slow.

A
 

hippy

Technical Support
Staff member
I don't think it's that hard to do with a PICAXE. Running at 1MHz is a red herring. The PWMOUT frequency can be anything you want so it's then a question of choosing a period which suits the 100% duty value, ie 63 period for a max 255 duty, 255 for 1023.

Putting that to one side, it's then just a question of 'where you want to be' minus 'where you are now' divided by 'how many steps left to get there'. Add that amount on, repeat for the next step.

I'd use a word value to hold the current duty, the top 10 bits as the duty value for a PWMOUT with a period of 255. the target duty could then be 0 to 255, the MSB of a word value.

Here's some example code which will fade a LED to a pot value so it tracks any change over a period of ( nominally, but not optimised to deliver ) 5 seconds ....

Code:
#Picaxe 08M2

Symbol ADC_CHN    = 1
Symbol PWM_PIN    = 2

Symbol target     = w0
Symbol target.msb = b1
Symbol current    = w1
Symbol delta      = w2
Symbol limit      = w3
Symbol duty       = w4
Symbol steps      = w5
Symbol last       = w6

PwmOut PWM_PIN, 255, 0

Do
  ReadAdc ADC_CHN, target.msb

  If target = last Then
    steps = steps Min 2 - 1
  Else
    last = target
    steps = 500
  End If

  If target <> current Then
    If target > current Then
      delta = target - current / steps
      limit = 65535 - delta
      current = current Max limit + delta
    Else
      delta = current - target / steps
      limit = delta
      current = current Min limit - delta
    End If
  End If
  
  duty = current / 64
  PwmDuty PWM_PIN, duty

  Pause 10
Loop
 
Top