Está en la página 1de 27

CeTAD – Facultad de Ingeniería - UNLP

Código C Eficiente para Sistemas


Embebidos y Software Codewarrior
(Basado en el Compilador de Freescale)

Cátedra de Circuitos digitales y Microprocesadores

Autores:
Ing. Jorge R. Osio
Ing. Walter Aróztegui
Ing. José A. Rapallini

Octubre de 2011 Versión 2.0


Código C Eficiente para Sistemas Embebidos Página 1
INDICE.

1. Código C Eficiente para Sistemas Embebidos…...…3


1.1. Compilador de Código C a Código de Máquina
1.1.1. Creación de Código C eficiente para el MC68HC08
1.1.1.1. Modelo del Registro de la CPU08
1.1.1.2. Tipos de datos Básicos
1.1.1.3. Variables locales Vs variables Globales
1.1.1.4. Variables de Página directa
1.1.1.5. Bucles
1.1.1.6. Estructuras de datos
1.1.1.7. Ejemplos
1.1.1.7.1. Registro 1
1.1.1.7.2. Copia de datos 1
1.1.1.7.3. Copia de datos 2
1.1.1.7.4. Copia de datos 3
1.1.1.7.5. Bucle
1.1.2. Estructura de un programa en C

2. Software CodeWarrior………...………………...12

2.1. Creación de Proyectos………………………………12


2.2. Generación de código mediante Processor Expert..15
2.3. Compilación y Emulación en Tiempo real ………..20

3. Interrupciones en Lenguaje C…………………...24


3.1. La directiva #pragma TRAP_PROC……………...24
3.2. La instrucción interrupt……………………………26

Código C Eficiente para Sistemas Embebidos Página 2


1. Código C Eficiente para Sistemas Embebidos
1.1. Compilador de Código C a Código de Máquina
1.1.1. Creación de Código C eficiente para el MC68HC08
Comparado con los otros microprocesadores la Arquitectura del HC908 se
adapta bien al lenguaje de programación en C. Tiene un conjunto de instrucciones
efectivas con modos de direccionamiento que permiten la eficiente implementación de
instrucciones en C. El conjunto de instrucciones incluye instrucciones para el manejo
del puntero de pila. Los modos de direccionamiento, incluyen direccionamiento
indexado, modos con el índice incluido y el registro índice o el registro de puntero de
pila. Estas características permiten acceso eficiente a variables locales.
El lenguaje C es ideal para crear un programa para el micro HC08 [1], pero
para producir el código de máquina mas eficiente, el programador debe construir el
programa cuidadosamente. En este contexto “Código Eficiente” significa tamaño de
código compacto y rápido tiempo de ejecución. A continuación se detallan algunas
sugerencias y consejos para ayudar al programador a escribir código C de manera que
un compilador pueda convertirlo en código de maquina eficiente.

1.1.1.1. Modelo del Registro de la CPU08

Para visualizar mejor los consejos en la figura 1 se detalla el modelo de


registro del HC08.

Figura 1. Modelo del Registro del CPU08

 Acumulador: El acumulador es un registro de 8 bits de propósito general. La


CPU lo usa para almacenar los operando o resultados de operaciones.
 Registro Índice: El registro índice de 16 bits es llamado H:X y es usado por los
modos de direccionamiento indexado para determinar la dirección efectiva de un
operando. El registro indice puede acceder a 64 Kbytes de espacios de

Código C Eficiente para Sistemas Embebidos Página 3


direcciones en este modo. El byte mas bajo X se usa para almacenar el operando
para la multiplicación y división de instrucciones. H:X para el almacenamiento
de la ubicación de datos temporarios.
 Puntero de Pila: Los 16 bits del puntero de pila se usan para almacenar la
dirección de la próxima ubicación disponible sobre la pila. La CPU usa el
contenido del registro del puntero de pila como un índice para acceder a
operandos sobre la pila en modos de direccionamiento offset de puntero de pila.
La pila puede se ubicada en cualquier dirección donde hay RAM en los 64 K
bytes de espacio de direcciones.
 Contador de Programa: Los 16 bits del contador de programa contienen la
dirección de la próxima instrucción u operando a ser utilizado. El contador de
programa puede acceder a 64 K bytes de espacio de direcciones.
 Registro de código de condición: Los 8 bits del registro de código de condición
contienen el bit mascara de interrupción global y 5 flags (indicadores) que
indican el resultado de la instrucción que ejecutada. Los bits 5 y 6 son
permanentemente sesteados a 1 lógico.

Estos registros junto con los modos de direccionamiento se encuentran bien


desarrollados en el Apunte “Descripción General de un Microcontrolador (CPU)” y es
necesario conocerlos bien para poder aprovechar al máximo los consejos para la
creación de código C eficiente.

1.1.1.2. Tipos de datos Básicos


Los tipos de datos básicos son los siguientes:

Tipos de datos Básicos Tamaño Rango sin signo Rango con signo
Char 8 bits 0 a 255 -128 a 127
Short int 16 bits 0 a 65535 -32768 a 32767
Int 16 bits 0 a 65535 -32768 a 32767
Long int 32 bits 0 a 4294967295 -2147483648 a 2147483647

La mejor performance en tamaño de código y tiempo de ejecución se puede


lograr definiendo el tipo de datos mas apropiado para cada variable. Esto es apropiado
para microcontroladores de 8 bits donde el tamaño de datos interno natural es de 8 bits.
El tipo de datos preferido en C es el “int”. El estándar ANSI no define precisamente el
tamaño de los tipos de datos naturales, pero los compiladores para microcontroladores
de 8 bits implementan “int” como un valor de 16 bits con signo. Como los
microcontroladores de 8 bits pueden procesar tipos de datos de 8 bits mas
eficientemente que de 16 bits. Los tipos de datos “int” y “long int” deben ser usados,
solo donde se requiere, por el tamaño de datos a ser representado. Las operaciones de
doble precisión y punto flotante son ineficientes y deben ser evitadas donde la
eficiencia es importante. Esto quizás parece obvio, pero frecuentemente se pasa por alto
y tiene un enorme impacto sobre el tamaño de código en tiempo de ejecución.
Como se sabe la magnitud del tipo de datos requeridos y el signo deben ser
especificados. El estándar ANSI C especifica „int‟ con signo por defecto, pero el „char‟
no está definido y puede variar entre compiladores. Por lo tanto para crear código
portable, el tipo de datos „char‟ no debe ser usado. Y para el tipo „char el signo debe ser
definido explícitamente: „unsigned char‟ o „signed char‟.

Código C Eficiente para Sistemas Embebidos Página 4


Es buena práctica crear definiciones de tipos para estos tipos de datos en un
encabezado de archivo que se incluye en todos los otros archivos. También vale la pena
crear definiciones de tipos para todos los otros tipos de datos usados, por consistencia, y
para permitir portabilidad entre compiladores. Se puede usar algo así:

typedef unsigned char UINT8;


typedef signed char SINT8;
typedef unsigned int UINT16;
typedef int SINT16;
typedef unsigned long int UINT32;
typedef long int SINT32;

Una variable se usa en mas de una expresión, pero algunas de estas expresiones
no requieren el tamaño de datos completo o con signo de la variable. En este caso el
ahorro se puede hacer definiendo la variable, o parte de la expresión que contiene la
variable, al tipo de datos mas apropiado.

1.1.1.3. Variables locales Vs variables Globales

Las variables pueden estar clasificadas por su alcance. Las variables globales
son accesibles por cualquier parte del programa y son almacenadas permanentemente en
RAM. Las variables locales son accesibles sólo por la función dentro de la cual son
declaradas y son almacenadas en la pila.
Las variables locales por consiguiente sólo ocupan RAM mientras se ejecuta la
función a la cual pertenecen. Su dirección absoluta no se puede determinar cuando el
código es compilado y conectado así es que son ubicadas en memoria relativa al puntero
de pila. Para acceder a las variables locales el compilador puede usar el modo de
direccionamiento del puntero de pila. Este modo de direccionamiento requiere un byte
adicional y un ciclo adicional para acceder a una variable, comparado a la misma
instrucción en el modo de direccionamiento indexado. Si el código requiere varios
accesos consecutivos a las variables locales, entonces el compilador transferirá el
puntero de pila al registro índice de 16 bits y usará direccionamiento indexado en lugar
de este. El acceso ' estático ' modificado puede ser usado con variables locales. Esto
causa que la variable local sea almacenada permanentemente en la memoria, como una
variable global, así es que el valor de la variable es preservado entre llamados a
funciones. Sin embargo la variable local estática es sólo accesible por la función dentro
de la cual está declarada.
Las variables globales son almacenadas permanentemente en memoria en una
dirección absoluta determinada cuando el código es conectado. La memoria ocupada
por una variable global no puede ser reusada por cualquier otra variable. De cualquier
manera las variables globales no están protegidas, ya que cualquier parte del programa
puede tener acceso a una variable global en cualquier momento. Esto da origen al
asunto de consistencia de datos para las variables globales de más de un simple byte de
tamaño. Esto quiere decir que los datos variables podrían ser corrompidos si una parte
de la variable proviene de un valor y el resto de la variable proviene de otro valor. Los
datos inconsistentes aparecen cuando una variable global es accedida (leída o escrita)
por una parte del programa y antes de que cada byte de la variable haya sido accedido el
programa se interrumpe. Esto se puede deber a una interrupción de hardware por
ejemplo, o un sistema operativo, si se usa uno. Si la variable global es entonces accedida

Código C Eficiente para Sistemas Embebidos Página 5


por la rutina de interrupción entonces pueden aparecer los datos inconsistentes. Esto se
debe evitar si se desea la ejecución confiable del programa y se logra frecuentemente
deshabilitando las interrupciones al acceder a las variables globales.
El acceso 'estático' modificado también puede ser usado con variables globales.
Esto da algún grado de protección a las variables como restringir el acceso a la variable
para esas funciones en el archivo en el cual la variable ha sido declarada.
El compilador generalmente usará el modo de direccionamiento extendido para
acceder a las variables globales o el modo de direccionamiento indexado si son
accedidas a través de un puntero. El uso de variables globales generalmente no resulta
significativamente más eficiente en código que las variables locales. Hay algunas
excepciones limitadas para esta generalización, una es cuando la variable global está
ubicada en la página directa.
Sin embargo, el uso de variables globales impide que una función sea recursiva
o entrante, y frecuentemente no hace el uso más eficiente de RAM, lo cual es un recurso
limitado en la mayoría de microcontroladores. El programador por consiguiente debe
hacer una elección meticulosa en decidir que variables definir como globales en el
ámbito. Las ganancias importantes en eficiencia algunas veces pueden obtenerse
definiendo justamente unas pocas de las variables, más intensivamente usadas, como
globales en el ambiente, particularmente si estas variables están ubicadas en la página
directa.

1.1.1.4. Variables de Página directa


El rango de direcciones $ 0000 a $ 00FF es llamado la página directa, la página
base o la página cero. En los microcontroladores M68HC08, la parte inferior de la
página directa siempre contiene los registros I/O y de control y la parte superior de la
página directa siempre contiene RAM. Después de un reset, el puntero de pila siempre
contiene la dirección $ 00FF. La página directa es importante porque la mayoría de las
instrucciones del CPU08 tienen un modo de direccionamiento directo por medio del que
pueden acceder a los operandos en la página directa en un ciclo de reloj menos que en el
modo de direccionamiento extendido. Además la instrucción de modo de
direccionamiento directo requiere un byte menos de código. Algunas instrucciones
altamente eficientes sólo surtirán efecto con operandos de la página directa. Estos son:
BSET, BCLR, BRSET y BRCLR. La instrucción MOV requiere que uno de los
operandos esté en la página directa.
Un compilador no puede sacar ventaja del modo de direccionamiento directo
eficiente a menos que las variables sean explícitamente declaradas para estar en la
página directa. No hay en estándar ANSI modo de hacer esto y los compiladores
generalmente ofrecen soluciones diferentes. El compilador hiware usa una declaración
#pragma:

#pragma DATA_SEG SHORT myDirectPageVars


UINT16 myDirectPageVar1; /* entero sin signo en página directa */
#pragma DATA_SEG DEFAULT

Esto declara los segmentos de página directa myDirectPageVars que contienen


la variable myDirectPageVar1 y puede ser accedida usando el modo de
direccionamiento directo. El programador debe acordarse de hacer la conexión al
segmento myDirectPageVars en una dirección de la página directa.

Código C Eficiente para Sistemas Embebidos Página 6


La cantidad de RAM en la página directa es siempre limitada, por eso solo las
variables más intensivamente usadas deberían estar ubicadas en la página directa.
Muchos I/O y registros de control están ubicados en la página directa y ellos
deberán estar declarados como tal, así el compilador puede usar el modo de
direccionamiento directo donde sea posible. Dos formas posibles de hacer esto son:
Defina el nombre del registro y su dirección juntos:

#define PortA (*((volatile UINT8 *)(0x0000)))


#define PortB (*((volatile UINT8 *)(0x0001)))

O define los nombres del registro en un segmento de página directa y define la


dirección del segmento en tiempo de conexión:

#pragma DATA_SEG SHORT myDirectPagePortRegisters


volatile UINT8 PortA;
volatile UINT8 PortB;
#pragma DATA_SEG DEFAULT

1.1.1.5. Bucles
Si un bucle debe ser ejecutado menos de 255 veces, se usa 'unsigned char' para
el tipo bucle contador. Si el bucle debe ser ejecutado más que 255 veces, se usa
'unsigned int' para el bucle contador. Esto es porque la aritmética de 8 bits es más
eficiente que la aritmética de 16 bits y la aritmética sin signo es mas eficiente que con
signo.
Si el valor del bucle contador es irrelevante, entonces es más eficiente el
contador para el decremento y comparar con cero que para incrementar y comparar con
un valor distinto de cero. Esta optimización no es efectiva si el bucle debe ser ejecutado
con el bucle contador igual a cero, como cuando el bucle contador se usa para indexar
un arreglo de elementos y el primer elemento debe ser accedido.
Si se usa el bucle contador en expresiones dentro del bucle, entonces se debe
guardar el tipo de datos más apropiado cada vez que se usa.
Cuando un bucle se ejecuta un número fijo de veces y aquel número es
pequeño, como tres o cuatro, es frecuentemente más eficiente no tener un bucle. En
lugar de eso, se escribe el código explícitamente tantas veces según lo solicitado. Esto
dará como resultado más líneas de código C pero a cambio generará menos código
ensamblador y puede ejecutarse mucho más rápido que un bucle. Los ahorros reales
variarán, dependiendo del código a ser ejecutado.

1.1.1.6. Estructuras de datos


Al programar en C es fácil crear estructuras de datos complejas, por ejemplo un
arreglo de estructuras, donde cada estructura contiene un número de tipos de datos
diferentes. Esto producirá código complejo y lento en un microcontrolador de 8 bits que
tiene un número limitado de registros de CPU para usar por indexado. Cada nivel de
referencia resultará en una multiplicación del número de elementos por el tamaño del
elemento, con el resultado probablemente puesto encima de la pila en orden, para hacer
el siguiente cálculo. Las estructuras deberán ser evitadas donde sea posible y las
estructuras de datos mantenerse simples. Esto puede hacerse organizando datos en

Código C Eficiente para Sistemas Embebidos Página 7


simples arreglos unidimensionales de un tipo de datos simple. Esto dará como resultado
un gran número de arreglos, pero el programa podrá acceder a los datos mucho más
rápidamente. Si las estructuras son inevitables, entonces no deberán hacerse pasar como
un argumento de función o un valor de retorno de función, en lugar de esto deberán
pasarse por referencia.

1.1.1.7. Ejemplos
Los siguientes ejemplos están basados en las siguientes definiciones de tipos:

typedef unsigned char UINT8;


typedef signed char SINT8;
typedef unsigned int UINT16;
typedef int SINT16;

1.1.1.7.1. Registro 1

Este ejemplo muestra manipulación de bits para un registro en paginado directo


(port A) y para un not en paginado directo (CMCR0). El Seteado o borrado de uno o
mas bits en una dirección que no es registro en el paginado directo requiere 7 bytes de
rom y 9 ciclos de CPU. El Seteado o borrado de un simple bit en un registro en el
paginado directo requiere 2 bytes de rom y 4 ciclos de CPU.

Código C Código Bytes Ciclos


Ensamblador
#define PORTA (*((volatile UINT8
*)(0x0000)))
#define CMCR0 (*((volatile UINT8
*)(0x0500)))

void LDHX #0x0500 3 3


register1(void) LDA ,X 1 2
{ AND #0xFE 2 2
CMCR0 &= ~0x01; /* clr bit1 */ STA ,X 1 2
PORTA |= 0x03; /* set b1,2 */ LDA 0x00 2 3
PORTA &= ~0x02; /* clr bit2 */ ORA #0x03 2 2
} STA 0x00 2 3
BSET 0,0x00 2 4
RTS 1 4

1.1.1.7.2. Copia de datos 1

Este es un ejemplo del uso inapropiado de „int‟ para la variable „i‟ que se usa
en el bucle contador y en el arreglo índice. El compilador es forzado a usar 16 bit con
signo aritméticos para calcular la dirección de cada elemento de dataPtr[ ]. Esta rutina
requiere 50 bytes de ROM y con 4 Iteraciones del bucle, ejecutados en 283 ciclos de
CPU.

Código C Código Ensamblador Bytes Ciclos


UINT8 buffer[4]; PSHA 1 2
PSHX 1 2

Código C Eficiente para Sistemas Embebidos Página 8


void AIS #-2 2 2
datacopy1(UINT8 * dataPtr) TSX 1 2
{ CLR 1,X 2 3
int i; CLR ,X 1 2
for (i = 0; i < 4; i++) TSX 1 2
{ LDA 3,X 2 3
buffer[i] = dataPtr[i]; ADD 1,X 2 3
} PSHA 1 2
} LDA ,X 1 2
ADC 2,X 2 3
PSHA 1 2
PULH 1 2
PULX 1 2
LDA ,X 1 2
TSX 1 2
LDX ,X 1 2
PSHX 1 2
LDX 3,SP 3 4
PULH 1 2
STA buffer,X 3 4
TSX 1 2
INC 1,X 2 4
BNE *1 2 3
INC ,X 1 3
LDA ,X 1 2
PSHA 1 2
LDX 1,X 2 3
PULH 1 2
CPHX #0x0004 3 3
BLT *-39 2 3
AIS #4 2 2
RTS 1 4

1.1.1.7.3. Copia de datos 2

En este ejemplo, el bucle contador y la variable son optimizados para un


„unsigned char'. Esta rutina ahora requiere 33 bytes de ROM, 17 bytes menos que en
copia de datos 1. Con cuatro iteraciones, la copia de datos 2 ejecuta en 180 ciclos de
CPU, 103 ciclos menos que en copia de datos 1. En este ejemplo el valor del bucle
contador es importante; El bucle debe ejecutarse con i = 0, 1, 2 y 3. No se obtiene
Ninguna mejora significante, en este caso, por decrementar el bucle contador en lugar
de incrementarlo. Tampoco, se obtiene ninguna mejora significante, en este caso, si la
variable „buffer‟ se ubica en la página directa: La instrucción „STA buffer, X‟ usa
direccionamiento directo en lugar de extendido, ahorrando un byte de código y un ciclo
de CPU por iteración.

Código C Código bytes Ciclos


Ensamblador
UINT8 buffer[4]; PSHA 1 2
PSHX 1 2
void PSHH 1 2
datacopy2(UINT8 * dataPtr) TSX 1 2
{ CLR ,X 1 2
UINT8 i; LDA ,X 1 2
for (i = 0; i < 4; i++) ADD 2,X 2 3

Código C Eficiente para Sistemas Embebidos Página 9


{ PSHA 1 2
buffer[i] = dataPtr[i]; CLRA 1 1
} ADC 1,X 2 3
} PSHA 1 2
PULH 1 2
PULX 1 2
LDX ,X 1 2
TXA 1 1
TSX 1 2
LDX ,X 1 2
CLRH 1 1
STA buffer,X 3 4
TSX 1 2
INC ,X 1 3
LDA ,X 1 2
CMP #0x04 2 2
BCS *-25 2 3
AIS #3 2 2
RTS 1 4

1.1.1.7.4. Copia de datos 3

En este ejemplo, los datos son copiados sin usar un bucle. Esta rutina requiere
23 bytes de ROM y se ejecuta en 36 ciclos de CPU. Ésta usa 10 bytes menos de ROM y
144 ciclos menos de CPU que en copia de datos 2. Si se copiaran 8 bytes, entonces este
método requeriría 10 bytes más de ROM que copia de datos 2, pero se ejecutaría en 280
ciclos menos de CPU. Aunque hay ahorros potenciales que se dan si la variable „buffer‟
está ubicada en la página directa, el compilador no saca mucha ventaja de ellos en este
caso.

Código C Código Ensamblador Bytes Ciclos


UINT8 buffer[4]; 1 2
1 2
void PSHX 1 1
datacopy3(UINT8 * dataPtr) PULH 1 2
{ TAX 3 4
buffer[0] = dataPtr[0]; LDA ,X 2 3
buffer[1] = dataPtr[1]; STA buffer 3 4
buffer[2] = dataPtr[2]; LDA 1,X 2 3
buffer[3] = dataPtr[3]; STA buffer:0x1 3 4
} LDA 2,X 2 3
STA buffer:0x2 3 4
LDA 3,X 1 4
STA buffer:0x3
RTS

1.1.1.7.5. Bucle

Si sólo importa el número de iteraciones y no el valor del bucle contador, es


más eficiente el decremento del bucle contador y compárar la variable con cero. En este
ejemplo la declaración „for‟ requiere 7 bytes de ROM, y el incremento y test del bucle
contador en dirección opuesta toman 6 ciclos de CPU por cada iteración. Esto ahorra 2
bytes de ROM y 9 ciclos de CPU por iteración comparada a la declaración „for‟ en

Código C Eficiente para Sistemas Embebidos Página 10


copia de datos 2. De cualquier forma esta optimización no puede ser aplicada a copia de
datos 2 ya que el código dentro de este bucle es ejecutado con i= 4, 3, 2 y 1.

Código C Código Ensamblador Bytes Ciclos


Void
loop1(void)
{ PSHH
UINT8 i; LDA #0x04 1 2
for(i=4; i!=0; i--) TSX 2 2
{ STA ,X 1 2
/* code */ 1 2
} TSX 1 2
} DBNZ ,X,*-offset 2 4
PULH 1 2
RTS 1 4

1.1.2. Estructura de un programa en C


Un programa en C define una función llamada main. El compilador conecta el
programa con el código de arranque y las funciones de librería en un archivo
"ejecutable", llamado para poder ejecutarlo en su target. En resumen, un programa en C
necesita un ambiente de target para establecer e inicializar el código de arranque en el
target que satisface estos requisitos.
En general, su rutina principal “main” realiza algunos pasos de inicialización y
ejecuta un bucle infinito. Como ejemplo, se examinará el archivo hello.c en el directorio
\icc\examples.08:

#include < hc08.h>


//#Include "vectors.c" //es un programa autónomo que provee los vectores de registros

main ()
{
setbaud (BAUD9600);
printf ("Hola Mundo\n");
while (1)
;
}

Como se puede ver, éste es un programa muy simple que coloca el baud rate a
9600, usando una constante definida en el hc08.h. Esta constante es válida sólo si su
sistema usa un cristal de 10 Mhz para el HC08, así es que puede ser necesario
modificarlo. Luego, usa una función estándar C para imprimir "Hola Mundo," e ir a un
bucle infinito.

El compilador del HC08, ICC08 incluye un Editor de aplicaciones GUI que


genera código de inicialización de periféricos vía una interfase.

Código C Eficiente para Sistemas Embebidos Página 11


2. Software CodeWarrior
El Codewarrior [5] es una herramienta de uso libre, que reúne un compilador, un
linkeador y un debugger de código assembler fuente, y que puede ser ampliada según
las necesidades del usuario.
La versión "Standard Edition" ofrece ensamblado de código fuente (assembler)
en forma ilimitada y limitada a 16K bytes en código “C”, además provee capacidades de
Debugging muy interesantes aún para programadores avanzados.
Esta herramienta poderosa, combina un Ambiente de Desarrollo Integrado de
Alta perfomance (I.D.E) con:

- Simulación Completa de Chip y programación de la memoria FLASH desde el sistema


EVAL08QTY.
- Un Compilador ANSI C altamente optimizado y un Debugger en nivel fuente C
- Generación automática de código C con "Processor Expert" desde unidades.

Ejecutar una sección de programación o debugging con proyectos basados en


entornos CodeWarrior IDE es tan simple como efectuar un doble "click" en el nombre
del proyecto (El formato es "nombredelproyecto.mcp") desde el archivo almacenado.
Comenzar un nuevo proyecto es un poco más complejo, pero los tutoriales, FAQs y
guías rápidas de comienzo son fáciles de seguir y ayudan a construir un nuevo proyecto,
usando "templates" pre-construidos, en muy poco tiempo.
Por otro lado, la nueva versión del entorno integrado de trabajo (IDE), el
CodeWarrior 5.0 ha sido pensada para simplificar el manejo de este poderoso entorno
por parte de usuarios no habituados a los ambientes profesionales de desarrollo, cosa
que no ocurría con las antiguas versiones de CodeWarrior para HC08 y HCS08.
Para mayor información de uso, ejemplos y tutoriales ver en el Web Site de
Freescale (www.freescale.com en la sección Codewarrior).

2.1. Creación de Proyectos


Para mostrar como se crea un nuevo proyecto usando el codeWarrior se
realizará un ejemplo que efectúe una interrupción en forma periódica, cada “n”
milisegundos, basado en el uso del Timer en modo TOV (Timer Overflow). Este
sencillo programa servirá como base para ejecutar un número de tareas más complejas
en forma periódica de modo similar a como lo haría un sistema operativo más complejo.
Primeramente se configura el sistema EVAL08QTY para trabajar con el MCU
MC68HC908QY4 que es el microcontrolador disponible en la placa que viene con el
kit del sistema.
Configurado el sistema EVAL08QTY, se procede a iniciar el programa en el
sistema CodeWarrior 5.0 efectuando los siguientes pasos:

1) Al ejecutar el CodeWarrior IDE, se abrirá una ventana de opciones como se ve en la


figura 2. Se debe elegír la opción “Create New Project” para armar el nuevo proyecto.
2) Se ingresará en la pantalla de configuración del proyecto donde se elegirá la opción
generación de lenguaje ASSEMBLY, y se le pondrá un nombre con extención “.mcp”,
según se puede ver en la figura 3.

Código C Eficiente para Sistemas Embebidos Página 12


Figura 2. Pantalla “Startup” con opciones de ayuda.

Figura 3. Pantalla de configuración del proyecto.

3) En la siguiente pantalla se debe configurar la familia y dispositivo en particular a


utilizar en el proyecto (MC68HC908QY4) según se puede ver en la figura 4.

Código C Eficiente para Sistemas Embebidos Página 13


Figura 4. Pantalla de configuración de Familia, dispositivo y tipo de conexión.

Como se observa en la figura 4, se debe elegir la familia HC08, dispositivo


MC68HC908QY4 y en cuanto a la conexión con la herramienta debe elegirse la opción
“Mon08 Interface” ya que es la opción universal de conexión para los sistemas de
desarrollo como los EVAL08QTY, E-FLASH08, FLASH_POD y toda otra herramienta
que no figure explícitamente en el listado de conexiones.

Figura 5. Pantalla de adición de archivos al proyecto.

4) Al hacer click en el botón “siguiente” se pasa a una pantalla (Figura 5) que permite
adicionar cualquier archivo al proyecto, para incluirlo en el trabajo. En este caso, se
saltea está opción siguiendo a la próxima pantalla.
5) En la pantalla que se observa en la figura 6, se puede elegir la generación de código
de inicialización de los distintos periféricos asistida o no. En este caso se seleccionará la
generación de código asistida, por medio del aplicativo “Processor Expert”, que guía al
usuario paso a paso en la inicialización de los distintos periféricos del MCU elegido.

Código C Eficiente para Sistemas Embebidos Página 14


Figura 6. Pantalla de elección o no de generación de código asistido
(Processor Expert).

6) Al hacer click en el botón “Finalizar”, se generarán todos los archivos del proyecto,
se lanzará la pantalla principal de trabajo del mismo y se podrá ver una interfaz gráfica
con los pines y los distintos módulos que constituyen el MCU (Figura 7).

2.2. Generación de código mediante Processor Expert

Figura 7. Pantalla principal del proyecto e interfaz gráfica de


Generación de código (Processor Expert).

Código C Eficiente para Sistemas Embebidos Página 15


Ahora solo queda generar el código de inicialización del Timer para producir
una interrupción periódica que será la base del sistema de disparo de tareas, inicializar
los puertos I/O, los registros de configuración, etc.
Para hacer esto, se debe usar el generador de código asistido “Processor Expert”
haciendo click primeramente en el modulo CPU para configurar el Clock del sistema y
otros aspectos como se observa en la figura 8.

Figura 8. Pantalla del módulo de CPU.

Para este ejemplo se debe configurar el módulo de CPU para:

• Clock ---- Externo ---- 9,8304Mhz (lo inyectará el EVAL08QTY por pin OSC1).
• LVI ----- Habilitado ---- 3V trip point ----- LVI deshabilitado en modo STOP.
• Interrupciones Habilitadas.
• Vector de Reset apuntando a la etiqueta “_Startup”
• Pin de Reset externo no disponible.

Figura 9. Pantalla con los detalles de configuración del CPU.

A continuación se procede a configurar el módulo de Timer (TIM)


ingresando al mismo como muestra en la figura 10.

Código C Eficiente para Sistemas Embebidos Página 16


Figura 10. Pantalla del Módulo de Timer.

Ahora se debe configurar el módulo del TIMER según lo siguiente:


• Prescaler = 32 ----- FBUS = 2,4576 MHz.
• Período del timer = 100 ms
• Modo de funcionamiento ----- Timer Overflow Interrupt (INT_TIMOvr).
• Overflow Interrupt = habilitado.
• Nombre de la interrupción = isrINT_TIMOvr
• Inicialización = Comienzo de la cuenta (arranque del timer).

Figura 11. Pantalla de configuración del TIMER

Una vez que se ha configurado el módulo de TIMER, se deben configurar los


puertos I/O según lo siguiente:

PORTA ---- PTA0 ---- INPUT ----- PTA1/PTA7 DISABLE.


PORTB ---- PTB5 ---- OUTPUT --- PTB0 / PTB7 DISABLE.

Código C Eficiente para Sistemas Embebidos Página 17


Figura 12. Pantallas de configuración de puertos (PORTA / PORTB).

Si luego se presiona el botón “Generation Code”, el generador de código del


Processor Expert generará código y mostrará una ventana explicando los pasos a seguir
para incorporarlo efectivamente al resto del programa.

Figura 13. Pantalla de generación de Código que producirá


archivos bajo el Nombre “MCUinit” para inicializar el MCU.

Figura 14. Pantalla de ayuda para integrar el código generado al proyecto.

Código C Eficiente para Sistemas Embebidos Página 18


Según lo sugerido por la ventana de ayuda una vez generado el código, se
procede a modificar y comentar lo siguiente:

• Comentar en el archivo “Project.prm” la línea --- VECTOR 0 _Startup /* Reset


vector. Con una barra cruzada y asterisco (/*) delante de la sentencia y luego salvarlo.
• Descomentar en el archivo “main.asm” la línea “JSR MCU_int” para que de esta
forma en el programa principal se pueda invocar a la sub rutina MCU_int quen
inicializa al MCU.

Figura 15. Ventana con código a modificar para utilizarlo.

Luego de realizar esas modificaciones sugeridas por el Processor Expert, se


deben introducir las líneas de código en la subrutina de interrupción por Timer
Overflow (isrINT_TIMOvr) para realizar, por ejemplo, un Toggle (inversión de
estado) del puerto PTB5 cada vez que se atienda la interrupción propiamente dicha. En
este punto se pueden poner todas las tareas en forma de llamado a subrutina que se irán
ejecutando una a una cada 100 ms.

Código C Eficiente para Sistemas Embebidos Página 19


Figura 16. Agregado de las líneas de código en la rutina de manejo de la
Interrupción por Timer Overflow (isrINT_TIMOvr).

2.3. Compilación y Emulación en Tiempo real


Una vez introducido el código, se debe compilar haciendo click en el botón
“Make” en la barra de proyecto o en la barra de herramientas general. Si no hay ningún
error de compilación se estará en condiciones de pasar a la etapa de EMULACION EN
TIEMPO REAL del programa.
Para realizar ello, primero se debe establecer una conexión entre el CodeWarrior
5.0 y el sistema de desarrollo EVAL08QTY que se irá configurando a lo largo de las
siguientes pantallas luego de hacer click en el botón “Debugger” (fecha verde en la
barra de proyecto).

Código C Eficiente para Sistemas Embebidos Página 20


Figura 17. Pantalla de selección de la interfaz con el hardware a utilizar.

En la ventana “Interface Selection” se elige la opción “Class 1 – ICS Board


with processor installed” y luego se debe presionar el botón “ok”.

Figura 18. Pantalla de manejo de la conexión con el hardware (EVAL08QTY).

Luego se debe configurar la siguiente pantalla eligiendo el número de puerto


COM en el que esté asignado el puerto utilizado por el EVAL08QTY para la conexión
PLACA – PC y se debe asignar un Baud Rate de 9600 Bps de acuerdo a lo configurado
en el sistema anteriormente.
Como se podrá observar en la figura, también se configurará la opción de
borrado y grabación de la memoria FLASH del MCU en forma previa y automática
cada vez que se quiera entrar en el modo de Debugging (Emulación en Tiempo Real) ya
que es la condición necesaria para que cualquier HC908 pueda trabajar como una
verdadera herramienta de desarrollo.

Código C Eficiente para Sistemas Embebidos Página 21


Se debe hacer click en el icono “contact target with these sedttings....” para
establecer la comunicación con la placa EVAL08QTY y entrar al entorno de Debugging
propiamente dicho.

Figura 19. Ventana de borrado y Programación de la Flash

Una vez que el sistema sortea las etapas de seguridad con éxito, aparecerá una
ventana (Erase and Program Flash?) preguntando por el borrado y grabación de la
FLASH antes de ingresar al modo de Debugging propiamente dicho. Se debe hacer
Click en el icono “Yes” para proceder a borrar y grabar el programa en la memoria
flash e ingresar al modo Debugging.

Figura 20.- Pantalla de Debugging (Emulación en Tiempo Real).

En esta instancia se posee la pantalla principal de Debugging (Emulación en


Tiempo Real) y solo resta correr el programa haciendo Click en el icono con la
“flechita verde” (Run / Continue) para poder ver la señal cuadrada de 200 ms de
período que se obtiene en el puerto PTB5 del QY4 en la placa EVAL08QTY, según
se observa en la figura 21.

Código C Eficiente para Sistemas Embebidos Página 22


Figura 21. Oscilograma de la señal de salida en PTB5.

El sistema EVAL08QTY puede soportar de igual forma, herramientas de


entorno integrado como el CodeWarrior de Metrowerks, asi como, el WinIDE ICS08
de P&E Microsystems sin modificación alguna.

Código C Eficiente para Sistemas Embebidos Página 23


3 Interrupciones en Lenguaje C

El empleo de Interrupciones o ISR (Interrupt Service Routine, o Rutina de Servicio


de Interrupciones) es indispensable en la programación de microcontroladores. Se
utilizan tanto para responder a una interrupción externa, una interrupción del timer, del
conversor A/D o de cualquier módulo existente en el microcontrolador que posea
interrupciones.
Hay dos mecanismos para escribir Interrupciones en C, uno es muy sencillo y
funciona solo en el compilador codewarrior y el otro es más genérico y es soportado por
la mayoría de los compiladores en C. Las 2 formas mencionadas son:

• Usando la instrucción interrupt (Propietaria de Codewarrior)


• Usando la directiva #pragma TRAP_PROC y el archivo de parámetros del Linker

3.1 La directiva #pragma TRAP_PROC

Esta directiva le indica al compilador que una función es una INTERRUPCIÓN.


Una ISR necesita cierto código especial de entrada y de salida que la hace diferente de
cualquier otra función.

Ejemplo:
#pragma TRAP_PROC
void Mi_INTERRUPCION(void) {
...
}

Una vez preparada la función que contiene el Código a ejecutarse durante la


interrupción, es necesario indicarle al Linker (programa que une todas los “archivos” de
código en un único programa) que instale la dirección de esta función en el vector
correspondiente. Para ello se debe editar el archivo de parámetros del linker
correspondiente al modelo de compilación que se está empleando. Por ejemplo, si se
compila un programa para el Micro GP32, este archivo se llamará GP32-FLASH.PRM
[6].
El contenido es el siguiente:

NAMES END

SECTIONS
/* Z_RAM = READ_WRITE 0x0040 TO 0x00FF; */
/* 0xF0 to 0xFF reserved for Monitor stacking */
Z_RAM = READ_WRITE 0x0040 TO 0x00EF;
RAM = READ_WRITE 0x0100 TO 0x023F;
ROM = READ_ONLY 0x8000 TO 0xFDFF;

PLACEMENT
DEFAULT_ROM, ROM_VAR, STRINGS INTO ROM;
DEFAULT_RAM INTO RAM;

Código C Eficiente para Sistemas Embebidos Página 24


_DATA_ZEROPAGE INTO Z_RAM;
END

STACKSIZE 0x50

VECTOR ADDRESS 0xFFFE _Startup

El bloque titulado “SECTIONS” sirve para asignar nombres a las áreas de memoria
del micro. En el ejemplo anterior, se han definido los nombres Z_RAM para la memoria
accesible con direccionamiento directo (hasta 0xFF), RAM para el resto de la memoria
RAM y ROM para la Flash, a excepción de las direcciones reservadas a los vectores.
El bloque titulado “PLACEMENT” permite ubicar las distintas secciones y
componentes del programa en áreas específicas de la memoria (RAM, Flash, etc)
La instrucción “STACKSIZE” define el tamaño del stack (0x50 bytes en el ejemplo).
Finalmente, las instrucciones “VECTOR” permiten especificar el contenido de los
vectores de interrupción del micro. La instrucción VECTOR tiene el siguiente formato:
VECTOR ADDRESS <dirección_vector> <contenido_vector>

La <dirección_vector> es una de las direcciones donde están los vectores de


interrupciones del micro. Por ejemplo en un GP32, en la dirección 0xFFFE (y 0xFFFF)
se almacena el vector de reset.
El valor <contenido_vector> puede especificarse como un valor absoluto en hexa o
con el nombre de la función. Opcionalmente en este caso también puede especificarse
un offset.

Ejemplo:
VECTOR ADDRESS 0xFFFE 0x1000
VECTOR ADDRESS 0xFFFE StartUp
VECTOR ADDRESS 0xFFFE StartUp OFFSET 2

En este caso, la función que se ha definido con #pragma TRAP_PROC se llama


Mi_INTERRUPCION. Si se desea usarla para atender las interrupciones del módulo
Time Base (TBM) cuyo vector se encuentra en la dirección 0xFFDC, se debe escribir:
VECTOR ADDRESS 0xFFDC Mi_INTERRUPCION

Un programa completo que use el Timebase para generar una onda cuadrada por
PTA7 tendría la siguiente forma:
#define TBON 2
#define TACK 8 //Ack del TBCR

#define ENABLE_INT {asm cli;}


#define DISABLE_INT {asm sei;}

#pragma TRAP_PROC
void Mi_INTERRUPCION (void)
{
TBCR |= TACK; // Acknowledge Int
PTA ^= 0x80; // invierte el estado de PTA7
}

Código C Eficiente para Sistemas Embebidos Página 25


//*********************************************************************************//
void main (void)
{
CONFIG1 = 1; //Parar COP
DDRA = 0xFF; //PTA todo salida
TBCR = 0x04; //Timebase divide por 8192
ENABLE_INT //Habilitar las interrupciones
TBCR |= TBON; //Prender el Timebase
while (1) { //Hacer nada
}
}

3.2 La instrucción interrupt


Code Warrior tiene una instrucción especial que no se encuadra dentro del estándar
ANSI-C llamada “interrupt” que permite simplificar el proceso de definición de una
interrupción [7]. Debe usarse como si fuera un calificador de tipo pero seguida de un
número que especifica la entrada en la tabla de vectores de interrupción que contendrá
la dirección de la función que se está definiendo. Si no se especifica ese número, tiene el
mismo efecto que TRAP_PROC y el vector debe definirse con la sentencia VECTOR
en el archivo de parámetros del linker, tal como se vio antes.

Ejemplo:

interrupt 17 void Mi_INTERRUPCION();

Instala Mi_INTERRUPCION en la entrada 17 de la tabla de vectores,


correspondiente al Timebase Module.
Con esta modificación, el código de ejemplo quedaría de la siguiente forma:

#define TBON 2
#define TACK 8 //Ack del TBCR
#define ENABLE_INT {asm cli;}
#define DISABLE_INT {asm sei;}

interrupt 17 void Mi_INTERRUPCION(void)


{
TBCR |= TACK; // Acknowledge Int
PTA ^= 0x80;
}
//*********************************************************************************//
void main (void)
{
CONFIG1 = 1; //Parar COP
DDRA = 0xFF; //PTA todo salida
TBCR = 0x04; //Timebase divide por 8192
ENABLE_INT //Habilitar las interrupciones
TBCR |= TBON; //Prender el Timebase
while (1) { //bucle infinito
}
}

Código C Eficiente para Sistemas Embebidos Página 26


Bibliografía:
[1] Stuart Robb, “Creating Efficient C Code for the MC68HC08”, Nota de Aplicación,
East Kilbride, Scotland, 2000.
[2] “Embedded C Development tools, ” http://www.imagecraft.com, 1994.
[3] Reference Manual: “Software ICCHCxx”, ImageCraft Creations Inc.,2000.
[4] “Ing. Gabriel Dubatti”, http://www.ingdubatti.com.ar, 2007
[5] User Guide: “PROCESSOR EXPERT FOR MOTOROLA HC(S)08 FAMILY”
Codewarrior V 1.4 may,2004
[6] MC68HC908GP32 Technical Data (Motorola)
[7] Motorola HC08 Compiler (Metrowerks)
[8] Manual Smart Linker (Metrowerks)

Código C Eficiente para Sistemas Embebidos Página 27

También podría gustarte