[Back] [Top] [Next]

4 Program Structure And Layout

4.1 Modular Programming In C51

This is possibly not the place to make the case for modular programming, but a brief justification might be appropriate.

In anything but the most trivial programs the overall job of the software is composed of smaller tasks, all of which must be identified before coding can begin. As an electronic system is composed of several modules, each with a unique function, so a software system is built from a number of discrete tasks. In the electronic case, each module is designed and perfected individually and then finally assembled into a complete working machine. With software, the tasks are the building blocks which are brought together to achieve the final objective.

The overall program thus has a loosely-predefined modular structure which could sensibly form the basis of the final software layout. The largest identifiable blocks within the program are the tasks. These are in turn built from modules, which themselves are constructed from functions in the case of C.

The modules are in reality individual source files, created with a text editor. Grouping the software sections together according to the function with which they are associated is the basis of modular programming.

Using the CEMS engine control system again as a real example, the task of running the engine is divided into the following tasks:

    Task 1
    Provide Timed Sparks For Ignition

    Task 2
    Provide controlled pulsewidths for fuel injection

    Task 3
    Allow alteration of tune parameters via terminal




Considering Task 1, this is in turn composed of modules thus:

    Task 1, Module 1
    Determine crank shaft position and speed

    Task 1, Module 2
    Measure engine load

    Task 1, Module 3
    Obtain required firing angle from look-up table

Taking module 2, a C function exists which uses an A/D converter to read a voltage from a sensor. It is part of the overall background loop and hence runs in a fixed sequence. In module 1 an interrupt function attached to an input capture pin calculates engine speed and generates the ignition coil firing pulse. Module 3 is another function in the background loop and takes speed and load information from the other modules constituting the ignition function, to calculate the firing angle. Obviously, data must be communicated from the data collecting functions to the processing functions and thence to the signal generation parts across module boundaries.

In this case, the data flows are thus:


Commonly, the variables used are declared in the module that first supplies them with data. Hence the engine_load would be defined in Module 2 as that is where its input data comes from.

In this system the data would be declared thus:

Module_1.c                      Module_3.c                          Module_2.c 

/* Global Data Declaration */   /* Global Data Declaration */       /* Global Data Declaration */

unsigned char engine_speed      unsigned char advance               unsigned char engine_load

/* External Data References */  /* External Data References */      /* External Data References */

extern unsigned char advance    extern unsigned char engine_speed   extern unsigned char engine_load 

The most important thing to note is how the data defined in another module is referenced by redeclaring the required data item but prefixed with "extern".

Now, with a complete program spread across many different source files, the problem arises of how data is communicated between modules (files) and how separate C functions which lie outside of the home module may be accessed.

The next section illustrates how the linkage between modules is undertaken.

4.2 Accessibility Of Variables In Modular Programs

A typical C51 application will consist of possibly five functional blocks (modules) contained in five source files. Each block will contain a number of functions (subroutines) which operate on and use variables in RAM. Individual functions will (ideally) receive their input data via parameter passing and will return the results similarly. Within a function temporary variables will be used to store intermediate calculation values. As used to be done years ago in assembler, all variables (even the temporary ones) will be defined in one place and will remain accessible to every routine.

This approach is very inefficient and would seriously limit the power of C programs, as the internal RAM would soon be used up. The high-level language feature of a clearly defined input and output to each function would also be lost.

Similarly, an entire C program might be written within one single source file. As has been said, this practice was common many years ago with simple assemblers. Ultimately the source program can get so big that the 640K of a PC will get full and the compiler will stop. Worse than this, the ideal of breaking programs into small, understandable chunks is lost. Programs then become a monolithic block and consume huge amounts of listing paper...

There should therefore be a hierarchical arrangement of variables and functions within a program; complete functional blocks should be identified and given their own individual source files or modules. Use should be made of the ability to access external variables and functions to achieve small program files!

The following should help explain:


MODULE1.c: **************************************************************
   unsigned char global1 ;    (1)
   unsigned char global2 ;
   extern unsigned char ext_function(unsigned char) ;    (2)

/* Utility Routine */
   int_function(x)      (3)
   unsigned char x ;    (4)
   {
   unsigned int temp1  ;    (5)
   unsigned char temp2 ;
   temp 1 = x * x ;
   temp2  = x + x ;
 
   x = temp1/temp2 ;
 
   return(x)    (6)
   }

/* Program Proper */
   main()    (7)
   {
   unsigned char local1 ;    (5)
   unsigned char local2 ;
   local2 = int_function(local1) ;    (8)
   local1 = ext_function(local2) ;    (9)

   }
end of MODULE1.c **************************************************************

MODULE2.c: **************************************************************
   extern unsigned char global1 ;    (10)

   ext_function(y)
   unsigned char y ;
   {
   unsigned char temp ;
   static unsigned char special ;    (11) 
   
   special++ ;
   y = temp * global1 ;    (12)  

   return(y) ;
   )

Line (1) declares variables which will be accessible from all parts of the program. Ideally, such global usage should be avoided but where an interrupt has to update a value used by the background program, for example, they are essential.

Line (2) makes an external reference to a function not defined in the current module (block). This line allows all the functions in this MODULE to call the external function.

Line (3) declares a function which is to be used by another function in this module. These utility functions are placed above the calling function (here "main()").

Line (4) declares the variable which has been passed-over by the calling function. When the variable left "main()", it was called "local1". Within this function it is known simply as "x". The byte of ram is allocated to "x" only while the 8051's program counter is within this function. At the closing }, x will vanish.

Line (5) like "x" above, these variables are simply used as intermediate values within the function. They have no significance outside. Again, the byte of RAM will be re-assigned within another function. However the locals defined in "main()" will always exist as the C program is entirely contained within "main()".

Line (6) allows the result of the calculation to be passed back to the calling function. Once back in "main()" the value is placed in "local2".

Line (7) defines the start of the C program. Immediately prior to the point at which the program counter reachs main(), the assembler routine "STARTUP.A51" will have been executed. This in turn starts at location C:0000, the reset vector. Note that no parameters are passed to "main()".

Line (8) effectively calls the function defined above, passing the value "local1" to it.

Line (9) is like 8, but this time a function is being called which resides outside of the current module.

Line(10) links up with line(1) in that it makes "global1" visible to function within MODULE 2.

Line(11) declares a variable which is local to this function but which must not be destroyed having exited. Thus it behaves like a global except that no other function can use it. If it were placed above the function, accessibility would be extended to all functions in MODULE 2.

The physical linking of the data names and function names between modules is performed by the L51 linker. This is covered in detail in section 8.

4.3 Building A Real Modular Program -

The Practicalities Of Laying Out A C51 Program

The need for a modular approach to program construction has been outlined earlier. Here the practicalities of building easily maintainable and documentable software is given, along with a trick for easing the development of embedded C programs using popular compilers such as the Keil C51.

4.3.1 The Problem

The simplest embedded C program might consist of just

/* Module Containing Serial Port Initialisation */ /* V24IN537.C */
void v24ini_537(void)
   {

   /* Serial Port Initialisation Code */
   }

/* Module Containing Main Program */ /* MAIN.C */
/* External Definitions */

extern void v24ini_537(void) ;

void main(void) {
   v24ini_537() ;
   while(1) {
      printf("Time = ") ;
      }

This minimal program has only one purpose - to print an as yet incomplete message on the terminal attached to the serial port. Obviously, a single source file or "module" is sufficient to hold the entire C program.

Any real program will of course contain more functionality than just this. The natural reaction is to simply add further code to the existing main function, followed by additional functions to the MAIN.C source file. Unless action is taken the program will consist of one enormous source file, containing dozens of functions and interrupts and maybe hundreds of public variables.

Whilst compilers will still compile the file, the compilation time can become greatly extended, meaning that even the smallest modification requires the entire program to be re-compiled. A monolithic program is usually symptomatic of a lack of proper program planning and is likely to contain suspect and difficult to maintain code.

The next stage in the sample program development is to add some means of generating the time thus:

/* Module Containing Timer0 Initialisation */ 
/* T0INI537.C */

   void timer0_init_537(void) {
        /* Enable Timer 0 Ext0 interrupts */ 
        } /*init_timer_0*/

/* Module Containing Timer0 Service Routine */ 
/* RLT_INT.C */
/* Local Data Declarations */
/* Clock Structure Template */

struct time { unsigned char msec ;
              unsigned char sec  ; } ;

/* Create XDATA Structure */

struct time xdata clock ;
bit clock_run_fl = 0 ;  // Flag to tell timer0 interrupt
                        // to stop clock

/* External References */

extern bit clock_reset_fl // Flag to tell timer0 interrupt                             // to reset clock to zero

/***  INTERRUPT SERVICE FOR TIMER 0  ***/
   void timer0_int(void) interrupt 1 using 1 {
     if(clock.msec++ == 1000) {
        clock.sec++ ;
        if(clock.sec == 60) {
           clock_sec = 0 ;
           }
        }
     }

To make this 4 module program useful, the main loop needs to be altered to:

/* Module Containing Main Program */
/* MAIN.C */

#include <reg517.h>

/* External Definitions */

extern void v24ini_537(void) ;
extern void timer0_init_537(void) ;

/* General Clock Structure Template */

struct time { unsigned char secs  ;
              unsigned char msec  ; } ;

/* Reference XDATA Structure In Another Module */

extern struct time xdata clock ; extern bit clock_reset_fl // Flag to tell timer0 interrupt to reset clock to zero
/* Local Data Declaration */
bit clock_run_fl ;  // Flag to tell timer0 interrupt
                    // to stop clock
void main(void) {
   v24ini_537() ;
   timer0_init_537() ;
   while(1) {
      printf("Time = %d:%d:%d:%d",clock.hours,
                                  clock.mins,
                                  clock.secs,
                                  clock.msecs) ;
      }
   if(P1 | 0x01) {
      clock_run_fl = 1 ; // If button pressed start clock 
      }
   else {
      clock_run_fl = 0 ; // If button released stop clock
      }
   if(P1 | 0x02) {
      clock_reset_fl = 1 ; // If button pressed clear clock 
      }
   }

4.3.2 Maintainable Inter-Module Links

The foregoing program has been contructed in a modular fashion with each major functional block in a separate module (file). However even with this small program a maintenance problem is starting to become apparent The source of the trouble is that to add a new data item or function, at least two modules need to be edited - the module containing the data declaration plus any other module which makes a reference to the additional items. With long and meaningful names common in C and complex memory space qualification widespread in C51, much time can be wasted in getting external references to match at the linking stage. Simple typographic errors can waste huge amounts of time!

In large programs with many functions and global variables, the global area preceding the executable code can get very untidy and cumbersome. Of course, there is an argument that says that having to add external references to the top of a module when first using a new piece of global data is good practice, as it means that you are always aware of exactly which items are used. It is preferable to the common approach of having a single include file incorporated as a matter of course in each source file, containing an external reference for every global item, regardless of whether the host file actually needs them all.

This latter method inevitably leads to the undesirable situation where an original data declaration in the source module is sitting alongside its external reference in the general include file.

A solution to this is to have "module-specific" include files. Basically, for each source module ".c" file, a second ".h" include is created. This auxilliary file contains both original declarations and function prototypes plus the external references. It is therefore similar in concept to the standard library .h files used in every C compiler. The trick is, however, to use conditional compilation to prevent the original declarations and the external versions being seen simultaneously.

When included in their home modules, i.e. the ".c" file having the same root, only the original declarations are seen by C51 whereas, when included in a foreign module, only the external form is seen. To achieve this apparent intelligence, each source module must somehow identify itself to the include file.

The means to achieve this is to place a #define at the top of each module giving the name of the module. When included in its "home" module, the #ifdef-#else#-endif will cause the preprocessor to see the original declarations. When placed in foreign modules not sharing the same root, the preprocessor will see the external equivalents. Keil supports __FILE__ but it is not of practicle use in this context, as its "value" cannot be used for a #define name.

By only including module-specific header files in those modules that actually need to access an item in another module, the operation of powerful make utilities such as Polymake or Keil's own AMAKE, is improved; provided the dependency list is kept up to date, any changes to a .h file will cause all modules that reference it to be recompiled automatically. Thus a modified program cannot be built for testing unless all modules referencing the altered item successfully re-compile. This usefully relieves the linker from being alone responsible for symbol attribute cross-checking - something which some linkers cannot be relied upon to do.

In most embedded C dialects this can be a major help in program development as, for example, a change in a widely-used function's memory model attribute can easily be propagated through an entire program; the change in the intelligent header file belonging to the function's home module causing the AMAKE to recompile all other modules referencing it. Likewise, a change in a variable's memory space from say XDATA to PDATA needs only one header file to be edited - AMAKE will do the rest!

Here's how it's done in practice:


/* Module Containing Main Program - MAIN.C */
#define _MAIN_
/* Define module name for include file control */
#include <reg517.h>       // Definitions for CPU 
#include <v24ini537.h>  // External references from V24INI.C #include <t0ini537.h>   // External references from 
                //T0INI537.C 
#include <rlt_int.h>   
// External references for RLT_INT.C 

void main(void) {

   v24ini_537() ;

   timer0_init_537() ;

   while(1) {

      printf("Time = %d.%d",clock.secs,clock.msecs) ;
      }
   if(P1 | 0x01) {
      clock_run_fl = 1 ; // If button pressed start clock 
      }
   else {
      clock_run_fl = 0 ; // If button released stop clock
      }
   if(P1 | 0x02) {
      clock_reset_fl = 1 ; // If button pressed clear clock 
      }
   }

/* Module Containing Timer0 Service Routine - RLT_INT.C */
#define _RLT_INT_  /* Identify module name */

/* External References */
extern bit clock_reset_fl // Flag to tell timer0 interrupt to
                        // reset clock to zero

/***  INTERRUPT SERVICE FOR TIMER 0  ***/
   void timer0_int(void) interrupt 1 using 1 {
     if(clock.msec++ == 1000) {
        clock.sec++ ;
        if(clock.sec == 60) {
           clock_sec = 0 ;
           }
        }
     }
Taking the include files:

/* Include File For RLT_INT.C */

/* General, non-module specific definitions */
/* such as structure and union templates */
/* Clock Structure Template - Available To All Modules */
struct time { unsigned char secs  ;
              unsigned char msec  ; } ;

#ifdef _RLT_INT_
/* Original declarations - active only in home module */
/* Create XDATA Structure */
struct time xdata clock ;
bit clock_run_fl = 0 ;  // Flag to tell timer0 interrupt to stop clock
#else
/* External References - for use by other modules */
extern struct time xdata clock ;
extern bit clock_run_fl = 0 ;  // Flag to tell timer0 interrupt to stop clock
#endif

/* Include File For MAIN.C */
#ifdef _MAIN_
/* Local Data Declaration */
bit clock_run_fl =  0 ;  // Flag to tell timer0 interrupt to stop clock
#else 
/* External References - for other modules */
extern bit clock_run_fl ;  // Flag to tell timer0 interrupt to stop clock
#endif

/* Include File For V24INI537.C */
#ifdef _V24INI537_
/* Original Function Prototype - for use in V24INI537.C */
void v24ini_537(void) ;
#else
/* External Reference - for use in other modules */
extern void v24ini_537(void) ;
#endif

Now, should any new global data be added to, for example, RLT_INT.C, adding the original declaration above the "#endif" and the external version below, this makes the new item instantly available to any other module that wants it.

To summarise, the basic source module format is:

#define _MODULE_
#include <mod1.h>#include <mod2.h? 
. 
. 
.
functions()

The include file format is:

/* General, non-module specific definitions such as structure and union templates */
#ifdef _MODULE_
/* Put original function prototypes and global data declarations here */
#else
/* Put external references to items in above section here */
#endif

Standard Module Layouts For C51

To help integrate this program construction method, the following standard source and header modules shown overleaf may be used.

Standard Source Module Template


#define __STD__          
/* Define home module name */
***********************************************************/
***********************************************************/ 
/* Project:        X                                      */
/* Author:         X          Creation Date:  XX\XX\XX    */
/* Filename:       X          Language:       X           */
/* Rights:         X          Rights:         X           */
/*                                                        */
/* Compiler:   X                   Assembler:  X          */
/* Version:    X.XX                Version:    X.XX       */
/**********************************************************/
/* Module Details:                                        */ 
/**********************************************************/ 
/* Purpose:                                               */    
/*                                                        */    
/*                                                        */         
/**********************************************************/
/* Modification History                                   */
/**********************************************************/
/* Name:           X                     Date:  XX\XX\XX  */
/* Modification:   X                                      */
/*                                                        */                                         
/* Name:           X                      Date:  XX\XX\XX */
/* Modification:   X                                      */
/*                                                        */
/* Name:           X                      Date:  XX\XX\XX */
/* Modification:   X                                      */
/*                                                        */
/**********************************************************/
/**********************************************************/ 
/* External Function Prototypes                           */ 
/**********************************************************/
#include ".h"                                          
/* Standard ANSI C header files                           */
/**********************************************************/ 
/* Global Data Declarations                               */ 
/**********************************************************/
#include ".h"                                          
/* Home header file                                       */
/**********************************************************/ 
/* External Declarations                                  */ 
/**********************************************************/
#include  ".h" 					  
/* Header files for other modules   		          */
/**********************************************************/ 
/* Functions Details:                			  */ 
/**********************************************************/ 
/* Function Name:                     			  */ 
/* Entered From:                       			  */  
/* Calls:                       			  */  
/**********************************************************/ 
 /*********************************************************/ 
/* Purpose: main loop for training program                */ 
/*                                                        */ 
/**********************************************************/ 
/* Resource Usage:                                        */ 
/*                                                        */ 
/* CODE      CONST      DATA       IDATA      PDATA       */
/* n/a        n/a        n/a        n/a        n/a        */
/*                                                        */
/* Performance:                                           */ 
/* Max Runtime:                    Min Runtime:           */
/*                                                        */
/*                                                        */ 
/**********************************************************/
/* Executable functions                  		  */
/**********************************************************/
/**********************************************************/ 
/* End Of STD.c                     			  */ 
/**********************************************************/

Standard Include Header File Template

/**********************************************************/ 
/* Project:        X                                      */
/* Author:         X            Creation Date:  XX\XX\XX  */
/* Filename:       X            Language:       X         */
/* Rights:         X            Rights:         X         */
/*                                                        */
/* Compiler:   X                Assembler:  X             */
/* Version:    X.XX             Version:    X.XX          */
/**********************************************************/
/* Modification History                      		  */ 
/**********************************************************/ 
/* Name:           X                     Date:  XX\XX\XX  */
/* Modification:   X                                      */
/*                                                        */
/* Name:           X                     Date:  XX\XX\XX  */
/* Modification:   X                                      */
/*                                                        */
/* Name:           X                     Date:  XX\XX\XX  */
/* Modification:   X                                      */
/*                                                        */
/**********************************************************/
/**********************************************************/ 
/* Global Definitions                      		  */ 
/**********************************************************/
/* Structure and union templates plus other definitions   */

#ifdef _STD_             
/* Check for inclusion in home module                    */ 
/*********************************************************/
/*********************************************************/ 
/* Within Module Function Prototypes                     */ 
/*********************************************************/
/* Function prototypes from home module                  */
/*********************************************************/ 
/* Within Module Data Declarations                       */ 
/*********************************************************/
/* Data declarations from home module                    */
/*********************************************************/ 
#else
 
/*********************************************************/ 
/*********************************************************/ 
/* External Function Prototypes                          */ 
/*********************************************************/
/* External function prototypes for use by other modules */
/*********************************************************/ 
/* External Data Declarations                            */ 
/*********************************************************/
/* External data definitions for use by other modules    */
/*********************************************************/

#endif


Summary

Provided the necessary module name defines are added to the first line of any new module and the new globals placed into the associated ".h" file, the overall amount of editing required over a major project is usefully reduced. Compilation and, more particularly, linking errors are reduced as there is effectively only one external reference for each global item in the entire program. For structures and unions the template only appears once, again reducing the potential for compilation and linking problems.

4.4 Task Scheduling

4.4.1 8051 Applications Overview

When most people first start to learn to program, BASIC is used on a PC or similar machine. The programs are not usually too complicated; they start when you type _"RUN" and finish at END or STOP. In between, the PC is totally devoted to executing your "HELLO WORLD" program. When it is finished you are simply thrown back to the BASIC editor/"operating environment".

All this is very good and you think you now know how to program. However, when writing for an embedded microcontroller like the 8051, the problem of where does the program start and finish suddenly presents itself. The average 8051 software system consists of many individual programs which, when executed together, contribute towards the fulfilment of the overall system objective. A fundamental problem is then how to ensure that each part is actually run.

4.4.2 Simple 8051 Systems

The simplest approach is to call each major sub-function in a simple sequential fashion so that after a given time each function has been executed the same number of times. This constitutes a background loop. In the foreground might be interrupt functions, initiated by real time events such as incoming signals or timer overflows.

Data is usually passed from background to foreground via global variables and flags. This essentially simple program model can be very successful if some care is taken over the order and frequency of execution of particular sections.

The background-called functions must be written so that they run a particular section of their code on each successive entry from the background loop. Thus each function is entered, a decision is taken as to what to do this time, the code is executed and finally the program is exited, probably with some special control flags set up to tell the routine program what to do next time. Thus each functional block must maintain its own control system to ensure that the right code is run on any particular entry.

In this system all functional blocks are considered to be of equal importance and no new block can be entered until its turn is reached by the background loop. Only interrupt routines can break this, with each one having its own priority. Should a block need a certain input signal, it can either keep watching until the signal arrives, so holding up all other parts, or it can wait until the next entry, next time round the loop. Now there is the possibility that the event will have been and gone before the next entry occurs. This type of system is OK for situations where the time-critical parts of the program are small.

In reality many real time systems are not like this. Typically they will consist of some frequently-used code, the execution of which is caused by or causes some real-world event. This code is fed data from other parts of the system, whose own inputs may be changing rapidly or slowly.

Code which contributes to the system's major functionality must obviously take precedence over those sections whose purpose is not critical to the successful completion of the task. However most embedded 8051 applications are very time-critical, with such parts being attached to interrupts. The need to service as many interrupts as quickly as possible requires that interrupt code run times are short. With most real world events being asynchronous, the system will ultimately crash when too many interrupt requests occur per unit time for the cpu to cope with.

Fast runtimes and hence acceptable system performance are normally achieved by moving complex functions into the background loop, leaving the time-critical sections in interrupts. This gives rise to the problem of communication between background code and its dependant interrupt routine.

The simple system is very egalitarian, with all parts treated in the same way. When the cpu becomes very heavily loaded with high speed inputs, it is likely that major sub-functions will not be run frequently enough for the real-world interrupt code to be able to run with sufficiently up to date information from the background. Thus, system transient response is degraded.

4.4.3 Simple Scheduling - A Partial Solution

The problems of the simple loop system can be partially solved by controlling the order and frequency of function calling. One approach is to attach a priority to each function and allow each function to specify the next one to be executed. The real-world driven interrupt functions would override this steady progression so that the most important (highest priority) jobs are executed as soon as the current job is completed. This kind of system can yield useful results, provided that no single function takes too long.

An alternative is to control overall execution from a real time interrupt so that each job is allocated a certain amount of time in which to run. If a timeout does occur, that task is suspended and another begins.

Unfortunately all these tend to be bolt-ons, added late in a project when run times are getting too long. Usually what had been a well-structured program degenerates into spaghetti code, full of fixes and special modes, designed to overcome the fundamental mismatch between the demands of real time events and the response of the program. Moreover, the individual control mechanisms of the called functions generate an overhead which simply contributes to the runtime bottle-neck.

The reality is that real time events are not orderly and predictable. Some jobs are naturally more important than others. However inconvenient, the real world produces events that must be responded to immediately.

4.4.4 A Pragmatic Approach

Without resorting to a full real time executive like RTX51, what can be done?

A simple mechanism to control the running of the background loop can be a simple switch statement, with the switch variable controlled by some external real time event. Ideally this should be the highest priority interrupt routine. The high priority background tasks are placed at the top case, with lower priority tasks located further down the case statement. Thus, on every occurrence of the interrupt, the switch is set back to the top. As the background tasks execute, they increment the switch. If the interrupt is absent for long enough, the switch will reach the lowest level and then return to the highest level automatically.

Should the interrupt occur at level 2, the switch variable is forced back to zero and so tasks at the lowest levels are simply missed. This is by no means an ideal system, since only the top level is ever executed.given a high enough interrupt frequency.

However under normal conditions it is a useful way of ensuring that low priority tasks are not executed frequently. For example, there would be little point in measuring ambient temperature more than once per second. In a typical system this measurement might be at level 100 in a switch scheduler.

To be able to make a judgement about how best to structure the program, it is vital to know the run times for each section.

Where this simple method falls down is when a low priority task has a long run time. Even though the interrupt has requested that the loop returns back to the top level to calculate more data, there is no way of exiting the task until completed. To do so requires a proper time-slice mechanism.

A useful dodge can be to utilise an unused interrupt to guarantee that high priority tasks will be run on time. By setting the unused interrupt pending flag within the exiting high priority interrupt routine and placing the background task into the corresponding service routine, the punctual execution of the second task will occur. Of course, the unused interrupt priority must be set to a lower priority in the appropriate interrupt priority register(s).

The most important factor overall is to keep run times as short as possible, particularly in interrupt routines. This means making full use of C51 extensions like memory-specific pointers, special function bits and local regsiter variables.


[Back] [Top] [Next]