You are on page 1of 24

UNIVERSIDAD DE ALCAL

Departamento de Electrnica

P ROGRAMACIN EN C DEL 8051

Jos Manuel Villadangos Carrizo

Julio Pastor Mendoza


Programacin en C del 8051

1. INTRODUCCIN

Cuando se desarroll el 8051, el lenguaje ensamblador era utilizado para producir cdigo eficiente
en aplicaciones en las que el tiempo era crtico. La reducida capacidad de la memoria interna del
chip, tanto de datos como de programa, haca que el cdigo generado por los primeros
compiladores de alto nivel que salieron al mercado, no fuese muy eficiente. Hoy en da existen
varios fabricantes de software, que han desarrollado compiladores de alto nivel capaces de generar
cdigo en C tan eficiente como aquellas partes de programa que sean necesarias escribir en
ensamblador.

Trataremos de demostrar las ideas de programacin en C del 8051 a partir del compilador de
Franklin C51, del cual es fcil disponer de un versin de evaluacin (www.fsinc.com) que
aunque est limitada a generar un tamao mximo de cdigo de 4 Kbytes, es suficiente para llevar
a cabo numerosas aplicaciones de inters, y sobre todo para conocer un entorno integrado de
programacin y depuracin en C totalmente profesional, para la familia MCS-51 de Intel.

Despus de su aparicin en 1978, el C supuso un gran cambio para los programadores debido a
las ventajeas que tena frente a otros lenguajes de alto nivel. En diciembre de 1989, el Instituto
Nacional de Standards de Amrica (ANSI) defini formalmente el standard del lenguaje C,
conocido como ANSI C. La mayora de los compiladores se rigen por este standard, y algunos
contienen extensiones al ANSI C para solucionar generalmente problemas de direccionamiento,
relacionados con aplicaciones especificas.

El lenguaje C tiene la capacidad de generar de manera rpida, comandos de bajo nivel para
acceder directamente a la memoria de datos y perifricos, mecanismos para combinar datos

Laboratorio de Sistemas Electrnicos Digitales II Pg. 1


Programacin en C del 8051

simples de tamao byte y word en estructuras complejas de datos que permiten accesos rpidos,
y herramientas para crear funciones complejas de comandos simples de bajo nivel. tambin tiene
una gran potencia para el control de estructuras, operadores, y libreras de funciones para llevar
a cabo operaciones de alto nivel y programacin estructurada.

El C se desarroll sin pensar indudablemente en el 8051. Un microcontrolador es bsicamente un


microprocesador especializado, con numerosos perifricos internos y pensado para aplicaciones
de control. Por esta razn, los programadores que habitualmente programan en C, necesitan
habituarse con el estilo de programacin en C para el 8051, debido a las restricciones y
extensiones que existen para el 8051.

2. ORGANIZACIN DE LA MEMORIA EN EL 8051

El 8051 tiene un espacio separado de memoria de programa y datos, dependiendo de la


configuracin particular del sistema, y tambin puede tener un direccionamiento interno o externo
de la memoria de programa y un slo un rea interna, o interna y externa de la memoria de datos.
En un sistema tpico basado en el 8051, pueden existir por tanto, tres espacios de memoria, todos
ellos comenzando a partir de la direccin cero.

Adems todo esto se complica por la estructura de la RAM interna, donde los primeros 128 bytes
son utilizados por el banco de registros, el area de direccionamiento bit a bit, y se completan con
posiciones de memoria de propsito general. A esto hay que aadir el segundo rea de 128 bytes
donde se localiza la zona de registros de funcin especial (SFR). Adems en el caso del 8052,
existe un nuevo rea de 128 bytes que se solapa con la zona SFR, y que slo permite un
direccionamiento indirecto, para distinguir los accesos de la zona SFR a la que slo se permite un
direccionamiento directo.

Dada esta estructura de la memoria tan compleja, existen 6 tipos de especificadores de memoria
cuyos mrgenes se detallan a continuacin:

code Memoria de programa (64 Kbytes).

data Memoria de datos direccionable directamente para permitir accesos a variables


ms rpidos, dentro de los primeros 128 bytes de RAM interna.

idata Memoria de datos con direccionamiento indirecto a los 256 bytes de memoria
RAM interna (en el 8052).

bdata rea de datos (16 bytes) con direccionamiento bit a bit, para permitir accesos tipo
bit a variables de carcter y enteras (128 bits).

xdata Memoria de datos externa (64 Kbytes).

pdata Memoria de datos externa paginada, para permitir accesos a los primeros 256
bytes (pgina 0) a travs del puerto 0.

A continuacin se muestran algunas sentencias en C, para especificar que las variables x e y

Laboratorio de Sistemas Electrnicos Digitales II Pg. 2


Programacin en C del 8051

residan en la RAM interna y sean direccionadas directamente, y que el array buffer[100] se site
en RAM externa.

char data x, y;
unsigned int xdata buffer[100];

En aplicaciones de control es habitual situar ciertas constantes en una tabla dentro de la memoria
de programa (ROM). Esto se define fcilmente, con la siguiente sintaxis:

unsigned char code parametros[]={0x10, 0x20, 0x40, 0x80};

La seleccin del tipo de memoria para las diferentes variables afecta directamente a la eficiencia
del programa final. Como regla general, se usa el rea de memoria de tipo data para almacenar
las variables que requieren un acceso rpido y frecuente. El tipo idata resulta ms pequeo que
el tipo data debido al empleo de direccionamiento indirecto. Se recomienda su empleo con arrays
o estructuras de pequeo tamao. Todas las variables que no requieran un acceso crtico en el
tiempo, y aquellos arrays y estructuras de tamao grande son propias de residir en el rea de tipo
xdata.

Pueden existir variables cuyos tipos de memoria no sean declarados explcitamente


mediante los especificadores tipo, o puede que no se desee especificar el tipo de memoria de
varias variables porque todas ellas residan en el mismo rea de memoria. Tambin, nuestro
programa puede ser pequeo y por tanto residir en la memoria interna del chip. En estos casos,
las siguientes definiciones de modelos de memoria permiten elegir, o un tipo de memoria para
todo el programa (o funcin), o un tipo de memoria por defecto para todas aquellas variables que
no se han declarado explcitamente.

small Las variables y los datos locales se definen para residir en la memoria de
datos interna, de igual forma que si hubiesen sido definidas por el
especificador data.

compact Las variables y los datos locales se definen para residir en la memoria de
datos externa, de igual forma que si hubiesen sido definidas por el
especificador pdata.

large Las variables y los datos locales se definen para residir en la memoria de
datos externa, de igual forma que si hubiesen sido definidas por el
especificador xdata.

La seleccin del modelo de memoria puede hacerse o en tiempo de compilacin ( opcin


del software de desarrollo ProView ) o usando la directiva #pragma dentro del cdigo fuente. El
modelo de memoria por defecto es small.

Todas las variables automticas de una funcin pueden forzarse al definir la funcin, a
residir en un rea de memoria particular. Por ejemplo la funcin:

int func(int x, int y) large


{
x=10;

Laboratorio de Sistemas Electrnicos Digitales II Pg. 3


Programacin en C del 8051

y=20;
printf(La suma es %d\n, x+y);
return (x-y+5);
}

sita sus variables locales en memoria externa debido a que el modelo de memoria se ha
declarado como large. Una forma de seleccionar globalmente un tipo de memoria para todo el
programa es utilizando la directiva #pragma COMPACT al comienzo del programa, y decidir
cuidadosamente que modelos de memoria son apropiados, para aquellas funciones individuales
y rutinas de interrupcin. Aquellas variables que son declaradas explcitamente mediante los
especificadores de tipo de memoria, no se ven afectadas por el modelo de memoria seleccionada.

3. CONSTANTES, VARIABLES, Y TIPOS DE DATOS

Los programas se escriben o para procesar datos, ya sean recibidos por alguna entrada.

El compilador C51 de Franklin soporta los siguientes tipos de datos:

Tabla 1. Tipos de datos soportados por C51

Tipo de datos Bits Bytes Rango de valores


signed char 8 1 -128 a +127
unsigned char 8 1 0 a 255
enum 16 2 -32768 a +32768
signed short 16 2 -32768 a +32768
unsigned short 16 2 0 a 65535
signed int 16 2 -32768 a +32768
unsigned int 16 2 0 a 65535
signed long 32 4 -2147483648 a 2147483647
unsigned long 32 4 0 a 4294967295
float 32 4 1.175494E-38 a 3.402823E+38
bit 1 0a1
sbit 1 0a1
sfr 8 1 0 a 255
sfr16 16 2 0 a 65535

Tal como puede observarse en la tabla, el compilador C51 soporta todos los tipos de datos del
standard C. Sin embargo, los tipos de datos sealados en negrita son especficos del 8051 y no
forman parte del ANSI C.

Por definicin, C no soporta accesos directos a datos de tipo bit ni su procesamiento. La forma
en que C accede a datos de tipo bit es mediante el uso de mscaras de bit con los
correspondientes operadores. Debido a que el 8051 est diseado para aplicaciones de control,
el acceso directo a los bit de informacin es esencial en su programacin. Los datos tipo bit
pueden utilizarse para declarar variables tipo bit que residirn en el rea de memoria interna de
direccionamiento a nivel de bit ( posiciones comprendidas entre la 20H y 2FH de la RAM interna).
Por ejemplo, las siguientes declaraciones

Laboratorio de Sistemas Electrnicos Digitales II Pg. 4


Programacin en C del 8051

bit flag1=0, flag2=0, semaforo;

declaran tres variables tipo bit y se inicializan dos de ellas a cero.

Los datos tipo sbit se diferencian de los datos tipo bit, en que se usan fundamentalmente para
declarar variables tipo bit asociadas con los registros de funcin especial (SFR). Recordar que
la mayora de los registros de funcin especial del 8051 son direccionables bit a bit
(concretamente aquellos que son divisibles por 8 ). Por ejemplo, la declaracin siguiente

sbit at 0xAF EA;

establece EA como una variable tipo bit con un direccionamiento absoluto, cuya direccin es la
0xAF de la memoria interna. Para el 8051, todos los bit de estado y control de aquellos registros
de funcin especial son predeclarados en el archivo reg51.h. Adems, al incluir el archivo reg51.h
en nuestro programa, podemos hacer uso de todos los smbolos de bit tales como TR0, TI, ET0,
CY, ...

Se puede emplear la declaracin sbit para acceder a los bit internos de los objetos declarados por
el tipo de especificador de memoria bdata . Por ejemplo, las declaraciones:

int bdata itemp;


sbit bit_ten=itemp^10;

permiten que una nueva variable bit_ten, acceder al dcimo bit de una variable entera.

Los datos tipo sfr se usan para declarar y acceder a los registros de la zona de memoria SFR,
aunque tal como ocurra con los datos tipo sbit, todos los registros definidos en el rea SFR estn
predefinidos en el archivo reg51.h. Los siguientes ejemplos muestran como acceder a los puertos
y registros internos del 8051:

TMOD=0x20;
TH1=0xfd;
TCON=0x40;
IE=0x90;
P1=P1&0xf0;

4. ARRAYS, ESTRUCTURAS, Y UNIONES

Las variables de los tipos de datos bsicos pueden agruparse para formar tipos de datos de nivel
superior, con objeto de mejorar la eficiencia del cdigo.

4.1. Arrays

Un array es un grupo de variables del mismo tipo, referenciadas con un nombre comn. Por
ejemplo, la sentencia:

int temp[20];

declara un array capaz de almacenar 20 enteros. Las 20 variables de tipo entero se referencian
desde temp[0] hasta temp[19].

Laboratorio de Sistemas Electrnicos Digitales II Pg. 5


Programacin en C del 8051

A continuacin se muestran algunos ejemplos de declaracin de arrays:

unsigned int pdata temp[40];


float xdata num[20][10];
unsigned char code texto[]=Hola;
unsigned char code tabla[]={10, 20, 30, 40};
unsigned char code tabla_orden[]=
{Primero,
Segundo,
Tercero,
Cuarto};

Si el tamao de un array no est explicitamente definido en la declaracin, C automticamente


calcula el tamao a partir de los datos que se han utilizado en la inicializacin. Cuando el tamao
del array sea grande, conviene que sea inicializado en el rea xdata.

4.2. Estructuras

Una estructura es un conjunto de variables relacionadas, que pueden o no ser del mismo tipo de
datos. Por ejemplo:
struct alarma{
unsigned char dia_string[10];
unsigned int hora;
unsigned int minuto;
unsigned int segundo;
};

declara una estructura de datos para representar el da y la hora de activacin de una alarma en
un programa. Con la estructura ya declarada, una variable con ese tipo de estructura sera
declarada de la forma siguiente:

struct alarma alarma_on;

Es importante diferenciar entre la declaracin del tipo de datos de la estructura y la declaracin


de la variable de tipo estructura. La declaracin del tipo de datos de la estructura, le dice slo al
compilador el tipo de datos que sern procesados, mientras que la declaracin de la variable le
dice al ordenador la cantidad de memoria necesaria para almacenar los datos de la estructura en
la definicin. En el ejemplo anterior, alarma es una estructura formada por un array de caracteres
seguido de tres enteros sin signo. Sin embargo, la variable alarma_on de tipo alarma, toma 15
bytes de espacio en memoria. La inicializacin de la variable podra ser:

struct alarma alarma_lunes={Lunes, 12, 30, 00};

Para acceder a las variables de la estructura, se utiliza el operador (.). Veamos un ejemplo:

printf(La activacin el %s sera a las %d:%d:%d,


alarma_lunes.dia_string, alarma_lunes.hora, alarma_lunes.minuto,
alarma_lunes.segundo);

Y el resultado de la ejecucin sera:

La activacin de la alarma el Lunes sera a las 12:30:00

Laboratorio de Sistemas Electrnicos Digitales II Pg. 6


Programacin en C del 8051

En aplicaciones de control, suele ser habitual que un grupo de datos sea recogido de la misma
fuente pero almacenado en variables de diferente tipo. Una estructura que permita accesos rpidos
a las diferentes variables recogidas bajo el mismo nombre simblico, hace que el programa sea
ms comprensible. Sin embargo, los bytes de la estructura se almacenan en memoria de forma
contigua, y el C51 no permite mezclar variables tipo bit con otros tipos de variables, debido a que
los datos tipo bit son restringidos a los 16 bytes de memoria interna que permiten un
direccionamiento tipo bit.

El tipo de especificador bdata, que declara una variable entera o de carcter para ser
direccionada bit a bit, puede utilizarse para forzar a una estructura a ser almacenada dentro del
rea de direccionamiento de bit:

bdata estruct swithches {


unsigned char sw1;
unsigned char sw2;
} swset;

sbit sw_read1 = swset.sw1 ^ 0;


sbit sw_read2 = swset.sw2 ^ 4;

4.3. Uniones

Las uniones son similares a las estructuras y ambas son utilizadas para agrupar variables de
diferente tipo bajo un nombre comn. Sin embargo, mientras que en una estructura las variables
se sitan en memoria de forma contigua, una unin guarda todos sus datos en las mismas
posiciones, solapandose unos con otros, es decir, que ocupan las mismas posiciones pero no al
mismo tiempo.

Una unin tiene aplicacin cuando parte de un dato se trata de forma diferente, en diferentes
instantes de tiempo. Supongamos que queremos almacenar el valor de un timer de 16 bits, para
leerlo como dos bytes.

Como ejemplo, podemos definir una unin constituda por una estructura de 2 bytes y un entero.
Cuando se quiere escribir en el byte alto, referenciamos dicho espacio como 2 bytes, pero cuando
queremos usar el resultado lo consideramos como un entero.

union split{unsigned int word; struct {unsigned char hi;unsigned


char low;} bytes};

Declaremos la variable new_count tipo union, y veamos la forma de utilizarla:

union split new_count;

new_count.bytes.hi = TH1;
new_count.bytes.low = TL1;
old_count=new_count.word;

5. PUNTEROS

La potencia de los punteros en C, es ya conocida. El concepto de puntero es simplemente una


variable especial que guarda la direccin de otras variables o constantes. Por ejemplo, en lenguaje

Laboratorio de Sistemas Electrnicos Digitales II Pg. 7


Programacin en C del 8051

ensamblador del 8051, las funciones principales de los registros R0, R1, DPTR, y SP son
almacenar las direcciones de los datos para ser accedidos mediante direccionamiento indirecto,
y por tanto, pueden ser definidos como punteros a los datos direccionados.

La principal utilizacin de los punteros es el direccionamiento indirecto de los datos. Como todas
las variables residen en RAM interna o externa, pueden ser accedidas mediante el uso de
punteros. Se utilizan normalmente en la indexacin, direccionamiento y movimiento de datos.

Una variable px se declara como variable puntero, de la forma siguiente:

char *px

y sirve para apuntar a datos de tipo carcter. Por definicin, las variables puntero son siempre de
tipo unsigned int, pues guardan slo direcciones. Actualmente, el tipo de datos de los punteros
depende del espacio de memoria de la tarjeta y del compilador utilizado. No hemos de confundir
la direccin de la variable....

El ejemplo siguiente resume los conceptos de indireccin de los punteros:

void main (void)


{
char x, *px;
px = &x;
x = d;
printf(El puntero px apunta a %c, *px);
}

Este programa imprimira el carcter contenido en la variable x, es decir, d.

Con px apuntando a x, el programa es capaz de acceder indirectamente al contenido de x.


Tambin existe otra forma de inicializar la variable puntero:

char x;
char *px = &x;

que asigna a px la direccin de x (no a *px !).

5.1. Tipos de punteros en el 8051

Cuando se declara un puntero, se necesita especificar no slo dnde el puntero va a ser


almacenado, sino tambin a qu tipo de zona de memoria va a apuntar. La declaracin siguiente

unsigned char data * xdata x_ptr = 0x4a;

Laboratorio de Sistemas Electrnicos Digitales II Pg. 8


Programacin en C del 8051

declara a x_ptr como una variable puntero que residir en memoria xdata, para apuntar a los
datos o variables de tipo carcter almacenados en la memoria de tipo data. El puntero x_ptr es
inicializado con el valor 0x4a, que ha de ser una direccin de tamao byte para apuntar dentro del
rea de memoria tipo data. En la figura 1 puede verse como x_ptr apunta a una posicin de
memoria del rea data.

La variable puntero consta de 3 bytes, uno para almacenar un nmero ndice y otros dos para
almacenar la direccin sin signo 4Ah.

Observe las posiciones de los dos tipos de especificadores de memoria de la declaracin


anteriormente vista: xdata se sita inmediatamente antes de la variable puntero y especifica el
tipo de memoria donde se sita el puntero. El especificador data seguido del asterisco (*) le dice
al compilador que este puntero se usa para apuntar los datos ubicados en el rea data de memoria.
Los punteros as declarados reciben el nombre de punteros tipo porque los tipos de memoria
estn explcitamente declarados.

Se puede resumir diciendo que un puntero tipo queda declarado bajo la siguiente sintaxis:

dato_tipo mem_tipo_d * [mem_tipo_ptr] ptr_nombre [ = direccin];

dato_tipo: Tipo de dato a ser apuntado, y puede ser uno de los habitualmente
utilizados: char, int, float, ...

mem_tipo_d: Tipo de especificador de memoria que define el espacio de


memoria donde los datos van a ser apuntados. Puede ser de tipo
code, data, idata, pdata, o xdata.

mem_tipo_ptr: Tipo de especificador de memoria (opcional) que define el espacio


de memoria donde reside el puntero. Puede ser de tipo code, data,
idata, pdata, o xdata. En el caso que no se defina, el tipo de
memoria seleccionado es el que exista por defecto.

ptr_nombre: N o m b r e
asignado a la Tipo de memoria donde reside el puntero
variable
puntero. 4
x_ptr
4A
direccin: Opcionalme
n t e , 4B
constituye el 4A xxxxxxxx
valor de
49
direccin con
el que se
inicializa. Un
puntero data
puede tomar
valores entre data xdata
0x00 y 0x7F; Fig.1. Punteros

Laboratorio de Sistemas Electrnicos Digitales II Pg. 9


Programacin en C del 8051

un puntero idata, entre 0x00 y 0xFF; un puntero xdata, entre


0x0000 y 0xFFFF; un puntero pdata, entre 0x00 y 0xFF; y un
puntero code, entre 0x0000 y 0xFFFF

Algunos ejemplos de declaracin de punteros son:

unsigned char xdata * data s_ptr = 0x1234;


int code * tabla_ptr = 0x1000;
unsigned float xdata * xdata u_ptr;

El compilador de C de Franklin permite operar con los denominados punteros sin tipo, o tambin
denominados genricos, que se utilizan para acceder a cualquier variable, sin importarnos su
posicin en la memoria del 8051. Varias de las libreras del C51 utilizan este tipo de puntero, pues
mediante estos punteros genricos, una funcin puede acceder a los datos sin tener en cuenta el
lugar de memoria donde estn almacenados. Se declaran de igual forma a como especifica el
ANSI C:

char *p;
int *ptr;
long *x;

Los punteros genricos siempre se almacenan en tres bytes. El primero de ellos es el que
especifica el tipo de memoria, el segundo es el byte alto que representa a la direccin, y el tercero
es el byte bajo. La tabla 2 muestra los valores del byte que identifica al tipo de memoria:

Tabla 2. Identificacin de la memoria del puntero


Tipo de memoria idata xdata pdata data/bdata code
Valor 1 2 3 4 5

Cuando a un puntero genrico se le asigna la direccin de la variable despus de su declaracin,


por ejemplo:

int xdata x;
ptr = &x;

el primer byte de ptr, representa al tipo de memoria de la variable x. Como x se ha definido en


memoria xdata, el primer byte toma el valor 2. Los siguientes dos bytes de ptr, constituyen la
direccin.

Un puntero genrico a xdata localizado en la memoria por defecto, puede inicializarse con una
direccin xdata de forma directa:

unsigned char * ptr = 0x24536L;

donde el valor 2, representa al tipo de memoria xdata que se toma por defecto (primer byte de
ptr) y el sufijo L inicializador long. Tal declaracin es equivalente a haber escrito:

unsigned char xdata * ptr = 0x4536;

Laboratorio de Sistemas Electrnicos Digitales II Pg. 10


Programacin en C del 8051

El cdigo que resulta al emplear punteros declarados de forma genrica es ms lento que el
equivalente considerando la declaracin bajo un puntero tipo. Esto se debe a que el rea de
memoria no se conoce hasta el tiempo de ejecucin. El compilador no puede optimizar los
accesos a memoria y produce un cdigo genrico que puede acceder a cualquier posicin de
memoria.

Puede especificarse el rea de memoria en la que se almacenar un puntero genrico, mediante


el uso del especificador de tipo de memoria:

char * xdata sptr;


int * data nptr;
long * idata vptr;

Las anteriores declaraciones son punteros a variables que pueden almacenarse en cualquier rea
de memoria. Sin embargo, los punteros se almacenan en xdata, data e idata respectivamente.

5.2. Conversin de punteros

El compilador C51 de Franklin permite conversiones entre punteros tipo y punteros genricos.
La conversin de punteros puede forzarse explcitamente en el programa usando un molde (type
cast) o puede ser forzado por el compilador.

Por ejemplo, el compilador C51 fuerza un puntero tipo en un puntero genrico cuando el puntero
tipo se pasa como argumento de una funcin que requiere un puntero genrico. Este es el caso
de funciones tales como printf, sprintf, y gets que utilizan punteros genricos como argumentos.

Un puntero tipo usado como argumento de una funcin, se convierte siempre a un puntero
genrico si no est presente en el prototipo de la funcin. Esto puede causar errores si la llamada
a la funcin espera un puntero ms corto como argumento. Con objeto de evitar este problema,
se recomienda utilizar ficheros include y prototipos en todas las funciones. Esto garantiza la
conversin de los tipos por el compilador e incrementa la probabilidad de que al compilar se
detecten errores de conversin.

En el men de ayuda del entrono de desarrollo ProView de Franklin, se pueden observar los tipos
de conversin entre punteros.

Los ejemplos siguientes ilustran algunas conversiones de punteros y el cdigo resultante:


int *ptr1; /* puntero genrico (3 bytes) */
int xdata *ptr2; /* puntero xdata (2 bytes) */
int idad *ptr3; /* puntero idata (1 byte) */
int code *ptr4; /* puntero code (2 bytes) */

void pconv(void)
{
ptr1 = ptr2; // xdata * a genrico *
ptr1 = ptr3; // idata * a genrico *
ptr1 = ptr4; // code * a genrico
ptr4 = ptr1; // genrico * a code *
ptr3 = ptr1; // genrico * a idata *
ptr2 = ptr1; // genrico * a xdata *
ptr2 = ptr3; // idata * a xdata *

Laboratorio de Sistemas Electrnicos Digitales II Pg. 11


Programacin en C del 8051

ptr3 = ptr4; // code * a idata *


}

5.2. Puntero a arrays

El nombre de cualquier array es un puntero fijo (constante puntero) que apunta al primer
elemento del array. Por ejemplo, un array de enteros temp[20], consta de los elementos temp[0],
temp[1], ... temp[19]. Entonces, temp, que es el nombre del array es un puntero a una constante
que es la direccin de temp[0], y por tanto, no puede modificarse. No es legal temp++, al igual
que tampoco lo es 5++.

Una contante puntero puede utilizarse en un programa al igual que cualquier otra constante, pero
ha de seguir ciertas reglas. Por ejemplo, temp+1 apunta al siguiente elemento del array, es decir
a temp[1], y no a la siguiente posicin de memoria donde el array est almacenado. Esto es
debido a que cada elemento del array requiere varios bytes de memoria. De ah que podamos
acceder a los elementos del array ya sea utilizando la notacin temp[n], o *(temp+n). Adems,
el operador de direccin puede tambin ser utilizado para direccionar cualquier elemento del
array. Por ejemplo, &temp[2] es la direccin del tercer elemento del array, temp[20].

Considerando arrays de mayor dimensin, las reglas son las mismas. As, para el caso de un array
bidimensional, *(*(temp+n)+m) es equivalente a temp[n][m].

Una variable puntero puede crearse para servir de ndice y acceder a los elementos de un array,
al mismo tiempo que permite ser manipulada fcilmente con ayuda de los diferentes operadores
de C. El ejemplo siguiente muestra como una variable puntero se utiliza para acceder a los
elementos de un array por indexacin.

#include <stdio.h>

void main(void)
{
int temp[3];
int sum=0, sum_celsius=0;
int num, dia=0;
int *ptemp;

printf(\n);
ptemp = temp;
do
{
printf(Introduce para el dia %d: , ++dia)
scanf(%d, ptemp);
}
while ( *(ptem++) > 0 );
ptemp = temp;
num = dia-1;

for (dia=0; dia<num; dia++)


{
sum += *(ptemp+dia);
sum_celsius += 5*(*(temp+dia)-32)/9;
}

printf (El valor medio de la temperatura es %d en Fahrenheit y %d en


Celsius, sum/num, sum_celsius/num);

Laboratorio de Sistemas Electrnicos Digitales II Pg. 12


Programacin en C del 8051

El programa toma hasta 31 valores de temperatura positivos e imprime en pantalla el valor medio.

Un uso comn de los punteros a arrays es la declaracin de un array de caracteres mediante una
variable puntero.

char *parray = {el sol, el mar, y la tierra};

5.3. Arrays de punteros

Un grupo de variables puntero pueden formar un array de punteros, que de forma simultnea
apuntan a diferentes elementos en un grupo de datos, de manera similar a un array bidimensional.
Una aplicacin de un array de punteros es en la indexacin de un grupo de mensajes para ser
mostrados por un display, y acceder de forma rpida con referencias de unos a otros. A
continuacin se muestra un programa ejemplo de como un array de punteros ordena un grupo
de nombres.

#include <stdio.h>
#include <conio.h>
#include <string.h>
#define MAXNUM 30
#define MAXLEN 30
void ordena(char *, int);
char nombre[MAXNUM][MAXLEN];

void main (void)


{
char *temp;
int cuenta=0;
int in, out;

clrscr();
printf(\nIntroduce 8 nombre. Pulsa return para finalizar la
entrada\n);

while (cuenta < MAXNUM)


{
printf (Nombre %d: , cuenta+1);
gets(nombre[cuenta]);
if (strcmp(nombre[cuenta++], )==0)
break;
}

ordena(nombre[0],cuenta-1);
printf(\n\n Pulsa una tecla para comenzar.);
getch();
}

void ordena(char *pp, int cnt)


{
char *kp, *pt[MAXNUM];
int x, y;

for(x=0; x<MAXNUM-1; x++)

Laboratorio de Sistemas Electrnicos Digitales II Pg. 13


Programacin en C del 8051

pt[x]=pp+40*x;

for(x=0; x<cnt-1; x++)


for(y=x+1; y<cnt; y++)
if (strcmp(pt[x],pt[y]) > 0)
{
kp = pt[y];
pt[y] = pt[x];
pt[x] = kp;
}
printf(\nLista ordenada: \n);
for (x=0; x<cnt; x++)
printf(\nNombre %d: %s, x+1, pt[x]);
}

Observe que la funcin ordena(), indexa los nombres almacenados en el array con array de
puntero.

Un array de punteros puede inicializarse para apuntar a un grupo de cadenas directamente. Por
ejemplo,

char *nombres[]=
{Jose,
Julio,
Antonio,
Ana
};

El array declarado podra apuntar a cada cadena interna. Esto es, nombres[0] es un puntero a la
direccin de la primera cadena, nombres[1] a la direccin de la segunda, y as sucesivamente. Las
cadenas agrupadas segn esta estructura ocupan menos memoria, mientras que si se hubiese
empleado un array de dimensin 4*10, se necesitara rellenar los caracteres no completados para
cada nombre.

5.4. Punteros a estructuras y uniones

Un puntero puede tambin apuntar a una estructura, de modo que se pueda acceder a cualquier
miembro de la estructura. La sintaxis se muestra a continuacin:

struct medida {
int tiempo;
int retardo;
char patron;
};

struct medida x, *ptr;


ptr = &x;

Se ha declarado un tipo de estructura llamado medida y definido una variable puntero ptr que
apunta a la variable x de tipo estructura. El acceso a los elementos de la estructura se lleva a cabo
con el operador ->.

ptr->retardo = 30;
ptr->patron = 0x40;

Laboratorio de Sistemas Electrnicos Digitales II Pg. 14


Programacin en C del 8051

donde el valor 30 se sita en la variable entera tiempo, y el dato 0x40 en la variable carcter
patron. Adems, en el caso de arrays de caracteres, C permite la declaracin de una varible de
tipo estructura, sin el nombre de la variable.

struct medida {
int tiempo;
int retardo;
char patron;
}*ptr;

En este caso el acceso a los elementos de la estructura es idntico al visto anteriormente.

Una unin es similar a una estructura, de ah que se use el mismo formato en su declaracin:
union medida {
int tiempo;
int retardo;
char patron;
}*ptr;

6. SENTENCIAS DE CONTROL

Se detallan a continuacin las diferentes sentencias de control que se pueden emplear en un


programa en C. Se utilizan para realizar bucles y mecanismos de decisin para controlar el flujo
del programa. Permiten que una sentencia de C o un grupo de ellas puedan ejecutarse de manera
selectiva, o repetirse un nmero determinado de veces, o que sean ejecutadas si se cumple una
determinada condicin dentro del programa.

6.1. Sentencias while y do...while

En aplicaciones de control, es habitual monitorizar una determinada entrada o registro, mientras


se lleva a cabo una determinada tarea. La sentencia while puede utilizarse para llevar a cabo dicha
monitorizacin. En el ejemplo siguiente,

while (P1&0x20)
{
x=P1;
P1=0x00;
P2=0x0C;
}

las sentencias que se encuentran entre las llaves, se ejecutan slo si el bit 5 del puerto 1 es un 1".

La forma de esperar a que suceda una determinada seal para continuar con la ejecucin del
programa, es utilizando la siguiente sentencia:

while (semaforo);

Se trata de un bucle infinito, mientras el valor de semaforo permanezca a nivel alto.

La sentencia do-while es similar a while salvo que la condicin se evala despus de haberse
ejecutado el cdigo que resida dentro del cuerpo. Esto trae como consecuencia que el bucle se

Laboratorio de Sistemas Electrnicos Digitales II Pg. 15


Programacin en C del 8051

ejecuta siempre al menos una vez.

do {
una o ms sentencias de C;
} while (expresin);

6.2. Sentencia if...else

La sentencia if constituye la manera ms simple de tomar una decisin en C. Tiene la sintaxis


general:

if (test)
sentencia1;
else
sentencia2;

Donde test es una expresin cuyo resultado se evala como cierto (distinto de cero) o falso (igual
a cero). Si la expresin test es cierta se ejecuta la sentencia1 y si es falsa la sentencia2. En la
expresin que condiciona la ejecucin, pueden emplearse los operadores lgicos y de igualdad.

if (++conta == limite){
if(!CY) {
set_alarma();
flag=1;
}
}
else
x=P1;

Observe en el ejemplo anterior que existe una nueva sentencia if, dentro de la primera. Slo si la
condicin ms externa es cierta, se entra en el segundo if. No debe confundir el operador de
igualdad == con el de asignacin =.

La palabra clave else se puede combinar con la palabra clave if para generar secuencias anidadas.

if (var=10)
printf(La variable es 10");
else if (var==20)
printf(La variable es 20);
else
printf(La variable no es ni 10 ni 20);

6.3. Sentencia for

Tiene la sintaxis general:

for (expresin_inico, expresin_test; expresin_repeticin)


{
una o ms sentencias de C;
}

Esta sentencia al igual que while consiste en la repeticin de un bucle. Dentro del for, se debe

Laboratorio de Sistemas Electrnicos Digitales II Pg. 16


Programacin en C del 8051

indicar el valor inicial de la expresin, la condicin que debe cumplirse para finalizar el bucle y
qu modificaciones deben realizarse en cada iteracin.

void main (void)


{
int n, factorial=1;
for(n=6;n==1;n--) factorial*=n;
}

6.4. Sentencias switch, case, break y default

La sintaxis es la siguiente:

switch (num)
{
case 1:
sentencia1;
break;
case 2:
sentencia2
break;
defaul:
otras_sentencias;
}

Esta sentencia permite ejecutar una serie de sentencias en funcin del valor de una variable. En
el ejemplo anterior la variable a consultar es num y los valores que se contemplan son 1 y 2.

En caso de que la variable valga 1 se ejecutara la sentencia1, despus de lo cual con la sentencia
break el flujo del programa se va a la lnea siguiente del final del bucle switch. Si num es 2 se
ejecuta la sentencia2, y si no fuese ninguno de los dos valores ira a default y ejecutara
otras_sentencias.

6.5. Sentencia continue

La sentencia continue provoca el salto de control del programa desde el lugar en que se encuentre
hasta el final del bloque en curso, saltando todas las sentencias que haya entre ellas, pero
permitiendo que el bucle contine.

7. FUNCIONES, MDULOS Y PROGRAMAS

Las funciones permiten construir bloques de un programa. Un programa en C es un conjunto de


funciones especialmente diseadas para realizar ciertas tareas especficas, y en conjunto llevar a
cabo el objetivo final del programa.

Generalmente una funcin se crea para realizar una determinada tarea por s misma, y
comunicarse con otras funciones ya sea mediante el paso de parmetros que ha de recibir como
entrada para ser procesados, o devolviendo algn resultado que otras funciones precisen.

Numerosas tareas bsicas pueden implementarse a travs de funciones y as ser utilizadas en


diferentes programas, o dentro de un mismo programa en diferentes instantes de tiempo.

Laboratorio de Sistemas Electrnicos Digitales II Pg. 17


Programacin en C del 8051

Cuando una funcin se llama, el control se pasa a la misma para su ejecucin y cuando esta
finaliza, el control es devuelto de nuevo al mdulo que llam, para continuar con la ejecucin del
mismo, a partir de la sentencia siguiente a la que se efectu la llamada.

7.1. Declaracin de una funcin (prototipos)

Teniendo en cuenta la convencin que existe en las funciones escritas en C, para declarar una
funcin es necesario es necesario especificar el prototipo de la funcin, es decir, el nombre de la
funcin, los argumentos, y el tipo de datos que pudiese retornar. Tambin puede ocurrir que se
desee modificar el modelo de memoria por defecto para una funcin particular, con objeto de que
tenga unas determinadas caractersticas particulares.

Para especificar un modelo de memoria para una funcin, es necesario que las variables locales
y los argumentos de la funcin se almacenen en el rea de memoria especificada. Como ya se
indic, la memoria RAM interna del 8051, especificada bajo el modelo small, es buena para
guardar datos que necesiten accesos rpidos. Aquellas funciones que tengan tiempos de ejecucin
crticos, deberan utilizar el modelo de memoria small. Cuando la memoria interna del 8051 no
sea suficiente para almacenar datos, dada su pequea capacidad, ser necesario entonces elegir
un modelo de memoria adecuado.

El 8051 tiene cuatro bancos de registros en RAM interna, cada uno de ellos formado por ocho
registros (R0-R7). El banco seleccionado po defecto despus del reset es el banco 0. Existe la
posibilidad de seleccionar el banco de trabajo por medio de la directiva REGISTERBANK. Esto
permite seleccionar un banco diferente para una determinada funcin. La conmutacin de los
bancos es habitual al trabajar con interrupciones o en sistemas de tiempo real, a fin de minimizar
el tiempo empleado en salvar los registros de inters al entrar en la funcin.

El formato standard para declarar una funcin de C, en el software de Franklin es:

return_tipo nombre_func(arg) [mem_tipo] [reentrant] [interrupt]


[using]

return_tipo: Tipo del valor retornado por la funcin. Si no se especifica, se


asume por defecto el tipo int.

nombre_func: Nombre de la funcin.

args: Es la lista de argumentos de la funcin.

mem_tipo: Especificacin explcita del modelo de memoria (small, compact,


o large)

reentrant: Indica que la funcin es recursiva o reentrante.

interrupt: Indica que se trata de una funcin de interrupcin.

using: Especifica el banco de registros utilizado por la funcin (ej. using

Laboratorio de Sistemas Electrnicos Digitales II Pg. 18


Programacin en C del 8051

2).

A continuacin se muestra un ejemplo con diferentes modelos de memoria, y el uso del banco
3 de registros en la funcin xfunc().

#pragma small

void main (void)


{
int i=10, j=20, k;
k= xfunc(i, j);
}

int xfunc(int x, int y) large using 2


{
int z;
z=x*y;
return(z-x-y);
}

Las variables locales (i, j, k) de main() se guardan en memoria RAM interna, debido a la directiva
#pragma small. Sin embargo, los valores i y j pasados a la funcin xfunc(), se localizan en la
memoria xdata, debido a que los argumentos y variables locales (x, y, z) de la funcin xfunc()
se les aplica el modelo de memoria large. Adems, el banco de registros utilizado por la funcin
xfunc(), es el banco 2.

7.2. Paso de parmetros a una funcin

La comunicacin entre las funciones se lleva a cabo a travs de los argumentos que se usan en
la llamada a la funcin y el valor retornado por la funcin. Una funcin es un bloque de
instrucciones que residen en memoria de cdigo. Las variables son, por otro lado, posiciones de
memoria que pueden ser de cualquiera de los tipos: small, compact, o large. Tradicionalmente en
los microprocesadores de 8 bits, los valores pasados a una funcin eran almacenados en la pila
(stack), la cual tena un tamao de hasta 64Kbytes. En el 8051 la pila reside en memoria RAM
interna (despus del reset el SP se sita en la posicin 07) y su tamao mximo es de 128 bytes,
pudiendo ser tan pequea como 64 bytes para algunas versiones de la familia (87C751 de Philips).
Debido a estas limitaciones existen diferentes formas de llevar a cabo el paso de parmetros a una
funcin.

Paso de parmetros por registros

Con compilador C51 de Franklin los parmetros se pasan por defecto, de una funcin a otra a
travs de registros, estando limitado el nmero de ellos a tres. Los parmetros que no pueden ser
localizados en registros, se pasan automticamente a travs de posiciones fijas de memoria siendo
el modelo de memoria utilizado el que se especifique en la declaracin. Los tres parmetros que
permiten pasarse por registro siguen ciertas reglas, debido a que existen slo 8 registros
disponibles en cada banco, y el tipo de dato de cada parmetro pueden llegar en cualquier orden.

La tabla 3 muestra los registros usados en el paso de parmetros a funciones cuando los
argumentos son de tipo diferente.

Laboratorio de Sistemas Electrnicos Digitales II Pg. 19


Programacin en C del 8051

Tabla 3. Registros usados en paso de parmetros a funciones


argumento char/ptr de 1byte int/ptr de 2 bytes long/float ptr genrico

1 R7 R6,R7 R4-R7 R1-R3


2 R5 R4,R5 R4-R7 R1-R3
3 R3 R2,R3 R1-R3

Obviamente, no todos los tres parmetro pueden pasarse por registros. Dependiendo de el tipo
de datos de los argumentos, un parmetro que no sea pasado por registro se pasa a travs del rea
de memoria asociada con la funcin. En el men de ayuda de ProView32, se puede consultar una
lista con la combinacin de varios parmetros y el tipo de memoria utilizado cuando se pasan a
una funcin.

Una funcin puede retornar un valor a travs de un registro. La tabla 4 muestra los registros
usados en funcin del tipo de dato retornado.

Tabla 4. Registros usados para el retorno de valores

Tipo de dato Registro


char (1 byte) R4
int (2 bytes) R4,R5
ptr genrico (3 bytes) R0,R2,R3
float (4 bytes) R4-R7
double (6 bytes) R2-R7
long double (7 bytes) R1-R7

Paso de parmetros a travs de memoria

El paso de parmetros a una funcin por registro podra utilizarse en caso de aplicaciones en las
que el tiempo es crtico, debido a que su direccionamiento es ms rpido. Sin embargo, no
siempre es posible utilizar los registros, pues slo existen 8 disponibles. Otra limitacin est en
que no pueden pasarse parmetros tipo bit a una funcin a travs de registros. Por ello, el
compilador C51 permite utilizar la memoria para llevar a cabo el proceso.

La directiva de control NOREGPARMS hace que slo se permita el paso de parmetros a travs
de memoria, desactivandose el modo por defecto (uso de registros). Igualmente, esiste la directiva
de control REGPARMS, para activar el modo de paso por registro. El ejemplo siguiente ilustra el
uso de estas direstivas:

Laboratorio de Sistemas Electrnicos Digitales II Pg. 20


Programacin en C del 8051

#include <stdio>
#pragma NOREGPARMS
extern int bfunc(float, int, char)

void main (void)


{
int i=10, j=20, n, k;
float x=3.1416;
char ch=65;
n=bfunc(x, i, ch);
k=xfunc(i, j);
printf (%d, %d , n, k);
}

#pragma REGPARMS
int xfunc(int x, int y)
{
int z;
z=x*y;
return(z-x-y);
}

7.3. Funciones reentrantes

Una funcin reentrante es aquella que puede ser llamada simultneamente por otras funciones o
recursivamente por ella misma. Los parmetros pasados a una funcin de este tipo se almacenan
junto con las variables locales de la funcin en una pila creada a tal efecto para la funcin. La
ubicacin de la pila queda determinada por el modelo de memoria definido en la funcin,
pudiendo ser cualquiera de los tres posibles.

Una funcin reeentrante se declara con el atributo reentrant tal como se vio en el apartado 7.1.
Por ejemplo, si la funcin xfunc(int x, int y) del programa anterior necesita ser reentrante, la
directiva REGPARMS no podra usarse debido a que las funciones reentrantes no reciben
parmetros de registros. La funcin entonces, se declarara as:

int xfunc (int x, int y) large reentrant


{
int z;
z=x*y;
return(z-x-y);
}

Los argumentos x, y, as como la variable local z, se ubicaran en xdata debido a que es donde
se genera la pila (stack) para la funcin. El modelo de memoria elegido para la funcin determina
no slo donde se guardan las variables, sino tambin la memoria para el paso de parmetros.

Debemos tener precaucin cuando queramos situar la pila en memoria interna, ya que puede ser
fcilmente desbordar la capacidad de la RAM interna. Al comienzo de la RAM y por encima de
la zona de registros, es donde se ubican las variables que se declaran tipo bit, data, e idata, y las
variables locales de aquellas funciones que se declaran usando el modelo small de memoria. Por
encima de estas posiciones es donde se inicializa el puntero de pila (SP). La pila que se crea
automticamente y se asocia con una funcin que usa el modelo de memoria small, se localiza
en la parte alta de la RAM interna, y crece hacia posiciones ms bajas a medida que recibe datos.

Laboratorio de Sistemas Electrnicos Digitales II Pg. 21


Programacin en C del 8051

Debido al nmero de veces que puede ser llamada la funcin, puede dar lugar al overflow de la
RAM interna.

7.4. Funciones de interrupcin y conmutacin de los bancos de registros

Una funcin de interrupcin es un elemento importante en aplicaciones de tiempo real con


microcontroladores. Cuando ocurre una interrupcin el sistema entra en un estado especial, que
demanda la ejecucin de una rutina particular del programa (funcin de interrupcin)
suspendiendose la ejecucin en curso, hasta que el servicio de interrupcin se completa.

El 8051 tiene un nmero de interrupciones hardware que permiten detectar eventos externos,
operaciones de los timers, y el control del puerto serie. El compilador C51 soporta interrupciones
y permite escribir en C la rutina asociada con la fuente de interrupcin. A partir de el nmero de
interrupcin y del banco de registros especificado para la funcin de interrupcin, el compilador
genera automticamente el vector de interrupcin as como la entrada y la salida al cdigo que
constituye la funcin de interrupcin.

El atributo interrupt en la declaracin de una funcin permite especificar que dicha funcin es
de interrupcin. El argumento del atributo de la funcin especifica el nmero de interrupcin
asociado con dicha funcin. Por ejemplo,

void tiempo (void) interrupt 3


{
TH1=-100;
TL1=-100;
ET1=1;
if (dtime=256) P1=1;
dtime++;
}

donde el nmero de interrupcin especifica que esta funcin corresponde a la funcin de


interrupcin del Timer 1. La correspondencia entre el nmero de interrupcin y el tipo de
interrupcin se muestra en la tabla 5.

Cuando se llama a una funcin de interrupcin, la CPU lleva a cabo las siguientes operaciones:

1. Salva el contenido de los registros ACC, B, DPH, DPL, y PSW en la pila cuando sea
necesario.

2. Salva todos los registros de trabajo que sern utilizados por la funcin de interrupcin
en la pila.

3. Ejecuta la funcin de interrupcin.

4. Restaura todos los registros de la pila salvados antes de salir de la funcin de


interrupcin.

5. Ejecuta la instruccin RETI para finalizar la funcin.

Tabla 5. Nmeros de interrupcin

Laboratorio de Sistemas Electrnicos Digitales II Pg. 22


Programacin en C del 8051

Interrupcin Nmero
Externa 0 0
Timer 0 1
Externa 1 2
Timer 1 3
P. Serie 4

Con objeto de emplear el menor tiempo posible en salvar los registros en la pila al entrar a una
funcin de interrupcin, se puede cambiar de banco de registros. Para ello, el compilador C51
permite utilizar el atributo using N en la declaracin de una funcin de interrupcin, donde el
valor N representa al banco seleccionado al entrar en la funcin. Para el ejemplo anterior, y si se
quisiese usar el banco 1 al entrar en la funcin, la declaracin sera:

void tiempo (void) interrupt 3 using 1


{
TH1=-100;
TL1=-100;
ET1=1;
if (dtime=256) P1=1;
dtime++;
}

Laboratorio de Sistemas Electrnicos Digitales II Pg. 23

You might also like