Frequency of PWM

Thread Starter

ActivePower

Joined Mar 15, 2012
155
How do you intuitively pick the PWM frequency for different applications?
Does it depend on the device switching characteristics?
For example, an LED could be turned ON and OFF relatively quickly compared to say, a DC motor.
Does this dictate higher PWM frequency for a motor when compared to an LED?
What other factors (if any) need to be taken into consideration?

To begin with, here is my PWM code:

Rich (BB code):
//main.c
//To use PWM for dimming an LED
//PWM Frequency: 19.53 MHz
//CCP1 Register Pin=RC2

#include<htc.h>
#define _XTAL_FREQ 20000000

__CONFIG(FOSC_HS & WDTE_OFF & DEBUG_ON & LVP_OFF & CPD_OFF & CP_OFF & PWRTE_OFF & BOREN_OFF);

void main()
{
	unsigned char duty;		//16 bit value
	TRISC=0;			//set PORTC to output
	PORTC=0;			//set PORTC low
	PR2=0xFF;				//set TMR2 period register
	T2CON=0b00000100;		//set postscaler to 1:1; prescaler to 1
	CCP1CON=0b00001111;		//set the 2 LSB of duty cycle to 0 and initialize CCP module as PWM
	duty=0;
	while(1)
	{			
			while(duty<255)			//increase duty cycle from 0% to 100%
			{
				CCPR1L=duty;
				duty++;
				#ifdef __DEBUG				//if debugging right now then skip the delay routine
				#else
				__delay_ms(300);
				#endif
			}
			while(duty>0)			//decrease duty cycle from 0% to 100%
			{
				CCPR1L=duty;
				duty--;
				#ifdef __DEBUG
				#else
				__delay_ms(300);
				#endif
			}
	}	
}
It compiles and builds correctly. However when I built the circuit the LED glows brightly instead of going to full brightness smoothly and back. The voltage across the LED is 2.5V.
Does this imply that the frequency of switching is a bit too high (19.53 MHz) so that the voltage appears continuous to the LED?

I have no prior experience with PWM much less using it alongside a microcontroller.

Thanks!
 

RogerAF

Joined Jul 19, 2012
1
If you want to see the LED ramp-up from off to full on, over a period of say 5 seconds, you should give it a longer time at the dim stage.

I'd advise a lower PWM freq <1KHz, but I don't think that is your main problem. I think your code zips from dim to bright so quickly you can't see it.

You could try to divide it into sections with duty cycles of 1%, 5 %, 10%, 20%, 50%, 75%, 90%, 99%, etc. Then check each by itself to see how dim the LED looks. Then you could step through the sections in such a way as to give the smoothest response.

Remember that the LED is really turning off and on, but visual persistence of our eyes is such that we can't see that fast, so it looks like it is on the whole time. The shorter the on time (smaller duty cycle) the dimmer the appearance. But you probably won't see much change from 75% on up--it will look like full on. Also small changes like from 20% to 21% may not be apparent. That is why I suggest trying several different settings, to get a feel for how dim a specific duty cycle looks--the dimness is not likely to be linear.

Once you establish what looks like equal steps, you can join them together with another loop.

Hope this helps.
 

Markd77

Joined Sep 7, 2009
2,806
Generally set the frequency as high as you need for the desired effect but not much higher. For a LED you want it to look continuously on without flickering, which is around 100-200Hz.
For a motor it's more complicated, most work well enough at a few hundred hertz, but at that frequency you can hear the switching frequency, which is annoying, so many motors are run at 20kHz.
The higher the frequency, the more power is wasted in switching, which is the reason to keep it as low as possible.
 

MMcLaren

Joined Feb 14, 2010
861
I wonder if something like this (below) might work? Unfortunately, I'm not familiar with Hi-Tech C so I'm not sure how you setup a constant rom array.

Good luck with your project.

Cheerful regards, Mike

Rich (BB code):
//main.c
//To use PWM for dimming an LED
//PWM Frequency: 19.53 kHz
//CCP1 Register Pin=RC2

#include<htc.h>
#define _XTAL_FREQ 20000000

__CONFIG(FOSC_HS & WDTE_OFF & DEBUG_ON & LVP_OFF & CPD_OFF & CP_OFF & PWRTE_OFF & BOREN_OFF);

const rom char gamma[] = {  0,  1,  2,  2,  2,  2,  3,  3,  3,  4,
                            4,  5,  5,  6,  6,  7,  8,  8,  9, 10,
                           11, 12, 13, 14, 15, 17, 18, 20, 21, 23,
                           25, 27, 29, 31, 34, 36, 39, 42, 45, 49,
                           53, 57, 61, 65, 70, 75, 81, 87, 93,100,
                          107,114,123,131,140,150,161,172,183,196,
                          209,224,239,255 };


void main()
{
  unsigned char duty;		// 8 bit value
  TRISC=0;			// set PORTC to output
  PORTC=0;			// set PORTC low
  PR2=0xFF;			// set TMR2 period register
  T2CON=0b00000100;		// TMR2, pre 1, post 1, 200 ns ticks
  CCP1CON=0b00001111;		// init CCP module as PWM
  duty=0;
  while(1)
  {			
    while(duty<64)	        // increase duty cycle from 0% to 100%
    {
      CCPR1L= gamma[duty++];	//
      __delay_ms(15);		// approx 1 second fade-up
    }
    duty--;
    while(duty>=0)		// decrease duty cycle from 100% to 0%
    {
      CCPR1L= gamma[duty--];	//
      __delay_ms(15);		// approx 1 second fade-down
    }
  }	
}
 
Last edited:

ErnieM

Joined Apr 24, 2011
8,377
Design it first, then write the code.

I did a LED dimmer once using a PIC12HV615. With it running on it's internal 8MHz oscillator the slowest I could control the LED via the built in PWM was 200 Hz.

Anything above about 30Hz is considered "good," though there are people like myself who see that flicker if the LED travels across our field of vision. I could not see that effect at 200Hz when I shook the unit across my test bench.

As you go up in frequency you may have to take switching losses into effect. For a LED, I saw an inexpensive backyard solar powered lamp running the LED at 100KHz I believe (was 2-3 years back when I measured it). The LED seemed not to mind.

A motor should look inductive for the most part, so I don't think it should "mind" a higher frequency, but my only experience here is controlling small DC motors on HO model trains. I believe I was running at 100Hz.

Anyone else with better (any?) experience with motor is very welcome to add onto that.
 

Thread Starter

ActivePower

Joined Mar 15, 2012
155
I am sorry I have not been able to compose a reply sooner. Being in a college dorm changes a few things as I don't have a PC with a serial port to support my programmer and need to wait the whole weekend for testing out my small programs in the lab the next week.
Meanwhile, I have decreased the PWM frequency and made a few smaller changes. Just hoping it would turn out okay. I'll let you know what happens. Fingers crossed! ;)
 

Thread Starter

ActivePower

Joined Mar 15, 2012
155
I tried the PWM program again today with little success (literally). I configured it first to 1.22 kHz just to test whether the frequency was playing the bad guy. (I know, stupid!)

I changed the register configs and fiddled around with bits a little just to see if I am lucky and some change pops up! It did, in part, as I could see some flicker in the LED (or was I straining my eyes too hard?).

All in all, it was a less than fruitful 90 minutes trying to get the thing work (but it was fun!).


Here is the modified code (I realize I might have messed this up a lot and quite a lot of it might be wrong!)

Rich (BB code):
//main.c
//To use PWM for dimming an LED
//PWM Frequency: 1.22 kHz
//CCP1 Register Pin=RC2

#include<htc.h>
#define _XTAL_FREQ 20000000

__CONFIG(FOSC_HS & WDTE_OFF & DEBUG_ON & LVP_OFF & CPD_OFF & CP_OFF & PWRTE_OFF & BOREN_OFF);

void main()
{
	unsigned int duty;		//16 bit value
	unsigned int wtch_duty;
	TRISC=0;			//set PORTC to output
	PORTC=0;			//set PORTC low
	PR2=0xFF;				//set TMR2 period register
	T2CON=0b00000101;		//set postscaler to 1:1; prescaler to 16
	CCP1CON=0b00001111;		//set the 2 LSB of duty cycle to 0 and initialize CCP module as PWM
	duty=0;
	while(1)
	{			
			while(duty<255)			//increase duty cycle from 0% to 100%
			{
				CCPR1L=duty;
				duty++;
				unsigned char x=CCP1X;
				unsigned char y=CCP1Y;
				wtch_duty=(unsigned int) CCPR1L<<2;
				wtch_duty=wtch_duty + (unsigned int)CCP1X<<1;
				wtch_duty=wtch_duty + (unsigned int)CCP1Y<<1;
				#ifdef __DEBUG				//if debugging right now then skip the delay routine
				#else
				__delay_ms(150);
				#endif
			}
			while(duty>0)			//increase duty cycle from 0% to 100%
			{
				CCPR1L=duty;
				duty--;
				#ifdef __DEBUG
				#else
				__delay_ms(250);
				#endif
			}
	}	
}
Please ignore the CCPX/Y part, it was plain stupid. That is just an indication of how much I have yet to learn.

A slightly unrelated remark: To add pain to the process though, I now realize why people go for In-Circuit Serial/USB programmers. I spent half my time shifting my PIC from the programmer to the circuit board!

EDIT: I now realize it was a little naive of me to not have used the oscilloscope to see the output when I was busy probing the LED voltages with my multimeter.
 

MMcLaren

Joined Feb 14, 2010
861
I just tested a version of the example I posted earlier and it fades up and down quite smoothly. I'm using the free/lite version of BoostC and a 12F1822 with INTOSC at 8 MHz and PWM frequency of ~7812.5 Hz. The gamma[] array is a simple mechanism for getting 64 linear brightness level steps spanning the non-linear 256 PWM brightness level steps.

I hope a working example helps. Of course motor control is an entirely different matter. Good luck on your project.

Cheerful regards, Mike

Rich (BB code):
   #include <system.h>

   #pragma DATA _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _MCLRE_ON
   #pragma DATA _CONFIG2, _LVP_OFF & _PLLEN_OFF

   #pragma CLOCK_FREQ 8000000   // 8-MHz INTOSC

   #define r08 const rom unsigned char


   r08 gamma[] = {  0,  1,  2,  2,  2,  2,  3,  3,  3,  4,
                    4,  5,  5,  6,  6,  7,  8,  8,  9, 10,
                   11, 12, 13, 14, 15, 17, 18, 20, 21, 23,
                   25, 27, 29, 31, 34, 36, 39, 42, 45, 49,
                   53, 57, 61, 65, 70, 75, 81, 87, 93,100,
                  107,114,123,131,140,150,161,172,183,196,
                  209,224,239,255 };

   void main()
   { ansela = 0;                // make pins digital
     trisa = 0b00000000;        // porta all outputs
     porta = 0;                 // all output latches low
     osccon = 0b01110010;       // initialize 8-MHz INTOSC
     while(!oscstat.HFIOFS);    // wait until OSC stable

     ccp1con = 0b00001100;      // pwm mode, p1a active hi
     ccpr1l = 0;                // 0% duty cycle initially
     pr2 = 255;                 //
     t2con = 1<<TMR2ON;         // pre 1, post 1, 7812.5 Hz

     while(1)                   //
     { static char duty = 0;    //
       while(duty < 64)         // fade up
       { ccpr1l = gamma[duty++];
         delay_ms(15);          // over period of ~1 sec
       }                        //
       duty--;                  //
       while(duty > 0)          // fade down
       { ccpr1l = gamma[duty--];
         delay_ms(15);          // over period of ~1 sec
       }                        //
     }                          //
   }
 
Last edited:

Thread Starter

ActivePower

Joined Mar 15, 2012
155
Sorry for not replying earlier. I finally got the thing to work today. The problem was the duty cycle which was changing way too fast for any fading effect to be visible. I set the fade up and fade down time to 0.5 ms and it worked!

@MMcLaren: Thanks, your code helped me catch the delay problem :)
 

Thread Starter

ActivePower

Joined Mar 15, 2012
155
I have an additional question related to PWM in PIC16F877A.

The PWM duty cycle register is 10 bit but I could not figure out a way to access the two bits stored in CCPCON register leaving me effectively with a 8 bit PWM.

Is there any way to use the duty cycle in its entirety?

It'd blend in well with the 10 bit ADC mode as I would not have to convert the 10 bit ADC value to 8 bit for adjusting the duty cycle for a photoresistor based light dimmer I am trying to get working.

Thanks!
 

ErnieM

Joined Apr 24, 2011
8,377
I have an additional question related to PWM in PIC16F877A.

The PWM duty cycle register is 10 bit but I could not figure out a way to access the two bits stored in CCPCON register leaving me effectively with a 8 bit PWM.

Is there any way to use the duty cycle in its entirety?

It'd blend in well with the 10 bit ADC mode as I would not have to convert the 10 bit ADC value to 8 bit for adjusting the duty cycle for a photoresistor based light dimmer I am trying to get working.

Thanks!
Yes there is, it's not very clean but you have to pull out the least two significant bits from your PWM and place them into CCP1CON.CCP1X and CCP1CON.CCP1Y.

Rich (BB code):
CCPR1L = PWM >> 2; // to shift out 2 LSB's
CCP1CON.CCP1X = PWM & 0x2;   // 2nd least sig bit
CCP1CON.CCP1Y = PWM & 0x1;   // least sig bit
 

Markd77

Joined Sep 7, 2009
2,806
Here it is in assembler if you are curious (duty_H and duty_L are not preserved):
Rich (BB code):
;H_byte:L_byte > CCPR1L+CCP1CON<5:4> 
    clrf temp 
;    bcf STATUS,C ;not needed
    rrf duty_H, F 
    rrf duty_L, F 
    rrf temp, F 
    rrf duty_H, F 
    rrf duty_L, F                    ;duty_L now contains highest 8 bits 
    rrf temp, F                        ;temp now contains low 2 bits in <7:6> 
    rrf temp, F 
    rrf temp, F                        ;temp now contains low 2 bits in <5:4> 
                                    ;phew 
     
    movf duty_L, W 
    movwf CCPR1L 
     
    movf temp, W                    ;low 2 bits to CCPR1CON <5:4> IOR with PWM on 
    iorlw b'00001100' 
    movwf CCP1CON
 
Top