We may wish to have a C function call an assembly function.
We may wish to have an assembly function call a C function.
AVR studio's assembler is not able to integrate the two languages.
The GCC compiler allows both C and assembly to be used together.
.s files (instead of .asm file) for assembly language code.
.c files for C language code.
.cpp files for C++ language code.
The choice of assembler is made when an AVR studio project is created.
Be aware that there are some syntax differences between the AVR studio's assembler and the GCC tools.
#include <avr/io.h> instead of .include “m32def.inc”
.segment .data instead of .dseg
.segment .text instead of .cseg
.asciz “message” instead of .db “message”, 0 (Notice that the character array is automatically null terminated.)
LO8() instead of HIGH()
HI8() instead of LOW()
File suffix of .s instead of .asm
No need for .org directives in code segement since the compiler handles this automatically.
.org directives in data segements are offset from the last location needed by the compiler.
If the compiler doesn't reserve any data memory, then .org 0x0 would actually map to 0x60.
The compiler uses data memory to store global variables and character string literals.
Lines containing preprocessor directives that begin with a # (e.g., #include …, #define …) must use C/C++ style comments (cannot use semicolon).
Every .s file should contain the following GCC directives:
#define _SFR_ASM_COMPAT 1 /* Not sure when/if this is needed */
#define __SFR_OFFSET 0
Without the second line, labels like PORTB resolve to their data space address value (0x36) instead of their I/O space address value (0x16).
Essentially this subtracts 0x20 from the data space address so that it matches up with how we used the I/O ports in AVR studio's assembler.
Every C compiler has it's own rules/conventions for passing parameters to subroutines.
Parameters may be passed via Registers and/or the stack.
uint8_t – An unsigned 8-bit integer.
Consider the function prototype:
uint8_t function(uint8_t i, uint8_t j);
Parameters are passed via R25:8 (R25 to R8).
A minimum of two registers (16 bits) are used for each parameter passed.
Parameters are passed left to right.
In our example,
If the parameters passed require more memory than is available in the registers R25:8, then the stack is used to pass additional parameters (we won't have time to explore this).
Return values are placed in registers beginning at R25.
An 8-bit value gets returned in R24.
An 16-bit value gets returned in R25:24.
An 32-bit value gets returned in R25:22.
An 64-bit value gets returned in R25:18.
Consider the following function prototype:
void function(uint8_t);
When writing an assembly function that would be called by C we just use R24 as the register containing the value passed to the function.
If the function is implemented in C and we want to call if from assembly we would need to:
lds r24, value
call function