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
uint8_t function(uint8_t i, uint8_t j);
Consider the following trivially simple program:
// driver.c #include <avr/io.h> extern void asmfunc_calledfrom_c(uint8_t val); int main() { DDRB = 0xff; asmfunc_calledfrom_c(3); return 0; }
// raw.s #define __SFR_OFFSET 0 // Use 0 for the I/O register offset #include <avr/io.h> // Defines I/O port aliases .global asmfunc_calledfrom_c ; Makes asmfunc_calledfrom_c visible in other source files .section .text ; Defines a code section asmfunc_calledfrom_c: ; Start of asmfunc_calledfrom_c subroutine out PORTB, r24 ; Send value passed to asmfunc_calledfrom_c to PORTB ret
When compiled and linked, the above two source files produce the following .lss file (only a portion of the file is included below):
00000092 <main>: 92: cf 93 push r28 94: df 93 push r29 96: cd b7 in r28, 0x3d ; 61 98: de b7 in r29, 0x3e ; 62 9a: e7 e3 ldi r30, 0x37 ; 55 9c: f0 e0 ldi r31, 0x00 ; 0 9e: 8f ef ldi r24, 0xFF ; 255 a0: 80 83 st Z, r24 a2: 83 e0 ldi r24, 0x03 ; 3 a4: 0e 94 59 00 call 0xb2 ; 0xb2 <asmfunc_calledfrom_c> a8: 80 e0 ldi r24, 0x00 ; 0 aa: 90 e0 ldi r25, 0x00 ; 0 ac: df 91 pop r29 ae: cf 91 pop r28 b0: 08 95 ret 000000b2 <asmfunc_calledfrom_c>: b2: 88 bb out 0x18, r24 ; 24 b4: 08 95 ret
Consider the following slightly more complicated program:
// driver.c #include <avr/io.h> /** * Send value passed to function to PORTB * * Implemented in assembly * * @param val Value to be output to PORTB */ extern void send_to_portb_in_asm(uint8_t val); /** * Divide value passed by 256 and return integer result. * * Implemented in assembly * * @param val Value to be divided by 256 * @return integer result of val/256 */ extern uint8_t divide_by_256_in_asm(uint16_t val); /** * A completely useless function that accepts two arguments * and writes a value to PORTB based on the relative values * of the arguments passed. * If the first argument is larger, then the value of the * first argument is written to PORTB. Otherwise, 0x00 is * written to PORTB. * * Implemented in assembly * * @param val Value to be acted upon * @param minus Value to be subtracted from val */ extern void strange_silliness_in_asm(uint8_t val, uint8_t minus); /** * Main program that demonstrates calling assembly subroutines from * C functions. */ int main() { DDRB = 0xff; send_to_portb_in_asm(3); PORTB = divide_by_256_in_asm(0x083f); strange_silliness_in_asm(32,16); return 0; } /** * Returns the absolute value of the arugment passed to the function. * * @param val A signed integer value * @return absolute value of the argument passed to the function */ uint8_t abs_in_c(int8_t val) { if(val<0) { val *= -1; } return val; }
and
// raw.s #define __SFR_OFFSET 0 // Use 0 for the I/O register offset #include <avr/io.h> // Defines I/O port aliases .global send_to_portb_in_asm ; Makes send_to_portb_in_asm visible in other source files .global divide_by_256_in_asm .global strange_silliness_in_asm .section .text ; Defines a code section ; Send value passed to subroutine to PORTB send_to_portb_in_asm: ; Start of asmfunc_calledfrom_c subroutine out PORTB, r24 ; Send value passed to asmfunc_calledfrom_c to PORTB ret ; Return number passed to subroutine divided by 256 divide_by_256_in_asm: mov r24, r25 ; Shift MSB into the LSB (divides by 256) ldi r25, 0x00 ; Clear MSB ret ; Return number passed to subroutine divided by 256 strange_silliness_in_asm: sub r24, r22 ; subtract minus (r22) from val (r24) mov r22, r24 ; copy value in r24 into r22 call abs_in_c ; call c function that returns absolute value in r24 sub r24, r22 ; subtract val from abs(val) breq equal out PORTB, 0x00 equal: out PORTB, r22 ret
00000092 <abs_in_c>: 92: 87 fd sbrc r24, 7 94: 81 95 neg r24 96: 90 e0 ldi r25, 0x00 ; 0 98: 08 95 ret 0000009a <main>: 9a: 8f ef ldi r24, 0xFF ; 255 9c: 87 bb out 0x17, r24 ; 23 9e: 83 e0 ldi r24, 0x03 ; 3 a0: 0e 94 5e 00 call 0xbc ; 0xbc <send_to_portb_in_asm> a4: 8f e3 ldi r24, 0x3F ; 63 a6: 98 e0 ldi r25, 0x08 ; 8 a8: 0e 94 60 00 call 0xc0 ; 0xc0 <divide_by_256_in_asm> ac: 88 bb out 0x18, r24 ; 24 ae: 60 e1 ldi r22, 0x10 ; 16 b0: 80 e2 ldi r24, 0x20 ; 32 b2: 0e 94 63 00 call 0xc6 ; 0xc6 <strange_silliness_in_asm> b6: 80 e0 ldi r24, 0x00 ; 0 b8: 90 e0 ldi r25, 0x00 ; 0 ba: 08 95 ret 000000bc <send_to_portb_in_asm>: bc: 88 bb out 0x18, r24 ; 24 be: 08 95 ret 000000c0 <divide_by_256_in_asm>: c0: 89 2f mov r24, r25 c2: 90 e0 ldi r25, 0x00 ; 0 c4: 08 95 ret 000000c6 <strange_silliness_in_asm>: c6: 86 1b sub r24, r22 c8: 68 2f mov r22, r24 ca: 0e 94 49 00 call 0x92 ; 0x92 <abs_in_c> ce: 86 1b sub r24, r22 d0: 09 f0 breq .+2 ; 0xd4 <equal> d2: 08 ba out 0x18, r0 ; 24 000000d4 <equal>: d4: 68 bb out 0x18, r22 ; 24 d6: 08 95 ret
Earlier it was noted that the stack is used to store argument values if there isn't sufficient space in registers R25:8. The following example contains a function, stack_pass_example, which requires the stack to pass the last two arguments.
#include <avr/io.h> /** * Example that uses the stack to pass arguments. * * Implemented in C * * @param val1 First long * @param val2 Second long * @param val3 Third long * @param val4 Fourth long * @param val5 Fifth long * @param val6 An eight bit integer * @return Nothing important */ long stack_pass_example(long val1, long val2, long val3, long val4, long val5, int8_t val6); /** * Main program that demonstrates calling assembly subroutines from * C functions. */ int main() { DDRB = stack_pass_example(1, 2, 3, 4, 5, 6); return 0; } /** * Example that uses the stack to pass arguments. * * @author t a y l o r@msoe.edu */ long stack_pass_example(long val1, long val2, long val3, long val4, long val5, int8_t val6) { DDRB = val1; DDRB = val2; DDRB = val3; DDRB = val4; DDRB = val5; DDRB = val6; return val1; }
Relevant portions of the .lss are found below:
00000092 <stack_pass_example>: 92: af 92 push r10 94: bf 92 push r11 96: cf 92 push r12 98: df 92 push r13 9a: ef 92 push r14 9c: ff 92 push r15 9e: 0f 93 push r16 a0: 1f 93 push r17 a2: cf 93 push r28 a4: df 93 push r29 a6: cd b7 in r28, 0x3d ; 61 a8: de b7 in r29, 0x3e ; 62 aa: 67 bb out 0x17, r22 ; 23 ac: 27 bb out 0x17, r18 ; 23 ae: e7 ba out 0x17, r14 ; 23 b0: a7 ba out 0x17, r10 ; 23 b2: 2d 85 ldd r18, Y+13 ; 0x0d b4: 27 bb out 0x17, r18 ; 23 b6: 29 89 ldd r18, Y+17 ; 0x11 b8: 27 bb out 0x17, r18 ; 23 ba: df 91 pop r29 bc: cf 91 pop r28 be: 1f 91 pop r17 c0: 0f 91 pop r16 c2: ff 90 pop r15 c4: ef 90 pop r14 c6: df 90 pop r13 c8: cf 90 pop r12 ca: bf 90 pop r11 cc: af 90 pop r10 ce: 08 95 ret 000000d0 <main>: d0: af 92 push r10 d2: bf 92 push r11 d4: cf 92 push r12 d6: df 92 push r13 d8: ef 92 push r14 da: ff 92 push r15 dc: 0f 93 push r16 de: 1f 93 push r17 e0: 86 e0 ldi r24, 0x06 ; 6 e2: 8f 93 push r24 e4: 85 e0 ldi r24, 0x05 ; 5 e6: 90 e0 ldi r25, 0x00 ; 0 e8: a0 e0 ldi r26, 0x00 ; 0 ea: b0 e0 ldi r27, 0x00 ; 0 ec: bf 93 push r27 ee: af 93 push r26 f0: 9f 93 push r25 f2: 8f 93 push r24 f4: 94 e0 ldi r25, 0x04 ; 4 f6: a9 2e mov r10, r25 f8: b1 2c mov r11, r1 fa: c1 2c mov r12, r1 fc: d1 2c mov r13, r1 fe: 83 e0 ldi r24, 0x03 ; 3 100: e8 2e mov r14, r24 102: f1 2c mov r15, r1 104: 01 2d mov r16, r1 106: 11 2d mov r17, r1 108: 22 e0 ldi r18, 0x02 ; 2 10a: 30 e0 ldi r19, 0x00 ; 0 10c: 40 e0 ldi r20, 0x00 ; 0 10e: 50 e0 ldi r21, 0x00 ; 0 110: 61 e0 ldi r22, 0x01 ; 1 112: 70 e0 ldi r23, 0x00 ; 0 114: 80 e0 ldi r24, 0x00 ; 0 116: 90 e0 ldi r25, 0x00 ; 0 118: 0e 94 49 00 call 0x92 ; 0x92 <stack_pass_example> 11c: 67 bb out 0x17, r22 ; 23 11e: 0f 90 pop r0 120: 0f 90 pop r0 122: 0f 90 pop r0 124: 0f 90 pop r0 126: 0f 90 pop r0 128: 80 e0 ldi r24, 0x00 ; 0 12a: 90 e0 ldi r25, 0x00 ; 0 12c: 1f 91 pop r17 12e: 0f 91 pop r16 130: ff 90 pop r15 132: ef 90 pop r14 134: df 90 pop r13 136: cf 90 pop r12 138: bf 90 pop r11 13a: af 90 pop r10 13c: 08 95 ret