|
|
View previous topic :: View next topic |
Author |
Message |
dossdev
Joined: 08 Jul 2009 Posts: 24
|
Maximum Timer ISR Speed |
Posted: Thu Aug 26, 2010 10:24 pm |
|
|
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
|
|
Posted: Thu Aug 26, 2010 10:46 pm |
|
|
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
|
|
Posted: Thu Aug 26, 2010 11:41 pm |
|
|
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
|
|
Posted: Fri Aug 27, 2010 5:01 am |
|
|
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: 19513
|
|
Posted: Fri Aug 27, 2010 8:09 am |
|
|
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
|
|
Posted: Fri Aug 27, 2010 9:45 pm |
|
|
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
|
|
Posted: Sun Aug 29, 2010 11:37 am |
|
|
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
|
|
Posted: Sun Aug 29, 2010 12:52 pm |
|
|
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: 19513
|
|
Posted: Sun Aug 29, 2010 3:11 pm |
|
|
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
|
|
Posted: Sun Aug 29, 2010 3:46 pm |
|
|
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. |
|
|
|
|
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
|