• Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

16F648A - PWM and RGB Leds

Status
Not open for further replies.

O-Zone

New Member
Hi all,
i am trying to use PWM to fade one or mode RGB leds using a PIC 16F648A. I use SDCC and PikLab. Here's the code:

Code:
#include <pic16f648a.h>

/* ----------------------------------------------------------------------- */
/* Configuration bits: adapt to your setup and needs */
typedef unsigned int word;
word at 0x2007 CONFIG = _WDT_OFF & _PWRTE_OFF & _INTOSC_OSC_NOCLKOUT & _MCLRE_ON & _BOREN_ON & _LVP_OFF & _DATA_CP_OFF & _CP_OFF;

#define setBit(var, bitnum)   (var)|=(1<<(bitnum))
#define resBit(var, bitnum)   (var)&=~(1<<(bitnum))
#define clearBit(var, bitnum) (var)&=~(1<<(bitnum))
#define testBit(var, bitnum)  (var)&(1<<(bitnum))

void delay(long ms) {
	long i;
	while (ms--) {
		for (i=0; i < 230; i++) {
			__asm nop __endasm;
		}
	}
}

#define LEDA	    RB3
#define LEDA_TRIS  TRISB3
#define LEDA_RED   RB0
#define LEDA_RED_TRIS  TRISB0
#define LEDA_GREEN RB1
#define LEDA_GREEN_TRIS TRISB1
#define LEDA_BLUE  RB2
#define LEDA_BLUE_TRIS  TRISB2

/* RGB PWM Channels */
unsigned short pwmChans[3] = {1,80,160}, pwmState[3], pwmPin[3]={0,1,2};
char *pwmPort;
unsigned short timerTick=0,portShadow=0;

void doPwm() {
	unsigned short i;
	for(i=0;i<3;i++) {
		if(timerTick == 0 && pwmChans[i] != 0) {             // reset at start.
			setBit(portShadow, pwmPin[i]); 
			*pwmPort=portShadow;
			pwmState[i] = 0;
		} else if(timerTick == pwmChans[i] && pwmChans[i] != 255) { // set at duty.
			resBit(portShadow, pwmPin[i]); 
			*pwmPort=portShadow;
			pwmState[i] = 1;
		}	
	}
}

static void Intr(void) interrupt 0 { /* interrupt service routine */
	timerTick++;
	if(timerTick >= 254) timerTick=0;
	doPwm();
	T0IF = 0;
}

void main() {
	OSCF=1; /* 4 MHz CLock */
	CMCON = 0x07;
	CCP1CON=0x00;
	/* Set LED driver's line as OUT */
	LEDA_TRIS = 0;
	LEDA_RED_TRIS = 0;
	LEDA_GREEN_TRIS = 0;
	LEDA_BLUE_TRIS = 0;
	/* Turn all LEDs OFF */
	LEDA_RED = 1;
	LEDA_GREEN = 1;
	LEDA_BLUE = 1;
	/* Setup PWM */
	pwmPort = (char *)&PORTB;
	portShadow = PORTB;
	/* Enable Timer0 */
	T0CS = 0;	// Clear to enable timer mode.
	PSA = 0;	// Clear to assign prescaler to Timer 0.

	PS2 = 0;	// Set up prescaler to 1:256.  
	PS1 = 0;
	PS0 = 0;

	INTCON = 0;	// Clear interrupt flag bits.
	GIE = 1;	// Enable all interrupts.
	
	T0IE = 1;	// Set Timer 0 to 0.  
	TMR0 = 0;	// Enable peripheral interrupts.

	while(1) {
	}	
}
PWM code seems to work (it' in an embrional stage) *but* the interrupt is too slow, so i can see leds blink instead of stable color. Maybe i have to use Timer1 or Timer2 ?

Thanks, O-Zone
 

testerplus

New Member
Hi all,
i am trying to use PWM to fade one or mode RGB leds using a PIC 16F648A. I use SDCC and PikLab. Here's the code:

PWM code seems to work (it' in an embrional stage) *but* the interrupt is too slow, so i can see leds blink instead of stable color. Maybe i have to use Timer1 or Timer2 ?

Thanks, O-Zone

You see the blinking because of PWM period is to long (TMR0 period = 512us, PWM period = 254 periods of TMR0 ~ 130 ms ~ 8 Hz!). You need to decrease TMR0 period and PWM resolution (now it is 8-bit). The problem is that your pwm() routine is too slow (dou to indirrect addressing).

Look at the code:
Code:
#define PWM_PERIOD_CONST    255
#define TMR0_PERIOD_CONST    20

// This routine executes at 20-35 cycles 

void doPwm() 
{
    // Check for duty cycles
    if (timerTick > pwmChans[0]) LEDA_RED = 0;
    if (timerTick > pwmChans[1]) LEDA_GREEN = 0;
    if (timerTick > pwmChans[2]) LEDA_BLUE = 0;

    // Check for end of PWM period
    if (timerTick++ >= PWM_CYCLE_CONST)
    {
        timerTick = 0;
        if (pwmChans[0]) LEDA_RED = 1;
        if (pwmChans[1]) LEDA_GREEN = 1;
        if (pwmChans[2]) LEDA_BLUE = 1;
    }
}

// Interrupt code

static void Intr(void) interrupt 0 
{ 	
    T0IF = 0;
    [B]TMR0 -= TMR0_PERIOD_CONST[/B];
    doPwm();
}
By modifiyng constants PWM_PERIOD_CONST and TMR0_PERIOD_CONST you can to find best compromise between PWM speed and PWM resulution. Are you sure you need to use 8-bit PWM? If it is just a lamp, then 4 or 5 bits will be anougth (set pwm const to 16 or 32). PWM frequency sould be at least 50 Hz.
 

Sceadwian

Banned
I've seen 4bit PWM before, it's pretty grainy looking unless you're fading the LED's in and out very fast. 5 bits would be smoother. If you're going for a true smooth flowing fade in and out you'll need to make a sine chart and step through that rather than simply linearly increasing or decreasing the PWM width. linear fade in and out appears 'abrupt' to the human eye. Sine wave fadeing appears very natural. For sine wave fading I would highly suggest full 8 bits.
 
Last edited:

testerplus

New Member
I've seen 4bit PWM before, it's pretty grainy looking unless you're fading the LED's in and out very fast. 5 bits would be smoother. If you're going for a true smooth flowing fade in and out you'll need to make a sine chart and step through that rather than simply linearly increasing or decreasing the PWM width. linear fade in and out appears 'abrupt' to the human eye. Sine wave fadeing appears very natural. For sine wave fading I would highly suggest full 8 bits.
A agree. 4 or 5 bits is rather bad resolution for sinus generating. But in case of night lamp (I just don't sure that topic autor makes a lamp) 5 bits will be anought. In any case it will be better to increase PWM frequency.

__________________

OSA - open source cooperative RTOS for Microchip PIC-controllers
 
Last edited:

Sceadwian

Banned
Oh one thing I forgot to suggest, even with slower PWM's if you want smoother dimming and don't need fast on and off you can toss a mid value capacitor on each LED leg. The LED's current limit resistor and the capacitor will form a low pass filter which would be a hardware way of reducing flicker, and smooth out steps if the bit depth is lower than you want. I'm particularly sensitive to flicker and LED's are hyper abrupt light sources when they do flicker so I'd do the capacitor anyways.
 
Last edited:

Sceadwian

Banned
Gayan it depends entirely on the speed of the dimming. Lets wait till O-zone replies to the last few posts before we flood him with too much information at once =)
 

Gayan Soyza

Active Member
With less stpes like 16, 32 when fading LEDs specialy the dim side is worse. From “OFF” position to the “FIRST DIM” position it has a big gap, this look likes the LED is starting from the “MID” brightness. Also you can see its changing steps.

This can prevent if you have more step size with a decent PWM frequency.

If you use more steps you don’t need that much of higher PWM frequencies.

Recently I generate a system with 256 levels with a 50Hz PWM frequency. Its smoothness is extremely superb.
 

O-Zone

New Member
Thanks to all for your replies. In past days i've tried some solutions by myself. I'm able to reduce flickering using an external 20MHz crystal and configure PIC with __HS_OSC. But if i add one more led, also 20Mhz seems to be a bit slower freq. So i set TRM0 ad 0xE0 on every interrupt to have it faster. Seems not to be enought...maybe i post the new code this evening (now i'm at work). Thanks however for all hints !!!

O-Zone
 

O-Zone

New Member
Ok, after some tests (that were too slow or didn't work) here's the pwm code:

Code:
/* RGB PWM Channels */
int led1Dir[3] = { -1,1,-1 };
int led2Dir[3] = { 1,-1,-1 };
int led3Dir[3] = { -1,1,1 };

unsigned char led1Duty[3] = { 100,20,100 };
unsigned char led2Duty[3] = { 10,130,200 };
unsigned char led3Duty[3] = { 80,100,20 };

unsigned char led1Pin[3] = { 0, 1, 2 }; /* RB0, RB1, RB2 */
unsigned char led2Pin[3] = { 5, 6, 7 }; /* RB5, RB6, RB7 */
unsigned char led3Pin[3] = { 0, 1, 2 }; /* RA0, RA1, RA2 */

unsigned char timerTick=0;

unsigned char doPwm(unsigned char pwmDuty[], unsigned char pwmPin[]) {
	unsigned char i;
	for(i=0;i<3;i++) {
		if(timerTick == 0 && pwmDuty[i] != 0) {             // reset at start.
			pwmPin[i] = 0;			
			// resBit(pShadow, pwmPin[i]); 
		} else if(timerTick == pwmDuty[i] && pwmDuty[i] != 255) { // set at duty.
			//setBit(pShadow, pwmPin[i]); 
			pwmPin[i] = 1;
		}
	}
	return 0;
}

static void Intr(void) interrupt 0 { /* interrupt service routine */
	unsigned char i;
	GIE=0;
	if(T0IF) {
		doPwm(led1Duty,led1Pin);
		doPwm(led2Duty,led2Pin);
		doPwm(led3Duty,led3Pin); 
		timerTick++;
		if(timerTick >= 254) {
			timerTick=0;
			for(i=0;i<3;i++) {
				led1Duty[i] += led1Dir[i];
				if((led1Duty[i] <= 0)||(led1Duty[i] >= 255)) {
					led1Dir[i] = led1Dir[i]*-1;
				}
				led2Duty[i] += led2Dir[i];
				if((led2Duty[i] <= 0)||(led2Duty[i] >= 255)) {
					led2Dir[i] = led2Dir[i]*-1;
				}
				led3Duty[i] += led3Dir[i];
				if((led3Duty[i] <= 0)||(led3Duty[i] >= 255)) {
					led3Dir[i] = led3Dir[i]*-1;
				} 
			}
		}
		TMR0 = 0xE0; 
		T0IF = 0;
	}
}
but i need to find any way to switch port's pins faster. First proposal is good if i use only one led but with tree leds i aims to find a non-redundand way to do pwm !

Thanks for replies. I promise when i finish the project i'll post complete code and pcb :)

O-Zone
 
Status
Not open for further replies.

EE World Online Articles

Loading
Top