20.1 Mixing Assembly Language and C Variables and Functions

The following guidelines indicate how to interface separate assembly language modules with C modules.

  • Follow the register conventions described in 14.2 Register Conventions. In particular, registers r0-r3 are used for parameter passing. An assembly language function will receive parameters, and should pass arguments to called functions, in these registers.
  • The table in 14.2 Register Conventions describes which registers must be saved across non-interrupt function calls.
  • Interrupt functions must preserve all registers. Unlike a normal function call, an interrupt may occur at any point during the execution of a program. When returning to the normal program, all registers must be as they were before the interrupt occurred.
  • Variables or functions declared within a separate assembly file that will be referenced by any C source file should be declared as global using the assembler directive .global. Undeclared symbols used in assembly files will be treated as externally defined.

The following example shows how to use variables and functions in both assembly language and C regardless of where they were originally defined.

The file ex1.c defines cFunction and cVariable to be used in the assembly language file. The C file also shows how to call an assembly function, asmFunction, and how to access the assembly defined variable, asmVariable.

Mixing C and Assembly

      .syntax unified
      .cpu cortex-m7
      .thumb

      .global asmVariable
      .type asmVariable,%object
      .data
      .align 2
asmVariable:
      .space 4
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
      @ char *asmFunction (char *s)
      @ {
      @    asmVariable = 0;
      @    if (s) {
      @       char *d = s, c;
      @       while ((c = *d)) {
      @         if (cFunction (c)) {
      @          *d = c & cVariable;
      @          ++asmVariable;
      @       }
      @       ++d;
      @      }
      @    }
      @    return s;
      @ }
      @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
      .global asmFunction
      .type asmFunction,%function

      .text
      .align 1

      .thumb_func
asmFunction:
      @ if the input string is not NULL
      cbnz r0, .L_not_NULL

      @ set 'asmVariable' to zero and return
      ldr    r1, =asmVariable
      str    r0, [r1]
      bx     lr

.L_not_NULL:
      @ r4-r7 are callee-saved registers
      @ LR contains the return address
      @ r0 is the first argument and also the return value of the
function
      push {r0, r4-r7, lr}

      @ d = s;
      mov r4, r0

      @ r6 - the value of the C variable (8-bit AND mask)
      @ r7 - counter of changed chars
      ldr     r6, =cVariable
      movs    r7, #0
      ldrb    r6, [r6]

.L_while:
      @ while ((c = *d))
      ldrb r5, [r4]
      cbz r5, .L_end_while
      @ if (cFunction (c))
      mov   r0, r5
      bl    cFunction
      cbz r0, .L_next_char

      @ *d = c & cVariable;
      ands r5, r5, r6
      strb r5, [r4]

      @ ++ the number of changed chars
      adds r7, r7, #1

.L_next_char:
      @ ++d;
      adds r4, r4, #1
      b .L_while

.L_end_while:
      @ write the no. of changes to 'asmVariable'
      ldr    r0, =asmVariable
      str    r7, [r0]

      @ return s;
      pop {r0, r4-r7, pc}

      .pool
      .size asmFunction, .-asmFunction

The file ex1.S defines asmFunction and asmVariable as required for use in a linked application. The assembly file also shows how to call a C function, cFunction, and how to access a C defined variable, cVariable.

#include <xc.h>
#include <stdio.h>

extern int asmVariable;
extern char *asmFunction (char *s);

char cVariable = 0xDF;

char cFunction (char c)
{
   return c >= 'a' && c <= 'z';
}

int main()
{
   char s[] = "heLLo, wOrlD!";
   printf ("%s\n", s);

   char *d = asmFunction (s);
   printf ("%s\nchanges: %d", d, asmVariable);

   return 0;
}

In the C file, ex2.c, although for the function declaration this isn’t required, note that asmFunction is a char * function and is declared accordingly.

In the assembly file, ex1.S, the symbols asmFunction and asmVariable are made globally visible through the use of the .global assembler directive and can be accessed by any other source file.