This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

From C, inline assembly LJMP to Numerical Address

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)();

Except for the call differences, I think this would be the same as doing
a jump to the numerical address? Keil, is this correct?

  • 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

    You'll need to fill in the rest of the vectors, but you can follow this format. If you are not using register bank zero, change "USING 0" or make the re-vector code bank-independent, but that would increase the interrupt latency even more.

    Somewhere in your C code before interrupts are enabled, you'd init Ext0_Addr, etc. with the addresses of your new C interrupt functions. When your interrupt fires (let's say INT0) the ISR front-end replaces the return address pushed by "LCALL Ext0_Revector" with the address stored in Ext0_Addr and "jumps" to that address through the RET instruction. The MCU state and stack is now back to what it would be as if the vector had jumped directly there and the C interrupt function's context save/restore and RETI will work as expected.

    Hope this helps,

    --Dan Henry

  • 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;
    }

    idata pointers and data pointers are equivalent, and in fact, have the "generic pointer" memory type. Declaring "p" as an idata pointer makes it more obvious (to me) that the pointer could access the full 256 byte range. I went ahead and tested with "p" declared as a "data *" also, just to be certain I'm not misleading anyone.

    --Dan Henry

  • I've tested the following code and verified that it works as advertised.<br>
    <br>

    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;
    }
    <br>
    idata pointers and data pointers are equivalent, and in fact, have the "generic pointer" memory type. Declaring "p" as an idata pointer makes it more obvious (to me) that the pointer could access the full 256 byte range. I went ahead and tested with "p" declared as a "data *" also, just to be certain I'm not misleading anyone.<br>
    <br>
    --Dan Henry<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;
    }
    What fun.

    - Mark