You are on page 1of 22

KEIL C Submitted




The use of C language to program microcontrollers is becoming too common.
And most of the time its not easy to buld an application in assembly which instead you
can make easily in C. So It’s important that you know C language for microcontroller
which is commonly known as Embedded C. As we are going to use Keil C51 Compiler,
hence we also call it Keil C.



Embedded system encompasses a variety of hardware and software

components, which perform specific functions in host systems. Embedded systems
have become increasingly digital with a non-digital periphery and therefore, both
hardware and software co-design is relevant. Majority of computers are used in such
systems to distinguish them from standard main frames, workstations and pc’s.
Advances in microelectronics have made possible applications that would have been
impossible without an embedded system design.

Embedded system applications have virtually entered every sphere of our lives
embedded systems cover a broad of products that generalization is difficult.

An embedded system is a microprocessor- based system that is incorporated into a

device to monitor and control the functions of the components of the device. They are
used in many devices. Developments in microelectronics, processor speeds and
memory elements have resulted in power embedded systems with a number of

This paper seeks differences between embedded computer design and desktop design,
which also presents design challenges.


An embedded system is a type of computer system or computing device,

which performs a dedicated function and/or is designed for use with a specific
embedded software application.


Embedded systems have the following components.


A processor fetches instructions from the memory unit and executes the
instructions. An instruction consists of an instruction code and the operands on which
the instruction should act upon. The format of instruction code and operands of a
processor is defined by the processor’s instruction set. Each type of processor has its
own instruction set. Performance of the system can be improved by dedicated
processors, which implement algorithms in hardware using building blocks such as
hardware counters and multipliers.
Some embedded processors have special fuzzy logic instructions. This is because
inputs to an embedded system are sometimes better represented as fuzzy variables.
For instance, the mathematical model for a control system may not exist or may involve
expensive computing power. Fuzzy logic can be employed for such control systems to
provide a cost-effective solution.


The memory unit in an embedded system should have low access time and high
density. (A memory chip- has greater density if it can store more bits in the same
amount of space. Memory in an embedded system consists of ROM and RAM .The
contents of ROM are non-volatile while RAM is volatile. ROM stores the program code
while RAM is used to store transient input or output data. Embedded systems generally
do not possess secondary storage devices such as magnetic disks. As programs of
embedded systems are small there is no need of virtual storage.


Peripherals are input and output devices connected to the serial and parallel
ports of the embedded system. Serial port transfers one bit at a time between the
peripheral and themicroprocessor. Parallel ports transfer an entire word consisting of
many bits simultaneously between the peripheral and the microprocessor.

Programmable interface devices that act as an interface between microprocessor

with peripherals provide flexibility since they can be programmed to perform I/O on
different peripherals. The microprocessor monitors the inputs from peripherals and
performs actions when certain events occur. For instance, when sensors indicate the
level of water in the washtub of a washing machine is above the present level, the
microprocessor starts the wash cycle.
Introduction to the Keil 8051 ‘C’ Compiler

We will use the Keil ‘C’ cross compiler to develop our 8051 programs.

The Keil ‘C’ Compiler Directory Structure is as follows

\BIN : This contains the compiler .exe files

\INC : This contains the header files

\LIB : This contains the object libraries
\WORKING : All user programs reside here
\A51 : Assembler directory used by comp

Program Development Sequence

Before we can start writing our ‘C’ programs we must create the right
Programming environment. To do this, execute the following sequence.

1. Enter Windows:

2. Select and open the Main program group icon from the program manager.

3. Select and open the MSDOS icon from the main program group.

4. Press Alt+Enter to reduce the DOS Window to a DOS box with Windows
5. Select the DOS box using the mouse or by pressing ALT+TAB to sequence through
the currently opened windows until the DOS box is selected as active

6. Change directory from C:\windows to C:\c51v4\bin.

7. Type START at the Command prompt C:\C51v4\bin:> START Press enter.

8. Change directory from C:\c51v4\bin to C:\c51v4\working

9. We are now ready to write our first program.

Creating ‘C’ Source Files

From within the directory C:\C51V4\WORKING you develop your ‘C’ programs.
You can create your programs using any ASCII text editor e.g. MSDOS ‘EDIT’ or the
Borland Editor within Borland ‘C’.

Windows Notepad may be used to create the source file, however, this source
file will still be required to be compiled in the DOS box at the DOS command prompt.

How to Compile Your ‘C’ Source File

1. Type COMP51 at the DOS prompt followed by the name of your file. NOTE DO
NOT include the filename extension .C . If you do the whole of your source file
will be corrupted.
2. C:\c51v4\working:> COMP51 test Press Enter.
3. This will call the compiler, assembler, linker and hex converter programs
automatically. Several files including .Hex will be created in the C:\c51v4\working
4. If you have any errors reported, you will find what they are by examining the .lst
file of your source file name, i.e. test.lst.
5. When you have understood the errors, modify the ‘C’ source file and recompile
as part 2.
6. Once you have complete the compilation process you are ready the proceed with
download your .hex to the Flight32 board.

Keil c

The C programming language was designed for computers, though, and not
embedded systems. It does not support direct access to registers, nor does it allow for
the reading and setting of single bits, two very important requirements for 8051
software. In addition, most software developers are acustomed to writing programs that
will by executed by an operating system, which provides system calls the program may
use to access the hardware. However, much code for the 8051 is written for direct use
on the processor, without an operating system. To support this, the Keil compiler has
added several extensions to the C language to replace what might have normally been
implemented in a system call, such as the connecting of interrupt handlers.

The purpose of this manual is to further explain the limitations of the Keil compiler,
the modifications it has made to the C language, and how to account for these in
developing software for the 8051 microcontroller.

Mathematical operations:

The most basic concept about mathematical operations in programming languages, is

the '=' operator which is used to store the content of the expression at its right, into the
variable at its left. For example the following code will store the value of 'b' into 'a' :

a = b;
And subsequently, the following expression in totally invalid:

5 = b;

Since 5 in a constant, trying to store the content of 'b' in it will cause an error.

You can then perform all kind of mathematical operations, using the operators '+','-','*'
and '/'. You can also use brackets '( )' when needed. Example:

a =(5*b)+((a/b)*(a+b));

If you include 'math.h' header file, you will be able to use more advanced functions in
your equations like Sin, Cos and Tan trigonometric functions, absolute values and
logarithmic calculations like in the following example:

a =(c*cos(b))+sin(b);

To be able to successfully use those functions in your programs, you have to know the
type of variables that those functions take as parameter and return as a result. For
example a Cosine function takes an angle in radians whose value is a float number
between -65535 and 65535 and it will return a float value as a result. You can usually
know those data types from the 'math.h' file itself, for example, the cosine function, like
all the others is declared in the top of the math header file, and you can read the line:

extern float cos (float val);

from this line you can deduce that the 'cos' function returns a float data type, and takes
as a parameter a float too. (the parameter is always between brackets.). Using the
same technique, you can easily know how to deal with the rest of the functions of the
math header file. the following table shows a short description of those functions:

Function Description
char cabs (char val); Return an the absolute value of a char variable.
int abs (int val); Return an the absolute value of a int variable.
long labs (long val); Return an the absolute value of a long variable.
float fabs (float val); Return an the absolute value of a float variable.
float sqrt (float val); Returns the square root of a float variable.
Returns the value of the Euler number 'e' to the power of
float exp (float val);
float log (float val); Returns the natural logarithm of val
float log10 (float val); Returns the common logarithm of val
float sin (float val);
float cos (float val);
float tan (float val);
float asin (float val); A set of standard trigonometric functions. They all take
float acos (float val); angles measured in radians whose value have to be
float atan (float val); between -65535 and 65535.
float sinh (float val);
float cosh (float val);
float tanh (float val);
This function calculates the arc tan of the ratio y / x, using
float atan2 (float y, float x); the signs of both x and y to determine the quadrant of the
angle and return a number ranging from -pi to pi.
Calculates the smallest integer that is bigger than val.
float ceil (float val);
Example: ceil(4.3) = 5.
Calculates the largest integer that is smaller than val.
float floor (float val);
Example: ceil(4.8) = 4.
Returns the remainder of x / y. For example:
float fmod (float x, float y);
fmod(15.0,4.0) = 3.
float pow (float x, float y); Returns x to the power y.

Logical operations:

You can also perform logic operations with variables, like AND, OR and NOT
operations, using the following operators:

Operator Description
! NOT (bit level) Example: P1_0 = !P1_0;
~ NOT (byte level) Example: P1 = ~P1;
| OR

Note that those logic operation are performed on the bit level of the registers. To
understand the effect of such operation on registers, it's easier to look at the bits of a
variable (which is composed of one or more register). For example, a NOT operation
will invert all the bit of a register. Those logic operators can be used in many ways to
merge different bits of different registers together.

For example, consider the variable 'P1', which is of type 'char', and hence stored in an
8-bit register. Actually P1 is an SFR, whose 8 bits represents the 8 I/O pins of Port 1. It
is required in that example to clear the 4 lower bits of that register without changing the
state of the 4 other which may be used by other equipment. This can be done using
logical operators according to the following code:

P1 = P1 & 0xF0; (Adding '0x' before a number indicates that it is a hexadecimal one)

Here, the value of P1 is ANDed with the variable 0xF0, which in the binary base is
'11110000'. Recalling the two following relations:

1 AND X = X
0 AND X = 0
(where 'X' can be any binary value)

You can deduce that the 4 higher bits of P1 will remain unchanged, while the 4 lower
bits will be cleared to 0.

By the way, note that you could also perform the same operation using a decimal
variable instead of a hexadecimal one, for example, the following code will have exactly
the same effect than the previous one (because 240 = F0 in HEX):

P1 = P1 & 240;

A similar types of operations that can be performed on a port, is to to set some of its bits
to 1 without affecting the others. For example, to set the first and last bit of P1, without
affecting the other, the following source code can be used:

P1 = P1 | 0x81;

Here, P1 is ORed with the value 0x81, which is '10000001' in binary. Recalling the two
following relations:

1 OR X = 1
0 OR X = X
(where 'X' can be any binary value)

You can deduce that the first and last pins of P1 will be turned on, without affecting the
state of the other pins of port 1. Those are just a few example of the manipulations that
can be done to registers using logical operators. Logic operators can also be used to
define very specific conditions, as you shall see in the next section.

The last types of logic operation studied in this tutorial is the shifting. It can be useful the
shift the bit of a register the right or to the left in various situations. this can be done
using the following two operators:

Operator Description
>> Shift to the right
<< Shift to the left

The syntax is is quite intuitive, for example:

P1 = 0x01; // After that operation, in binary, P1 = 0000 0001

P1 = (P1 << 8) // After that operation, in binary P1 = 1000 0000
Data Types

The Cx51 Compiler provides several basic data types you may use in your C programs.
The compiler supports the standard C data types as well as several data types that are
unique to the Cx51 platform.

Data Types Bits Bytes Value Range

bit 1 0 to 1
signed char 8 1 -128 — +127
unsigned char 8 1 0 — 255
enum 8 / 16 1 or 2 -128 — +127 or -32768 — +32767
signed short int 16 2 -32768 — +32767
unsigned short int 16 2 0 — 65535
signed int 16 2 -32768 — +32767
unsigned int 16 2 0 — 65535
signed long int 32 4 -2147483648 — +2147483647
unsigned long int 32 4 0 — 4294967295
float 32 4 ±1.175494E-38 — ±3.402823E+38
double 32 4 ±1.175494E-38 — ±3.402823E+38
sbit 1 0 or 1
sfr 8 1 0 — 255
sfr16 16 2 0 — 65535

 The bit, sbit, sfr, and sfr16 data types are not provided in ANSI C. They are unique to
the Cx51 Compiler.

Keil Limitations

There are several very important limitations in the evaluation version of Keil's
Developer's Kit that users need be aware of when writing software for the 8051.

Object code must be less than 2 Kbytes

The compiler will compile any-sized source code file, but the final object code
may not exceed 2 Kbytes. If it does, the linker will refuse to create a final binary
executable (or HEX file) from it. Along the same lines, the debugger will refuse any files
that are over 2Kbytes, even if they were compiled using a different software package.

Few student projects will cross this 2Kbyte threshold, but programmers should
be aware of it to understand why code may no longer compile when the project grows
too large.

Program code starts at address 0x4000

All C code compiled and linked using the Keil tools will begin at address
0x4000 in code memory. Such code may not be programmed into devices with less than
16Kbytes of Read-Only Memory. Code written in assembly may circumvent this
limitation by using the "origin" keyword to set the start to address 0x0000. No such
work-around exists for C programs, though. However, the integrated debugger in the
evaluation software may still be used for testing code. Once tested, the code may be
compiled by the full version of the Keil software, or by another compiler that supports
the C extensions used by Keil.

C Modifications

The Keil C compiler has made some modifications to anotherwise ANSI-

compliant implementation of the C programming language. These modifications were
made solely to facilitate the use of a higher-level language like C for writing programs
on microcontrollers.

Variable Types

The Keil C compiler supports most C variable types and adds several of its own.

Standard Types
The evaluation version of the Keil C compiler supports the standard ANSI C variable
types, with the exception of the floating point types. These types are summarized below.

Type Bits Bytes Range

char 8 1 -128 to +127

unsigned char 8 1 0 to 255

enum 16 2 -32,768 to +32,767

short 16 2 -32,768 to +32,767

unsigned short 16 2 0 to 65,535

int 16 2 -32,768 to +32,767

unsigned int 16 2 0 to 65,535

long 32 4 -2,147,483,648 to +2,147,483,647

unsigned long 32 4 0 to 4,294,697,295

In addition to these variable types, the compiler also supports the struct and union
data structures, as well as type redefinition using typedef.

Keil Types

To support a microcontroller and embedded systems applications, Keil added

several new types to their compiler. These are summarized in the table below.
Type Bits Bytes Range

bit 1 0 0 to 1

sbit 1 0 0 to 1

sfr 8 1 0 to 255

sf16 16 2 0 to 65,535

Of these, only the bit type works as a standard variable would. The other three have
special behavior that a programmer must be aware of.


This is a data type that gets allocated out of the 8051's bit-addressable on-chip
RAM. Like other data types, it may be declared as either a variable. However, unlike
standard C types, if may not be used as a pointer. An example of its usage follows. /*
declare two bit variables - the compiler will decide which */
/* declare two bit variables - the compiler will decide which */

/* addresses they are at. Initialize them to 0 and 1. */

bit testbit1 = 0;

bit testbit2 = 1;

/* set testbit1 to the value in testbit2 */

testbit1 = testbit2;

/* clear testbit2 */

testbit2 = 0;

/* testbit1 is now a 1, and testbit2 is now a 0 */

/* Note that the assignment of testbit2 to testbit1 only copied */

/* the contents of testbit2 into testbit1. It did *not* change */

/* the location of testbit1 to be the same as testbit2. */

sbit, sfr, and sf16

These are special types for accessing 1-bit, 8-bit, and 16-bit special function
registers. Because there is no way to indirectly address registers in the 8051, addresses
for these variables must be declared outsite of functions within the code. Only the data
addressed by the variable may be manipulated in the code. An example follows.
/* create an sbit variable that points to pin 0 of port 1 */

/* note that this is done outside of any functions! */

sbit P10 = 0x90;

/* now the functions may be written to use this location */

void main (void)

/* forever loop, toggling pin 0 of port 1 */

while (1==1)

P10 = !P10;

delay (500); /* wait 500 microseconds */

Conveniently, the standard special function registers are all defined in the reg51.h file
that any developer may include into their source file. Only registers unique to the
particular 8051-derivative being used for the project need have these variable declared,
such as registers and bits related to a second on-chip serial port.

Keil Variable Extensions

In writing applications for a typical computer, the operating system handles manages
memory on behalf of the programs, eliminating their need to know about the memory
structure of the hardware. Even more important, most computers having a unified
memory space, with the code and data sharing the same RAM. This is not true with the
8051, which has separate memory spaces for code, on-chip data, and external data.
To accommodate for this when writing C code, Keil added extensions to variable
declarations to specify which memory space the variable is allocated from, or points to.
The most important of these for student programmers are summarized in the following

Extension Memory Type Related ASM

Directly-addressable data memory

data (data memory addresses 0x00- MOV A, 07Fh

Indirectly-addressable data memory

MOV R0, #080h
idata (data memory addresses 0x00-

xdata External data memory MOVX @DPTR

code Program memory MOVC @A+DPTR

These extensions may be used as part of the variable type in declaration or casting
by placing the extension after the type, as in the example below. If the memory type
extension is not specified, the compiler will decide which memory type to use
automatically, based on the memory model (SMALL, COMPACT, or LARGE, as
specified in the project properties in Keil).

/* This is a function that will calculate and return a checksum of */

/* a range of addresses in code memory, using a simple algorithm */

/* that simply adds each consecutive byte together. This could be */

/* useful for verifying if the code in ROM got corrupted (like if */

/* the Flash device were wearing out). */

unsigned int checksum (unsigned int start, unsigned int end)


/* first, declare pointers to the start and end of */

/* the range in code memory. */

unsigned int code *codeptr, *codeend;

/* now declare the variable the checksum will be */

/* calculated in. Because direct-addressable data */

/* is faster to access than indirect, and this */

/* variable will be accessed frequently, we will */

/* declare it in data memory (instead of idata). */

/* In reality, if left unspecified, the compiler */

/* would probably leave it in the accumulator for */

/* even faster access, but that would defeat the */

/* point of this example. */

unsigned int data checksum = 0;

/* Initialize the codestart and codeend pointers to */

/* the addresses passed into the function as params. */

/* because start and end are passed in as values, */

/* not pointers, they must be cast to the correct */

/* pointer type */

codeptr = (unsigned int code *)start;

codeend = (unsigned int code *)end;

/* Now perform the checksum calculation, looping */

/* until the end of the range is reached. */

while (codeptr <= codeend)

checksum = checksum + (unsigned int data)*codeptr;

codeptr++; /* go to the next address */

return (checksum);

Keil Function Extensions

As in most other C compilers, functions may be declared in one of two fashions:

unsigned int functionname functionname (var)

(unsigned int var)
unsigned int var
return (var);
return (var);

Most modern programmers use the first syntax, as do the examples in this document.
Keil provides two important extensions to the standard function declaration to allow for
the creation of interrupt handlers and reentrant functions.


In writing applications for a typical computer, the operating system provides system
calls for setting a function, declared in the standard manner, as the handler for an
interrupt. However, in writing code for an 8051 without an operating system, such a
system would not be possible using solely C code. To eliminate this problem, the Keil
compiler implements a function extension that explicitly declares a function as an
interrupt handler. The extension is interrupt, and it must be followed by an integer
specifying which interrupt the handler is for. For example:

/* This is a function that will be called whenever a serial */

/* interrupt occurs. Note that before this will work, interrupts */

/* must be enabled. See the interrupt example in the appendix. */

void serial_int (void) interrupt 4


In the example, a function called serial_int is set as the handler for interrupt 4, which is
the serial port interrupt. The number is calculated by subtracting 3 from the interrupt
vector address and dividing by 8. The five standard interrupts for the 8051 are as

Vector Interrupt
address number

External 0 0003h 0
Timer 0 000Bh 1

External 1 0013h 2

Timer 1 001Bh 3

Serial 0023h 4

Other interrupts are dependent on the implementation in the particular 8051-derivative

being used in the project, but may be calculated in the same manor using the vector
addresses specified by the manufacturer.


Since the processor only save the current program counter before executing an
interrupt handler, the handler can potentially damage any data that was in the registers
prior to the interrupt. This in turn would corrupt the program once the processor goes
back to where it left off. To avoid this, the Keil compiler determines which registers will
be used by the interrupt handler function, pushes them out to the stack, executes the
handler, and then restores the registers from the stack, before returning to the
interrupted code. However, this incurs extra time, especially if a lot of registers will be
used. It is preferred that as little time be spent in interrupts as possible. To decrease this
time, Keil provides an optional extension, using, to the interrupt extension that tells the
compiler to simple change to a new register bank prior to executing the handler, instead
of pushing the registers to the stack.

/* This is a function that will be called whenever a serial */

/* interrupt occurs. Prior to executing the handler, the */

/* processor will switch to register bank 1

void serial_int (void) interrupt 4 using 1


In the 8051, interrupts have two possible priorities: high and lo. If, during the
processing of an interrupt, another interrupt of the same priority occurs, the processor
will continue processing the first interrupt. The second interrupt will only be processed
after the first has finished. However, if an interrupt of a higher priority arrives, the first
(low priority) interrupt will itself be interrupted, and not resume until the higher priority
interrupt has finished. Because of this, all interrupts of the same priority may use the
same register bank.

The using extension should be used when quick execution time is of high
importance, or when other functions are called from the interrupt handler, as it would
otherwise push all of the registers on to the stack prior to calling the function, incurring
more time penalties.


Similar to the case described for interrupts above, it is possible for a single function
to be interrupted by itself. For example, in the middle of normal execution of the
function, the interrupt occurs, and that interrupt makes a call to the same function. While
the interrupt handler will save the registers before entering this function, no protective
measures are taken from overwriting the contents of local variables allocated in data
memory. When the interrupt is serviced and control is passed back to normal execution,
the corrupted data in those variables could ruin the entire program.

The general term for a function that may be called more than once simultaneously is
"reentrant." Accordingly, the reentrant extension may be used in a function declaration
to force the compiler to maintain a separate data area in memory for each instance of
the function. While safe, this does have the potential to use large area of the rather
limited data memory. An example of such a function follows.
/* Because this function may be called from both the main program */

/* and an interrupt handler, it is declared as reentrant to */

/* protect its local variables. */

int somefunction (int param) reentrant


return (param);

/* The handler for External interrupt 0, which uses somefunction() */

void external0_int (void) interrupt 0



/* the main program function, which also calls somefunction() */

void main (void)

while (1==1)