Programming in C for the 8051 Microcontroller, Study notes of Microprocessors

A syllabus for a course on programming in c for the 8051 microcontroller using the keil compiler. It covers topics such as the keil compiler, variable extensions, memory types, and register definitions. The course includes examples of how to access the serial port and how to use interrupts. The document also includes example code for initializing the serial port and for sending and receiving data.

Typology: Study notes

Pre 2010

Uploaded on 08/19/2009

koofers-user-rkt
koofers-user-rkt 🇺🇸

10 documents

1 / 40

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
CECS 347
PROGRAMMING in C SYLABUS
Writing C Code for the 8051
by Matthew Kramer
(Available on-line at: http://ubermensch.org/Computing/8051/8051-c/#appa)
About the Keil Compiler
Keil Software (http://www.keil.com) publishes one of the most
complete development tool suites for 8051 software, which is used
throughout industry. For development of C code, their Developer's Kit
product includes their C51 compiler, as well as an integrated 8051
simulator for debugging. A demonstration version of this product is
available on their website, but it includes several limitations (see next
section). This is the software that will be used for CECS-347.
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 accustomed 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 micro
controller.
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
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28

Partial preview of the text

Download Programming in C for the 8051 Microcontroller and more Study notes Microprocessors in PDF only on Docsity!

CECS 347

PROGRAMMING in C SYLABUS

Writing C Code for the 8051

by Matthew Kramer

(Available on-line at: http://ubermensch.org/Computing/8051/8051-c/#appa)

About the Keil Compiler

Keil Software (http://www.keil.com) publishes one of the most

complete development tool suites for 8051 software, which is used

throughout industry. For development of C code, their Developer's Kit

product includes their C51 compiler, as well as an integrated 8051

simulator for debugging. A demonstration version of this product is

available on their website, but it includes several limitations (see next

section). This is the software that will be used for CECS-347.

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 accustomed 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 micro

controller.

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 0x

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 an otherwise

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 micro controllers.

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

Byte

s

Range

char 8 1 -128 to +

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 / / 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 sf

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

Extension Memory Type Related ASM

data

Directly-addressable data memory (data

memory addresses 0x00-0x7F)

MOV A, 07Fh

idata

Indirectly-addressable data memory

(data memory addresses 0x00-0xFF)

MOV R0, #080h

MOV A, R

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 */

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

using

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.

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.

/* 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 { ... somefunction(0); } / the main program function, which also calls somefunction() */ void main (void) { while (1==1) { ... somefunction(); } }

* INPUT: N/A

* RETURNS: N/A

main() { unsigned int i; /* will be used for a delay loop / / First, Port 0 will be initialized to zero / P0 = 0; /

  • Now the counter loop begins. Because this program is intended
  • to run in an embedded system with no user interaction, and will
  • run forever, the rest of the program is placed in a non-exiting
  • while() loop. / while (1==1) { /
  • This is a very unpredictable method of implementing a delay
  • loop, but remains the simplest. More reliable techniques
  • can be done using the using the built-in timers. In this
  • example, though, the for() loop below will run through 60000
  • iterations before continuing on to the next instruction.
  • The amount of time required for this loop varies with the
  • clock frequency and compiler used. / for (i = 0; i < 60000; i++) {;} / Increment Port 0 */ P0 = P0 + 1; } } Example 2 INT.C /*************************************************************************
  • int.c - A demonstration of how to write interrupt-driven code for an
  • 8051 using the Keil C compiler. The same techniques may work in other
  • 8051 C compilers with little or no modifcation. This program will
  • combine the functionality of both basic.c and serial.c to allow serial
  • communications to be entirely in the background, driven by the serial
  • interrupt. This allows the main() function to count on Port 0 without
  • being aware of any ongoing serial communication. / / included headers */

#include /* function declarations / char getCharacter (void); / read a character from the serial port / void sendCharacter (char); / write a character to the serial port / /

  • Interrupt handlers:
  • Here the code for the interrupt handler will be placed. In this
  • example, a handler for the serial interrupt will be written.
  • Examination of the 8051 specs will show that the serial interrupt is
  • interrupt 4. A single interrupt is generated for both transmit and
  • receive interrupts, so determination of the exact cause (and proper
  • response) must be made within the handler itself.
  • To write an interrupt handler in Keil, the function must be declared
  • void, with no parameters. In addition, the function specification
  • must be followed by a specification of the interrupt source it is
  • attached to. The "using" attribute specifies which register bank
  • to use for the interrupt handler. / void serial_int (void) interrupt 4 { static char chr = '\0'; / character buffer / /
  • The interrupt was generated, but it is still unknown why. First,
  • check the RI flag to see if it was because a new character was
  • received. / if (RI == 1) / it was a receive interrupt / { chr = SBUF; / read the character into our local buffer / RI = 0; / clear the received interrupt flag / TI = 1; / signal that there's a new character to send / } else if (TI == 1) / otherwise, assume it was a transmit interrupt / { TI = 0; / clear the transmit interrupt flag / if (chr != '\0') / if there's something in the local buffer... / { if (chr == '\r') chr = '\n'; / convert to / SBUF = chr; / put the character into the transmit buffer / chr = '\0'; } } } / functions */ /*************************************************************************
  • 8051 using C code. To avoid using interrupts, this example polls
  • the interrupt flags in the serial port to know when the serial port
  • is ready. / / included headers / #include / register names / /
  • function declarations - Here the functions in our code are declared.
  • In C, this is only necessary if the actual implementation of the
  • function is performed in a separate file or after any function that
  • calls it in its own file. In this program, these functions will
  • all be implemented within this file. However, for asthetics,
  • functions will be implemented in order of highest-level to lowest.
  • By nature, this creates a scenario where the functions will be
  • called by code placed above the actual implementation, so the
  • functions must first be declared here. / char getCharacter (void); / read a character from the serial port / void sendCharacter (char); / write a character to the serial port / / functions */ /*************************************************************************
  • main - Program entry point. This program will simply receive characters
  • from the serial port, then send them back.
  • INPUT: N/A
  • RETURNS: N/A / main() { char chr; / variable to hold characters in / / Before the serial port may be used, it must be configured. / /
  • The serial controll register configures the method of operation
  • for the serial port. The value used below is 0x50 (or 50h in
  • 8051 lingo), referring to the bits within the SCON register,
  • this does the following:
  • MODE = 010 (8-bit UART serial buffer, no stop bit - this is typical)
  • REN = 1 (enabled receiving)
  • TB8, RB8 = 00 (unused in this mode)
  • RI,TI = 00 (start the interupt flags as ready to receive and send) / SCON = 0x50; / mode 1, 8-bit uart, enable receiver / /
  • Because a standard serial port transmits no clocking signal, both
  • ends of the serial connection must agree on a clock frequency,
  • which is then generated internally at each end. For this example,
  • a baud rate of 2400 bits per second will be used. The timer must be
  • configured accordingly.
  • The formula for determining the reload value based on desired baud
  • rate and clock frequency is:
  • TH1 = 256 - clock frequency (in Hz) / (384 * baud rate)
  • For 2400bps and a 11.02Mhz clock:
  • TH1 = 256 - 11,020,000 / (384 * 2400) = 255 = 0xFE / TMOD = 0x20; / timer 1, mode 2, 8-bit reload / TH1 = 0xFE; / reload value for 2400 baud / / Setting TR1 will start the timer, and serial communications / TR1 = 1; /
  • Set the Transmit Interrupt flag to send the the character in
  • the serial buffer, clearing it for use by the program. / TI = 1; /
  • Now the program isr eady to send and receive data on the serial
  • port. Because it is going to do this indefinitely (until the
  • device is effectively turned off), the rest of the program will
  • be in an infinite while() loop. / while (1==1) { / read the next character from the serial port / chr = getCharacter (); / send it back to the original sender */ sendCharacter (chr); } } /*************************************************************************
  • getCharacter - Waits for a new character to arrive in the serial port,
  • then reads it.
  • INPUT: N/A
  • RETURNS: newly received character */ char getCharacter (void) {

while (TI != 1) {;} /*

  • Clear the Transmit Interrupt flag to prepare the serial port
  • to send a new character. / TI = 0; / Write the character into the serial port buffer register, SBUF / SBUF = chr; /
  • The serial port hardware takes over from here, and the program
  • may continue with other operations. */ return; } Example 4 : Implementing a 4 bit Counter using an 8051 and Interfacing it to an LCD Prof. Frank Vahid

Purpose:

In this lab, you will learn how to write a simple C program for 80C

micro-controller, compile it using C51 compiler, and emulate it on an

emulator using Pro51. The program will be used to control a simple 4-

bit up-down counter, capable of counting from 0 to 15. At each step

the count should be displayed in decimal format on the LCD.

Assignment:

In this lab :

 You will design a 4-bit Up-Down counter using the C programming language for

the 8051 micro-controller and display the count on an LCD.

 You will then test and run your program on the 8051.

 The 4 bit counter has the following functionality:

o The counter has the following input pins :

a. Reset : when high resets the counter dataout to ``0000''

b. Updown : decides whether the counter counts up or down.

c. Load : makes the counter count from the 4 bit input Datain

d. Datain : which is a 4 bit input count

o The counter has a 4 bit Dataout to reflect the count.

o The count has to be sent to the LCD and displayed in decimal format.

Apparatus Required:

1. 4.7k resistors(8)

2. 1k resistor(1)

3. DIP switch

4. LCD

5. 5V power supply

6. Philips PDS51 development board \

sbit load = P0^6; sbit Start_LCD = P0^7; /* bit 7 of Port 3 / / Delay function / void delay() { int i, j; for(i=0; i<1000; i++) for(j=0; j<100; j++) i = i + 0; } / Function to output the decimal value of the count on the LCD / void PrintInt(unsigned char i) { char ch[4]; / Write code to convert the count to a string value and use the PrintString function provided in io.c / PrintString(ch); } void main(void) { unsigned char count = 0; InitIO(); / Initialize the LCD / while (1) { if (Start_LCD == 1) { ClearScreen(); PrintString("Ready..."); delay(); } else if (reset == 1) { / Output 0 on the LCD / } else if (load == 1) { / Output the current value of Datain on the LCD / } else { / Check the Up/Down pin for 1 or 0 count up or down accordingly. Display each value on the LCD */ } } }

Steps to be followed:

7. Wire up the circuit as shown in the schematic.

Note: Port 0 should not be used for output because it does cannot

sufficiently drive the LCD.

8. Map your network drive to

P:\Peart\cs

9. Run the batch file cs122.bat

10. Get the IO files to control the LCD. The functions specified in

these files are used to handle initialization and other special functions of

the LCD.

o io.c

o io.h

2. Open up a DOS window and edit your program under C: For eg:

  1. C:\Temp\count.c

4. Compile your programs

  1. c51 count.c
  2. c51 io.c

This would generate object files: count.obj, io.obj

7. Link the object files to create your executable file.

  1. bl51 count.obj, io.obj to count.omf Example 5 : Implementing a Calculator Using Peripherals Like a Keypad and LCD Prof. Frank Vahid

Purpose:

In this lab, you will build a simple calculator using the keypad as an

input and the LCD as an output peripheral. After debugging and testing

your program, you will have to burn the compiled program to a

standalone 8051 chip at the end of this lab.

Description:

Keypads are often used as a primary input device for embedded micro

controllers. The keypads actually consist of a number of switches,

connected in a row/column arrangement as shown in Fig 1.

In order for the micro controller to scan the keypad, it outputs a nibble

to force one (only one) of the columns low and then reads the rows to

see if any buttons in that column have been pressed. The rows are

pulled up by the internal weak pull-ups in the 8051 ports.

Consequently, as long as no buttons are pressed, the micro controller