News:

Let's find out together what makes a PIC Tick!

Main Menu

Is HPWM controlled with Timer0

Started by Yves, Sep 28, 2024, 03:11 PM

Previous topic - Next topic

Yves

Hello,

I may be wrong, is the Positron HPWM is controlled by timer0? Is it possible to use another timer as I already using timer0 in my code. I need to generate a one second interrupt I could do that one not using HPWM but when I change the HPWM duty cycle it affect the 1 second interrupt  . Any idea how I can use another timers to generate my 1 second interrupt? I'm using the 18F26k22 with 16Mhz with PLLCFG = On.
Many thanks.

Yves
Yves

JonW

#1
It's the PIC architecture that defines which timer is associated with the Hardware PWM.  For some of the CCP modules, you can define which timer is used, think its T2,4 and 6 on the 26k22, I am not sure if you can declare a specific timer to the PWM peripheral.

The best way to generate a tick timer from any timer is to use a timer's timeout interrupt.  You don't necessarily have to use interrupt routines but instead you can test the timers overflow int flag in a routine and reset the flag and preload the timer for a specific timeout time. You can keep the loop fast so as not to introduce lots of delays.  For instance, a non-interrupt loop waiting for the timer to timeout in a 100us loop then count X loops and then process at 1 second etc 

Interrupts do make it easier and much more accurate.

flosigud

HPWM is not controlled by Timer in 18F26k22 or any other PIC that I'M aware off. In 18F26k22 HPWM is controlled by timer 2,4,6.

Yves

Hi Flosigud,

I'm confused from what you said. you said "HPWM is not controlled by Timer in 18F26k22 or any other PIC that I'M aware off" then next sentence you said "In 18F26k22 HPWM is controlled by timer 2,4,6" the 18F family has timer 0,1,2 and 3?
I thought the Duty Cycle is controlled a timer. My question is can I choose the timer to control the duty cycle?

Yves
Yves

JonW

#4
I think he meant 'Timer0' Yves.  He is correct in the timers.  See below, The 18F series are unfortunately not all the same.
You can define the timer if you create the PWM manually. There may be a way to change this in the compiler's setup files or manually assign a specific TimerX after the initialisation of the PWM to overwrite the timer the compiler assigns, you should be able to see this in the ASM file.   It can 100 per cent be done by rewriting the HPWM in a custom Proc. Duty change is simply a register write

Yves

Let me try to be more clear on my issue.
In my code I need HPWM2 to control the speed of a small motor and also I need to have an interrupt to control some timing using Timer1 overflow or any other timer. When I use any of those routines on their own everything works fine. When I combine the two my HPWM stop functioning.
My question is: which timer from the 18F26k22 I could use that doesn't affect the HPWM function.

Regards,

Yves
Yves

top204

#6
The compiler's HPWM command uses Timer2 for its timing of the CCP peripherals, and this will not effect any other timers.

Some devices have the ability to use different timers for PWM operation, and some even for different CCP channels. However, this would be too much setting up for a simple command such as HPWM, so it defaults to Timer2. For a specialised type of PWM use, it would be a must to create a set of procedures, because the peripheral differences in some devices is staggering!

Below is a demonstration program that uses a Timer1 overflow interrupt as a millisecond counter, and enables PWM on CCP2 at 2KHz, with a duty cycle of 50%, on a PIC18F26K22 device running at 16MHz:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Demonstration of a Timer1 overflow interrupt counting every millisecond.
' It also demonstrates the HPWM command working alongside it, because the HPWM command uses Timer2.
' For use on a PIC18F26K22 device.
'
' Written by Les Johnson for the Positron8 compiler.
'
    Device = 18F26K22                                                   ' Tell the compiler what device to compile for
    Declare Xtal = 16                                                   ' Tell the compiler what frequency the device will be operating at (in MHz)
    On_Hardware_Interrupt Goto ISR_Handler                              ' Point the device to the High Level Interrupt handler
'
' Setup USART1
'
    Declare Hserial_Baud = 115200                                       ' Set the Baud rate for USART1
    Declare HRsout1_Pin = PORTC.6                                       ' Set the pin to use for USART1 TX
'
' Setup the CCP2 pin for the HPWM command
'
    Declare HPWM2_Pin = PORTC.1

$ifndef True
    $define True 1
$endif
$ifndef False
    $define False 0
$endif
'
' Create global variables here
'
    Dim dMilliSeconds      As Dword Access                              ' Holds the milliseconds counted via the Timer1 interrupt
    Dim dPrev_MilliSeconds As Dword                                     ' Holds the previous count for displaying only changes

    Dim wTimer1_SFR As TMR1L.Word                                       ' Create a 16-bit SFR from TMR1L and TMR1H

'------------------------------------------------------------------------------
' Interrupt Meta-Macros for a PIC18F26K22 device
'
$define IntGlobal_Enable()  INTCONbits_GIE = 1                          ' Enable global interrupts
$define IntGlobal_Disable() INTCONbits_GIE = 0                          ' Disable global interrupts
$define IntPeriph_Enable()  INTCONbits_GIEL = 1                         ' Enable peripheral interrupts
$define IntPeriph_Disable() INTCONbits_GIEL = 0                         ' Disable peripheral interrupts

'-------------------------------------------------------------
' Timer1 Meta-Macros for a PIC18F26K22 device
'
$define Timer1_Flag() PIR1bits_TMR1IF                                   ' The Timer1 interrupt flag
$define Timer1_FlagClear() Timer1_Flag() = 0                            ' Clear the Timer1 interrupt flag
$define Timer1_IntEnable() PIE1bits_TMR1IE = 1                          ' Enable Timer1 interrupt
$define Timer1_IntDisable() PIE1bits_TMR1IE = 0                         ' Disable Timer1 interrupt
$define Timer1_Enable() T1CONbits_TMR1ON = 1                            ' Enable Timer1
$define Timer1_Disable() T1CONbits_TMR1ON = 0                           ' Disable Timer1

'-------------------------------------------------------------
' Meta-Macro to alter the bits for a required Timer1 Prescaler on a PIC18F26K22 device
'
$define Timer1_Prescaler(pPrescaler)'
    $if pPrescaler = 1              '
        T1CONbits_T1CKPS0 = 0       '
        T1CONbits_T1CKPS1 = 0       '
    $elseif pPrescaler = 2          '
        T1CONbits_T1CKPS0 = 1       '
        T1CONbits_T1CKPS1 = 0       '
    $elseif pPrescaler = 4          '
        T1CONbits_T1CKPS0 = 0       '
        T1CONbits_T1CKPS1 = 1       '
    $elseif pPrescaler = 8          '
        T1CONbits_T1CKPS0 = 1       '
        T1CONbits_T1CKPS1 = 1       '
    $else                           '
        $error "Unknown Timer1 prescaler value. Supported values for this device are 1 or 2 or 4 or 8" '
    $endif

'---------------------------------------------------------------------------------
' Calculate the value to place into the TMR1L\H SFRs in order to achieve a certain overflow interrupt rate (in us)
' The calculation will also work for other timers
'
    $define Timer1_cFOSC $eval (_xtal / 4)                              ' The fOSC of the microcontroller device (OSC / 4)
    $define Timer1_cPrescalerValue 8                                    ' Alter this to match the prescaler loaded into T1CON.
    $define Timer1_cMicroSeconds 1000                                   ' Timer1 Interrupt rate (in uS)
    $define Timer1_cTweakValue 0                                        ' Holds a tweak value for the timer calculation

    $define Timer1_cValue $eval ((65536 + Timer1_cTweakValue) - ((Timer1_cMicroSeconds / Timer1_cPrescalerValue) * Timer1_cFOSC))

$if Timer1_cPrescalerValue >= Timer1_cMicroSeconds
    $error "Timer1_cPrescalerValue is too large for the value in Timer1_cMicroSeconds"
$endif
$if Timer1_cValue > 65535
    $error "Timer1_cValue is too large for the interrupt duration"
$elseif Timer1_cValue <= 0
    $error "Timer1_cValue is too small for the interrupt duration"
$endif

'-------------------------------------------------------------------------------------------
' The main program starts here
' Transmit the interrupt driven millisecond count to a serial terminal
'
Main:
    Setup()                                                             ' Setup the program and any peripherals
    HPWM 2, 127, 2000                                                   ' Start CCP2 as HPWM at 2KHz and 50% duty
    Do                                                                  ' Create a loop
        If dMilliSeconds <> dPrev_MilliSeconds Then                     ' Is dMilliSeconds equal to the previous millisecond value?
            HRsout1Ln Dec dMilliSeconds, " ms"                          ' No. So transmit the milliseconds counted to a serial terminal
            dPrev_MilliSeconds = dMilliSeconds                          ' Load dPrev_MilliSeconds with the value held in dMilliSeconds
        EndIf
        DelayMs 10                                                      ' A small delay between loop iterations
    Loop                                                                ' Do it forever

'----------------------------------------------------------------------------------
' Setup the program and any peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    dPrev_MilliSeconds = 0                                              ' Clear the previous milliseconds value
    dMilliSeconds = 0                                                   ' Clear the milliseconds value
    Timer1_Init()                                                       ' Initialise Timer1
    Timer1_Enable()                                                     ' Enable Timer1
    Timer1_IntEnable()                                                  ' Enable a Timer1 interrupt
    IntPeriph_Enable()                                                  ' Enable Peripheral Interrupts
    IntGlobal_Enable()                                                  ' Enable Global Interrupts
EndProc

'-------------------------------------------------------------
' Initialise Timer1
' Input     : None
' Output    : None
' Notes     : Does not enable Timer1
'
Proc Timer1_Init()
    wTimer1_SFR = Timer1_cValue                                         ' Load TMR1L\H with the value for a specific interrupt time
    Timer1_FlagClear()                                                  ' Clear the interrupt flag
    T1CON = %00000110                                                   ' Not synchronised. Timer1 disabled. 16-bit enabled
    Timer1_Prescaler(Timer1_cPrescalerValue)                            ' Set the prescaler value
EndProc

'-------------------------------------------------------------------------------------------
' Interrupt Service Routine handler
' Input     : None
' Output    : dMilliSeconds holds the amount of milliseconds counted
' Notes     : Interrupts on a Timer1 overflow
'
ISR_Handler:
    Context Save                                                        ' Save any variables and SFRs used

    If Timer1_Flag() = 1 Then                                           ' Is it Timer1 that has triggered the interrupt?
        Inc dMilliSeconds                                               ' Yes. So increment the millisecond counter
        wTimer1_SFR = Timer1_cValue                                     ' Load TMR1L\H with the value for a specific interrupt time
        Timer1_FlagClear()                                              ' Clear the Timer1 interrupt flag
    EndIf
'
' **** More user interrupt servicing code goes here if required ****
'
    Context Restore                                                     ' Restore variables then exit the interrupt

The code has been tried on a real device and in the proteus simulator, and a screenshot of it is below:

Timer1_Screenshot.jpg

Yves

Hello Less,
Thank you very much for your code. I suppose it should also run on 64Mhz with the appropriate chip fuses configurations. I am pretty good at finding useful applications for scientific instrumentation but don't have the skill to go in depth into the nitty gritty of programing. I will turn the code into a Inc file with some superficial adjustments, Many thanks again.

Kind regards,

Yves
Yves

top204

#8
It will run at any valid microcontroller speed, and the interrupt timing calculations automatically know what speed the compiler has been told by the $eval (_xtal / 4) expression. Where _xtal is a preprocessor meta-macro that gets added when the preprocessor reads the Xtal directive in the program listing.

With a faster operating speed, and devices that do not have large prescalers (such as the 18F26K22 with a maximum of 8 ), slow interrupt intervals are not possible, but then it is better to create the slowest interrupt that is possible that is a power of 10, and a simple counter within the interrupt handler, for slow timings. i.e. seconds etc... However, the calculation will give an error if the value for a timer is too large or too small for the interval time required:

$if Timer1_cValue > 65535
    $error "Timer1_cValue is too large for the interrupt duration"
$elseif Timer1_cValue <= 0
    $error "Timer1_cValue is too small for the interrupt duration"
$endif


Some of the newer devices have both a precaler and a postscaler, and some have a prescaler of 128, which means 128 ticks before the timer will increment.