I'm using Keil compiler for 8051Warp uP. I would like to make a jump from an interrupt routine back to the main function, this way: main () { init_function(); if (setjmp(env)!=0) { // Recovery procedure here ... } while (1) { // body of my program ... } } void irq (void) interrupt (12) { if (condition) longjmp (env,1); } void init_function() { IE = 0xA0; IEN1 = 0xC4; IP = 0x00; IP1= 0x40; } I'm experiencing the following problem. Right after the longjmp instruction, the execution actually restarts at the setjmp instruction point (as i wish), but no more interrupt can be serviced after this jump. It looks like the IE.7 bit has been reset in some way. Anyway, even if i set the IE / IE1 registers after the jump, no more interrupt are serviced by the uP. Somebody could help me? Thanx.
Pietro, the thing is that the controller doesn't know that the interrupt service routine is finished as no RETI instruction has been executed. There is a way to do what you want but only with direct assembly code insertion. In general, such tricks are considered "Bad programming practice", but I did it and it works. Regards, M.
Jumping to main() anew bypasses the C run-time startup code. Better to reset. Take a look at my reset function written in C at http://www.embeddedfw.com You will need to execute the code directly in your ISR to get the required IRET instead of a plain old RET, e.g. don't call my function, inline relevant portions. I hope it helps.
Take also a look to the following Knowledge Base Aritcle: http://www.keil.com/support/docs/307.htm
The 80C52 has two levels of interrupt: high and low priority. You will need to do two RETIs to ensure that both levels are taken into account. See my post in this thread: http://www.keil.com/forum/docs/thread1627.asp
The 8051 since the dawn of time has had two levels of interrupt priority. Any RETI from either level to address 0x0000 will reset the processor with the interrupt system enabled. Only one RETI should be needed unless I'm missing something. E.g. If a low priority ISR executes a RETI with 0x0000 on the stack for a return address then by definition there is no high priority ISR executing. It would be wise to clear IE before the RETI of course. By corallary, if a high priority ISR executes a RETI with 0x0000 on the stack for a return address then by definition there is no low priority ISR executing at that particular moment. Again, clear IE before the RETI.
...if a high priority ISR executes a RETI with 0x0000 on the stack for a return address then by definition there is no low priority ISR executing at that particular moment. I don't understand. My Philips datasheet seems quite clear about this "A low-priority interrupt can itself be interrupted by a high-priority interrupt, but not by another low-priority interrupt." Besides, I know that this is exactly what happens from experience. Also, I am not aware that 0x0000 on the stack has any special meaning to the processor - you may be right, but I cannot find anything in the datasheets. Personally, I have always used a timed-out watchdog to effect a real reset, but I am certainly curious to get to the bottom of this issue.
I don't understand. My Philips datasheet seems quite clear about this "A low-priority interrupt can itself be interrupted by a high-priority interrupt, but not by another low-priority interrupt." Besides, I know that this is exactly what happens from experience. This is quite accurate. However, it says nothing about re-enabling the interrupt subsystem when jumping to 0x0000. If you jump to 0x0000 from main line code (not from any ISR) the processor will reset and run properly. If you jump to 0x0000 from a low priority ISR the processor will reset but interrupts will be permanently disabled. You must indicate to the processor that the ISR has been handled via a single RETI, not two. From a high priority ISR you probably have to RETI to some stub code that executes another RETI after setting the return address to 0x0000. I guess a double RETI when only a single high priority ISR has fired (e.g. no preempted low priority ISR's) would work okay. Just make you life simple and either reset from a low priority ISR or from the main line code. Also, I am not aware that 0x0000 on the stack has any special meaning to the processor - you may be right, but I cannot find anything in the datasheets. 0x0000 on the stack is very important when you do a RETI or RET because this is the address the processor will return to. Thus, putting 0x0000 on the top of the stack and issuing a RETI (RET from main line code) will cause the processor to properly reset. Personally, I have always used a timed-out watchdog to effect a real reset, but I am certainly curious to get to the bottom of this issue. A watchdog reset is a far simpler and sure way to reset any processor of course.
Thanks a lot for your suggestion. But my problem is a little bit different from pure reset. Actually i don't need to restart the execution at the beginning of main, because in this case, the code would execute also the init_function(), what takes very long time. I only need to make the execution after the RETI restart at the point after the init_function. Do you know maybe some way to do that in assembly code? Thank you.
You can do it in C. Just stuff the address of main() on to the stack and do a RETI. See my reset example at http://www.embeddedfw.com and inline the code in you ISR.
Inside init_function(), save the address it will be returning to:
restartAddrMSB = *(SP - 0); restartAddrLSB = *(SP - 1);
| ... | +-------------+ | RETADDR_LSB | +-------------+ | RETADDR_MSB |<-- SP - 11 +-------------+ | ACC | +-------------+ | B | +-------------+ | PSW | +-------------+ | R0 | +-------------+ | R1 | +-------------+ | R2 | +-------------+ | R3 | +-------------+ | R4 | +-------------+ | R5 | +-------------+ | R6 | +-------------+ | R7 |<-- SP +-------------+ | |
*(SP - 11) = restartAddrMSB; *(SP - 12) = restartAddrLSB;
...And a high priority ISR needs to know if it interrupted a low priority ISR and if so, don't perform the return address replacement. Let the low priority ISR do that.
Sheesh! ...And in main() after the call to init_function(), you'll want to reset SP to its initial value.
I know, I'm becoming tedious, but to properly terminate the interrupt posture and ensure that interrupts are disabled upon ISR return, you'd still want to do the ISR return address replacement I described above, but you'd also want to RETI to a CLR EA instruction. The MCU will always execute one instruction after the RETI, the CLR EA in this case. So the init_function() would look something like:
void init_function( void ) { initialSP = SP - 2; restartAddrMSB = *(SP - 0); restartAddrLSB = *(SP - 1); /* ... */ }
void main( void ) { init_function(); EA = 0; /* CLR EA */ SP = initialSP; /* No effect on 1st through */ /* ... */ EA = 1; /* Ready to rumble */ /* ... */ }
Pietro, I see gentlemen are discussing something rather different from your initial question. But to solve your problem you can use the following function just after your long jump: void dummy(void) { #pragma asm reti #pragma endasm } This function effectively does nothing but it resets interrupt service routine flag (not available directly) and thus reanables your interrupt. Be careful - if you have multiple interrupts EACH of them should be treated in this way. On the other hand, nothing bad will happen if you perform this function excessive number of times, so if you have enough time, you can do it in a loop. Regards, M.
Pietro, I see gentlemen are discussing something rather different from your initial question. I respectfully disagree. I think we all agree that longjmp() does not restore the MCU fully when invoked from an ISR. We are merely discussing two ways to do the same thing. longjmp() ignores the "priority level active" flip-flops and PSW (register banking!) entirely, but does handle restoring the MCU SP and reentrant SP's (whether you are using reentrancy or not). You are recommending handling the flip-flops after the longjmp(). To be truly safe, the PSW still needs attention in case a called function or ISR changed register banks. The "custom" longjmp() handles the flip-flops as a natural consequence of ISR return, but the stacked PSW can be modified to normalize bank selection upon return. If used, the reentrant SP can be handled before or after the return. The MCU's SP gets handled after the return. Do you now see how the two methods' results are not so different after all and how they both address the original issue? A comparison of complexity and maintainability, on the other hand, is an entirely different matter. I suppose my posts are driven by what I personally use the Discussion Forum for -- not so much for support, but to see other ways to do things that I might not have otherwise thought of and to get ideas. For example, detecting the slight odor of RTOS context switching in the "custom" longjmp() solution might get a reader thinking about simple context switching mechanisms to use in their next fun project. Oh well, sorry for the intrusion.