16f1824 STD PWM - Cannot acheive 100% duty cycle & 256 discrete steps

Status
Not open for further replies.
Hi,

My first attempt at 'simple' pwm.

In the 16f1824 data sheet there is a table, shown below. If I understand it correctly it should be possible to get a 0 to 100% duty cycle, in 256 increments, using the values in the table. But using those values only gives me a 0 to 62.5% duty cycle. Reducing the value in PR2 from 0x65 to 0x3f does give 0 to 100% but at a higher frequency, 1.95KHz, vs the stated 1.22KHz.

The higher frequency isn't a problem but why doesn't the default PR2 value give the full range of period? I'm misunderstanding something or my code is wrong. Can you point me in the right direction?

View attachment 68959

Code:
#include <xc.h>

/*------------------------------------------------------------------------*/

#pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = OFF, MCLRE = ON, CP = OFF
#pragma config CPD = OFF, BOREN = OFF, CLKOUTEN = OFF, IESO = OFF, FCMEN = OFF
#pragma config WRT = OFF, PLLEN = OFF, STVREN = OFF, BORV = LO, LVP = OFF

/*------------------------------------------------------------------------*/

#define _XTAL_FREQ 8000000      /* Not using xtal, only to make delays work as
                                   it won't compile without this */

/*------------------------------------------------------------------------*/

int    x;

/*------------------------------------------------------------------------*/

void pwm_init (void);

/*------------------------------------------------------------------------*/

void main (void)
{

OSCCON = 0b01110010;        // Fosc = 8MHz (Inst. clk = 2MHz)

TRISA = 0;                  // All outputs
TRISC = 0;                  // All outputs
ADCON0bits.ADON = 0;        // No analog inputs

pwm_init();                 // Set up the pwm

/********* Start of main program loop **********/

/* This gives 256 discrete periods but max 62.5% duty cycle when PR2 = 0x65
   PWM f is 1.22KHz as per data sheet
   Reducing PR2 to 0x3f gives 100% duty cycle but PWM f is then 1.95KHz */

 while(1)

    for(x = 0; x < 256; x++)    // Increment the period
    
{           
        CCPR3L = ( x >> 2);                         // 6 MSBs
        CCP3CON = (CCP3CON & ~0x30 | (x << 4));     // 2 LSBs

    __delay_ms(100);  // So incremental change in period can be viewed on scope
} 
//********** End of main program loop ************

}

void pwm_init (void)
{
    TRISAbits.TRISA2 = 1;       // Disable pin output driver (CCP3)
  //PR2 = 0x3f;                   // PWM f =  1.95kHz  / 0 to 100% period
    PR2 = 0x65;                   // PWM f =  1.22kHz  / 0 to 62.5% period
    CCP3CON = 0b00001100;       // bit 7-6 Enhanced PWM bits (00 = single o/p)
                                // bit 5-4 Duty cycle LSBs
                                // bit 3-0 ECCPx mode select bits (11xx = PWM)
    CCPR3L = 0b00000000;        // Alters duty cycle
    CCPTMRS0 = 0b11001111;      // bit 5-4 CCP3 timer selection, 00 = TMR2
    PIR1bits.TMR2IF = 0;        // Clear the interrupt flag
    T2CON = 0b00000110;         // Postscaler = 0, prescaler = 16, timer = on

    while (!PIR1bits.TMR2IF)    // Wait until the Timer overflows and the
    {}                          // TMRxIF bit of the PIRx register is set.

    TRISAbits.TRISA2 = 0;       // Enable the CCPx pin output driver by
                                // clearing the associated TRIS bit.
}

Thanks in advance for any help.
 
Isn't 100% pwm just DC!! I'm sure I've read in the datasheet that the duty cycle ( value in the CCPRxL ) HAS to be smaller than the PR2 ( period timer ) even if its just 1 less.
 
Last edited:
Hi,

Thanks Ian.

Yes, when the value in PR2 is exceeded the output does not get reset so you have a constant high output - DC. But if I only write my 'period value' to CCPRxL the max value I can set is 0x65, the same value as stated for PR2 in the data sheet table. But that only gives 101 discrete increments in period - shouldn't there be 256 increments for 8 bit resolution?

View attachment 68960
 
Right!!! The way I read it is... 10bit = PR2 with 0xFF CCPRxL can contain 0 ->0xFF... CCPxCON has bits 4 & 5 = '11' that's your 10 bits..

If the PR2 reg = 0x3F ( 8 bit ) then CCPRxL can contain 6 bits 0 -> 0x3F... CCPxCON has the two LSbits, and that's your 8 bits..

If the PR2 reg = 0x1F ( 7 bit ) then CCPRxL can contain 5 bits 0 -> 0x1F... CCPxCON has the two LSbits, and that's your 7 bits..
 
Hi,

Thanks again. Incidentally just watched the latest EEVBLOG video - PWM with 555 timer.

Anyway, the thing is, when PR2 = 0x3F (as tried in my code above), 256 discrete duty cycles are attainable but the PWM frequency increases to 1.95KHz. No problem with that per se. It's just that I thought it should be possible to get the same result at 1.22KHz as per data sheet table. Maybe the table is incorrect, Microchip data sheets cannot always be taken as 'Bible' !

Cheers!
 
If PR2 = 0x65 (101) that's physically 408 resolutions... Why don't you use the 0x3F!... Every calculation I've done results in the same!!! It doesn't figure.. does it!
 
Hi,

There are a lot of exclamation marks there.

I did the calcs too and got the same result. So what's to be concluded? The data sheet is incorrect?

Being a dopey noob programmer means I'm very loath to take issue with a data sheet as when the result isn't what I expected it invariably means my code is screwed up or I've failed to grasp the fundamentals.

Anyway, works nicely with PR2 = 0x3F @ 1.95KHz. Program makes PWM duty cycle from min to max and then back again.

Appreciate your help.

Code:
#include <xc.h>

/*------------------------------------------------------------------------*/

#pragma config FOSC = INTOSC, WDTE = OFF, PWRTE = OFF, MCLRE = ON, CP = OFF
#pragma config CPD = OFF, BOREN = OFF, CLKOUTEN = OFF, IESO = OFF, FCMEN = OFF
#pragma config WRT = OFF, PLLEN = OFF, STVREN = OFF, BORV = LO, LVP = OFF

/*------------------------------------------------------------------------*/

#define _XTAL_FREQ 8000000      /* Not using xtal, only to make delays work as
                                   it won't compile without this */

/*------------------------------------------------------------------------*/

void pwm_init (void);

/*------------------------------------------------------------------------*/

void main (void)
{

OSCCON = 0b01110010;        // Fosc = 8MHz (Inst. clk = 2MHz)

TRISA = 0;                  // All outputs
TRISC = 0;                  // All outputs
ADCON0bits.ADON = 0;        // No analog inputs

pwm_init();                 // Set up the pwm

/********* Start of main program loop **********/

/* This gives 256 discrete periods when PR2 = 0x3f; PWM f is 1.95KHz 
   When PR2 = 0x65 as per data sheet PWM f is 1.22KHz but max duty cycle 62.5% 
   */

 while(1)
 
 {
     int x = 1;
     
        for(int i = 0; i > -1; i = i + x)   // Increment duty cycle from 0 to 100
    {
    CCPR3L =  i >> 2;                       // 6 MSBs
    CCP3CON = ((i << 4)|(CCP3CON & 0xCF));  // 2 LSBs

    if (i == 255) x = -1;                   // Decrement duty cycle from 100 to 0

    __delay_ms(200);  // So incremental change in period can be viewed on scope
    }
 }
//********** End of main program loop ************

}

void pwm_init (void)
{
    TRISAbits.TRISA2 = 1;       // Disable pin output driver (CCP3)
    PR2 = 0x3f;                 // PWM f =  1.95kHz  / 0 to 100% period
    CCP3CON = 0b00001100;       // bit 7-6 Enhanced PWM bits (00 = single o/p)
                                // bit 5-4 Duty cycle LSBs
                                // bit 3-0 ECCPx mode select bits (11xx = PWM)
    CCPR3L = 0b00000000;        // Alters duty cycle
    CCPTMRS0 = 0b11001111;      // bit 5-4 CCP3 timer selection, 00 = TMR2
    PIR1bits.TMR2IF = 0;        // Clear the interrupt flag
    T2CON = 0b00000110;         // Postscaler = 0, prescaler = 16, timer = on

    while (!PIR1bits.TMR2IF)    // Wait until the Timer overflows and the
    {}                          // TMRxIF bit of the PIRx register is set.

    TRISAbits.TRISA2 = 0;       // Enable the CCPx pin output driver by
                                // clearing the associated TRIS bit.
}

After messing about with pwm some more, single, half and full bridge modes, it's obvious I was on completely the wrong wavelength at the beginning. Was writing code for 10 bit resolution when only 8 bit was required. The shifting and masking totally pointless. Better code for single mode:

Code:
# include <xc.h>

#pragma config FOSC = INTOSC, MCLRE = ON, CLKOUTEN = OFF, WDTE = OFF, LVP = OFF

#define _XTAL_FREQ (8000000)     /* Not using xtal, only to make delays work as
                                   it won't compile without this */
void pwm_init (void);

/*------------------------------------------------------------------------*/
void main (void)
{

OSCCON = 0b01110010;        // Fosc = 8MHz

TRISA = 0;                  // All outputs
TRISC = 0;                  // All outputs
ADCON0bits.ADON = 0;        // No analog inputs

pwm_init();                 // Set up the pwm

/********* Start of main program loop **********/

/* PR2 = 0xFF. This gives 256 discrete duty cycles @ 1.95KHz
   The figures in Table 24-7 for the 16f1824 are bobbins as far as I can tell */
   
 while(1)

    for(int x = 0; x < 256; x++)    // Increment the pulse width (duty cycle)

        {
        CCPR3L = x;             // 8 bit resolution
        __delay_ms(500);        // So increments can be viewed on scope
        }
//********** End of main program loop ************

}

void pwm_init (void)
{
    TRISAbits.TRISA2 = 1;       // Disable pin output driver (CCP3)
    PR2 = 0xff;                 // PWM f =  1.95kHz  / 0 to 100% duty cycle
    CCP3CON = 0b00001100;       // bit 7-6 Enhanced PWM bits (00 = single o/p)
                                // bit 5-4 Duty cycle LSBs (When 10 bit resolution)
                                // bit 3-0 ECCPx mode select bits (11xx = PWM)
    CCPR3L = 0b00000000;        // Duty cycle (8 bit)
    CCPTMRS0 = 0b11001111;      // <5:4> CCP3 timer select, 00 = TMR2
    PIR1bits.TMR2IF = 0;        // Clear the interrupt flag
    T2CON = 0b00000101;         // Postscaler = 0, prescaler = 4, timer = on

    while (!PIR1bits.TMR2IF)    // Wait until the Timer overflows and the
    {}                          // TMRxIF bit of the PIRx register is set.

    TRISAbits.TRISA2 = 0;       // Enable the CCPx pin output driver by
                                // clearing the associated TRIS bit.
}
 
Last edited:
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…