How to write C & Assembly together

How to write C and Assembly together in dsPIC

Tags: Mixed C, how to write mixed c and assembly, how to write c and assembly ,tutorials on mixed c,,
Overall rating
Introduction of MPLAB

The MPLAB X IDE is the new graphical, integrated debugging tool set for all of Microchip’s more than 800 8-bit, 16-bit and 32-bit MCUs and digital signal controllers, and memory devices.

It includes a feature-rich editor, source-level debugger, project manager, software simulator, and supports Microchip’s popular hardware tools, such as the MPLAB ICD 3 in-circuit debugger, PICkit™ 3, and MPLAB PM3 programmer.

MPLAB IDE is a Windows Operating System (OS) software program that runs on a PC to develop applications for Microchip microcontrollers and digital signal controllers. It is called an Integrated Development Environment, or IDE, because it provides a single integrated "environment" to develop code for embedded microcontrollers. Experienced embedded systems designers may want to skip ahead to Components of MPLAB IDE.

The Object-HEX Converter creates Intel HEX files from absolute object modules.

ASSEMBLER Introduction

The assembler is a Windows console application that provides a platform for developing assembly language code. The dsPIC Assembler is a program that translates symbolic code into executable object code. This object code can be executed with a dsPIC-based microcontroller. If you have ever written a computer program directly in machine-recognizable form, such as binary or hexadecimal code, you will appreciate the advantages of programming in symbolic assembly language.

Assembly language operation codes are designed to be easy to remember. You may symbolically express addresses and values referenced in the operand field of instructions. Since you assign these names, you can make them as meaningful as the mnemonics for the instructions.

An assembly program consists of three types of constructs:

  • Machine instructions which are code the machine can execute.
  • Assembler directives which define the program structure and symbols, and generate non-executable code (data, messages, and so on.).
  • Assembler controls which set assembly modes and direct assembly flow.

C C30 Introduction

The C programming language is a general-purpose programming language that provides code efficiency, elements of structured programming, and a rich set of operators. C is not a big language and is not designed for any one particular area of application. Its generality, combined with its absence of restrictions, makes C a convenient and effective programming solution for a wide variety of software tasks.

Many applications can be solved more easily and efficiently with C than with other more specialized languages.

The C30 Compiler is not a universal C compiler adapted for the dsPIC target. It is a ground-up implementation, dedicated to generating extremely fast and compact code for dsPIC microcontrollers. The C30 Compiler provides you with the flexibility of programming in C and the code efficiency and speed of assembly language.

The C language on its own is not capable of performing operations (such as input and output) that would normally require intervention from the operating system. Instead, these capabilities are provided as part of the standard library. Because these functions are separate from the language itself, C is especially suited for producing code that is portable across a wide number of platforms.

MPLAB C30 is an ANSI x3.159-1989-compliant, optimizing C compiler that includes language extensions for dsPIC DSC embedded-control applications.

The compiler is a Windows® console application that provides a platform for developing C code. The compiler is a port of the GCC compiler from the Free Software Foundation.

The MPLAB C Compiler for PIC24 MCUs and dsPIC DSCs compiles C source files, producing assembly language files.

These compiler-generated files are assembled and linked with other object files and libraries to produce the final application program in executable COFF or ELF file format.

The COFF or ELF file can be loaded into the MPLAB IDE, where it can be tested and debugged, or the conversion utility can be used to convert the COFF or ELF file to Intel® hex format, suitable for loading into the command- line simulator or a device programmer.

Interfacing C program to Assembler

In some situations you may find it necessary to mix C and assembly language in the same program. For example, a program may require particular routines which are performance-critical, and which must therefore be hand-coded in order to run at optimum speed.

You can easily interface your programs to ASM routines written in dsPIC Assembler. All you need to do is follow few programming rules; you can call assembly routines from C and vice-versa. Public variables declared in assembly modules a re-available to your C program.

There may be several reasons to call an assembly routine like faster execution of program, accessing SFRs to directly using assembly etc.

For any assembly routine to be called from C program, you must know how to pass parameters or arguments to function and get return values from a function.

Most people prefer C to assembly, and there are rarely any significant speed differences between writing in C and writing in assembly.

The C compiler is so good at optimizing code that it can usually match or surpass the efficiency of any average to moderate skill assembly programmer. However, the C compiler is not perfect, and there are still times when people want to use assembly.

This is fine, but even in these times; it is rarely to write an entire program. Instead, you usually just want to hand-optimize a particular function which needs to have optimum speed.

The MPLAB development tools for dsPIC offer numerous features and advantages that help you quickly and successfully develop embedded applications. They are easy to use and are guaranteed to help you achieve your design goals.

The dsPIC Software Development Toolkit enables AOF object files to be generated from C and assembly language source by the appropriate tools (compiler and assembler, respectively), and then linked with one or more libraries to produce an executable file, as shown below:


Irrespective of the language in which they are written, routines that make cross-calls to other modules need to observe a common convention of argument and result passing. For the ARM, this convention is called the ARM Procedure Call Standard, or APCS.

Inline Assembly

One of the most common methods for using assembly code fragments in a C programming project is to use a technique called inline assembly. Inline assembly is invoked in different compilers in different ways.

Also, the assembly language syntax used in the inline assembly depends entirely on the assembly engine used by the C compiler. Microsoft C++, for instance, only accepts inline assembly commands in MASM syntax, while GNU GCC only accepts inline assembly in GAS syntax (also known as AT&T syntax). This page will discuss some of the basics of mixed-language programming in some common compilers.

Microsoft C Compiler


  void foo(char x, char y){
      x = x + 1;
      y = y + 2;



Linked assembly

When an assembly source file is assembled by an assembler, and a C source file is compiled by a C compiler, those two object files can be linked together by a linker to form the final executable. The beauty of this approach is that the assembly files can write using any syntax and assembler that the programmer is comfortable with. Also, if a change needs to be made in the assembly code, all of that code exists in a separate file, that the programmer can easily access.

The only disadvantages of mixing assembly and C in this way are that a) both the assembler and the compiler need to be run, and b) those files need to be manually linked together by the programmer. These extra steps are comparatively easy, although it does mean that the programmer needs to learn the command-line syntax of the compiler, the assembler, and the linker.


Advantages of inline assembly

  • Short assembly routines can be embedded directly in C function in a C code file.
  • The mixed-language file then can be completely compiled with a single command to the C compiler (as opposed to compiling the assembly code with an assembler, compiling the C code with the C Compiler, and then linking them together).
  • This method is fast and easy.
  • If the in-line assembly is embedded in a function, then the programmer doesn't need to worry about #Calling Conventions, even when changing compiler switches to a different calling convention.

Advantages of linked assembly

  • If a new microprocessor is selected, all the assembly commands are isolated in a ".asm" file.
  • The programmer can update just that one file -- there is no need to change any of the ".c" files (if they are portably written).

Calling Conventions

When writing separate C and Assembly modules, and linking them with your linker, it is important to remember that a number of high-level C constructs are very precisely defined, and need to be handled correctly by the assembly portions of your program. Perhaps the biggest obstacle to mixed-language programming is the issue of function calling conventions. C functions are all implemented according to a particular convention that is selected by the programmer (if you have never "selected" a particular calling convention, it's because your compiler has a default setting).

This page will go through some of the common calling conventions that the programmer might run into, and will describe how to implement these in assembly language.

Code compiled with one compiler won't work right when linked to code compiled with a different calling convention. If the code is in C or another high-level language (or assembly language embedded in-line to a C function), it's a minor hassle -- the programmer needs to pick which compiler / optimization switches she wants to use today, and recompile every part of the program that way. Converting assembly language code to use a different calling convention takes more manual effort and is more bug-prone.

Unfortunately, calling conventions are often different from one compiler to the next -- even on the same CPU. Occasionally the calling convention changes from one version of a compiler to the next, or even from the same compiler when given different "optimization" switches.

Unfortunately, many times the calling convention used by a particular version of a particular compiler is inadequately documented. So assembly-language programmers are forced to use reverse engineering techniques to figure out the exact details they need to know in order to call functions written in C, and in order to accept calls from functions written in C.

The typical process is:

  • Write a ".c" file with stubs ... details??? ... ... exactly the same number and type of inputs and outputs that you want the assembly-language function to have.
  • Compile that file with the appropriate switches to give a mixed assembly-language-with-c-in-comments file (typically a ".cod" file). (If your compiler can't produce an assembly language file, there is the tedious option of disassembling the binary ".obj" machine-code file).
  • Copy that ".cod" file to an ".asm" file. (Sometimes you need to strip out the compiled hex numbers and comment out other lines to turn it into something the assembler can handle).
  • Test the calling convention -- compile the ".asm" file to an ".obj" file, and link it (instead of the stub ".c" file) to the rest of the program. Test to see that "calls" work properly.
  • Fill in your ".asm" file -- the ".asm" file should now include the appropriate header and footer on each function to properly implement the calling convention. Comment out the stub code in the middle of the function and fill out the function with your assembly language implementation.


Typically programmer single-steps through each instruction in the new code, making sure it does what they wanted it to do.

Parameter Passing

Normally, parameters are passed between functions (either written in C or in Assembly) via the stack.

For example, if a function foo1 () calls a function foo2 () with 2 parameters (say characters x and y), then before the control jumps to the starting of foo2 (), two bytes (normal size of a character in most of the systems) are filled with the values that need to be passed.

Once control jumps to the new function foo2 (), and you use the values (passed as parameters) in the function, they are retrieved from the stack and used.

There are two parameter passing techniques in use,

  • Pass by Value
  • Pass by Reference

Parameter passing techniques can also use,

  • right-to-left (C-style)
  • left-to-right (Pascal style)

On processors with lots of registers the standard calling convention puts *all* the parameters (and even the return address) in registers.

Some calling conventions allow "re-entrant code".

Pass by Value

With pass-by-value, a copy of the actual value (the literal content) is passed. For example, if you have a function that accepts two characters like,


   void foo (char x, char y)

       x = x + 1;
       y = y + 2;


And you invoke this function as follows,

char a,b;
   foo (a,b);

And then the program pushes a copy of the ASCII values of 'A' and 'B' (65 and 66 respectively) onto the stack before the function foo is called. You can see that there is no mention of variables 'a' or 'b' in the function foo (). So, any changes that you make to those two values in foo will not affect the values of a, b in the calling function.

Pass by Reference

Imagine a situation where you have to pass a large amount of data to a function and apply the modifications, done in that function, to the original variables. An example of such a situation might be a function that converts a string with lower case alphabets to upper case.

It would be an unwise decision to pass the entire string (particularly if it is a big one) to the function, and when the conversion is complete, pass the entire result back to the calling function. Here we pass the address of the variable to the function. This has two advantages, one, you don't have to pass huge data, thereby saving execution time and two, you can work on the data right away so that by the end of the function, the data in the calling function is already modified.

But remember, any change you make to the variable passed by reference will result in the original variable getting modified. If that's not what you wanted, then you must manually copy the variable before calling the function.

Join the World's Largest Technical Community

we respect your privacy.