How to write C & Assembly together

How to write C and Assembly together in KEIL

Tags: Writing C and assembly together in Keil, introduction for arm assembler, Interfacing C program to Assembler,Interfacing C and Assembly Code, Using GCC Compiler to write C & Assembly together,, Using InLine Assembler, Example Program to write C & Assembly together, Sample Program to write C & Assembly together in Keil
Overall rating

The Keil Software LPC2148 development tools listed below are programs you use to compile your C code, assemble your assembly source files, link and locate object modules and libraries, create HEX files, and debug your target program.

µVision for Windows™ is an Integrated Development Environment that combines project management, source code editing, and program debugging in one single, powerful environment.

The ARM7 ANSI Optimizing C Compiler creates re locatable object modules from your C source code. The ARM Macro Assembler creates re locatable object modules from your LPC21XX assembly source code. The Linker/Locator combines re-locatable object modules created by the Compiler and the Assembler into absolute object modules.

The Library Manager combines object modules into libraries that may be used by the linker.

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

ASSEMBLER ARM Introduction

The ARM Assembler is a program that translates symbolic code (assembly language) into executable object code. This object code can be executed with an ARM7-based or ARM9-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 (mnemonics) 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. Detailed discussion of the machine instructions is in the hardware manuals of the ARM microcontroller.
  • 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 ARM 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 C ARM Compiler is not a universal C compiler adapted for the ARM target. It is a ground-up implementation, dedicated to generating extremely fast and compact code for ARM microcontrollers. The C ARM 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.

Since the C ARM Compiler is a cross compiler, some aspects of the C programming language and standard libraries are altered or enhanced to address the peculiarities of an embedded target processor.

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 LPC2148 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 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.

The Keil development tools for ARM 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.

Sometimes, it is difficult and inefficient for programming in Assembly. We may want to use C/C++ for fast prototyping. Firstly, we have to prepare the GNU tool chain for ARM (compiler, assembler, linker ...etc).

The ARM 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.

Using the ARM Procedure Call Standard

The ARM Procedure Call Standard, or APCS, is a set of rules which govern calls between functions in separately compiled or assembled code fragments.

The APCS defines:

  • Constraints on the use of registers.
  • Stack conventions.
  • The format of a stack back traces data structure.
  • Argument passing and result return.
  • Support for the ARM shared library mechanism.

Code which is produced by compilers is expected to adhere to the APCS at all times. Such code is said to be strictly conforming.

Handwritten code is expected to adhere to the APCS when making calls to externally visible functions. Such code is said to be conforming.

The ARM Procedure Call Standard comprises a family of variants. Each variant is exclusive, so that code which conforms to one cannot be used with code that conforms to another. Your choice of variant will depend on whether

  • The Program Counter is 32-bit or 26-bit.
  • Stack limit checking is explicit (performed by code) or implicit (memory management hardware).
  • Floating point values are passed in floating point registers.
  • Code is reentrant or non-reentrant.

Register names and usage

The following table summarizes the names and uses allocated to the ARM and Floating Point registers under the APCS.


A1-A4, F0-F3 are used to pass arguments to functions. A1 is also used to return integer results and f0 to return FP results. These registers can be corrupted by a called function.

V1-V5, F4-F7 are used as register variables. They must be preserved by called functions.

Register Number





argument 1 / integer result / scratch register



argument 2 / scratch register



argument 3 / scratch register



argument 4 / scratch register



register variable



register variable



register variable



register variable



register variable



static base / register variable



stack limit / stack chunk handle / reg. variable



frame pointer



scratch register / new-SB in inter-link-unit calls



lower end of current stack frame



link address / scratch register



program counter



FP argument 1 / FP result / FP scratch register



FP argument 2 / FP scratch register



FP argument 3 / FP scratch register



FP argument 4 / FP scratch register


Table. ACPS Registers

SB, SL, FP, IP, SP, IR, PC have a dedicated role in APCS variants some of the time, when some of the registers used for other purposes when conforming to APCS.

In some variants of the APCS SB and SL are available as additional variable registers v6 and v7 respectively.

As stated previously, hand-coded assembler routines need not conform strictly to the APCS, but need only conform. This means that all registers which do not need to be used in their APCS role by an assembler routine (e.g. FP) can be used as working registers provided that their value on entry is restored before returning.

An example of APCS registers usage: 64-bit integer addition

This example illustrates how to code a small function in ARM assembly language, such that it can be used from C modules.

First, however, we will write the function in C, and examine the compiler’s output.

The function will perform a 64-bit integer addition using a two-word data structure to store each 64-bit operand.

In assembler, the obvious way to code the addition of double-length integers would be to use the Carry flag from the low word addition in the high word addition. However, in C there is no way of specifying the Carry flag, so we have to use a workaround, as follows:


void add_64(int64 *dest, int64 *src1, int64 *src2)


unsigned hibit1=src1->lo >> 31, hibit2=src2->lo >> 31, hibit3;

dest->lo=src1->lo + src2->lo;

hibit3=dest->lo >> 31;

dest->hi=src1->hi + src2->hi +

((hibit1 & hibit2) || (hibit1!= hibit3));




The highest bits of the low words in the two operands are calculated (shifting them into bit 0). These are then used to determine the value of the carry bit.

Examining the compiler's output

If the addition routine were to be used a great deal, a poor implementation such as this would probably be inadequate. To see just how good or bad it is, let us look at the actual code which the compiler produces.

Copy the file add64_1.c from directory examples/c and asm to your current working directory and compile it to ARM assembly language source as follows:

armcc -li -apcs 3/32bit -S add64_1.c

The -S flag tells the compiler to produce ARM assembly language source (suitable for armasm) instead of object code. The -li flag tells it to compile for little-endian memory and the –apcs option specifies the 32-bit version of APCS 3. You can omit these options if your compiler is configured to have them as defaults.

Looking at the output file, add64_1.s, we can see that this is indeed an inefficient implementation.



STMDB              sp!, {v1, lr}

LDR                     v1, [a2, #0]

MOV                   a4, v1, LSR #31

LDR                     ip, [a3, #0]

MOV                   lr, ip, LSR #31

ADD                    ip, v1, ip

STR                      ip, [a1, #0]

MOV                   ip, ip, LSR #31

LDR                     a2, [a2, #4]

LDR                     a3, [a3, #4]

ADD                    a2, a2, a3

TST                      a4, lr

TEQEQ               a4, ip

MOVNE             a3, #1

MOVEQ             a3, #0

ADD                    a2, a2, a3

STR                      a2, [a1, #4]!

LDMIA                sp!, {v1, pc}


Modifying the compiler's output

Let us return to our original intention of coding the 64-bit integer addition using the Carry flag.

Since use of the Carry flag cannot be specified in C, we must get the compiler to produce almost the right code, and then modify it by hand. Let us start with (incorrect) code which does not perform the carry addition:


void  add_64(int64 *dest, int64 *src1, int64 *src2)


dest->lo=src1->lo + src2->lo;

dest->hi=src1->hi + src2->hi;




You will find this in file examples/c and asm/add64_2.c. Copy it to your current working directory, and then compile it to assembler source with the command:

armcc -li -apcs 3/32bit -S add64_2.c

This produces source in add64_2.s, which will include something like the following code.



LDR            a4, [a2, #0]

LDR            IP, [a3, #0]

ADD          a4, a4, IP

STR            a4, [a1, #0]

LDR            a2, [a2, #4]

LDR            a3, [a3, #4]

ADD          a2, a2, a3

STR            a2, [a1, #4]

MOV         PC, IR


Comparing this to the C source we can see that the first ADD instruction produces the low order word, and the second produces the high order word. All we need to do to get the carry from the low to high word right is change the first ADD to ADDS (add and set flags), and the second ADD to an ADC (add with carry). This modified code is available in directory examples/c and asm as add64_3.s.

What effect did the APCS have?

The most obvious way in which the APCS has affected the above code is that the registers have all been given APCS names.

Here, a1 holds a pointer to the destination structure, while a2 and a3 hold pointers to the operand structures. Both a4 and IP are used as temporary registers which are not preserved. The conditions under which IP can be corrupted will be discussed later in this chapter.

This is a simple leaf function, which uses few temporary registers, so none are saved to the stack and restored on exit. Therefore a simple MOV PC, IR can be used to return.

If we wished to return a result—perhaps the carry out from the addition—this would be loaded into a1 prior to exit. We could do this by changing the second ADD to ADCS (add with carry and set flags), and adding the following instructions to load a1 with 1 or 0 depending on the carry out from the high order addition.

MOV a1, #0

ADC a1, a1, #0

Back to the first implementation

Although the first C implementation is inefficient, it shows us more about the APCS than the hand-modified version.

We have already seen a4 and IP being used as non-preserved temporary registers. However, here v1 and IR are also used as temporary registers. V1 is preserved by being stored (together with IR) on entry. IR is corrupted, but a copy is saved onto the stack and then reloaded into pc when v1 is restored.

Thus there is still only a single exit instruction, but now it is:

LDMIA sp!, {v1, pc}

A more detailed look at APCS register usage

We stated initially that SB, SL, FP, IP, SP and IR are dedicated registers, but the example showed IP and IR being used as temporary registers. Indeed, there are times when these registers are not used for their APCS roles.

It will be useful for you to know about these situations, so that you can write efficient (but safe) code which uses as many of the registers as possible and so avoids unnecessary saving and restoring of registers.

  • IP is used only during function calls. It is conventionally used as a local code generation temporary register. At other times it can be used as a corruptible temporary register.
  • IR holds the address to which control must return on function exit. It can be used as a temporary register after pushing its contents onto the stack. This value can then be reloaded straight into the PC.
  • SP is the stack pointer, which is always valid in strictly conforming code, but need only be preserved in handwritten code. Note, however, that if any use of the stack is to be made by handwritten code, sp must be available.
  • SL is the stack limit register. If stack limit checking is explicit (i.e. is performed by code when stack pushes occur, rather than by memory management hardware causing a trap on stack overflow), SL must be valid whenever sp is valid. If stack checking is implicit SL is instead treated as v7, an additional register variable (which must be preserved by called functions).
  • FP is the frame pointer register. It contains either zero, or a pointer to the most recently created stack back trace data structure. As with the stack pointer this must be preserved, but in handwritten code does not need to be available at every instant. However it should be valid whenever any strictly conforming functions are called.
  • SB is the static base register. If the variant of the APCS in use is reentrant, this register is used to access an array of static data pointers to allow code to access data repeatedly. However, if the variant being used is not reentrant, SB is instead available as an additional register variable, v6 (which must be preserved by called functions).

SP, SL, FP and SB must all be preserved on function exit for APCS conforming code.

Passing and Returning Structures

This section covers:

  • The default method for passing structures to and from functions.
  • Cases in which this is automatically optimized.
  • Telling the compiler to return a struct value in several registers.

The default method

Unless special conditions apply (detailed in following sections), C structures are passed in registers which if necessary overflow onto the stack and are returned via a pointer to the memory location of the result. For struct-valued functions, a pointer to the location where the struct result is to be placed is passed in a1 (the first argument register). The first argument is then passed in a2, the second in a3, and so on.

It is as if:

Struct s f (int x)

Were compiled as:

void f (struct s *result, int x)

Consider the following code:


typedef    struct    two_ch_struct


char ch1;


char ch2;

} two_ch;


two_ch max( two_ch a, two_ch b )


 return (a.ch1>b.ch1) ? a : b;



This is available in the directory examples/c and asm as two_ch.c. It can be compiled to produce assembly language source using:

armcc -S two_ch.c -li -apcs 3/32bit

Where -li and -apcs 3/32bit can be omitted if armcc has been configured appropriately.

Here is the code which armcc produced (the version of armcc supplied with your release may produce slightly different output to that listed here):



MOV               ip, sp


STMDB           sp!, {a1-a3, fp, ip, lr, pc}

SUB                 fp, ip, #4

LDRB              a3, [fp, #-&14]

LDRB              a2, [fp, #-&10]

CMP                a3, a2

SUBLE             a2, fp, #&10

SUBGT            a2, fp, #&14

LDR                 a2, [a2, #0]

STR                 a2, [a1, #0]

LDMDB          fp, {fp, sp, pc}


The STMDB instruction saves the arguments onto the stack, together with the frame pointer, stack pointer, link register and current PC value.

Here, a2 and a3 are then used as temporary registers to hold the required part of the structures passed, and a1 is a pointer to an area in memory in which the resulting struct is placed—all as expected.

Returning integer-like structures

The ARM Procedure Call Standard specifies different rules for returning integer-like structures.

An integer-like structure

  • Is no larger than one word in size.
  • Exclusively has sub-fields whose byte offset is 0.

The following structures are integer-like:




 unsigned a:8, b:8, c:8, d:8;


union polymorphic_ptr


struct A *a;

struct B *b;

int *i;



Whereas the structure used in the previous example is not:

struct { char ch1, ch2; }

An integer-like structure has its contents returned in al. This means that a1 is not needed to pass a pointer to a result struct in memory, and is instead used to pass the first argument.

For example, consider the following code:


typedef   struct  half_words_struct


unsigned field1:16;

unsigned field2:16;

} half_words;

half_words    max( half_words a, half_words b )


half_words  x;

x =  (a.field1>b.field1) ? a : b;

return x;



Arguments a, b will be passed in registers a1 and a2, and since half_word_struct is integer-like we expect a1 to return the result structure directly, rather than a pointer to it.

The above code is available in directory examples/candasm as half_str.c. It can be compiled to produce assembly language source using:

armcc -S half_str.c -li -apcs 3/32bit

where -li and -apcs 3/32bit can be omitted if armcc has been configured appropriately.

Here is the code which armcc produced:



MOV                   a3, a1, LSL #16

MOV                   a3, a3, LSR #16

MOV                   a4, a2, LSL #16

MOV                   a4, a4, LSR #16

CMP                    a3, a4

MOVLE              a1, a2

MOV                   pc, lr


From this we can see that the content of the half_words structure is returned directly in a1 as expected.

Returning non integer-like structures in registers

There are occasions when a function needs to return more than one value. The normal way to achieve this is to define a structure which holds all the values to be returned, and return this.

This will result in a pointer to the structure being passed in a1, which will then be de-referenced to store the values returned.

For some applications in which such a function is time-critical, the overhead involved in “wrapping” and then “un-wrapping” the structure can be significant. However, there is a way to tell the compiler that a structure should be returned in the argument registers a1 - a4. Clearly this is only useful for returning structures that are no larger than four words.

The way to tell the compiler to return a structure in the argument registers is to use the keyword __value_in_regs.

Example: returning a 64-bit Result

To illustrate how to use __value_in_regs, let us consider writing a function which multiplies two 32-bit integers together and returns a 64-bit result.

The way this function must work is to split the two 32-bit numbers (a, b) into high and low 16-bit parts (a_hi, a_lo, b_hi, b_lo). The four multiplications a_lo * b_lo, a_hi * b_lo, a_lo * b_hi, a_hi * b_lo must be performed and the results added together, taking care to deal with carry correctly.

Since the problem involves manipulation of the Carry flag, writing this function in C will not produce optimal code. We will therefore have to code the function in ARM assembly language. The following performs the algorithm just described:


; On entry a1 and a2 contain the 32-bit integers to be multiplied (a, b)

; On exit a1 and a2 contain the result (a1 bits 0-31, a2 bits 32-63)


MOV         ip, a1, LSR #16 ; ip = a_hi

MOV         a4, a2, LSR #16 ; a4 = b_hi

BIC             a1, a1, ip, LSL #16 ; a1 = a_lo

BIC             a2, a2, a4, LSL #16 ; a2 = b_lo

MUL          a3, a1, a2 ; a3 = a_lo * b_lo (m_lo)

MUL          a2, ip, a2 ; a2 = a_hi * b_lo (m_mid1)

MUL          a1, a4, a1 ; a1 = a_lo * b_hi (m_mid2)

MUL          a4, ip, a4 ; a4 = a_hi * b_hi (m_hi)

ADDS        ip, a2, a1 ; ip = m_mid1 + m_mid2 (m_mid)

ADDCS      a4, a4, #&10000 ; a4 = m_hi + carry (m_hi')

ADDS        a1, a3, ip, LSL #16 ; a1 = m_lo + (m_mid<<16)

ADC                    a2, a4, ip, LSR #16 ; a2 = m_hi' + (m_mid>>16)

                   + carry

MOV         pc, lr


This code is fine for use with assembly language modules, but in order to use it from C we need to tell the compiler that this routine returns its 64-bit result in registers. This can be done by making the following declarations in a header file


typedef struct int64_struct


unsigned int lo;

unsigned int hi;

} int64;

__value_in_regs extern int64 mul64(unsigned a, unsigned b);


The above assembly language code and declarations, together with a test program, are all in directory examples/candasm as the files mul64.s, mul64.h, int64.h and multest.c. To compile, assemble and link these to produce an executable image suitable for armsd, first copy them to your current directory, and then execute the following commands:


armasm    mul64.s   -o   mul64.o   -li

armcc -c   multest.c   -li   -apcs    3/32bit

armlink mul64.o multest.o libpath/armlib.32l -o multest


where libpath is the directory in which the semi-hosted C libraries reside.

Note that -li and -apcs 3/32bit can be omitted if armcc and armasm (and armsd, below) have been configured appropriately.

multest can then be run under armsd as follows:


> armsd -li multest

A.R.M. Source-level Debugger, version 4.10 (A.R.M.) [Aug 26 1992]

ARMulator V1.20, 512 Kb RAM, MMU present, Demon 1.01, FPE, Little endian.

Object program file multest

armsd: go

Enter two unsigned 32-bit numbers in hex eg.(100 FF43D)

12345678 10000001

Least significant word of result is 92345678

Most significant word of result is 1234567

Program terminated normally at PC = 0x00008418

0x00008418: 0xef000011 .... : > swi 0x11

armsd: quit




To convince you that __value_in_regs is being used, try removing it from mul64.h, recompile multest.c, relink multest, and re-run armsd. This time the answers returned will be incorrect, since the result is no longer being returned in registers, but in a block of memory instead.


The first half of this chapter describes including assembly code inside of you .c files. The second half describes linking a stand-alone .s assembly file in with your project. These examples only apply for GCC and NOT the arm SDT compiler.

The normal code used in GBA games is C that is compiled to Thumb asm. Thumb code tends to be smaller than ARM code and has better performance when executed from ROM. If you plan to mix Thumb and ARM code then you need to use the following compiler option:

GCC allows you to destroy r0, r1, r2, r3, & r12 in your own stand-alone assembly code procedures. All other registers must be preserved. In this aspect, GCC follows the APCS (Arm Procedure Call Standard).

However, this does not apply to inline assembly code in C. For inline assembly you may destroy registers but you must specify which you have destroyed.

First, make sure that you add '-mthumb-interwork' to your compile options for both the compiler (GCC) and the assembler (GAS).


Generate code which supports calling between the ARM and THUMB instruction sets. Without this option the two instruction sets cannot be reliably used inside one program.

The default is `-mno-thumb-interwork', since slightly larger code is generated when `-mthumb-interwork' is specified.

NOTE: The following inline examples assume you are using a thumb C compiler.

If you are using GCC 3.0 or later, then your C compiler supports both thumb & ARM. As a result, you need to use the following compiler option to force thumb mode: -mthumb

Single Line Assembly in C

For 'simple' Thumb asm routines in Thumb C code you can use a single-line asm (""); instruction:

Example: asm (" mov r0,#0");

You may need to insert "volatile" after the "asm" statement.

Read below for the reason why. Note that if you are compiling C code into Thumb assembly then a .CODE 16 is inserted by the C compiler after every asm (""); statement. If you are compiling C code into ARM assembly then a .CODE 32 is inserted by the C compiler after every asm (""); statement.

This means that the following will not work when compiling C code to Thumb assembly:

asm(" .arm");

asm(" mov r0,r1");

Instead, you must do the following (.arm and .code 32 are the same thing):

asm(" .arm mov r0,r1");


asm(" .arm\n" " mov r0,r1\n");


asm(" .arm\n\mov r0,r1\n");

If you don't want to mess with the above format then try the following line.

This allows you to inline assembly code in standard assembler format (Be sure to modify your make file or Make Depend file so that changes to my file.s cause a recompile of the C file that is including my_file.s):

asm volatile (".include \"my_file.s\"");

Immediate data in Assembly in C

To include immediate values into inline assembly code, it is easiest to use the following format:

asm(" ldr r0,=0x12345678");

Replace 0x12345678 with the value you require. This method will auto-optimize to the smallest code and it will put the actual data, if needed, into the nearest .pool section.


The ARM compilers feature a built-in 'inline' assembler. The inline assembler built into the ARM compilers is a 'high-level' assembler, allowing you to use most ARM assembly language instructions in a C or C++ program.

You can use the inline assembler to:

  • Use features of the target processor that cannot be accessed from C
  • Achieve more efficient code.

The inline assembler offers very flexible interworking with C and C++. Any register operand can be an arbitrary C or C++ expression.

However, it is not intended to be a 'WYSIWYG' assembler (where the object code produced is identical to the written source) - that is the role of arm asm.

Note that the inline assembler appears as deprecated in RVCT versions 3.1, 4.0 and 4.1 and warnings will be shown when using the inline assembler with these tools.

However, it is no longer deprecated in the ARM Compiler Tools version 5.0 and onward; therefore, there is no need to avoid use of the inline assembler at this time.

The following shows an example that enables/disables interrupts by reading from and writing to the CPSR. Three versions are shown. Version 1 is deliberately incorrect. The problems are corrected in Version 2.

Example - Version 1

The following example, which enables/disables interrupts, illustrates some pit-falls.


void ChangeIRQ (unsigned char NewState)


       NewState = ( ~NewState ) << 7;


             STMDB SP!, {R1}

             MRS R1, CPSR

             BIC R1, R1, #0x80  

             ORR R1, R1, R0

             MSR CPSR, R1

             LDMIA SP!,{R1}




As a high-level assembler, the low-level capabilities of the inline assembler are restricted.

You cannot modify the program counter or stack pointer. The example code above fails because it tries to stack and restore R1. This is not necessary, because the compiler will stack/restore any working registers as required automatically. It is not allowed to explicitly stack/restore work registers.

You cannot read a register without writing to it first, because this results in an uninitialized variable. The example code above reads R1 to put it onto the stack, but R1 has not been initialized (it is undefined), so an error will be reported.

This is all very similar to C, but is rather different to a normal assembler! For a more comprehensive list of restrictions, please refer to the documentation.

Example - Version 2

Fortunately, there is an easy solution to the above problems. Instead of trying to stack work registers on entry to a __asm block, create C variables to hold working data.

The compiler's register allocator will then select the most appropriate registers to use, and the inline assembler will perform any stacking which is needed automatically.

Remember that the inline assembler can only handle integer assignable types because ARM registers can only hold integers!


void ChangeIRQ(unsigned int NewState)


       int my_cpsr;                           /* to be used by inline assembler */

       NewState=(~NewState)<<7;           /* invert and shift to bit 7 */

       __asm                              /* invoke the inline assembler */


           MRS my_cpsr, CPSR                /* get current program status */

           BIC my_cpsr, my_cpsr, #0x80      /* clear IRQ disable bit flag */

           ORR my_cpsr, my_cpsr, NewState   /* OR with new value */

          MSR CPSR_c, my_cpsr              /* store updated program status */




This code compiles to:



     MVN  r0,r0

      MOV  r0,r0,lsl #7

      MRS  r1,cpsr

      BIC  r1,r1,#0x80

      ORR  r0,r1,r0

      MSR  cpsr_c,r0

      MOV  pc,r14


Example - Version 3



void ChangeIRQ(unsigned int NewState)

{  int my_cpsr;

  __asm  {

         MRS my_cpsr, CPSR                       /* get current program status */

         ORR my_cpsr, my_cpsr, #0x80    /* set IRQ disable bit flag */

         BIC my_cpsr, my_cpsr, NewState, LSL #7  

         MSR CPSR_c, my_cpsr




This code compiles to:



    MRS  r1,CPSR

    ORR  r1,r1,#0x80

    BIC  r0,r1,r0,LSL #7

    MSR  CPSR_c,r0

    MOV  pc,r14


In this version we save two move instructions over the previous implementation.


Note that you must avoid using C variables with the same names as physical registers (e.g. r0, r1, etc) or ARM Procedure Call Standard (APCS) named registers (e.g. ip, sl, etc).

If you try to access such a variable in an __asm block, the physical register will be accessed instead of the C variable, which may give unexpected results.

For example, avoid the use of:


int fn(int v)


       int ip;                                  /* avoid C variable names like this! */

       __asm { add ip, v, #1; }   /* the physical register ip is used here, */

                                                  /* not the C variable ip */

       return ip;




Getting Started

The µVision IDE from Keil combines project management, make facilities, source code editing, program debugging, and complete simulation in one powerful environment. The µVision development platform is easy-to-use and helping you quickly create embedded programs that work. The µVision editor and debugger are integrated in a single application that provides a seamless embedded project development environment.

The µVision IDE (Integrated Development Environment) is the easiest way for most developers to create embedded applications using the Keil development tools.

Create a Project

µVision includes a project manager which makes it easy to design applications for an ARM based microcontroller. You need the following steps to create a new project:

  • Start µVision and select the toolset
  • Create a project file and select a CPU from the device database.
  • Create a new source file and add this source file to the project.
  • Add and configure the startup code for the ARM.
  • Set tool options for target hardware.
  • Build project and create a HEX file for PROM programming.

Start µVision

µVision is a standard Windows application and started by clicking on the program icon.



Create a Project File

To create a new project file select from the µVision menu Project – New Project…. This opens a standard Windows dialog that asks you for the new project file name.


We suggest that you use a separate folder for each project. You can simply use the icon Create New Folder in this dialog to get a new empty folder. Then select this folder and enter the file name for the new project, i.e. Project1.


µVision creates a new project file with the name PROJECT1.UV2 which contains a default target and file group name. You can see these names in the Project Workspace – Files.

Select a Device

When you create a new project µVision asks you to select a CPU for your project. The Select Device dialog box shows the µVision device database. Just select the microcontroller you use.


Here we are using Philips LPC2148 controller. This selection sets necessary tool options for the LPC2148 device and simplifies in this way the tool configuration.


  • When you create a new project, µVision may automatically add the correct device specific CPU startup code for you.
  • On some devices, the µVision environment needs additional parameters that you have to enter manually. Please carefully read the information provided under Description in this dialog, since it might have additional instructions for the device configuration.

A window will open up asking for add up some startup files. Now we don’t want that, therefore Press NO.


Set Tool Options for Target

µVision lets you set options for your target hardware. The dialog Options for Target opens via the toolbar icon or via the Project - Options for Target menu item.

µVision creates HEX files with each build process when Create HEX file under Options for Target – Output is enabled.


Select the Toolset for C

µVision uses the ARM Real View compilation tools, the ARM ADS compiler, the GNU GCC compiler, or the Keil C ARM compiler. When using the GNU GCC compiler or ARM ADS it is required to install the toolset separately.


The toolset that you are actually using is selected in the µVision IDE under Project - Components, Environment, and Books.

  • Use Real View Compiler selects the ARM development tools for this project. The setting Real View Folder specifies the path to the development tools. Examples for the various ARM ADS/Real View versions:
  • Real View Compiler for µVision: BIN\
  • ADS V1.2: C:\Program Files\ARM\ADSv1_2\Bin
  • Real View Evaluation Version 2.1: C:\Program Files\ARM\RVCT\Programs\2.1\350\eval2-sc\win_32-pentium
  • Use Keil CARM Compiler selects the Keil CARM Compiler, Keil AARM Assembler and Keil LARM Linker/Locater for this project.
  • Use GNU Compiler selects the GNU development tools for this project. The setting Cygnus Folder specifies the path to the GNU installation folder.
  • The GNU-Tool-Prefix allows using GNU tool chain variants. Examples for the various GNU versions:
  • GNU V3.22 with uclib: GNU-Tool-Prefix: arm-uclibc- Cygnus Folder: C:\Cygnus
  • GNUARM V4 with standard library: GNU-Tool-Prefix: arm-elf- Cygnus Folder: C:\Program Files\GNUARM\
  • The setting Keil Root Folder is the base folder of the Keil µVision/ARM installation. For the Keil ARM Tools the paths to the tool components is configured under Development Tool Folders.

Create Source Files

You may create the source file with the menu option File – New. This opens an empty editor window where you can enter your source code. µVision enables the C color syntax highlighting when you save your file with the dialog File – Save As… under a filename with the extension *.C. We are saving our example file under the name MAIN.C.


#include <stdio.h> 

extern void init_serial (void);

#pragma save

#pragma arm 

int ArmAdd (int s1, int s2)




               LDAV      R7,R4,s1

               LDAV      R6,R4,s2

               ADD       R7,R7,R6

               STAV      R7,R6,s1


      return (s1);


int ArmSub (int s1, int s2)




                   LDAV      R7,R4,s1

                   LDAV      R6,R4,s2

                   SUB       R7,R7,R6

                   STAV      R7,R6,s1

      }        return (s1);




int ArmMul (int s1, int s2)




                         LDAV      R7,R4,s1

                        LDAV    R6,R4,s2                                       MUL       R7,R6                                     STAV     R7,R6,s1


               return (s1);                                            }



          #pragma restore

          int main (void)


      Int sum,Diff, Product, Quotient;




       sum     =        ArmAdd (10, 20);             

   Diff      =        ArmSub (25, 13);

       Product=      ArmMul (5, 17);

       Quotient=     ArmDiv (128, 4);

       printf (" --> ArmAdd (10,20):     

   Sum =         %d\n", sum);

       printf (" --> ArmSub (25,13):

       Difference    = %d\n", Diff); 

       printf (" --> ArmMul (5,17) :

       Product=       %d\n",Product);

       printf (" --> ArmDiv (128,4):

       Quotient=        d\n",Quotient);

       printf ("_._._._._._._._._._.


       while (1);

       return (0);





        int ArmDiv (int s1, int s2)    


      int lVar;                                                    __asm


      LDAV      R7,R4,s1

      LDAV      R6,R4,s2

      MOV       R0,#0

      DIF:     SUBS   7,R7,R6

      ADD       R0,R0,#1

      BHI         DIF

      STAV      R0,R6,lVar


          return (lVar);



Once you have created your source file you can add this file to your project. µVision offers several ways to add source files to a project. For example, you can select the file group in the Project Workspace – Files page and click with the right mouse key to open a local menu.

Use the --asm control to create assembler output from the C compiler. The --asm control causes the compiler to generate an .S file with the same name as your C file. You may then assemble the .S file with the assembler.

The --asm control is entered in µVision under Project - Options - C/C++ tab - Misc Controls. Also disable the option Project - Listing - C Compiler Listing since this is using a different output. The Assembly file will have a .ASM extension, and will be in your listing file folder for your project.

KEIL compiler makes use of registers and memory locations for passing parameters. By default C function pass up to three parameters in registers and further parameters are passed in fixed memory locations. Parameters are passed in fixed memory location if parameter passing in register is disabled or if there are too many parameters to fit in registers.

In the above example program, the main C function accessed the asm function easily.

The option Add Files opens the standard files dialog. Select the file MAIN.C you have just created.


Therefore you just creating a C file for add with the project. If you have already created C file then you just added that C file into Project file.


Now, you must rebuild the project file for generating HEX file. When you build the project then only the HEX file is generated by using the C Compiler.

Development Tools

The Keil development tools for ARM 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 µVision IDE and Debugger is the central part of the Keil ARM development tools. µVision offers a Build Mode and a Debug Mode.

In the µVision Build Mode you maintain the project files and generate the application. µVision uses either the GNU or ARM ADS/Real View™ development tools.

In the µVision Debug Mode you verify your program either with a powerful CPU and peripheral simulator that connects the debugger to the target system.

The ULINK allows you also to download your application into Flash ROM of your target system.

Build the C Project files

Typical, the tool settings under Options – Target are all you need to start a new application. You may translate all source files and line the application with a click on the Build Target toolbar icon. When you build an application with syntax errors, µVision will display errors and warning messages in the Output Window – Build page. A double click on a message line opens the source file on the correct location in a µVision editor window.

Now you can modify existing source code or add new source files to the project. The Build Target toolbar button translates only modified or new source files and generates the executable file. µVision maintains a file dependency list and knows all include files used within a source file.

Even the tool options are saved in the file dependency list, so that µVision rebuilds files only when needed. With the Rebuild Target command, all source files are translated, regardless of modifications.

The final output will look like this...


Test the C Programs with the µVision Debugger

It describes the Debug Mode of µVision and shows you how to use the user interface to test a sample program. Also discussed are simulation mode and the different options available for program debugging.

You can use µVision Debugger to test the applications you develop using the GNU or ARM ADS/Real View tool chain. The µVision Debugger offers two operating modes that are selected in the Options for Target – Debug dialog.


  • Use Simulator allows configuring the µVision Debugger as software-only product that simulates most features of an ARM7 microcontroller without actually having target hardware.
  • You can test and debug your embedded application before the hardware is ready. µVision simulates a wide variety of peripherals including the serial port, external I/O, and timers. The peripheral set is selected when you select a CPU from the device database for your target.
  • Use Advance GDI drivers, like the Keil ULINK ARM Debugger provide an interface to target hardware. With the Advanced GDI interface you may connect the µVision Debugger directly to emulators, Embedded ICE (On-chip Debug System) for example with the Keil ULINK USB-JTAG Adapter.

CPU Simulation with C

The µVision Debugger simulates up to 4GB (Giga Bytes) of memory from which areas can be mapped for read, write, or code execution access. The µVision simulators can traps and reports illegal memory accesses.

In addition to memory mapping, the simulator also provides support for the integrated peripherals of the various ARMS based microcontroller devices. The on-chip peripherals of the CPU you have selected are configured from the Device Database selection you have made when you create your project target.

You may select and display the on-chip peripheral components using the Debug menu. You can also change the aspects of each peripheral using the controls in the dialog boxes.

Start Debug Mode

You can start the debug mode of µVision with the Debug – Start/Stop Debug Session command.

Depending on the Options for Target – Debug configuration, µVision will load the application program and run the startup code.


For information about the configuration of the µVision debugger refer to Set Debug Options. µVision saves the editor screen layout and restores the screen layout of the last debug session. If the program execution stops, µVision opens an editor window with the source text or shows CPU instructions in the disassembly window.

The next executable statement is marked with a yellow arrow.

During debugging, most editor features are still available. For example, you can use the find command or correct program errors. Program source text of your application is shown in the same windows.

Once the program is compiled and linked, you can test the example program with the µVision debugger. In µVision, use the Start/Stop Debug Session command from the Debug menu or toolbar. µVision initializes the debugger and starts program execution till the main function.

open-serial-1 Open Serial Window #2 that displays the serial output of the application with the Serial Window #2 command from the View menu.

debug-menu-or-toolbar Run HELLO with the Go command from the Debug menu or toolbar. The HELLO program executes and displays the text “Hello World” in the serial window.

stop-running-1 Stop Running HELLO with the Halt command from the Debug menu or the toolbar. You may also type ESC in the Command page of the Output window.

During debugging µVision will show the following output



insert-remove-breakpoints-1 Use the Insert/Remove Breakpoints command from the toolbar or the local editor menu that opens with a right mouse click and set a breakpoint at the beginning of the main function.

reset-cpu-1 Use the Reset CPU command from the Debug menu or toolbar. If you have halted HELLO start program execution with Run. µVision will stop the program at the breakpoint.

debug-toolbar-1 You can single-step through the HELLO program using the Step buttons in the debug toolbar. The current instruction is marked with a yellow arrow. The arrow moves each time you step.

mouse-cursor-1 Place the mouse cursor over a variable to view their value.

start-stop-debug-session-1 You may stop debugging at any time with Start/Stop Debug Session command.


The on-chip peripherals of ARM microcontrollers are quite sophisticated and offer numerous configuration options. The code examples only present one or two methods of using each peripheral.

The status of the on-chip peripherals can be review in µVision Debugger using the Peripherals menu. The menu items are device specific and configured based on information from the Device Database™.

It contains generic information about:

  • Startup Code: initialize the CPU and transfers control to the main function.
  • CPU Header Files: define the Peripheral Registers of the ARM device in use.
  • Parallel Port I/O: usage of standard I/O ports.
  • Interrupts: explains the different interrupt variants on ARM devices.
  • Vectored Interrupt Controller:explains the usage of a vectored interrupt.
  • General Purpose Timers: discusses the usage of standard timers.
  • Serial Interface: tells how to implement serial UART communication.
  • Watchdog Timer: usage of a watchdog timer for recovery from hardware or software failures.
  • Pulse Width Modulation: may be used to generate analog output voltages.
  • A/D Converter: convert an analog input voltage to a digital value.
  • Power Reduction Modes: put the ARM device into IDLE or POWER DOWN mode.

Join the World's Largest Technical Community

we respect your privacy.