|
|
View previous topic :: View next topic |
Author |
Message |
5440
Joined: 08 Feb 2009 Posts: 11
|
INT_GLOBAL vs INT_FAST vs INT_HIGH on 18F parts |
Posted: Sat Nov 13, 2010 6:35 pm |
|
|
I did some reading but have a couple of Q's regarding priority ints on 18F devices.
I am playing with a 18F2585 on CCS 4.106
I think I understand the following:
1) Using INT FAST will allow a high priority int to interrupt a low priority in progress. No context saving is performed as I seen from the listing
2)Using INT HIGH will allow a high priority int to interrupt a low priority in progress, but will generate context saving code is performed as I seen from the listing, 50 cycles or so extra over the FAST option.The compiler needs the statement #device HIGH_INTS=TRUE for priority Ints.
3) Using an interrupt that has no assigned priority (Microchip design) such an INT0 pin, will basically generate code that is the same as if HIGH was used.ex One INT_EXT and one fast int, INT_TIMER1 FAST would generate code that adds the saving overhead.
4) INT_GLOBAL, generates nothing, but the complier expects a user ISR at 0x08 which is the high vector when used (low priority vector is 0x18), else 0x08 is used for all non high priority ints
Q's:
Q1) If one wants the tightest high priority int code, is it best to use INT_Global or use FAST INTs? The user would have to add context saving and check the flags for Global Ints to determine what ints occurred? If so a low int is in progress, what happens when a high priority occurs? Would it just vector back to 0x08? How does it back to the low priority int? Any good example code for something like this?
Q2) If one needed a INT such as EXT (INT0 pin) that is no a priority int, are you stuck using a Global Int as all FAST Int would be handled as HIGH and have the overhead automatically added?
Q3) Is it a good idea to have more than one high priority (HIGH or FAST) enabled at one time? What happens if two high priority ints are pending at the same time?
Q4) If one were to use GLOBAL INTS, what registers really need to be saved if one is doing basic code in the ISR such as saving a CCP ot TMR reg, or inc a counter variable? What is the point of all the saving I seen in the listing file for HIGH INT?
Q5) If one wanted tight code for non priority Ints (compatibility mode), is it GLOBAL INT to only option?
Q6) Is it common practice to mix asm (for save/restore) and C code in the ISR? |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Sun Nov 14, 2010 3:40 am |
|
|
If you have _any_ interrupt set as high priority, and also have the standard 'INT' in use, this _always_ becomes a high priority INT. Hence an interrupt source test becomes 'forced'. This is a hardware limitation of the chips, with this interrupt having no 'priority' bit. This is why in your notes, when you used INT_TIMER1 FAST, the saving overhead was generated, since the compiler did this automatically. This is also your Q2 answer. If you want a single high priority interrupt with the minimum overhead, then it is vital to not have INT0 in use as well...
You can only ever have one interrupt flagged as 'FAST', since otherwise the code must test for the source, and the code to do this together with saving the registers, is automatically added.
The amount of saving needed, depends totally on the code _you_ use in the interrupt handler. If you perform array operations, the table pointers have to be saved. Most things will affect the W register, but the RETFIE1 instruction, restores this for you (and the BSR etc..). The only way to really optimise the interrupt handler, is to write the handler code, then go through the assembler listing generated, and note down every register that is used. Then add code to save these.
The compiler defaults to saving everything that it uses, even if the routines using the registers don't exist in the interrupt handler. I have often 'wished' for a compiler option, that checked for what registers were actually used in the interrupt routines, and only saved these. Lacking this, you have to do it yourself.
There is no inherent speed difference between using INT_GLOBAL, or fast INTs. In fact the 'FAST' directive, can be used to generate the exact equivalent to INT_GLOBAL for the high priority interrupts. If you generate a _single_ interrupt handler and flag it as 'FAST', this becomes the 'global' handler for high priority interrupts. You can add code to this to test for what interrupt occurred, and then manually set the priority flag for another interrupt source.
You need to slightly change your thinking about the interrupt vectors. 0008, is the 'high priority' vector address _when interrupt priorities are enabled_, and the 'global' vector address when priorities are disabled. Hence if you define INT_GLOBAL with no high priority vector enabled, the handler goes at address 8. If you wanted to handle both high and low priority interrupts, you would need to write the INT_GLOBAL routine to immediately vector to the high priority handler, and then have a second entry point for the low priority interrupts.
You can still use high priority interrupts with an INT_GLOBAL, but you have to set the priority bits yourself.
Have a look at ex_glint.c, which gives a basic example of handling an interrupt 'fast' using the global declaration.
The below is a small 'snippet' from a handler designed to give minimum latency for a quadrature change, with this needing less registers saved than the other routines:
Code: |
#int_global
void myint(void) {
static int INT_scratch[10];
#ASM
//Here for maximum speed, I test the RB interrupt - since it is allways
//enabled, I don't have to test the enable bit
BTFSS INTCON,RBIF
GOTO NXT
#endasm
quad(); //Quadrature handler.
#asm
GOTO FEXIT //Use the fast stack for exit
NXT:
//Now I save the registers needed for the other interrupt handlers
MOVFF FSR0L,INT_scratch
MOVFF FSR0H,INT_scratch+1
MOVFF FSR1L,INT_scratch+2
MOVFF FSR1H,INT_scratch+3
MOVFF FSR2L,INT_scratch+4
MOVFF FSR2H,INT_scratch+5
MOVFF scratch,INT_scratch+6
MOVFF scratch1,INT_scratch+7
MOVFF scratch2,INT_scratch+8
MOVFF scratch3,INT_scratch+9
//Test for the external interrupt (power fail).
BTFSS INTCON,INT0E
GOTO NEXTA
BTFSC INTCON,INT0
#endasm
pfail();
#asm
NEXTA:
//Now for Timer 2
BTFSS PIE1,TMR2
GOTO NEXT1
BTFSC PIR1,TMR2
#endasm
TIMER2_isr();
#asm
NEXT1:
//Receive data available
BTFSS PIE1,RCIE
GOTO NEXT2
BTFSC PIR1,RCIE
#endasm
RDA_isr();
#asm
NEXT2:
//Transmit buffer empty
BTFSS PIE1,TXIE
GOTO EXIT
BTFSC PIR1,TXIE
#endasm
TBE_isr();
#asm
EXIT:
//Restore registers
MOVFF INT_scratch,FSR0L
MOVFF INT_scratch+1,FSR0H
MOVFF INT_scratch+2,FSR1L
MOVFF INT_scratch+3,FSR1H
MOVFF INT_scratch+4,FSR2L
MOVFF INT_scratch+5,FSR2H
MOVFF INT_scratch+6,scratch
MOVFF INT_scratch+7,scratch1
MOVFF INT_scratch+8,scratch2
MOVFF INT_scratch+9,scratch3
//Here the 'fast' exit.
FEXIT:
RETFIE 1
nop
#ENDASM
}
|
The register saving used here is only about half the amount normally done. Basically the FSR registers, and four scratch locations. These represent the 'total' of registers used in all the other routines.
The 'nop' after the RETFIE 1, is because this was one of the chips with a fault requiring this.
Best Wishes |
|
|
dmitrboristuk
Joined: 26 Sep 2020 Posts: 55
|
|
Posted: Wed Feb 01, 2023 1:25 am |
|
|
Hello. Is it possible to access addresses 0x08 and 0x18 to write your own interrupt handler with different priority levels for pic18 devices.
The standard handler uses a lot of things that I don't need, slowing down the work. This can be seen from the assembler listing. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Wed Feb 01, 2023 3:44 am |
|
|
Yes, just use #INT_GLOBAL.
You just do exactly what I show, except you justify the code between
arriving at the fast handler, and it's exit, to fill 16 bytes. Then have
the low priority handler arrive at what is labelled NXT in my code.
Then don't have the low priority code use the fast return stack.
You do realise that if you use 'FAST', the register saving is removed. So
you can just do your own.
So for INT_GLOBAL:
Code: |
#include <18F25K20.h>
#device ADC=10
#FUSES NOWDT //No Watch Dog Timer
#use delay(crystal=20000000)
#USE RS232(UART1, baud=9600)
int8 W_TEMP;
int8 BSR_TEMP;
int8 STATUS_TEMP;
int8 INT_SCRATCH[10];
#BIT RBIF=getenv("BIT:RBIF")
#BIT TMR2IF=getenv("BIT:TMR2IF")
#BIT TMR2IE=getenv("BIT:TMR2IE")
#BIT RCIF=getenv("BIT:RCIF")
#BIT RCIE=getenv("BIT:RCIE")
#BIT TXIF=getenv("BIT:TXIF")
#BIT TXIE=getenv("BIT:TXIE")
#BYTE FSR0L=getenv("SFR:FSR0L")
#BYTE FSR1L=getenv("SFR:FSR1L")
#BYTE FSR2L=getenv("SFR:FSR2L")
#BYTE FSR0H=getenv("SFR:FSR0H")
#BYTE FSR1H=getenv("SFR:FSR1H")
#BYTE FSR2H=getenv("SFR:FSR2H")
#BYTE BSR=getenv("SFR:BSR")
#BYTE STATUS=getenv("SFR:STATUS")
#byte PORTB=getenv("SFR:PORTB")
void TBE_isr(void);
void RDA_isr(void);
void TIMER2_isr(void); //prototypes for the interrup handlers
#SEPARATE
void quad(void)
{
int B_val;
//handler code for RBIF
//Remember this has to save every register it changes, excwpt W, BSR & STATUS
//fast return stack handles these.
//The saving must be to a separate location to the one used by the slow
//code
//Need to read port B or this cannot clear
B_val=PORTB;
RBIF=FALSE;
#ASM
RETFIE 1 //restore W, BSR & STATUS and return
NOP
#ENDASM
}
#int_global
void myint(void) {
static int INT_scratch[10];
#ASM
//Here for maximum speed, I test the RB interrupt - since it is allways
//enabled, I don't have to test the enable bit
BTFSS RBIF
RETFIE 1 //restore W, BSR & STATUS and return
NOP
#endasm
quad(); //Quadrature handler.
#asm
//Now need to fill space here to 0x18.
NOP
NOP
//This is now the low priority interrupt handler entry point
//Now I save the registers needed for the other interrupt handlers
MOVWF W_TEMP;
MOVFF STATUS,STATUS_TEMP
MOVFF BSR,BSR_TEMP; //save these, since fast return used on high priority
MOVFF FSR0L,INT_scratch
MOVFF FSR0H,INT_scratch+1
MOVFF FSR1L,INT_scratch+2
MOVFF FSR1H,INT_scratch+3
MOVFF FSR2L,INT_scratch+4
MOVFF FSR2H,INT_scratch+5
//Then you need to do every other register your interrupt code can change
//Now some low priority interrupts
BTFSS TMR2IE
GOTO NEXT1
BTFSC TMR2IF
#endasm
TIMER2_isr();
#asm
NEXT1:
//Receive data available
BTFSS RCIE
GOTO NEXT2
BTFSC RCIF
#endasm
RDA_isr();
#asm
NEXT2:
//Transmit buffer empty
BTFSS TXIE
GOTO EXIT
BTFSC TXIF
#endasm
TBE_isr();
#asm
EXIT:
//Restore registers
//Need to do every other register yout code has used here
MOVFF INT_scratch,FSR0L
MOVFF INT_scratch+1,FSR0H
MOVFF INT_scratch+2,FSR1L
MOVFF INT_scratch+3,FSR1H
MOVFF INT_scratch+4,FSR2L
MOVFF INT_scratch+5,FSR2H
MOVFF BSR_TEMP, BSR
MOVF W_TEMP
MOVFF STATUS_TEMP,STATUS
RETFIE 0
NOP //Some chips have a bug requiring this NOP. Put here in case.
#ENDASM
}
//dummy handlers for the three low priority interrupts
void TBE_isr(void)
{
putc('A');
clear_interrupt(INT_TIMER2);
}
void RDA_isr(void)
{
int dummy;
dummy=getc();
clear_interrupt(INT_RDA);
}
void TIMER2_isr(void)
{
clear_interrupt(INT_TIMER2);
}
void main()
{
while(TRUE)
{
//TODO: User Code
}
}
|
|
|
|
dmitrboristuk
Joined: 26 Sep 2020 Posts: 55
|
|
Posted: Wed Feb 01, 2023 5:27 am |
|
|
Ttelmah,
Thank you, I'll give it a try soon.
I also take this opportunity to ask you why the compiler for PIC18 does not use fast commands such as ADDWFC, COMF, DECFSZ, BC, BNC, RETURN, RCALL and others, instead they use constructions from several instructions inherent in PIC16 |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9226 Location: Greensville,Ontario
|
|
Posted: Wed Feb 01, 2023 7:44 am |
|
|
My 'gut' feeling is that CCS programmers used what worked in the past, to get the product 'up and running', reliably. Considering the HUGE numbers of PICs today, trying to 'optimize' code for a handful of PICs would be very time consuming.
As Mr T says ,YOU can modify the code to suit EXACTLY what you want the PIC to do, you don't need to use what CCS supplies. |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Wed Feb 01, 2023 8:36 am |
|
|
Several of the instructions mentioned, they do use. Just perhaps not where
you expect. ADDWFC, DECFSZ BNC, and RETURN I have just found in the
listings from some of my code here. They avoid RCALL, because I think
it makes the calculations very difficult with the optimiser. RETURN they use
regularly. How else do you think they get back from routines?. Remember
though that they will always tend to 'inline' code if they can, and the stack
size on most PIC's is so limited that they need to avoid using it if they can,
so they will substitute a GOTO if they possibly can.
DIY'ing things has become less and less needed. Historically it was essential
for much fast code, but now the optimiser tends to get very close to as
good as you can get. Generally unless interrupt handlers are very limited in
what they do, the number of extra registers saved is very small, and
remember that the saving used is normally shared by all routines, If you
have one that needs to be really fast, then just use the FAST option. |
|
|
temtronic
Joined: 01 Jul 2010 Posts: 9226 Location: Greensville,Ontario
|
|
Posted: Wed Feb 01, 2023 9:01 am |
|
|
also... today's PICs have more memory and run a LOT faster than the original 4MHz ones where we had to cut in assembler and play 'tricks' to get use of every bit of memory and every microsecond of time. |
|
|
dmitrboristuk
Joined: 26 Sep 2020 Posts: 55
|
|
Posted: Thu Feb 02, 2023 12:49 am |
|
|
Ttelmah,
Your code is much faster than the standard one! I want to ask, is it necessary to set the RBIP and IPEN bits to high priority with this approach?
Code: | static int INT_scratch[10]; | for what, it was already above
Another assembler question: how to use the RETURN command in assembler inserts
if you just write it down, it is highlighted in blue (same as RETURN for C). |
|
|
Ttelmah
Joined: 11 Mar 2010 Posts: 19513
|
|
Posted: Thu Feb 02, 2023 1:36 am |
|
|
Return in C, if it is in a function that has been called, is exactly the same
instruction as RETURN in the assembler. The highlighter knows it as a
C keyword, so makes it blue.
Remember though you cannot use a RETURN inside anything that is
in the global interrupt handler (or called from it). The compiler will
substitute a RETFIE if you do.
Yes, you manually have to set the priority. The enable the standard
enable_interrupts instruction will set.
Remember though to look at the assembler listing for all functions you
use inside the interrupt handlers, and you then need to add saving any
registers these use. Even quite basic things will change other registers,
and if the code is going to work correctly you have to be handling these. |
|
|
dmitrboristuk
Joined: 26 Sep 2020 Posts: 55
|
|
Posted: Thu Feb 02, 2023 3:17 am |
|
|
Ttelmah, Thank you, so far no questions |
|
|
|
|
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
|