News:

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

Main Menu

reading TMR2

Started by Lemans, Sep 01, 2022, 06:48 PM

Previous topic - Next topic

Lemans

I read TMR2 and use an interrupt on change (PORTB.4) to determine the speed (RPM's) of an electromotor.
using a 16F628A @20Mhz

TMR2 is used as a counter, on each TMR2-interrupt a counter is incremented
and when the interrupt on PORTB.4 is generated (hallsensor and magnet), the counter en registervalue of TMR2 is read and stored for futher use.

code TMR2-interrupt
If TMR2IF = 1 Then GoTo TMR2Flag

TMR2Flag:
PORTA.3 = 1
TMR2IF = 0
    Inc Teller
PORTA.3 = 0
GoTo interrupt_end

code int on change PORTB.4
If RBIF = 1 Then GoTo RBIflag

RBIFLAG:
If  PORTB.4 = 1 Then
PORTB.1 = 1
    TMR2ON = 0
        TMR2_R = TMR2              'reading reg-value of TMR2 and store it in var  "TMR2_R"
        meting = Teller                 'storing countervalue in var "meting"
        Teller = 0                         'reset counter
    TMR2ON = 1     
    End If
RBIF = 0
PORTB.1 = 0
GoTo interrupt_end

this works fairly well except
      -when the int-on-change is active and reading the registervalue of TMR2
      -TMR2 generates an interrupt at the same time

you can see this with a logic analyzer; int-on-change is active (PORTB.1 = 1) and immediatly afterwards the TMR2-int occurs (PORTA.3 = 1) then the reading of the counter or the registervalue of TMR2 is not correct. the speed indication is off by one count.

what am I doing wrong?.
regards A

TimB


The issue I think you saying is that if you are servicing an interrupt and you get another one at the same time your timing is messed up. Is that right?

Some observations and suggestions

1 Think about moving to a 18 series as they have 2 level interrupts.

2 I'm sure use a comparator to stop a timer so you can read the value of the timer. 100% accurate as long as no dividers are being used.

3 On entering the interrupt if its not the timing critical one set a flag and when done jump back to the start of the interrupt check the flags if the timing one is set and the flag is set ignore the tmr this time. Clear the flag exiting the interrupt. I doubt you will need to read the speed 100% of the time

Food for thought....

joesaliba

Quote from: TimB on Sep 01, 2022, 08:54 PM3 On entering the interrupt if its not the timing critical one set a flag and when done jump back to the start of the interrupt check the flags if the timing one is set and the flag is set ignore the tmr this time. Clear the flag exiting the interrupt. I doubt you will need to read the speed 100% of the time

Tim,

So you are saying that after service the interrupt for an, say, interrupt on change, you do literally a goto to the ISR to check for other flags?

Never thought of it, interesting!

Regards

Joe

Stephen Moss

Quote from: Lemans on Sep 01, 2022, 06:48 PMyou can see this with a logic analyzer; int-on-change is active (PORTB.1 = 1) and immediately afterwards the TMR2-int occurs (PORTA.3 = 1) then the reading of the counter or the register value of TMR2 is not correct. the speed indication is off by one count.

what am I doing wrong?.
regards A
I don't understand how the timer value can be off by 1, the timer interrupt is usually generated when it overflows (count rolls over from 255 to 0), therefore I would expect the value you get back to be off by around 255 and that as a result you will always be reading a very small, probably single digit value.

1) If you are using Timer 2 as a counter rather than to perform an action after a fixed time period why is its interrupt enabled? I can see no useful purpose in doing that in a counting situation.

2) If the Timer Interrupt is kicking in then...
  • That suggests your interrupt on change occurs close to maximum value of the timer count (255 being, 8 bit), if you use timer 1 instead and set it to 16 bits then you would be clearing the count long before the interrupt is triggered (counts to 65535), but then as the timer should not overflow generating the interrupt we come back to why use the interrupt on your counting timer at all.
  • You have not fully shown how you have set up your interrupts/Interrupt handler, if you are not doing so it would be wise to use the Context Save and Restore commands in your ISR (see the Positron manual). If I recall correctly those commands automatically disable and re-enable interrupts thus preventing one interrupt interfering with another while in the ISR (the primary cause of your problem). If they do not then you should do it yourself, usually that is done by clearing the GIE bit when entering the ISR and setting it immediately before exiting the ISR

3) In your interrupt handler to keep the value read from the timer as close to the real count as possible you need to minimise the number of instructions in your interrupt handler that are executed before you turn off the timer, stopping the count.
So the first instruction in your interrupt handler (after Context Save/Disable Interrupts) should be to check if it was triggered by interrupt on change.
If so, then first instruction in that section of code should be to stop the timer, the faster you can stop it the less additional counts may be detected. If you want a very precise count you could count the number of instructions used in the assembler file for the interrupt handler before the timer is stopped (plus the 4 cycle latency on Interrupt on change) and then calculate how many extra counts may have occurred before the timer was stopped and subtract them if necessary.

Quote from: joesaliba on Sep 02, 2022, 12:04 AMTim,
So you are saying that after service the interrupt for an, say, interrupt on change, you do literally a goto to the ISR to check for other flags?
It sound like you are thinking of somehow re-entering the ISR, having dealt with one interrupt. I  may be wrong, but I don't think you have to.
The interrupt flags should be set regardless, however it only triggers an interrupt event if the Interrupt is enabled. Therefore once in your interrupt handler even with GIE disabled if you poll each Interrupt flag of interest in turn you will know which interrupts have been triggered, even if the flag was set while still in the interrupt handler. Any flag set after you had checked should generate an new interrupt as soon as interrupts are re-enabled.
The order in which to you check the flags is entirely up to you, thus you would check them in what ever order of priority you deem appropriate. So with regard to this specific issue you would check the IOC flag before the TMR flag but as I stated above I think the main cause of the issue was not disabling interrupts while in the interrupt handler thereby permitting one interrupt to interrupt another.   

I may be wrong but I think Tim is suggesting that in your ISR you use an collection of If-Then statement to check each flag in turn and for each flag you have something like...
Check_Flags:
If TMR1IF = True and Falg_Variable.0 = False then
Flag_Variable.0 = True
Else
Deal with Interrupt (Low Priority)
Flag_Variable.0 = False
TMR1IF = False
EndIf

If TxIF = True and Falg_Variable.1 = False then
Flag_Variable.1 = True
Else
Deal with Interrupt (Low Priority)
Flag_Variable.1 = false
TxIF = False
EndIf

If RxIF = True then
handle interrupt (High priority)
RxIF = False
EndIF

If Flag_Variable > 0 then goto Check_Flags 'Loop again until all low priority interrupts handled
Context Restore.
End_ISR

That would allow you to deal with certain flags (High Priority) on the first pass through your ISR and catch the rest (Low Priority) on a subsequent pass, effective creating two levels of priority within the ISR.
If so then I think there is a potential problem of you getting stuck in the ISR for a very long time if any new flag is set/cleared flag reset during a loop as it could result in you round the loop again and again, and again.

Lemans

#4
thanks for all the elaborate answers, they do help.

more information on my problem;
I use timer2 to determine rotational speed of a motor.
so every time reg TMR2 overflows from 255 to 0 an interrupt is generated.
the TMR2-ISR then just increments a counter. total duration of 1 revolution is then "Teller" and reg-value of TMR2. (it is somewhat more complex but you only need to read "teller" and reg-value TMR2 once every revolution)

I could use TMR1 and read the reg-value of TMR1H and TMR1L but as rpm can be low, you still need a counter (teller) as counts sometimes are greater than 65531. a counter "dim teller as word" does the trick.

my ISR is this
context is saved and restored after the ISR

Flash:
Context Save
If T0IF = 1 Then GoTo TMR0flag
If TMR2IF = 1 Then GoTo TMR2Flag
If RBIF = 1 Then GoTo RBIflag

GoTo interrupt_end

TMR0 is used for some delay-time operation and is never at the same moment as RBIF, initiated after RBIF and occurs never ever at the same moment.

then I think I've the correct priority for TMR2 and RBI. first is Timer2 than RBIF.
but when they occur at the same moment or RBIF is slightly earlier then TMR2 it goes wrong

to my knowledge I stop the TMR2 by clearing bit "TMR2ON" (but I'm not sure) by stating TMR2ON = 0
so Timer@ is stopped, shouldn't increment any more and the reg-value can be read.
of course TMR2 is started again

If RBIF = 1 Then GoTo RBIflag

RBIFLAG:
If  PORTB.4 = 1 Then
PORTB.1 = 1
    TMR2ON = 0
        TMR2_R = TMR2              'reading reg-value of TMR2 and store it in var  "TMR2_R"
        meting = Teller                 'storing countervalue in var "meting"
        Teller = 0                         'reset counter
    TMR2ON = 1     
    End If
RBIF = 0
PORTB.1 = 0
GoTo interrupt_end

and the ISR for TMR2 is this one

TMR2Flag:
PORTA.3 = 1
TMR2IF = 0
    Inc Teller
PORTA.3 = 0
GoTo interrupt_end



so if TMR2 is executed directly after RBIF then this means to my knowledge

RBIF has generated an interrupt
due to the ISR RBIF   -> TMR2 is stopped
values are read
TMR2 is started again
some more statements are executed by RBIF; (RBIF = 0 and PORTB.1 = 0)
ISR RBIF has ended
context restored

then
TMR2 generates or has generated an interrupt due to reg-value rolling over to zero

so when reading TMR2 reg value (ISR RBIF) you should read 250 or 254 but the "teller" has not been incremented as the TMR2-ISR hasn't been executed.


however, when I recalculate the rpm-reading based on an outcome-value (measured time delay by logic analyzer) then the answer is of by one increment of the counter "teller" or the reading of the reg-value of TMR2. I think the reg-value is then read as 001 or 002 instead of 253.

I've tried to dump data with HRSOUT but that didn't give any usable info.

I've also tried to check if TMR2IF has been set at the end of the ISR-RBIF. and if yes adjust the calculation but that didn't solve the problem.

I wouldn't like to switch to a different pic as I still have a number of designs working with the 16F628.

regards A

Stephen Moss

#5
GoTo instructions may be simple to understand but do not make for the best code flow, you should consider using an If, Then, Else Structure instead. It makes the code a little more human readable because the relevant instructions are placed directly after their conditional test, instead of having to scroll through code to see it, for example...
Flash:
Context Save

If T0IF = 1 Then
       'Do Timer0 stuff here

ElseIf TMR2IF = 1 Then
  PORTA.3 = 1
    TMR2IF = 0
    Inc Teller
  PORTA.3 = 0

ElseIf RBIF = 1 Then
  If  PORTB.4 = 1 Then
      PORTB.1 = 1
      TMR2ON = 0
        TMR2_R = TMR2              'reading reg-value of TMR2 and store it in var  "TMR2_R"
        meting = Teller            'storing countervalue in var "meting"
        Teller = 0                 'reset counter
      TMR2ON = 1   
  End If
  RBIF = 0
  PORTB.1 = 0

EndIf
Context Restore
Now I see what you are doing, Teller is a count of the number of times that Timer 2 has rolled over, and so your total count for determining the RPM = TMR2 + (Teller * 255), in that case for an accurate count you should not need to stop Timer 2 and doing so may result in missed counts.

The problem with the way you are doing it is that whatever your order of priority you risk a delay in one interrupt occurring while the other is being handled and so either your Teller count or the tMR2 count will be off when you read it, of the two better for TMR2 to be out than Teller as the inconsistencies in TMR2 value can be minimised if you take an average of say 10 readings (add them and divide by 10).

QuoteI could use TMR1 and read the reg-value of TMR1H and TMR1L but as rpm can be low, you still need a counter (teller) as counts sometimes are greater than 65531. a counter "dim teller as word" does the trick.
65535 is a big count to overflow, do you know that you can slow down the rate the timer count is incremented by setting the prescaler so that it only increments once for say every 4 or 16 clock inputs? So you would read a smaller number but then multiply it by the appropriate value to get the true count, that might not be suitable for what you are doing but is one possible way to stop the timer overflowing and generating an interrupt without having to stop the timer, just clear it after reading. 

Have you considered measuring the RPM a different way. It sounds like the source of the interrupt on PORTB.4 is mounted on the motor shaft and so occurs once per revolution of the motor
Quotetotal duration of 1 revolution is then "Teller" and reg-value of TMR2
.
So why not use that as an external clock input for the timer so that the timer holds a direct count of the number of revolutions and then you read that count at a fixed timer interval (timer interrupt). If you read the count at a fixed time interval then you can calculate the RPM. For example if you read the count once per second, and TMR2 is incremented once per revolution of the motor then...
RPM = TMR2 * 60
This is easily scalable if you get the calculation correct, i.e. if the count is read every MilliSecond then RPM = TMR2 * 60000
or if the timer was incremented 4 times per revolution and read every Millisecond then RPM = (TMR2 * 60000) / 4
Provided you get the read interval correct the TMR2 count should not overflow, so if you were using TMR3 to create the RPM read interrupt then instead of handling a TMR2 interrupt in your ISR, you handle a TMR3 interrupt with something like...
If TMR3IF = 1 then
RPM = TMR2 * 60   'Calculate the RMP based on the TMR2 value at this moment and the read interval
TMR2 = 0          'Clear TMR2 Value before it overflows
EndIf

tumbleweed

In addition, TMR2 counts instruction cycles (FOSC/4), so any code in the ISR will get counted as well.
If there's conditional code in the ISR before reading it then the TMR2 value can change from interrupt to interrupt.

Lemans

@Stephen Moss, thanks for the reply. Have to chew on that one.
As for the isr-routine-> i"ll rewrite that one.
But the external clock input is a different approach. I"ll look into that one but might take some time
Thanks A

@T'weed, ok, understood