3.20.3 16-bit PWM with Compare Mode

16-bit Pulse-Width Modulator with Compare Mode

3.20.3.1 Introduction

This module is a 16-bit Pulse-Width Modulator (PWM) with a compare feature and multiple outputs. The outputs are grouped in slices, where each slice has two outputs. There can be up to four slices in each PWM module.

In the below Tech Brief, some of the same use cases are build up from scratch, using bare metal coding, as well as MCC.

3.20.3.2 Supported Device Families

PIC16F171xxPIC16F181xxPIC18F-Q20
PIC18F-Q40PIC18F-Q41PIC18F-Q43
PIC18F-Q71PIC18F-Q83PIC18F-Q84

3.20.3.3 Required Header Files

#include "mcc_generated_files/pwm/pwm[x]_16bit.h"
Note: Replace [X] with the instance number of the selected PWM module.

3.20.3.4 How to Use the PWMx_16BIT PLIB Driver

The links below provide examples for different use cases of the PWMx_16BIT Peripheral Library (PLIB) driver.

For general instructions common to all examples, refer to this section: PIC PWM Use Case Code Snippet Instructions

  • PIC PWM use-case: Signal with 25% duty cycle: This example shows how to use the PWM module to generate an 1Hz signal with a 25% duty cycle.
  • PIC PWM use-case: Duty Cycle Update on Timer Callback: This example shows how to use the PWM module to generate an 1Hz signal with a variable duty cycle. There are 5 duty cycles that the application cycles through every 10 seconds.
  • PIC PWM use-case: Duty Cycle Update on Button Press: This example shows how to use the PWM module to cycle through different preset duty cycles, when a switch is pressed. If a LED pin is chosen as the PWM output pin, then the LED brightness will change as the button is pressed.
  • PIC PWM use-case: Gradually Brightening LED: This example shows how to scaling the percentage brighness of PWM LED, based on current value, max value, e.g., to match a PWM LED to the range of a sensor, or an ADC connected to a potentiometer. Here a timer callback is used to update the PWM_DutyCyclePercentage. The top period count of the PWM is specified, to make the code more readable.

3.20.3.5 Module Documentation

3.20.3.5.1 PWM1_16BIT

This file contains the API prototypes for the PWM1_16BIT driver.

Module description

This file contains the API prototypes for the PWM1_16BIT driver.

Version: PWM1_16BIT Driver Version 1.0.1
Functions

Function Documentation

PWM1_16BIT_Disable()

void PWM1_16BIT_Disable ( )

Disables the PWM1_16BIT module.

Parameters:
None.
Returns:

None. In case the user wants to reinitialize the PWM1_16BIT, this function must be called before PWM1_16BIT_Initialize().

PWM1_16BIT_Enable()

void PWM1_16BIT_Enable ( )

Enables the PWM1_16BIT module.

Parameters:
None.
Returns:

None.

PWM1_16BIT_Initialize()

void PWM1_16BIT_Initialize (void )

Initializes the PWM1_16BIT module. This API must be called before any other PWM1_16BIT routine is called.

PWM1_16BIT_LoadBufferRegisters()

void PWM1_16BIT_LoadBufferRegisters (void )

Reloads the period or duty cycle registers on the next period event.

Parameters:
None.
Returns:

None. This routine must be called after calling PWM1_16BIT_WritePeriodRegister, PWM1_16BIT_SetSlice1Output1DutyCycleRegister and PWM1_16BIT_SetSlice1Output2DutyCycleRegister.

PWM1_16BIT_Period_SetInterruptHandler()

void PWM1_16BIT_Period_SetInterruptHandler (void(*)(void) InterruptHandler)

Registers a callback function to be called for the period interrupt event.

Parameters:
void

(* InterruptHandler)(void) - Pointer to the period interrupt event handler.

Returns:

None.

PWM1_16BIT_PWMI_ISR()

void PWM1_16BIT_PWMI_ISR (void )

Interrupt handler for PWM1_16BIT parameter interrupt events.

Parameters:
None.
Returns:

None.

PWM1_16BIT_PWMPI_ISR()

void PWM1_16BIT_PWMPI_ISR (void )

Interrupt handler for PWM1_16BIT period interrupt events.

Parameters:
None.
Returns:

None.

PWM1_16BIT_SetSlice1Output1DutyCycleRegister()

void PWM1_16BIT_SetSlice1Output1DutyCycleRegister (uint16_t value)

Sets the active period or duty cycle of the slice 1, parameter 1 output. PWM1_16BIT_LoadBufferRegisters() must be called after this routine.

Parameters:
in uint16_t

value - PWMPWM1_16BITS1P1 register value.

Returns:

None.

PWM1_16BIT_SetSlice1Output2DutyCycleRegister()

void PWM1_16BIT_SetSlice1Output2DutyCycleRegister (uint16_t value)

Sets the active period or duty cycle of the slice 1, parameter 1 output. PWM1_16BIT_LoadBufferRegisters() must be called after this routine.

Parameters:
in uint16_t

value - PWMPWM1_16BITS1P2 register value.

Returns:

None.

PWM1_16BIT_Slice1Output1_SetInterruptHandler()

void PWM1_16BIT_Slice1Output1_SetInterruptHandler (void(*)(void) InterruptHandler)

Registers a callback function to be called for the slice 1, parameter 1 interrupt event.

Parameters:
void

(* InterruptHandler)(void) - Pointer to the slice 1, parameter 1 interrupt event handler.

Returns:

None.

PWM1_16BIT_Slice1Output2_SetInterruptHandler()

void PWM1_16BIT_Slice1Output2_SetInterruptHandler (void(*)(void) InterruptHandler)

Registers a callback function to be called for the slice 1, parameter 2 interrupt event.

Parameters:
void

(* InterruptHandler)(void) - Pointer to the slice 1, parameter 2 interrupt event handler.

Returns:

None.

PWM1_16BIT_WritePeriodRegister()

void PWM1_16BIT_WritePeriodRegister (uint16_t periodCount)

Configures the total PWM1_16BIT period. PWM1_16BIT_LoadBufferRegisters() must be called after this routine.

Parameters:
in uint16_t

periodCount - Desired 16-bit PWM1_16BIT period.

Returns:

None.

3.20.3.6 File Documentation

3.20.3.6.1 source/pwm1_16bit.c File Reference

This file contains the API implementation for the PWM1_16BIT module.

#include <xc.h>
#include "../pwm1_16bit.h"

Functions

Detailed Description

This file contains the API implementation for the PWM1_16BIT module.

PWM1_16BIT Generated Driver File.

version PWM1_16BIT Driver Version 1.0.1

Function Documentation

PWM1_16BIT_Period_DefaultInterruptHandler()

static void PWM1_16BIT_Period_DefaultInterruptHandler (void )[static]

PWM1_16BIT_Slice1Output1_DefaultInterruptHandler()

static void PWM1_16BIT_Slice1Output1_DefaultInterruptHandler (void )[static]

PWM1_16BIT_Slice1Output2_DefaultInterruptHandler()

static void PWM1_16BIT_Slice1Output2_DefaultInterruptHandler (void )[static]

Variable Documentation

PWM1_16BIT_Period_InterruptHandler

void(* PWM1_16BIT_Period_InterruptHandler) (void)[static]

PWM1_16BIT_Slice1Output1_InterruptHandler

void(* PWM1_16BIT_Slice1Output1_InterruptHandler) (void)[static]

PWM1_16BIT_Slice1Output2_InterruptHandler

void(* PWM1_16BIT_Slice1Output2_InterruptHandler) (void)[static]

3.20.3.6.2 source/pwm1_16bit.h File Reference

#include <stdint.h>
#include <stdbool.h>

Functions

Detailed Description

PWM1_16BIT Generated Driver API Header File.

3.20.3.7 Module Documentation

3.20.3.7.1 PWMx_16BIT Use Cases

PIC PWM Use Case Code Snippet Instructions

The use cases show example uses of the PWM PLIB Driver, within a MCC Melody project:

  • Add PWMx Component to the project

  • Configure:
    1. PWMx_16BIT component as described in the example.

    2. Any other components or pins needed for the use case.

  • Generate the code

  • Add the code snippet(s) to the application code

  • Program the board

PIC PWM use-case: Signal with 25% duty cycle

This example shows how to use the PWM module to generate an 1Hz signal with a 25% duty cycle.

Device Resources:
  1. Add PWMx_16BIT  

PWMx_16BIT Configuration:
  • Hardware Settings:
    1. Clock Source: LFINTOSC.

  • Slice 1 Output Settings
    1. Requested Frequency (kHz): 1 (Default).

    2. Output 1 Duty Cycle (%): 25.

System Configuration:
  • Pin Grid View:
    1. Select a pin for PWM1OUT1 (this is the PWM output pin)

  • Pins:
    1. PWM1OUT1 output pin: Uncheck Analog, name it PWM.

Wiring:
  • Connect PWM to DebugIO PIN (check schematics) using a wire, to see PWM waveform on the MPLAB Data Visualizer

#include "mcc_generated_files/system/system.h"  
int main(void)
{
    SYSTEM_Initialize();

    while(1){}
}

PIC PWM use-case: Duty Cycle Update on Timer Callback

This example shows how to use the PWM module to generate an 1Hz signal with a variable duty cycle. There are 5 duty cycles that the application cycles through every 10 seconds.

Device Resources:
  1. Add PWMx_16BIT

  2. Add TimerX

PWMx_16BIT Configuration:
  • Software Settings
    1. Custom Name: PWM (so the user can easily switch between PWM instances)

  • Slice 1 Output Settings
    1. Requested Frequency (kHz): 1 (default)

    2. Invert Output1 Polarity: Yes (If LED is active low: Check schematics)

TimerX Configuration:
  • Hardware Settings
    1. Timer Mode: 16-bit

    2. Clock Source: LFINTOSC

    3. Requested Period (s): 1(s)  

  • Interrupt Settings
    1. TMR Interrupt: Yes. (Timer callback used to change PWM duty cycle)

System Configuration:
  • Pin Grid View:
    1. Select a pin for PWM1OUT1 (On a LED pin if possible)

  • Pins:
    1. PWM1OUT1 output pin: Uncheck Analog, name it PWM.

Wiring:
  • Connect PWM to DebugIO PIN (check schematics) using a wire, to see PWM waveform on the MPLAB Data Visualizer

#include "mcc_generated_files/system/system.h"

#define NO_DUTY_CYCLES    (5)

/* Create a pointer of type TMR_INTERFACE and assign it to the address of the Timer0 TMR_INTERFACE struct.
This enables us to access the portable API interface, ensuring it's easy to change the peripheral instance that the timer runs on. */
const struct TMR_INTERFACE *Timer = &Timer0;
static uint8_t currentDutyCyclePos = 1;
/*
 *  Enum that encompasses all the values for PWM's duty cycle register that the application cycles through.
 */
typedef enum DUTY_CYCLE
{
    DUTY_CYCLE_0   = 0x0000,    
    DUTY_CYCLE_2   = 0x0004,  
    DUTY_CYCLE_5   = 0x000A,  
    DUTY_CYCLE_10  = 0x0014,
    DUTY_CYCLE_100 = 0x00C8
} duty_cycle_t;

/*
 *  PWM duty cycles that the application cycle through. LEDs are non linear and saturate quickly, so the duty values are low.
 *  NOTE: If LED is active low: Invert Output1 Polarity: Yes 
 */
static duty_cycle_t const dutyCycles[NO_DUTY_CYCLES] = {
    DUTY_CYCLE_0,
    DUTY_CYCLE_2,
    DUTY_CYCLE_5,
    DUTY_CYCLE_10,
    DUTY_CYCLE_100  //Starts ON fully.
};
/*
 *  Wrapper made for the PWM's API to update the duty cycle.
 */
inline static void UpdateDutyCycle(duty_cycle_t const dutyCycle)
{
    PWM_SetSlice1Output1DutyCycleRegister(dutyCycle);
    PWM_LoadBufferRegisters();
}

void Timer_Callback_SwitchDutyCycle(void){
    
    UpdateDutyCycle(dutyCycles[currentDutyCyclePos]);
    currentDutyCyclePos = (currentDutyCyclePos + 1) % NO_DUTY_CYCLES;
}
int main(void)
{
    SYSTEM_Initialize();
    Timer->TimeoutCallbackRegister(Timer_Callback_SwitchDutyCycle);

    INTERRUPT_GlobalInterruptEnable();

    while(1){}
}

PIC PWM use-case: Duty Cycle Update on Button Press

This example shows how to use the PWM module to cycle through different preset duty cycles, when a switch is pressed. If a LED pin is chosen as the PWM output pin, then the LED brightness will change as the button is pressed.

Device Resources:
  • Add PWM

Microchip Code Configurator (MCC) settings: PWMx_16BIT:
  • Software Settings
    1. Custom Name: PWM (So can easily change PWM hardware instance use case runs on)

  • Slice 1 Output Settings
    1. Invert Output1 Polarity: Yes (If LED is active low: Check schematics)  

System Configuration:
  • Pin Grid View:
    1. Select a pin for PWM1OUT1 (Select a LED pin if possible)

    2. Select the input pin for GPIO for BUTTON (check schematic)

  • Pins:
    1. PWM out pin: Custom name "PWM", uncheck Analog

    2. BUTTON pin: Custom name "BUTTON",

    3. Enable Weak Pullup if needed (check schematic)

    4. Interrupt on Change: Sense negative/positive on BUTTON press (check schematic)

Wiring:
  1. Connect PWM_OUT to LED using a wire to be able to see the result on the LED (if PWM pin is different from LED pin)

#include "mcc_generated_files/system/system.h"
#define NO_DUTY_CYCLES       (5)

static uint8_t currentDutyCyclePos = 1;
volatile bool CHANGE_DUTYCYCLE = false;
/*
 *  Enum that encompasses all the values for PWM's duty cycle register that the application cycles through.
 */
typedef enum DUTY_CYCLE
{
    DUTY_CYCLE_0   = 0x0000,    
    DUTY_CYCLE_2   = 0x0004,  
    DUTY_CYCLE_5   = 0x000A,  
    DUTY_CYCLE_10  = 0x0014,
    DUTY_CYCLE_100 = 0x00C8
} duty_cycle_t;

/*
 *  PWM duty cycles that the application cycle through. LEDs are non linear and saturate quickly, so the duty values are low.
 *  NOTE: If LED is active low: Invert Output1 Polarity: Yes 
 */
static duty_cycle_t const dutyCycles[NO_DUTY_CYCLES] = {
    DUTY_CYCLE_0,
    DUTY_CYCLE_2,
    DUTY_CYCLE_5,
    DUTY_CYCLE_10,
    DUTY_CYCLE_100  //Starts ON fully.
};
/*
 *  Wrapper made for the PWM's API to update the duty cycle.
 */
inline static void UpdateDutyCycle(duty_cycle_t const dutyCycle)
{
    PWM_SetSlice1Output1DutyCycleRegister(dutyCycle);
    PWM_LoadBufferRegisters();
}

void BUTTON_Callback_SwitchDutyCycle(void)
{
    INTERRUPT_GlobalInterruptDisable();
    UpdateDutyCycle(dutyCycles[currentDutyCyclePos]);
    currentDutyCyclePos = (currentDutyCyclePos + 1) % NO_DUTY_CYCLES;
    CHANGE_DUTYCYCLE = true;    
}
int main(void)
{
    SYSTEM_Initialize();
    RC0_SetInterruptHandler(BUTTON_Callback_SwitchDutyCycle);  //Change RC0 to pin connected to BUTTON
    
    INTERRUPT_GlobalInterruptEnable();

    while(1){
        if(CHANGE_DUTYCYCLE)
        {
            CHANGE_DUTYCYCLE = false;
            __delay_ms(50);  //Debounce
            INTERRUPT_GlobalInterruptEnable();
        }
    }
}

PIC PWM use-case: Gradually Brightening LED

This example shows how to scaling the percentage brighness of PWM LED, based on current value, max value, e.g., to match a PWM LED to the range of a sensor, or an ADC connected to a potentiometer. Here a timer callback is used to update the PWM_DutyCyclePercentage. The top period count of the PWM is specified, to make the code more readable.

Device Resources:
  1. Add PWMx_16BIT

  2. Add TimerX

PWMx_16BIT Configuration:
  • Software Settings
    1. Custom Name: PWM (so the user can easily switch between PWM instances)

  • Slice 1 Output Settings
    1. Requested Frequency (kHz): 1 (default)

TimerX Configuration:
  • Device Resources: Add TIMER from Drivers (Select any timer).

  • Hardware Settings:
    1. So that Requested Period (s) ≤ Max Period, modify Clock Prescaler/Postscaler/Clock Source

    2. Requested Period (s): 0.02 (20 ms)

  • Interrupt Settings
    1. TMR Interrupt: Yes. (Timer callback used to change PWM duty cycle)

System Configuration:
  • Pin Grid View:
    1. Select a pin for PWM1OUT1 (On a LED pin if possible)

  • Pins:
    1. PWM1OUT1 output pin: Uncheck Analog, name it IO_PWM.

Wiring:
  1. Connect PWM_OUT to LED using a wire to be able to see the result on the LED (if PWM pin is different from LED pin)

#include "mcc_generated_files/system/system.h"

static void Timer_Callback_20ms(void);
static void PWM_DutyCycleSetPercentage_Slice1(uint16_t, uint16_t, uint16_t);
static const struct TMR_INTERFACE *Timer = &TimerX; 
// TODO: Replace Timer0 with name of const struct TMR_INTERFACE, from MCC Generated Files > timer > tmrx.c

#define MAXIMUM_INPUT_VALUE 100  // TODO: Replace with the desired maximum value, e.g. a 16 bit ADC connected to a potentiometer could be 4100)
static uint16_t Count = 0;
static uint16_t periodCountTop = 100;
void PWM_DutyCycleSetPercentage_Slice1(uint16_t current, uint16_t max, uint16_t periodCountTop)
{
    uint16_t PWM_DytyCyclePercentage = (1.0f - ((double) current / (double) max)) * periodCountTop;
    PWM_SetSlice1Output1DutyCycleRegister(PWM_DytyCyclePercentage);
    PWM_LoadBufferRegisters();
}

static void Timer_Callback_20ms(void)
{
    /* If used with an analog sensor or a simple POT, this function could be called when ADC result available, replacing Count with adcResult, and updating MAXIMUM_INPUT_VALUE*/ 
    PWM_DutyCycleSetPercentage_Slice1(Count, MAXIMUM_INPUT_VALUE, periodCountTop);
    
    IO_Debug_Toggle();
    if (Count < MAXIMUM_INPUT_VALUE)
    {
        Count = Count++;
    }
    else
    {
        Count = 0;
    }
}
int main(void)
{
    SYSTEM_Initialize();
    Timer->TimeoutCallbackRegister(Timer_Callback_20ms);
    PWM_WritePeriodRegister(periodCountTop); //Set number of counts for PWM period, set here to make code more readable.

    INTERRUPT_GlobalInterruptEnable();

    while (1)
    {
    }
}