HPWM/HPWM2/HPWM3 hardware PWM modules

General discussion relating to the library modules supplied with the compiler

Moderators: David Barker, Jerry Messina

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Wed Apr 26, 2023 12:56 pm

Here's a set of new hardware PWM modules (HPWM/HPWM2/HPWM3) that expand on David's original PWM.bas module, with new features and capabilities.

The modules can be used alone or together to provide up to three channels of independent PWM (if your device supports it).

Among some of the new features are:
- adds support for many J, K, and Q family devices, including those with PPS
- #options to assign output pin, CCP, and TMR peripheral and other settings
- all modules support using choice of CCP1/CCP2/CCP3 and TMR2/TMR4/TMR6 (if available)
- supports devices with standard prescaler settings 1/4/16 or expanded prescalers 1/2/4/8/16/32/64/128
- user callback event to allow customizing the setup (assign PPS pins, etc)
- SetFreq allows up to Fosc/4 output freq (16MHz at clock=64)
- low-level SetPWM(period, prescaler, duty) function for custom settings

Other than new options for setup (and new functions), its basic operation is similar to the original.
Here are a few examples...

example 1 - basic functions

Code: Select all

// HPWM example using CCP1 and TMR2
device = 18F26K22
clock = 32

include "intosc.bas"
#option DIGITALIO_INIT = true
include "setdigitalio.bas"

#option HPWM_CCP = 1                    // CCP1 (module default)
#option HPWM_TMR = 2                    // TMR2 (module default)
#option HPWM_PIN = PORTC.2              // CCP1 output pin (26K22, module default)
#option HPWM_DEFAULT_DUTYCYCLE = 50     // new default duty cycle for SetFreq
include "hpwm.bas"

dim fact, fmin, fmax as longword    // freq, in hz
dim duty, maxduty as word
dim pr as byte                      // period reg
dim prescaler as byte               // prescaler (1/4/16 or 1/2/4/...128)
dim i as byte
dim stat as boolean

main:
// get min and max pwm freq
fmin = hpwm.MinFreq()
fmax = hpwm.MaxFreq()

// set pwm to fmax, default duty cycle
hpwm.SetFreq(fmax)

// get details
fact = hpwm.Freq()                  // actual freq
pr = hpwm.getPR()                   // period reg 
prescaler = hpwm.GetTMRPrescaler()  // timer prescaler setting
maxduty = hpwm.MaxDuty()            // max duty setting
duty = hpwm.GetDuty()               // current duty setting


// set pwm to 10KHz, 25% duty
stat = hpwm.SetFreq(10000, 25) 

// get details
fact = hpwm.Freq()                  // actual freq
pr = hpwm.getPR()                   // period reg 
prescaler = hpwm.GetTMRPrescaler()  // timer prescaler setting
maxduty = hpwm.MaxDuty()            // max duty setting
duty = hpwm.GetDuty()               // current duty setting

// stop pwm
hpwm.Stop()

// set pwm using period, prescaler, and duty cycle (low-level)
hpwm.SetPWM(pr, prescaler, duty)
hpwm.Start()                        // SetPWM requires calling Start

// step duty cycle, in percent
for i = 0 to 100 step 25
    hpwm.SetDutyPercent(i)
    duty = hpwm.GetDuty()           // get duty cycle word
    delayms(10)
next    

// step duty cycle value, 0 to max
for duty = 0 to hpwm.MaxDuty()
    hpwm.SetDuty(duty)
    delayms(10)
next

// stop pwm
hpwm.Stop()

// for each prescaler range, get the max and min freqs
prescaler = 1
repeat
    hpwm.setPWM(0, prescaler)   // set max freq
    fmax = hpwm.Freq()
    hpwm.setPWM(255, prescaler) // set min freq
    fmin = hpwm.Freq()
    prescaler = hpwm.GetNextPrescaler(prescaler)
until (prescaler = 0)
example 2 - using PPS

Code: Select all

// HPWM2 example using CCP2, TMR4, and PPS
device = 18F26K40
clock = 32

include "intosc.bas"
#option DIGITALIO_INIT = true
include "setdigitalio.bas"

include "pps.bas"

#option HPWM2_CCP = 2                    // CCP2 (module default)
#option HPWM2_TMR = 4                    // TMR4 (module default)
#option HPWM2_PIN = PORTC.1              // CCP2 output pin
include "hpwm2.bas"


// user callback event to set CCP2 PPS
// (this assignment must match '#option HPWMx_PIN' setting) 
event user_ccp2()
    pps.unlock()
    pps.assign_output(RC1PPS, PPS_CCP2)     // CCP2 -> RC1
end event

main:
// setup event
hpwm2.SetEvent(user_ccp2)

// set pwm to 10KHz, 25% duty
hpwm2.SetFreq(10000, 25) 
example 3 - three independent HPWM outputs

Code: Select all

// HPWM/HPWM2/HPWM3 with PPS
device = 18F26K42
clock = 64

include "intosc.bas"
#option DIGITALIO_INIT = true
include "setdigitalio.bas"

include "pps.bas"

#option HPWM_CCP = 1
#option HPWM_TMR = 2
#option HPWM_PIN = PORTC.2
include "hpwm.bas"

#option HPWM2_CCP = 2
#option HPWM2_TMR = 4
#option HPWM2_PIN = PORTC.1
include "hpwm2.bas"

#option HPWM3_CCP = 3
#option HPWM3_TMR = 6
#option HPWM3_PIN = PORTB.5
include "hpwm3.bas"

// user callback events to set CCP PPS
// (these assignments must match '#option HPWMx_PIN' settings) 
event user_ccp1()
    pps.unlock()
    pps.assign_output(RC2PPS, PPS_CCP1)     // CCP1 -> RC2
end event

event user_ccp2()
    pps.unlock()
    pps.assign_output(RC1PPS, PPS_CCP2)     // CCP2 -> RC1
end event

event user_ccp3()
    pps.unlock()
    pps.assign_output(RB5PPS, PPS_CCP3)     // CCP3 -> RB5
end event

main:
// setup events
hpwm.SetEvent(user_ccp1)
hpwm2.SetEvent(user_ccp2)
hpwm3.SetEvent(user_ccp3)

// turn off autostart for all modules
hpwm.Autostart(false)
hpwm2.Autostart(false)
hpwm3.Autostart(false)

// setup all three pwm modules
hpwm.SetFreq(100000, 25)
hpwm2.SetFreq(200000, 50)
hpwm3.SetFreq(300000, 75)

// start all three pwm channels running
hpwm.start()
hpwm2.start()
hpwm3.start()
These will be included as part of the standard libraries in the next update, so you may want to unzip the modules into the Swordfish\Library folder so they get updated automatically.

There's bound to be some devices that aren't supported, so if your favorite doesn't work let me know and we'll add it.
Attachments
hpwm.zip
(20.52 KiB) Downloaded 1645 times

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Wed Apr 26, 2023 1:44 pm

Here's another example showing two HPWM modules sharing the same TMR

Code: Select all

// HPWM/HPWM2 sharing a single TMR
device = 18F26K22
clock = 64

include "intosc.bas"
#option DIGITALIO_INIT = true
include "setdigitalio.bas"

#option HPWM_CCP = 1
#option HPWM_TMR = 2
#option HPWM_PIN = PORTC.2
include "hpwm.bas"

#option HPWM2_CCP = 2
#option HPWM2_TMR = 2
#option HPWM2_PIN = PORTC.1
include "hpwm2.bas"

dim duty1, duty2 as byte    // duty cycle for each chan

main:
// setup the two modules
// since they share a TMR, they must be set to the same freq
hpwm.SetFreq(100000, 25)
hpwm2.SetFreq(100000, 50)

delayms(1)
hpwm.stop()
hpwm2.stop()

// step duty cycles, in percent
// hpwm increments in steps of 5%, hpwm2 in steps of 10%
duty1 = 25
duty2 = 10
hpwm.start()
hpwm2.start()
while (duty2 <= 100)            // stop when hpwm2 = 100%
    hpwm.SetDutyPercent(duty1)
    hpwm2.SetDutyPercent(duty2)
    duty1 = duty1 + 5
    duty2 = duty2 + 10
    delayus(100)
end while

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Fri Apr 28, 2023 2:52 pm

The user module PWM2 on the wiki has provisions for using a table of PWM settings.
The HPWM modules have a SetPWM() routine that can be used to provide a similar function.

SetPWM() is a low-level routine that can be used in place of SetFreq(). It results in a program about 1/3 the size and more than 20 times faster, allowing you to rapidly switch between PWM settings.

Here's a program that will generate all PWM settings for a set clock freq and dump them out the uart:
pwm_writer.bas

Code: Select all

// display all HPWM settings using the uart
program pwm_table_writer

device = 18F26K22
clock = 64

include "intosc.bas"
#option DIGITALIO_INIT = true
include "setdigitalio.bas"

include "hpwm.bas"

include "convert.bas"
include "usart.bas"

// set false to display all combinations, true to skip duplicate freqs
const skip_dup as boolean = false

const CR = 13, LF = 10
    
dim pr as byte                  // period reg
dim prescaler as byte           // prescaler (1/4/16 or 1/2/4/...128)
dim fmin as longword

structure pwm_table_t
    freq as longword            // freq
    maxduty as word             // max duty
    pr as byte                  // PR period reg
    prescaler as byte           // prescaler
end structure

// 256 table entries for each prescaler setting
dim pwm_table(256) as pwm_table_t

sub crlf()
    usart.write(CR, LF)
    delayms(25)     // allow time for terminal to display newline
end sub

const TABLE_HEADER = "    FREQ  DUTY   PR  PRE"
//                    xxxxxxxx, xxxx, xxx, xxx
sub show_table(pt as pwm_table_t)
    usart.write(
        DecToStr(pt.freq, 8, " "),     // freq, in hz
        ", ", 
        DecToStr(pt.maxduty, 4, " "),  // max duty cycle, 0-1023
        ", ", 
        DecToStr(pt.pr, 3, " "),       // PR period reg, 0-255
        ", ", 
        DecToStr(pt.prescaler, 3, " ") // prescaler, 1/4/16 or 1/2/4/...128
        )
    crlf()
end sub

main:
usart.setbaudrate(br115200)
usart.write("PWM table writer", CR, LF)
usart.write("device = ", _device, CR, LF)
usart.write("clock = ", DecToStr(_clock), CR, LF)
usart.write("skip_dup = ")
if (skip_dup) then
    usart.write("true", CR, LF)
else    
    usart.write("false", CR, LF)
endif
crlf()

usart.write(TABLE_HEADER)
crlf()

// for each prescaler range, get a list of all PWM freqs 
// and max duty settings (number of steps/resolution)
fmin = _clock * 1000000 + 1                     // set for skip_dup test
prescaler = 1
repeat
    // get all freqs and maxduty settings for this prescaler
    for pr = 0 to 255                           // period reg
        hpwm.SetPWM(pr, prescaler)
        // get details
        pwm_table(pr).freq = hpwm.Freq()        // freq for this (PR, prescaler) setting
        pwm_table(pr).maxduty = hpwm.MaxDuty()  // max duty value (resolution)
        pwm_table(pr).pr = pr
        pwm_table(pr).prescaler = prescaler
        if (skip_dup and (pwm_table(pr).freq >= fmin)) then
            continue                            // skip displaying freq already shown
        endif            
        show_table(pwm_table(pr))
    next
    crlf()
    fmin = pwm_table(255).freq                  // set new min freq
    prescaler = hpwm.GetNextPrescaler(prescaler)
until (prescaler = 0)

end program
Running the above and capturing the output produces something like:

Code: Select all

PWM table writer
device = 18F26K22
clock = 64
show_dup = true

    FREQ  DUTY   PR  PRE
16000000,    4,   0,   1
 8000000,    8,   1,   1
 5333333,   12,   2,   1
 4000000,   16,   3,   1
   <output snipped>
   63241, 1012, 252,   1
   62992, 1016, 253,   1
   62745, 1020, 254,   1
   62500, 1023, 255,   1

 4000000,    4,   0,   4
 2000000,    8,   1,   4
 1333333,   12,   2,   4
 1000000,   16,   3,   4
   <output snipped>
   15810, 1012, 252,   4
   15748, 1016, 253,   4
   15686, 1020, 254,   4
   15625, 1023, 255,   4

 1000000,    4,   0,  16
  500000,    8,   1,  16
  333333,   12,   2,  16
  250000,   16,   3,  16
   <output snipped>
    3952, 1012, 252,  16
    3937, 1016, 253,  16
    3921, 1020, 254,  16
    3906, 1023, 255,  16
Here's an example of using the data above to fill in a const table of settings
pwmtables.bas

Code: Select all

// pwm table example using values from the tablewriter
// shows using SetPWM vs SetFreq
//  SetPWMbyTable(0)           //  413 program bytes, 48 variable bytes, exec time: 9.3us
//  hpwm.SetFreq(4000000, 50)  // 1217 program bytes, 68 variable bytes, exec time: 220.4us
//
program pwm_tables

device = 18F26K22
clock = 64

include "intosc.bas"
#option DIGITALIO_INIT = true       // automatically call setalldigital at startup
include "setdigitalio.bas"

include "hpwm.bas"

// given a MaxDuty value and a percent 0-100, create a PWM table duty cycle setting
macro DC(maxduty, percent)
    ((maxduty * percent)/100)
end macro

// PWM table
// each table entry consists of three values:
//  PR          period reg (byte), 0-255
//  prescaler   TMR prescaler (byte), 1/4/16 or 1/2/4/...128
//  duty        duty cycle value (word), 0-1023 (should not exceed MaxDuty)
// the table is defined as words to make setting the table values easier
//
const PWM_TABLE() as word = 
(                           //    FREQ  DUTY   PR  PRE      equivalent SetFreq calls
    3, 1, DC(16, 50),       // 4000000,   16,   3,   1      hpwm.SetFreq(4000000, 50)
    1, 1, DC(8, 25),        // 8000000,    8,   1,   1      hpwm.SetFreq(8000000, 25)
    7, 1, 24,               // 2000000,   32,   7,   1      hpwm.SetFreq(2000000, (24*100/32))
    3, 4, DC(16, 75),       // 1000000,   16,   3,   4      hpwm.SetFreq(1000000, 75)
    1,16, 4,                //  500000,    8,   1,  16      hpwm.SetFreq(500000, (4*100/8))
    3,16, 2                 //  250000,   16,   3,  16      hpwm.SetFreq(250000, (2*100/16))
)
const PWM_TABLE_ENTRY = 6      // each table entry is 3 words (6 bytes)
const MAX_PWM_TABLE_IX = (sizeof(PWM_TABLE)/PWM_TABLE_ENTRY) - 1   // max table index

// read TBLPTR, increment it, and return value
inline function TBLRD_POSTINC() as TABLAT
    asm-
        TBLRD*+
    end asm
end function
    
// given a pwm table index read the table values and call SetPWM
sub SetPWMbyTable(ix as byte)
    dim pr as byte
    dim prescaler as byte
    dim duty as word
    
    TABLEPTR = ix * PWM_TABLE_ENTRY     // get table offset
    TABLEPTR = TABLEPTR + addressof(PWM_TABLE)

    pr = TBLRD_POSTINC()                // get PR byte
    TBLRD_POSTINC()                     // dummy read to incr tblptr

    prescaler = TBLRD_POSTINC()         // get prescaler byte
    TBLRD_POSTINC()                     // dummy read to incr tblptr

    duty.bytes(0) = TBLRD_POSTINC()     // get dutycycle word
    duty.bytes(1) = TBLRD_POSTINC()

    HPWM.SetPWM(pr, prescaler, duty)
    HPWM.Start()
end sub

dim ix as byte

main:
delayms(10)     // wait for intosc to stabilize

for ix = 0 to MAX_PWM_TABLE_IX
    SetPWMbyTable(ix)   // set pwm using table entry ix
    delayus(10)         // let pwm run for a bit
    HPWM.Stop()
next

end program

richardb
Posts: 315
Joined: Tue Oct 03, 2006 8:54 pm

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by richardb » Tue Oct 10, 2023 11:32 am

just a quicky i have just tried this with an 18F06Q41 and immediately throws a pin error
"invalid Hpwm_pin selection for 18f06q41.

has anyone tried this part with pwm and do you have a known working config.

Rich
Hmmm..

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Tue Oct 10, 2023 1:05 pm

There seems to be an issue with the 14-pin versions of the Q40/Q41 and the 'IsValidPort()' check
Those parts have no PORTB (just PORTA and PORTC), and that's causing the failure.
HPWM.bas works ok with the 20-pin version 18F16Q41... it has ports A, B, and C

For the time being you can comment out the test in hpwm.bas:

Code: Select all

#if not(IsValidPort(HPWM_PIN) and IsValidPortPin(HPWM_PIN))
  #error HPWM_PIN, "invalid HPWM_PIN selection for " + _device
#endif  
OR
for the 14-pin parts only use PORTA

Code: Select all

#option HPWM_PIN = PORTA.1              // CCP1 output pin

Try this:

Code: Select all

// HPWM1 example using CCP1, TMR2, and PPS
// 14-pin 18F0xQ40/Q41 is restricted to using PORTA only (IsValidPort issue)
// 20-pin 18F1xQ40/Q41 can use PORTB
device = 18F06Q41
clock = 32

include "intosc.bas"
#option DIGITALIO_INIT = true
include "setdigitalio.bas"

include "pps.bas"

#option HPWM_CCP = 1                    // CCP1
#option HPWM_TMR = 2                    // TMR2
#option HPWM_PIN = PORTA.1              // CCP1 output pin
include "hpwm.bas"


// user callback event to set CCP1 PPS
// (this assignment must match '#option HPWMx_PIN' setting) 
event user_ccp1()
    pps.unlock()
    pps.assign_output(RA1PPS, PPS_CCP1)     // CCP1 -> RA1
end event

main:
// setup event
hpwm.SetEvent(user_ccp1)

// set pwm to 10KHz, 25% duty
hpwm.SetFreq(10000, 25) 

richardb
Posts: 315
Joined: Tue Oct 03, 2006 8:54 pm

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by richardb » Tue Oct 10, 2023 1:19 pm

Thanks Jerry I'll have a look tomorrow.
Hmmm..

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Tue Oct 10, 2023 2:44 pm

Seems this is my screw up in generating the device files.

For the 14-pin 18F0xQ40 and 18F0xQ41, you should change the device files.
Find the line '#const _ports' and add '#const _no_portb'...

Code: Select all

#const _ports = 2                      // 2 available ports
#const _no_portb = 1                 // no PORTB
That should fix it.

Changes apply to files:
18F04Q40.bas
18F05Q40.bas
18F06Q40.bas
18F04Q41.bas
18F05Q41.bas
18F06Q41.bas

I'll make note of that and fix it in the next update

richardb
Posts: 315
Joined: Tue Oct 03, 2006 8:54 pm

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by richardb » Wed Oct 11, 2023 7:23 am

ok so it compiles , but doesnt output anything .
i tried to do an led test to check it was all working and noticed some of the other pins don't work as normal outputs.

Code: Select all

Device = 18F06Q41
Clock = 64

Config
    FEXTOSC = OFF,      // Oscillator not enabled
    RSTOSC = HFINTOSC_64MHZ,// HFINTOSC with HFFRQ = 64 MHz and CDIV = 1:1
    CLKOUTEN = OFF     // CLKOUT function is disabled


Include"IntOSC.bas"
Include "utils.bas"
Dim led As PORTa.1

SetAllDigital

Output(led)
trisa=0
trisc=0
while true
	porta = 255
	portc=255
	delayms(100)		 
	porta = 0
	portc=0
	delayms(100)
wend


 
End
running the above code does the following
port
a.0 flash
a.1 = 0
a.2 = flash
a.4 = flash
a.5 = flash
c.0 = 1
c.1 = flash
c.2 = flash
c.3 = flash
c.4 = flash
c.5 = flash

i've used c.0 as a usart previously

odd.

unfortunately i need to get on and finish a board design and will use an old failtfull part but i was hoping to use this part as its so small and has all you could need.


Rich
Hmmm..

richardb
Posts: 315
Joined: Tue Oct 03, 2006 8:54 pm

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by richardb » Wed Oct 11, 2023 7:46 am

ok so seconds after posting i thought i'd try changing to c.0 as the output and that worked....

but only after adding

Code: Select all

while true
wend
to the end of the program



also a.1 and a.0 worked.

thanks again Jerry.
Hmmm..

richardb
Posts: 315
Joined: Tue Oct 03, 2006 8:54 pm

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by richardb » Tue Nov 14, 2023 9:45 am

I have noticed the normal duty sub glitches periodically but my example code when using duty2 sub seems to work correctly.

the pink trace is the tp signal
yellow the pwm switched op
light blue the voltage out of the psu.



Code: Select all

Device = 18F26k22
Clock = 64   'adc needs changing to work at 64mhz

Include"IntOSC.bas"   'this defaults to 64mhz
Include "utils.bas"
Include "convert.bas"
#option HPWM_CCP = 1                    // CCP1 (module default)
#option HPWM_TMR = 2                    // TMR2 (module default)
#option HPWM_PIN = PORTC.2              // CCP1 output pin (26K22, module default)
#option HPWM_DEFAULT_DUTYCYCLE = 50     // new default duty cycle for SetFreq
Include "hpwm.bas"
Dim PWMOutput As PORTC.2
Dim PWMDisable As PORTB.5
Dim tp As PORTA.4
Dim CCPxCON As CCP1CON
dim CCPRxL  As CCPR1L

Public Sub SetDuty1(pDuty As Word)
    CCPxCON.bits(5) = pDuty.1       // 2 lsb's go to DCxB[1:0]
    CCPxCON.bits(4) = pDuty.0  
    CCPRxL = pDuty >> 2
 
End Sub

Public Sub SetDuty2(pDuty As Word)
    Dim x As Byte
    x =pDuty >> 2
    CCPxCON.bits(5) = pDuty.1       // 2 lsb's go to DCxB[1:0]
    CCPxCON.bits(4) = pDuty.0  
    CCPRxL = x'pDuty >> 2
 
End Sub

SetAllDigital
Output(tp)
Output(PWMOutput)
Output(PWMDisable)
PWMDisable =0
PWMOutput=1
HPWM.SetPWM(249, 1, 500) 'THIS CONFIG RUNS AT 64KhZ WITH 0-1000 COUNT PWM RANGE   'hpwm.SetPWM(pr, prescaler, duty)
HPWM.Start()

While true
	tp=1
	SetDuty1(500)
	tp=0
	DelayUS(100)
End While
Attachments
image of glitch
image of glitch
RigolDS1.png (134.13 KiB) Viewed 73523 times
Hmmm..

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Tue Nov 14, 2023 2:03 pm

That's interesting... I never noticed that.
The difference is in how it codes the 'pDuty >> 2' statement.

In the hpwm.bas SetDuty() routine (and your SetDuty1 routine) it has this:

Code: Select all

?I000043_F000_000026_P000140 ; L#MK CCPRxL = pDuty >> 2
    RRCF F0_U16H,0,0
    MOVWF PRODL,0
    RRCF F0_U16,0,0
    MOVWF CCPR1L,0        <<<<<<<< write to CCPR1L
    RRCF PRODL,1,0
    RRCF CCPR1L,1,0         <<<<<<<< write to CCPR1L
which ends up doing 2 writes to the CCPR1L register as it does the shift, whereas SetDuty2 does a single write to CCPR1L since the shift is done on a variable first and then the CCPR1L is set to the result.

I'll change the HPWM routines to look more like your SetDuty2, but I'll also have a look at the code generator since this kind of thing could effect other code as well if it messes with a SFR more than once.

Thanks for the heads up!

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Tue Nov 14, 2023 2:43 pm

Seems like an easy way to fix this is to add a cast to the shift...

Code: Select all

Public Sub SetDuty1(pDuty As Word)
    CCPxCON.bits(5) = pDuty.1       // 2 lsb's go to DCxB[1:0]
    CCPxCON.bits(4) = pDuty.0  
    CCPRxL = byte(pDuty >> 2)       // adding cast avoids two writes to CCPRxL
End Sub
Would you mind checking this out and see how the hdw reacts?
It should produce pretty much the same code as SetDuty2, just in a slightly different order...

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Wed Nov 15, 2023 12:32 am

So, it seems that it may not be the sequence so much as the timing of when you can update the registers in order to get glitchless operation. I'll look at it a little closer tomorrow.

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by Jerry Messina » Wed Nov 15, 2023 4:08 pm

ok, so I've looked at a number of different methods for updating the duty cycle registers, and the worst is definitely the algorithm that's in the current HPWM.bas module where it ends up writing to CCPRxL twice. That's a pretty straight-forward thing to fix.

But, that's not the whole story. The 10-bit duty cycle settings are spread across two registers. These are double buffered and are only really updated when the TMR overflows (TMRxIF = 1). To get true glitchless operation you have to ensure that you aren't updating the duty cycle when this occurs, so you have to sync writes to the CCPRxL/CCPxCON to when the timer overflows (right after it's latched in the settings) so that you know it's safe to update them.

If you do that even the current algorithm w/the two writes to CCPRxL works properly. It ends up looking something like this...

Code: Select all

// wait for TMR2IF (18F26K22)
dim TMR2ON as T2CON.bits(2),
    TMR2IF as PIR1.bits(1)
    
public sub SetDuty(pDuty as word)
    // make sure tmr2 is on...
    if (TMR2ON = 1) then
        TMR2IF = 0              // clear tmr2 if so we can detect update
        while (TMR2IF = 0)      // wait until the next rollover
        end while
    endif
// current HPWM SetDuty method
    CCPxCON.bits(5) = pDuty.1       // 2 lsb's go to DCxB[1:0]
    CCPxCON.bits(4) = pDuty.0  
    CCPRxL = pDuty >> 2             // <<<<<< THIS IS FUBAR
end sub
That's not a big deal, but currently the HPWM module doesn't have definitions for where the various TMRxIF flags are located, and of course they move around from device to device. I'll also change the sequence to minimize the time it takes to do the CCPxCON/CCPRxL update to reduce the hazard there.

I'm going to add the TMRIF flags and also make a new #option to allow you to include the TMRIF wait code or not since it slows things down if you don't really care.

richardb
Posts: 315
Joined: Tue Oct 03, 2006 8:54 pm

Re: HPWM/HPWM2/HPWM3 hardware PWM modules

Post by richardb » Fri Nov 17, 2023 8:05 am

thanks for the reply, its odd as it implies in datasheet that it's double buffered. Anyway the method I used definitely works all the time correctly on this specific pic, as i can trigger the scope on the analogue voltage glitch .

I just thought i would report it in case anyone else has this problem.
Hmmm..

Post Reply