PDA

View Full Version : Strange results with PBP3 and instant interrupts



R.G.Keen
- 11th December 2011, 16:25
I have a set of code that worked very well under 2.6a. I changed processors to an enhanced midrange PIC supported only by the new level of PBP, so I upgraded to 3.0.

Code worked, once I stamped out the issues with different ports/pins/etc. on the new hardware. But I'm getting really strange results with the T1 interrupt.

T1 interrupt was set for 1000hz, and sure enough, I can make it flip an output pin every 1mS, with some effort. However,
- I can't make it NOT do things on 1mS boundaries by changing the required Hz in the instant interrupts setup
- the interrupt routine drops timing; I do various things on the 1mS tick, and some of these involve running a counter variable and making it roll over to 0 at some count as in
counter = counter +1
IF counter = 5 THEN counter = 0
This works, but those statements seem to make the interrupt routine sometimes miss a timer tick and run a long time before it starts back up again, which I think means it misses the next interrupt tick while still on the interrupt, so the timer runs a full count. That's the only good explanation I can come up with.

It acts like the system clock is not fast enough to service the timer tick interval and still get off the interrupt level before the timer times out. However, I ran the same basic code on a midrange (not enhanced midrange) PIC with the same clock frequency and had no problems with the interrupt timing on that one. It's possible I've set up the clock speed incorrectly on the newer and more complex PIC, but I've thrashed the clock oscillator setup pretty thoroughly over the last two days.

My best guess right now is that either (a) I'm really going to slap my forehead hard when someone tells me the answer or (b) there is some setup/migration issue with PBP3 and instant interrupts that I missed. Naturally, I think it's (b). 8-)

So where's the secret "migrate instant interrupts to PBP3" file?

Acetronics2
- 11th December 2011, 16:44
Could you post a source file showing the problem ???

Alain

R.G.Keen
- 11th December 2011, 17:21
Sure.
Salient (I think) points:
- config bits set for external 4MHZ crystal; oscilloscope shows this is actually running at 4MHz
- DEFINE for 4MHz set
- Just in case, the OSCCON register is set for doing what the config bits say, and to select the internal 4MHz oscillator if that fails
- The PORTA.4 = Mpx.0 is used just to toggle an otherwise not used pin so I can see it flip every time the interrupt routine runs.
This happens on 1mS intervals MOST of the time; all the time when it's the only thing in the interrupt routine, missing a tick every so often when I put more code into the interrupt routine. This is what made me suspect the system clock.
- the output pins will sometimes go 80mS between changes instead of the expected 5mS or so. This is what makes me think I am missing an intterupt return time.
- Changing the frequency in the instant interrupts code from 1000 to some thing else does NOT change the 1mS interrupt interval as seen on the porta.4 pin. This baffles me and makes me think there is an inconsistency with PBP3 and instant interrupts.


'============ Rotating output pins =========
' Compiler : PICBASIC PRO Compiler 3.0
' Target PIC : 16F1518
' Oscillator : set to 4MHz external crystal
' timing ticks happen every 1mS
' ...
DEFINE OSC 4 ; use external 4MHz crystal
' Includes for interrupts
wsave VAR BYTE $20 SYSTEM
wsave1 VAR BYTE $A0 SYSTEM
'
INCLUDE "DT_INTS-14.bas" ' Base Interrupt System
INCLUDE "ReEnterPBP.bas" ' Include if using PBP interrupts
'...
'===============config bits=================
#CONFIG
__config _CONFIG1, _FOSC_XT & _PWRTE_ON & _MCLRE_ON & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _WDTE_OFF & _FCMEN_OFF
__config _CONFIG2, _WRT_OFF & _VCAPEN_OFF & _BORV_19 & _LPBOR_OFF & _LVP_OFF
#ENDCONFIG
'================================
ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler TMR1_INT, ReloadTMR1, ASM, no ; MUST be first
INT_Handler TMR1_INT, _T1handler, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
ENDASM
' ================================================== ==========
;--- Change these to match the desired interrupt frequency ----------
@Freq = 1000 ; Frequency of Interrupts in Hz
@Prescaler = 1 ; Timers Prescaler setting
T1CON = $00 ; $30 = Prescaler 1:8, TMR1 OFF
; $00=1:1, $10=1:2, $20=1:4, $30=1:8 -- Must match @Prescaler value

@ INT_ENABLE TMR1_INT ; enable Timer 1 interrupts
GOSUB StartTimer ; Start the Timer
'======== pre- main loop code here =================
' Initialization
OSCCON = $68 ' set oscillator control for 4MHZ, external
'...
'============ main loop starts here=================
'
Value = 120
mainloop:
Value = Value +1
Pause 500
GOTO mainloop
'
'================================================= ===========
T1handler: ' what happens when T1 clicks over a time interval
' interval is set for 1mS / 1000Hz
' each fifth interrupt, move to a new output pin
Mpx = Mpx + 1
IF Mpx = 6 THEN Mpx = 0
PORTA.4 = Mpx.0 ' *** Debug *** toggle an output pin to see interrupt handling timing
IF Mpx = 1 THEN ' only do index processing every fourth count
i = i + 1 ' update which pin is changed
IF i > 2 THEN i = 0 ' force i to be 0..2 and roll over to 0
n = Value PID i ' makes n be the i-th digit of Value
PINS = ~ DCD 1
LATA = LATA & PINS ' pull the enable pins
ENDIF
'
@ INT_RETURN ' done with this interrupt pass
'
;---[TMR1 reload - interrupt handler]-----------------------------------------
ASM ; Calculate Timer Reload Constant
ReloadInst = 8 ; # of Intructions used to reload timer
if ((Prescaler == 1)||(Prescaler == 2)||(Prescaler == 4)||(Prescaler == 8))
MaxCount = 65536 + (ReloadInst / Prescaler)
TimerReload = MaxCount - (OSC*1000000/4/Prescaler/Freq)
if ((TimerReload < 0) || (TimerReload > (65535-ReloadInst)))
error Invalid Timer Values - check "OSC", "Freq" and "Prescaler"
endif
else
error Invalid Prescaler
endif
ENDASM

@Timer1 = TMR1L ; map timer registers to a word variable
Timer1 VAR WORD EXT
TimerReload CON EXT ; Get the External Constant
TMR1ON VAR T1CON.0 ; Alias the Timers ON/OFF bit

;---Reload Timer1------
ASM
ReloadTMR1
MOVE?CT 0, T1CON, TMR1ON ; 1 stop timer
MOVLW LOW(TimerReload) ; 1 Add TimerReload to the
ADDWF TMR1L,F ; 1 value in Timer1
BTFSC STATUS,C ; 1/2
INCF TMR1H,F ; 1
MOVLW HIGH(TimerReload) ; 1
ADDWF TMR1H,F ; 1
MOVE?CT 1, T1CON, TMR1ON ; 1 start timer
INT_RETURN
ENDASM

;---Start/Stop controls -----
StartTimer:
Timer1 = TimerReload ; Load Timer
TMR1ON = 1 ; start timer
RETURN

StopTimer:
TMR1ON = 0 ; stop timer
RETURN

R.G.Keen
- 11th December 2011, 22:13
It gets stranger, if more useful. The real problem with blowing out of the interrupt timing was the
'n = Value DIG i ' statement (which I notice I hosed up posting the source code.)

I moved the "@ INT_RETURN " statement up a line at a time, and when I got above the "DIG" statement, timing was magically as expected. No amount of dinking with the statement and retrying the syntax, limits, whatever would stop this.

When I split the "Value" variable into three digits of one byte each and used an IF array to make decimal counting work in the main loop, and changed to a select statement for the pre-carved-up digits in the interrupt loop, it started working just fine.

Either there is a vaster subtlety than I can see, or the "DIG" math function is broken in some way that interacts with timer1 interrupts.

HenrikOlsson
- 12th December 2011, 06:22
Hi,
The first thing that got my attention was the dual interrupt service routines coupled to the same interrupt source. I see that you're not resetting the flag in the ASM handler so it goes in, reload the timer, returns and immediately goes back to PBP handler - I think that's what it'll do or at least what's it meant to do. Is there any reason for not having the reload code, in ASM, as a block at the beginning of the T1_Handler ISR? Not saying the way you've done it doesn't work but it seams a bit wasteful and I don't think I've seen dual ISR's like that before.

As for the DIG operator I can't say for sure but I'm having a hard time seeing HOW it could interfere with TMR1 or the interrupts. Apparently something strange is going on but I suspect the actual problem is something else than the DIG operator.

I know, not much real help but anyway...

/Henrik.

Acetronics2
- 12th December 2011, 07:33
Hi,

Henrik found it ...

Why not reload timer simply using PBP ???

would only make ONE interrupt stubb.

Past that, I don't think using PBP "bits" into an ASM part is really safe ...



;---Reload Timer1------
ASM
ReloadTMR1
MOVE?CT 0, T1CON, TMR1ON ; 1 stop timer
MOVLW LOW(TimerReload) ; 1 Add TimerReload to the
ADDWF TMR1L,F ; 1 value in Timer1
BTFSC STATUS,C ; 1/2
INCF TMR1H,F ; 1
MOVLW HIGH(TimerReload) ; 1
ADDWF TMR1H,F ; 1
MOVE?CT 1, T1CON, TMR1ON ; 1 start timer
INT_RETURN
ENDASM




Alain

SteveB
- 12th December 2011, 14:04
R.G,
From a overall perspective, the best way to handle interrupts is to do as little as possible inside them, and let the maid code loop do all the heavy lifting. So along those lines, here are some suggestions:
- Get rid of the the line: "INT_Handler TMR1_INT, _T1handler, PBP, yes"
- Set a flag in the "ReloadTMR1" to track when it has been called
- In you main loop, check to see if the flag has been set
- - if yes, run T1Handler and clear the flag

If your main code loop is really long, you can check the flag a couple of times in the loop.


I don't think using PBP "bits" into an ASM part is really safe ...
Unless you are REALLY careful, and fully understand what's happening behind the scenes at the assembly level, this is good advice.

HenrikOlsson
- 12th December 2011, 15:37
Hi,
While I agree that you shouldn't spend more time in the interrupt than neccessary (ie, don't use any Pause or other commands that "holds" the processor) using the interrupt to set a flag which is then polled and handled in the main routine kind of defeats the purpose of the interrupt in the first place IMHO. You might as well poll the interrupt flag in the main routine and then GOSUB a handler that reloads the timer and resets the flag.

Obviosuly it depends on how often the code is supposed to run and with what latency etc. For example if something is to be run at 1Hz and a couple of ms or whatever worth of "jitter" doesn't matter then having a tick count maintained by the ISR which signals the main routine is a good idea. But if the code needs to run at specific and precise intervals then having it in the ISR is the way to go.

Depending in the application it can also be a good idea to split the tasks between the ISR and the main routine. For example use the ISR to capture and calculate the value, then signal the main routine that a new value is ready and have IT send it instead of holding up the ISR with a HSEROUT or whatever. Even better in this case is of course an interrupt driven TX-routine as well but that's not the point.

/Henrik.

R.G.Keen
- 12th December 2011, 15:58
Thanks for the reading, and the good advice. What you're saying is generally correct, and it is always possible that I have flatly missed something on the return from interrupt.

However, the interrupt scheme is the DT instant interrupts include files; it may be a little roundabout but it works in general. And while interrupts do need to be short and quick, I did use exactly this code, statement for statement, in the 2.60a compiler and it worked perfectly for other PICs.

I do need the preciseness of running the code on the interrupts. I normally use flags to the main routine as Henrik suggests when I can, but this needs the timing precision, and equally important, not having a second interrupt happen while the actions are being taken.

And, as I mentioned, I recoded the logic into other (longer and clumsier) statements, and it started working. That was the one that got me. It appears that the single statement was what was throwing off the timer interrupt. It looked like it made the ISR miss the service interval, and let the timer count through its full 16 bits instead of being reset. And removing that one statement made the longer and clumsier coding work.

R.G.Keen
- 12th December 2011, 20:05
Yet more interesting results.

The "DIG" statement works outside of an interrupt routine and does not interfere with interrupts.
A "DIG" statement inside the interrupt routine sometimes make the timer interrupt miss.

HenrikOlsson
- 12th December 2011, 20:59
Hi,
The only thing I can think of is that the DIG operator (combined witht the rest of the ISR) consumes too many cycles and that the interrupt overruns itself. Can you, just as a test, increase the time between interrupts to say 2ms instead of 1 and see if that takes care of it?

/Henrik.

R.G.Keen
- 12th December 2011, 23:21
Yep. I'll give it a try in the next day or so.

I coded around it by using a sacrificial variable to accumulate the count, then using DIG outside the interrupt routine to separate digits. That works fine.

R.G.Keen
- 13th December 2011, 01:03
I stripped out the extraneous stuff and verified that this test code compiles and gives the same strange results as the whole program. It's running on a 16F1518 with an external 4MHz crystal. I watched the interrupt speed by copying the LSB of the interrupt counter variable to an output pin, and watching it on an oscilloscope.

Things to note:
- I tried Henrik's suggestion of slowing the interrupts down. Worked fine, and this stub shows missed interrupts at 725Hz, but not at 700Hz. I wanted to get to 1mS, which had worked with 2.60a and other PICs.
- I tried subbing in a different statement with the same variables (n, Value), and found that it worked fine to at least 2000Hz.
- All I can guess at is that Value is a WORD variable, and maybe the use of DIG takes longer on words. I didn't try it with a BYTE variable.

I'm well aware that I make silly mistakes in programs, but it looks to me like the use of the DIG function is what does this.



' Name : DIG Interrupt statement test
' Compiler : PICBASIC PRO 3.0
' Target PIC : 16F1518
' Oscillator : set to 4MHz external crystal
' =======================================
' RA4 = output test pin
' RA6 = Oscillator/crystal pin
' RA7 = Oscillator/crystal pin
'
DEFINE OSC 4 ; use external 4MHz crystal
' Includes for interrupts
wsave VAR BYTE $20 SYSTEM
wsave1 VAR BYTE $A0 SYSTEM
'
INCLUDE "DT_INTS-14.bas" ' Base Interrupt System
INCLUDE "ReEnterPBP.bas" ' Include if using PBP interrupts
' Variable Declarations
i Var Byte ' counter for which digit is being displayed in the display Mpx
n Var Byte ' transfer variable for converting digit number to segments in lookup table
Value Var WORD '

'===============config bits=================
#CONFIG
__config _CONFIG1, _FOSC_XT & _PWRTE_ON & _MCLRE_ON & _CP_OFF & _BOREN_OFF & _CLKOUTEN_OFF & _IESO_OFF & _WDTE_OFF & _FCMEN_OFF
__config _CONFIG2, _WRT_OFF & _VCAPEN_OFF & _BORV_19 & _LPBOR_OFF & _LVP_OFF
#ENDCONFIG
'================================
ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler TMR1_INT, ReloadTMR1, ASM, no ; MUST be first
INT_Handler TMR1_INT, _T1handler, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
ENDASM
' ================================================== ==========
;--- Change these to match the desired interrupt frequency ----------
;*** found that 700 works, 725 or greater frequency does not***
@Freq = 725 ; Frequency of Interrupts in Hz
@Prescaler = 1 ; Timers Prescaler setting
T1CON = $00 ; $30 = Prescaler 1:8, TMR1 OFF
; $00=1:1, $10=1:2, $20=1:4, $30=1:8 -- Must match @Prescaler value

@ INT_ENABLE TMR1_INT ; enable Timer 1 interrupts
GOSUB StartTimer ; Start the Timer
'======== pre- main loop code here =================
' Initialization
OSCCON = $68 ' set oscillator control for 4MHZ, external
ANSELA = 0
ANSELB = 0
ANSELC = 0
TRISB = 000000
TRISC = 111111
TRISA = 000000
PORTB = 111111
PORTA = 110111
Value = 100

'============ main loop starts here=================
mainloop:
Value = Value + 1
IF Value > 299 THEN Value = 0 ' Value increments from 0 to 299, rolls over to 0
Pause 300
GOTO mainloop
'================================================= ===========

T1handler:
i = i +1
IF i > 3 THEN i = 0 ' force i to be 0..2 and roll over to 0
i = i & 000011
PORTA.4 = i.0 ' wiggle the output bit for debug purposes
n = Value DIG i ' this statement causes missed interrupts after four are caught
' n = Value & 000111 ' this alternative statement works fine at 2000Hz
@ INT_RETURN ' done with this interrupt pass

;---[TMR1 reload - interrupt handler]-----------------------------------------
ASM ; Calculate Timer Reload Constant
ReloadInst = 8 ; # of Intructions used to reload timer
if ((Prescaler == 1)||(Prescaler == 2)||(Prescaler == 4)||(Prescaler == 8))
MaxCount = 65536 + (ReloadInst / Prescaler)
TimerReload = MaxCount - (OSC*1000000/4/Prescaler/Freq)
if ((TimerReload < 0) || (TimerReload > (65535-ReloadInst)))
error Invalid Timer Values - check "OSC", "Freq" and "Prescaler"
endif
else
error Invalid Prescaler
endif
ENDASM

@Timer1 = TMR1L ; map timer registers to a word variable
Timer1 VAR WORD EXT
TimerReload CON EXT ; Get the External Constant
TMR1ON VAR T1CON.0 ; Alias the Timers ON/OFF bit

;---Reload Timer1------
ASM
ReloadTMR1
MOVE?CT 0, T1CON, TMR1ON ; 1 stop timer
MOVLW LOW(TimerReload) ; 1 Add TimerReload to the
ADDWF TMR1L,F ; 1 value in Timer1
BTFSC STATUS,C ; 1/2
INCF TMR1H,F ; 1
MOVLW HIGH(TimerReload) ; 1
ADDWF TMR1H,F ; 1
MOVE?CT 1, T1CON, TMR1ON ; 1 start timer
INT_RETURN
ENDASM

;---Start/Stop controls -----
StartTimer:
Timer1 = TimerReload ; Load Timer
TMR1ON = 1 ; start timer
RETURN

StopTimer:
TMR1ON = 0 ; stop timer
RETURN

Darrel Taylor
- 13th December 2011, 07:13
Like Henrik said, @ 4Mhz the DIG command takes longer than the interrupt period.
But it's a little different than you might think.

The digit number that you request makes a huge difference in the execution time.

@ 4Mhz, DIG 0 takes ~322us, well within the 1ms period.
But DIG 1 has to do another divide, so it takes ~638 uS.
DIG 2 = 950us.
And finally, DIG 3 takes ~1.28ms, obviously too long to be in a 1ms interrupt.
After that, the timer reload leaves it overflowed and it has to count all the way back to 65536 before the next interrupt happens. Which is why it seemed like it was doing 4 interrupts at a time.

http://support.melabs.com/DT/DIG_Time.png

These times were measured by wrapping the DIG command with HIGH/LOW statements and reading the pulse width on a scope.

HIGH PORTA.3
n = Value DIG i ' this statement causes missed interrupts after four are caught
LOW PORTA.3

After bumping up the oscillator to 16Mhz internal, the commands take 1/4th of the time and everything works within the 1ms.

But since the digit only changes when the variable in the mainloop changes, there's no need to calculate it every time in the interrupt handler.
Setting the segment patterns in the mainloop will reduce the interrupt handlers time significantly. Then the interrupt handler just puts the pattern to the pins.
You could probably run at ~20Khz, not that you would want to. 1Khz is fine and there lots of time left over for the mainlop to use.
So that brings us back to how could it work on PBP 2.60 with a 16F, and not on PBP3 with an enhanced core..

Well, I don't think that's possible.
I ran the same program on a 16F886 @ 4Mhz, and DIG took even longer
DIG 0 = 385us
DIG 1 = 760us
DIG 2 = 1.15ms
DIG 3 = 1.54ms

Maybe you were running at a different frequency and didn't remember it?

R.G.Keen
- 13th December 2011, 14:57
@ 4Mhz, DIG 0 takes ~322us, well within the 1ms period.
But DIG 1 has to do another divide, so it takes ~638 uS.
DIG 2 = 950us. And finally, DIG 3 takes ~1.28ms, obviously too long to be in a 1ms interrupt.
OK. Looks like a DIG command takes roughly 325 instructions per digit number. I would not have guessed that. And clearly didn't guess that. 8-)


These times were measured by wrapping the DIG command with HIGH/LOW statements and reading the pulse width on a scope.
Good trick. I'll remember that one. It fits my debug style well.


But since the digit only changes when the variable in the mainloop changes, there's no need to calculate it every time in the interrupt handler.
Setting the segment patterns in the mainloop will reduce the interrupt handlers time significantly. Then the interrupt handler just puts the pattern to the pins.
In the real app, it needs to change dynamically on timer ticks, but I could spend the time in the main loop and precalculate all the digits, I guess, so they're all always current except for the ones where the interrupt happens in the middle of the DIG instruction. That means the pattern will be incorrect on average half the time for DIG 2, 3 in 10 for DIG 1, and 3 in 20 for DIG 0; then the pattern will be corrected on the next interrupt. That's certainly better than always being wrong for DIG 4 and nearly always for DIG 2, but requires some thinking as to whether being wrong for one interrupt period is OK.

Fortunately, this isn't running machinery, so I might get away with it. Otherwise, sounds like I might make it with an 8MHz or higher clock. More system design to do.


So that brings us back to how could it work on PBP 2.60 with a 16F, and not on PBP3 with an enhanced core..
Well, I don't think that's possible. ...
Maybe you were running at a different frequency and didn't remember it?
Maybe. I'll dig out the hardware and look. Could be my idiot tech (that's me... ) put in the wrong crystal, which is more likely than your analysis being wrong here. But it's worth digging out to look at. I'm very familiar with solving problems with the Method of Offsetting Errors.

Thank you for the look at the problem. I have annotated my compiler manual at the "DIG" instruction with "NOTE: execution takes about 325 instructions per digit number." Getting to there has now cost me a couple of days, but knowledge is always expensive, I guess. 8-)