Dynamically Linking Functions

Go To Last Post
12 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I don't know the proper name for this task, or if it even has a name.

Essentially I would like use a atmega32 to take compiled hex off an SD card, put that in the program space, and then call functions from that newly placed program from my main routine.

I have started with the SD card interface, there are a lot of existing libraries for this and have chosen the suitable one. It doesn't look to difficult.

Next was burning code to the flash memory during run-time. I'm going to take a read through this later, it looks like it should work:

http://www.atmel.ca/Images/doc25...

And then finally I have to dynamically link the new functions, during runtime, with my existing code. for this I think I should be able to use function pointers, return the starting address of the new function and then call it. Should I need to worry about returning to the main? I read about that here:

http://en.wikipedia.org/wiki/Fun...

I was just wondering if this is the best way to go about this task or if I'm completely mental.

Thanks

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

My project here: http://spaces.atmel.com/gf/proje... may contain a lot of what you require (and fits in 2K in a mega32).

The function pointer thing sounds fine. It's not really "dynamic" in that both codes and the shared dispatch table will all be fixed in flash. The linking can actually be static at build time.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
The linking can actually be static at build time.

Thanks for your reply, this will be very helpful.

Just to clarify, The function's and the main will be compiled separately, there wont be any re-compilation.

ie: I will be happily running the main, then plug in an SD card, a trigger in the main will happen, then the main will start calling functions from the SD card via the method stated above. The same SD card could then be removed, different functions loaded on, and then plugged back in and the same process repeat.

Just clarifying, after reading it was a little vague.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Are you able to share some background on your application that takes you to the sort of solution that you describe above?

if it is for debugging then you could just reprogram the flash. if it is to have a range behaviours with the same core code then this cold be possible with multiple sets of functions in flash and the use of function pointers.

I am guessing that you are doing something a bit more interesting than either of these scenarios

regards
Greg

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Essentially, you'll need an indirect call, similar to the way that the interrupt vector table works. You would define a bunch of KNOWN locations in memory associated with each function you want to "link", and in that location you'd stick a jump to the actual function location in the loaded code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

An AVR will not execute code in an SD card.
You will need to rewrite flash or run an interpreter.
In the latter case, you will need a mechanism for producing something you can interpret.
Selection of the above interpreter and mechanism is left as an exercise for the reader.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
An AVR will not execute code in an SD card.

To be more precise - AVRs can only execute from embedded flash.
Pick something that can execute from ram. PIC, 8051, ARM, STM8, or preferably x86.

No RSTDISBL, no fun!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Just to clarify, The function's and the main will be compiled separately, there wont be any re-compilation.

Yeah I know what you meant. When you build either main() or the "functions" you can link against a "stub" that has the vector table addresses (but no code behind them) for the layout of the "other part". I assume it's a one way street and main() will only call out to the inserted functions? So main can be built against such a stub that just has the link addresses for the entries in the dispatch table. When the "function" is later inserted the CALL 0xB37F in main() will now arrive at "real code" rather than the dummy dispatch table entry.

BTW do remember you can only do this trick 10,000 times. So don't go SPMing the code with new functions every 5 minutes!!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
When you build either main() or the "functions" you can link against a "stub"

What does this do as far as passing data? Because as I understand it; in your main your calling a function, then your using an ASM instruction to call a subroutine. Then returning from the subroutine into the function then back into the main. What's the best way to pass parameters?

As a side note, what's the format for putting Assembly instructions in C code, do you have to worry about preserving registers? ie: pushing them into the stack.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Shredability wrote:
clawson wrote:
When you build either main() or the "functions" you can link against a "stub"

What does this do as far as passing data? Because as I understand it; in your main your calling a function, then your using an ASM instruction to call a subroutine. Then returning from the subroutine into the function then back into the main. What's the best way to pass parameters?

The use of function pointers does not affect the calling sequence.
You do not need to do anything special.
The special is getting the pointees to where they can be executed.
Quote:
As a side note, what's the format for putting Assembly instructions in C code, do you have to worry about preserving registers? ie: pushing them into the stack.
http://www.nongnu.org/avr-libc/user-manual/inline_asm.html
The semantics of GNU's inline assembly is much better than that of other inline assemblies.
The syntax is often treated like the only kid with glasses.

Iluvatar is the better part of Valar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

AVRs don't have a single address space for RAM and Flash. Many other microprocessors do, notably the ARM7 and Cortex. Just relink to a different address space to choose RAM vs flash (size permitting).

IIRC, the 8 bit PICs also have two address spaces (and no viable stack).

Harvard vs. von Neuman architectures.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

do you have to worry about preserving registers?

http://www.nongnu.org/avr-libc/u...
Quote:

What does this do as far as passing data? Because as I understand it; in your main your calling a function, then your using an ASM instruction to call a subroutine. Then returning from the subroutine into the function then back into the main. What's the best way to pass parameters?

All the C code you write gets converted to Asm so it's easy to see the ABI in that FAQ entry in action. If, for example I write:

__attribute__((noinline)) uint8_t bar(volatile uint8_t * port, uint8_t byte){
	*port= byte;
	return *(port - 2);
}

int main(void) {
	uint8_t x = bar(&PORTB , 0x55);
}

The assembler code is:

	.section	.text.bar,"ax",@progbits
.global	bar
	.type	bar, @function
bar:
//==> __attribute__((noinline)) uint8_t bar(volatile uint8_t * port, uint8_t byte){
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
//==> 	*port= byte;
	movw r30,r24
	st Z,r22
//==> 	return *(port - 2);
	sbiw r30,2
	ld r24,Z
//==> }
	ret
	.size	bar, .-bar

	.section	.text.startup.main,"ax",@progbits
.global	main
	.type	main, @function
main:
//==> int main(void) {
/* prologue: function */
/* frame size = 0 */
/* stack size = 0 */
.L__stack_usage = 0
//==> 	uint8_t x = bar(&PORTB , 0x55);
	ldi r22,lo8(85)
	ldi r24,lo8(56)
	ldi r25,0
	call bar
//==> }
	ldi r24,0
	ldi r25,0
	ret
	.size	main, .-main

Here you can see that for the call to bar() it sets up the call parameters by putting 56 (0x38 - the address of PORTB) into R25:R24 which is used for the first passed parameter and 85 (0x55) into R22 for the second passed parameter.

In the function foo() the compiler then moves the R25:R24 to R31:R30 to use Z as an index register to access 0x0038. It then stores the 0x55 value to that address so 0x55 is written to 0x0038 which is the RAM address for the PORTB register. It then subtracts 2 from Z so it points to 0x0036 (the address of PINB) and then reads the input value into R24 which is the return register for a uint8_t.

The point here being that if you want to know which registers to use in an Asm function that will be called from C then just prototype the interface in C first, use -save-temps then study the .s file to see what the C compiler used - then copy its behaviour.

BTW I enhance the look of .s files (as above) using my own avr-source utility:

http://spaces.atmel.com/gf/proje...