Software Testing Ideas For ISO9000 & Tickit

|< back

You can find a PDF version of this document here for printing.


Document Overview


Synopsis

This piece seeks to fill in some of the gaps left in the DTi’s TickIT guide in the areas of source documentation standards, coding practices and testing techniques. In the same way that TickIT interprets ISO9000 for software development in general, solutions to the special problems of embedded microcontroller development are explored here within a TickIT framework.

The contention is made that system source files are part of system documentation and therefore be within the scope of the quality system. The need for modular program is identified, along with special steps to cope with the language extensions commonly made by embedded compiler vendors. Test harnesses are proposed as a major aid to ensuring software integrity and the case for true in-circuit testing made.

Real embedded system examples are used to illustrate how the relevant section headings given in the guide can be implemented in practice, using commercially available development tools. The new HiSCRIPT test language is used to create the test examples.


The relevant TickIT sections are:

4.5.5 Produce High Level Software Design
Test strategy - identify critical and testable blocks and incorporate test point labels. Employing a suitable approach to embedded program construction.
“Design tools and methods”.
Simulation and in-circuit emulation methods

4.5.7 Coding And Unit Test
“Coding Standards”
Avoiding bad program practices and structures
“Testing standards”
Designing inherent testability into software.

4.5.8 Integrate And System Test
Functional block testing before installation into main system software.
“Test Results”
Assessing test results
“Testing Standards”
Extent of testing required
“Test tools and test harnesses”
Simulation and in-circuit test harnesses
“Coverage”
The use of the coverage metric as an indicator of completeness of testing and integrity of software under test.

4.5.15 Maintenance
Confirming continued correct operation after modification/optimisation of current software products by using original test harnesses..

5.7 Testing and Validation 5.7.2 Test Planning,
Parts a-e

5.7.3 Testing
Parts:
a: “Test results recorded” c: “Areas impacted by modifications should be identified and retested.” e: “The hardware and software configuration should be considered and documented.”

5.7.4 Validation
“supplier validates operation as a complete product in real conditions.”

5.10.7 Release Procedures
d: “Methods to confirm that changes implemented will not introduce other problems.”
6.2 Document Control
Note: It is our contention that source files source files should be under control of quality system.

6.2.2 Part c: “verification and validation plans and results”.

6.4 Measurement 6.4.1 Product Measurement
Parts:
a: “Collect data and report metric values on a regular basis” b: “To identify the current level of performance on each metric” c: “Take corrective action if metric levels grow worse or exceed established target levels”
Performance analysis as a continued activity throughout the development phase.

6.6 Tools And Techniques
Source documentation, program construction and test harness generation. The use of in-circuit testing techniques.
Further sections will be added on configuration management (“version control”) in a later version.


Introduction

Apart from surviving the recession, perhaps the greatest issue within most companies is the implementation of ISO9000 and its European equivalent, ISO9000. This is particularly true of small to medium sized companies who are often dependant on contracts as suppliers to the giants like GEC, BT etc..

In the mechanical design and development areas, production, distribution and maintenance aspects of a companies’ activities are covered by ISO9000 and there is now a good deal of accumulated experience within industry and the consultants and specialists who advise and accredit. However, on the software side of a companies design and development effort there is as yet an enormous grey area.

It is not that ISO9000’s directives on software are any more obscure than for the other facets. Indeed the whole standard is completely non-specific about any activity. In-non-software fields, wisdom from conventional quality control methods have been neatly repackaged and is successfully applied to ISO9000.

Software, being a young discipline has no such tradition of QA methods; so when the software department is asked to submit its proposals for how the companies’ software should be produced so as to match the ISO9000-compliant techniques used in other parts of the product, a level of panic sets in!

To some extent, the DTI has tried to assist in a sensible interpretation via its TickIT initiative. However, this tends to be more relevant to mainframe/mini/desktop computer software rather than the truly embedded, single chip microcontroller systems, which are likely to be of more concern to readers. Like its parent, ISO9000, it offers no specific guidance on what to do, merely setting out a management framework for software production in the form of subject headings.

Within very large organisations who have operated in markets where software quality has always been an issue - aerospace for example, a whole department may be devoted just to the task of defining software production methods. For instance, GEC Avionics has a “Productivity Executive” whose brief is to provide procedures and tools that should be used to ensure quality.

The software producer in the small company therefore has a problem; given that the text of ISO9000 and TickIT provide no guidance on what should be done, how can his software be demonstrably in accordance with the standard?

To further compound the problem, the are of software quality and reliability is the subject of a raging (academic) debate about the use of formal mathematical methods etc., most of which are beyond the experience of most software engineers.

Pragmatism must rule in this area and whilst recognising that “perfect” software is currently an impossibility, the use of some basic approaches and procedures will allow most companies to say with a clear conscience that they have at made the effort to improve their software methods.

The real dilemma of the software engineer or department trying to show that the development process meets ISO9000 is that there are no rules. Indeed, ISO9000 allows the rules to be written to fit the current regime, so no matter how sloppy or flawed the procedures might be, as long as it is documented and adhered to, it meets the standard.

To do this however, borders on the dishonest or at least, unethical. In the preparations for Hitex’s ISO9001 certification, the software development procedures and conventions were examined and in some cases tightened up before documentation. The ideas contained in the next few articles are based on our own in-house methods. Hopefully, they may be of use to others or can be adapted.

It must be made clear however, that the ideas presented are just that - ideas and are offered to assist others in reviewing how their software development procedures can be improved prior to certification!

1. Overview

To try an alleviate the overall ISO9000 dilemma, this series of articles will provide suggestions for way in which coding and testing phases can be made more suitable for a ISO9000 software regime.

- How to build-in sufficient information into source files to provide traceability

- How to layout the source to assist readability and ease of maintenance

- Source file documentation for ISO9000

- Using modular programming to enhance program structure

- Tricks to allow easier maintenance an speed of initial program creation.

- Programming practices to avoid

- The role of the test harness

- How to use custom test-harnesses

- Program coverage measurement as a part of system documentation

To make the material as relevant to the greatest number of readers, the C language will be used as a basis. Please accept our apologies in advance to speakers of Modula2, PASCAL etc. for this necessary restriction!

 

2. Documenting Source Files For ISO9000

With one of the main objectives of ISO9000 being the maintenance of records to allow complete trace ability, perhaps the most basic step to be taken is the definition of a standardised source file layout. Ultimately, this is the only tangible manifestation of the code to be shipped.

2.1 The Boilerplate

The most important section from the point of view of records is the source file “boilerplate”. This is a text block, usually near the top of the file, which gives information about the history of the file. Typical information would be project name, original creation date, author(s), language, compiler etc.. It allows even the casual reader to know who wrote the file, which modules it contains, when it was created, which harnesses were used to test it etc..
The most important tool for testing and the subsequent production of documentary evidence of testing is the test harness. Ideally, each defined function in a module must have an associated set of test harnesses (test harness construction will be covered in detail in a later article). These ought to be viewed as inseparable from the actual C function and should be written alongside the function as it is created. Indeed, the function should be designed from the outset to be inherently “testable”. Thus any boilerplate for a function must also give details of its associated harnesses. The main boilerplate must therefore make mention of these test programs as well.

2.2 Module And Function Level Boilerplates

Two basic types of boilerplate are necessary, one at the top of the source file accompanied by separate ones for each function. Besides the creation dates, names etc., the module level boilerplate contains a summary of the boilerplates for each function so that each function contained can be easily identified. The function-level boilerplate contains detailed information on the function’s history and author, plus runtimes, memory resources used and test harness names.

The boilerplate formats employed must encourage the programmer to run the appropriate test harness after each modification has been made. Obviously, this is not practicable during the initial debugging stage but before the completed function (or module) is re-introduced to form the next formal version of the whole program, the test harness(es) must be run to prove the integrity of the altered function.

2.3 Module-Level Boilerplate

What follows is a typical module-level boilerplate for a ISO9000 source file:

/************************************************************/ 
/* MODULE NAME:                                             */ 
/* Project:                                                 */ 
/* Name:                                                    */ 
/* Filename:                                                */ 
/* Language:                                                */ 
/* System:                                                  */ 
/* Rights:                                                  */ 
/*                                                          */ 
/* Compiler:                                                */ 
/* Assembler:                                               */ 
/* Version:                                                 */ 
/*                                                          */ 
/************************************************************/ 
/* Modification History:                                    */ 
/*                                                          */ 
/************************************************************/ 
/* Date:                                                    */ 
/* Name:                                                    */ 
/* Function:                                                */ 
/* Test Harness:                                            */ 
/*                                                          */ 
/* Modification Details:                                    */ 
/*                                                          */ 
/*                                                          */ 
/*                                                          */ 
/* Date Test Performed:                                     */ 
/* Engineer:                                                */ 
/*                                                          */ 
/* Signed Off By:                                           */ 
/*              ———————————————————      */
/*                                                          */
/************************************************************/ 
/* Date:                                                    */ 
/* Name:                                                    */ 
/* Function:                                                */ 
/* Test Harness:                                            */ 
/*                                                          */ 
/* Modification Details:                                    */ 
/*                                                          */ 
/*                                                          */ 
/* Date Test Performed:                                     */ 
/* Engineer:                                                */ 
/*                                                          */
/* Signed Off By:                                           */ 
/*              ———————————————————      */
/*                                                          */
/************************************************************/ 
. . . . (More function blocks...)

 

2.4 Function Level Boilerplates

The object is to make it possible to trace the history of module just by printing out the top boilerplate. After final release, the engineers involved should sign the print-out in the allotted positions. This signed-off document should be added to the main software records.

However, the most important information blocks are those accompanying each declared function. These should contain details of received parameters, return value, maximum and minimum run time and memory requirements. Some of the entries such as “coverage obtained”, “Max. Runtime (exclude)” will be covered in later articles in this series, along with the specialised tools required to acquire them.

The function boilerplate should also contain details of tests performed and the names of test harnesses used . Date and time of test should be recorded along with the engineer performing the test. A space should be left for the engineer to sign the final listing of the released function.

/**************************************/ 
/* FUNCTION No.                       */ 
/*                                    */ 
/* Functions Details:                 */ 
/**************************************/ 
/* Function Name:                     */ 
/**************************************/  
/************************************************************/ 
/* Purpose:                                                 */ 
/*                                                          */ 
/*                                                          */
/************************************************************/ 
/************************************************************/ 
/* Task Or Interrupt Source:                                */ 
/************************************************************/ 
/* Resource Usage:                                          */ 
/*                                                          */ 
/* CODE     CONST     IDATA     XDATA    STACK   USER STACK */
/*                                                          */
/************************************************************/ 
/*                                                          */ 
/* Performance Analysis:                                    */ 
/*                                                          */ 
/* Max. Run Time (include)                                  */ 
/* Min. Run Time: (include)                                 */ 
/* Average Run Time: (exclude)                              */ 
/* Max. Run Time (exclude)                                  */ 
/* Min. Run Time: (exclude)                                 */ 
/* Average Run Time: (exclude)                              */ 
/*                                                          */ 
/* Called From:                                             */ 
/* Calls:                                                   */ 
/*                                                          */ 
/* Date Test Performed:                                     */ 
/* Engineer:                                                */ 
/*                                                          */ 
/* Signed Off By:                                           */ 
/*              ———————————————————      */
/************************************************************/
/* Test Harness Details                                     */ 
/************************************************************/ 
/* Harness Name:                                            */ 
/*                                                          */ 
/* Purpose:                                                 */ 
/*                                                          */ 
/* % Coverage Obtained:                                     */ 
/* Date Test Performed:                                     */ 
/* Engineer:                                                */ 
/*                                                          */ 
/* Signed Off By:                                           */ 
/*              ———————————————————      */
/************************************************************/
. . . (further test harnesses...)

 

Using the above module- and function-level boilerplates will keep the need for record keeping and traceability in front of the programmer throughout a project.

A complete source file might take the form:

Module Boilerplate

#include files

Function Boilerplate0

function0()

Function Boilerplate1

function1()
.
.
Further functions....

3. Making Source Files Readable

As the executable statements are basically just text, they are part of the system documentation and thus under the umbrella of quality rejime. Ideally, the actual program should contain sufficient “comment” information for even a non-software engineer to be able to derive some understanding of how a piece of code works. The boilerplate’s part in this has already been explained but it is equally important that each part of a function is explained in plain English text.

3.1 Building A Comment Index

In a written report, the chapters, paragraphs and sometime even sentences are given reference numbers, as in this humble piece! The reason is of course to allow a specific subject to be located easily from a contents page or index, regardless of the overall size of the piece. The same principle can be applied to a source file. After all, in its own way it is a written work.

Thus modules, functions and major groups of C statements should all be given reference numbers:

 

/******************************************************/ 
/* MODULE NAME: TESTMOD                               */ 
. 
. 
/**************************************/ 
/* FUNCTION No. TESTMOD.1                  */ 
. 
. 
/*** TESTMOD.1.4 - Major Block In Function TESTMOD.1 ***/ 
. 
/*   TESTMOD.1.4.2 - Minor Block In Function TESTMOD.1.4 */ 
.

From this, a complete reference can be built up which will allow any section of code to be found instantly. This can be achieved by a text editor macro. Editors such as brief can search for the module name and then for any title that contains it. The gathered titles are then placed into an index file which refers to every module in the program.

Any quality assessor should be satisfied with this type of comprehensive indexing!

3.2 Aids To Comprehension

Readability and hence maintainability can be enhanced at a function level by adopting a few simple conventions.

3.3 Titles And Comments

Each group of statements that have some identifiable end-product should have a title. Conditional loops are easily isolated sections and demand a major title if they contain some “active” statement:

/* TESTMOD.10.1.1 Clear Buffer Tx Buffer */
while(i < Buf_Length) {  
   tx_buff[i] = ‘\0’ ;     // Write null terminator into buffer
   }

In the case where only a very simple action is performed, only a comment is sufficient:

while(!TI) ; // Wait for bit to become set

Where several identifiable processes are naturally grouped together, a major title is required.

/*** TESTMOD.10.1 Copy String Into Tx Buffer ***/
while(!TI) ; // Wait for bit to become set
/* TESTMOD.10.1.1 Clear Buffer Tx Buffer */
while(i < Buf_Length) {  
   tx_buff[i] = ‘\0’ ;    // Write null terminator into buffer
   }
/* TESTMOD.10.1.2 Put Message Into Buffer */
strcpy(tx_buff,message) ; 


/*** TESTMOD.10.2 Transmit String ***/
   do {
      while(!TI) ;             // Wait for character to be sent   
      TI = 0 ;                        // Clear TI flag  
      SBUF = tx_buff[i] ;             // Put next char into SBUF
      } while(tx_buff[i++] != ‘\0’)  // Check for end of buffer

3.4 Variables

The norm with C is that variables are always lower case.

Constants (Data)

Commonly, these too are lower case but to make reading the source as informative as possible, a useful idea is to capitalise the first letters as per:

#define Test_Data 0x80

It is then immediately obvious when reading a source which items are variables and which are constants. As C is case sensitive, it is also possible to have constants with the same name as a variable. This is especially useful when dealing with initialised data:

int test_data = Test_Data ;

3.5 Conditional Compilation Constants

These are not program constants and their position within a source file should make them easily distinguishable. A good idea is to pre-pend and append underscores (‘_’), even though K & R argue against this!

#define _CONTROL_ 
#ifdef _CONTROL_
/* Do something! */
#endif

In the case of compiler generated definitions, they have by convention, double underscores such as __DATA__ where a string corresponding to the date of compilation is generated.

4. Embedded C Program Construction For ISO9000 Regimes

4.1 Giving Programs A Maintainable Structure

The manner in which a program is built can greatly influence the ease with which is can be subsequently modified. This is especially true in long-term projects where engineers may move in and out of a team periodically. A well structured program will be simpler to comprehend and thus be less daunting to change. However, a good program structure must be planned from the outset. Whilst there are any number of specialist CASE tools designed to aid the analysis of new systems to be implemented in software, it is possible to use just a little forethought and common sense to get a project’s source files started on good foundations.

4.2 The Need For Modular Programming

4.2.1 Task-Based Modules

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. In the same way that 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 job to be performed thus has a loosely-predefined modular structure which could sensibly form the basis of the final software construction. The largest identifiable blocks within the program are usually the “tasks”. These are in turn built from modules, which themselves are constructed from functions, in the case of C.

4.2.2 Grouping Functions Into Modules

The modules are in reality individual source files, created with a text editor. Grouping the software sections together into modules according to which aspect of the system’s functionality they are associated with is the basis of modular programming. In some systems, modules can be formally grouped into tasks like functions but these are unusual in the embedded world. The Siemens 80C166 tools have this capability, for example. Generally though, the grouping of modules into tasks only exists within the programmer’s head and is really only a notional thing! If however a real time executive is in use, then t
his association becomes more tangible.

Task0 Or “Job0”

module A         moduleB          moduleC
function1a       function1b       function1c       
function2a       function2b       function2c
function3a       function3b       function3c

Task1 Or “Job2”

module A         moduleB          moduleC
function1a       function1b       function1c       
function2a       function2b       function2c
function3a       function3b       function3c

 

4.2.3 Breaking A Complex System Into Tasks And Modules

As an example, in an automotive engine management system, the job 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.

4.2.4 Communication Between Modules

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”. To make this data visible to other modules i.e., “public”, it must be defined outside any function. For a complete discussion of the scope of C variables, please refer to a standard C reference book.

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.

4.3 Building A Real Modular Program - A Method For Laying Out A Program Suitable For ISO9000 / TickIT

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 An Example

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.

4.3.2 Avoiding Monlithic Source Files

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.

4.3.3 Increasing Program Complexity

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.4 Constructing Maintainable Inter-Module Links

The foregoing program has been constructed 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.

4.3.5 The “Intelligent” 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 auxiliary 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 acheive this apparent intelligence, each source module must somehow identify itself to the include file.
The means to acheive 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. As an alternative to a user-defined file name macro, some compilers will allow a file to identify itself via a predefined macro of name “__FILE__”.

4.3.6 Simplifiying Embedded C Maintenance

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

Here’s how its done:

/* 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, 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()

4.3.7 Method Summary

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

4.3.8 Standard Module Layouts For ISO9000

To help integrate this program construction method into a ISO9000 regime, the following standard source and header modules may be used.

Note: for the sake of brevity, the blocks referring to tests and test harnesses have been omitted!

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

Please Note: For the sake of brevity, the boilerplate sections referring to test harness-based activities, as shown in sections 2.3 and 2.4, have been omitted.

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

4.4 Modular Construction 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.


Section 5 Of This Document Continues....