Embedded C Programming For 8051 Using Keil
Embedded C Programming For 8051 Using Keil
What is Embedded C?
The C programming language was originally designed for computer and not embedded
systems. For programming microcontrollers used in embedded systems, support for direct access
to different registers and reading and setting of bits are important. This support was not provided
by C language.
Embedded C is a set of language extensions for the C programming language by the C
standards Committee to cater to the programming of Embedded systems.
Difference between C and Embedded C?
C is used for desktop computers while embedded C is used for microcontroller based
applications.
C has the luxury to use resources of a desktop computer like memory, OS etc. Embedded
C has to use limited resources (RAM, ROM, I/Os) on an embedded processor.
C Modifications
The Keil C compiler has made some modifications to the 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.
Some of the modifications are mentioned below.
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
-128 to +127
unsigned char
0 to 255
enum
16
-32,768 to +32,767
short
16
-32,768 to +32,767
unsigned short
16
0 to 65,535
int
16
-32,768 to +32,767
unsigned int
16
0 to 65,535
long
32
-2,147,483,648 to +2,147,483,647
unsigned long
32
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.
void main(void){
unsigned char z;
for(z=0,z<=255;z++){
P1 =z}
Bits
Bytes Range
bit
0 to 1
sbit
0 to 1
sfr
0 to 255
1. bit
This is a data type that gets allocated out of the 8051's bitaddressable on-chip RAM. Like other data types, it may be declared as a
variable. However, unlike standard C types, it may not be used as a
pointer. An example of its usage follows.
/* 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
/* Note that the assignment
only copied */
/* the contents of testbit2
*not* change */
/* the location of testbit1
testbit2. */
testbit2 is now a 0 */
of testbit2 to testbit1
into testbit1.
It did
to be the same as
LED = 1;
delay();
}
}
void delay(void){
unsigned int i;
for( i=0;i<50000;i++);
}
/*Port2 pin 0 is addressed using the sbit
declaration.
This port pin is toggled after a specific delay
*/
3. sfr
sfr type defines a special function registers. For example
sfr
sfr
sfr
sfr
P0
P1
P2
P3
=
=
=
=
0x80;
0x90;
0xA0;
0xB0;
P0, P1, P2 and P3 are the SFR name declarations. Names for the sfr
variables are defined just like other C variable declarations. Any symbolic
name can be used in an sfr declaration. Classic 8051 devices support the
SFR address range 0x80 0xFF. sfr variables may not be declared inside a
function. The must be declared outside of the function body.
Related ASM
Program memory
MOVC @A+DPTR
Example:
#include<AT89C5131.h>
void main(void)
{
code unsigned char mydata[]= Hello;
unsigned char z;
for(z=0;z<=5;z++){
P1 = mydata[z];
}
}
This program uses a separate area of the code space for data. This allows the size
the array to be as long as you want if you have enough on chip ROM. However, more
the code space you use for data, the less is the space left for the program code.
interrupt
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.*/
void serial_int (void) interrupt 4
{
...
}
In the example above, 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 follows:
Interrupt
Vector
address
Interrupt
number
External 0
0003h
Timer 0
000Bh
External 1
0013h
Timer 1
001Bh
Serial
0023h
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 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 low.
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
Reentrant
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.
Operators in C
The compiler supports the following operators,
Logical operators: AND (&&), OR (||) and NOT (!)
Bitwise operators: AND(&), OR(|),XOR(^), Inverter(~)
Shift operators: Shift Right(>>), Shift Left(<<)
Bitwise operators for C
A
0
0
1
1
B
0
1
0
1
AND
A&B
0
0
0
1
OR
A|B
0
1
1
1
XOR
A^B
0
1
1
0
INVERTER
Y=~B
0
0
1
0
The shift right(>>) and the shift left(<<) operators have the following format in C.
data >> number of bits to be shifted right
data << number of bits to be shifted left
1. 0x9A >>3 = 0x13 /* Shift right 3 times */
2. 0x77 >> 4 = 0x07 /* Shift right 4 times */
3. 0x06 << 4 = 0x60 /*Shift left 4 times */
Sample C programs
I/O programming examples
#include<AT89C5131.h>
void main(void){
unsigned char mybyte;
P0 = 0xFF;
while(1){
mybyte = P0;
if(mybyte < 100)
P1 = mybyte;
Else
P2 = mybyte
}
}
#include<AT89C5131.h>
sbit inbit = P1^0
sbit outbit = P2^7
bit membit =27H
void main(void){
while(1){
membit= inbit;
outbit = membit;
}
}
/* sbit data type is used for input */
#include<AT89C5131.h>
sbit switch = P1^7
void main(void){
while(1){
if(switch == 1){
P0 = 0x55;
else
P2 = 0XAA;
}
}
/* Switch variable corresponds to PORT 1 Pin7. It is
used as an input. We put 0x55 on P0 if the P1.7 status
is 1 else we put 0xAA on P2*/
Time Delay
There are two ways to generate time delay in 8051 C
1. Using a simple for loop
2. Using the 8051 timer.
Sample program to generate delay using for loop is given below
#include<AT89C5131.h>
void main(void){
while(1){
P1= 0x55;
msdelay(250)
P1 = 0XAA;
msdelay(250);
}
}