Está en la página 1de 31

Estructuras

Una estructura es una colección de una o más variables de tipos posiblemente diferentes,
agrupados bajo un solo nombre para un manejo conveniente. Las estructuras se conocen como
“records” en algunos otros lenguajes, principalmente Pascal. Las estructuras ayudan a organizar
datos complicados, en particular dentro de programas grandes debido a que, le permiten a un
grupo de variables relacionadas tratarlas como una unidad en lugar de hacerlo como entidades
separadas.

Un ejemplo tradicional de estructura es el registro de una nómina: un empleado está descripto por
un conjunto de atributos tales como nombre, domicilio, número de CUIL, salario, etc. Algunos de
estos atributos pueden, a su vez, ser estructuras: un nombre tiene varios componentes, como los
tiene un domicilio y, aún un salario. Otro ejemplo más típico en C procede de las gráficas: un
punto es un par de coordenadas, un rectángulo es un par de puntos y, otros casos semejantes.

El principal cambio realizado por el estándar ANSI C es la definición de la asignación de estructuras


– las estructuras se pueden copiar y asignar, pasar a funciones y ser regresadas por ellas. Esto se
manejó así por muchos compiladores durante varios años pero, las propiedades están ahora
definidas en forma precisa. Las estructuras y los arreglos automáticos ahora también pueden
inicializarse.

Conceptos básicos sobre estructuras

Definamos algunas estructuras propias para graficación. El objeto básico es un punto, del cual
supondremos que tiene una coordenada x y, una coordenada y, ambas enteras.

Los dos componentes pueden colocarse en una estructura declarada así:

struct punto{
int x;
int y;
};

La palabra reservada struct presenta la declaración de una estructura, que es una lista de
declaraciones entre llaves. Un nombre optativo, llamado rótulo de estructura, puede seguir a la
palabra struct (como aquí lo hace punto). El rótulo da nombre a esta clase de estructura y, en
adelante, puede utilizarse como una abreviatura para la parte de declaraciones entre llaves.

Las variables nombradas dentro de la estructura se llaman miembros. Un miembro de estructura o


rótulo y una variable ordinaria (esto es, no miembro) pueden tener el mismo nombre sin conflicto

1
puesto que, siempre se pueden distinguir por el contexto. Además, en diferentes estructuras
pueden encontrarse los mismos nombres de miembros, aunque por cuestiones de estilo se
deberían de usar los mismos nombres sólo para objetos estrechamente relacionados.

Una declaración struct define un tipo. La llave derecha que termina la lista de miembros puede
ser seguida por una lista de variables, como se hace para cualquier tipo básico, Esto es,

struct punto{...}x, y, z;

Es sintácticamente análogo a

int x, y, z;

En el sentido de que cada proposición declara a x,y y z, como variable del tipo nombrado y
causa que se les reserve espacio contiguo.

Una declaración de estructura que no está seguida por una lista de variables no reserva espacio de
almacenamiento sino que, simplemente describe una plantilla o la forma de una estructura. Sin
embargo, si la declaración está rotulada, el rótulo se puede emplear posteriormente en
definiciones de instancias de la estructura. Por ejemplo, dada la declaración anterior de punto,

struct punto pt;

Define una variable pt que es una estructura de tipo struct punto. Una estructura se puede
inicializar al seguir su definición con una lista de inicializadores, cada uno mediante una expresión
constante, para los miembros:

struct punto maxpt={320, 200};

Se hace referencia a un miembro de una estructura en particular, en una expresión, con una
construcción de la forma: nombre-estructura.miembro.

El operador miembro de estructura “.” conecta al nombre de la estructura con el nombre del
miembro. Por ejemplo, para imprimir las coordenadas del punto pt,

printf("%d, %d", pt.x, pt.y);

O para calcular la distancia del origen (0,0) a pt,

double dist, sqrt(double);


dist=sqrt((double)pt.x * pt.x + (double) pt.y * pt.y);

2
Una estructura automática también se puede inicializar por asignación o llamando a una función
que regresa una estructura del tipo adecuado.

// Estructura arbitraria que consume


// un cantidad de memoria no trivial
typedef struct
{
int x;
int y;
char *z;
int a[4];
}initStruct;

static initStruct staticStruct = {1,2,"Hello", {3,4,5,6}};


// initAuto es una estructura "readonly" usada para inicializar
// autoStruct, se inicializa en tiempo de compilación
//si las variables de tipo struct no necesitan reinicializarse cada vez que son
//usadas conviene declararlas static
static initStruct initAuto = {7,8,"World",{9,10,11,12}};
/* Si necesito crear una variable de tipo estructura automática
puedo inicializarla con los valores de la estática recién creada (este tipo de
variables se inicializa en tiempo de ejecución, para ello se
genera una secuencia de asignaciones campo a campo, esta operación puede ser
relativamente rápida pero puede consumir bastante memoria en caso de estructuras muy
grandes), esta es una forma de reducir el tamaño de memoria usada en la
inicialización de una estructura automática*/
initStruct autoStruct = initAuto;

Las estructuras pueden anidarse. Una representación de un rectángulo puede definirse mediante
un par de puntos que denotan las esquinas diagonalmente opuestas:

struct rect{
struct punto pt1;
struct punto pt2;
};

La estructura rect contiene dos estructuras punto. Si declaramos pantalla como:

struct rect pantalla;

3
Entonces,

pantalla.pt1.x

Se refiere a la coordenada x del miembro pt1 de pantalla.

Estructuras y funciones

Las únicas operaciones legales sobre una estructura son: copia o asignación como unidad, tomar
su dirección con & y tener acceso a sus miembros. La copia y la asignación incluyen pasarlas como
argumentos a funciones y también regresar valores de funciones. Las estructuras no se pueden
comparar. Una estructura se puede inicializar con una lista de valores constantes de miembros;
una estructura automática también se puede inicializar con una asignación.

Investiguemos las estructuras escribiendo algunas funciones para manipular puntos y rectángulos.
Hay por lo menos tres acercamientos posibles: pasar separadamente los componentes, pasar una
estructura completa o pasar un puntero a ella. Cada uno tiene sus puntos buenos y malos.

La primera función, creapunto, toma dos enteros y regresa una estructura punto:

/*creapunto: crea un punto con coordenadas x e y */


struct punto creapunto (int x, int y)
{
struct punto temp;
temp.x=x;
temp.y=y;
return temp;
}

Nótese que no hay conflicto entre el nombre del argumento y el miembro con el mismo nombre;
incluso la reutilización de los nombres refuerza el vínculo.

creapunto ahora se puede usar para inicializar dinámicamente cualquier estructura o, para
proporcionar los argumentos de la estructura a una función:

struct rect pantalla;


struct punto medio;
struct punto creapunto(int, int);

pantalla.pt1=creapunto(0,0);
pantalla.pt2=creapunto(XMAX,YMAX);
medio=creapunto((pantalla.pt1.x +
pantalla.pt2.x)/2,(pantalla.pt1.y+pantalla.pt2.y)/2);

El siguiente paso es un conjunto de funciones para hacer operaciones aritméticas sobre los
puntos.

4
Por ejemplo,

/*sumapuntos: suma dos puntos*/


struct punto sumapuntos(struct punto p1, struct punto p2)
{
p1.x+=p2.x;
p1.y+=p2.y;
return p1;
}

Aquí, tantos los argumentos como el valor de retorno son estructuras. Incrementamos los
componentes en p1, en lugar de utilizar explícitamente una variable temporal, para hacer énfasis
en que los parámetros de la estructura son pasados por valor como cualquier otro.

Como otro ejemplo, la función enrectangulo prueba si un punto está dentro de un rectángulo,
donde hemos adoptado la convención de que un rectángulo incluye sus lados izquierdo e inferior
pero no sus lados superior y derecho:

/*enrectangulo: regresa 1 si p está en r, 0 sino lo está*/


int enrectangulo (struct punto p, struct rect r)
{
return p.x>=r.pt1.x && p.x<r.pt2.x && p.y >=r.pt1.x && p.y<r.pt2.y;
}

Esto supone que el rectángulo está representado en una forma estándar en donde las
coordenadas pt1 son menores que las coordenadas pt2. La siguiente función regresa un
rectángulo, garantizando que está en forma canónica:

#define min(a,b) ((a) < (b) ? (a) : (b))


#define max(a,b) ((a) > (b) ? (a) : (b))

/*canonrect: pone en forma canónica las coordenadas de un rectángulo*/


struct rect canonrect(struct rect r)
{
struct rect temp;
temp.pt1.x=min(r.pt1.x, r.pt2.x);
temp.pt1.y=min(r.pt1.y, r.pt2.y);
temp.pt2.x=max(r.pt1.x, r.pt2.x);
temp.pt2.x=max(r.pt1.x, r.pt2.x);
return temp;
}

Si una estructura grande va a pasarse como argumento a una función, generalmente es más
eficiente pasar un puntero que copiar la estructura completa. Los punteros a estructuras se
comportan como los punteros a variables ordinarias.

5
La declaración

struct punto *pp;

Dice que pp es un puntero a una estructura de tipo struct punto. Si pp apunta a una estructura
punto, *pp es la estructura y (*pp).x y (*pp).y son los miembros. Para emplear pp, se podría
escribir, por ejemplo,

struct punto origen, *pp;


pp=&origen;
printf("El origen es (%d, %d)\n", (*pp).x, (*pp).y);

Los paréntesis son necesarios en (*pp).x debido a que la precedencia del operador miembro de
estructura “.”, es mayor que la de *. La expresión *pp.x significa *(pp.x), lo cual es ilegal
debido a que x no es un puntero.

Los punteros a estructuras se usan con tanta frecuencia que se ha proporcionado una notación
alternativa como abreviación. Si p es un puntero a estructura, entonces

p->miembro de estructura

se refiere al miembro en particular. (El operador -> es un signo menos seguido inmediatamente
por >). De esta manera podríamos haber escrito

printf("El origen es (%d, %d)\n", pp->x, pp->y);

Tanto el operador “.”, como el “->” se asocian de izquierda a derecha, de modo que si tenemos

struct rect r, *rp=r;

Entonces estas cuatro expresiones son equivalentes:

r.pt1.x
(r.pt1).x
rp->pt1.x
(rp->pt1).x

Los operadores de estructuras “.” y, “ ->”, junto con () para llamadas a funciones y [] para
subíndices, están arriba de la jerarquía de precedencias y se asocian estrechamente. Por ejemplo,
dada la declaración

struct {
int len;
char *str;
}*p;

6
Entonces

++p->len

Incrementa a len, no a p, puesto que los paréntesis implícitos son ++(p->len). Los paréntesis
se pueden emplear para alterar la asociación: (++p)->len incrementa a p, antes de tener acceso
a len, y (p++)->len incrementa a p después del acceso (Este último conjunto de paréntesis es
innecesario).

De la misma manera, *p->str obtiene cualquier cosa a la que str apunte; *p->str++
incrementa a str después de acceder a lo que apunta (exactamente como *s++); (*p->str)++
incrementa cualquier cosa a la que str apunte; y *p++->str incrementa a p después de acceder
a lo que str apunta.

Pueden ver un ejemplo de uso de punteros y estructuras en


http://c.learncodethehardway.org/book/ex16.html,
http://www2.elo.utfsm.cl/~lsb/elo320/labs/tiposbasicos/estructuras_fechas.c y, otro de uso de
las dos notaciones posibles para acceder a los miembros de la estructura a través de punteros
(extraído de http://iie.fing.edu.uy/ense/asign/str/curso-c):

#include <stdio.h>
#include <math.h>

struct punto {
int x;
int y;
};
struct rectangulo {
struct punto pto_a;
struct punto pto_b;
};

double area (struct rectangulo *);


double per (struct rectangulo *);

void main()
{
struct rectangulo r1 = { 3, 4, 5, 6 };

printf("\n\nESTRUCTURAS: rectangulo (pto_a, pto_b)\n");


printf("Punto pto-a: x=%d, y=%d\n", r1.pto_a.x, r1.pto_a.y);
printf("Punto pto-b: x=%d, y=%d\n", r1.pto_b.x, r1.pto_b.y);
printf("Area del rectangulo: %f\n", area( &r1 ));
printf("Perimetro del rectangulo: %f\n\n", per( &r1 ));
getchar();
}

7
/* calculo del area */
double area(struct rectangulo *p)
{
return ( abs ((*p).pto_b.x - (*p).pto_a.x ) *
abs( (*p).pto_b.y - (*p).pto_a.y ) );
}

/* calculo del perimetro */


double per(struct rectangulo *p)
{
return ( 2 * ( abs (p->pto_b.x - p->pto_a.x ) +
abs( p->pto_b.y - p->pto_a.y ) ) );
}

Arreglos de estructuras

Consideremos escribir un programa, extraído de


http://www.ib.cnea.gov.ar/~servos/CursoC/estructuras.htm, para mantener la información
necesaria para describir un intervalo de la recta real y, la cantidad de cuentas que caen en ese
intervalo como para armar un histograma.

#include <stdlib.h>
#include <stdio.h>

struct Intervalo
{
double Lower, /* Límite inferior del intervalo */
Upper; /* Límite superior del intervalo */
int Counter; /* Cantidad de puntos dentro */
};

#define NUM_INTER 100

main()
{
struct Intervalo Inter[NUM_INTER];
int Index;
double Delta = RAND_MAX/NUM_INTER;

/* Inicialización del vector de estructuras */


for (Index=0; Index<NUM_INTER; Index ++)
{
Inter[Index].Counter = 0;
Inter[Index].Lower = Delta*Index;
Inter[Index].Upper = Delta*(Index+1);
}

for (Index=0; Index<10000; Index++)


Inter[(int)(rand()/Delta)].Counter++;

/* impresión de resultados */
for (Index=0; Index<NUM_INTER; Index++)
printf("[%lf,%lf]=%d\n", Inter[Index].Lower,
Inter[Index].Upper, Inter[Index].Counter);}

8
La declaración struct Intervalo Inter[NUM_INTER] define un arreglo Inter de
estructuras de tipo Intervalo y reserva espacio de almacenamiento para 100 elementos. Cada
elemento del arreglo es una estructura. Esto también se podría escribir como

struct Intervalo
{
double Lower, /* Límite inferior del intervalo */
Upper; /* Límite superior del intervalo */
int Counter; /* Cantidad de puntos dentro */
} Inter[NUM_INTER];

Veamos este otro ejemplo:

#include <stdio.h>
#include <stddef.h>
struct Student {
int id;
char name[32];
};
int main() {
struct Student s[] = {{101, "John"},{102, "Doe"},{103, "Peter"}};
int i, num;
num = sizeof(s)/sizeof(struct Student);
printf("El numero de elementos del vector es: %d\n", num);
for(i=0; i<num; i++) {
printf("s[%d].id = %d\n", i, s[i].id);
printf("s[%d].name = %s\n", i, s[i].name);
}

return 0;
}

Como puede verse la inicialización del arreglo Student es exactamente análogo al de cualquier
otro tipo, es decir, la definición seguida por una lista de inicializadores entre llaves:

struct Student s[] = {{101, "John"},{102, "Doe"},{103, "Peter"}};

Los inicializadores se listan en parejas correspondientes a los miembros de las estructuras. Las
llaves internas no son necesarias cuando los inicializadores son variables simples o cadenas de
caracteres y, cuando están todos presentes:

struct Student s[] = {101, "John",102, "Doe",103, "Peter"};

Otro ejemplo más cercano al hardware, el de un arreglo de datos asociados a determinados


sensores de presión:

struct SENSOR_DESC
{
unsigned char gain ;
unsigned char offset ;
unsigned char temp_coeff ;
unsigned char span ;
unsigned char amp_gain ; };

9
Que podemos agrupar en arreglos de sensores que comparten esta información e inicializarlos con
valores hexadecimales:

struct SENSOR_DESC sensor_database[4] =


{
{0x20,0x40,0x50,0xA4,0x21},
{0x33,0x52,0x65,0xB4,0x2F},
{0x30,0x50,0x48,0xC4,0x3A},
{0x32,0x56,0x56,0xC5,0x28}
} ;

Como es usual, el número de entradas en un arreglo se calculará si los inicializadores están


presentes y el [ ] se deja vacío. El número de elementos del arreglo puede, en el ejemplo de los
estudiantes, contarse manualmente pero es mucho más fácil y seguro que lo haga la máquina,
especialmente si la lista de estudiantes está sujeta a cambios.

El tamaño en bytes de un arreglo está completamente determinado en tiempo de compilación, el


mismo es el tamaño de una entrada multiplicado por el número de elementos que contiene, así
que el número de elementos es, para este ejemplo,

Tamaño de s / tamaño de struct Student

C proporciona un operador unario en tiempo de compilación llamado sizeof que se puede


emplear para calcular el tamaño de cualquier objeto. Las expresiones

sizeof objeto y sizeof (nombre de tipo)

dan un entero igual al tamaño en bytes del tipo u objeto especificado. Estrictamente, sizeof
produce un valor entero sin signo cuyo tipo size_t está definido en el archivo de cabecera
<stddef.h>. Un objeto puede ser una variable, arreglo o estructura. Un nombre de tipo puede
ser el de un tipo básico como int o double o, un tipo derivado como una estructura o un
puntero.

En nuestro caso el número de elementos es el tamaño del arreglo dividido entre el tamaño de un
elemento. Este cálculo se puede usar en una proposición #define:

#define NUMELEMEN (sizeof s/sizeof (struct Student))

Otra forma de calcularlo es dividiendo el tamaño del arreglo por el tamaño de un elemento
específico:

#define NUMELEMEN (sizeof s / sizeof s[0])

Esto tiene la ventaja de que no necesita modificarse si el tipo cambia.

10
Punteros a estructuras

Para ilustrar algunas de las consideraciones involucradas con apuntadores y arreglos de


estructuras, veamos el ejemplo de uso de estructuras para manejo de matrices (arreglos de dos
dimensiones) usando arreglos de una dimensión, extraído de
http://www.ic.unicamp.br/~ra069320/PED/MC102/1s2008/Apostilas/Cap08.pdf y modificado:

#include <stdio.h>
#include <stdlib.h>

struct matriz {
int lin;
int col;
int* v;
};

struct matriz* crea (int m, int n)


{
struct matriz* mat = (struct matriz*) malloc(sizeof(struct matriz));
mat->lin = m;
mat->col = n;
mat->v = (int*) malloc(m*n*sizeof(int));
return mat;
}

void libera (struct matriz* mat)


{
free(mat->v);
free(mat);
}

int accede (struct matriz* mat, int i, int j)


{
int k; /* índice del elemento del vector */
if (i<0 || i>=mat->lin || j<0 || j>=mat->col) {
printf("Acesso no valido!\n");
exit(1);
}
k = i*mat->col + j;
return mat->v[k];
}

void valorelemento (struct matriz* mat, int i, int j, int v)


{
int k; /* índice del elemento del vetor */
if (i<0 || i>=mat->lin || j<0 || j>=mat->col) {
printf("Operacion no valida!\n");
exit(1);
}
k = i*mat->col + j;
mat->v[k] = v;
}
void main()
{
int a=2, b=2,i,j;

11
struct matriz *p=crea(2,2);//matriz identidad
valorelemento(p,0,0,1);
valorelemento(p,0,1,0);
valorelemento(p,1,0,0);
valorelemento(p,1,1,1);
for(i=0;i<a;i++){
for(j=0;j<b;j++)
printf("%d ",accede(p,i,j));
printf("\n");
}
libera(p);
}

Si p es un apuntador a una estructura, la aritmética con p toma en cuenta el tamaño de la


estructura, así p++ incrementa p con la cantidad correcta para obtener el siguiente elemento del
arreglo de estructuras. Sin embargo, no hay que suponer que el tamaño de una estructura es la
suma de los tamaños de sus miembros. Debido a requisitos de alineación para diferentes objetos,
podría haber “huecos” no identificados dentro de una estructura. Así, por ejemplo, si un char es
de un byte y un int de cuatro bytes, la estructura

struct {

char c;

int i;

};

Bien podría requerir 8 bytes, no cinco. El operador sizeof regresa el valor apropiado. En general,
una instancia de una estructura tiene el alineamiento impuesto por el campo o miembro más
ancho (en términos de sizeof). Los compiladores lo hacen así, como la forma más simple de
asegurarse que todos los miembros de la estructura estén auto alineados para obtener un acceso
más rápido a los mismos y, que implique menos código de máquina.

En C la dirección de una estructura se corresponde con aquella del primer campo o miembro; si se
tiene un campo cuya longitud en bytes sea mayor que la del siguiente miembro, se añaden bytes
de relleno (internal padding), lo mismo se hace luego del último campo (trailing padding) para
lograr el alineamiento de la estructura con el ancho de la palabra del procesador. Una forma de
reducir el tamaño de una estructura se obtiene, si se puede, reordenando los campos de mayor a
menor tamaño.

Por ejemplo, la siguiente estructura utilizada para representar una lista enlazada:

struct foo10 {
char c;//1 byte
struct foo10 *p;//4 bytes
short x;//2 bytes
};

12
Ocupa 12 bytes al ejecutarla en una PC, añadiendo 5 bytes de relleno; si reordenamos sus campos
de mayor a menor tamaño:

struct foo11 {
struct foo11 *p;
short x;
char c;
};

Ocupa 8 bytes, y se añade un solo byte de relleno.

Typedef

C proporciona una facilidad llamada typedef para crear nuevos tipos de datos. Por ejemplo, la
declaración

typedef int Longitud;

hace del nombre Longitud un sinónimo de int. El tipo Longitud puede emplearse en
declaraciones, casts, etc., exactamente de la misma manera en que lo podría ser int.

Longitud len, maxlen;

Longitud *lengths[];

De modo semejante, la declaración

typedef char *Cadena;

hace a Cadena un sinónimo para char* o puntero a carácter, que puede usarse en declaraciones
y casts:

Cadena p, lineptr[MAXLINES], alloc(int);

int strcmp(Cadena, Cadena);

p= (Cadena) malloc (100);

Nótese que el tipo que se declara en un typedef aparece en la posición de un nombre de


variable, no justo después de la palabra typedef. Sintácticamente typedef es como las clases de
almacenamiento extern, static, etc. Hemos empleado nombres con mayúscula para los
typedef para destacarlos.

Se debe destacar que una declaración typedef no crea un nuevo tipo en ningún sentido;
simplemente agrega un nuevo nombre para algún tipo ya existente. Tampoco representa alguna
nueva semántica: las variables declaradas de esta manera tienen exactamente las mismas
propiedades que las variables cuyas declaraciones se escriben explícitamente. En efecto, typedef
es como #define, excepto que al ser interpretado por el compilador puede realizar sustituciones
textuales que están más allá de las capacidades del preprocesador. Por ejemplo,

13
typedef int (*AAF)(char *, char *);

crea el tipo AAF, de puntero a función con dos argumentos de tipo char * y que retorna un int,
el cual puede usarse en contextos como

AAF strcmp;

Además de las razones puramente estéticas, hay dos razones principales para emplear typedef.
La primera es parametrizar un programa contra los problemas de portabilidad. Si se emplea
typedef para tipos de datos que pueden ser dependientes de la máquina, cuando un programa
se traslada, sólo los typedef requieren de cambios. Una situación común es usar nombres de
typedef para varias cantidades enteras y, entonces hacer un conjunto apropiado de selecciones
de short, int y long para cada máquina. Tipos como size_t de la biblioteca estándar son
ejemplos.

Los registros asociados a periféricos en sistemas embebidos suelen estar localizados en


direcciones fijas de memoria, por ello es muy común utilizar algunos campos de relleno dentro de
la estructura para ajustarse a dichas direcciones, tal es caso del campo control en esta estructura

typedef unsigned int uint16_t;


typedef struct
{
uint16_t count; /* Offset 0 */
uint16_t maxCount; /* Offset 2 */
uint16_t _reserved1; /* Offset 4 */
uint16_t control; /* Offset 6 */
} volatile timer_t;

En este tipo de aplicaciones es muy común el uso de operadores a nivel de bits:

timer_t *pTimer = (timer_t *)(0xABCD0123);

if (pTimer->control & 0x08)//chequeo bits


pTimer->control |= 0x10; //pongo a uno algunos bits
pTimer->control &= ~(0x04);//poner a 0 algunos bits
pTimer->control ^= 0x80;//conmuto algunos bits

El segundo propósito de typedef es proporcionar mejor documentación para un programa –un


tipo llamado Treeptr puede ser más fácil de entender que uno declarado sólo como un apuntador
a una estructura complicada, como puede ser un árbol binario.

14
Este último ejemplo pertenece a la librería de línea de comandos de un microcontrolador y
permite controlar programas y dispositivos con simples comandos de texto de manera similar a la
terminal de linux y la linea de comandos de Windows. Los comandos pueden transmitirse al
microcontrolador mediante un puerto serie, un socket TCP/IP o una conexión USB. Es interesante
puesto que incorpora un campo con el nombre del comando (a través de un puntero) y el nombre
una función que será invocada cuando se ingrese un comando:

#define MAX_COMMAND_LEN (10)


#define COMMAND_TABLE_SIZE (4)
typedef struct
{
char const *name;
void (*function)(void);
} command_t;

void commandsHelp(void);
void commandsLed(void);
void commandsBuzzer(void);

command_t const gCommandTable[COMMAND_TABLE_SIZE] =


{
{"HELP", commandsHelp,},//Los commandos se reciben por Puerto
serie
{"LED", commandsLed, },
{"BUZZER", commandsBuzzer, },
{NULL, NULL }
};

Estructuras autoreferenciadas

Veamos el siguiente ejemplo (extraído de https://eva.fing.edu.uy/course/view.php?id=637) de uso


de estructuras que, contienen miembros que son punteros a la misma estructura (ojo que es ilegal
que una estructura contenga una instancia de sí misma), en este caso, para armar una armar una
matriz como si fuese una pila (el último elemento ingresado es el primero que extraigo)
conteniendo un carácter en cada posición de la misma.

//archivo lista_celdas.h

#ifndef LISTA_CELDAS_H
#define LISTA_CELDAS_H

//cada posición en la matriz indicado por fila columna


typedef struct {
unsigned fila;
unsigned columna;
} posicion_t;

15
//la celda contiene una posición en la matriz y el caracter que almacena
typedef struct {
posicion_t posicion;
char simbolo;
} celda_t;
//arma la matriz con nodos que contienen una celda y un puntero a la siguiente celda
typedef struct nodo{
celda_t celda;
struct nodo * siguiente;//declaración recursiva de nodo
} nodo_t;

typedef nodo_t * lista_t;


typedef nodo_t * lpos_t;
//crea lista vacía
lista_t crear_lista();
//agrega nueva celda a la lista
void agregar_elemento(celda_t celda, lista_t * lista);
void imprimir_lista(lista_t);

#endif

//archivo lista_celdas.c

#include "lista_celdas.h"
#include <stdlib.h>
#include <stdio.h>
lista_t crear_lista(){
return NULL;
}

void agregar_elemento(celda_t celda, lista_t * ptrlista){


//espacio para un nuevo nodo
nodo_t * nuevo = (nodo_t *) malloc (sizeof (nodo_t));
nuevo->celda = celda;
//el siguiente nodo es el que está en la lista, para el primer nodo es NULL
nuevo->siguiente = *ptrlista;
//guardo en la lista el nuevo nodo
*ptrlista = nuevo;
}

void imprimir_lista(lista_t lista){


//trabajo con punteros
lpos_t pos = lista;
//recorre la lista desde el último ingresado hasta el primero ingresado
(pila)
while (pos != NULL) {
celda_t celda = pos->celda;
posicion_t posicion = celda.posicion;
printf("[fila=%d, col=%d, simbolo=%c]", posicion.fila, posicion.columna,
celda.simbolo );
pos = pos->siguiente;
}
printf("\n");

16
//archivo main.c

#include "lista_celdas.h"
#include <stdio.h>

int main() {
celda_t celda;
lista_t lista = crear_lista();
celda.posicion.fila = 1;
celda.posicion.columna = 2;
celda.simbolo = '*';
agregar_elemento(celda, &lista);
celda.posicion.fila = 3;
celda.posicion.columna = 2;
celda.simbolo = 'o';
agregar_elemento(celda, &lista);
imprimir_lista(lista);
return 0;
}

Uniones

Una unión es una variable que puede contener (en momentos diferentes) objetos de diferentes
tipos y tamaños y, el compilador hace el seguimiento del tamaño y requisitos de alineación. Las
uniones proporcionan una forma de manipular diferentes clases de datos dentro de una sola área
de almacenamiento, sin incluir en el programa ninguna información dependiente de la máquina.

El uso más común de uniones en sistemas embebidos es para permitir un rápido acceso a bytes
individuales de un int o long. Un ejemplo podría ser el de contadores de tiempo real de 16 o 32
bits:

union clock
{
long real_time_count ; // Reservo 4 bytes
int real_time_words[2] ; // Reservo 4 bytes como un array de int
char real_time_bytes[4] ; // Reservo 4 bytes un array de char
}c;

Otro ejemplo adaptado de


http://www.freescale.com/files/soft_dev_tools/doc/white_paper/CWPORTINGWP.pdf, muestra
cómo escribir código que sea más portable usando directivas del preprocesador que tienen en
cuenta la forma de ordenamiento de los bytes en memoria (endianness).

#ifdef INTEL
#define ENDIAN 0
#else
#define ENDIAN 1
#endif
#define VERSIONMASK 0x0F

17
union {
short headerFlags;
unsigned char flagByte[2];
} flagsBuff;

flagsBuff.headerFlags = 0x3132;
/* Directive fixes byte-ordering */
versionNumber = flagsBuff.flagByte[ENDIAN ? 0 : 1] & VERSIONMASK;

Este es el propósito de una “unión” –una sola variable que puede legítimamente guardar uno de
varios tipos. La sintaxis se basa en las estructuras.

La variable será suficientemente grande como para mantener al mayor de los tipos: el tamaño
específico depende de la implantación. Cualquiera de estos tipos puede ser asignado a c y,
después empleado en expresiones mientras que el uso sea consistente: el tipo recuperado debe
ser el tipo que se almacenó más recientemente. Es responsabilidad del programador llevar el
registro del tipo que está almacenado actualmente en una unión; si algo se almacena como un
tipo y se recupera como otro, el resultado depende de la implantación.

Las uniones, aparte de usarse para simbolizar diferentes representaciones de datos, se usan para
almacenar información condicionada, por ejemplo:

typedef union {
int unidades;//artículos que se venden por unidad
float kgs;//artículos que se venden por peso
} cantidad;

Sintácticamente, se tiene acceso a los miembros de una unión con

Nombre-unión.miembro

O puntero-unión->miembro

Precisamente como a las estructuras. En el siguiente ejemplo se tiene una variable utipo que se
emplea para llevar el registro del tipo actualmente almacenado en la unión, normalmente esta
variable se chequea en estructuras switch/case o if/else-if if/else

#include <stdio.h>
#include <string.h>

#define INT 1
#define FLOAT 2
#define STRING 3

union u_tag { /* estructura de uni¢n */


int ival;
float fval;
char *sval;
};

18
void impru(union u_tag, int);

void main()
{

union u_tag udato; /* udato es variable de tipo u_tag */


int utipo; /* para indicar el tipo de miembro */

printf("\n\nTipo union. Impresi¢n de distintos tipos de datos.\n");

utipo = INT;
udato.ival = 35;
impru(udato, utipo);

utipo = FLOAT;
udato.fval = 5.123;
impru(udato, utipo);

utipo = STRING;
strcpy (udato.sval, "Esto es una cadena");
impru(udato, utipo);

/* imprime seg£n el tipo de par metro */


void impru(union u_tag udato, int utipo)
{
if (utipo == INT)
printf(" Valor entero: %d\n", udato.ival);
else if (utipo == FLOAT)
printf("Valor punto flotante: %f\n", udato.fval);
else if (utipo == STRING)
printf(" Valor cadena: %s\n", udato.sval);
else
printf("ERROR: tipo no asignado\n");

return;
}

Las uniones pueden presentarse dentro de estructuras y arreglos y, viceversa. La notación para
tener acceso a un miembro de una unión en una estructura (o viceversa) es idéntica a la de las
estructuras anidadas. Por ejemplo, dos formas distintas de representar una fecha del presente
año,

typedef union {
char date[25];

struct {
short int month;
short int day;
} dayValues;
} DAY;

Una función que usa esta estructura, a modo de ejemplo para apreciar cómo se accede a los
miembros de la misma

19
#define YEAR 2016
typedef union {
char fecha[25];

struct {
short int mes;
short int dia;
} dayValues;
} DAY;
void convertDays(DAY *days) {
int loop, cantfechas;
short int dia, mes;
const char *nombresmes[] = {"enero","febrero","marzo","abril","mayo","junio",
"julio","agosto","septiembre","octubre",
"noviembre","diciembre"};
cantfechas=sizeof(days)/sizeof(DAY);
// ciclo a través de los días del array y almaceno la fecha como string
for (loop = 0; loop < cantfechas; loop++) {
dia = days[loop].dayValues.dia;
mes = days[loop].dayValues.mes;

sprintf(days[loop].fecha,"%s %d, %d",nombresmes[mes],dia,YEAR);


}
}

Una unión es una estructura en la cual todos los miembros tienen un desplazamiento cero a partir
de la base, la estructura es suficientemente grande para mantener al miembro “más ancho” y, la
alineación es la apropiada para todos los tipos de la unión. Están permitidas las mismas
operaciones sobre las uniones como sobre las estructuras: asignación o copia como una unidad,
tomar la dirección y, hacer el acceso a un miembro.

ANSI C provee una macro cuya definición está en stddef.h llamada offsetof() que retorna el offset
(la distancia desde el comienzo de la estructura) de un miembro dentro de una estructura o unión;
su implementación no es 100% portable. En el siguiente ejemplo usamos la macro para saber
dónde reside el campo b.h dentro de la union llamada UFO:

#include <stdio.h>
#include <stddef.h>

typedef union
{
int i;//todos los campos de la unión tienen offset cero
float f;
char c;
struct
{
float g;
double h;//en PC offset de h es 8
} b;

} UFOO;

20
void main()
{
printf("Offset of 'h' is %u", offsetof(UFOO, b.h));

Una unión sólo se puede inicializar con un valor del tipo de su primer miembro, así que la unión
DAY descripta anteriormente sólo se puede inicializar con un valor que sea un array de char de
longitud 25.

Es muy común el uso de uniones emparejado con un valor discreto (discriminador o etiqueta) que
indique el miembro activo de la unión, a este tipo de estructura se lo suele llamar unión
discriminada o etiquetada (tagged). La etiqueta normalmente es una enumeración aunque podría
ser un tipo integral. Veamos el siguiente ejemplo ilustrativo

typedef struct Flight {


enum { PASSENGER, CARGO } type;//vuelo de pasajeros o de cargas
union {
int npassengers;//nro.de pasajeros
double tonnages; // las unidades de carga, pueden no ser
toneladas
} cargo;
} Flight;

Flight flights[ 1000 ];

flights[ 42 ].type = PASSENGER;


flights[ 42 ].cargo.npassengers = 150;

flights[ 20 ].type = CARGO;


flights[ 20 ].cargo.tonnages = 356.78;

Campos de bits

Cuando el espacio de almacenamiento es escaso, puede ser necesario empaquetar varios objetos
dentro de una sola palabra de máquina; un uso común es un conjunto de banderas de un bit como

typedef struct permisos {


unsigned int lectura :1;
unsigned int escritura :1;
unsigned int ejecucion :1;
} permisos_t;

La forma usual en que esto (codificar información con un conjunto de banderas de un bit dentro
de un char o int) se realiza es definiendo un conjunto de “máscaras” correspondientes a las
posiciones relevantes de bits, como en:

21
#define LECTURA 1
#define ESCRITURA 2
#define EJECUCION 4
O

enum {LECTURA=1,ESCRITURA=2,EJECUCION=4};

El acceso a los bits viene a ser cosa de “jugar” con los operadores de corrimiento,
enmascaramiento y complemento.

int i = LECTURA | EJECUCION;

Aunque estas expresiones se dominan fácilmente como alternativa, C ofrece la capacidad de


definir y tener acceso a campos de una palabra, más directamente que por medio de operadores
lógicos de bits. Un campo de bits, o simplemente campo, es un conjunto de bits adyacentes dentro
de una unidad de almacenamiento definida por la implantación, al que llamaremos “palabra”. La
sintaxis para la definición y acceso a campos está basada en estructuras.

Esto define una variable llamada permisos_t que contiene tres campos de un bit. El número
que sigue al carácter dos puntos representa el ancho del campo en bits. Los campos son
declarados unsigned int para asegurar que sean cantidades sin signo. Los campos individuales son
referidos en la misma forma que para otros miembros de estructuras. Los campos se comportan
como pequeños enteros y pueden participar en expresiones aritméticas, como lo hacen otros
enteros.

#include <stdio.h>

typedef struct permisos {


unsigned int lectura :1;
unsigned int escritura :1;
unsigned int ejecucion :1;
} permisos_t;

#define LECTURA 1
#define ESCRITURA 2
#define EJECUCION 4

int main() {
struct permisos p = {1,0,1};
int i = LECTURA | EJECUCION;
printf("%ld\n", sizeof(permisos_t));
printf ("p=%u,%u,%u\n", p.lectura, p.escritura,
p.ejecucion);
printf ("i=%u,%u,%u,%u\n", i & LECTURA , i & ESCRITURA, i
& EJECUCION,i);
return 0;
}
Otro ejemplo, extraído de http://www.csee.wvu.edu/~cukic/CS350/Spring98/C_Ch10.txt:

22
#include <stdio.h>

struct bitCard {//juego de cartas españolas


unsigned face : 4;//puedo representar hasta 16 nros, en este caso se necesita
sólo hasta 13
unsigned suit : 2;//representa hasta 4, palo de la carta
unsigned color : 1;//puedo representar dos colores
};

typedef struct bitCard Card;

void fillDeck(Card *);


void deal(Card *);

main()
{
Card deck[52];//mazo de 52 cartas

fillDeck(deck);
deal(deck);

return 0;
}

void fillDeck(Card *wDeck)


{
int i;

for (i = 0; i <= 51; i++) {


wDeck[i].face = i % 13;
wDeck[i].suit = i / 13;
wDeck[i].color = i / 26;
}
}

/* Imprime info cartas en formato de columnas */


/* Columna 1 contiene las cartas con valor de 0-25 subscriptas con k1 */
/* Columna 2 contiene las cartas con valor de 26-51 subscriptas con k2 */

void deal(Card *wDeck)


{
int k1, k2;

for (k1 = 0, k2 = k1 + 26; k1 <= 25; k1++, k2++) {


printf("Card:%3d Suit:%2d Color:%2d ",
wDeck[k1].face, wDeck[k1].suit, wDeck[k1].color);
printf("Card:%3d Suit:%2d Color:%2d\n",
wDeck[k2].face, wDeck[k2].suit, wDeck[k2].color);
}
}

Casi todo acerca de los campos es dependiente de la implantación. El que un campo pueda
traslapar el límite de una palabra se define por la implantación. Los campos no necesitan tener
nombres; los campos sin nombre (dos puntos y su amplitud solamente) se emplean para llenar
espacios. El ancho especial 0 puede emplearse para obligar a la alineación al siguiente límite de
palabra.

23
Los campos se asignan de izquierda a derecha en algunas máquinas y de derecha a izquierda en
otras. Esto significa que aunque los campos son útiles para el mantenimiento de estructuras de
datos definidas internamente, la pregunta de qué punta viene primero tiene que considerarse
cuidadosamente cuando se seleccionan datos definidos externamente; los programas que
dependen de tales cosas no son portables. Se denomina endianness al atributo de un sistema que
indica si un dato multibyte (por ejemplo, enteros, flotantes, etc.) se almacena en memoria con su
byte más significativo en las direcciones de memoria más bajas o en las más altas. En el primer
caso se dice que la arquitectura del procesador es de tipo big endian y en el segundo se trata de
una tipo little endian. El problema es similar a los idiomas en los que se escriben de derecha a
izquierda, como el árabe, o el hebreo, frente a los que se escriben de izquierda a derecha, pero
trasladado de la escritura al almacenamiento en memoria de los bytes. Esta nomenclatura
proviene de la novela Los viajes de Gulliver de Jonathan Swift, donde estos nombres
correspondían a dos facciones guerreras liliputienses y, se puede entender como "de comienzo
por el extremo pequeño" (Little endian) y "de comienzo por el extremo mayor" (big endian),
aunque es propenso a confundirse con "acaba en pequeño" y "acaba en grande" respectivamente.
Su etimología proviene de un juego de palabras en inglés con los términos compuestos little-end-
in y big-end-in.

Usando este criterio el sistema big-endian adoptado por Motorola y en la mayoría de las
arquitecturas RISC, consiste en representar los bytes en el orden "natural": así el valor
hexadecimal 0x4A3B2C1D se codificaría en memoria en la secuencia {4A, 3B, 2C, 1D}. En el sistema
little-endian adoptado por Intel y varias plataformas con arquitecturas CISC, el mismo valor se
codificaría como {1D, 2C, 3B, 4A}, de manera que de este modo se hace más intuitivo el acceso a
datos, porque se efectúa fácilmente de manera incremental de menos relevante a más relevante
(siempre se opera con incrementos de contador en la memoria), en un paralelismo a "lo
importante no es como empiezan las cosas, sino como acaban."

En el siguiente ejemplo extraído de http://www.ibm.com/developerworks/aix/library/au-


endianc/:

#include <stdio.h>
#include <string.h>

int main (int argc, char* argv[]) {


FILE* fp;
struct {
char one[4];
int two;
char three[4];
} data;

/* Inicializo estructura */
strcpy (data.one, "foo");
data.two = 0x01234567;
strcpy (data.three, "bar");

/* Escribo datos a un archivo binario */


fp = fopen ("output", "wb");

24
if (fp) {
fwrite (&data, sizeof (data), 1, fp);
fclose (fp);
}
}

Se copian los datos de una estructura en un archivo binario, el mismo puede compilarse
perfectamente en distintas máquinas pero la salida que produce es diferente según el endianness,
usando la herramienta hexump para examinar el contenido del archivo binario:

En una máquina big endian

00000000 66 6f 6f 00 12 34 56 78 62 61 72 00
|foo..4Vxbar.|
0000000c
Y en una Little endian

00000000 66 6f 6f 00 78 56 34 12 62 61 72 00
|foo.xV4.bar.|
0000000c

Si el contenido del archivo se envía de una máquina que maneja big endian a otra que maneja
Little endian, se leería el contenido en orden inverso al grabado. Como otro ejemplo de la
importancia de determinar el orden de almacenamiento de variables multibytes, considérese la
siguiente estructura correspondiente a manejo de paquetes de datos del protocolo USB:

struct setup {
char bmRequestType, bRequest;
short wValue, wIndex, wLength;
};

Bajo este protocolo los datos son transferidos e interpretados en un orden little-endian. Si esta
estructura forma parte de un programa escrito para una máquina Intel, posteriormente es portado
a una arquitectura ARM de tipo big endian, se deberían intercambiar los valores de a bytes tal
como ocurriría si se tendría redefinida de esta forma la estructura:

struct setup {
short wLength, wIndex, wValue;
char bRequest, bmRequestType;
};

lo cual puede implicar realizar un trabajo importante cambiando el código.


En http://www.ibm.com/developerworks/aix/library/au-endianc/ se indican distintas formas de
revertir el orden de los bytes que componen una variable, por ejemplo, para el caso de un entero
proponen, entre otras, el uso de un puntero a un arreglo de caracteres, lo cual puede extrapolarse
a otro tipo de datos:

short reverseInt (char *c) {


int i;
char *p = (char *)&i;

25
if (is_bigendian()) {
p[0] = c[0];
p[1] = c[1];
p[2] = c[2];
p[3] = c[3];
} else {
p[0] = c[3];
p[1] = c[2];
p[2] = c[1];
p[3] = c[0];
}

return i;
}

En el código anterior se utiliza una forma sencilla de determinar la endianness, chequeando la


disposición en memoria de una constante predefinida. Se sabe que la disposición de una variable
de 32 bits de valor 1 es 00 00 00 01 en big-endian and 01 00 00 00 para little-endian. Por tanto,
podemos escribir

const int i = 1;
#define is_bigendian() ( (*(char*)&i) == 0 )

En el caso de protocolos de comunicaciones y sus stacks, deben definir su propia endianness, de


otra forma dos nodos de distintas endianness serían incapaces de comunicarse correctamente.
Por ejemplo, todas las capas del protocolo TCP/IP son big endian; todos los valores transmitidos,
tales como una dirección de IP, un checksum, etc., deben enviarse y recibirse comenzando por el
byte más significativo. Si una PC cuya dirección de IPv4 es 192.0.1.2 (esta notación decimal con
puntos debe trasladarse a un entero de 32 bits) desea comunicarse sobre Internet con un servidor
basado en SPARC. Sin ninguna manipulación adicional, el procesador 80x86 de la PC convertirá
esta dirección en el entero Little endian 0x020100C0 y transmitirá los bytes en el orden 02 01 00
C0, del otro lado el servidor SPARC recibirá los bytes en el orden 02 01 00 C0, reconstruirá los
bytes en el entero big endian 0x020100c0 y malinterpretará la dirección de IP como 2.1.0.192. Si el
stack TCP/IP se ejecuta en un procesador Little endian, en tiempo de ejecución deberían
reordenarse los bytes de cada campo de dato multi-byte. De forma tal que la pila de protocolos
sea portable en tiempo de compilación debería de alguna forma decidirse si se reordenan o no los
distintos bytes de datos. Existen distintas funciones que realizan estas conversiones de orden de
los bytes de un host a un orden requerido por la red:

#include <netinet/in.h>

htons() /* Orden de Host a Orden de Red (short) */

htonl() /* Orden de Host a Orden de Red (long) */

ntohs() /* Orden de Red a Orden de Host (short) */

ntohl() /* Orden de Red a Orden de Host (long) */

Los campos sólo se pueden declarar como enteros; por portabilidad, se debe especificar
explícitamente si son signed o unsigned, salvo los de longitud uno que, sólo pueden ser

26
unsigned. No son arreglos y no tienen direcciones, de modo que el operador & no puede
aplicarse a ellos.

Los formatos de datos impuestos externamente, como interfaces hacia dispositivos de hardware,
frecuentemente requieren la capacidad de tomar partes de una palabra. Un ejemplo extraído de
http://balaiotecnologico.blogspot.com.ar/2010/11/trabalhando-com-campos-de-bits.html, utiliza
campos de bits para representar los datos procedentes de una balanza electrónica que
implementa un protocolo específico y, transmite a través de un puerto serie dos bytes
representando en uno de ellos el estado actual y en el otro el peso que está sobre ella. La imagen
ilustra el significado de cada bit presente en uno de los dos bytes indicando su estado, el cual será
representado mediante campos de bits; para representar los datos de la balanza se usa una
estructura común:

typedef struct
{
unsigned int flagDecimal : 3; /* Ocupa 3 bits */
unsigned int pesoNegativo : 1; /* Ocupa 1 bit */
unsigned int pesoEmMovto : 1;
unsigned int saturacao : 1;
unsigned int sobrecarga : 1;
unsigned int bit7 : 1;
} TWStatus1;

typedef struct
{
char STX;
double peso;
double tara;
TWStatus1 status;
char ETX;
} TWDadosBalanca;

Normalmente se usan uniones conjuntamente con campos de bits para representar y acceder a los
registros de hardware. En la figura extraída de

27
http://www2.electron.frba.utn.edu.ar/~afurfaro/Clases/Info1/Clase22-Unions.pdf se puede
observar una hoja de datos describiendo un ejemplo de este tipo de registros

En https://vidaembebida.wordpress.com/2014/01/07/libreria-para-manejo-de-bitfields-campos-
de-bits-avr-gcc/ podemos apreciar el uso de campos de bits para el manejo de los registros de un
microcontrolador ATmega16. En este ejemplo se conectan 4 leds a los 4 bits más significativos del
puerto B (PB0-PB3) y 2 push buttons conectados la PB4 y PB5 del mismo puerto B, al presionar un
push button se desplazará el led encendido de derecha a izquierda, y al presionar el otro se
desplazará en sentido contrario.

#define F_CPU 8000000L

#include <avr/io.h>
#include <util/delay.h>
#include "sfrbits.h"

//estructura definida para manipular los Leds


typedef union Leds_t{
byte value : 4;

struct{
byte led1 : 1; //para el led 1. PB0
byte led2 : 1; //para el led 2. PB1
byte led3 : 1; //para el led 3. PB2
byte led4 : 1; //para el led 4. PB3
byte btn1pull : 1; //para activar pull-up en el boton 1.
byte btn2pull : 1; //para activar pull-up en el boton 12
byte : 1; //sin uso.
byte : 1; //sin uso.
};}Leds;

28
//estructura definida para manipuilar los botones
typedef struct Btns_t{
byte : 1; //sin uso.
byte : 1; //sin uso.
byte : 1; //sin uso.
byte : 1; //sin uso.
byte left : 1; //Para el boton a la izquierda.
byte right : 1; //para el boton a la derecha.
byte : 1; //sin uso.
byte : 1; //sin uso.
}Btns;

#define LEDS SFR_STRUCT_BITS(Leds,PORTB) //Mapeamos el PORTB a la estructura Leds


#define BTNS SFR_STRUCT_BITS(Btns,PINB) //Mapeamos el PORTB a la estructura Btns

int main(void)
{
byte valant;
//establecemos la direccion de los pines como salida para los leds
//es equivalente a DDRBbits.NLOW=0xF
DDRBbits.B0=OUTPUT;
DDRBbits.B1=OUTPUT;
DDRBbits.B2=OUTPUT;
DDRBbits.B3=OUTPUT;

//establecemos la direccion de los pines como entradas para los botones


//es equivalente a DDRBbits.NHIGH=0x3
DDRBbits.B4=INPUT;
DDRBbits.B5=INPUT;

//Activamos pull-ups en los botones


LEDS.btn1pull=HIGH;
LEDS.btn2pull=HIGH;
LEDS.value=0; //apagamos todos los leds.
LEDS.led1=HIGH; //encendemos el primer led

while(1) //cilco infinito


{

valant=LEDS.value; //guardamos el valor de los


leds
while(BTNS.left==LOW && BTNS.right==LOW) //mientras se presionan los
dos botones
LEDS.value=0xF; //encendemos todos los leds
LEDS.value=valant;

//desplazamos a la izquieda
if(BTNS.left==LOW && LEDS.led4==LOW)
{
LEDS.led4=LEDS.led3;
LEDS.led3=LEDS.led2;
LEDS.led2=LEDS.led1;
LEDS.led1=0;
}
//desplazamos a la derecha
/*https://github.com/alfreedom/Vida-Embebida/blob/master/Librerias%20AVR/sfrbits.h*/

29
if(BTNS.right==LOW && LEDS.led1==LOW)
{
LEDS.led1=LEDS.led2;
LEDS.led2=LEDS.led3;
LEDS.led3=LEDS.led4;
LEDS.led4=0;
}

_delay_ms(50); //retardo anti-rebotes


}

return 0;
}

En el caso de sistemas embebidos es muy común usar las uniones conteniendo campos de bits
para hacer el código más portable permitiendo el acceso al registro completo:

typedef unsigned char uint8_t;


#define TIMER_COMPLETE (0x08)
#define TIMER_ENABLE (0xC0)

union
{
uint8_t byte;//representa timer status register
struct
{
uint8_t bit0 : 1;
uint8_t bit1 : 1;
uint8_t bit2 : 1;
uint8_t bit3 : 1;
uint8_t nibble : 4;
} bits;
} foo;

En lugar de acceder a los bits individuales el registro representado mediante esta unión puede
escribirse como un todo, para ello es muy útil combinar el uso de unión con operadores a nivel de
bits:

foo.byte = (TIMER_COMPLETE | TIMER_ENABLE);

En http://www.sase.com.ar/2012/files/2012/09/Prog-en-C-para-sistemas-embebidos-en-
MSP430.pdf se aconseja no usar campos de bits de tamaño mayor a 1. Por ejemplo dada la
siguiente estructura de campos de bits

union bits{
struct{
unsigned int b1:1;
unsigned int b2:2;
unsigned int b3:3;
unsigned int b4:4;
unsigned int b6:6;
}b;
unsigned int w;
}bf;

30
Las operaciones sobre campos de un bit pueden requerir una sola instrucción de máquina

En C: bf.b.b1 = 1;
En assembler: bis.w #0x1,&bf

Las operaciones sobre campos de más de un bit requieren varias instrucciones de máquina. En C:

bf.b.b3 = 5;
En assembler:
mov.w &bf,R15 ; copia
and.w #0xFFC7,R15 ; borra
bis.w #0x28,R15 ; setea
mov.w R15,&bf ; copia

La misma operación implementada sin campos de bits es más eficiente. En C:

w &= 0x00c7; // borra 3 bits


w |= 5 << 3; // escribe el 5
En assembler:
and.w #0xC7,&w ; borra
bis.w #0x28,&w ; escribe

31

También podría gustarte