| Details |
Message |
|
Read-Only
Author Ted Dubroff
Posted 8-Dec-2000 16:51 GMT
Toolset C51
|
 From C, inline assembly LJMP to Numerical Address
Ted Dubroff
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?
|
|
|
Read-Only
Author Mark Odell
Posted 8-Dec-2000 17:31 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Mark Odell
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
|
|
|
Read-Only
Author Ted Dubroff
Posted 8-Dec-2000 17:37 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Ted Dubroff
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.
|
|
|
Read-Only
Author Mark Odell
Posted 8-Dec-2000 17:51 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Mark Odell
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
|
|
|
Read-Only
Author Dan Henry
Posted 9-Dec-2000 17:41 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Dan Henry
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
|
|
|
Read-Only
Author Mark Odell
Posted 9-Dec-2000 19:28 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Mark Odell
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
|
|
|
Read-Only
Author Jon Young
Posted 10-Dec-2000 03:11 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Jon Young
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.
|
|
|
Read-Only
Author Dan Henry
Posted 10-Dec-2000 15:07 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Dan Henry
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
|
|
|
Read-Only
Author Ted Dubroff
Posted 12-Dec-2000 15:26 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Ted Dubroff
Hmm that's quite interesting. Thanks for the info. Unfortunately I don't
have a constant to use, rather its a variable.
|
|
|
Read-Only
Author Dan Henry
Posted 12-Dec-2000 18:19 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Dan Henry
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
|
|
|
Read-Only
Author Dan Henry
Posted 12-Dec-2000 18:37 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Dan Henry
Oops. I forgot about a nuance of 8051 stack pointer operation. Change:
p = SP;
To:
p = SP + 1;
--Dan Henry
|
|
|
Read-Only
Author Ted Dubroff
Posted 12-Dec-2000 20:18 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Ted Dubroff
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?
|
|
|
Read-Only
Author Dan Henry
Posted 12-Dec-2000 21:04 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Dan Henry
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
|
|
|
Read-Only
Author Ted Dubroff
Posted 12-Dec-2000 21:08 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Ted Dubroff
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.
|
|
|
Read-Only
Author Dan Henry
Posted 13-Dec-2000 17:55 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Dan Henry
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
|
|
|
Read-Only
Author Mark Odell
Posted 12-Dec-2000 19:32 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Mark Odell
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
|
|
|
Read-Only
Author Dan Henry
Posted 12-Dec-2000 19:57 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Dan Henry
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
|
|
|
Read-Only
Author Mark Odell
Posted 12-Dec-2000 20:17 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Mark Odell
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
|
|
|
Read-Only
Author Dan Henry
Posted 13-Dec-2000 17:57 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
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
|
|
|
Read-Only
Author Mark Odell
Posted 14-Dec-2000 02:35 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Mark Odell
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>
|
|
|
Read-Only
Author Mark Odell
Posted 14-Dec-2000 02:37 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Mark Odell
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
|
|
|
Read-Only
Author Ted Dubroff
Posted 13-Dec-2000 16:42 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Ted Dubroff
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.
|
|
|
Read-Only
Author Andrew Neil
Posted 13-Dec-2000 17:32 GMT
Toolset C51
|
 RE: From C, inline assembly LJMP to Numerical Address
Andrew Neil
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)
|
|