How do I perform a long jump to a numerical address from my C code? I'm pretty sure I have to do this with inline assembly - however that seems complicated for C51. I figured in C I could do this:
typedef void (*jmpPtr)(void); jmpPtr jmp; ... unsigned int address = 0x4000U; jmp = (jmpPtr) address; (*jmp)();
Actually, I was trying to do all this in C so I wouldn't have to figure out how to do 'inline' assembly from C. However, it may just be easier to do the assembly. What I am doing is caputring an interrupt and passing it on to another interrupt handler. So it would be interrupt handler to interrupt handler. Now that I think about it I suppose I would have to fix up the stack regardless, otherwise I'll crash... Any help would be appreciated of course.
Most Keil help suggests that you can't do inline assembly in C code (without jumping through a few hoops). However I found this obscure link, and it turns out it is easy! http://www.keil.com/support/docs/754.htm I'll have to go back and look to see if #pragma asm has been in the C51 user's manual this whole time.
It certainly has - page 12! What gave you the impression it couldn't be done? It's no more difficult than many compilers which support inline Assembler. The catch is that you also need to specify the SRC directive to make the Compiler emit assembler source, and then pass that to the assembler to generate the object! (in uVision2, just check the 'Generate Assembler SRC File' and 'Assemble SRC File' options in the file properties for the 'C' source)
Your needing an ISR-to-ISR jump complicates things a bit. Conceptually, the "jump using RET" is still do-able, but a C interrupt function returns using RETI instruction to restore the interrupt priority logic and that's (probably) not desireable at that point in servicing the interrupt. There's also the issue of differences in saved processor context needs between ISR functions. Maybe I've fallen victim to some form of limited thinking, but to reliably do what you want requires a bit of assembler, I think. Keil doesn't support inline assembly in the way we both probably understand the term, so I'm providing a short assembly language excerpt to show how I'd dynamically re-vector an interrupt. The idea is to provide a fixed ISR front-end that will "jump" through an address you provide in a global. That address is the dynamic address of your C interrupt function, which has presumably been relocated in flash. Since you want some ISR front-end code, you have to disable the compiler's automatic vector generation by using the NOINTVECTOR compiler option and write your own interrupt vectoring code. The following is a snippet for that assembler file (say, intvects.a51). I have tested this code and know it to work.
EXTRN DATA (Ext0_Addr) EXTRN DATA (Tmr0_Addr) CSEG AT 00003H Ext0_IntVector: ;** Calling the revector function pushes a ;* return address that will be overwritten ;* by the target ISR's address. ;** LCALL Ext0_Revector ; Never returning to here! CSEG AT 0000BH Timer0_IntVector: LCALL Timer0_Revector ; Never returning to here! ;** ==MORE VECTOR CODE SNIPPED== Ext0_Revector: USING 0 PUSH AR0 ; Save MOV R0,SP ; &Saved_R0 DEC R0 ; &RET_ADDR_MSB MOV @R0,Ext0_Addr ; Target ISR's addr MSB DEC R0 ; &RET_ADDR_LSB MOV @R0,Ext0_Addr+1 ; Target ISR's addr LSB POP AR0 ; Restore RET ; "Jump" to target ISR ; ..and RETI from there. Timer0_Revector: USING 0 PUSH AR0 ; Save MOV R0,SP ; &Saved_R0 DEC R0 ; &RET_ADDR_MSB MOV @R0,Tmr0_Addr ; Target ISR's addr MSB DEC R0 ; &RET_ADDR_LSB MOV @R0,Tmr0_Addr+1 ; Target ISR's addr LSB POP AR0 ; Restore RET ; "Jump" to target ISR ; ..and RETI from there. ;** ==MORE RE-VECTOR CODE SNIPPED== END
I've tested the following code and verified that it works as advertised.
unsigned address; void JumpedTo( void ) { while (1) ; } void main( void ) { unsigned char idata *p; /* Set stack pointer to 0x7E, forcing the * "pushed" address to straddle the data/idata * boundary at 0x80. NOTE: Only for 8052 * derivatives. */ SP = 0x7E; address = JumpedTo; p = SP + 1; SP += 2; p[0] = address & 0xFF; p[1] = address >> 8; return; }
I've tested the following code and verified that it works as advertised.<br> <br>
Let's try that again. Dan, you have inspired me. Here's my "Jump to an absolute address" that I hope solves the "jump from C" issue.
// 'r_' prefix denotes a CPU 'r'egister. sfr r_stackPointer = 0x81; void main(void) { // Point so that this will work for 8051's and 52's. unsigned char data *p = 0x7E; // Little endian, LSB in lower address, MSB in higher address. *p = 0x00; // LSB of address 0x4000. ++p; *p = 0x40; // MSB of address 0x4000; // Now fool the CPU (this is the magic part). r_stackPointer = 0x7F; // "Jump via return" return; }