How do you return values from an assembly language program to a calling sketch?

I have written a small assembly language routine that is supposed to return a value to a sketch, but it doesn't appear to be working. I thought to return a value from a assembly function one would push the return value onto the stack and the caller function would pop it off and store it, but this doesn't appear to be the case. How is it done?

FastMath.ino

extern "C" {
  void start();
  uint32_t addition();
  uint32_t subtract();
  uint32_t multiply();
  uint32_t divide();
}

void setup() {
  Serial.begin(9600);
  while( !Serial )
    ;
    
  start();
  Serial.println("Initialized.");
  //Serial.flush();
  
  uint32_t result = 0;
  addition();
  Serial.println("Add returned: ");
  Serial.println(result);
}

ASM_Func.s

// Special function register for AVR, for better backward compatibility as
// used by avr-libc's register definitions (default value is 0x20)
#define __SFR_OFFSET 0x00

// Include Arduino style register references (e.g PORTB, PORTD etc,.)
#include "avr/io.h"

// Make the functions accessible from the sketch so that we can call them
.global start
.global addition
.global subtract
.global multiply
.global divide

// Initialize
start:
  ret                 ; Return to caller

addition:
  push 10
  ret
subtract:
  push 10
  ret
multiply:
  push 10
  ret
divide:
  push 10
  ret

When calling functions in AVR with C and assembler, that is used for passing and returning is a specific register, not the stack.
See Chapter 6 of the application note from Microchip below.

Also, strictly follow the register usage shown in Chapter 5.
Otherwise you will easily destroy the sketch.

For example, the assembly code that takes two unsigned 8 bit value as arguments and returns the multiplicated 16 bit value is as follows.

[ MyASM.ino ]

extern "C"
{
  unsigned int sample1(byte x, byte y);
}

void setup()
{
  Serial.begin(9600);
  unsigned int i = sample1(200, 50);
  Serial.println(i); // 10000 shown
}

void loop()
{
}

[ ASMfunc.S ]

.global sample1
.func sample1
      sample1:
        push r0
        mul r24, r22
        movw r24, r0 
        clr r1
        pop r0
        ret
.endfunc

Note:
All arguments use an even number of registers, so 8-bit arguments are also passed as register pairs.
Also, the result of the hardware multiplier is returned in R0 and R1, so this register needs to be saved and restored as the above document shows.

3 Likes

Thanks.

Why is r1 cleared? From the ATMEL document I see that the result of a mul operation is placed in r1 and r0 (High byte, Low byte order). If r1 is cleared, doesn't this clear the high byte of the result, leaving only the low byte?

I told you to look at Chapter 5 of the document shown above, did you see it? need snippet?
Since r1 is used as a zero register and the compiler thinks it is always zero, It's a problem to leave high byte of the multiplication result to r1.
The result of the multiplication has already been copied to r25:r24 using the instruction "movw" to pass as a return value.

Thanks. I get it.

What is fast about routines that communicate via global variables?

If you read the question, I asked how you return values from an assembler function. :^

Please tell me what is multiplied by multiply(); and how that data gets there.

If your assembly code had successfully processed parameters provided by C on the AVR before,
you would have stumbled over the return value convention.

If it is only a nonsensical example, so be it.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.