CCS C Software and Maintenance Offers
FAQFAQ   FAQForum Help   FAQOfficial CCS Support   SearchSearch  RegisterRegister 

ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

CCS does not monitor this forum on a regular basis.

Please do not post bug reports on this forum. Send them to CCS Technical Support

Maximum Timer ISR Speed

 
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion
View previous topic :: View next topic  
Author Message
dossdev



Joined: 08 Jul 2009
Posts: 24

View user's profile Send private message

Maximum Timer ISR Speed
PostPosted: Thu Aug 26, 2010 10:24 pm     Reply with quote

I'm working with a PIC16F616 and am playing with different Timer0 ISR settings and can't quite make sense of some of the timings I'm experiencing. Some of my measurements (pin toggling within ISR) are spot on while others are not quite so close, and still others are way off. I've also verified my expected results using some of the timer calculators available online. My main has nothing but a while loop. I've also tried Timer1 with similar, less than predictable results.
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Thu Aug 26, 2010 10:46 pm     Reply with quote

For a 16F PIC with one interrupt routine only, it takes about 50 instruction
cycles for the interrupt overhead. That doesn't include the time required
to execute the code that you put inside the interrupt routine.

With a 4 MHz oscillator, that's 1us per instruction. So that's 50us just to
get in and out of the interrupt routine. That limits the maximum
interrupt frequency to 20 KHz. It will less, when you add in the time
for your user code in the isr.

You could increase the PIC oscillator frequency. At 20 MHz, everything
will run 5x faster. The maximum frequency will become 100 KHz
(without any user code).
ckielstra



Joined: 18 Mar 2004
Posts: 3680
Location: The Netherlands

View user's profile Send private message

PostPosted: Thu Aug 26, 2010 11:41 pm     Reply with quote

And when you have other interrupts enabled this will make the timing calculations even less predictable. The interrupts are not all handled in one sequence, but one-by-one. When one interrupt is handled the interrupt handler returns control to 'main'. If another interrupt is waiting the interrupt handler is entered immediately again (with again the 50 instruction overhead).

Now what happens when multiple interrupts are waiting? The choice for deciding which interrupt to handle is by going through a linear list and the first match is being served. What could happen is that you have served one interrupt, return to main, and immediately enter the same interrupt again. Even when another interrupt was waiting longer to be served.

With the #priority declaration you can control the sequence of the interrupts in the earlier mentioned linear list. This gives you some control but has limitations.
collink



Joined: 08 Jan 2010
Posts: 137
Location: Michigan

View user's profile Send private message Visit poster's website

PostPosted: Fri Aug 27, 2010 5:01 am     Reply with quote

ckielstra wrote:
And when you have other interrupts enabled this will make the timing calculations even less predictable. The interrupts are not all handled in one sequence, but one-by-one. When one interrupt is handled the interrupt handler returns control to 'main'. If another interrupt is waiting the interrupt handler is entered immediately again (with again the 50 instruction overhead).

Now what happens when multiple interrupts are waiting? The choice for deciding which interrupt to handle is by going through a linear list and the first match is being served. What could happen is that you have served one interrupt, return to main, and immediately enter the same interrupt again. Even when another interrupt was waiting longer to be served.

With the #priority declaration you can control the sequence of the interrupts in the earlier mentioned linear list. This gives you some control but has limitations.


I wonder: Does it really have to be like this or is this a limitation of the compiler? Forgive this naive question since I do next to nothing with PIC assembly but can't the interrupt handler do sequence processing of the interrupts to skip the 50 instruction overhead per interrupt if there is more than one to process? I'm sure there would still be some overhead but maybe not as much. Also, it wouldn't seem to be too difficult to do a simple form of dynamic priority for interrupts so that none are left unserviced for too long. That is, an interrupt not serviced would get a dynamic bump in priority until it finally bubbled up past the priority of the interrupt that was blocking it.

Perhaps this is all too complicated for a little microprocessor but it never hurts to brainstorm and investigate what's possible.
Ttelmah



Joined: 11 Mar 2010
Posts: 19546

View user's profile Send private message

PostPosted: Fri Aug 27, 2010 8:09 am     Reply with quote

Yes, this, or approaches like this, 'can' be done. However on the PIC, they will generally need to be 'custom', since in most situations needing the extra speed, there will be perhaps only one interrupt that really needs to be treated as 'super important'. This of course makes them not ammenable to 'generic' solutions.
The answer in such cases, is to take advantage of the #int_global solution, and combine the sequential interrupt checking, with only saving the registers that 'must' be saved for the particular interrupt.
The downside of sequential checking, is that it increases the risk of getting 'stuck' permanently in the interrupt handler.
This problem doesn't appear for more complex chips that have separate interrupt vectors for each source, and hardware priority allocations, and generic 'variable' stacks, as opposed to only the call stack available on the normal PIC, allowing code to easily be made 're-entrant'.
The biggest improvement that could be made to the CCS solution, without much extra work, would be to calculate what registers are actually used in each interrupt routine, and only save these. This could easily halve the overhead, without any other changes.....

Best Wishes
John P



Joined: 17 Sep 2003
Posts: 331

View user's profile Send private message

PostPosted: Fri Aug 27, 2010 9:45 pm     Reply with quote

Yes, it may be necessary to use the #global directive to speed up response to interrupts, though it's a thing to do with caution. There are various ways to lose!

With the processors that only have one level of priority, I usually have just one interrupt, and that's a constant-frequency clock. Then any other item that has to be dealt with is done in that interrupt by polling the relevant flag. The advantage to this is that there's never any uncertainty about which interrupt is blocking another, or what the order of service is. But you do have to be certain that the basic clock is cycling fast enough to catch everything that needs to be done. For instance, if there's a serial port running, the clock must be at least as fast as 10 bit times, or 1/10 the baud rate in other words. But even at 115.2KB that's only 11500 per second, not a real killer. The other thing (obviously) to be concerned about is that there's time to do the maximum amount of work that might need to be done in the interrupt.

There could be something that would need immediate response, but how can you ever achieve that if there's a possibility that there's another interrupt in progress when it happens? It's impossible.
dossdev



Joined: 08 Jul 2009
Posts: 24

View user's profile Send private message

PostPosted: Sun Aug 29, 2010 11:37 am     Reply with quote

I understand about there being a limit on servicing a timer ISR, but I can't seem to even get close to a practical limit. Switching to fastio did minimize the code required to toggle C3 and made a difference in the performance, but not quite enough. Using the internal 8 MHz oscillator with the prescaler set to 1:1 and the timer preload set to 6, I should be able to get 8.0 kHz or 4 kHz at pin C3. Instead, I'm only getting 3.4 kHz. Switching to an osc freq of 4 MHz with a timer preload of 131 should also yield 8 kHz (4 kHz toggle at C3). Instead, I'm only getting about 3 kHz.

Code:

#include <16F616.h>

#device  ADC=8
#define  ADC_CLOCK__DIV_2
#define  ADC_CLOCK__INTERNAL
#device  *=8

#fuses   INTRC_IO, NOWDT, NOPROTECT, NOMCLR
#use     delay(clock = 8000000)
#fuses   IOSC8
#use     fast_io(C)

void main()

{
  setup_timer_0(RTCC_INTERNAL|RTCC_8_BIT|RTCC_DIV_1);
  set_timer0(6);
  set_tris_c(0x00);
  enable_interrupts(GLOBAL);
  enable_interrupts(INT_TIMER0);

  for (;;);

}

#INT_TIMER0
void timer0_isr()
{
  output_toggle(PIN_C3);
  set_timer0(6);      // 6 = 8 kHz @ 8 MHz osc
                      // 131 = 8 kHz @ 4 MHz osc
}

.................... #INT_TIMER0
.................... void timer0_isr()
00BC:  SLEEP
.................... {
.................... output_toggle(PIN_C3);
*
0044:  MOVLW  08
0045:  XORWF  PORTC,F
.................... 
....................    set_timer0(6);      // 6 = 8 kHz @ 8 MHz osc
0046:  MOVLW  06
0047:  MOVWF  TMR0
.................... 
.................... 
.................... 
.................... }
.................... 
0048:  BCF    INTCON.TMR0IF
0049:  BCF    PCLATH.PCLATH3
004A:  GOTO   02F

dossdev



Joined: 08 Jul 2009
Posts: 24

View user's profile Send private message

PostPosted: Sun Aug 29, 2010 12:52 pm     Reply with quote

It turns out that I can actually get 30 KHz (15 kHz at pin C3) when I use a timer preload value of 255 with an 8 MHz osc (1:1). The calculation for these values yields 2 MHz which is never going to happen. Are my calculations off or is there more going on that meets the eye?
Ttelmah



Joined: 11 Mar 2010
Posts: 19546

View user's profile Send private message

PostPosted: Sun Aug 29, 2010 3:11 pm     Reply with quote

Very first reply.
"it takes about 50 instruction
cycles for the interrupt overhead"
When an interrupt happens, the processor responds on the first cycle of the next instruction. Now, jumps are two operation instructions (take eight clocks), so potentially sitting in a for loop, the hardware response can be eight clock cycles later.
Then the processor executes a 'call' to the global interrupt handler. The global handler, then saves the status of all the hardware registers (takes about 26 instructions), and tests which interrupt has triggered.
Only then do you arrive at your 'handler'
After the handler completes, the interrupt flag is cleared, and all the registers restored, and finally the processor returns, re-enabling the interrupts as it does so.
In all, just getting into the handler and out again, takes a total of up to about 224 clock cycles.
Add a couple of instructions to toggle the pin, and a few more to load a value into the timer register, and you are up to perhaps 260 clock cycles, giving a fastest 'possible' loop somewhere just under 31KHz.

Now, we have mentioned int_global.
The 'point' here is that if your interrupt only uses a very limited number of registers, you don't need to save them all. Also if only one interrupt is running, there is no need to test 'which' interrupt has occurred. Look at ex_glint.c, which shows how to do this.
Big caveat though. With this, it becomes _your_ responsibility to save what is needed. Using this though, and putting your timer interrupt code in place of the section entitled 'code for isr', and enabling timer1, as the interrupt, instead of the example - remember you need to add the code to clear the interrupt as well, since this is no longer done for you, will take your loop speed up to around 100KHz.

Best Wishes
PCM programmer



Joined: 06 Sep 2003
Posts: 21708

View user's profile Send private message

PostPosted: Sun Aug 29, 2010 3:46 pm     Reply with quote

For the current program, my count of instruction cycles gives:
Code:

250  Timer0 counts from 6 to 0.
 29  Maximum overhead count in CCS interrupt dispatcher code
  3  Internal interrupt latency (per 16F-series Reference Manual)
  4  instructions inside the isr, to reach and do the Timer0 reload
  2  Timer0 reload "inhibit" time (per 16F Reference Manual)
-------
288  Total

The measured interrupt period of 3.460 KHz is caused by toggling an i/o pin.
So the real interrupt rate is 2x that, which is 6.92 KHz.
This gives a period of 144.5us.
With a 8 MHz oscillator, there are 2 instructions per usec.
This gives 289 instructions.

My instruction count is off by 1 from the value derived from the frequency
counter measurement. Close enough. The list above accounts for the
observed interrupt frequency.
Display posts from previous:   
Post new topic   Reply to topic    CCS Forum Index -> General CCS C Discussion All times are GMT - 6 Hours
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group