Preemptive FAQ, since I've already fielded the same question twice involving putting large amounts of data in your addin. I also wrote up how you can link chunks of C and assembly together. Maybe more to come if multiple people bring up the same questions.
Code:
Code:
Q. I get an error like ".data will not fit into region RAM". Help!
A. You have more than 64k of initialized data. Usually, this is because you
have some large array declared which you never write to (such as an image
to be displayed at some time). Declare such things with the const qualifier
to put them in .rodata instead so the compiler knows they don't need to fit
in to the 64k block of RAM available to us.
Bad:
char picture[] = {0, 1, 2, 3, ...., 65535};
Good:
const char picture[] = {0, 1, 2, 3, ..., 65535};
If you need a lot of RAM at runtime, you should be able to get large amounts
via malloc() instead of declaring large arrays within functions, which is
generally preferred to large static allocations anyway.
If you want to write to a large preinitialized buffer at some time, you'll
have to copy it into a dynamically allocated chunk of RAM. For example:
const char initializedArray[] = {...};
char *RWArray = malloc(sizeof(initializedArray));
memcpy(RWArray, initializedArray, sizeof(initializedArray));
Q. How can I write subroutines for a C program in assembly (or call C routines
from assembly)?
A. Declare the entry point of your function as global. Beyond that, you'll
usually want to provide a prototype so the compiler can better know what
to expect from your function, and you need to obey the ABI, a quick
description of which follows.
Every register has a specified purpose in the ABI. They are defined as
either caller or callee saves. If you change the value in a callee-saved
register, be sure to restore it to its original value before returning.
- r0-r1 Stores function return value, caller saves.
- r2-r3 Scratch space, caller saves.
- r4-r7 Function parameters, caller saves.
- r8-r13 Scratch, callee saves.
- r14 Frame pointer, callee saves.
- r15 Stack pointer, callee saves.
- MACH,MACL Callee saves.
Parameters are passed in the order they are declared on r4 through r7. For
example, if you delared the function as `void foo(int a, char b, long c)',
a gets passed in r4, b in r5, and c in r6. Registers will always be filled,
except if an argument's type is too large to fit in registers. If a fourth
argument of type 'long long' (64 bits) were to be added to the above
example, the entire value would be passed on the stack, not half in r7 and
half on the stack. Aggregate types(struct/union) are always passed entirely
on the stack.
When arguments overflow the four registers allocated to them, they will be
pushed on to the stack from last to first, putting the lowest-numbered
argument which is not in registers at the lowest address. Pushes to the
stack are always aligned to 4 bytes. If two bytes were pushed, for example,
the first would be at (r15+2) and the other at (r15+3), with the two bytes
at (r15+0) and (r15+1) having undefined values.
Return values of 32 bits or less are returned entirely in r0. If 8 bytes
or smaller, r0 is the most significant part of the value and r1 is the least
significant part. Otherwise, the caller allocates a buffer in which the
result should be stored, and passes a pointer to that buffer as a hidden
last argument to the function. Neither values returned nor passed in to
functions will be automatically sign-extended.
A short example of an assembly subroutine, assuming the following prototype:
int strlen(char *str);
.global _strlen ; Global linkage
_strlen:
mov #0, r2
_strlen_loop:
mov.b @r4, r0
cmp/eq #0, r0
bt _strlen_done
add #1, r2
bra _strlen_loop
add #1, r4 ; Delay slot
_strlen_done:
mov r2, r0
rts
To compile, include the prototype in a source or header file, and include
the file containing your routine(s) in the sources to compile. A good
choice for the above example might be a file called strlen.s.