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)();
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)(); }
So does this with a lot less parentheses:
void (code *lcall)(void) = 0x4000; lcall();
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
0081 124000 LCALL 04000H
00A9 024000 LJMP 04000H
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;
Oops. I forgot about a nuance of 8051 stack pointer operation. Change:
p = SP;
p = SP + 1;
unsigned int address = 0x4000U; unsigned char idata *p; p = SP; SP += 2; p[0] = address & 0xFF; p[1] = address >> 8;
unsigned char data *p;
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
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; }
View all questions in Keil forum