News:

;) This forum is the property of Proton software developers

Main Menu

Positron8 - Hardware PWM library for PIC18FxxK42 devices

Started by top204, Feb 23, 2025, 09:40 AM

Previous topic - Next topic

top204

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, and found this listing in the general Positron forum, so thought it would be better catagorised in the PWM section of the froum's WIKI.

The last time I used it, it worked fine for the tests I made. It also allows frequencies above 1MHz 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
*)