Você está na página 1de 17

8.7.

ARREGLOS Y PUNTEROS Los arreglos son uno de los pilares del lenguaje ANSI C ya que permiten la manipulacin de mltiples valores numricos asociados a una sola variable denominada vector. Los arreglos y punteros hacen uso intensivo de la memoria de datos ya que normalmente se requiere el almacenamiento de ms de un valor. Es as que los arreglos interactan con el dato y su direccin. El uso de arreglos es vital para la implementacin de algoritmos avanzados, tales como los usados en procesamiento de seales digitales. Adems que facilita la creacin de mensajes y tablas de datos que pueden ser muy fcilmente accedidas en el programa. Un arreglo de datos se declara como una variable, indicando entre corchetes ([ ]) la cantidad de elementos que la componen. Por ejemplo, para definir un arreglo se pueden utilizar los siguientes formatos: unsigned char vector[10]; char mensaje[] = Programando el PIC18F4550; unsigned int valores[] = {0x9040,0x1234,0x6798,0x1A2B,0xF761}; En el primer caso, el arreglo vector se declara de tipo unsignedchar (8 bits sin signo) y de un tamao de 10 elementos. Esto implica que el arreglo vector ocupar 10 espacios de memoria donde cada espacio es de 1 bytes de capacidad, en total seran 10 bytes de memoria reservados para la variable vector. En el segundo caso, el vector mensaje no tiene indicado el nmero de elementos. Pero, en su inicializacin se le asigna la cadena de caracteres ASCII Programando el PIC18F4550, la cual se encuentra comprendida por 25 bytes, lo cual implica que mensaje es un vector de 25 posiciones de memoria. Finalmente, el arreglo valores est compuesto de 5 elementos donde cada elemento es de 2 bytes y ocupa dos direcciones de memoria. Cuando un arreglo se inicializa con valores no ASCII, todos los valores se deben colocar entre llaves y separados por una coma. Los arreglos pueden declararse tanto en la memoria de datos como en la memoria de programa. Observe la siguiente declaracin.Aqu se crean dos vectores uno de cinco elementos y 8 bits por elemento y otro de cuatro elementos y 16 bits por elemento. En la Figura 8.2 se muestran

como los datos de inicializacin estaran almacenados en la memoria de datos o SRAM. Obsrvese que el formato para los datos de 16 bits es endianness en donde el byte de ms peso ocupa la direccin ms alta. #pragma idata VECTORES = 0x100 char mensaje[] = "PIC18"; int vector[] = {0xA091,0x1234,0xB751,0x45F1}; #pragma idata De la misma manera, si un arreglo se quiere declarar en la memoria de programa (por lo cual ninguno de sus elementos podra ser modificado) se puede hacer uso de la siguiente declaratoria: #pragma rom data TABLA = 0x5000 const rom char tabla[]= "Hola Mundo"; #pragma rom data

Figura 8.2. Alojamiento de los datos de los arreglos mensaje y vector en la memoria de datos En este caso, el arreglo tabla se compone de 10 bytes y estos se encuentran almacenados en la memoria de programa entre las direcciones 0x5000 y 0x5009. Cada carcter de tabla puede ser accedido en el programa principal, pero, solo para el proceso de lectura ms no escritura. El compilador arrojara un error si se tratara de realizar el ltimo caso. Otra variante de los arreglos es la declaracin en forma de matrices. Aqu el arreglo presenta dos ndices: el nmero de filas y el nmero de columnas. Para declarar un arreglo de forma matricial se sigue el siguiente formato: unsigned char matriz[3][4];

En este ejemplo, la variable matriz presenta tres filas y 4 columnas, donde cada elemento de esta matriz es de un byte sin signo. En total este arreglo ocupa 12 bytes de la memoria de programa. No es recomendable el uso intensivo de este tipo de arreglo ya que el consumo de memoria se multiplicara. El acceso a la informacin de los arreglos se realiza mediante su ndice. El ndice representa la posicin de cada elemento del arreglo y siempre debe estar representado por una cifra entera positiva. El primer valor del ndice es el 0 y el ltimo es el tamao del arreglo menos uno. Por ejemplo, en el Programa 8.21 se muestra un ejemplo de acceso a un arreglo declarado en la memoria de datos. Programa 8.21. Ejemplo de acceso a la informacin contenida en un arreglo de datos
#pragma idata VECTORES = 0x100 unsigned char vector[]="Hola Mundo"; #pragma idata unsigned char a,b; void main() { a = vector[0]; b = 'k'; vector[5] = b; while(1); }

En el ejemplo mostrado, la variable a toma el valor ASCII H que es el primer elemento del arreglo vector (ndice = 0). Luego, la variable b toma el valor ASCII k y este valor se asigna al sexto elemento del arreglo (ndice = 5), el cual reemplaza el valor M por k. Por otro lado, los punteros son ms verstiles que los arreglos ya que permiten reasignar las direcciones en las cuales van a almacenar sus datos. La utilidad de los punteros reside en la capacidad para apuntar a determinada direccin de memoria y extraer o guardar un dato en dicha posicin. Con los punteros se pueden reutilizar las direcciones de memoria que han sido asignadas previamente a otras variables. La declaracin de un puntero sigue el siguiente formato:

Tipo *nombre del puntero; El tipo implica el tamao del dato al cual va a apuntar el puntero, por ejemplo, puede apuntar a valores de 8 bits o de 16 bits el nombre del puntero debe ir precedido por un *. El acceso a un puntero tambin se puede hacer mediante ndices al igual que un arreglo o mediante el acceso al contenido precediendo * a la variable puntero. Por defecto, un puntero apunta a un dato de 16 bits. Para asignar la direccin a un puntero se debe hacer uso del operador &. Por ejemplo, suponga que una variable a es declarada de la siguiente manera: unsigned char a=9; Entonces, suponga que la variable a ocupa la direccin 0x60 de la memoria SRAM. Dicha direccin contiene el valor 9 ya que se est asignando esta cifra a la variable en su declaracin. Si posteriormente, se declara un puntero de nombre p de la siguiente manera y se hace la siguiente asignacin: unsigned char *p; void main() { p = &a; *p=10; } En este ejemplo, p es un puntero que apunta a registros de 8 bits. El puntero tiene asignada la direccin de la variable a (p = &a) por lo cual apuntar a la direccin 0x60.Posteriormente, se carga el valor de 10 al contenido apuntado por p (instruccin *p = 10), el cual reemplaza al valor de 9 que tena la variable a. En otras palabras se ha cambiado el contenido de la variable a usando el puntero p. Por ejemplo, en el Programa 8.22 se muestra la declaracin de un puntero y como este interacta con el arreglo de nombre vector previamente declarado.

Programa 8.22. Ejemplo de manejo de punteros


#pragma idata VECTORES = 0x100 unsigned char vector[]="Hola Mundo"; #pragma idata unsigned char *p; void main() { p = &vector[5]; *p = 'R'; p++; *p = 'e'; p[2]='g'; while(1); }

El programa crea el vector de 10 elementos inicializado con el mensaje Hola Mundo a partir de la direccin 0x100 de la SRAM. Posteriormente se crea el puntero p que apunta a valores de 8 bits. Cuando se asigna la direccin del sexto elemento de vector a p, este apunta a la direccin 0x105. Inmediatamente, al contenido de esta direccin se le asigna el cdigo ASCII R, reemplazando al ASCII M que estaba all. Luego, el puntero se auto incrementa en 1 dgito, apuntando a la direccin 0x106. Cuando se asigna el valor ASCII al contenido apuntado en p (instruccin *p = e), este reemplaza al valor ASCII u que se encontraba en esa posicin. Finalmente, el tercer elemento de p es asignado con el valor g, lo cual implica que la direccin 0x106+0x02 = 0x108 toma el valor ASCII j reemplazando al valor ASCII d. De esta manera, el mensaje inicial cargado en vector cambia a ser Hola Rengo. 8.8. ESTRUCTURAS Y UNIONES Las estructuras son arreglos de datos que se pueden invocar desde una sola variable al igual que los punteros. La diferencia principal con estos ltimos radica en que los tipos de datos asociados a la variablepueden ser diferentes. Por ejemplo, suponga que queremos crear una variable de nombre Persona, la cual tieneasociados los siguientes datos: edad, peso, talla, DNI, nombre. Cada dato de la variable presenta diferentes caractersticas: 1. La edad de la persona es un valor de 8 bits, ya que puede oscilar entre 0 y 255 aos.

2. El peso de la persona tambin es un valor de 8 bits y puede alternar entre 0 y 255 kg. 3. La talla de la persona tambin es un valor de 8 bits y puede alternar entre 0 y 255 centmetros. 4. El DNI de la persona es un valor de 32 bits sin signo ya que se consideran 7 cifras numricas. 5. El nombre de la persona es una cadena de caracteres de N dgitos. Queda claro que se tienen tres tipos diferentes de variable. Los tres primeros son del tipo unsigned char, el cuarto es unsigned long y el ltimo un puntero a una cadena de caracteres tipo char. Cmo crear una variable que englobe estos tres tipos de datos distintos? Una estructura es la solucin a este problema. Basta con declararla de la siguiente manera: struct{ unsigned char edad; unsigned char peso; unsigned char talla; unsigned long DNI; char *nombre; }Persona; La variable Persona es una estructura y contiene cinco campos. Los tres primeros ocupan 1 byte cada uno, mientras que el cuarto ocupa 4 bytes y el ltimo campo, al ser un puntero a 8 bits ocupa solo dos bytes. En total, la estructura Persona requiere de 9 bytes de la memoria SRAM. Para hacer uso de la variable Persona solo basta declarar la estructura e invocar cada uno de sus campos. En una estructura el campo se especifica colocando el nombre de la variable, un punto y luego el nombre del campo. Por ejemplo, para actualizar la altura de la variable Persona se ejecuta la instruccin: Persona.talla = 170. En el Programa 8.23 se muestra un ejemplo de creacin de una estructura y la actualizacin de sus campos.

Programa 8.23. Ejemplo de declaracin y actualizacin de datos de una estructura


struct{ unsigned char edad; unsigned char peso; unsigned char talla; unsigned long DNI; char *nombre; }Persona; char name[] = "Sergio Salas A."; void main() { Persona.edad= 31; Persona.peso= 80; Persona.talla= 180; Persona.DNI = 40765319; Persona.nombre = &name[0]; while(1); }

Una de las ventajas que ofrece la estructura es la posibilidad de declarar campos de longitudes variables. Es decir, se pueden crear campos de un bit, dos bits, tres bits o cualquier tamao a definir hasta un mximo de 8 bits. El formato para determinar el tamao del campo es colocar el nombre de este seguido de dos puntos ( : ) y el valor en bits. Cabe mencionar que este procedimiento solo es vlido en el mbito de una estructura. Por ejemplo, se tiene la siguiente declaracin de estructura: struct{ char bit0: 1; char bit1: 1; char bit2: 1; char bit3: 1; char bit4: 1;

char bit5: 1; char bit6: 1; char bit7: 1; }_bits; La variable _bits posee 8 campos internos, cada campo tiene un bit de tamao. Esto es interesante ya que la estructura _bits solo ocupa 1 byte y presenta 8 campos: uno para cada bit de dicho byte. El bit menos significativo (bit0) se declara primero, mientras que el bit ms significativo (bit7) se declara al final. Si esta estructura se introduce en la memoria de datos del PIC18, entonces solo ocupara una nica direccin. En el Programa 8.24 se muestra un ejemplo de manipulacin de una estructura. Programa 8.24. Ejemplo de manejo de estructuras con campos de 1 bit de longitud
#pragma udata ESTRUCTURA = 0x100 struct{ char bit0: 1; char bit1: 1; char bit2: 1; char bit3: 1; char bit4: 1; char bit5: 1; char bit6: 1; char bit7: 1; }_bits; char car; #pragma udata char *p; void main() { _bits.bit0 = 1; _bits.bit1 = 1; _bits.bit2 = 0; _bits.bit3 = 0; _bits.bit4 = 0; _bits.bit5 = 0;

_bits.bit6 = 0; _bits.bit7 = 1; p=&car; p--; car = *p; while(1); }

En el Programa 8.24 se muestra una estructura de nombre _bits cuyos 8 campos son de 1 bit de longitud. Esta estructura se encuentra declarada en la direccin 0x100 de la memoria de datos, con lo cual todo la estructura _bits se encuentra alojada en esta direccin. Por tal motivo, la siguiente variable declarada: car ocupa la direccin siguiente o 0x101. Posteriormente, se culmina la directiva #pragma y se declara el puntero *p en la direccin por defecto. Cuando el programa inicia, se actualizan los 8 campos binarios de la estructura _bits con los siguientes valores 100000112. Esto significa que la direccin 0x100 de la memoria de datos automticamente (despus de la actualizacin del campo _bits.bit7) contiene el valor 0x83. Luego, el puntero p toma la direccin de la variable car, que es 0x101. Luego, el puntero se auto decrementa, apuntando a la direccin 0x100. Finalmente, la variable car toma el valor apuntado por el puntero p, lo que implica el contenido de la direccin 0x100 que contiene el valor 0x83. Ahora analice el Programa 8.26 que es una copia fiel del Programa 8.25. Se ha incluido un pequeo cambio que corresponde a un campo ms a la estructura: bit8y9:2. Qu cambios ocurrirn en el programa? Programa 8.26. Ejemplo de manejo de estructuras con ms de ocho campos de 1 bit
#pragma udata ESTRUCTURA = 0x100 struct{ char bit0: 1; char bit1: 1; char bit2: 1; char bit3: 1; char bit4: 1; char bit5: 1; char bit6: 1; char bit7: 1; char bit8y9: 2;

}_bits; char car; #pragma udata char *p; void main() { _bits.bit0 = 1; _bits.bit1 = 1; _bits.bit2 = 0; _bits.bit3 = 0; _bits.bit4 = 0; _bits.bit5 = 0; _bits.bit6 = 0; _bits.bit7 = 1; _bits.bit8y9 = 2; p=&car; p--; car = *p; while(1); }

Ahora, los resultados son diferentes. En primer lugar, la estructura _bits posee un campo adicional: bit8y9 que es de 2 bits, con lo cual toda la variable ocupa 10 bits de longitud. Debido a esto, la estructura deber ocupar dos direcciones de la memoria de datos. Los primeros 8 bits menos significativos ocuparn toda la direccin 0x100, mientras que el campo _bits.bit8y9 ocupar los dos bits menos significativos de la direccin 0x101. Qu ocurre con los 6 bits ms significativos de la direccin 0x101? Pues, mediante la estructura no es posible acceder a ellos. Tendra que utilizarse un puntero para poder apuntar a dicha direccin y modificar el valor de 8 bits de esa posicin. En el programa principal se vuelve a cargar la direccin 0x100 con el valor 0x83 y el campo _bits.bit8y9 se carga con 2 (102), lo cual modifica el contenido de los dos bits menos significativos de la posicin 0x101. Los 6 bits ms significativos de esta direccin permanecen en 0000002, con lo cual el valor obtenido es 0x02. Luego, la variable car ocupa la direccin 0x102, con lo cual al tomar

el puntero p su direccin y ejecutar el auto decremento, se apuntar la direccin 0x101. Cuando car tome el valor contenido en esa direccin recibir 0x02 en vez de 0x83. Por otro lado, Qu pasara si se quisiera tener acceso a cada bit de un registro y al mismo tiempo poder acceder a todo el valor de dicho registro? En los ejemplos mostrados en los Programas 8.26 y 8.25 se vio que para tal efecto se tena que reasignar la direccin de la estructura a un puntero. Pero, esto requiere de carga computacional adicional. Las uniones son otro tipo de dato del ANSI C que contribuyen a solucionar el problema presentado. Observe el siguiente programa en lenguaje ensamblador: movlw 0x88 movwf LATD bsf LATD,0 Se puede observar de esta rutina en ensamblador que el registro LATD ha sido accedido de dos formas diferentes. Primero, se ha cargado el valor 0x88 en todo su registro de 8 bits de longitud. Luego, solo se ha afectado el bit LSB, cargndose el nivel 12 y modificando la cifra completa del registro de 0x88 a 0x89. Este mismo efecto se puede lograr haciendo uso de una unin. Las uniones en ANSI C presentan una sintaxis muy parecida a la estructura. Observe la siguiente unin: #pragma udata ESTRUCTURA = 0x100 union{ unsigned char a; unsigned char b; int dato;

}campos; #pragma udata

La variable campos es una unin de tres elementos: uno de 8 bits de nombre a, otro de 8 bits de nombre b y un campo de 16 bits de nombre dato. En una unin, los campos declarados ocupan el mismo espacio en memoria. Es decir, a y b al ser de la misma longitud toman posicin de la direccin 0x100 de la memoria de datos. En cambio el campo dato, al ser de 16 bits debe ocupar dos direcciones. Su parte baja ocupa la direccin 0x100, mientras que sus 8 bits ms significativos ocupan la posicin 0x101, tal como se observa en el esquema de la Figura 8.3. De tal manera, acceder a la variable a o b implica reutilizar la misma direccin de memoria. De igual forma, la parte baja del campo dato apunta a la misma direccin. Esto ofrece una gran ventaja, ya que como se ver ms adelante, se podr programar un cdigo equivalente al visto en la rutina de ensamblador de la pgina anterior. Observe el Programa 8.27. En este se crean los tres campos: a, b y dato. En la funcin principal main() se asignan tres valores diferentes a cada campo. Al final, cada asignacin sobrescribe el valor de la variable anterior. Por ejemplo, la asignacin de campos.b reemplaza al valor del campos.a y finalmente, el byte menos significativo de campos.dato reemplaza a las dos variables anteriores con el valor 0x34.

Figura 8.3. Esquema de asignacin de direcciones a los campos de una unin en la memoria de datos.

Programa 8.27. Ejemplo de manipulacin de campos de una unin


#pragma udata ESTRUCTURA = 0x100 union{ unsigned char a; unsigned char b; int dato; }campos; #pragma udata unsigned char car; void main() { campos.a = 0x50; campos.b = 0x12; campos.dato = 0x7434; car = campos.a; while(1); }

Es as que al finalizar el programa, la variable campos.a = 0x34, campos.b = 0x34, campos.dato = 0x7434 y car = 0x34. Lo interesante resulta al combinar la estructura y la unin. Regresando al ejemplo en lenguaje ensamblador. Suponga que se quiere crear una variable de nombre PUERTOB cuyos campos son: _Byte tipo char y RB0, RB1, RB2, RB3, RB4, RB5, RB6 y RB7 cada uno de un 1 bit. Cuando uno de los campos de un bit se actualiza, inmediatamente esto se ve reflejado en el campo _Byte. Eso es lo mismo que ocurre en el ejemplo mostrado en ensamblador. Observe el Programa 8.28 en donde se muestra este ejemplo. Programa 8.28. Ejemplo de combinacin de una estructura y una unin
#pragma udata ESTRUCTURA = 0x100 union{ struct{ char RB0:1; char RB1:1; char RB2:1;

char RB3:1; char RB4:1; char RB5:1; char RB6:1; char RB7:1; }_bits; char _Byte; }PUERTOB; #pragma udata unsigned char car; void main() { PUERTOB._Byte = 0xA5; PUERTOB._bits.RB1 = 1; PUERTOB._bits.RB7 = 0; car = PUERTOB._Byte; while(1); }

En el programa se observa que la variable PUERTOB es declarada como una unin. Esta presenta dos campos internos: _Byte y la estructura _bits. Al estar ambos campos dentro de la unin y al tener la misma dimensin (la estructura presenta 8 campos de 1 bit cada uno, es decir 8 bits en total) ocupan la misma direccin en la memoria de datos (definida en el valor 0x100). En el programa principal, se asigna el valor 0xA5 al campo _Byte de la variable PUERTOB con lo cual la direccin 0x100 se carga con este valor. Luego, el campo RB1 de la estructura _bits se pone a nivel 12, con lo cual el valor de la direccin 0x100 cambia a 0xA7. Luego, el campo RB7 de la estructura se pone a 02, quedando como valor final el valor 0x27 el cual se carga en la variable car. El uso de esta combinacin de estructuras y uniones es muy til para diversas aplicaciones en las cuales se quieren manipular los bits de un registro de modo independiente. Esto es muy til para aplicaciones de conversin serial a paralelo, como se ver ms adelante en la aplicacin de interfaz con un teclado PS/2 o tambin en el siguiente captulo cuando se vea la deteccin de teclas pulsadas de un control remoto infrarrojo para televisin.

8.9. TIPOS DE VARIABLES COMPUESTOS Para mejorar la legibilidad de un programa muchas veces es necesario aclarar los tipos de variable a usar. Por ejemplo, el tipo int se sabe representa una variable de 16 bits. Queda claro esto para un programador que hace uso de otro compilador para programar otra marca de sistema embebido? La respuesta es no. Lo ms probable es que el programador dude si el tipo int representa 8 o 16 bits. Para dejar en claro qu se est declarando se puede hacer uso del operador typedef. Al redefinir un tipo de variable podemos dejar muy en claro que se quiere representar. Por ejemplo, si queremos declara una variable num de tipo int y se quiere dejar muy presente que esta variable es de 16 bits con signo se puede hacer lo siguiente: typedef int _16bits_con_signo; _16bits_con_signo num; Entonces, queda claro que la variable num corresponde a un tipo de tamao de 16 bits con signo. El programador sabr de acuerdo a su experiencia cual es el tipo de dato que corresponde a estas caractersticas y de esta manera tendr una comprensin ms clara del programa. De la misma manera, las uniones y estructuras se pueden convertir a nuevos tipos compuestos, de tal manera que una estructura muy necesitada se puede definir a travs del operador typedef y de esta manera crear nuevas variables. Suponga que se tienen conectados a cada uno de los pines RB0, RB1, RB2 y RB3 un sensor magntico que opera como alarma de seguridad para cuatro ventanas. Cuando una de las ventanas se abre, el sensor enva un nivel 12 al pin respectivo.Dependiendo de la ventana (ventana 1, 2, 3 o 4) se requiere generar un cdigo de alarma de 4 bits que ser transmitido a un dispositivo externo que recibe este valor binario a travs de los pines RB4, RB5, RB6 y RB7 del PIC18F4550. Si la alarma es detectada por el pin RB0 el cdigo a enviar es 0x08, si se detecta por RB1 el cdigo ser 0x0E, si se detecta en el pin RB2 el cdigo es 0x0A y por el pin RB3 el cdigo ser 0x02. En el Programa 8.29 se muestra un ejemplo. Observe que se crea un nuevo tipo de variable compuesto por una estructura dentro de una unin. Los campos de la estructura presentan un nombre asociado a su funcin, lo cual hace ms entendible el programa.

Programa 8.29. Ejemplo de un tipo de dato compuesto para una aplicacin de deteccin de alarmas
typedef union { struct{ char alarma1:1; char alarma2:1; char alarma3:1; char alarma4:1; char cod_alarma:4; }info_alarma; char estado; }alarmas; alarmas puertob; void main() { TRISB = 0x0F; while(1) { puertob.estado = PORTB; if(puertob.info_alarma.alarma1 = 1) puertob.info_alarma.cod_alarma = 8; elseif(puertob.info_alarma.alarma2 = 1) puertob.info_alarma.cod_alarma = 14; elseif(puertob.info_alarma.alarma3 = 1) puertob.info_alarma.cod_alarma = 10; elseif(puertob.info_alarma.alarma4 = 1) puertob.info_alarma.cod_alarma = 2; else puertob.info_alarma.cod_alarma = 0; LATB = puertob.estado; } }

Você também pode gostar