Positron8 - Hardware PWM library for PIC18FxxK42 devices

Started by top204, Aug 09, 2022, 12:27 PM

Previous topic - Next topic

top204

As an offshoot to the RC5 infrared thread, I have listed below an include file that controls all 8 PWM channels on a PIC18F27K42 or PIC18F47K42 device. I created the library for the Positron8 board I was developing.

The last time I used it, it worked fine for the tests I made. It also allows frequencies above 1 MHz to come out of the PWM channels when the device is operating at 64Mhz, because of the extra prescalers the Timers have.

The library's code listing is below:

$ifndef _CCP_PWM_K42_INC_
$define _CCP_PWM_K42_INC_
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Hardware PWM library for the PIC18FxxK42 devices.
' Written for the Positron8 BASIC Compiler by Les Johnson.
'
$if (_device <> _18F27K42) And (_device <> _18F47K42)
    $error "The CCP_PWM libary is only for 18F27K42 or 18F47K42 devices"
$endif
'-------------------------------------
' Procedures:
' HPWM1(pTimer, pFrequency, pDuty)
' HPWM2(pTimer, pFrequency, pDuty)
' HPWM3(pTimer, pFrequency, pDuty)
' HPWM4(pTimer, pFrequency, pDuty)
' HPWM5(pTimer, pFrequency, pDuty)
' HPWM6(pTimer, pFrequency, pDuty)
' HPWM7(pTimer, pFrequency, pDuty)
' HPWM8(pTimer, pFrequency, pDuty)
'
' Alters the frequency the a PWM waveform and the duty cycle then starts it.
'
' pTimer chooses the timer to use for the PWM clock: Timer2, Timer4 or Timer6
' pFrequency alters the frequency (in Hz) of a CCP or PWM peripheral and can be any value from 0 to 1000000,
' however, lower or upper frequencies depend on the microcontroller's oscillator.
' pDuty alters the duty cycle of a PWM waveform and can be any value from 0 to 255.
'
' On completion, the Carry flag (STATUSbits_C) will hold 1 if the frequency is achievable.
' Use this as a guide indication only and not as an absolute indicator.
'
'-------------------------------------
' Procedures:
' HPWM1_Duty(pDuty)
' HPWM2_Duty(pDuty)
' HPWM3_Duty(pDuty)
' HPWM4_Duty(pDuty)
' HPWM5_Duty(pDuty)
' HPWM6_Duty(pDuty)
' HPWM7_Duty(pDuty)
' HPWM8_Duty(pDuty)
'
' Alters the duty of the PWM waveform
'
' pDuty alters the duty cycle of a waveform and can be any value from 0 to 255.
'
'-------------------------------------
' Meta-Macros:
' HPWM1_Start()
' HPWM2_Start()
' HPWM3_Start()
' HPWM4_Start()
' HPWM5_Start()
' HPWM6_Start()
' HPWM7_Start()
' HPWM8_Start()
'
' Enables a PWM peripheral
'
'-------------------------------------
' Meta-Macros:
' HPWM1_Stop()
' HPWM2_Stop()
' HPWM3_Stop()
' HPWM4_Stop()
' HPWM5_Stop()
' HPWM6_Stop()
' HPWM7_Stop()
' HPWM8_Stop()
'
' Disables a PWM peripheral
'
'-------------------------------------------------------------------------------------------------------
'
' Create some PPS defines to make the CCP types look like PWM types
'
$define PPS_Fn_PWM1 PPS_Fn_CCP1
$define PPS_Fn_PWM2 PPS_Fn_CCP2
$define PPS_Fn_PWM3 PPS_Fn_CCP3
$define PPS_Fn_PWM4 PPS_Fn_CCP4
'
' Create defines for the valid timers to use for the PWM generators
'
$define HPWM_cTimer2 2
$define HPWM_cTimer4 4
$define HPWM_cTimer6 6

$define HPWM_cFosc $eval (_xtal * 1000)
'
' Create variables used by the library routines
'
    Dim HPWM_dFrequency      As Dword                   ' Used to pass the 32-bit frequency value as a parameter
    Dim HPWM_bDuty           As Byte                    ' Used to pass the 8-bit duty cycle as a paramater
    Dim HPWM_bTimerReq       As Byte                    ' Used to pass the timer required as a parameter
    Dim HPWM_bPrescaleSelect As Byte                    ' Holds the timer's prescaler chosen for a particular frequency
    Dim HPWM_dCalcDuty       As Dword                   ' Used to calcualte the duty cycle value
    Dim HPWM_wCalcDuty       As HPWM_dCalcDuty.Word0    ' Used to pass the duty cycle value to other procedures
    Dim HPWM_wTxCON_Addr     As Word                    ' Holds the address of a timer's TxCON SFR. i.e. T2CON, T4CON or T6CON
    Dim HPWM_wPR_Addr        As Word                    ' Holds the address of a timer's PR SFR. i.e. T2PR, T4PR or T6PR

'-----------------------------------------------------------------------------
' A helper routine to calculate a duty cycle required for a given PWM frequency
' Input     : HPWM_dFrequency holds the 32-bit frequency of the PWM
'           : HPWM_bPrescaleSelect holds the 8-bit prescale value
'           : HPWM_bDuty holds the 8-bit duty cycle (0 to 255)
' Output    : HPWM_wCalcDuty holds the 16-bit duty cycle value
' Notes     : None
'

Proc HPWM_hCalculateDuty()
    Dim dMaxDuty As Dword

    HPWM_dCalcDuty = HPWM_dFrequency * HPWM_bPrescaleSelect ' \
    HPWM_wCalcDuty = HPWM_cFosc / HPWM_dCalcDuty            ' / Determine the maximum duty
    dMaxDuty = HPWM_wCalcDuty * HPWM_bDuty                  ' \
    HPWM_wCalcDuty = dMaxDuty / 256                         ' / Calculate the duty value
EndProc

'-----------------------------------------------------------------------------
' A helper routine to calculate the values required for a given timer, frequency and duty cycle
' Input     : pFrequency holds the 32-bit frequency (in Hz)
'           : Global variable HPWM_bDuty holds the 8-bit duty cycle (0 to 255)
'           : Global Variable HPWM_wTxCON_Addr holds the address of the timer's TxCON SFR. i.e. T2CON, T4CON or T6CON
'           : Global Variable HPWM_wPR_Addr holds the address of the timer's PR SFR. i.e. T2PR, T4PR or T6PR
' Output    : Returns 1 if the frequency is possibly achievable, Otherwise, returns a 0
'           : Global variable HPWM_bPrescaleSelect holds the timer's prescaler value for the required frequency
' Notes     : None
'
Proc HPWM_hCalcFreq(pFrequency As HPWM_dFrequency), STATUSbits_C
    Dim wPRConst   As Word                                  ' A temporary variable for calculations
    Dim bTConValue As Byte                                  ' Holds the value to place into a timer's TxCON SFR
    Dim wPRxValue  As Word                                  ' Holds the value that is placed into a timer's PR SFR

    pFrequency = pFrequency / 1000
    wPRConst = HPWM_cFosc / pFrequency
    wPRConst = wPRConst / 4
'
' Loop through all the valid prescalers
'
    HPWM_bPrescaleSelect = 1
    Repeat
        wPRxValue = wPRConst / HPWM_bPrescaleSelect         ' \
        wPRxValue = wPRxValue - 1                           ' / Calculate a timer's PR2 value
        If wPRxValue <= 255 Then                            ' \ Is the value to place into the timer's PRx valid?
            If wPRxValue > 1 Then                           ' /
                bTConValue = 0b10000000                     ' Yes. So Default to TxCON loaded with 0 (1:1 Prescaler) and Enable the timer
                If HPWM_bPrescaleSelect = 2 Then            ' Is HPWM_bPrescaleSelect 2?
                    bTConValue = 0b10010000                 ' Yes. So load bTConValue for a 1:2 Prescaler and Enable the timer
                ElseIf HPWM_bPrescaleSelect = 4 Then        ' Is HPWM_bPrescaleSelect 4?
                    bTConValue = 0b10100000                 ' Yes. So load bTConValue for a 1:4 Prescaler and Enable the timer
                ElseIf HPWM_bPrescaleSelect = 8 Then        ' Is HPWM_bPrescaleSelect 8?
                    bTConValue = 0b10110000                 ' Yes. So load bTConValue for a 1:8 Prescaler and Enable the timer
                ElseIf HPWM_bPrescaleSelect = 16 Then       ' Is HPWM_bPrescaleSelect 16?
                    bTConValue = 0b11000000                 ' Yes. So load bTConValue for a 1:16 Prescaler and Enable the timer
                ElseIf HPWM_bPrescaleSelect = 32 Then       ' Is HPWM_bPrescaleSelect 32?
                    bTConValue = 0b11010000                 ' Yes. So load bTConValue for a 1:32 Prescaler and Enable the timer
                ElseIf HPWM_bPrescaleSelect = 64 Then       ' Is HPWM_bPrescaleSelect 64?
                    bTConValue = 0b11100000                 ' Yes. So load bTConValue for a 1:64 Prescaler and Enable the timer
                ElseIf HPWM_bPrescaleSelect = 128 Then      ' Is HPWM_bPrescaleSelect 128?
                    bTConValue = 0b11110000                 ' Yes. So adjust T2CON for a 1:128 Prescaler and Enable the timer
                EndIf
                HPWM_hCalculateDuty()                       ' Calculate the duty value based upon global variable HPWM_bDuty

                Ptr8(HPWM_wPR_Addr)    = wPRxValue.Byte0    ' Load the appropriate timer's PR register. T2PR, T4PR or T6PR
                Ptr8(HPWM_wTxCON_Addr) = bTConValue         ' Load the appropriate timer's TxCON register

                Result = 1                                  ' Return the Carry flag holding 1 if successful
                ExitProc                                    ' Exit the procedure
            EndIf
        EndIf
        HPWM_bPrescaleSelect = HPWM_bPrescaleSelect * 2     ' Move to the prescaler select value
    Until HPWM_bPrescaleSelect > 128
    Result = 0                                              ' Return the Carry flag holding 0 if unsuccesful
EndProc

'-----------------------------------------------------------------------------
' Select the Timer to use for a CCP peripheral operating as PWM, or a PWM peripheral
' Input     : pChannel holds the PWM channel (1 to 8)
'           : pTimer holds the timer to use: HPWM_cTimer2, HPWM_cTimer4 or HPWM_cTimer6
' Output    : HPWM_wTxCON_Addr holds the address of the timer's TxCON SFR. i.e. T2CON, T4CON or T6CON
'           : HPWM_wPR_Addr holds the address of the timer's PR SFR. i.e. T2PR, T4PR or T6PR
' Notes     : The timer chosen has its clock set to fOsc/4
'
Proc HPWM_SelectTimer(pChannel As Byte, pTimer As HPWM_bTimerReq)
    Dim CCPTValue As Byte                                   ' Holds the bit values required for a particular timer
    If pChannel = 0 Then ExitProc

    If pTimer = HPWM_cTimer2 Then                           ' Is Timer2 being used for the PWM
        CCPTValue.0 = 1                                     ' \ Yes. So select Timer2
        CCPTValue.1 = 0                                     ' /
        T2CLK = 0b00000001                                  ' T2CS is fOsc/4
        HPWM_wTxCON_Addr = AddressOf(T2CON)                 ' Load the address of T2CON into HPWM_wTxCON_Addr
        HPWM_wPR_Addr    = AddressOf(T2PR)                  ' Load the address of T2PR into HPWM_wPR_Addr

    ElseIf pTimer = HPWM_cTimer4 Then                       ' Is Timer4 being used for the PWM
        CCPTValue.0 = 0                                     ' \ Yes. So select Timer4
        CCPTValue.1 = 1                                     ' /
        T4CLK = 0b00000001                                  ' T4CS is fOsc/4
        HPWM_wTxCON_Addr = AddressOf(T4CON)                 ' Load the address of T4CON into HPWM_wTxCON_Addr
        HPWM_wPR_Addr    = AddressOf(T4PR)                  ' Load the address of T4PR into HPWM_wPR_Addr

    ElseIf pTimer = HPWM_cTimer6 Then                       ' Is Timer6 being used for the PWM
        CCPTValue.0 = 1                                     ' \ Yes. So select Timer6
        CCPTValue.1 = 1                                     ' /
        T6CLK = 0b00000001                                  ' T6CS is fOsc/4
        HPWM_wTxCON_Addr = AddressOf(T6CON)                 ' Load the address of T6CON into HPWM_wTxCON_Addr
        HPWM_wPR_Addr    = AddressOf(T6PR)                  ' Load the address of T6PR into HPWM_wPR_Addr
    Else                                                    ' Otherwise... Not a valid timer
        ExitProc                                            ' So exit the procedure
    EndIf

    If pChannel = 1 Then
        CCPTMRS0bits_C1TSEL0 = CCPTValue.0                  ' \ Select the timer for the CCP1 peripheral acting as PWM
        CCPTMRS0bits_C1TSEL1 = CCPTValue.1                  ' /

    ElseIf pChannel = 2 Then
        CCPTMRS0bits_C2TSEL0 = CCPTValue.0                  ' \ Select the timer for the CCP2 peripheral acting as PWM
        CCPTMRS0bits_C2TSEL1 = CCPTValue.1                  ' /

    ElseIf pChannel = 3 Then
        CCPTMRS0bits_C3TSEL0 = CCPTValue.0                  ' \ Select the timer for the CCP3 peripheral acting as PWM
        CCPTMRS0bits_C3TSEL1 = CCPTValue.1                  ' /

    ElseIf pChannel = 4 Then
        CCPTMRS0bits_C4TSEL0 = CCPTValue.0                  ' \ Select the timer for the CCP4 peripheral acting as PWM
        CCPTMRS0bits_C4TSEL1 = CCPTValue.1                  ' /

    ElseIf pChannel = 5 Then
        CCPTMRS1bits_P5TSEL0 = CCPTValue.0                  ' \ Select the timer for the PWM5 peripheral
        CCPTMRS1bits_P5TSEL1 = CCPTValue.1                  ' /

    ElseIf pChannel = 6 Then
        CCPTMRS1bits_P6TSEL0 = CCPTValue.0                  ' \ Select the timer for the PWM6 peripheral
        CCPTMRS1bits_P6TSEL1 = CCPTValue.1                  ' /

    ElseIf pChannel = 7 Then
        CCPTMRS1bits_P7TSEL0 = CCPTValue.0                  ' \ Select the timer for the PWM7 peripheral
        CCPTMRS1bits_P7TSEL1 = CCPTValue.1                  ' /

    ElseIf pChannel = 8 Then
        CCPTMRS1bits_P8TSEL0 = CCPTValue.0                  ' \ Select the timer for the PWM8 peripheral
        CCPTMRS1bits_P8TSEL1 = CCPTValue.1                  ' /
    EndIf
EndProc

'-----------------------------------------------------------------------------
' Set the frequency of all the PWM channels
' Input     : pFrequency holds the 32-bit frequency (in Hz)
' Output    : Returns 1 if the frequency is possibly achievable, Otherwise, returns a 0
' Notes     : Uses Timer2 for the clock source for the CCP and PWM peripherals
'
Proc HPWM_SetFreq(pFrequency As HPWM_dFrequency), STATUSbits_C
    Dim wPRConst As Word
    Dim wPRxValue As Word

    HPWM_bDuty = 0                                          ' Set the duty value to 0
    T2CLK = 0b00000001                                      ' T2CS is FOSC/4
    CCPTMRS0 = 0b01010101                                   ' Select Timer2 for the CCP peripherals acting as PWM
    CCPTMRS1 = 0b01010101                                   ' Select Timer2 for the PWM peripherals

    pFrequency = pFrequency / 1000
    wPRConst = HPWM_cFosc / pFrequency
    wPRConst = wPRConst / 4
'
' Loop through all the valid prescalers
'
    HPWM_bPrescaleSelect = 1
    Repeat
        wPRxValue = wPRConst / HPWM_bPrescaleSelect         ' \
        wPRxValue = wPRxValue - 1                           ' / Calculate a PR2 value
        If wPRxValue <= 255 Then                            ' \ Is the value to place into PR2 valid?
            If wPRxValue > 1 Then                           ' /
                T2CON = 0b10000000                          ' Yes. So Default to T2CON loaded with 0 (1:1 Prescaler)
                If HPWM_bPrescaleSelect = 2 Then            ' Is HPWM_bPrescaleSelect 2?
                    T2CON = 0b10010000                      ' Yes. So adjust T2CON for a 1:2 Prescaler
                ElseIf HPWM_bPrescaleSelect = 4 Then        ' Is HPWM_bPrescaleSelect 4?
                    T2CON = 0b10100000                      ' Yes. So adjust T2CON for a 1:4 Prescaler
                ElseIf HPWM_bPrescaleSelect = 8 Then        ' Is HPWM_bPrescaleSelect 8?
                    T2CON = 0b10110000                      ' Yes. So adjust T2CON for a 1:8 Prescaler
                ElseIf HPWM_bPrescaleSelect = 16 Then       ' Is HPWM_bPrescaleSelect 16?
                    T2CON = 0b11000000                      ' Yes. So adjust T2CON for a 1:16 Prescaler
                ElseIf HPWM_bPrescaleSelect = 32 Then       ' Is HPWM_bPrescaleSelect 32?
                    T2CON = 0b11010000                      ' Yes. So adjust T2CON for a 1:32 Prescaler
                ElseIf HPWM_bPrescaleSelect = 64 Then       ' Is HPWM_bPrescaleSelect 64?
                    T2CON = 0b01100000                      ' Yes. So adjust T2CON for a 1:64 Prescaler
                ElseIf HPWM_bPrescaleSelect = 128 Then      ' Is HPWM_bPrescaleSelect 128?
                    T2CON = 0b11110000                      ' Yes. So adjust T2CON for a 1:128 Prescaler
                EndIf
                T2PR = wPRxValue                            ' Load T2PR
                HPWM_hCalculateDuty()                       ' Calculate the duty value based upon global variable HPWM_bDuty
                Result = 1                                  ' Return Carry flag holding 1 if successful
                ExitProc                                    ' Exit the procedure
            EndIf
        EndIf
        HPWM_bPrescaleSelect = HPWM_bPrescaleSelect * 2     ' Move to the prescaler select value
    Until HPWM_bPrescaleSelect > 128
    Result = 0                                              ' Return Carry flag holding 0 if unsuccesful
EndProc

'*****************************************************************************
$ifdef _CCP1CON
'-----------------------------------------------------------------------------
' Start CCP1 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM1_Start()         '
    CCP1CON = 0b10001100      '
#ifSym __CCP1_PIN             '
    PinOutput __CCP1_PORT_PIN '
#endIfSym

'-----------------------------------------------------------------------------
' Stop CCP1 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM1_Stop()         '
#ifSym __CCP1_PIN            '
    PinInput __CCP1_PORT_PIN '
#endIfSym                    '
   CCP1CON = 0b00001100

'-----------------------------------------------------------------------------
' Helper routine to load the duty cycle value into the CCPR1L and CCPR1H SFRs
' Input     : HPWM_dCalcDuty holds the duty cycle value
' Output    : None
' Notes     : None
'
$define HPWM1_hSetDuty()                                     '
    HPWM_dCalcDuty.Byte1 = HPWM_dCalcDuty.Byte1 & 0b00000011 '
    CCPR1H = HPWM_dCalcDuty.Byte1                            '
    CCPR1L = HPWM_dCalcDuty.Byte0

'-----------------------------------------------------------------------------
' Adjust the duty cycle of CCP1
' Input     : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : None
' Notes     : None
'
Proc HPWM1_Duty(pDuty As HPWM_bDuty)
    HPWM_hCalculateDuty()
    HPWM1_hSetDuty()
EndProc

'-----------------------------------------------------------------------------
' Calculate the values required for a required frequency and duty cycle for CCP1
' Input     : pFrequency (HPWM_dFrequency) holds the 32-bit frequency (in Hz)
'           : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : Returns 1 if the frequency is possibly achievable, Otherwise, returns a 0
' Notes     : Also starts the PWM for CCP1
'
Proc HPWM1(pTimer As HPWM_bTimerReq, pFrequency As HPWM_dFrequency, pDuty As HPWM_bDuty), STATUSbits_C
    HPWM_SelectTimer(1, pTimer)                         ' Choose which timer to use for the channel
    HPWM_hCalcFreq(pFrequency)                          ' Calculate the values for a given frequency and duty cycle
    If STATUSbits_C = 1 Then                            ' Was the frequency accepted?
        HPWM1_hSetDuty()                                ' Yes. So load the CCP1 registers with the appropriate value
        HPWM1_Start()                                   ' Start CCP1
        Result = 1                                      ' Indicate everything OK
    EndIf
EndProc
$endif ' _CCP1CON

'*****************************************************************************
$ifdef _CCP2CON
'-----------------------------------------------------------------------------
' Start CCP2 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM2_Start()         '
    CCP2CON = 0b10001100      '
#ifSym __CCP2_PIN             '
    PinOutput __CCP2_PORT_PIN '
#endIfSym

'-----------------------------------------------------------------------------
' Stop CCP2 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM2_Stop()         '
#ifSym __CCP2_PIN            '
    PinInput __CCP2_PORT_PIN '
#endIfSym                    '
   CCP2CON = 0b00001100

'-----------------------------------------------------------------------------
' Helper routine to load the duty cycle value into the CCPR2L and CCPR2H SFRs
' Input     : HPWM_dCalcDuty holds the duty cycle value
' Output    : None
' Notes     : None
'
$define HPWM2_hSetDuty()                                     '
    HPWM_dCalcDuty.Byte1 = HPWM_dCalcDuty.Byte1 & 0b00000011 '
    CCPR2H = HPWM_dCalcDuty.Byte1                            '
    CCPR2L = HPWM_dCalcDuty.Byte0

'-----------------------------------------------------------------------------
' Adjust the duty cycle of CCP2
' Input     : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : None
' Notes     : None
'
Proc HPWM2_Duty(pDuty As HPWM_bDuty)
    HPWM_hCalculateDuty()
    HPWM2_hSetDuty()
EndProc

'-----------------------------------------------------------------------------
' Calculate the values required for a required frequency and duty cycle for CCP2
' Input     : pFrequency holds the 32-bit frequency (in Hz)
'           : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : Returns 1 if the frequency is possibly achievable, Otherwise, returns a 0
' Notes     : Also starts the PWM for CCP2
'
Proc HPWM2(pTimer As HPWM_bTimerReq, pFrequency As HPWM_dFrequency, pDuty As HPWM_bDuty), STATUSbits_C
    HPWM_SelectTimer(2, pTimer)                         ' Choose which timer to use for the channel
    HPWM_hCalcFreq(pFrequency)                          ' Calculate the values for a given frequency and duty cycle
    If STATUSbits_C = 1 Then                            ' Was the frequency accepted?
        HPWM2_hSetDuty()                                ' Yes. So load the CCP2 registers with the appropriate value
        HPWM2_Start()                                   ' Start CCP2
        Result = 1                                      ' Indicate everything OK
    EndIf
EndProc
$endif ' _CCP2CON

'*****************************************************************************
$ifdef _CCP3CON
'-----------------------------------------------------------------------------
' Start CCP3 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM3_Start()          '
    CCP3CON = 0b10001100       '
#ifSym __CCP3_PIN              '
    PinOutput __CCP3_PORT_PIN  '
#endIfSym

'-----------------------------------------------------------------------------
' Stop CCP3 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM3_Stop()        '
#ifSym __CCP3_PIN           '
   PinInput __CCP3_PORT_PIN '
#endIfSym                   '
   CCP3CON = 0b00001100

'-----------------------------------------------------------------------------
' Helper routine to load the duty cycle value into the CCPR3L and CCPR3H SFRs
' Input     : HPWM_dCalcDuty holds the duty cycle value
' Output    : None
' Notes     : None
'
$define HPWM3_hSetDuty()                                    '
    HPWM_dCalcDuty.Byte1 = HPWM_dCalcDuty.Byte1 & 0b00000011 '
    CCPR3H = HPWM_dCalcDuty.Byte1                           '
    CCPR3L = HPWM_dCalcDuty.Byte0

'-----------------------------------------------------------------------------
' Adjust the duty cycle of CCP3
' Input     : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : None
' Notes     : None
'
Proc HPWM3_Duty(pDuty As HPWM_bDuty)
    HPWM_hCalculateDuty()
    HPWM3_hSetDuty()
EndProc

'-----------------------------------------------------------------------------
' Calculate the values required for a required frequency and duty cycle for CCP3
' Input     : pFrequency holds the 16-bit frequency (in Hz)
'           : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : Returns 1 if the frequency is possibly achievable, Otherwise, returns a 0
' Notes     : Also starts the PWM for CCP3
'
Proc HPWM3(pTimer As HPWM_bTimerReq, pFrequency As HPWM_dFrequency, pDuty As HPWM_bDuty), STATUSbits_C
    HPWM_SelectTimer(3, pTimer)                         ' Choose which timer to use for the channel
    HPWM_hCalcFreq(pFrequency)                          ' Calculate the values for a given frequency and duty cycle
    If STATUSbits_C = 1 Then                            ' Was the frequency accepted?
        HPWM3_hSetDuty()                                ' Yes. So load the CCP3 registers with the appropriate value
        HPWM3_Start()                                   ' Start CCP3
        Result = 1
    EndIf
EndProc
$endif  ' _CCP3CON

'*****************************************************************************
$ifdef _CCP4CON
'-----------------------------------------------------------------------------
' Start CCP4 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM4_Start()          '
    CCP4CON = 0b10001100       '
#ifSym __CCP4_PIN              '
    PinOutput __CCP4_PORT_PIN  '
#endIfSym

'-----------------------------------------------------------------------------
' Stop CCP4 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM4_Stop()        '
#ifSym __CCP4_PIN           '
   PinInput __CCP4_PORT_PIN '
#endIfSym                   '
   CCP4CON = 0b00001100

'-----------------------------------------------------------------------------
' Helper routine to load the duty cycle value into the CCPR4L and CCPR4H SFRs
' Input     : HPWM_dCalcDuty holds the duty cycle value
' Output    : None
' Notes     : None
'
$define HPWM4_hSetDuty()                                    '
    HPWM_dCalcDuty.Byte1 = HPWM_dCalcDuty.Byte1 & 0b00000011 '
    CCPR4H = HPWM_dCalcDuty.Byte1                           '
    CCPR4L = HPWM_dCalcDuty.Byte0

'-----------------------------------------------------------------------------
' Adjust the duty cycle of CCP4
' Input     : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : None
' Notes     : None
'
Proc HPWM4_Duty(pDuty As HPWM_bDuty)
    HPWM_hCalculateDuty()
    HPWM4_hSetDuty()
EndProc

'-----------------------------------------------------------------------------
' Calculate the values required for a required frequency and duty cycle for CCP4
' Input     : pFrequency holds the 16-bit frequency (in Hz)
'           : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : Returns 1 if the frequency is possibly achievable, otherwise, returns a 0
' Notes     : Also starts the PWM for CCP4
'
Proc HPWM4(pTimer As HPWM_bTimerReq, pFrequency As HPWM_dFrequency, pDuty As HPWM_bDuty), STATUSbits_C
    HPWM_SelectTimer(4, pTimer)                         ' Choose which timer to use for the channel
    HPWM_hCalcFreq(pFrequency)                          ' Calculate the values for a given frequency and duty cycle
    If STATUSbits_C = 1 Then                            ' Was the frequency accepted?
        HPWM4_hSetDuty()                                ' Yes. So load the CCP4 registers with the appropriate value
        HPWM4_Start()                                   ' Start CCP4
        Result = 1                                      ' Indicate everything OK
    EndIf
EndProc
$endif  ' _CCP4CON

'*****************************************************************************
$ifdef _PWM5CON
'-----------------------------------------------------------------------------
' Start PWM5 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM5_Start()           '
    PWM5CON = 0b10000000        '
#ifSym __HPWM5_PIN              '
    PinOutput __HPWM5_PORT_PIN  '
#endIfSym

'-----------------------------------------------------------------------------
' Stop PWM5 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM5_Stop()         '
#ifSym __HPWM5_PIN           '
   PinInput __HPWM5_PORT_PIN '
#endIfSym                    '
   PWM5CON = 0b00000000

'-----------------------------------------------------------------------------
' Helper routine to load the duty cycle value into the PWM5DCL and PWM5DCH SFRs
' Input     : HPWM_wCalcDuty holds the duty cycle value
' Output    : None
' Notes     : None
'
Proc HPWM5_hSetDuty()
    WREG = 0                            ' Clear WREG before the shifts
    Ror HPWM_wCalcDuty                  ' Rotate the Least Significant bit into the Carry flag
    Ror WREG                            ' Rotate the Carry flag into the Most Significant bit
    Ror HPWM_wCalcDuty                  ' Rotate the Least Significant bit into the Carry flag
    Ror WREG                            ' Rotate the Carry flag into the Most Significant bit
    PWM5DCL = WREG
    PWM5DCH = HPWM_dCalcDuty.Byte0
EndProc

'-----------------------------------------------------------------------------
' Adjust the duty cycle of PWM5
' Input     : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : None
' Notes     : None
'
Proc HPWM5_Duty(pDuty As HPWM_bDuty)
    HPWM_hCalculateDuty()                               ' Calculate the duty value into HPWM_wCalcDuty
    HPWM5_hSetDuty()
EndProc

'-----------------------------------------------------------------------------
' Calculate the values required for a required frequency and duty cycle for PWM5
' Input     : pFrequency holds the 16-bit frequency (in Hz)
'           : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : Returns 1 if the frequency is possibly achievable, otherwise, returns a 0
' Notes     : Also starts the PWM for PWM5
'
Proc HPWM5(pTimer As HPWM_bTimerReq, pFrequency As HPWM_dFrequency, pDuty As HPWM_bDuty), STATUSbits_C
    HPWM_SelectTimer(5, pTimer)                         ' Choose which timer to use for the channel
    HPWM_hCalcFreq(pFrequency)                          ' Calculate the values for a given frequency and duty cycle
    If STATUSbits_C = 1 Then                            ' Was the frequency accepted?
        HPWM5_hSetDuty()                                ' Yes. So load the PWM5DC registers with the appropriate value
        HPWM5_Start()                                   ' Start PWM5
        Result = 1                                      ' Indicate everything OK
    EndIf
EndProc
$endif  ' _PWM5CON

'*****************************************************************************
$ifdef _PWM6CON
'-----------------------------------------------------------------------------
' Start PWM6 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM6_Start()           '
    PWM6CON = 0b10000000        '
#ifSym __HPWM6_PIN              '
    PinOutput __HPWM6_PORT_PIN  '
#endIfSym

'-----------------------------------------------------------------------------
' Stop PWM6 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM6_Stop()         '
#ifSym __HPWM6_PIN           '
   PinInput __HPWM6_PORT_PIN '
#endIfSym                    '
   PWM6CON = 0b00000000

'-----------------------------------------------------------------------------
' Helper routine to load the duty cycle value into the PWM6DCL and PWM6DCH SFRs
' Input     : HPWM_wCalcDuty holds the duty cycle value
' Output    : None
' Notes     : None
'
Proc HPWM6_hSetDuty()
    WREG = 0                            ' Clear WREG before the shifts
    Ror HPWM_wCalcDuty                  ' Rotate the Least Significant bit into the Carry flag
    Ror WREG                            ' Rotate the Carry flag into the Most Significant bit
    Ror HPWM_wCalcDuty                  ' Rotate the Least Significant bit into the Carry flag
    Ror WREG                            ' Rotate the Carry flag into the Most Significant bit
    PWM6DCL = WREG
    PWM6DCH = HPWM_dCalcDuty.Byte0
EndProc

'-----------------------------------------------------------------------------
' Adjust the duty cycle of PWM6
' Input     : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : None
' Notes     : None
'
Proc HPWM6_Duty(pDuty As HPWM_bDuty)
    HPWM_hCalculateDuty()                               ' Calculate the duty value into HPWM_wCalcDuty
    HPWM6_hSetDuty()
EndProc

'-----------------------------------------------------------------------------
' Calculate the values required for a required frequency and duty cycle for PWM6
' Input     : pFrequency holds the 32-bit frequency (in Hz)
'           : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : Returns 1 if the frequency is possibly achievable, otherwise, returns a 0
' Notes     : Also starts the PWM for PWM6
'
Proc HPWM6(pTimer As HPWM_bTimerReq, pFrequency As HPWM_dFrequency, pDuty As HPWM_bDuty), STATUSbits_C
    HPWM_SelectTimer(6, pTimer)                         ' Choose which timer to use for the channel
    HPWM_hCalcFreq(pFrequency)                          ' Calculate the values for a given frequency and duty cycle
    If STATUSbits_C = 1 Then                            ' Was the frequency accepted?
        HPWM6_hSetDuty()                                ' Yes. So load the PWM6DC registers with the appropriate value
        HPWM6_Start()                                   ' Start PWM6
        Result = 1                                      ' Indicate everything OK
    EndIf
EndProc
$endif  ' _PWM6CON

'*****************************************************************************
$ifdef _PWM7CON
'-----------------------------------------------------------------------------
' Start PWM7 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM7_Start()           '
    PWM7CON = 0b10000000        '
#ifSym __HPWM7_PIN              '
    PinOutput __HPWM7_PORT_PIN  '
#endIfSym

'-----------------------------------------------------------------------------
' Stop PWM7 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM7_Stop()         '
#ifSym __HPWM7_PIN           '
   PinInput __HPWM7_PORT_PIN '
#endIfSym                    '
   PWM7CON = 0b00000000

'-----------------------------------------------------------------------------
' Helper routine to load the duty cycle value into the PWM7DCL and PWM7DCH SFRs
' Input     : HPWM_wCalcDuty holds the duty cycle value
' Output    : None
' Notes     : None
'
Proc HPWM7_hSetDuty()
    WREG = 0                            ' Clear WREG before the shifts
    Ror HPWM_wCalcDuty                  ' Rotate the Least Significant bit into the Carry flag
    Ror WREG                            ' Rotate the Carry flag into the Most Significant bit
    Ror HPWM_wCalcDuty                  ' Rotate the Least Significant bit into the Carry flag
    Ror WREG                            ' Rotate the Carry flag into the Most Significant bit
    PWM7DCL = WREG
    PWM7DCH = HPWM_dCalcDuty.Byte0
EndProc

'-----------------------------------------------------------------------------
' Adjust the duty cycle of PWM7
' Input     : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : None
' Notes     : None
'
Proc HPWM7_Duty(pDuty As HPWM_bDuty)
    HPWM_hCalculateDuty()                               ' Calculate the duty value into HPWM_wCalcDuty
    HPWM7_hSetDuty()
EndProc

'-----------------------------------------------------------------------------
' Calculate the values required for a required frequency and duty cycle for PWM7
' Input     : pFrequency holds the 32-bit frequency (in Hz)
'           : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : Returns 1 if the frequency is possibly achievable, otherwise, returns a 0
' Notes     : Also starts the PWM for PWM7
'
Proc HPWM7(pTimer As HPWM_bTimerReq, pFrequency As HPWM_dFrequency, pDuty As HPWM_bDuty), STATUSbits_C
    HPWM_SelectTimer(7, pTimer)                         ' Choose which timer to use for the channel
    HPWM_hCalcFreq(pFrequency)                          ' Calculate the values for a given frequency and duty cycle
    If STATUSbits_C = 1 Then                            ' Was the frequency accepted?
        HPWM7_hSetDuty()                                ' Yes. So load the PWM7DC registers with the appropriate value
        HPWM7_Start()                                   ' Start PWM7
        Result = 1                                      ' Indicate everything OK
    EndIf
EndProc
$endif  ' _PWM7CON

'*****************************************************************************
$ifdef _PWM8CON
'-----------------------------------------------------------------------------
' Start PWM8 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM8_Start()           '
    PWM8CON = 0b10000000        '
#ifSym __HPWM8_PIN              '
    PinOutput __HPWM8_PORT_PIN  '
#endIfSym

'-----------------------------------------------------------------------------
' Stop PWM8 operation
' Input     : None
' Output    : None
' Notes     : None
'
$define HPWM8_Stop()         '
#ifSym __HPWM8_PIN           '
   PinInput __HPWM8_PORT_PIN '
#endIfSym                    '
   PWM8CON = 0b00000000

'-----------------------------------------------------------------------------
' Helper routine to load the duty cycle value into the PWM8DCL and PWM8DCH SFRs
' Input     : HPWM_wCalcDuty holds the duty cycle value
' Output    : None
' Notes     : None
'
Proc HPWM8_hSetDuty()
    WREG = 0                            ' Clear WREG before the shifts
    Ror HPWM_wCalcDuty                  ' Rotate the Least Significant bit into the Carry flag
    Ror WREG                            ' Rotate the Carry flag into the Most Significant bit
    Ror HPWM_wCalcDuty                  ' Rotate the Least Significant bit into the Carry flag
    Ror WREG                            ' Rotate the Carry flag into the Most Significant bit
    PWM8DCL = WREG
    PWM8DCH = HPWM_dCalcDuty.Byte0
EndProc

'-----------------------------------------------------------------------------
' Adjust the duty cycle of PWM8
' Input     : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : None
' Notes     : None
'
Proc HPWM8_Duty(pDuty As HPWM_bDuty)
    HPWM_hCalculateDuty()                               ' Calculate the duty value into HPWM_wCalcDuty
    HPWM8_hSetDuty()
EndProc

'-----------------------------------------------------------------------------
' Calculate the values required for a required frequency and duty cycle for PWM8
' Input     : pFrequency holds the 32-bit frequency (in Hz)
'           : pDuty (HPWM_bDuty) holds the 8-bit duty cycle (0 to 255)
' Output    : Returns 1 if the frequency is possibly achievable, otherwise, returns a 0
' Notes     : Also starts the PWM for PWM8
'
Proc HPWM8(pTimer As HPWM_bTimerReq, pFrequency As HPWM_dFrequency, pDuty As HPWM_bDuty), STATUSbits_C
    HPWM_SelectTimer(8, pTimer)                         ' Choose which timer to use for the channel
    HPWM_hCalcFreq(pFrequency)                          ' Calculate the values for a given frequency and duty cycle
    If STATUSbits_C = 1 Then                            ' Was the frequency accepted?
        HPWM8_hSetDuty()                                ' Yes. So load the PWM8DC registers with the appropriate value
        HPWM8_Start()                                   ' Start PWM8
        Result = 1                                      ' Indicate everything OK
    EndIf
EndProc
$endif  ' _PWM8CON

$endif  ' _CCP_PWM_K42_INC_

The library was never "fully" tested because I decided to drop the Positron8 board, but what I tested seemed to work well.

Name the above listing: "CCP_PWM_K42.inc", and place it in the compiler's "Includes" directory located here: "C:\Users\User Name\PDS\Includes\". Then all programs can see it when it is included with the Include directive.

A crude test demo I created for the above library is listed below. You can see I decided to drop the Positron8 board because the test demo never got finished. :-(

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Hardware PWM routines that make use of the CCP and PWM peripherals on the PIC18FxxK42 devices.
' It allows frequencies up to 1.2 Mhz to be generated when the device is operating at 64 Mhz
'
' Low end frequencies and high end frequencies depend on the oscillator value used.
' Frequencies may be approximate depending on the oscillator value used and the frequency required.
' This is not due to a limitation of the compiler, but of the CCP and PWM peripherals themselves.
'
' Written for the Positron8 BASIC Compiler by Les Johnson.
'
    Include "Positron.inc"                              ' Load the Positron8 board's setup (PIC18F27K42 operating at 64MHz, with internal oscillator)
'
' Set which pins are used by the CCP and PWM peripherals
'
    Declare HPWM1_Pin = PORTC.2
    Declare HPWM2_Pin = PORTC.1
    Declare HPWM3_Pin = PORTB.5
    Declare HPWM4_Pin = PORTB.0
    Declare HPWM5_Pin = PORTC.2
    Declare HPWM6_Pin = PORTC.2
    Declare HPWM7_Pin = PORTC.2
    Declare HPWM8_Pin = PORTC.3

    Include "CCP_PWM_K42.inc"                       ' Load the CCP PWM library into the program
'
' Create some variables
'
    Dim bDuty As Byte = 127
    Dim wFrequency As Word = 6200

'---------------------------------------------------------------------------
Main:
'
' Alter the PPS (Peripheral Pin Select) for the pins used for PWM output
'
    PPS_Unlock()
    'RC2PPS = PPS_Fn_PWM1                    ' Configure PWM1 PPS
    'RC1PPS = PPS_Fn_PWM2                    ' Configure PWM2 PPS
    'RB5PPS = PPS_Fn_PWM3                    ' Configure PWM3 PPS
    'RB0PPS = PPS_Fn_PWM4                    ' Configure PWM4 PPS
    'RC2PPS = PPS_Fn_PWM5                    ' Configure PWM5 PPS
    'RC2PPS = PPS_Fn_PWM6                    ' Configure PWM6 PPS
    RC2PPS = PPS_Fn_PWM7                    ' Configure PWM7 PPS
    RC3PPS = PPS_Fn_PWM8                    ' Configure PWM8 PPS

    'HPWM7(HPWM_cTimer4, 6000, 127)         ' Set the frequency and duty for PWM channel 7
    'HPWM8(HPWM_cTimer6, 2000, 127)         ' Set the frequency and duty for PWM channel 8
    HPWM_SetFreq(6000)
    HPWM8_Start()
    For bDuty = 0 To 255
        HPWM8_Duty(bDuty)
        DelayMS 50
    Next
'
' Adjust the frequency of all the peripherals
'
(*
    For wFrequency = 0 To 65535 Step 64             ' Create a loop for frequency adjustment
        HPWM1(HPWM_cTimer2, wFrequency, 127)        ' Alter frequency with 50 percent duty cycle
        HPWM2(HPWM_cTimer2, wFrequency, 127)        ' Alter frequency with 50 percent duty cycle
        HPWM3(HPWM_cTimer2, wFrequency, 127)        ' Alter frequency with 50 percent duty cycle
        HPWM4(HPWM_cTimer2, wFrequency, 127)        ' Alter frequency with 50 percent duty cycle
        HPWM5(HPWM_cTimer2, wFrequency,127)         ' Alter frequency with 50 percent duty cycle
        DelayMS 1
    Next
*)
'
' Adjust the duty cycles of the peripherals
'
(*
    HPWM1_Start()                           ' \
    HPWM2_Start()                           ' | Enable peripherals
    HPWM3_Start()                           ' |
    HPWM4_Start()                           ' /
    Do                                      ' Create a loop
        For bDuty = 0 To 255
            HPWM1_Duty(bDuty)               ' \
            HPWM2_Duty(bDuty)               ' |
            HPWM3_Duty(bDuty)               ' | Adjust the duty cycle of the PWMs
            HPWM4_Duty(bDuty)               ' /
            DelayMS 10
        Next
    Loop
*)