PWM Frequency Functions - setPWMFreq(...) & setPWMDuty(...)



The standard Mikroelektronika PWM library only allows constant values for setting the PWM frequency. I needed to set them through variables in software so I created the functions shown in the code below. The Flash movie above shows part of the results of the code. The duty cycle increases to the maximum then the frequency increases by 1000 and returns to 10% duty cycle. Note the frequency on the scope. Feel free to use the functions in your code.


 /*
 * Project name:
 *    BJD_PWM_Functions
 * Author:
 *    Barton J.  Dring 08/2007
 * Description:
 *    This code demonstrates some PWM functions
 * Test configuration:
 *   MCU:             PIC16F877A
 *    Dev.Board:       EasyPIC4
 *    Oscillator:      HS, 08.0000 MHz
 *    Ext. Modules:    LCD
 *    SW:              mikroC v6.0
 * NOTES:
 *    None.
 */

/*
 * ================ setPWMDuty ==================================================
 *
 * This sets the registers for the PWM duty.  This is a maximum of a 10-bit value,
 * but many PWM settings will limit this anount.
 *
 * Parameters:
 *    duty:   The duty value to set in the register.  This is the actual value,
 *            not a percent.
 *
 * Returns:
 *    none:
 *
 * =============================================================================
 */
void setPWMDuty(unsigned int duty)
{
    CCPR1L = duty >> 2;                  // Set MSB values
    CCP1CON = ((duty & 3) << 4) | 0x0C;  // set initial duty and set PWM mode bits.
}



 /*
 * ================ setPWMFrq ==================================================
 *
 * The function sets the PWM registers for a requested PWM frequency.  It
 * loops through the valid prescaler values (1,4,16) unitl it finds one that
 * will work.  The lowest value will yeild the highest resolution (maxDuty)
 * value, so the loop will quit at the first value it finds.  The function does
 * not range check any parameters.  This could be added or done before the function
 *
 * Parameters:
 *   Fpwm:         This is the desired PWM frequency in Hz
 *   Fosc:         This is the oscillator frequency in Hz
 *   dutyPercent:  The initial duty for the PWM.
 *
 * Returns:
 *   maxDuty:      This is the maximum value (100%) that the duty cycle can be
 *                 set to.  maxDuty/2 would give a 50% duty cycle.  The return
 *                 value is 0 then suitable setting could not be found.
 *
 * =============================================================================
 */

unsigned int setPWMFreq(unsigned long Fpwm, unsigned long Fosc, unsigned int dutyPercent)
{
	unsigned int pr2Val;
	unsigned int preScaler;
	unsigned int maxDuty;
	unsigned int duty;

	for (preScaler=1; preScaler<=16; preScaler *= 4)   //loop through all the valid prescalers
	{
	        pr2Val = Fosc/Fpwm/4;
		pr2Val = pr2Val / preScaler -1;

		if (pr2Val < 256 && pr2Val > 1)  // as soon as we find a good one...break out of the loop
			break;
	}

	if (preScaler > 16)  // if this exceeds 16 we were not able to find values so return 0
		return 0;

	switch (preScaler)
	{
	 case 1:
	      T2CON = 0b00000100;
	 break;
	 case 4:
	      T2CON = 0b00000101;
	 break;
	 default:
	      T2CON = 0b00000111;
	 break;
	}

	PR2 = pr2Val;

	maxDuty = Fosc / (Fpwm * preScaler);  // determine the maximum duty value for the registers

	duty = (maxDuty * dutyPercent) / 100;   // calculate the initial duty value from the percent

	setPWMDuty(duty);

	return maxDuty;
}

char *tc;
#define MIN_PWM_FREQ 5000
#define MAX_PWM_FREQ 15000
#define MAX_FREQ_INCR 1000

#define MIN_DUTY 10
#define DUTY_INCR 10

void main() {

unsigned int maxDuty, fred;
  int currentPwmDuty;
  unsigned long currentPwmFreq;

  TRISC = 0; // need PWM pin (2) to be an output
  
  LCD_Init(&PORTD);
  LCD_Cmd(LCD_CURSOR_OFF);
  LCD_Cmd(LCD_CLEAR);

  tc = "PWM Duty Test";
  LCD_Out(1,1,tc);

  Delay_ms(3000);
  
  LCD_Cmd(LCD_CLEAR);                // send command  to LCD (clear LCD)
  IntToStr(maxDuty, tc);
  LCD_Out(2,1,tc);                   // print string a on LCD, 2nd row, 1st column

  currentPwmDuty = 10;
  currentPwmFreq = MIN_PWM_FREQ;

  maxDuty = setPWMFreq(currentPwmFreq,8000000,currentPwmDuty);
  while (1)
  {
        setPWMDuty(currentPwmDuty);
        // print the current Frequency and duty to the LCD
        LCD_Cmd(LCD_CLEAR);
        LongToStr(currentPwmFreq, tc);
        LCD_Out(1,1,tc);
        IntToStr(currentPwmDuty, tc);
        LCD_Out(2,1,tc);
        
        Delay_ms(150);  // hold at this level for a while
        
        currentPwmDuty += DUTY_INCR;  // incrent the duty
           
        if (currentPwmDuty > maxDuty)  // see it it exceedss the max
        {
           currentPwmDuty = MIN_DUTY;  // reset to the min
           currentPwmFreq += MAX_FREQ_INCR;  // increment the freqency
           
           if (currentPwmFreq > MAX_PWM_FREQ)  // see if it is the max
              currentPwmFreq = MIN_PWM_FREQ;   // reset to min
              
           maxDuty = setPWMFreq(currentPwmFreq,8000000,75);   // we have a new frequency here
	}
  }
}
	   
Dring Engineering Services