Looking for simple maths system for handling interrupt flag timing

Started by TimB, Mar 17, 2023, 11:22 AM

Previous topic - Next topic

TimB

Hi all

I use interrupts on almost every bit of code I write. Mainly to provide an accurate tick every say 10ms or 1s. My main code is just a loop checking the flags and when set running code to handle the tasks.

I generally use a layered counting system

My 100hz counter counts the 1000hz interrupts and when it reaches 10 it Zeros and sets the 100hz flag it also incs the 10hz counter.
When the 10hz reaches 10 it sets the 10hz flag and incs the 1hz counter
etc etc

So it works well but the code ends up doing loads at one time, It has to service the 100hz code the 10hz the 1hz all at the same time.

What I want is to offset them
If I were counting to 1000 then
9,19,29,39 etc set the 100hz flag
98,198,298 etc the 10hz flag
and also have special events at say when the counter is 657 to run code


The issue is that having a massive select case list in an interrupt would be stupid doing divisions in an interrupt is even more stupid

What I need is a simple say masking system to work out the instances with minimal overheads. Is there a method?

Thanks

John Drew

Hi Tim,
I also use interrupts in most of my programs. I use the fastest interrupt as the base and then within the interrupt handler I have various counters and accompanying flags. Each interrupt the counters are decremented if the corresponding flag is not set
In one case I had about 8 different periods available with the maximum resolution of the primary interrupt.
I have multiple subroutines setup so that when called a particular value is assigned to the relevant counter in the interrupt service routine and the flag is cleared, essentially starting the count.

Is this what you propose?
John

John Lawton

I look at this as a timer based state machine.

KISS, just have one simple interrupt timer that counts up, arrange for it to clear at a convenient but high terminal value just to avoid rollover which would happen at the wrong time and give you lumpy main loop timing.

Then in the main code you can just loop round, checking the timer and firing off events at the desired times i.e. particular timer counts. A CASE statement makes this very readable. You can put seconds or minutes etc counters in your main loop as they should have plenty of time to do their thing when needed.

TimB

Thanks John

Its not what I do when I set the flags its the process to set the flags at the right time.


Example A

        If b100hzCntr >= 10 Then

            b100hzCntr = 0                                                                          ; Reset the counter
            xT100hz = cTrue                                                                        ; Set the 100hz flag
            Inc b10hzCntr
            Inc b4hzCntr
            Inc b2hzCntr
            Inc b1hzCntr


            Inc b100hzFillCntr                                                                      ; This var is used to count sub seconds and is cleared every 1 sec
                                                                                                    ; Note there used to be code that did not count if there was a bubble seen
                                                                                                    ; This has now been removed as bubbles are not taking up fluid space
                                                                                                    ; And keeing it effected the result
        EndIf


        If b10hzCntr >= 10 Then
            b10hzCntr = 0
            xT10hz = cTrue                                                                          ; Set the 10hz flag
        EndIf


        If b4hzCntr >= 25 Then
            b4hzCntr = 0
            xT4hz = cTrue                                          ; Set the 4hz flag
        EndIf

        If b2hzCntr >= 50 Then
            b2hzCntr = 0
            xT2hz = cTrue                                          ; Set the 2hz flag
        EndIf


    Select Wcounter

    CAse 9,19,29,39,49.........
        x100HzFlag = cTrue

    Case 98,198,128,138,148....
        x10hzFlag = cTrue

    Case 1000
        x10hzFlag = cTrue
        Wcounter = 0


All those test for 9,19,29 etc would be a massive overhead

There has to be a better system

TimB

Replay to John L


It still is an issue doing the maths to see if the right period is set and it also does not cater properly for a missed period

With the flag system you will service the code for that period as soon as you are free

TimB


I think I need to invent my own counting and masking system

eg

Rather than count in ones add a value so that a mask will show up the 10s (100hz) the 100's (10hz) and the 1000's (1hz)



top204

Whatever timers you have left in your program, you can use for individual timings. Then, within the interrupt handler it is a simple case of:

If Timer0_Flag = 1 Then
' Interrupt code for Timer0 Overflow here
    Timer0_FlagClear()
Endif
If Timer1_Flag = 1 Then
' Interrupt code for Timer1 Overflow here
    Timer1_FlagClear()
Endif


I created a set of generic meta-macros to calculate the times for timer overflow interrupts here:

Timer overflow interrupt calculation within the BASIC source code

This way, each interrupt fires at the correct, or close, interval, then it will be 8-bit increments or tests for the amount of them. With 18F devices, 8-bit operations are extremely efficient.

For a more accurate and controllable timer interrupt, for the 91929 timings etc, use either the CCP1 or CCP2 peripheral with a timer as well. Depending on the oscillator used for the device, you may just find you are accurate enough with a single overflow interrupt. I use a CCP interrupt whenever I have to create accuratetely timed interrupts. For example, audio timings etc...

SCV

Assuming they are multiples of the fundamental interrupt then a simple method is below. Put into the ISR at 1mS tick. To offset multiple flags, don't use zero as the remainder!


Msec1 = Msec1 + 1
IF Msec1 = 40000 then Msec1 = 0    'Lowest denominator divisible by 40, 100, 500 & 1000

IF Msec1 // 10 = 0 then Msec10_FLAG = 1      'Ints at 0, 10, 20 etc
IF Msec1 // 40 = 5 then Msec40_FLAG = 1      'Ints at 5, 45, 85 etc
IF Msec1 // 100 = 6 then Msec100_FLAG = 1    'Ints at 6, 106, 206 etc
IF Msec1 // 500 = 7 then Msec500_FLAG = 1    'Ints at 7, 507, 1007 etc
IF Msec1 // 1000 = 8 then Msec1000_FLAG = 1  'Ints at 8, 1008, 2008 etc





top204

Remember, the modular operator is a 16-bit division's remainder, so it is not very efficient on an 8-bit device.

TimB

I came up with my own counting system not sure its 100% correct yet as not tested but this looks like it will work


I can using some more tests mask easily for say the 9's


    DIm b100cnt as byte
    Dim b10cnt as Byte
    DIm x10hz as bit
    DIm x100hz as bit

; Incrument seperate bytes 1 byte for the 10s eg 0,1,2, -9 : 1 byte for the 100's eg 0,100,200,- 900
    if b10cnt < 10 then
        inc b10cnt
    Else
        b10s = 0
        If b100cnt < 10 then
            inc b100cnt
        Else
            b100cnt = 0
        Endif
    Endif


; Now test for counts eg 10's (100hz)  and 100's (10hz) and
    if b10s = 0 then
        x100hz = cTrue
        if b100cnt = 0
            x10hz = ctrue
        Endif
    Endif

TimB


Just to post the corrected code

Counting from 0 - 9 is needed

It is to my mind the fastest system to implement a 100hz, 10hz and 1 hz tick and can be adapted very easily to look for say every 99,199 etc steps as I was originally looking for

     ; Incrument seperate bytes 1 byte for the Units eg 0,1,2, -9 : 1 byte for the Tens 's eg 0,100,200,- 900 and 1 for the hundreds
         if bUnits < 9 then                                                     ' Inc this counter every 1000hz
             inc bUnits
         Else
             bUnits = 0                                                          '  Every tenths reset the counter and inc the Hundreds counter
             If bTens < 9 then
                 inc bTens
             Else
                 bTens = 0                                                       ' Every 100ths
                 if bHundreds < 9 then
                     inc  bHundreds
                 else
                     bHundreds = 0
                 Endif
             Endif
         Endif


     ; Now test for counts eg Units ='s (100hz)  and 100's (10hz) and 1000's (1hz)
         if bUnits = 0 then
             x100hz = cTrue
             if bTens = 0  then
                 x10hz = ctrue
                 If bHundreds = 0  then
                     x1hz = cTrue
                 Endif
             Endif
        Endif

John Drew

@Tim,
Very neat and fast solution Tim and all done in bytes without resorting to complex maths.
John

trastikata

Hello TimB,

if you are looking for accuracy and simplicity, you can use the HW timers as counters and interrupt generators. For example let Timer1 run constantly with period of 1 or 10ms and increment the Timer0, Timer2, Timer3 within the ISR as each will represent 1 Hz, 10 Hz, and 100Hz.

In general this is the same as what you are doing now, but instead of handling variables, you handle timer counters. Example:

Symbol TMR0IF = INTCON.2
Symbol TMR1IF = PIR1.0

Dim bTimer0 As TMR0L                '8-bit counter for TMR0
Dim wTimer1 As TMR1L.Word           '16bit counter for TMR1

Dim p100H_Flag As Bit
Dim p10H_Flag As Bit

On_Interrupt GoTo Isr
GoTo Main

Isr:
    Context Save
        If TMR1IF = 1 Then
            p100H_Flag = 1          'set 100Hz flag
            wTimer1 = 0xA015        'preset Timer1 for 10ms interrupt @ 48 MHz
            TMR1IF = 0              'clear Timer1 Interrupt Flag
            Inc bTimer0             'increment Timer0
        EndIf
       
        If TMR0IF = 1 Then
            TMR0IF = 0              'clear Timer0 Interrupt Flag
            p10H_Flag = 1           'set 10Hz flag
            bTimer0 = 0xF6          'preset Timer0 for 100ms interrupt
        EndIf
    Context Restore
Main:

TimB


Hello trastikata

I have no problems generating the basic timing off 1 interrupt. I can easly get fixed times. What I was asking for was a system to generate intermediate periods E.G-

I have a 100hz and some code runs at on the 100hz ticks but I need other code to run on of set of 10ms so making a flag at the 99th,199,299th etc

Using a normal system that would be hard as I would have to do a lot of maths or a lot of compares to get the 99th,199th,299th etc

The code I posted above makes it very simple

     ; Now test for counts eg Units ='s (100hz)  and 100's (10hz) and 1000's (1hz)
         if bUnits = 0 then
             x100hz = cTrue
             if bTens = 0  then
                 x10hz = ctrue
                 If bHundreds = 0  then
                     x1hz = cTrue
                 Endif
             Endif
        Endif

        If bUnits = 9 then
            If bTens = 9 then
                x99thsFlag = cTrue
            Endif
        Endif



trastikata

Quote from: TimB on Mar 20, 2023, 02:21 PMWhat I was asking for was a system to generate intermediate periods E.G-

I have a 100hz and some code runs at on the 100hz ticks but I need other code to run on of set of 10ms so making a flag at the 99th,199,299th etc


Hello TimB,

that's what I was saying - Timer1 generates 1ms interrupts for example and withing that Timer1 interrupt you increment the Timer0 counter - not from the clock but in software like "Inc TMR0L". If your Timer0 is preset for 99 cycles until interrupt, it will effectively generate an interrupt in 99ms.

Similar case is the 299th - use another timer - for example Timer3, which is a 16b - preset it for 299 cycles until interrupt and increment it within Timer1 interrupt.

Not really much difference from what you are doing, except accuracy is higher - because Timer1 is constantly running and you are loosing only 4 cycles in a millisecond to reset the Timer1 Interrupt Flag and preset Timer1 again. The rest of the timers are updated within the interrupt of TMR1 but after it has been reset.

Symbol TMR0IF = INTCON.2
Symbol TMR1IF = PIR1.0
Symbol TMR2IF = PIR1.1
Symbol TMR3IF = PIR2.1

Dim bTimer0 As TMR0L                '8-bit counter for TMR0
Dim wTimer1 As TMR1L.Word           '16bit counter for TMR1
Dim wTimer3 As TMR3L.Word           '16bit counter for TMR3

Dim pTMR0_Flag As Bit
Dim pTMR3_Flag As Bit

On_Interrupt GoTo Isr
GoTo Main

Isr:
    Context Save
        If TMR1IF = 1 Then
            wTimer1 = 0xD120        'preset Timer1 for 1ms interrupt @ 48 MHz
            TMR1IF = 0              'clear Timer1 Interrupt Flag
            Inc bTimer0             'increment Timer0
            Inc wTimer3             'increment Timer3
        EndIf
       
        If TMR0IF = 1 Then
            TMR0IF = 0              'clear Timer0 Interrupt Flag
            pTMR0_Flag = 1          'set Timer0 flag
            bTimer0 = 0x9E          'preset Timer0 for 98 cycles/increments to interrupt
        EndIf
       
        If TMR3IF = 1 Then
            TMR3IF = 0              'clear Timer3 Interrupt Flag
            pTMR3_Flag = 1          'set Timer3 flag
            wTimer3 = 0xB8FE        'preset Timer3 for 328 cycles/increments to interrupt
        EndIf
    Context Restore
Main:
 

TimB


Thanks for the suggestions..

All I need to do the following it is 1 x 100mhz interrupt and some software counters.


0
99
100
199
200
299
300
399
400
..
..



John Drew

Hi Tim,
I have a total of 13 software counters running in a Timer0 interrupt. The device is a 18F2480 at 64MHz. As well as the interrupt the PIC is also measuring voltages and audio levels, sending and receiving I2C, sending tones and checking and changing ports. The device is a repeater controller with a main repeater and two link systems. The whole system works flawlessly and the interrupt does not impact the main code. Admittedly the timers do not have to be of the ultimate accuracy. There may be some jitter. A couple of percent error is not discernible to the users but it may make this approach unsuitable for some. I'm interested in times from 100ms-60 minutes and sometimes I turn interrupts off for the sending of PIC generated tones (tail beeps and morse code).
The code works on the assumption a timer can be started just before it is needed and shuts a function when the Flag =1 is encountered.

I've attached some extracts and added an example Main. It may or may not be useful for you.
John

Timeouts:                                   'the interrupt routine, runs at 0.1 Hz
     Context Save
     If TMR0IF = 1 Then
        TMR0H     = 0x3C                    'preload timer0 for 100 msec
        TMR0L     = 0xB0

       If TailTimer >0 Then                 'main tail before beep
           Dec TailTimer
       Else
           TailFlag=1                       'when 1 time out
       EndIf

       If Calltimer > 0 Then                'call timer
           Dec Calltimer
       Else
           CallFlag=1
       EndIf
       'plus other timers similar to above (I have a total of 13 for various tasks)
       
     EndIf
     TMR0IF = 0
     Context Restore
     
'The next few are routines to reset the timer

SetTailTimer:
     TailTimer = DurationTail     'set duration for tail
     TailFlag = 0                 'Flag reset
Return

SetCallTimer:
      Calltimer = DurationCall    'set calltimer
      CallFlag = 0
Return
' plus other routines to start the timers (total of 13 in my case)
' DurationTail and DurationCall are recovered from eedata on startup
' and may be adjusted by a touch panel display

Main:
    ' do Tail delay while transmitter is on for tail
    GoSub SetTailTimer
    While Tailflag = 0
      ' do stuff e.g. transmitter tail and beep
    Wend
    ' send call sign with transmitter on
    GoSub SetCallTimer
    While Tailflag = 0
      ' do stuff e.g. TX on and send call sign in morse
    Wend
   
   ' etc
GoTo Main   

TimB


Thanks everyone

What should be realised is that the problem has been solved. I showed the solution above.

SeanG_65

Quote from: trastikata on Mar 20, 2023, 02:50 PM
Quote from: TimB on Mar 20, 2023, 02:21 PMWhat I was asking for was a system to generate intermediate periods E.G-

I have a 100hz and some code runs at on the 100hz ticks but I need other code to run on of set of 10ms so making a flag at the 99th,199,299th etc


that's what I was saying - Timer1 generates 1ms interrupts for example and withing that Timer1 interrupt you increment the Timer0 counter - not from the clock but in software like "Inc TMR0L". If your Timer0 is preset for 99 cycles until interrupt, it will effectively generate an interrupt in 99ms.

Similar case is the 299th - use another timer - for example Timer3, which is a 16b - preset it for 299 cycles until interrupt and increment it within Timer1 interrupt.

Not really much difference from what you are doing, except accuracy is higher - because Timer1 is constantly running and you are loosing only 4 cycles in a millisecond to reset the Timer1 Interrupt Flag and preset Timer1 again. The rest of the timers are updated within the interrupt of TMR1 but after it has been reset.

Symbol TMR0IF = INTCON.2
Symbol TMR1IF = PIR1.0
Symbol TMR2IF = PIR1.1
Symbol TMR3IF = PIR2.1

Dim bTimer0 As TMR0L                '8-bit counter for TMR0
Dim wTimer1 As TMR1L.Word           '16bit counter for TMR1
Dim wTimer3 As TMR3L.Word           '16bit counter for TMR3

Dim pTMR0_Flag As Bit
Dim pTMR3_Flag As Bit

On_Interrupt GoTo Isr
GoTo Main

Isr:
    Context Save
        If TMR1IF = 1 Then
            wTimer1 = 0xD120        'preset Timer1 for 1ms interrupt @ 48 MHz
            TMR1IF = 0              'clear Timer1 Interrupt Flag
            Inc bTimer0             'increment Timer0
            Inc wTimer3             'increment Timer3
        EndIf
       
        If TMR0IF = 1 Then
            TMR0IF = 0              'clear Timer0 Interrupt Flag
            pTMR0_Flag = 1          'set Timer0 flag
            bTimer0 = 0x9E          'preset Timer0 for 98 cycles/increments to interrupt
        EndIf
       
        If TMR3IF = 1 Then
            TMR3IF = 0              'clear Timer3 Interrupt Flag
            pTMR3_Flag = 1          'set Timer3 flag
            wTimer3 = 0xB8FE        'preset Timer3 for 328 cycles/increments to interrupt
        EndIf
    Context Restore
Main:
 

I read TimB's issue and came up with EXACTLY the same method as Trastikata. I have done this before. You are using the "base" timer like an NCO. In fact you could simply use a PIC with an NCO and set that to the base 1000Hz and use that as your clock, then derive your other signals using this method.

TimB


Thanks

My question was not about the use of timers. It was about a maths system to test for a number of repeating periods and singles.

My solution only needs one timer. And I can using a very small amount of code test for the single occurrences or repeating ones

It uses Units, Tens and Hundreds to count. I can very easily set flags repeating every on every say 5th, 99th ,100th, and a singular 123