




Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Material Type: Notes; Class: Microprocessors and Controllers II; Subject: Computer Engr & Computer Sci; University: California State University - Long Beach; Term: Unknown 1989;
Typology: Study notes
1 / 8
This page cannot be seen from the preview
Don't miss anything!





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 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. 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 anotherwise ANSI- compliant implementaiton of the C programming language. These modifications were made solely to facilitate the use of a higher-level language like C for writting 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 Byte s Range char 8 1 -128 to + unsigned char 8 1 0 to 255 enum 16 2 -32,768 to +32, short 16 2 -32,768 to +32, unsigned short 16 2 0 to 65, int 16 2 -32,768 to +32, unsigned int 16 2 0 to 65,
/* 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 MOV R0, #080h
(data memory addresses 0x00-0xFF) 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 / / 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 (unsigned int var) { .... functionname (var) unsigned int var {
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(); } } Appendix A - Sample Code Sample code that is fully compilable in the evaluation version of Keil may be found at the following URL: http://ubermensch.org/Computing/8051/code