Timer overflow interrupt calculation within the BASIC source code.

Started by top204, Apr 26, 2021, 08:51 PM

Previous topic - Next topic

top204

There is no need to use a seperate program to calculate a timer's overflow interrupt timing once the mechanism behind them is understood, and is the same calculation used by the PC programs, but at the user's fingertips whenever required.

The program below shows a simple method of calculating an overflow interrupt time, all in the program's code, so it can be changed to suit and can be transferred to other programs, and the calculation can be used with other timers etc... A much better method in my opinion, and it shows the flexability and power of the compiler's preprocessor, and remember, preprocessor meta-macros take up no code space unless they are used in a program, and then use the code assigned to them. Also, $defines take up no code space and only create constants if no code is assigned for them to create, so more calculations in the code for more timers will not increase the code memory used on the device.

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Calculate an interrupt overflow rate and start an interrupt to toggle a port's pin
' Written for the Proton8 BASIC compiler by Les Johnson
'   
    Device = 18F25K20                                   ' Tell the compiler what device to compile for
    Declare Xtal = 64                                   ' Tell the compiler that the device will be running at 64MHz
    On_Hardware_Interrupt GoTo ISR_Handler              ' Tell the compiler that interrupts are being used and where to find the interrupt handler routine
  
    Dim wTimer0 As TMR0L.Word                           ' Combine TMR0L and TMR0H into a 16-bit SFR
  
'------------------------------------------------------------------------------
' General interrupt preprocessor meta-macros
'
$define IntGlobal_Enable()  Set INTCONbits_GIE          ' Enable global interrupts
$define IntGlobal_Disable() Clear INTCONbits_GIE        ' Disable global interrupts
$define IntPeriph_Enable()  Set INTCONbits_GIEL         ' Enable peripheral interrupts
$define IntPeriph_Disable() Clear INTCONbits_GIEL       ' Disable peripheral interrupts
 
'------------------------------------------------------------------------------
' Timer0 preprocessor meta-macros for a PIC18F25K20 device
'
$define Timer0_Flag() INTCONbits_T0IF                   ' Timer0 Flag
$define Timer0_FlagClear()  Clear Timer0_Flag()         ' Clear the Timer0 interrupt flag
$define Timer0_IntEnable()  Set INTCONbits_TMR0IE       ' Enable a Timer0 interrupt
$define Timer0_IntDisable() Clear INTCONbits_TMR0IE     ' Disable a Timer0 interrupt
$define Timer0_Start() Set T0CONbits_TMR0ON             ' Start Timer0
$define Timer0_Stop()  Clear T0CONbits_TMR0ON           ' Stop Timer0
'
' Preprocessor meta-macro to alter the bits for a required Timer0 Prescaler on a PIC18F25K20 device
'
$define Timer0_Prescaler(pPrescaler)'
    $if pPrescaler = 2              '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 4          '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 8          '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 16         '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 32         '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 64         '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 128        '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 256        '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 1         '
    $else                           '
        $error "Unsupported prescaler value. Supported values for this device are 2 or 4 or 8 or 16 or 32 or 64 or 128 or 256" '
    $endif

'------------------------------------------------------------------------------
' Calculate the value to place into the TMR0L\H registers in order to achieve a certain overflow interrupt rate (in uS)
' The calculation will also work for other timers, and can be duplicated in a program with different $define names used, such as Timer1_cMicroSeconds, Timer1_cValue etc...
' See the compiler's manual on how to create meta-macros with the compiler's preprocessor.
' If calculating for an 8-bit timer, use the value 256 instead of 65536 and check for greater than 255 instead of 65535
'
$define Timer0_cPrescaler 2                     ' The prescaler used for the timer (must be the same prescaler value as used for the relevant timer's setup)
$define Timer0_cMicroSeconds 100                ' Interrupt rate (in uS)
$define Timer0_cTweakValue 8                    ' Holds a tweak value for the timer calculation

$define Timer0_cValue $eval ((65536 + Timer0_cTweakValue) - ((Timer0_cMicroSeconds / Timer0_cPrescaler) * (_xtal / 4)))

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

'---------------------------------------------------------------------------------
' The main program starts here
'
Main:
    Low PORTB.0
    Timer0_Setup()                  ' Setup Timer0  
    IntGlobal_Enable()              ' Enable global interrupts   
    Stop

'---------------------------------------------------------------------------------    
' Setup Timer0

Proc Timer0_Setup()  
    T0CON = 0
    Timer0_Prescaler(2)             ' Set the prescaler for Timer0
    wTimer0 = Timer0_cValue         ' Load the value for a specific overflow time into TMR0L/H
    Timer0_FlagClear()              ' Clear the Timer0 interrupt flag
    Timer0_IntEnable()              ' Enable a Timer0 interrupt
    Timer0_Start()                  ' Start Timer0
EndProc

'---------------------------------------------------------------------------------
' Interrupt handler
' Toggles pin PORTB.0 when the Timer0 overflow interrupt occurs
'  
ISR_Handler:
    Context Save

    If Timer0_Flag() = 1 Then       ' Is it a Timer0 interrupt?
        Btg PORTB.0                 ' Yes. So toggle PORTB.0
        Timer0_FlagClear()          ' Clear the interrupt flag
        wTimer0 = Timer0_cValue     ' Re-load TMR0L\H with the value to give a specified overflow time       
    EndIf

    Context Restore

'-------------------------------------------------------------
' Setup the fuses for the 4xPLL on a PIC18F25K20 device
'
Config_Start
    FOSC = HSPLL        ' HS oscillator. PLL enabled and under software control
    DEBUG = Off         ' Background debugger disabled. RB6 and RB7 configured as general purpose I/O pins
    XINST = Off         ' Instruction set extension and Indexed Addressing mode disabled
    STVREN = Off        ' Reset on stack overflow/underflow disabled
    WDTEN = Off         ' WDT disabled (control is placed on SWDTEN bit)
    FCMEN = Off         ' Fail-Safe Clock Monitor disabled
    IESO = Off          ' Two-Speed Start-up disabled
    WDTPS = 128         ' Watchdog is 1:128
    BOREN = Off         ' Brown-out Reset disabled in hardware and software
    BORV = 18           ' VBOR set to 1.8 V nominal
    MCLRE = On          ' MCLR pin enabled. RE3 input pin disabled
    HFOFST = Off        ' The system clock is held Off until the HF-INTOSC is stable
    LPT1OSC = Off       ' T1 operates in standard power mode
    PBADEN = Off        ' PORTB<4:0> pins are configured as digital I/O on Reset
    CCP2MX = PORTC      ' CCP2 input/output is multiplexed with RC1
    LVP = Off           ' Single-Supply ICSP disabled
    CP0 = Off           ' Block 0 not code-protected
    CP1 = Off           ' Block 1 not code-protected
    CPB = Off           ' Boot block not code-protected
    CPD = Off           ' EEPROM not code-protected
    WRT0 = Off          ' Block 0 not write-protected
    WRT1 = Off          ' Block 1 not write-protected
    WRTB = Off          ' Boot block not write-protected
    WRTC = Off          ' Configuration registers not write-protected
    WRTD = Off          ' EEPROM not write-protected
    EBTR0 = Off         ' Block 0 not protected from table reads executed in other blocks
    EBTR1 = Off         ' Block 1 not protected from table reads executed in other blocks
    EBTRB = Off         ' Boot block not protected from table reads executed in other blocks
Config_End

The Timer0_cTweakValue constant is used to fine tune the overflow timing to take into account interrupt overheads, such as interrupt type checking etc... For example, the Timer0_cTweakValue value of 8 with a Timer0 prescaler of 2, in the above program, will give an exact 100uS Timer0 overflow timing because it takes into account the line of code "If Timer0_Flag() = 1 Then", and it is used to take into account any SFRs and variables saved with Context Save. Without this tweak value, no datasheet calculation will work correctly because they create timing values that do not take into account a "real" device's operation.

The image below shows the program working and toggling pin PORTB.0 every Timer0 overflow interrupt. It can be seen by the frequency of 20000Hz that the timing for the interrupt is 100uS.

Interrupt Timing.jpg

Yves

Hello Les,

I'm trying to adapt the routine above that calculates the timer0 frequency to my code. Basically I need to increase a variable every time the overflow tick. I did so by using inc I in the ISR_Handler,and that works fine. My question is: I fail to find where I can place my routine which is basically a "while I < 1000..... code is there.... Wend". I have seen the label Main: but it doesn't work there. Many thanks for your time.

Best regards,

Yves

       
Yves

See_Mos

Hi Yves,

Your basic idea is correct.  What device are you using and what is the tick period you want?


Yves

Hi See_Mos,

I'm using a 18F25k22 the crystal is 20Mhz. I would like 1khz but that is no very important as I will convert into seconds using a factor. In the future I will  add a timing AC or independent oscillator but right now the printed circuit is made so I will have to rely on interrupt timer.

Regards,

Yves
Yves

top204

Your program operation will run inside the main Do-Loop.

The interrupt increments the 32-bit variable "dMilliSeconds", every 1.0040 milliseconds using a 20MHz crystal, so that variable can be used for none blocking delays etc... But remember, with the interrupt running so often, it will slow down the main program somewhat, so standard delays and commands using internal delays will be a bit off with their timings.
 
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Calculate a Timer0 interrupt overflow rate
' Written for the Positron8 BASIC compiler by Les Johnson
'
    Device = 18F25K22
    Declare Xtal = 20
    On_Hardware_Interrupt GoTo ISR_Handler
'
' Setup USART1
'   
    Declare Hserial1_Baud = 9600
    Declare HRSOut1_Pin = PORTC.6
'
' Create variables
'
    Dim dMilliSeconds As Dword Access                   ' Counts milliseconds from the interrupt
   
    Dim wTimer0 As TMR0L.Word                           ' Combine TMR0L and TMR0H into a 16-bit SFR

'------------------------------------------------------------------------------
$define IntGlobal_Enable()  Set INTCONbits_GIE          ' Enable global interrupts
$define IntGlobal_Disable() Clear INTCONbits_GIE        ' Disable global interrupts
$define IntPeriph_Enable()  Set INTCONbits_GIEL         ' Enable peripheral interrupts
$define IntPeriph_Disable() Clear INTCONbits_GIEL       ' Disable peripheral interrupts

'------------------------------------------------------------------------------
' Timer0 Meta-Macros for a PIC18F25K22 device
'
$define Timer0_Flag() INTCONbits_T0IF                   ' Timer0 Flag
$define Timer0_FlagClear()  Clear Timer0_Flag()         ' Clear the Timer0 interrupt flag
$define Timer0_IntEnable()  Set INTCONbits_TMR0IE       ' Enable a Timer0 interrupt
$define Timer0_IntDisable() Clear INTCONbits_TMR0IE     ' Disable a Timer0 interrupt
$define Timer0_Start() Set T0CONbits_TMR0ON             ' Start Timer0
$define Timer0_Stop()  Clear T0CONbits_TMR0ON           ' Stop Timer0
'
' Alter the bits for a required Timer0 Prescaler on a PIC18F25K22 Device
'
$define Timer0_Prescaler(pPrescaler)'
    $if pPrescaler = 2              '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 4          '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 8          '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 16         '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 0         '
    $elseif pPrescaler = 32         '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 64         '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 0         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 128        '
        T0CONbits_T0PS0 = 0         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 1         '
    $elseif pPrescaler = 256        '
        T0CONbits_T0PS0 = 1         '
        T0CONbits_T0PS1 = 1         '
        T0CONbits_T0PS2 = 1         '
    $else                           '
        $error "Unknown prescaler value. Supported values for this device are 2 or 4 or 8 or 16 or 32 or 64 or 128 or 256" '
    $endif

'---------------------------------------------------------------------------------
' Calculate the value to place into the TMR0L\H registers in order to achieve a certain overflow interrupt rate (in us)
' The calculation will also work for other timers
' If using an 8-bit timer, use the value 256 instead of 65536 and check for greater than 255 instead of 63335
'
$define Timer0_cPrescaler 4                     ' The prescaler used for the timer (must be the same prescaler as used for the timer's setup)
$define Timer0_cMicroSeconds 1000               ' Interrupt rate (in uS)
$define Timer0_cTweakValue 0                    ' Holds a tweak value for the timer calculation

$define Timer0_cValue $eval ((65536 + Timer0_cTweakValue) - ((Timer0_cMicroSeconds / Timer0_cPrescaler) * (_xtal / 4)))

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

'---------------------------------------------------------------------------------
' The main program starts here
'
Main:
    Setup()                         ' Setup the program and peripherals           
'
' Create a loop for the main program to operate within
'   
    Do
        HRSOutLn Dec dMilliSeconds  ' Transmit the millisecond value to a serial terminal
        DelayMS 100   
    Loop                            ' Do it forever

'---------------------------------------------------------------------------------
' Setup the program and peripherals
' Input     : None
' Output    : None
' Notes     : None
'
Proc Setup()
    dMilliSeconds = 0               ' Reset the millisecond counter
    Timer0_Setup()                  ' Setup Timer0
    IntGlobal_Enable()              ' Enable global interrupts   
EndProc

'---------------------------------------------------------------------------------
' Setup Timer0
'
Proc Timer0_Setup()
    T0CON = 0
    Timer0_Prescaler(Timer0_cPrescaler) ' Set the prescaler value for Timer0
    wTimer0 = Timer0_cValue             ' Load the value for a specific overflow time
    Timer0_FlagClear()                  ' Clear the Timer0 interrupt flag
    Timer0_IntEnable()                  ' Enable a Timer0 interrupt
    Timer0_Start()                      ' Start Timer0
EndProc

'---------------------------------------------------------------------------------
' Interrupt handler
' Increments the variable "dMilliSeconds" when the Timer0 overflow interrupt occurs
'
ISR_Handler:
    Context Save

    If Timer0_Flag() = 1 Then       ' Is it a Timer0 interrupt?
        Inc dMilliSeconds           ' Yes. So increment the millisecond counter
        Timer0_FlagClear()          ' Clear the interrupt flag
        wTimer0 = Timer0_cValue     ' Re-load TMR0L\H with the value to give a specified overflow time
    EndIf

    Context Restore

Yves

Yves