Home 9 The Art of Technology 9 XC8 Code Tutorial : PWM Functions for PIC10F322 Microcontroller

Understanding and Implementing PWM with the PIC10F322 Using XC8

The PIC10F322 microcontroller includes two PWM (Pulse Width Modulation) modules. While the period or frequency of the PWM output is common to all PWM modules, each module’s duty cycle is independently controlled.

PWM Output Pins

The PWM outputs are available on the following pins:

  • PORT A0: PWM1
  • PORT A1: PWM2

PWM Setup Instructions

To properly set up PWM on the PIC10F322, you need to configure the TRIS registers and disable the analog functions for the PWM output pins:

ANSELAbits.ANSA0 = 0; //Disable Analog for port A0
ANSELAbits.ANSA1 = 0; //Disable Analog for port A1
TRISAbits.TRISA0 = 0; //Set Port A0 Output
TRISAbits.TRISA1 = 0; //Set Port A1 Output

Understanding PWM Terms

Period: The total length of one cycle of the PWM signal, often referred to as the frequency. It indicates how often the cycle repeats in a given amount of time.

Duty Cycle: The percentage of one period in which the signal is active (high). For example, if the period is 1 second and the signal is active for 500ms, the duty cycle is 50%.

How PWM Works

At the start of the period, the output signal will be high for a certain duration (duty cycle). For instance, with a 1-second period and a 500ms high signal, the duty cycle is 50%. At the end of the period, the cycle starts again.

About the PIC10F322 PWM Module

The PIC10F322’s PWM module can produce up to a 10-bit resolution output, depending on the period selection. The period is determined by the Timer2 (TMR2) and PR2 registers, while the duty cycle is configured using the PWMxDCL and PWMxDCH registers (where x represents the PWM module number).

When TMR2 matches PR2, the following occurs:

  1. TMR2 is reset.
  2. The PWM output is active, except when the duty cycle is 0%.
  3. The values in PWMxDCH and PWMxDCL registers are latched into the buffers.

Setting Up PWM on PIC10F322

To achieve a PWM frequency of 15.7 kHz with a 10-bit resolution at a 16MHz clock speed, you need to configure the registers as follows:

PR2 = 0xFF; // Set the period register
T2CONbits.T2CKPS = 0b00; // Set Timer2 prescaler to 1
T2CONbits.TMR2ON = 0x01; // Turn on Timer2
PWM1CONbits.PWM1OE = 0x01; // Enable PWM1 output
PWM1CONbits.PWM1EN = 0x01; // Enable PWM1 module

Calculating PWM Duty Cycle

The duty cycle is controlled by two registers: PWMxDCH (8 MSBs) and PWMxDCL (2 LSBs). For a 10-bit resolution, the duty cycle value ranges from 0 to 1023, where 512 corresponds to a 50% duty cycle.

To convert a percentage to a duty cycle value:

  • For 33% duty cycle: 1023×0.33=337.591023 \times 0.33 = 337.591023×0.33=337.59 (rounded to 338)

Here is the logic analyzer on the 33% duty cycle.

33percentduty

The Code

/*
 * File:  pwm_example.c
 * Author: Jamie Starling - GizoFoundry.com 
 *
 * Created on:  September 8, 2021, 7:45 AM
 * 
 * Code/Circuit provided as-is.
 */

#include <xc.h>
#include <stdint.h>

//Device Configuration
#pragma config FOSC = INTOSC  // Oscillator Selection 
#pragma config BOREN = ON    // Brown-out Reset
#pragma config WDTE = OFF    // Watchdog Timer
#pragma config PWRTE = ON    // Power-up Timer
#pragma config MCLRE = OFF   // MCLR Pin Function Select bit->MCLR pin function is digital input, MCLR internally tied to VDD
#pragma config CP = OFF      // Code Protection 
#pragma config LVP = OFF     // Low-Voltage Programming 
#pragma config LPBOR = ON    // Brown-out Reset Selection bits
#pragma config BORV = LO    // Brown-out Reset Voltage Selection
#pragma config WRT = OFF    // Flash Memory Self-Write Protection

//Used to calculate the delay time - Change depending on processor Speed
#define _XTAL_FREQ 16000000  //16Mhz



//Prototypes
void setup(void);
void set_dutycycle(volatile uint8_t* pDutyCycleHigh, volatile uint8_t* pDutyCycleLow, uint16_t dutyValue);


void main(void)
{
    setup();
    
    while(1)
    {
     
    }
}

void setup(void)
{    
    //Set the System Clock - You can change this to match the setting you are looking for
    OSCCONbits.IRCF = 0b111;  //Set System Clock to 16Mhz
    
    //disable analog - Set A.0 and A.1 as output 
    ANSELAbits.ANSA0 = 0;
    ANSELAbits.ANSA1 = 0;
    TRISAbits.TRISA0 = 0; 
    TRISAbits.TRISA1 = 0;

    
    //Clear out the duty cycle registers
    set_dutycycle(&PWM1DCH,&PWM1DCL,0);
    set_dutycycle(&PWM2DCH,&PWM2DCL,0);
    
    PR2 = 0xFF;   
    T2CONbits.T2CKPS = 0b00;
    T2CONbits.TMR2ON = 0x01;
    PWM1CONbits.PWM1OE = 0x01;   //PWM1 Turn on 
    PWM1CONbits.PWM1EN = 0x01;  //PWM1 Enable Output

    PWM2CONbits.PWM2OE = 0x01;   //PWM2 Turn on 
    PWM2CONbits.PWM2EN = 0x01;  //PWM2 Enable Output


    set_dutycycle(&PWM1DCH,&PWM1DCL,338); //This will be 33%
    
    //100% example
    //set_dutycycle(&PWM2DCH,&PWM2DCL,1023); //This will be 100%
    
}


void set_dutycycle(volatile uint8_t* pDutyCycleHigh, volatile uint8_t* pDutyCycleLow, uint16_t dutyValue)
/*Sets the duty value of the supplied registers. 
 *Usage set_dutycycle(&PWM_duty_register_high,&PWM_duty_register_low, duty_value)
 *Valid range for 10bit resolution is 0 - 1023
 *TO convert percent to value, lets say we want 33%.  1023 x .33 = 337.59  round it to 338.
 *338 would be the value we enter for the duty.  */
{
    *pDutyCycleLow =  (uint8_t)((dutyValue & 0b11) << 0x06);  //Get the LSB
    *pDutyCycleHigh = (uint8_t)(dutyValue >> 0x02);    //Get the MSB
}

Have a Project or Idea!?

Seeking Bespoke Technology Solutions?

jamie@jamiestarling.com


Pin It on Pinterest

Share This