Dear all,
Does anyone as a good source for a tutorial on software interrupts with AVR micro?
Thanking you
Dear all,
Does anyone as a good source for a tutorial on software interrupts with AVR micro?
Thanking you
This appears in the mega48/88/168 datasheet (and similar text can be found in most of the datasheets):
Observe that, if enabled, the interrupts will trigger even if the INT0 and INT1 or PCINT23..0 pins are configured as outputs. This feature provides a way of generating a software interrupt.
Cliff
I'm not sure I understand the purpose of a software interrupt in an embedded system. Can someone explain their use and benefit?
Well the only use I've ever heard suggested that made some sense was to use them for app to boot or boot to app communications. Say the app is running but the IVSEL means the boot code is handling the vectors then the app could force a s/w interrupt and get the boot to service the request. (or that could all be the other way round).
But otherwise, like you, I'm hard pushed to think of a reason (unless it's a "college assignment" perhaps just being done as a learning exercise?)
Maybe someone else knows of some other reason for them? (the OP perhaps as he wants to use them?)
Cliff
That makes sense when two sets of code linked and loaded separately need some absolute base for communicating.
It just seems to come up often for something that has such little use and other methods.
That makes sense when two sets of code linked and loaded separately need some absolute base for communicating.It just seems to come up often for something that has such little use and other methods.
A software interrupt can be a handy way to make an operating system call, especially with processors (not the AVR) that support interrupt and execution priorities. The interrupter/caller doesn't need to know where the request handler is. The interrupt can also start saving context and setting a new priority level for servicing the caller's request.
...especially with processors (not the AVR) that support ...
The interrupter/caller doesn't need to know where the request handler is.
So, if I'm going to use the vector at address 0x8 for this SWI, then in the code where it is invoked is there any difference to force the interrupt, or RCALL/CALL 0x08, or RJMP/JUMP if it is not going to return normally? Perhaps a word or cycle one way or the other, and that could well be cancelled-out with the setup/enable sequences. An ISR is pretty much a void (void) function anyway.
Well, I didn't write that using software interrupts made sense on an AVR. It lacks a tolerable few things that might make them more useful.
I think the datasheet is just pointing out that the voltage on a pin (causing an interrupt) can be just as easily generated internally as externally and to explain this "oddity" they wrap it up as a "feature" and call it "software interrupts" ;)
In practice I've only seen one use over the years NMI on x86 for breakpoint/single-stepping. Not really applicable on flash-based AVRs.
I searched out the prior discussions on software interrupts, and they turned out pretty much like this one. I can't disagree that they would be a valuable feature--once someone gives me the case where they have a use! The prior "examples" seem to be task switching, or building some kind of alternate context. Again, you can get there any number of ways, and whether you come back again is up to you[r code].
I think they write it into the datasheet so nobody accidently triggers an interrupt. And then there's always the it's not a bug, it's a feature ...
I know the original 68000 Macintosh used a software interrupt to implement quick draw. The application pushed all the command parameters on the stack, and did a software interrupt to have the ROM take over.
But, for the an AVR, the only application would be the boot loader thing mentioned earlier. It just doesn't seem practical.
I wish the OP would chime in and tell us his problem rather than his solution.
Well I remember when I worked on the Sinclair Spectrum firmware we used the Z80's RST instructions - which are a bit like 68000 SWIs - to call to the most common routines (like printf etc) as it was simply a shorter opcode sequence than a full call and that 16K of firmware was completely "stuffed"
Cliff
I've seen arguments that it would be beneficial to restrict the situations in which interrupts are allowed to be inhibited - specifically, grant that priviledge to a specified few "trusted" routines in the OS or kernel, and use hardware means to enforce an absolute prohibition preventing untrustworthy code (any arbitrary user-supplied software) from being allowed to arbitrarily turn the global interrupt mask on and off.
But, because user-level applications can have a legitimate need for constructs such as semaphores, and since such constructs require the use of atomicity, the OS kernel provides a series of system calls implemented as software interrupts, which force the CPU into a privileged mode of operation in which trusted code (the OS itself) is allowed to make judicious use of inhibited interrupts in the act of servicing the user application's request.
Such a system is admittedly meaningless in an AVR because there is no hardware distinction between "privileged" and "user" level code.
One possible use that somes to mind, if an external interrupt is expected to occur after a certain period but doesn't, then a timeout routine can invoke the ISR.
Or part of a built-in test can "simulate" the external interrupt to measure the correct response.
The first use that came to my mind was, of course, the OS call, since that's the way M$ DOS/Windoze is run, as well as others.
However, one other use came to mind -- they might be useful for implementing exception throwing in C++. Errrm, or maybe not.
I'm not looking to volunteer to do that, by the way. :wink:
Stu
Ah so the rules then become "sure you can use C++ but you need to tie up one of your IO pins to do it" ? ;)
...then a timeout routine can invoke the ISR.
A SWI for your example would invoke the SWI ISR, not the "lost/missing signal" ISR so the SWI is going to go there anyway! I guess one could have multiple vectors going to the same ISR.
Again, can't we already do that without a "software interrupt"?
CLI
CALL isr
A SWI for your example would invoke the SWI ISR, not the "lost/missing signal" ISR so the SWI is going to go there anyway!
EDIT: Wrote SEI meant CLI (in code).
Triggering an SWI in main code doesn't seem much different from a direct CALL (leaving the linking issue aside, which can also be solved by a function table).
Triggering an SWI from within an ISR on the other hand is different issue, because the current ISR is allowed to complete its operation before any other interrupt (including the SWI) is serviced.
Lets say you bit bang an interface and timing is critical. You detect the EOF pattern in your datastream and now have a valid byte that should get processed immediately, but you have to continue to bit bang the interface to fulfill the protocol.
Triggering an SWI in this scenario looks like the most efficient way of doing that. It also clears the interrupt flag for the original ISR as soon as possible.
Mhh, I wonder if I ever get into a situation where this might turn out to be useful ...
But ISRs run with interrupts disabled so you can't use an interrupt in an interrupt. Or were you suggesting to SEI inside the ISR - seems very dangerous
But ISRs run with interrupts disabled so you can't use an interrupt in an interrupt. Or were you suggesting to SEI inside the ISR - seems very dangerous
Yes interrupts are disabled during an ISR, but the interrupt flag gets set if an interrupt is triggered. And once you return from your current ISR the AVR will service any pending interrupt, which would be the SWI.
I'm probably the last person who should be stepping into this, but doesn't the swi just effectively become a 'cli();my_swi();'? So instead of a pin toggle, you do those 2 instructions (cli not needed in isr, though).
Maybe it would save some pushing/popping in an isr if instead of having to call that swi function from the isr, you just do a pin toggle.
Anyway, I have lots of 'features' on my avr's I never use, but at least they are there if I need them.
(I wonder if there is any Atmel application note that makes use of an swi)
Curt,
Well so far the idea from Megatorr is the only thing I can see that couldn't be achieved in "normal" C. In that you can effectively queue the next interrupt to be serviced on the RETI from the current ISR. One opcode (I think) will be excuted from the mainline and then it'll immediately go into the "soft interrupt". I'm not sure how else this could be achieved - so there maybe IS some point to it after all (but I still think the existence in the datasheet is simply a case of "oops, a consequence of the hardware implementation is this - so let's dress it up and call it a feature")
Cliff
I have an ISR which runs very frequently, so to keep it small and fast, it's coded in asm. From time to time, the ISR has to call another function. This function is fairly complex, with a large case statement so is best left coded in C.So, to avoid having to push and pop every register on the planet in the small ISR, I have implemented the function as an external interrupt ISR, and simply trigger it from the small ISR with a single sbi instruction. Works a treat. 8)
Michael,
I fear you've missed the point of the very clever idea Colin was suggesting.
He's exchanging:
ISR() { //lots of pushes because of CALL invoke_function(); //lot of pops }
for
ISR() { // few pushes invoke ISR2() soft int // few pops } ISR2() { //few pushes code of "invoke_function()" //few pops }
I thought this sounded like a really clever idea to call a function from an ISR without the huge push/pop overhead.
Maybe it would save some pushing/popping in an isr if instead of having to call that swi function from the isr, you just do a pin toggle.
I can imagine some purposes of software interrupts:
1.If you want to test the fragment of code which is evoked by external interrupt. You can just sent a value into the port and check how the interrupt routine will be working (at once you have disabled interrupts, proper port value, and its shorter than asm call interrupt).
2.Easy implementation of some protocols, for example 1-wire. If you want to send something first you need to pull down the bus for about 1micros. So all you have to do is to pull down appropriate port bit, and for example in cary flag put the bit you want to send. This evokes interrupt, which will take care of the rest of the bus timing. You can do it using out and call, but using software interrupts you just save the space required by call command.
3.Bus monitoring. Your application sends/receives some transmission, and another part of application traces the activity on the port. Its easier to use for that software interrupts, because you can easy turn on/off bus tracing without complicating actual sending routine.
4.Most important difference is when program will work with interrupts disabled, making software interrupt with disabled interrupt flag evokes the interrupt AFTER sei(), not immediately . So we can evoke interrupt routine after finishing more important things.
Michael,I fear you've missed the point of the very clever idea Colin was suggesting.
He's exchanging:
ISR() { //lots of pushes because of CALL invoke_function(); //lot of pops }
forISR() { // few pushes invoke ISR2() soft int // few pops } ISR2() { //few pushes code of "invoke_function()" //few pops }I thought this sounded like a really clever idea to call a function from an ISR without the huge push/pop overhead.
Michael,
No no I'm not sure if you are being deliberately obtuse or not but we're talking about C. I added the "// lost of pushes" comment to imply that "behind" the C there will be a lot of pushes/pops generated by the C compiler that you cannot control. This is the inevitable consequence of invoking another function from within an ISR. Colin's idea avoids this happening. Without resorting to implementing the ISRs in assembler (which is not what was being discussed here) I cannot think of another way of achieveing the same result in C alone.
And of course I didn't include the obvious SEI as this was PSUEDO-CODE. Clearly there's no such macro as ISR2() - again this was PSUEDO-CODE to differentiate the two. But I kind of wonder if you are just a troll trying to wind me up or not (in which case I regret rising to your bait and typing this post!)
The superfreak needs more ketchup.
Michael,No no I'm not sure if you are being deliberately obtuse or not but we're talking about C. I added the "// lost of pushes" comment to imply that "behind" the C there will be a lot of pushes/pops generated by the C compiler that you cannot control. This is the inevitable consequence of invoking another function from within an ISR. Colin's idea avoids this happening. Without resorting to implementing the ISRs in assembler (which is not what was being discussed here) I cannot think of another way of achieveing the same result in C alone.
I have an ISR which runs very frequently, so to keep it small and fast, it's coded in asm. From time to time, the ISR has to call another function. This function is fairly complex, with a large case statement so is best left coded in C.So, to avoid having to push and pop every register on the planet in the small ISR, I have implemented the function as an external interrupt ISR, and simply trigger it from the small ISR with a single sbi instruction. Works a treat.
And of course I didn't include the obvious SEI as this was PSUEDO-CODE. Clearly there's no such macro as ISR2() - again this was PSUEDO-CODE to differentiate the two. But I kind of wonder if you are just a troll trying to wind me up or not (in which case I regret rising to your bait and typing this post!)
Michael,
OK, I'll bite - what does the fact that my fictious ISR() might be written in asm rather than C have to do with it? Colin said he was calling a large and complex C function so at the time of writing the ISR he faced exactly the same conundrum as the C compiler of not knowing which registers might be clobbered within that C function so, whether ISR() is implemented in Asm or C either the programmer (Colin) or the C compiler can't "see" what's going on inside invoke_function() so both have no option to protectively save the complete register state BEFORE the call into the unknown function contents.
In the software interrupt case it's when the compiler comes to compile ISR2() that it can "see" what registers are being used WITHIN it and it can put just the necessary protection on the prologue and epilogue of that ISR.
In what sense does this not save PUSH/POPs ?
No no I'm not sure if you are being deliberately obtuse or not but we're talking about C.
Michael,OK, I'll bite - what does the fact that my fictious ISR() might be written in asm rather than C have to do with it?
Colin said he was calling a large and complex C function so at the time of writing the ISR he faced exactly the same conundrum as the C compiler of not knowing which registers might be clobbered within that C function so, whether ISR() is implemented in Asm or C either the programmer (Colin) or the C compiler can't "see" what's going on inside invoke_function() so both have no option to protectively save the complete register state BEFORE the call into the unknown function contents.In the software interrupt case it's when the compiler comes to compile ISR2() that it can "see" what registers are being used WITHIN it and it can put just the necessary protection on the prologue and epilogue of that ISR.
In what sense does this not save PUSH/POPs ?
The essential usefulness of interrupts, in my opinion, comes from the fact that they are asynchronous to other program execution. They're not some kind of coverup or conspiracy or afterthought by Atmel, they are a legitimate and useful feature, supported in many processor families.
This is true of external and hardware generated interrupts as well as those generated by software.
If your program needs to execute a task asynchronously - and the task isn't supported directly by hardware that generates an interrupt, then you will need a software interrupt.
You might say "but I can just use a periodic timer interrupt to run my task." In this case, your software is no longer event driven (you are doing a form of polling), which might be a serious issue for you, especially if you are doing low power design.
If you could imagine a FIFO into which your task would enqueue data, perhaps received from a serial port, then that data would be dequeued from the fifo, but at a rate slower than the input (maybe you're relaying the data out to a very slow network) -
The question is: how could you continue to empty the FIFO while still enqueueing data, only using a single task? Your FIFO would shortly fill up, and your task would be stuck in a polling loop that would require some asynchronous signal to break out of. Since you most likely don't have a preemptive OS, this is where a software interrupt would come in - you would have a separate task, triggered initially when data was enqueued, and which ran, triggered by an SWI for every element in the FIFO, until emptied.
I agree that for most simple stuff, using a software interrupt is not necessary, but there are design problems where they are the only effective solution.
If you want a software interrupt, simple enable the SPM_RDY interrupt.
During program execution this interrupt fires instantly, as it was enabled.
Peter
My point was that to call a C function from within an ISR (whether written in C or ASM) requires that the ISR pushes and pops everything, because the compiler (or programmer) doesn't know what registers will be used by the C function. This carries a time penalty which is undesirable in a frequently running ISR. The key point is that the C function is called infrequently. My technique results in a time advantage in this circumstance, not a memory advantage.
ISR(fred) // this will get a warning { // something you rarely need } ISR(timer0_vect) { // whatever you need if(rare_event) fred(); // whatever else you need }
This works even if you need all your pin change interrupt handlers for other things.
If fred() compiles to more than one instruction, replace it with asm volatile ("call fred").
With the software interrupt, you might also need to inhibit optimization by making some variables volatile.
A call to an interrupt handler declared as such should compile to a single instruction
Emphasis added
Quote:
A call to an interrupt handler declared as such should compile to a single instruction
Yes but the "time penalty" is all the push/pop's around it that are/aren't needed.
Michael,
Then sorry to labour the point but I'm obviously missing some point you are trying to make. Here's a piece of test code:
#include#include //#define MICHAELS_SUGGESTION #ifdef MICHAELS_SUGGESTION void my_fn(void) __attribute__ ((signal, used, externally_visible)); #endif void my_fn(void) { uint32_t fred; fred = PINA + ((uint32_t)PINB << 24); fred *= 3; fred >>= 16; PORTB = fred & 0xFF; PORTA = fred >> 24; } ISR(INT0_vect) { my_fn(); } int main(void) { while(1); return 0; }
Now if I don't define MICHAELS_SUGGESTION then the code generated (built -Os) is:
void my_fn(void) { 92: 89 b3 in r24, 0x19 ; 25 94: 26 b3 in r18, 0x16 ; 22 96: 33 27 eor r19, r19 98: 44 27 eor r20, r20 9a: 55 27 eor r21, r21 9c: 52 2f mov r21, r18 9e: 44 27 eor r20, r20 a0: 33 27 eor r19, r19 a2: 22 27 eor r18, r18 a4: 28 0f add r18, r24 a6: 31 1d adc r19, r1 a8: 41 1d adc r20, r1 aa: 51 1d adc r21, r1 uint32_t fred; fred = PINA + ((uint32_t)PINB << 24); fred *= 3; ac: da 01 movw r26, r20 ae: c9 01 movw r24, r18 b0: 88 0f add r24, r24 b2: 99 1f adc r25, r25 b4: aa 1f adc r26, r26 b6: bb 1f adc r27, r27 b8: 82 0f add r24, r18 ba: 93 1f adc r25, r19 bc: a4 1f adc r26, r20 be: b5 1f adc r27, r21 fred >>= 16; c0: cd 01 movw r24, r26 c2: aa 27 eor r26, r26 c4: bb 27 eor r27, r27 PORTB = fred & 0xFF; c6: 88 bb out 0x18, r24 ; 24 PORTA = fred >> 24; c8: 8b 2f mov r24, r27 ca: 99 27 eor r25, r25 cc: aa 27 eor r26, r26 ce: bb 27 eor r27, r27 d0: 8b bb out 0x1b, r24 ; 27 d2: 08 95 ret 000000d4 <__vector_1>: } ISR(INT0_vect) { d4: 1f 92 push r1 d6: 0f 92 push r0 d8: 0f b6 in r0, 0x3f ; 63 da: 0f 92 push r0 dc: 11 24 eor r1, r1 de: 2f 93 push r18 e0: 3f 93 push r19 e2: 4f 93 push r20 e4: 5f 93 push r21 e6: 6f 93 push r22 e8: 7f 93 push r23 ea: 8f 93 push r24 ec: 9f 93 push r25 ee: af 93 push r26 f0: bf 93 push r27 f2: ef 93 push r30 f4: ff 93 push r31 my_fn(); f6: cd df rcall .-102 ; 0x92f8: ff 91 pop r31 fa: ef 91 pop r30 fc: bf 91 pop r27 fe: af 91 pop r26 100: 9f 91 pop r25 102: 8f 91 pop r24 104: 7f 91 pop r23 106: 6f 91 pop r22 108: 5f 91 pop r21 10a: 4f 91 pop r20 10c: 3f 91 pop r19 10e: 2f 91 pop r18 110: 0f 90 pop r0 112: 0f be out 0x3f, r0 ; 63 114: 0f 90 pop r0 116: 1f 90 pop r1 118: 18 95 reti 0000011a : } int main(void) { 11a: ff cf rjmp .-2 ; 0x11a
while if I make the #define (so that my_fn is declared as an ISR in the same way that the ISR() macro would define an ISR) I get:
void my_fn(void) { 92: 1f 92 push r1 94: 0f 92 push r0 96: 0f b6 in r0, 0x3f ; 63 98: 0f 92 push r0 9a: 11 24 eor r1, r1 9c: 2f 93 push r18 9e: 3f 93 push r19 a0: 4f 93 push r20 a2: 5f 93 push r21 a4: 8f 93 push r24 a6: 9f 93 push r25 a8: af 93 push r26 aa: bf 93 push r27 uint32_t fred; fred = PINA + ((uint32_t)PINB << 24); ac: 89 b3 in r24, 0x19 ; 25 ae: 26 b3 in r18, 0x16 ; 22 b0: 33 27 eor r19, r19 b2: 44 27 eor r20, r20 b4: 55 27 eor r21, r21 b6: 52 2f mov r21, r18 b8: 44 27 eor r20, r20 ba: 33 27 eor r19, r19 bc: 22 27 eor r18, r18 be: 28 0f add r18, r24 c0: 31 1d adc r19, r1 c2: 41 1d adc r20, r1 c4: 51 1d adc r21, r1 fred *= 3; c6: da 01 movw r26, r20 c8: c9 01 movw r24, r18 ca: 88 0f add r24, r24 cc: 99 1f adc r25, r25 ce: aa 1f adc r26, r26 d0: bb 1f adc r27, r27 d2: 82 0f add r24, r18 d4: 93 1f adc r25, r19 d6: a4 1f adc r26, r20 d8: b5 1f adc r27, r21 fred >>= 16; da: cd 01 movw r24, r26 dc: aa 27 eor r26, r26 de: bb 27 eor r27, r27 PORTB = fred & 0xFF; e0: 88 bb out 0x18, r24 ; 24 PORTA = fred >> 24; e2: 8b 2f mov r24, r27 e4: 99 27 eor r25, r25 e6: aa 27 eor r26, r26 e8: bb 27 eor r27, r27 ea: 8b bb out 0x1b, r24 ; 27 ec: bf 91 pop r27 ee: af 91 pop r26 f0: 9f 91 pop r25 f2: 8f 91 pop r24 f4: 5f 91 pop r21 f6: 4f 91 pop r20 f8: 3f 91 pop r19 fa: 2f 91 pop r18 fc: 0f 90 pop r0 fe: 0f be out 0x3f, r0 ; 63 100: 0f 90 pop r0 102: 1f 90 pop r1 104: 18 95 reti 00000106 <__vector_1>: } ISR(INT0_vect) { 106: 1f 92 push r1 108: 0f 92 push r0 10a: 0f b6 in r0, 0x3f ; 63 10c: 0f 92 push r0 10e: 11 24 eor r1, r1 110: 2f 93 push r18 112: 3f 93 push r19 114: 4f 93 push r20 116: 5f 93 push r21 118: 6f 93 push r22 11a: 7f 93 push r23 11c: 8f 93 push r24 11e: 9f 93 push r25 120: af 93 push r26 122: bf 93 push r27 124: ef 93 push r30 126: ff 93 push r31 my_fn(); 128: b4 df rcall .-152 ; 0x9212a: ff 91 pop r31 12c: ef 91 pop r30 12e: bf 91 pop r27 130: af 91 pop r26 132: 9f 91 pop r25 134: 8f 91 pop r24 136: 7f 91 pop r23 138: 6f 91 pop r22 13a: 5f 91 pop r21 13c: 4f 91 pop r20 13e: 3f 91 pop r19 140: 2f 91 pop r18 142: 0f 90 pop r0 144: 0f be out 0x3f, r0 ; 63 146: 0f 90 pop r0 148: 1f 90 pop r1 14a: 18 95 reti 0000014c : } int main(void) { 14c: ff cf rjmp .-2 ; 0x14c
If I now change it to Colin's ISR invoked from ISR (except that I'm not bothering with the code to enable the soft int):
#include#include ISR(INT1_vect) { uint32_t fred; fred = PINA + ((uint32_t)PINB << 24); fred *= 3; fred /= 22; PORTB = fred & 0xFF; PORTA = fred >> 24; } ISR(INT0_vect) { sei(); PORTD |= (1<<PD3); } int main(void) { while(1); return 0; }
I get:
00000092 <__vector_2>: #include#include ISR(INT1_vect) { 92: 1f 92 push r1 94: 0f 92 push r0 96: 0f b6 in r0, 0x3f ; 63 98: 0f 92 push r0 9a: 11 24 eor r1, r1 9c: 2f 93 push r18 9e: 3f 93 push r19 a0: 4f 93 push r20 a2: 5f 93 push r21 a4: 8f 93 push r24 a6: 9f 93 push r25 a8: af 93 push r26 aa: bf 93 push r27 uint32_t fred; fred = PINA + ((uint32_t)PINB << 24); ac: 89 b3 in r24, 0x19 ; 25 ae: 26 b3 in r18, 0x16 ; 22 b0: 33 27 eor r19, r19 b2: 44 27 eor r20, r20 b4: 55 27 eor r21, r21 b6: 52 2f mov r21, r18 b8: 44 27 eor r20, r20 ba: 33 27 eor r19, r19 bc: 22 27 eor r18, r18 be: 28 0f add r18, r24 c0: 31 1d adc r19, r1 c2: 41 1d adc r20, r1 c4: 51 1d adc r21, r1 fred *= 3; c6: da 01 movw r26, r20 c8: c9 01 movw r24, r18 ca: 88 0f add r24, r24 cc: 99 1f adc r25, r25 ce: aa 1f adc r26, r26 d0: bb 1f adc r27, r27 d2: 82 0f add r24, r18 d4: 93 1f adc r25, r19 d6: a4 1f adc r26, r20 d8: b5 1f adc r27, r21 fred >>= 16; da: cd 01 movw r24, r26 dc: aa 27 eor r26, r26 de: bb 27 eor r27, r27 PORTB = fred & 0xFF; e0: 88 bb out 0x18, r24 ; 24 PORTA = fred >> 24; e2: 8b 2f mov r24, r27 e4: 99 27 eor r25, r25 e6: aa 27 eor r26, r26 e8: bb 27 eor r27, r27 ea: 8b bb out 0x1b, r24 ; 27 ec: bf 91 pop r27 ee: af 91 pop r26 f0: 9f 91 pop r25 f2: 8f 91 pop r24 f4: 5f 91 pop r21 f6: 4f 91 pop r20 f8: 3f 91 pop r19 fa: 2f 91 pop r18 fc: 0f 90 pop r0 fe: 0f be out 0x3f, r0 ; 63 100: 0f 90 pop r0 102: 1f 90 pop r1 104: 18 95 reti 00000106 <__vector_1>: } ISR(INT0_vect) { 106: 1f 92 push r1 108: 0f 92 push r0 10a: 0f b6 in r0, 0x3f ; 63 10c: 0f 92 push r0 10e: 11 24 eor r1, r1 sei(); 110: 78 94 sei PORTD |= (1<<PD3); 112: 93 9a sbi 0x12, 3 ; 18 114: 0f 90 pop r0 116: 0f be out 0x3f, r0 ; 63 118: 0f 90 pop r0 11a: 1f 90 pop r1 11c: 18 95 reti 0000011e : } int main(void) { 11e: ff cf rjmp .-2 ; 0x11e
Unless my eyes deceive me the latter is smaller/faster than either of the first two. Now what point is it that you are making that I'm clearly missing?
For those of us who have seen the compiler output because we wondered why our byte count was so bloated, it was clear what you were talking about -
It looks like the misunderstanding has to do with the word "call". I think Michael was trying to say that there would be no pushes and pops around the CALL to the interrupt handler, not the handler itself.
Then sorry to labour the point but I'm obviously missing some point you are trying to make.
Here's a piece of test code:#include#include //#define MICHAELS_SUGGESTION #ifdef MICHAELS_SUGGESTION void my_fn(void) __attribute__ ((signal, used, externally_visible)); #endif void my_fn(void) { uint32_t fred; fred = PINA + ((uint32_t)PINB << 24); fred *= 3; fred >>= 16; PORTB = fred & 0xFF; PORTA = fred >> 24; } ISR(INT0_vect) { my_fn(); } int main(void) { while(1); return 0; }
Here is some more test code.
#include#include int dest, src; ISR(PCINT0_vect) { dest=src; } ISR(fred) { dest=src; } ISR(TIMER0_COMPA_vect) { fred(); } ISR(TIMER0_COMPB_vect) { asm volatile ("CALL fred"); } ISR(TIMER2_COMPA_vect) { // should compile SBI, RETI PORTB|=0x01; } ISR(TIMER2_COMPB_vect) { // should compile to RETI } int main() { while(1) {} }
Here is the result. See interleaved comments.
isr_test.elf: file format elf32-avr Sections: Idx Name Size VMA LMA File off Algn Flags 0 .text 0000018c 00000000 00000000 00000074 2**1 CONTENTS, ALLOC, LOAD, READONLY, CODE 1 .bss 00000004 00800100 00800100 00000200 2**0 ALLOC 2 .stab 00000378 00000000 00000000 00000200 2**2 CONTENTS, READONLY, DEBUGGING 3 .stabstr 00000071 00000000 00000000 00000578 2**0 CONTENTS, READONLY, DEBUGGING 4 .debug_aranges 00000020 00000000 00000000 000005e9 2**0 CONTENTS, READONLY, DEBUGGING 5 .debug_pubnames 00000082 00000000 00000000 00000609 2**0 CONTENTS, READONLY, DEBUGGING 6 .debug_info 00000111 00000000 00000000 0000068b 2**0 CONTENTS, READONLY, DEBUGGING 7 .debug_abbrev 00000076 00000000 00000000 0000079c 2**0 CONTENTS, READONLY, DEBUGGING 8 .debug_line 000000f4 00000000 00000000 00000812 2**0 CONTENTS, READONLY, DEBUGGING 9 .debug_frame 00000080 00000000 00000000 00000908 2**2 CONTENTS, READONLY, DEBUGGING 10 .debug_str 000000c1 00000000 00000000 00000988 2**0 CONTENTS, READONLY, DEBUGGING Disassembly of section .text: 00000000 <__vectors>: 0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end> 4: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 8: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> c: 0c 94 53 00 jmp 0xa6 ; 0xa6 <__vector_3> 10: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 14: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 18: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 1c: 0c 94 af 00 jmp 0x15e ; 0x15e <__vector_7> 20: 0c 94 ba 00 jmp 0x174 ; 0x174 <__vector_8> 24: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 28: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 2c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 30: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 34: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 38: 0c 94 7f 00 jmp 0xfe ; 0xfe <__vector_14> 3c: 0c 94 a3 00 jmp 0x146 ; 0x146 <__vector_15> 40: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 44: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 48: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 4c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 50: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 54: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 58: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 5c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 60: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 64: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt> 00000068 <__ctors_end>: 68: 11 24 eor r1, r1 6a: 1f be out 0x3f, r1 ; 63 6c: cf ef ldi r28, 0xFF ; 255 6e: d4 e0 ldi r29, 0x04 ; 4 70: de bf out 0x3e, r29 ; 62 72: cd bf out 0x3d, r28 ; 61 00000074 <__do_copy_data>: 74: 11 e0 ldi r17, 0x01 ; 1 76: a0 e0 ldi r26, 0x00 ; 0 78: b1 e0 ldi r27, 0x01 ; 1 7a: ec e8 ldi r30, 0x8C ; 140 7c: f1 e0 ldi r31, 0x01 ; 1 7e: 02 c0 rjmp .+4 ; 0x84 <.do_copy_data_start> 00000080 <.do_copy_data_loop>: 80: 05 90 lpm r0, Z+ 82: 0d 92 st X+, r0 00000084 <.do_copy_data_start>: 84: a0 30 cpi r26, 0x00 ; 0 86: b1 07 cpc r27, r17 88: d9 f7 brne .-10 ; 0x80 <.do_copy_data_loop> 0000008a <__do_clear_bss>: 8a: 11 e0 ldi r17, 0x01 ; 1 8c: a0 e0 ldi r26, 0x00 ; 0 8e: b1 e0 ldi r27, 0x01 ; 1 90: 01 c0 rjmp .+2 ; 0x94 <.do_clear_bss_start> 00000092 <.do_clear_bss_loop>: 92: 1d 92 st X+, r1 00000094 <.do_clear_bss_start>: 94: a4 30 cpi r26, 0x04 ; 4 96: b1 07 cpc r27, r17 98: e1 f7 brne .-8 ; 0x92 <.do_clear_bss_loop> 9a: 0e 94 c4 00 call 0x188 ; 0x1889e: 0c 94 c5 00 jmp 0x18a ; 0x18a <_exit> 000000a2 <__bad_interrupt>: a2: 0c 94 00 00 jmp 0 ; 0x0 <__vectors> 000000a6 <__vector_3>: int dest, src;
The first two only need one register, but avr-gcc saves more.
ISR(PCINT0_vect) { a6: 1f 92 push r1 a8: 0f 92 push r0 aa: 0f b6 in r0, 0x3f ; 63 ac: 0f 92 push r0 ae: 11 24 eor r1, r1 b0: 8f 93 push r24 b2: 9f 93 push r25 dest=src; b4: 80 91 02 01 lds r24, 0x0102 b8: 90 91 03 01 lds r25, 0x0103 bc: 90 93 01 01 sts 0x0101, r25 c0: 80 93 00 01 sts 0x0100, r24 c4: 9f 91 pop r25 c6: 8f 91 pop r24 c8: 0f 90 pop r0 ca: 0f be out 0x3f, r0 ; 63 cc: 0f 90 pop r0 ce: 1f 90 pop r1 d0: 18 95 reti 000000d2: } ISR(fred) { d2: 1f 92 push r1 d4: 0f 92 push r0 d6: 0f b6 in r0, 0x3f ; 63 d8: 0f 92 push r0 da: 11 24 eor r1, r1 dc: 8f 93 push r24 de: 9f 93 push r25 dest=src; e0: 80 91 02 01 lds r24, 0x0102 e4: 90 91 03 01 lds r25, 0x0103 e8: 90 93 01 01 sts 0x0101, r25 ec: 80 93 00 01 sts 0x0100, r24 f0: 9f 91 pop r25 f2: 8f 91 pop r24 f4: 0f 90 pop r0 f6: 0f be out 0x3f, r0 ; 63 f8: 0f 90 pop r0 fa: 1f 90 pop r1 fc: 18 95 reti 000000fe <__vector_14>: }
avr-gcc has the machine code for fred, but it saves more registers than necessary.
That is why I suggested inline assembler.
ISR(TIMER0_COMPA_vect) { fe: 1f 92 push r1 100: 0f 92 push r0 102: 0f b6 in r0, 0x3f ; 63 104: 0f 92 push r0 106: 11 24 eor r1, r1 108: 2f 93 push r18 10a: 3f 93 push r19 10c: 4f 93 push r20 10e: 5f 93 push r21 110: 6f 93 push r22 112: 7f 93 push r23 114: 8f 93 push r24 116: 9f 93 push r25 118: af 93 push r26 11a: bf 93 push r27 11c: ef 93 push r30 11e: ff 93 push r31 fred(); 120: 0e 94 69 00 call 0xd2 ; 0xd2124: ff 91 pop r31 126: ef 91 pop r30 128: bf 91 pop r27 12a: af 91 pop r26 12c: 9f 91 pop r25 12e: 8f 91 pop r24 130: 7f 91 pop r23 132: 6f 91 pop r22 134: 5f 91 pop r21 136: 4f 91 pop r20 138: 3f 91 pop r19 13a: 2f 91 pop r18 13c: 0f 90 pop r0 13e: 0f be out 0x3f, r0 ; 63 140: 0f 90 pop r0 142: 1f 90 pop r1 144: 18 95 reti 00000146 <__vector_15>: }
With inline assembler, avr-gcc still saves more registers than necessary,
but the boilerplate is down to that for an empty interrupt handler.
ISR(TIMER0_COMPB_vect) { 146: 1f 92 push r1 148: 0f 92 push r0 14a: 0f b6 in r0, 0x3f ; 63 14c: 0f 92 push r0 14e: 11 24 eor r1, r1 asm volatile ("CALL fred"); 150: 0e 94 69 00 call 0xd2 ; 0xd2154: 0f 90 pop r0 156: 0f be out 0x3f, r0 ; 63 158: 0f 90 pop r0 15a: 1f 90 pop r1 15c: 18 95 reti 0000015e <__vector_7>: }
Slower, but one word smaller than the previous.
Had I used an RCALL, it wouldn't be smaller either.
ISR(TIMER2_COMPA_vect) { 15e: 1f 92 push r1 160: 0f 92 push r0 162: 0f b6 in r0, 0x3f ; 63 164: 0f 92 push r0 166: 11 24 eor r1, r1 // should compile SBI, RETI PORTB|=0x01; 168: 28 9a sbi 0x05, 0 ; 5 16a: 0f 90 pop r0 16c: 0f be out 0x3f, r0 ; 63 16e: 0f 90 pop r0 170: 1f 90 pop r1 172: 18 95 reti 00000174 <__vector_8>: }
An empty interrupt handler really doesn't need any saving and restoring.
ISR(TIMER2_COMPB_vect) { 174: 1f 92 push r1 176: 0f 92 push r0 178: 0f b6 in r0, 0x3f ; 63 17a: 0f 92 push r0 17c: 11 24 eor r1, r1 17e: 0f 90 pop r0 180: 0f be out 0x3f, r0 ; 63 182: 0f 90 pop r0 184: 1f 90 pop r1 186: 18 95 reti 00000188: // should compile to RETI } int main() { 188: ff cf rjmp .-2 ; 0x188 0000018a <_exit>: 18a: ff cf rjmp .-2 ; 0x18a <_exit>
fred has the additional advantage of not using up an interrupt.
fred has the additional advantage of not using up an interrupt.
Unless I'm mistaken (odds not in my favor), making 'fred' into an INT0/INT1/PCINTn isr, and simply toggle a pin to trigger it, would take care of the above problems. 'fred' could be 'triggered' anywhere, without having to worry about where it was triggered from.
I think.
Quote:Not that I understand any of this anymore, but making 'fred' a normal function that takes on the properties of an interrupt, you have a few things to consider. 'fred' now has no priority over any interrupt, which may or may not be important (but assume it is since we are talking about software 'interrupts' and not software 'functions'). And since 'fred' is now an 'interrupt' function, you get a 'reti' at the end. So if I wanted 'fred' to run from an interrupt (call), you will get irq's turned back on at the end of 'fred'. I haven't thought it all through, but I suspect that may be 'dangerous' unless its carefully thought out. Having that 'reti' also prevents using 'fred' inside any code that has irq's turned off (although a cli() right after the call would take care of it).fred has the additional advantage of not using up an interrupt.
Unless I'm mistaken (odds not in my favor), making 'fred' into an INT0/INT1/PCINTn isr, and simply toggle a pin to trigger it, would take care of the above problems. 'fred' could be 'triggered' anywhere, without having to worry about where it was triggered from.
@Hangaround nailed it.
My serial buffering ISR detects end-of-packet, and needs to tell my code (which is 100% flat-out doing slow screen IO) to stop what it's doing and process the packet ASAP - which can't be done inside the ISR itself because the process takes way too much time; software interrupt lets my hardware ISR end so it's free to handle the next byte this way...
Eh? Surely you just move execution from one ISR to another? You still take as much time within ISRs to handle it. (in fact slightly more as there's the overhead of getting from one ISR to another).
I apologize for digging up such an ancient thread, but I ran into a similar problem--which seems to have a similar answer of calling an ISR to run slow (non blocking) code from within an ISR that needs to be fast/small....
Anyone who has disassembled code with an ISR with a simple c call of another function has seen this push/pop issue and besides we need to get those global ints re-enabled as fast as possible....
Eh? Surely you just move execution from one ISR to another? You still take as much time within ISRs to handle it. (in fact slightly more as there's the overhead of getting from one ISR to another).
Ah but you may be forgetting about the ISR_NOBLOCK for the second ISR so that the second isr can be interrupted while your larger bits of code can still be triggered by the original ISR....
Would this allow us to have our "cake" and eat it too?
I am going to try this solution with the
SPM_RDY
You can easily run out of ram with nested interrupts. You have been warned! The general solution is to use a RTOS.
All a "software interrupt" will get you is to bypass the caller-side overhead.
It will get you the ISR overhead.
Some registers will be saved and restored by both ISRs.
Replacing the software interrupt's ISR with an ordinary
static inline function might get you the best of all worlds.
If said function is called more than once, code-bloat is likely.
Note that an ordinary call from an ISR to another ISR
could result in the same register being saved and restored three times:
once each by the ISR epilogues and prologues,
and again by the function call mechanism.
To make the call, I'd use a line or two of inline assembly.
My reading of it was that he was trying to create something like top-halfs/bottom-halfs in Linux or ISR/HISR in Nucleus. That is split the ISR work into the core, interrupt protected handler that then triggers and interruptible second level handler. Just usin a static inline block it would still be within the interrupt disabled context of the original, core handler.
But just like re-enabling interrupts in the core handler itself if you have a core that hands off to a soft handler that is then interrupted by another instance of the core handler then where does that leave you exactly - you are re-entering the soft handler before the last instance had completed.
The whole thing seems like a can of worms to me. I'd just have the core handler set a flag to say that "high level service is required" then have that actioned off the flag in the main() loop as soon as it can.
You can easily run out of ram with nested interrupts. You have been warned! The general solution is to use a RTOS.
First thing in the slow ISR you disable the slow interrupt (INT0 for example) and then enable interrupts (SEI). When done, disable interrupts (CLI), enable back the slow ISR (INT0) and RET will enable them back. This way the slow interrupt can't call itself.
For an already tight application, the last thing I would think of is adding a RTOS.
For the case cndgavruser described, I would split that slow screen IO function in two, three or as much as needed and in between poll a bit that the fast IRS sets when a full frame is in the receiving buffer. I would make sure the buffer is large enough to hold few frames. While in this case it may work, I think that there are cases where a software interrupt would be appropriate.
In the days of DOS and 80386 software I once wrote an application that was very slow on screen update, especially when zooming or panning over all the graphical data.
There was a logical place in the screen update procedure which my code visided a few hundred times for a complete screen refhesh.
At that place I inserted a little check, to simply return from the whole screen update procedure, if a mouse button was clicked, and that would result in a nother screen refhesh.
The result was excellent.
I had only partial refreshes during zooming and scrolling, but it was fast and responsive.
As soon as the mouse stopped giving input, the screen refresh completed a full redraw.
No need for a RTOS or other complicated solution, just a well placed check and a return in the right place.
My use for a software interrupt is as follows. I am using a light sensor to raise/lower a window blind. INT0 is set to interrupt upon a state change from a 555 configured as a Schmidt trigger watching the output of a photoresistor/resistor voltage divider. The interrupt routine checks to see whether the state is light or dark, since a change to either will trigger the interrupt. BUT upon power-up, the window blind just stays where it is, since there is no state change on the INT0 pin. So I need a one-time software interrupt, triggered upon power-up, so put the blind in its proper position. Tom
Gotta be kidding? Why would anyone need an interrupt to detect a window blind?....that is about the slowest moving object. We could probably poll it about a 50 million times as it creeps along. The AVR could check it every 20ms & never miss anything. Interrupts are usually used for something that is needing an extremely fast, microseconds, reaction. Are you concerned the blinds may be lowered for an extra 6 microseconds?
Also why would a 555 be needed? The AVR can almost certainly handle the task--use an ADC input to monitor the light detector.
So I need a one-time software interrupt, triggered upon power-up, so put the blind in its proper position.
To be honest I have yet to see any reason why an AVR needs "software interrupts". You might as well just CALL the code at the point where you would be triggering the software interrupt?!?
If you want a software interrupt, simple enable the SPM_RDY interrupt. During program execution this interrupt fires instantly, as it was enabled. Peter
Thank you! Top tip!
On PICs you can just set the interrupt flag on any unused interrupt. I was scratching my head trying to do the same on AVR...
Andy