Module Design Guidelines for Real-Time Systems

Dr. Dobb's Journal February 2001

Team programming can work — if you follow the rules

By David Janik

David is an engineer for Tellabs and can be contacted at david.janik@tellabs.com.

A common problem faced by programming teams is the ability to produce consistent, reliable, and maintainable modules. One solution to this is to develop a set of coding convention guidelines that are adhered to by all team members. Typical coding conventions deal with syntax issues at the lowest level. However, the module guidelines I present in this article deal with module issues at the level above the syntax — that is, at a level of abstraction above the specific source code. These guidelines are therefore intuitive and maintainable, and promote reuse. Ultimately, development teams that follow guidelines such as these produce modules that are cohesive, decoupled, and encapsulated. For instance, we've used these guidelines in producing team-developed firmware for a 12-channel DS3/STS1 card of a digital cross-connect system. The card consists of several ASIC devices and is controlled by an internal LAN connection. In truth, the guidelines are generic for any C language module, including nonreal-time scenarios. However, the example queue and interrupt routines presented here show how you can apply the guidelines to real-time systems.

The module convention is modeled after the class data type from object-oriented programming languages. There are three sections to this convention:

Figure 1 is a simple model of module ABC showing the relationship between the abc_ProtectedData, abc_InternalFunctions, and abcExternalFunctions. It also shows the calling relationship between modules ABC and XYZ. Module ABC's internal or external functions can call only external functions of module XYZ. This module model convention facilitates data encapsulation. Module cohesion and decoupling characteristics are realized from the software architecture.

Figure 1 also illustrates the simple naming convention. External functions have the module name as a prefix (xyzExternalFunction(), for instance), while internal functions and protected data have the module name prefix and the underscore character (xyz_internalFunction() and xyz_protectedData, respectively). The intent of this naming convention is to intuitively identify exactly where the function or data resides when reading the source code. Consequently, there is no need to grep or cscope for a function.

Module Model Source Files

There are three types of source file structures associated to this module model:

Listing Three illustrates the simple file naming convention. The external functions have the module name as a prefix (abcMessage.c). Internal functions and protected data have the module name prefix and the underscore letter (abc_task.c, abc_map.h). The intent of the XXX_appSpec.h file is to group all the application-specific parameters into one file. This makes the module easier to reuse by only modifying one source file. Also note that there is no nesting of header files.

Module Types: Passive and Active

The two types of modules are passive and active. A passive module type is one where there are no real-time operating system (RTOS) tasks running. It is a simple function call and return. A passive module can use a semaphore to control access to its protected data.

An active module, on the other hand, is a module type that has at least one RTOS task created. The active module may be driving data into passive modules periodically or waiting for a message to be put onto its queue:

For the module model presented here, the queue is a protected data structure and not visible to the other modules. The other modules will call external functions of a module and these functions will post a message to the task queue; see Figure 3.

Modules ABC and XYZ do not have access to the queue pdq_queue since it is a protected data structure of module PDQ. Module ABC calls the external function pdqService1(), which puts a message onto the queue pdq_queue. Also, module XYZ calls external function pdqService2(), which puts a different message onto the queue pdq_queue.

The advantages of this type of design are cleaner interface to the PDQ module, maintainability, the format and message content for the queue may change without affecting modules ABC or XYZ, and data encapsulation (the only functions posting messages to the PDQ queue are within the PDQ module).

Interrupt Service Routines

The Interrupt Service Routine (ISR) can be internal or external functions, depending on how the hardware is designed. If the hardware has an interrupt controller device and all the interrupts go through this device, then all of the ISRs will be external functions — except the low-level driver module associated to the interrupt controller device. This will be an internal function that the interrupt vector points to. If the hardware has separate interrupt vectors for each device, then all of the ISRs will be internal functions. Each interrupt vector points to the associated internal function ISR. Figures 4 and 5 illustrate the two models.

Conclusion

By following the guidelines for firmware development I've presented here, the modules created will be more robust, maintainable, and reusable because they will have the object-oriented properties of being cohesive, decoupled, and data encapsulated.

DDJ

Listing One

(a)
/* Name: abc.c          */
/* Description:         */
/* Developer:           */

#include abc.h
#include abc_appSpec.h
#include pdq.h          /* prototype for pdqAction() */
#include rtos.h         /* prototype for rtosStartTask() */

/*** Protected Data             ***/
typedef
{
   int mode;
   int *device;
}abc_struct
abc_struct abc_data[ABC_INSTANCES];

/*** Internal Functions Prototypes ***/
int abc_instanceCheck(int instance);
int abc_messageCheck(int message);

/*** Internal Functions ***/
int abc_instanceCheck(int instance)
{
   int result = ABC_ERROR;
   if (instance < ABC_INSTANCE)
   {
      result = ABC_OK;
   }
   return (result);
}
/*** ***/
int abc_messageCheck(int message)
{
   int result = ABC_ERROR;
   if (message == ABC_MESSAGE_1 || message == ABC_MESSAGE_2)
   {
      result = ABC_OK;
   }
   return (result);
/*** External Functions ***/
int abcInit()
{
   int i;
   for (i=0; i<ABC_INSTANCES; i++)
   {
      abc_data[i].mode   = 0;
      abc_data[i].device = (int *)(ABC_BASE_ADDRESS + (0x100 * i);
   }
   rtosStartTask(abc_task, ABC_PRIORITY);
   return (ABC_OK);
}
/***  ***/
int abcModeGet(int instance, int *mode)
{
   int result = ABC_ERROR;
   if (abc_instanceCheck(instance) == ABC_OK)
   {
      *mode = abc_data[instance].mode;
      if (*mode == 1)
      {
         pdqAction(*abc_data[instance].device);
         abc_data[instance].mode = 0;
      }
      result = ABC_OK;
   }
   return (result);
}
/*** ***/
int abcMessageSend(int instance, int message)
{
   int result = ABC_ERROR;
   if (abc_instanceCheck(instance) == ABC_OK)
   {
      if (abc_messageCheck(message) == ABC_OK)
      {
         *abc_data[instance].device = message;
         result = ABC_OK;
      }
   }
   return (result);
}

(b)
/* Name: abc.h          */
/* Description:         */
/* Developer:           */

#define ABC_ERROR       1
#define ABC_OK          0

#define ABC_MESSAGE_1   1
#define ABC_MESSAGE_2   2

int abcInit(void);
int abcModeGet(int instance, int *mode);
int abcMessageSend(int instance, int message);

(c)
/* Name: abc_appSpec..h            */
/* Description:                    */
/* Developer:                      */

#define ABC_INSTANCES      10
#define ABC_BASE_ADDRESS   0x1230

#define ABC_IRQ            7
#define ABC_PRIORITY       5
#define ABC_PAUSE          100       /* 1 second */

Back to Article

Listing Two

(a) 
/* Name: abc.c          */
/* Description:         */
/* Developer:           */

#include abc.h
#include abc_appSpec.h
#include abc_internal.h
#include abc_map.h
#include pdq.h          /* prototype for pdqAction() */
#include rtos.h         /* prototype for rtosStartTask() */

/*** Protected Data             ***/
abc_struct abc_data[ABC_INSTANCES];   /* declare Protected Data Structure */

/*** External Functions ***/
int abcInit()
{
   int i;

   for (i=0; i<ABC_INSTANCES; i++)
   {
      abc_data[i].mode   = 0;
      abc_data[i].device = (int *)(ABC_BASE_ADDRESS + (0x100 * i);
   }

   rtosStartTask(abc_task, ABC_PRIORITY);

   return (ABC_OK);
}

/***  ***/
int abcModeGet(int instance, int *mode)
{
   int result = ABC_ERROR;
   if (abc_instanceCheck(instance) == ABC_OK)
   {
      *mode = abc_data[instance].mode;
      if (*mode == 1)
      {
         pdqAction(*abc_data[instance].device);
         abc_data[instance].mode = 0;
      }
      result = ABC_OK;
   }
   return (result);
}

/*** ***/
int abcMessageSend(int instance, int message)
{
   int result = ABC_ERROR;
   if (abc_instanceCheck(instance) == ABC_OK)
   {
      if (abc_messageCheck(message) == ABC_OK)
      {
         *abc_data[instance].device = message;
         result = ABC_OK;
      }
   }
   return (result);
}

(b)
/* Name: abc_internal.c     */
/* Description:             */
/* Developer:               */

#include abc_appSpec.h
#include abc_internal.h
#include abc_map.h
#include rtos.h         /* prototype for rtosPause() */

/*** Internal Functions ***/

int abc_instanceCheck(int instance)
{
   int result = ABC_ERROR;
   if (instance < ABC_INSTANCE)
   {
      result = ABC_OK;
   }
   return (result);
}

/*** ***/
int abc_messageCheck(int message)
{
   int result = ABC_ERROR;
   if (message == ABC_MESSAGE_1 || message == ABC_MESSAGE_2)
   {
      result = ABC_OK;
   }
   return (result);
}

/*** ***/
int abc_task()
{
   int instance = 0;

   for(;;)
   {
      rtosPause(ABC_PAUSE);
      if (*abc_data[instance].device != 0)
      {
         abc_data[instance].mode = 1;
      }
      instance++;
      if (instance >= ABC_INSTANCE)
      {
         instance = 0;
      }
   }
}

(c) 
/* Name: abc_internal.h     */
/* Description:             */
/* Developer:               */

/*** Internal Functions ***/
int abc_instanceCheck(int instance);
int abc_messageCheck(int message);
int abc_task(void);

(d) 
/* Name: abc_map.h      */
/* Description:         */
/* Developer:           */

/*** Protected Data             ***/
typedef
{
   int mode;
   int *device;
}abc_struct

extern abc_struct abc_data[ABC_INSTANCES];

(e) 
/* Name: abc.h          */
/* Description:         */
/* Developer:           */

#define ABC_ERROR       1
#define ABC_OK          0

#define ABC_MESSAGE_1   1
#define ABC_MESSAGE_2   2

int abcInit(void);
int abcModeGet(int instance, int *mode);
int abcMessageSend(int instance, int message);

(f)  
/* Name: abc_appSpec..h    */
/* Description:            */
/* Developer:              */

#define ABC_INSTANCES      10
#define ABC_BASE_ADDRESS   0x1230
#define ABC_IRQ            7
#define ABC_PRIORITY       5
#define ABC_PAUSE          100 /* 1 second */

Back to Article

Listing Three

(a) 
/* Name: abcControl.c       */
/* Description:             */
/* Developer:               */

#include abc.h
#include abc_appSpec.h
#include abc_utilities.h
#include abc_map.h
#include abc_task.h
#include pdq.h          /* prototype for pdqAction() */
#include rtos.h         /* prototype for rtosStartTask() */

/*** Protected Data             ***/
abc_struct abc_data[ABC_INSTANCES];   /* declare Protected Data Structure */

/*** External Functions ***/
int abcInit()
{
   int i;

   for (i=0; i<ABC_INSTANCES; i++)
   {
      abc_data[i].mode   = 0;
      abc_data[i].device = (int *)(ABC_BASE_ADDRESS + (0x100 * i);
   }

   rtosStartTask(abc_task, ABC_PRIORITY);

   return (ABC_OK);
}
/***  ***/
int abcModeGet(int instance, int *mode)
{
   int result = ABC_ERROR;
   if (abc_instanceCheck(instance) == ABC_OK)
   {
      *mode = abc_data[instance].mode;
      if (*mode == 1)
      {
         pdqAction(*abc_data[instance].device);
         abc_data[instance].mode = 0;
      }
      result = ABC_OK;
   }
   return (result);
}

(b) 
/* Name: abcMessage.c       */
/* Description:             */
/* Developer:               */

#include abc.h
#include abc_appSpec.h
#include abc_utilities.h
#include abc_map.h

/*** External Functions ***/
int abcMessageSend(int instance, int message)
{
   int result = ABC_ERROR;
   if (abc_instanceCheck(instance) == ABC_OK)
   {
      if (abc_messageCheck(message) == ABC_OK)
      {
         *abc_data[instance].device = message;
         result = ABC_OK;
      }
   }
   return (result);
}

(c) 
/* Name: abc.h          */
/* Description:         */
/* Developer:           */

#define ABC_ERROR       1
#define ABC_OK          0

#define ABC_MESSAGE_1   1
#define ABC_MESSAGE_2   2

int abcInit(void);
int abcModeGet(int instance, int *mode);
int abcMessageSend(int instance, int message);

(d) 
/* Name: abc_utilities.c   */
/* Description:            */
/* Developer:              */

#include abc_utilities.h
#include abc_appSpec.h
#include abc_map.h

/*** Internal Functions ***/
int abc_instanceCheck(int instance)
{
   int result = ABC_ERROR;
   if (instance < ABC_INSTANCE)
   {
      result = ABC_OK;
   }
   return (result);
}

/*** ***/
int abc_messageCheck(int message)
{
   int result = ABC_ERROR;
   if (message == ABC_MESSAGE_1 || message == ABC_MESSAGE_2)
   {
      result = ABC_OK;
   }
   return (result);
}

(e) 
/* Name: abc_utilities.h        */
/* Description:                 */
/* Developer:                   */

/*** Internal Functions ***/
int abc_instanceCheck(int instance);
int abc_messageCheck(int message);

(f) 
/* Name: abc_task.c     */
/* Description:         */
/* Developer:           */

#include abc_task.h
#include abc_appSpec.h
#include abc_map.h
#include rtos.h         /* prototype for rtosPause() */
/*** Internal Functions ***/
int abc_task()
{
   int instance = 0;

   for(;;)
   {
      rtosPause(ABC_PAUSE);
      if (*abc_data[instance].device != 0)
      {
         abc_data[instance].mode = 1;
      }
      instance++;
      if (instance >= ABC_INSTANCE)
      {
         instance = 0;
      }
   }
}

(g) 
/* Name: abc_task.h     */
/* Description:         */
/* Developer:           */

/*** Internal Functions ***/
int abc_task(void);

(h) 
/* Name: abc_map.h      */
/* Description:         */
/* Developer:           */

/*** Protected Data             ***/
typedef
{
   int mode;
   int *device;
}abc_struct

extern abc_struct abc_data[ABC_INSTANCES];

(i)  
/* Name: abc_appSpec..h    */
/* Description:            */
/* Developer:              */

#define ABC_INSTANCES      10
#define ABC_BASE_ADDRESS   0x1230
#define ABC_IRQ            7
#define ABC_PRIORITY       5
#define ABC_PAUSE          100 /* 1 second */

Back to Article