News:

PROTON pic BASIC Compilers for PIC, PIC24, dsPIC33

Main Menu

Positron16 - High Resolution PWM library for dsPIC33CKxxMPxxx devices

Started by top204, Jan 30, 2026, 01:36 PM

Previous topic - Next topic

top204

I was in need of a PWM waveform for the LD06 LIDAR unit I am writing code for. However, the dsPIC33CK devices do not use standard PWM peripherals, so it gave me a chance to write a library for them that uses the High Resolution PWM peripherals on them.

The library will work for any dsPIC33CKxxMPxxx device that has 4 High-Res PWM peripherals on it, and the device I am writing the code for is a dsPIC33CK128MP202.

So I thought I would share the library code with our users, in case you want to create PWM waveforms on a dsPIC33CK device, with ease.

Below is the code listing of the library, and it is named:  "HPWM_CK.inc":

$ifndef _HPWM_CK_INC_
$define _HPWM_CK_INC_
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' HPWM peripheral library for dsPIC33CKxxMPxxx, 28-Pin devices.
' Written by Les Johnson for the Positron16 BASIC Compiler.
' https://sites.google.com/view/rosetta-tech/positron-compilers-experimenters-notebook
'
' Setup the fixed pins for PWM1, PWM2, PWM3, and a default for PWM4
'
$define PWM1_Low_Pin PORTB.15                                                   ' The low PWM1 pin
$define PWM1_High_Pin PORTB.14                                                  ' The high PWM1 pin

$define PWM2_Low_Pin PORTB.13                                                   ' The low PWM2 pin
$define PWM2_High_Pin PORTB.12                                                  ' The high PWM2 pin

$define PWM3_Low_Pin PORTB.11                                                   ' The low PWM3 pin
$define PWM3_High_Pin PORTB.10                                                  ' The high PWM3 pin
'
' Set the default PWM4 pins, because these can be moved via PPS
'
$ifndef PWM4_Low_Pin
    $define PWM4_Low_Pin PORTB.9                                                ' The low PWM4 pin
    $SendWarning "$define PWM4_Low_Pin not used in teh main program, so defaulting to PORTB.9 for the PWM4 Low pin"
$endif
$ifndef PWM4_High_Pin
    $define PWM4_High_Pin PORTB.8                                               ' The high PWM4 pin
    $SendWarning "$define PWM4_High_Pin not used in teh main program, so defaulting to PORTB.8 for the PWM4 High pin"
$endif
$ifndef False
    $define False 0
$endif
$ifndef True
    $define True 1
$endif
'
' Create variables and constants used by the library routines
'
    Symbol HPWM_cFosc = (_xtal * 1000000)                                       ' Calculate the fOsc value, based upon the Xtal declare
    Dim HPWM_dFrequency As Dword                                                ' Used to pass the 32-bit frequency value
    Dim HPWM_wDuty      As Word                                                 ' Used to pass the calculated 16-bit duty cycle value
    Dim HPWM_tDisEnReq  As Bit                                                  ' Used as a parameter bit

'-----------------------------------------------------------------------------------------
' Meta-Macros for PWM1 on dsPIC33CKxxMPx02 devices
'
$define PWM1_Enable()  PG1CONLbits_ON = 1                                       ' Enable PWM1
$define PWM1_Disable() PG1CONLbits_ON = 0                                       ' Disable PWM1
$define PWM1_Update()  PG1STATbits_UPDREQ = 1                                   ' Signal that the PWM1 can update

'------------------------------------------------------
' Set the period for the PWM1 generator specific Time Base
' Input     : pPeriod holds the period value
'
$define PWM1_hPeriod(pPeriod) '
    PG1PER = pPeriod          '
    PWM1_Update()

'------------------------------------------------------
' Set the PWM1 generator duty cycle register
' Input     : pDuty holds the duty cycle value
'
$define PWM1_hDuty(pDuty) '
    PG1DC = pDuty         '
    PWM1_Update()

'------------------------------------------------------
' Set the phase value for the PWM1 generator Time Base
' Input     : pPhase holds the phase value
'
$define PWM1_hPhase(pPhase) '
    PG1PHASE = pPhase       '
    PWM1_Update()

'-----------------------------------------------------------------------------------------
' Meta-Macros for PWM2 on dsPIC33CKxxMPx02 devices
'
$define PWM2_Enable()  PG2CONLbits_ON = 1                                       ' Enable PWM2
$define PWM2_Disable() PG2CONLbits_ON = 0                                       ' Disable PWM2
$define PWM2_Update()  PG2STATbits_UPDREQ = 1                                   ' Signal that the PWM2 can update

'------------------------------------------------------
' Set the period for the PWM2 generator specific Time Base
' Input     : pPeriod holds the period value
'
$define PWM2_hPeriod(pPeriod) '
    PG2PER = pPeriod          '
    PWM2_Update()

'------------------------------------------------------
' Set the PWM2 generator duty cycle register
' Input     : pDuty holds the duty cycle value
'
$define PWM2_hDuty(pDuty) '
    PG2DC = pDuty         '
    PWM2_Update()

'------------------------------------------------------
' Set the phase value for the PWM2 generator Time Base
' Input     : pPhase holds the phase value
'
$define PWM2_hPhase(pPhase) '
    PG2PHASE = pPhase       '
    PWM2_Update()

'-----------------------------------------------------------------------------------------
' Meta-Macros for PWM3 on dsPIC33CKxxMPx02 devices
'
$define PWM3_Enable()  PG3CONLbits_ON = 1                                       ' Enable PWM3
$define PWM3_Disable() PG3CONLbits_ON = 0                                       ' Disable PWM3
$define PWM3_Update()  PG3STATbits_UPDREQ = 1                                   ' Signal that the PWM3 can update

'------------------------------------------------------
' Set the period for the PWM3 generator specific Time Base
' Input     : pPeriod holds the period value
'
$define PWM3_hPeriod(pPeriod) '
    PG3PER = pPeriod          '
    PWM3_Update()

'------------------------------------------------------
' Set the PWM3 generator duty cycle register
' Input     : pDuty holds the duty cycle value
'
$define PWM3_hDuty(pDuty) '
    PG3DC = pDuty         '
    PWM3_Update()

'------------------------------------------------------
' Set the phase value for the PWM3 generator Time Base
' Input     : pPhase holds the phase value
'
$define PWM3_hPhase(pPhase) '
    PG3PHASE = pPhase       '
    PWM3_Update()

'-----------------------------------------------------------------------------------------
' Meta-Macros for PWM4 on dsPIC33CKxxMPx02 devices
'
$define PWM4_Enable()  PG4CONLbits_ON = 1                                       ' Enable PWM4
$define PWM4_Disable() PG4CONLbits_ON = 0                                       ' Disable PWM4
$define PWM4_Update()  PG4STATbits_UPDREQ = 1                                   ' Signal that the PWM4 can update

'------------------------------------------------------
' Set the period for the PWM4 generator specific Time Base
' Input     : pPeriod holds the period value
'
$define PWM4_hPeriod(pPeriod) '
    PG4PER = pPeriod          '
    PWM4_Update()

'------------------------------------------------------
' Set the PWM4 generator duty cycle register
' Input     : pDuty holds the duty cycle value
'
$define PWM4_hDuty(pDuty) '
    PG4DC = pDuty         '
    PWM4_Update()

'------------------------------------------------------
' Set the phase value for the PWM4 generator Time Base
' Input     : pPhase holds the phase value
'
$define PWM4_hPhase(pPhase) '
    PG4PHASE = pPhase       '
    PWM4_Update()

'-----------------------------------------------------------------------------------------
' Calculate the value to place into the PG1PER SFR, for the frequency of the PWM1 waveform
' Input     : pFreq (HPWM_dFrequency) holds the frequency required (in Hz)
' Output    : Loads the PG1PER SFR with the calculated value
'           : Holds a copy of the frequency value in PWM1_dFrequency
' Notes     : The calculation is: PG1PER = fOsc / (PWM Frequency * PWM_Clk_Prescale)
'
Proc PWM1_Freq(pFreq As HPWM_dFrequency)
Global Dim PWM1_dFrequency As Dword Shared                                      ' Holds a copy of PWM1 frequency value

    PWM1_dFrequency = pFreq                                                     ' Keep a copy of PWM1 frequency value
    PG1PER = (HPWM_cFosc / pFreq)                                               ' Alter the frequency (period) of PWM1
    PWM1_Update()                                                               ' Update PWM1
EndProc

'-----------------------------------------------------------------------------------------
' Alter the Duty of PWM1
' Input     : pDuty (HPWM_wDuty) holds the 8-bit duty value (0 to 255)
'           : pDisEn is true if the PWM peripheral is disables, duty altered, then re-enabled.
'           : False if the PG1STATbits_UPDREQ is set after the duty value is loaded into the PG1DC SFR
'           : Global variable: PWM1_dFrequency holds the frequency of the PWM1 peripheral
' Output    : None
' Notes     : pDisEn is required, because the PG1STATbits_UPDREQ alone, only works if there are multiple calls to alter the duty value
'
Proc PWM1_Duty(pDuty As HPWM_wDuty, pDisEn As HPWM_tDisEnReq)
Global Dim HPWM_wCalcDuty As Word Shared                                        ' Used to hold the duty cycle value

    HPWM_hCalculateDuty(PWM1_dFrequency, pDuty)                                 ' Calculate the duty, based upon the frequency value held in PWM1_dFrequency
    If pDisEn = False Then                                                      ' Is the pDisEn parameter false?
        PWM1_hDuty(HPWM_wCalcDuty)                                              ' Yes. So do not disable/enable the PWM1 periperal
    Else                                                                        ' Otherwise...
        PWM1_Disable()                                                          ' Disable PWM1 so that a change can occur
        PG1DC = HPWM_wCalcDuty                                                  ' Transfer the duty value into the PWM SFR
        PWM1_Enable()                                                           ' Re-Enable the PWM1 peripheral
    EndIf
EndProc

'-----------------------------------------------------------------------------------------
' Calculate the value to place into the PG2PER SFR, for the frequency of the PWM2 waveform
' Input     : pFreq (HPWM_dFrequency) holds the frequency required (in Hz)
' Output    : Loads the PG2PER SFR with the calculated value
'           : Holds a copy of the frequency value in PWM2_dFrequency
' Notes     : The calculation is: PG2PER = fOsc / (PWM Frequency * PWM_Clk_Prescale)
'
Proc PWM2_Freq(pFreq As HPWM_dFrequency)
Global Dim PWM2_dFrequency As Dword Shared                                      ' Holds a copy of PWM2 frequency value

    PWM2_dFrequency = pFreq                                                     ' Keep a copy of PWM2 frequency value
    PG2PER = (HPWM_cFosc / pFreq)                                               ' Alter the frequency (period) of PWM2
    PWM2_Update()                                                               ' Update PWM2
EndProc

'-----------------------------------------------------------------------------------------
' Alter the Duty of PWM2
' Input     : pDuty (HPWM_wDuty) holds the 8-bit duty value (0 to 255)
'           : pDisEn is true if the PWM peripheral is disables, duty altered, then re-enabled.
'           : False if the PG2STATbits_UPDREQ is set after the duty value is loaded into the PG2DC SFR
'           : Global variable: PWM2_dFrequency holds the frequency of the PWM2 peripheral
' Output    : None
' Notes     : pDisEn is required, because the PG2STATbits_UPDREQ alone, only works if there are multiple calls to alter the duty value
'
Proc PWM2_Duty(pDuty As HPWM_wDuty, pDisEn As HPWM_tDisEnReq)
Global Dim HPWM_wCalcDuty As Word Shared                                        ' Used to hold the duty cycle value

    HPWM_hCalculateDuty(PWM2_dFrequency, pDuty)                                 ' Calculate the duty, based upon the frequency value held in PWM2_dFrequency
    If pDisEn = False Then                                                      ' Is the pDisEn parameter false?
        PWM2_hDuty(HPWM_wCalcDuty)                                              ' Yes. So do not disable/enable the PWM2 periperal
    Else                                                                        ' Otherwise...
        PWM2_Disable()                                                          ' Disable PWM2 so that a change can occur
        PG2DC = HPWM_wCalcDuty                                                  ' Transfer the duty value into the PWM SFR
        PWM2_Enable()                                                           ' Re-Enable the PWM2 peripheral
    EndIf
EndProc

'-----------------------------------------------------------------------------------------
' Calculate the value to place into the PG3PER SFR, for the frequency of the PWM3 waveform
' Input     : pFreq (HPWM_dFrequency) holds the frequency required (in Hz)
' Output    : Loads the PG3PER SFR with the calculated value
'           : Holds a copy of the frequency value in PWM3_dFrequency
' Notes     : The calculation is: PG3PER = fOsc / (PWM Frequency * PWM_Clk_Prescale)
'
Proc PWM3_Freq(pFreq As HPWM_dFrequency)
Global Dim PWM3_dFrequency As Dword Shared                                      ' Holds a copy of PWM3 frequency value

    PWM3_dFrequency = pFreq                                                     ' Keep a copy of PWM3 frequency value
    PG3PER = (HPWM_cFosc / pFreq)                                               ' Alter the frequency (period) of PWM3
    PWM3_Update()                                                               ' Update PWM3
EndProc

'-----------------------------------------------------------------------------------------
' Alter the Duty of PWM3
' Input     : pDuty (HPWM_wDuty) holds the 8-bit duty value (0 to 255)
'           : pDisEn is true if the PWM peripheral is disables, duty altered, then re-enabled.
'           : False if the PG3STATbits_UPDREQ is set after the duty value is loaded into the PG3DC SFR
'           : Global variable: PWM3_dFrequency holds the frequency of the PWM3 peripheral
' Output    : None
' Notes     : pDisEn is required, because the PG4STATbits_UPDREQ alone, only works if there are multiple calls to alter the duty value
'
Proc PWM3_Duty(pDuty As HPWM_wDuty, pDisEn As HPWM_tDisEnReq)
Global Dim HPWM_wCalcDuty As Word Shared                                        ' Used to hold the duty cycle value

    HPWM_hCalculateDuty(PWM3_dFrequency, pDuty)                                 ' Calculate the duty, based upon the frequency value held in PWM3_dFrequency
    If pDisEn = False Then                                                      ' Is the pDisEn parameter false?
        PWM3_hDuty(HPWM_wCalcDuty)                                              ' Yes. So do not disable/enable the PWM3 periperal
    Else                                                                        ' Otherwise...
        PWM3_Disable()                                                          ' Disable PWM3 so that a change can occur
        PG3DC = HPWM_wCalcDuty                                                  ' Transfer the duty value into the PWM SFR
        PWM3_Enable()                                                           ' Re-Enable the PWM3 peripheral
    EndIf
EndProc

'-----------------------------------------------------------------------------------------
' Calculate the value to place into the PG4PER SFR, for the frequency of the PWM4 waveform
' Input     : pFreq (HPWM_dFrequency) holds the frequency required (in Hz)
' Output    : Loads the PG4PER SFR with the calculated value
'           : Holds a copy of the frequency value in PWM4_dFrequency
' Notes     : The calculation is: PG4PER = fOsc / (PWM Frequency * PWM_Clk_Prescale)
'
Proc PWM4_Freq(pFreq As HPWM_dFrequency)
Global Dim PWM4_dFrequency As Dword Shared                                      ' Holds a copy of PWM4 frequency value

    PWM4_dFrequency = pFreq                                                     ' Keep a copy of PWM4 frequency value
    PG4PER = (HPWM_cFosc / pFreq)                                               ' Alter the frequency (period) of PWM4
    PWM4_Update()                                                               ' Update PWM4
EndProc

'-----------------------------------------------------------------------------------------
' Alter the Duty of PWM4
' Input     : pDuty (HPWM_wDuty) holds the 8-bit duty value (0 to 255)
'           : pDisEn is true if the PWM peripheral is disables, duty altered, then re-enabled.
'           : False if the PG4STATbits_UPDREQ is set after the duty value is loaded into the PG4DC SFR
'           : Global variable: PWM4_dFrequency holds the frequency of the PWM3 peripheral
' Output    : None
' Notes     : pDisEn is required, because the PG4STATbits_UPDREQ alone, only works if there are multiple calls to alter the duty value
'
Proc PWM4_Duty(pDuty As HPWM_wDuty, pDisEn As HPWM_tDisEnReq)
Global Dim HPWM_wCalcDuty As Word Shared                                        ' Used to hold the duty cycle value

    HPWM_hCalculateDuty(PWM4_dFrequency, pDuty)                                 ' Calculate the duty, based upon the frequency value held in PWM4_dFrequency
    If pDisEn = False Then                                                      ' Is the pDisEn parameter false?
        PWM4_hDuty(HPWM_wCalcDuty)                                              ' Yes. So do not disable/enable the PWM4 periperal
    Else                                                                        ' Otherwise...
        PWM4_Disable()                                                          ' Disable PWM4 so that a change can occur
        PG4DC = HPWM_wCalcDuty                                                  ' Transfer the duty value into the PWM SFR
        PWM4_Enable()                                                           ' Re-Enable the PWM4 peripheral
    EndIf
EndProc

'-----------------------------------------------------------------------------------------
' A helper routine to calculate a duty cycle value required for a given PWM frequency
' Input     : pFreq (HPWM_dFrequency) holds the 32-bit frequency of the PWM
'           : pDuty (HPWM_wDuty) holds the 16-bit duty cycle
' Output    : HPWM_wCalcDuty holds the 16-bit duty cycle value
' Notes     : None
'
Proc HPWM_hCalculateDuty(pFreq As HPWM_dFrequency, pDuty As HPWM_wDuty)
Global Dim HPWM_wCalcDuty As Word Shared                                        ' Used to hold the duty cycle value
    Dim dDutyCalc As Dword                                                      ' Used to calculate the duty cycle value
    dDutyCalc = HPWM_cFosc / pFreq                                              ' Find the maximum duty cycle value for the PWM frequency
    dDutyCalc = (dDutyCalc * pDuty) / 256                                       ' Calculate the 8-bit duty value
    HPWM_wCalcDuty = dDutyCalc.Word0                                            ' Place the duty value into HPWM_wCalcDuty
EndProc

'-----------------------------------------------------------------------------------------
' A helper procedure to setup the Master PWM clock
' Input     : None
' Output    : None
' Notes     : For a dsPIC33CKxxMPx02 devices
'
Proc PWM_hMasterInit()
    PCLKCON   = $00                                                             ' MCLKSEL FOSC uses System Clock. LOCK disabled. DIVSEL is 1:2
    FSCL      = $00
    FSMINPER  = $00
    MPHASE    = $00
    MDC       = $00
    MPER      = $10                                                             ' Master period set for default value
    Lfsr      = $00
EndProc

'-----------------------------------------------------------------------------------------
' Setup the PWM1 peripheral
' Input     : None
' Output    : None
' Notes     : For dsPIC33CKxxMPx02 devices
'
Proc PWM1_Init()
Global Dim PWM1_dFrequency As Dword Shared                                      ' Holds a copy of PWM1 frequency value

$ifdef PWM1_Low_Pin
    PinLow PWM1_Low_Pin                                                         ' Make the PWM1 low pin an output low
$endif
$ifdef PWM1_High_Pin
    PinLow PWM1_High_Pin                                                        ' Make the PWM1 high pin an output low
$endif
    PWM_hMasterInit()                                                           ' Setup the Master PWM clock
    PG1CONL   = %0000000000001000                                               ' MODSEL is Independent Edge. CLKSEL uses Master clock. PWM1 is disabled
    PG1CONH   = $00
    PG1STAT   = $00
    PG1IOCONL = $00
    PG1IOCONH = %0000000000011100                                               ' PENL is enabled. PMOD is Independent. PENH is enabled. POLH is Active-high
    PG1EVTL   = %0000000000001000                                               ' A write of the PG1DC register automatically sets the PG1STATbits_UPDREQ bit
    PG1EVTH   = $00
    PG1FPCIL  = $00
    PG1FPCIH  = $00
    PG1CLPCIL = $00
    PG1CLPCIH = $00
    PG1FFPCIL = $00
    PG1FFPCIH = $00
    PG1SPCIL  = $00
    PG1SPCIH  = $00
    PG1LEBL   = $00
    PG1LEBH   = $00
    PG1PHASE  = $00
    PG1DC     = $00
    PG1DCA    = $00
    PG1PER    = $00
    PG1TRIGA  = $00
    PG1TRIGB  = $00
    PG1TRIGC  = $00
    PG1DTL    = $00
    PG1DTH    = $00
    PWM1_Enable()                                                               ' Enable PWM1
EndProc

'-----------------------------------------------------------------------------------------
' Setup the PWM2 peripheral
' Input     : None
' Output    : None
' Notes     : For dsPIC33CKxxMPx02 devices
'
Proc PWM2_Init()
Global Dim PWM2_dFrequency As Dword Shared                                      ' Holds a copy of PWM2 frequency value

$ifdef PWM2_Low_Pin
    PinLow PWM2_Low_Pin                                                         ' Make the PWM2 low pin an output low
$endif
$ifdef PWM2_High_Pin
    PinLow PWM2_High_Pin                                                        ' Make the PWM2 high pin an output low
$endif
    PWM_hMasterInit()                                                           ' Setup the Master PWM clock
    PG2CONL   = %0000000000001000                                               ' MODSEL is Independent Edge. CLKSEL uses Master clock. PWM2 is disabled
    PG2CONH   = $00
    PG2STAT   = $00
    PG2IOCONL = $00
    PG2IOCONH = %0000000000011100                                               ' PENL is enabled. PMOD is Independent. PENH is enabled. POLH is Active-high
    PG2EVTL   = %0000000000001000                                               ' A write of the PG2DC register automatically sets the PG2STATbits_UPDREQ bit
    PG2EVTH   = $00
    PG2FPCIL  = $00
    PG2FPCIH  = $00
    PG2CLPCIL = $00
    PG2CLPCIH = $00
    PG2FFPCIL = $00
    PG2FFPCIH = $00
    PG2SPCIL  = $00
    PG2SPCIH  = $00
    PG2LEBL   = $00
    PG2LEBH   = $00
    PG2PHASE  = $00
    PG2DC     = $00
    PG2DCA    = $00
    PG2PER    = $00
    PG2TRIGA  = $00
    PG2TRIGB  = $00
    PG2TRIGC  = $00
    PG2DTL    = $00
    PG2DTH    = $00
    PWM2_Enable()                                                               ' Enable PWM2
EndProc

'-----------------------------------------------------------------------------------------
' Setup the PWM3 peripheral
' Input     : None
' Output    : None
' Notes     : For dsPIC33CKxxMPx02 devices
'
Proc PWM3_Init()
Global Dim PWM3_dFrequency As Dword Shared                                      ' Holds a copy of PWM3 frequency value

$ifdef PWM3_Low_Pin
    PinLow PWM3_Low_Pin                                                         ' Make the PWM3 low pin an output low
$endif
$ifdef PWM3_High_Pin
    PinLow PWM3_High_Pin                                                        ' Make the PWM3 high pin an output low
$endif
    PWM_hMasterInit()                                                           ' Setup the Master PWM clock
    PG3CONL   = %0000000000001000                                               ' MODSEL is Independent Edge. CLKSEL uses Master clock. PWM3 is disabled
    PG3CONH   = $00
    PG3STAT   = $00
    PG3IOCONL = $00
    PG3IOCONH = %0000000000011100                                               ' PENL is enabled. PMOD is Independent. PENH is enabled. POLH is Active-high
    PG3EVTL   = %0000000000001000                                               ' A write of the PG3DC register automatically sets the PG3STATbits_UPDREQ bit
    PG3EVTH   = $00
    PG3FPCIL  = $00
    PG3FPCIH  = $00
    PG3CLPCIL = $00
    PG3CLPCIH = $00
    PG3FFPCIL = $00
    PG3FFPCIH = $00
    PG3SPCIL  = $00
    PG3SPCIH  = $00
    PG3LEBL   = $00
    PG3LEBH   = $00
    PG3PHASE  = $00
    PG3DC     = $00
    PG3DCA    = $00
    PG3PER    = $00
    PG3TRIGA  = $00
    PG3TRIGB  = $00
    PG3TRIGC  = $00
    PG3DTL    = $00
    PG3DTH    = $00
    PWM3_Enable()                                                               ' Enable PWM3
EndProc

'-----------------------------------------------------------------------------------------
' Setup the PWM4 peripheral
' Input     : None
' Output    : None
' Notes     : For dsPIC33CKxxMPx02 devices
'
Proc PWM4_Init()
Global Dim PWM4_dFrequency As Dword Shared                                      ' Holds a copy of PWM4 frequency value
$ifdef PWM4_Low_Pin
    PinLow PWM4_Low_Pin                                                         ' Make the PWM4 low pin an output low
$endif
$ifdef PWM4_High_Pin
    PinLow PWM4_High_Pin                                                        ' Make the PWM4 high pin an output low
$endif
    PWM_hMasterInit()                                                           ' Setup the Master PWM clock
    PG4CONL   = %0000000000001000                                               ' MODSEL is Independent Edge. CLKSEL uses Master clock. PWM4 is disabled
    PG4CONH   = $00
    PG4STAT   = $00
    PG4IOCONL = $00
    PG4IOCONH = %0000000000011100                                               ' PENL is enabled. PMOD is Independent. PENH is enabled. POLH is Active-high
    PG4EVTL   = %0000000000001000                                               ' A write of the PG4DC register automatically sets the PG4STATbits_UPDREQ bit
    PG4EVTH   = $00
    PG4FPCIL  = $00
    PG4FPCIH  = $00
    PG4CLPCIL = $00
    PG4CLPCIH = $00
    PG4FFPCIL = $00
    PG4FFPCIH = $00
    PG4SPCIL  = $00
    PG4SPCIH  = $00
    PG4LEBL   = $00
    PG4LEBH   = $00
    PG4PHASE  = $00
    PG4DC     = $00
    PG4DCA    = $00
    PG4PER    = $00
    PG4TRIGA  = $00
    PG4TRIGB  = $00
    PG4TRIGC  = $00
    PG4DTL    = $00
    PG4DTH    = $00
    PWM4_Enable()                                                               ' Enable PWM4
EndProc

'-----------------------------------------------------------------------------------------
' A helper procedure to calculate an integer Log2
' Input     : pValue holds the 16-bit value to get the Log2 of
' Output    : Returns the 16-bit Log2 value
' Notes     : Used to find the amount of bits resolution a duty cycle will be,
'             based upon the frequency of the PWM
'
Proc PWM_IntLog2(pValue As Word), Word
    pValue = Abs(pValue)
    If pValue = 0 Then
        Result = $FFFF
        ExitProc
    EndIf
    If pValue = 1 Then
        Result = $0000
        ExitProc
    EndIf
    Result = 0
    While pValue > 1
        pValue = pValue >> 1
        Inc Result
    Wend
EndProc

$endif          ' _HPWM_CK_INC_

The library has procedures for the four high resolution PWM peripherals on the 28-pin devices, but more can easily be added if required.

Below is a code listing for the Positron16 compiler, for a simple demonstration of the PWM4 peripheral for a dsPIC33CK128MP202 device, operating at 200 MHz on an Amicus16B development board:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Demonstrate the HPWM_CK.inc include file that has procedures for the High Resolution PWM peripherals on dsPIC33CKxxMPxxx, 28-pin devices.
' The device is setup to run at 200 MHz using its internal oscillator, on an 'Easy Driver' Amicus16B development board.
'
' Written by Les Johnson for the Positron16 BASIC Compiler.
' https://sites.google.com/view/rosetta-tech/positron-compilers-experimenters-notebook
'
    Device = 33CK128MP202                                                       ' Tell the compiler what device to compile for
    Declare Xtal = 200                                                          ' Tell the compiler what frequency the device is operating at (in MHz)
'
' Set the USART1 pins for an Amicus16B board
'
    Declare Hserial1_Baud = 115200                                              ' Set the Baud rate of USART1 to 115200
    Declare HRSOut1_Pin   = PORTB.0                                             ' Set pin PORTB.0 for TX1
    Declare HRSIn1_Pin    = PORTB.1                                             ' Set pin PORTB.1 for RX1
'
' Set the PWM4pins, because these are not fixed, as are PWM1, PWM2 and PWM3 pins
'
$define PWM4_Low_Pin PORTB.15                                                   ' The low PWM4 pin
$define PWM4_High_Pin PORTB.14                                                  ' The high PWM4 pin

    Include "HPWM_CK.inc"                                                       ' Load the dsPIC33xxCK PWM library into the program
'
' Create any global variables here
'
    Dim bDuty As Byte                                                           ' Holds the duty cycle value of the PWM

'-----------------------------------------------------------------------------------------
' The main program loop starts here
' Initialise a waveform from the PWM4 peripheral, and alter its duty cycle.
'
Main:
    Setup()                                                                     ' Setup the program and any peripherals

    PWM4_Freq(22000)                                                            ' Set the frequency of the PWM4 waveform to 22 KHz
'
' Find the actual resolution of the duty cycle, and transmit it to a serial terminal.
' This is for demo, and curiosity only, and is not needed for the library procedures.
'
    HRSOut1Ln "Resolution is: ", Dec PWM_IntLog2(HPWM_cFosc / PWM4_dFrequency)  ' Calculate the amount duty resolution there is (in bits)
'
' Alter the duty cycle in a loop
'
    Do                                                                          ' Create a loop
        For bDuty = 0 To 255                                                    ' Create a duty cycle loop
            PWM4_Duty(bDuty, 0)                                                 ' Alter the duty cycle, and only set the UPDREQ bit afterwards
            DelayMS 50                                                          ' A small delay, so that the duty cycles can be seen changing on an oscilloscope
            HRSOut1Ln Dec bDuty                                                 ' Transmit the duty cycle value to a serial terminal
        Next                                                                    ' Close the duty cycle loop
    Loop                                                                        ' Do it forever

'-----------------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    Osc_200MHz()                                                                ' Initialise the internal oscillator to run the device at 200 MHz
    PPS_Unlock()                                                                ' Unlock the PPS
    PPS_Output(cOut_Pin_RP32, cOut_Fn_U1TX)                                     ' Make pin RB0 (PPS pin RP32) USART1 TX for an Amicus16B
    PPS_Input(cIn_Pin_RP33, cIn_Fn_U1RX)                                        ' Make pin RB1 (PPS pin RP33) USART1 RX for an Amicus16B
    PPS_Output(cOut_Pin_RP47, cOut_Fn_PWM4L)                                    ' Make Pin RB15 (PPS pin RP47) the PWM4L
    PPS_Output(cOut_Pin_RP46, cOut_Fn_PWM4H)                                    ' Make Pin RB14 (PPS pin RP46) the PWM4H
    PWM4_Init()                                                                 ' Initialise the PWM4 peripheral
EndProc

'-----------------------------------------------------------------------------------------
' Initialise the internal oscillator to run the device at 200 MHz
' Input     : None
' Output    : None
' Notes     : For a dsPIC33CKxxMP202 device
'
Proc Osc_200MHz()
    CLKDIV    = %0011000000000001                                               ' RCDIV is FRC/1. PREPLL is 1:1
    PLLFBD    = %0000000000110010                                               ' 200 MHz
    OSCTUN    = %0000000000000000
    PLLDIV    = %0000000000010001                                               ' POSTPLL is 1:1. FVCO/4. POST2DIV is 1:1
    ACLKCON1  = %0000000100000001                                               ' FRC Oscillator. PREPLL is 1:1
    APLLFBD1  = %0000000010010110
    APLLDIV1  = %0000000001000001                                               ' APSTSCLR is 1:4. APOST2DIV is 1:1. AVCODIV is FVCO/4
    REFOCONL  = %0000000000000000
    REFOCONH  = %0000000000000000
    REFOTRIMH = %0000000000000000
    Write_OSCCON(%0000000100000001)                                             ' Enable PLL
    DelayMS 100                                                                 ' Wait for the clock to stabilise
EndProc

'-----------------------------------------------------------------------------------------
' Setup the config fuses for an internal oscillator on a dsPIC33CKxxMP202 device.
' The OSC pins are general purpose I/O.
'
    Config FSEC = BWRP_OFF,_                                                    ' Boot Segment may be written
                  BSS_DISABLED,_                                                ' Boot Segment No Protection (other than BWRP)
                  BSEN_OFF,_                                                    ' No Boot Segment
                  GWRP_OFF,_                                                    ' General Segment may be written
                  GSS_DISABLED,_                                                ' General Segment No Protection (other than GWRP)
                  CWRP_OFF,_                                                    ' Configuration Segment may be written
                  CSS_DISABLED,_                                                ' Configuration Segment No Protection (other than CWRP)
                  AIVTDIS_OFF                                                   ' Alternate Interrupt Vector Table Disabled

    Config FOSCSEL = FNOSC_FRC,_                                                ' Oscillator Source is Internal Fast RC (FRC)
                     IESO_OFF                                                   ' Start up with user-selected oscillator source

    Config FOSC = POSCMD_NONE,_                                                 ' Primary Oscillator disabled
                  OSCIOFNC_ON,_                                                 ' OSC2 is general purpose digital I/O pin
                  FCKSM_CSECMD,_                                                ' Clock switching is enabled. Fail-safe Clock Monitor is disabled
                  PLLKEN_ON,_                                                   ' PLL clock output will be disabled if Lock is lost
                  XTCFG_G3,_                                                    ' XT Config is 24 to 32 MHz crystals
                  XTBST_ENABLE                                                  ' XT Boost the kick-start

    Config FWDT = RWDTPS_PS2147483648,_                                         ' Run Mode Watchdog Timer Post Scaler is 1:2147483648
                  RCLKSEL_LPRC,_                                                ' Watchdog Timer Clock is use LPRC
                  WINDIS_ON,_                                                   ' Watchdog Timer in Non-Window mode
                  WDTWIN_WIN25,_                                                ' Watchdog Timer Window is 25% of WDT period
                  SWDTPS_PS2147483648,_                                         ' Sleep Mode Watchdog Timer Post Scaler is 1:2147483648
                  FWDTEN_ON_SW                                                  ' Watchdog Timer controlled via software. Use WDTCON.ON bit

    Config FPOR = BISTDIS_DISABLED                                              ' Memory BIST on reset disabled

    Config FICD = ICS_PGD1,_                                                    ' ICD Communicate on PGC1 and PGD1
                  JTAGEN_OFF                                                    ' JTAG is disabled

    Config FDMT = DMTDIS_OFF                                                    ' Dead Man Timer is disabled and can be enabled by software

    Config FDEVOPT = ALTI2C1_OFF,_                                              ' I2C1 mapped to SDA1/SCL1 pins
                     ALTI2C2_OFF,_                                              ' I2C2 mapped to SDA2/SCL2 pins
                     SMBEN_STANDARD,_                                           ' I2C input buffer operation
                     SPI2PIN_PPS                                                ' SPI2 uses I/O remap (PPS) pins

    Config FALTREG = CTXT1_OFF,_                                                ' (IPL) Associated to Alternate Working Register Not Assigned
                     CTXT2_OFF,_                                                ' (IPL) Associated to Alternate Working Register Not Assigned
                     CTXT3_OFF,_                                                ' (IPL) Associated to Alternate Working Register Not Assigned
                     CTXT4_OFF                                                  ' (IPL) Associated to Alternate Working Register Not Assigned

The demo will set the PWM waveform to 22 KHz, and continuously sweep its duty cycle in a loop. However, tests have shown that when the dsPIC33 device is operating at 200 MHz, a PWM waveform of 20 MHz is achievable. At very high frequencies the waveform is not an exact square wave, but it is still operational.

Below is a picture of the demonstration working, and shows the 22 KHz waveform on the lovely oscilloscope I got from Tim a few years ago:

Positron16 - PWM_On_Scope.jpg

The demo and the library are also attached to this post, and the file is named: "Positron16 - HPWM_CK_Library.zip"