Mix
Random mix of pages and files
Arduino_asm
2025.02.21 20:52:55 end Arduino HowTo SW Gilhad

avr-gcc -mmcu=atmega328p -Os -S demo.c -o demo.Os.s

Arduino_asm

http://eleccelerator.com/fusecalc/fusecalc.php?chip=atmega328p

https://gcc.gnu.org/wiki/avr-gcc#Register_Layout

Ve zkratce:

  • 1 bytové argumenty se považují za dvojbytové
  • předávají se v registrech kde nejnižší byte je v registru s nejnižším číslem
  • R26 se nepoužíje, vše se naštosuje POD něj
  • R8 se nepoužíje, pokud by měl přijít na řadu, tak to jde na stack od té chvíle všechno
  • R9-R17 se použijou, ale musí mít při návratu stejnou hodnotu

Summary

Register Purpose Caller-Saved?
R0 Temporary use, often used for multiplication/division Yes
R1 Must always be zero before returning Yes (restore to 0)
R2-R17 Callee-saved (preserve if modified) No
R18-R27 Temporary variables, can be freely changed Yes
R26-R27 (X) General indirect addressing in SRAM Yes
R28-R29 (Y) Frame pointer (stack-related) No
R30-R31 (Z) Used for indirect addressing (e.g., LD, ST) & reading from Flash memory Yes

When programming the ATmega328P (used in Arduino Uno) in assembly, especially when interfacing with C++ functions, you need to follow the AVR GCC calling convention.

Register Usage in AVR GCC Calling Convention

AVR uses a modified caller-save convention, meaning the caller is responsible for saving certain registers before calling a function.

1. Argument Passing (Registers Used for Function Parameters)

  • The first two arguments (for 8-bit values) are passed in R24 and R22.
  • For 16-bit values: - First argument: R24-R25 - Second argument: R22-R23
  • For 32-bit values: - First argument: R22-R25 - Second argument: R18-R21
  • For pointer values (16-bit): Passed in R24-R25
  • For return values: - 8-bit: R24 - 16-bit: R24-R25 - 32-bit: R22-R25

2. Registers That Can Be Freely Changed (Caller-Saved)

  • R18-R27 (X-register, used for indirect addressing)
  • R30-R31 (Z-register, used for indirect addressing)
  • R0 (but GCC expects it to be zero after use)

These must be saved if needed after calling a function.

3. Registers That Must Be Preserved (Callee-Saved)

These registers must not be changed by the assembly function unless they are saved/restored (via push/pop or store/load on the stack):

  • R2-R17
  • R28-R29 (Y-register, used for the frame pointer)

4. Special Registers

  • R1: Always assumed to be zero. If modified, it must be cleared before returning.
  • R0: Used for temporary storage, can be modified but needs special care.
  • R30-R31: Often used for pointer arithmetic (Z-register).

Push/Pop Preservation

If your assembly function uses callee-saved registers, you must push them to the stack and restore them before returning. Example:

.global my_asm_function
my_asm_function:
        push r2  ; Save registers that should be preserved
        push r3
        push r4
        ...
        ; Function logic
        ...
        pop r4   ; Restore registers before returning
        pop r3
        pop r2
        ret

What Should Not Be Used?

  • Do not modify R1 without restoring it to zero (`clr r1`).
  • Avoid corrupting callee-saved registers without restoring them (R2-R17, R28-R29).
  • Use stack (`push`/`pop`) for temporary storage if you need extra registers.

Example: Calling an Assembly Function from C++

C++ Function Prototype (Arduino Sketch)

extern "C" uint8_t my_asm_function(uint8_t x);

void setup() {
        Serial.begin(9600);
        uint8_t result = my_asm_function(42);
        Serial.println(result);
}

void loop() {}

Assembly Code (`file.S`)

.global my_asm_function
my_asm_function:
        ; Argument comes in R24
        mov r18, r24  ; Save input value in R18
        lsl r18       ; Example operation: Multiply by 2
        mov r24, r18  ; Store result in return register
        ret           ; Return value is in R24
ldi ZL,pm_lo8(foo)
ldi ZH,pm_hi8(foo)
ijmp
; indirect jmp
        ldi     zl,low(CommandTable)    ;Z points to table
        ldi     zh,high(CommandTable)
        add     zl,Mode                 ;add mode as an offset
        brcc    DoS1
        inc     zh                      ;account for a carry
DoS1:
        ijmp                            ;jump to the command to run
CommandTable:            ;table of commmands to run from menus
        rjmp    MainLoop                ;blank entry
        rjmp    DoLearnMode             ;learn the maze
        rjmp    DoSolveMode             ;solve the maze
        rjmp    DoSolveFast             ;solve the maze fast
        rjmp    DoSingleStep            ;single step debug
        rjmp    DoSensors               ;display sensor readings
        rjmp    DoShowCells             ;display cell data
        rjmp    MainLoop                ;blank entry

lo8(name), pm_lo8(name), lo8(gs( name ))

gcc generuje trampoliny pro funkce, ktere jsou moc vysoko, nebo si o to reknou

some_fn:
        ldi r16,lo8(some_fn)     ; Byte-address, low byte
        ldi r17,hi8(some_fn)     ; Byte-address, high byte
        ldi r18,hh8(some_fn)     ; Byte-address, highest byte

        ldi r19,pm_lo8(some_fn)  ; Word-address, low byte
        ldi r20,pm_hi8(some_fn)  ; Word-address, high byte
        ldi r21,pm_hh8(some_fn)  ; Word-address, highest byte

        ldi r22,lo8(gs(some_fn)) ; Word-address where the linker will
        ldi r23,hi8(gs(some_fn)) ; generate a stub as needed.

jak gcc rict, ze jde o funkci:

.global some_fn
.type some_fn,@function

Co taky jde:

subi r30,lo8(-(VRAM))
sbci r31,hi8(-(VRAM)) ; pricist adresu pomoci odecteni zaporne

See:

Registry v C a ve FORTHu

C/C++ C/C++ C/C++ FORTH FORTH FORTH comment
reg Preserve means reg Preserve means Comment
r0 No   r0 No DT 2 W = DT pointer - generated for each function again (= may be used as any other temp)
r1 No/0 zero r1 No/0 zero Should be set to zero on exit from any C / FORTH routine
r2 Yes   r2 Yes TOS 0 Top of Data STack value (for faster access)
r3 Yes   r3 Yes TOS 1 ditto
r4 Yes   r4 Yes TOS 2 ditto
r5 Yes   r5 Yes IP 0 IP Instruction pointer
r6 Yes   r6 Yes IP 1 ditto
r7 Yes   r7 Yes IP 2 ditto
r8 Yes par 9 r8 Yes RST 0 Pointer to Return STack 2 bytes - native RAM only
r9 Yes par 9 r9 Yes RST 1 ditto
r10 Yes par 8 r10 Yes   Free to later use
r11 Yes par 8 r11 Yes    
r12 Yes par 7 r12 Yes    
r13 Yes par 7 r13 Yes    
r14 Yes par 6 r14 Yes    
r15 Yes par 6 r15 Yes    
r16 Yes par 5 r16 Yes    
r17 Yes par 5 r17 Yes    
r18 No par 4 r18 No   Random temp registers
r19 No par 4 r19 No    
r20 No par 3 r20 No    
r21 No par 3 r21 No    
r22 No par 2 r22 No P 0 FORTH parametr 3 bytes
r23 No par 2 r23 No P 1 ditto
r24 No par 1 r24 No P 2 ditto
r25 No par 1 r25 No Z 2 Temporary variable/pointer
r26 Xl No   r26 No DT 0 W = DT pointer - generated for each function again (= may be used as any other temp)
r27 Xh No   r27 No DT 1 ditto
r28 Yl Yes   r28 Yes DST 0 Pointer to Data STack 2 bytes - native RAM only - point to second value (first is in TOS) - used often, Y+, -Y, Y+q
r29 Yh Yes   r29 Yes DST 1 ditto
r30 Zl No   r30 No Z 0 Temporary variable/pointer
r31 Zh No   r31 No Z 1 ditto
  • Y is DST as it can be used stack like and is preserved - Y+, -Y, Y+q
  • X can be used in "LD rx, X+" for data, so it is W = DT pointer - generated for each function again - if no longer needed, may be reuse as scratch
  • Z is probably overwritten just after entering "function" - also only register to use with ELPM and EIJMP/EICALL

ATmega stacks

  • SP grow down
  • init TOPMEM = $FFFF
old push new old pop = new old
old old old
--- <---- new --- <----
--- --- <---- ---
--- --- ---
--- --- ---
  • register X,Y,Z grow down
    • I will use THIS model for FORTH
  • init TOPMEM+1 = $10000 = $0000
  • push = ST -X, rx
  • pop = LD rx, X+
old push new old pop = new old
old <---- old old <----
--- new <---- ---
--- --- ---
--- --- ---
--- --- ---
  • register X,Y,Z grow up
  • init LOWMEM = $0000
  • push = ST X+, rx
  • pop = LD rx, -X
--- push new --- pop = new ---
--- --- <---- ---
--- <---- new --- <----
old old old
old old old
old old old