Contenido>Indice>Intro CursoC51

LA CONSTRUCCIÓN DE UN PROGRAMA MODULAR



Ya se ha explicado anteriormente la necesidad de construir programas modulares. Aquí se tratará sobre los aspectos prácticos de la construcción de software documentado y fácilmente mantenible, junto a un truco para desarrollar programas C utilizando compiladores como el Keil C51.

El problema

Uno de los más sencillos programas C consitiría en:

/* Módulo de inicialización del puerto serie */ /* V24IN537.C */
void v24ini_537(void)
   {
   /* Código inicialización del puerto serie */
   }

/* Módulo con el programa principal */ /* MAIN.C */
/*  Definiciones Externas */

extern void v24ini_537(void) ;

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

 

Este pequeño programa tiene un solo propósito, imprimir un mensaje todavía incompleto a un terminal conectado al puerto serie. Obviamente, un solo módulo, o fichero fuente es suficiente para almacenar el programa completo.

Cualquier programa real contiene mayor funcionalidad que el anterior. La reacción natural consiste en añadirle funciones hasta lograr la funcionalidad deseada. Pero si no se toman acciones en contra, se puede conseguir un fichero enorme con docenas de funciones e interrupciones y quizás con cientos de variables públicas.

Se tardaría mucho tiempo en compilar el programa, y la menor modificación del mismo que normalmente obliga a re-compilar, haría aún mas lento el proceso. Un programa monolítico indica que no ha existido una planificación suficiente, además es difícil de mantener y su código a primera vista no ofrece muchas garantías. 

El paso siguiente en el desarrollo del programa anterior consiste en añadir algún medio para generar la temporización:

/* Módulo con la inicialización del Timer0 */ /* T0INI537.C */

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

/* Módulo con la rutina de atención al Timer0 */ /* RLT_INT.C */
/* Declaración de variables locales */
/* Estructura para el reloj */

struct time { UCHAR msec ;
              UCHAR sec  ; } ;

/* Creación de la estructura en XDATA */

struct time xdata clock ;

bit clock_reset_fl = 0   // Flag para indicar a la interrupción
                         // del timer 0, que ponga el reloj a cero

/* Referencias externas */

extern bit clock_run_fl; // Flag para indicar a la interrupción
                         // del timer 0 que detenga el reloj

/***  ATENCION A LA INTERRUPCION DEL TIMER 0  ***/
   void timer0_int(void) interrupt 1 using 1 {
     if(clock.msec++ == 1000) {
        clock.sec++ ;
        if(clock.sec == 60) {
           clock.sec = 0 ;
           }
        }
     }


Para que este módulo sea útil, hay que cambiar el bucle principal a:

/* Módulo con el programa principal */ /* MAIN.C */

#include <reg517.h>

/* Definiciones externas */

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

/* Estructura para el reloj */

struct time { UCHAR hours ;
              UCHAR mins ;
              UCHAR secs ;
              UCHAR msec  ; } ;

/* Referencia a estructura XDATA en otro módulo */

extern struct time xdata clock ; 
extern bit clock_reset_fl // Flag para indicar a la interrupción
                          // del timer 0, que ponga el reloj a cero
/* Declaración de variables locales */
bit clock_run_fl  ;     // Flag para indicar a la interrupción
                        // del timer 0 que detenga el reloj
void main(void) {
   v24ini_537() ;
   timer0_init_537() ;
   while(1) {
      printf("Tiempo = %d:%d:%d:%d",clock.hours,
                                  clock.mins,
                                  clock.secs,
                                  clock.msecs) ;
      }
   if((P1 & 0x01) != 0) {
      clock_run_fl = 1 ; // Si se ha pulsado start clock 
      }
   else {
      clock_run_fl = 0 ; // Si se ha soltado stop clock
      }
   if((P1 & 0x02) != 0) {
      clock_reset_fl = 1 ; // Si se ha pulsado clear clock 
      }
   }

 

Matenimiento de los vínculos inter-módulo

El programa anterior se ha construido de una forma modular, con cada bloque funcional dentro de un módulo separado. Sin embargo, aún en este pequeño programa se hace evidente el problema del mantenimiento. Está claro que, cada vez que se añade una nueva variable o función, es necesario editar dos ficheros, el módulo que contiene la definición y los módulos que se refieren a ella. Si además se utilizan nombres largos, aparecen errores tipográficos que originan grandes pérdidas de tiempo.

En programas largos, con muchas funciones y variables externas, el área global que precede al código ejecutable puede quedar en desorden y muy abultada. Se puede argumentar que añadir referencias externas al principio de un módulo es una buena práctica, porque se tiene un control preciso de las variables utilizadas. No obstante, es preferible la práctica utilizada con frecuencia, que consiste en incluir en cada módulo fuente, un fichero que contenga las referencias externas a cada variable global o función, independientemente de que el módulo en cuestión tenga necesidad de ellas.

Este método conduce a una situación indeseable, en la que un módulo fuente define una función y encuentra una referencia externa a la misma en el fichero general incluido a comienzo del mismo. 

Una solución puede consistir en disponer de ficheros include específicos. Así por cada módulo ".c", se crearía un segundo fichero ".h". Este fichero auxiliar debería contener los prototipos de las funciones del módulo ".c", las declaraciones de variables globales, y las referencias externas a las mismas funciones y variables globales. Se trata de un concepto similar al de los ficheros ".h" y las librerías estándar de cada compilador C. El truco, consiste en utilizar compilación condicional para evitar que las declaraciones de los elementos, y las mismas declaraciones externas resulten visibles a la vez.

Así al incluir el fichero ".h" en el fichero ".c" con igual raíz, sólo se verán las declaraciones originales, pero al incluirlo en un módulo de diferente raíz, sólo se verán las declaraciones con el prefijo extern. Para lograrlo, cada fichero fuente debe identificarse a si mismo dentro de su fichero include, colocando un #define a comienzo del mismo, y utilizando las directivas del preprocesador #ifdef, #else, y #endif. De esta forma cada fichero que haga referencia a un elemento situado en otro fichero, deberá añadir al principio del mismo un #include del fichero ".h" referenciado. Por otro lado al usar las utilidades make, o los modernos gestores de proyectos, cada vez que se haga un cambio en un fichero ".h", provocará que los ficheros que lo incluyen se recompilen de forma automática.

En la mayoría de los compiladores C, esta característica será de gran ayuda en el desarrollo de programas. Así por ejemplo, se puede cambiar fácilmente el modelo de memoria utilizado en un fichero ".h", y conseguir que el cambio se propague a lo largo de todo el proyecto. Veamos a continuación como poner en práctica todo esto:

/* Módulo principal - MAIN.C */
#define _MAIN_
/* Define el nombre del módulo para control de la inclusión */
#include <reg517.h>     // Definiciones para la CPU 
#include "v24ini537.h"  // Referencias externas a V24INI.C 
#include "t0ini537.h"   // Referencias externas a T0INI537.C 
#include "rlt_int.h"    // Referencias externas a RLT_INT.C
#include "main.h"       // Referencias locales a MAIN.C


void main(void) {
   v24ini_537() ;
   timer0_init_537() ;
   while(1) {
      printf("Tiempo = %d:%d:%d:%d",clock.hours,
                                  clock.mins,
                                  clock.secs,
                                  clock.msecs) ;
      }
   if((P1 & 0x01) != 0) {
      clock_run_fl = 1 ; // Si se ha pulsado start clock 
      }
   else {
      clock_run_fl = 0 ; // Si se ha soltado stop clock
      }
   if((P1 & 0x02) != 0) {
      clock_reset_fl = 1 ; // Si se ha pulsado clear clock 
      }
   }

/* Módulo con la rutina de atención al Timer0 */ /* RLT_INT.C */
#define _RLT_INT_  /* Identifica el nombre del módulo */
/* Referencias externas */
#include "rlt_int.h"    // Referencias locales RLT_INT.C


/***  ATENCION A LA INTERRUPCION DEL TIMER 0  ***/
   void timer0_int(void) interrupt 1 using 1 {
     if(clock.msec++ == 1000) {
        clock.sec++ ;
        if(clock.sec == 60) {
           clock_sec = 0 ;
           }
        }
     }

Los ficheros include deben ser:

/* Fichero Include para RLT_INT.C */
/* Estructura para el reloj - Disponible a todos los módulos */

struct time { UCHAR hours ;
              UCHAR mins ;
              UCHAR secs ;
              UCHAR msec  ; } ;

#ifdef _RLT_INT_
/* Declaraciones originales - activas sólo en el módulo raíz */
/* Creación de la estructura en XDATA */
struct time xdata clock ;
bit clock_reset_fl        // Flag para indicar a la interrupción
                          // del timer 0, que ponga el reloj a cero
#else
/* Referencias externas - para módulos distintos al raíz */
extern struct time xdata clock ;
extern bit clock_reset_fl // Flag para indicar a la interrupción
                          // del timer 0, que ponga el reloj a cero
#endif

/* Fichero Include para MAIN.C */
#ifdef _MAIN_
/* Declaraciones de variables locales */
bit clock_run_fl  ;     // Flag para indicar a la interrupción
                        // del timer 0 que detenga el reloj
#else
/* Referencias externas - para módulos distintos al raíz */
extern struct time xdata clock ;
extern bit clock_run_fl = 0 ;  // Flag para indicar a la interrupción
                        // del timer 0 que detenga el reloj
#endif
/* Fichero Include para V24INI537.C */
#ifdef _V24INI537_
/* Prototipos de funciones originales - para usar en V24INI537.C */
void v24ini_537(void) ;
#else
/* Referencias externas - para usar en otros módulos */
extern void v24ini_537(void) ;
#endif

Ahora, si se añade un nuevo dato global, por ejemplo a RLT_INT.C, solo se necesita incluir la declaración original por encima del "#else", y la versión extern debajo, lo cual hace que el nuevo dato esté disponible a cualquier módulo que lo necesite. Para resumir, el formato del módulo fuente es:

#define _MODULO_
#include <mod1.h>
#include "modulo.h" 
. 
. 
.
funciones()

El formato del fichero include es:

/* Definiciones generales, tales como estructuras y uniones */
#ifdef _MODULO_
/* Poner aquí los prototipos originales y las declaraciones globales */
#else
/* Poner aquí referencias externas a las funciones y datos anteriores */
#endif

Estructura estándar de los módulos en C51

Para ayudar en la integración de este método de construcción de programas, se presentan seguidamente las plantillas estándar para el módulo fuente y para el módulo include asociado:

Plantilla para el módulo fuente estándar

#define __STD__            /* Define el nombre del módulo */
/**********************************************************/
/* Projecto:       X                                      */
/* Autor:          X          Fecha creación:  XX\XX\XX   */
/* Nombre fichero: X          Lenguaje:        X          */
/* Derechos:       X          Derechos:        X          */
/*                                                        */
/* Compilador:   X                Ensamblador:  X         */
/* Versión:    X.XX               Versión:    X.XX        */
/**********************************************************/
/* Detalles del módulo:                                   */
/**********************************************************/
/* Propósito:                                             */
/*                                                        */
/*                                                        */
/**********************************************************/
/* Historia de modificaciones                             */
/**********************************************************/
/* Nombre:         X                    Fecha:  XX\XX\XX  */
/* Modificación:   X                                      */
/*                                                        */
/* Nombre:         X                    Fecha:  XX\XX\XX  */
/* Modificación:   X                                      */
/*                                                        */
/* Nombre:         X                    Fecha:  XX\XX\XX  */
/* Modificación:   X                                      */
/*                                                        */
/**********************************************************/
/* Prototipos de Funciones Externas                       */
/**********************************************************/
#include ".h"
/* Ficheros header del Estándar ANSI C                    */
/**********************************************************/
/* Declaraciones de Datos Globales                        */
/**********************************************************/
#include ".h"
/* Fichero header de este módulo                          */
/**********************************************************/
/* Declaracones Externas                                  */
/**********************************************************/
#include  ".h"
/* Ficheros header de otros módulos                       */
/**********************************************************/
/* Detalles de Funciones:                                 */
/**********************************************************/
/* Nombre de Función:                                     */
/* Llamada desde:                                         */
/* Llama a:                                               */
/**********************************************************/
/* Propósito: lazo main para programa de entrenamiento    */
/*                                                        */
/**********************************************************/
/* Uso de Recursos:                                       */
/*                                                        */
/* CODE      CONST      DATA       IDATA      PDATA       */
/* n/a        n/a        n/a        n/a        n/a        */
/*                                                        */
/* Prestaciones:                                          */
/* Tiempo Máx Ejecución:      Tiempo Mín Ejecución:       */
/*                                                        */
/*                                                        */
/**********************************************************/
/* Funciones Ejecutables                                  */
/**********************************************************/
/* Fin de STD.c                                           */
/**********************************************************/

Plantilla para el módulo "Include" estándar

/**********************************************************/
/* Projecto:       X                                      */
/* Autor:          X          Fecha creación:  XX\XX\XX   */
/* Nombre fichero: X          Lenguaje:        X          */
/* Derechos:       X          Derechos:        X          */
/*                                                        */
/* Compilador:   X                Ensamblador:  X         */
/* Versión:    X.XX               Versión:    X.XX        */
/**********************************************************/
/* Historia de modificaciones                             */
/**********************************************************/
/* Nombre:         X                    Fecha:  XX\XX\XX  */
/* Modificación:   X                                      */
/*                                                        */
/* Nombre:         X                    Fecha:  XX\XX\XX  */
/* Modificación:   X                                      */
/*                                                        */
/* Nombre:         X                    Fecha:  XX\XX\XX  */
/* Modificación:   X                                      */
/*                                                        */
/**********************************************************/
/* Definiciones Globales                                  */
/**********************************************************/
/* Estructuras, uniones y otras definiciones              */

#ifdef _STD_
/* Para comprobar la inclusión en el módulo raíz          */
/**********************************************************/
/* Prototipos de Funciones Propias                        */
/**********************************************************/
/* Prototipos de Funciones del Módulo Raíz                */
/**********************************************************/
/* Declaraciones de Datos Propias                         */
/**********************************************************/
/* Declaraciones de Datos del Módulo Raíz                 */
/**********************************************************/
#else
/**********************************************************/
/* Prototipos de Funciones Externas                       */
/**********************************************************/
/* Prototipos de Funciones Externas para otros Módulos    */
/**********************************************************/
/* Declaraciones de Datos Externas                        */
/**********************************************************/
/* Declaraciones de Datos Externas para otros Módulos     */
/**********************************************************/
#endif

Resumen

Se puede reducir el tiempo de edición de un proyecto, si a cada nuevo módulo se le añade en su primera línea el define correspondiente, y si los nuevos objetos globales se colocan en su fichero ".h" asociado. Además se reduce el tiempo de compilación y los errores de linkado. Las definiciones de las estructuras y uniones sólo aparecen una vez, con lo que se eliminan problemas de compilación y linkado. 


   Contenido>Indice>Intro CursoC51