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?

  • First, why all the hoops (eg typedef)?

    Just:
        void (code *jump)(void) = 0x4000;
    and:
        jump();

    Which would be an LCALL, not an LJMP.

    See setjmp()/longjmp() function descriptions (ISO C library) for jumps (vs. calls) done portably in C.

    - Mark

  • First, why all the hoops (eg typedef)?

    Just to make it more readable I suppose.

    Setjmp() and longjmp() save the state from one code point to
    another and doesn't allow for specifying an address. The reason
    I need an address is because I am going to dynamically program
    Flash and then execute code out of the just-programmed Flash.

  • Just to make it more readable I suppose.

    I'm not sure I'd agree that 5 lines is more readable than 2. To each their own.

    Setjmp() and longjmp() save the state from one code point to
    another and doesn't allow for specifying an address.


    Good point. If you don't like the LCALL then you'll have to use some assembler I'm afraid. How bad is the LCALL implementation for you?

    - Mark

  • Having recently done this for a project, I know that the following code produces an LJMP instruction when placed at the end of a function (which it likely would be, given it's application):

    #define JMP_TARGET_ADDR ((unsigned char code *)0x4000)
    
    /* At end of function here... */
    
        ((void (*)())JMP_TARGET_ADDR)();
    }
    

    -- Dan Henry

  • So does this with a lot less parentheses:

    void (code *lcall)(void) = 0x4000;
    
    lcall();
    but he needs to jump, not call. Calls imply and return a can waste some stack space. We can't do what he needs in C.

    - Mark

  • I think Dan's point is that, if you have the right level of optimization, and you place the subroutine call at the end of the function, the compiler will emit a jmp not a call.

  • Yes, and I'm using the default optimization level. However, it appears that I should have been more clear and made the distinction between the techniques for the casual reader.

    The "function pointer as data object" approach that both Ted and Mark are attempting to use results in a call (through a library routine, no less).

    00A2 AA00        R     MOV     R2,lcall
    00A4 A900        R     MOV     R1,lcall+01H
    00A6 120000      E     LCALL   ?C?ICALL
    

    The "function pointer cast of a manifest constant" approach that I advocate using results in a call or a jump (direct to the target address, not using a library routine), depending on its location within the function. When it appears before the last statement in a function, the compiler emits:

    0081 124000            LCALL   04000H
    

    When it appears as the last statement in a function the compiler emits:

    00A9 024000            LJMP    04000H
    

    So, despite it's unpleasing parentheses, it satisfies Ted's requirement as I understand it.

    --Dan Henry

  • Hmm that's quite interesting. Thanks for the info. Unfortunately I don't
    have a constant to use, rather its a variable.

  • OK, I refuse to be stumped (yet). How about implementing jump-like functionality by pushing a return address on the stack and returning to it? This should be achievable in C thusly:

    unsigned int address = 0x4000U;
    unsigned char idata *p;
    
    p = SP;
    SP += 2;
    p[0] = address & 0xFF;
    p[1] = address >> 8;
    return;

    --Dan Henry

  • Oops. I forgot about a nuance of 8051 stack pointer operation. Change:

    p = SP;
    To:

    p = SP + 1;

    --Dan Henry

  • unsigned int address = 0x4000U;
    unsigned char idata *p;
    
    p = SP;
    SP += 2;
    p[0] = address & 0xFF;
    p[1] = address >> 8;

    Won't work. C will indirect through R0 pointing you into IDATA space and SP lives in DATA space.

    unsigned char data *p;
    won't work either, sorry.

    And yes, as your own follow-up indicates. The 8051 treats 16-value as little-endian (LSB in lower numbered address) as is the case with the SP.

    - Mark


  • Huh? The difference between IDATA (0x80-0xFF) and DATA (0x00-0x7F) is that IDATA is only accessible through SP, R0, and R1. DATA is accessible through SP, R0, R1, and using direct addressing. What you're suggesting is that R0 can't access DATA space, and we know that's not right (e.g., STARTUP.A51 uses exactly that technique to clear DATA, as well as IDATA).

    My followup has nothing to do with endianness. My byte ordering is correct. It has to do with the SP's pre-increment behavior. For example, reset initializes SP to 0x07 (&RB0/R7) the first LCALL writes the return address addresses 0x08-9.

    Now, I'll admit, that my approach may very well *not* work, but I don't think it will be for the reasons you give. I've not tried it, since I'm not at my development system, but I intend to later. Then when it doesn't work I'll tell everone why and take the heat for my lame suggestion :->

    --Dan Henry

  • What you're suggesting is that R0 can't access DATA space, and we know that's not right (e.g., STARTUP.A51 uses exactly that technique to clear DATA, as well as IDATA).

    Actually, we're both right. Using R0 as an indirection register startup.a51 clears out the entire IDATA space from 0x00 to 0xFF (on 8052's). Note that the IDATA space overlap's the DATA space. The I stands for Indirect, thus, whenever you Indirect through R0 or R1 you are by definition in IDATA.

    Your byte ordering was correct, I didn't say otherwise. The 8051 is little-endian for 16-bit values.

    Regards,

    - Mark

  • Thanks Dan,
    I've used the same type of thing with the pentium. Thanks for reminding
    me about that. Can you post the code for fixing up the stack from
    inside an interrupt handler?

  • Gee, and just when things were looking easy... Well, I can give it a shot. It obviously depends on whether the straight-forward version already posted will work first. Since you are probably following all of this thread, you know that Mark says it won't work, and since I haven't tried it yet, I can't say whether he's right. But, in case it does work, can you provide us with some more information?

    I take it that you'll be performing this "jump" from within an ISR, right? Upon entry, ISR has pushed some processor context on the stack and possibly even switched register banks. What kind of "fixing up" is required? We need to know what, if any (and hopefully none), of the saved processor context needs to be preserved across the jump. Are you jumping from interrupt context to interrupt or non-interrupt context? Or is it more like a software reset to a dynamic address? You're still wanting all this done in C, right?

    --Dan Henry