Professional Documents
Culture Documents
Sistemas embebidos
Apunte de C embebido
La intencin de este apunte es brindar un breve repaso sobre ciertas caractersticas del
lenguaje de programacin C haciendo principal hincapi en las metodologas y formas que
sern utilizadas al programar sistemas embebidos con microcontroladores.
1. Preprocesador y Macros
Uno de los temas que queda mencionado muy superficialmente en los cursos bsicos de
programacin en C son las caractersticas del preprocesador y sus directivas. El
preprocesador de C es el primer programa invocado por el compilador y tiene ms o
menos su propio lenguaje el cual puede ser una herramienta muy poderosa para el
programador. Todas las directivas de preprocesador o comandos inician con un # como
por ejemplo #include, #define, #ifdef, #ifndef, etc.
Las ventajas que tiene usar el preprocesador son:
Los programas son ms fciles de desarrollar.
Son ms fciles de leer.
Son ms fciles de modificar.
El cdigo de C es ms transportable entre diferentes arquitecturas.
Particularmente, en nuestro caso mencionaremos las propiedades de las directivas
#include y #define, dejando de lado las directivas de compilacin condicional.
1.1 Directiva include
La directiva del preprocesador #include instruye al compilador para incluir otro archivo
fuente. El archivo fuente que se leer se debe encerrar entre comillas dobles o parntesis
de ngulo. Cuando se indica <archivo> se le dice al compilador que busque donde estn
Pgina 1 de 22
Se expande a:
a = 3.14 * c + 3 * c + 3
que, por culpa de la precedencia de operadores (ver precedencia), es equivalente a
a = (3.14 * c) + (3 * c) + 3
en lugar de expandir a:
a = 3.14 * (c + 3) * (c + 3)
que es lo que queramos.
Uno de los usos ms frecuentes de las macros que tendremos en la prctica con sistemas
embebidos es la funcin para determinar la posicin de un determinado bit dentro de un
registro, pero este tema ser mencionado en la seccin de Operaciones de Bits.
Pgina 3 de 22
2. Precedencia de operadores
Una expresin est compuesta por operadores, variables y constantes. Para simplificar,
podemos pensar que la forma en la que C evala esta expresin es dividiendo el todo en
subexpresiones. Las reglas que definen que subexpresin evaluar primero, se denominan
reglas de precedencia. Aunque siempre podemos alterar dichas reglas mediante la
utilizacin de parntesis. En la siguiente tabla detallamos la precedencia entre los
operadores de C.
2.1 Asociatividad
En una expresin tal como 3 * 4 + 5 el compilador realiza primero la multiplicacin
(por tener el operador * mayor precedencia) y luego la suma, por tanto, produce 17. Para
forzar un orden en las operaciones se deben utilizar parntesis. 3 * (4 + 5) produce
27, ya que 4 + 5 se realiza en primer lugar.
Pgina 4 de 22
3. Operaciones de Bits
Aunque normalmente no se suelen utilizar, es bueno conocer cmo actan estos
operadores y de cuales disponemos.
En la mayor parte de un programa para un sistema embebido con un microcontrolador
nos interesar manipular datos a nivel de bit; por ejemplo activar o desactivar flags. Un flag
es una variable que puede tomar 2 valores, por lo que se suele representar con un bit.
Debido a que en C no existen tipos predefinidos de un bit, lo que se suele hacer es agrupar
hasta ocho flags en una variable de tipo carcter (char).
Para acceder a estos flags o simplemente para activarlos es necesario utilizar operadores a
nivel de bit.
Pgina 5 de 22
La razn por la cual debemos utilizar esta mascara es que no podemos leer el pin B1 de
forma individual ya que solamente podemos acceder al estado de los 8 bits del puerto
simultneamente.
El operador OR (|): El operador OR compara dos bits; si cualquiera de los dos bits es 1,
entonces el resultado es 1; en otro caso ser 0. Ejemplo:
i1 = 0x47 01000111
i2 = 0x53 01010011
i1 | i2 = 0x57 01010111
El operador XOR (^): El operador OR exclusivo o XOR, dar como resultado un 1 si
cualquiera de los dos operandos es 1, pero no los dos a la vez. Ejemplo:
i1 = 0x47 01000111
i2 = 0x53 01010011
i1 ^ i2 = 0x14 00010100
Uno de los usos ms comunes de este operador es en lo que se llama Toggling de bits.
Por ejemplo si queremos que un bit de un registro cambie de estado, pero que el mismo
dependa del estado anterior (si estaba en 0 pase a 1 y viceversa). En el siguiente ejemplo
hacemos un toggling de un pin en puerto de salida.
Pgina 6 de 22
Pgina 7 de 22
! En este caso es importante aclarar que tanto el nombre del registro DDRx
los valores PDx se encuentran previamente asignados mediante una
directiva #define incluidas en la librera avr/io.h.
Utilizando la macro _BV():
#define _BV(bit) (1 << (bit))
Otro ejemplo: queremos que el puerto B se configure con los pines 3, 2 y 1 como
entradas.
Utilizando byte completo
DDRB & = 0xF1;
Utilizando el operador desplazamiento 1<<XXX
DDRB & = ~ ((1<<PD3) l (1<<PD2) l (1<<PD1));
Utilizando la macro _BV():
DDRB & = ~ (_BV(PD3) l _BV(PD2) l _BV(PD1));
Pgina 8 de 22
! Es importante tener en cuenta que no todos los registros de configuracin son siempre
direccionables bit a bit, esto hace que deba generarse una variable de 8 bits en espejo
para luego mover los datos de configuracin de una sola vez hacia el registro.
Al igual que con la igualdad hay que tener especial cuidado con los operadores && y , ya
que si ponemos slamente un & o un | , nos estamos refiriendo a un and o un or a
nivel de bit, por lo que el cdigo puede que no haga lo que queremos (o que algunas veces
lo haga y otras veces no).
4.2 Operadores Relacionales
Al igual que en matemticas, estos operadores nos permitirn evaluar las relaciones
(igualdad, mayor, menor, etc.) entre un par de operandos (en principio, pensemos en
nmeros). Los operadores relacionales de los que disponemos en C son:
Pgina 9 de 22
Pgina 10 de 22
Operacin
(x
(x
(x
(x
(x
(x
(x
(x
(x
(x
&&
&
ll
l
==
!=
<
>
<=
>=
Resultado
y)
y)
y)
y)
y)
y)
y)
y)
y)
y)
True
False
True
True
False
True
True
False
True
False
Donde:
1. char por lo menos de tamao suficiente para el conjunto de caracteres bsico
2. short al menos de 16 bits
3. long al menos de 32 bits
Con el fin de evitar conflictos en los tamaos de las variables al traspasar cdigo desde
una plataforma a otra, el estndar C99 (Es la revisin de 1999 de C) incluy los
siguientes tipos de datos:
int8_t, int16_t, int32_t, int64_t - uint8_t, uint16_t, uint32_t, uint64_t
Pgina 11 de 22
Pgina 12 de 22
Escribir:
if ((b < 0) && (-b > a)) { };
7. Modificadores
7.1 Modificador static
La caracterstica de las variables de perder su valor al salir del bloque en que han sido
definidas, es un serio inconveniente en algunas ocasiones. Para resolver el problema se
inventaron las variables estticas, un tipo especial de variable que no fuese destruida
cuando la ejecucin saliese de su mbito y que conservase su valor. Este nuevo tipo se
declara mediante el modificador de tipo de almacenamiento static, con lo que se
previene que una variable pierda su valor cuando se sale del bloque en que se ha definido.
Pgina 13 de 22
Si por error escribimos cdigo que la modifica, el compilador emite una advertencia, y
podemos repararlo sin que cause ms problemas. Por lo tanto, los const no son
imprescindibles, pero hacen que la verificacin esttica sea ms potente.
Pgina 14 de 22
8. Punto Flotante
Los nmeros en coma flotante son usados, habitualmente, para aproximar valores
analgicos y contnuos, debido a que ofrecen una mayor resolucin que los enteros. Las
variables tipo float tienen el valor mximo 3.4028235E+38, y como mnimo pueden
alacanzar el -3.4028235E+38. Ocupan 4bytes (32bits).
Los floats tienen una precisin de 6 o 7 dgitos decimales. Esto significa el nmero total de
dgitos, no el nmero a la derecha de la coma decimal. Al contrario que en otras
plataformas, donde tu podras obtener mayor precisin usando una variable tipo double
(por ejemplo, por encima de 15 dgitos), en la plataforma de sistemas embebidos de 8 bits
los double tienen el mismo tamao que los float.
Los nmeros en coma flotante no son exactos, y muchos proporcionan falsos resultados
cuando son comparados. Por ejemplo, 6.0 / 3.0 puede no ser igual a 2.0. Debes comprobar
que el valor absoluto de la diferencia entre los nmeros pertenezca a un rango pequeo.
La matemtica en coma flotante es mucho ms lenta que la matemtica de enteros para
realizar operaciones, por lo que deberas evitarla si, por ejemplo, un bucle tiene que
ejecutarse a la mxima velocidad para funciones con temporizaciones precisas.
A continuacin se muestra una tabla con los tiempos resultantes de realizar la
multiplicacin algebraica de dos valores con distintos tipos de variables.
Variable
char
int
long int
float
Tiempo [us]
1,2
1,5
9,5
210
Pgina 15 de 22
Pgina 17 de 22
int *p_entero;
int entero1, entero2;
entero1 = 2;
entero2 = 5;
p_entero = &entero1;
entero1 = *p_entero + entero2;
Del mismo modo podemos asignar un valor a la variable a la que apunta el puntero, de la
forma:
int *p_entero;
int entero1, entero2;
entero1 = 2;
entero2 = 5;
p_entero = &entero1;
*p_entero = entero1 + entero2;
Este ltimo cdigo ejecutara exactamente lo mismo que el anterior. Debemos notar que
no tendra sentido prescindir de la lnea 5, puesto que estaramos intentando introducir el
valor entero1 + entero2 en una direccin de memoria que no conocemos, puesto que no
le habramos dado valor a p_entero.
Pgina 18 de 22
10.
Asignacin de memoria
La mayora de los dispositivos con los cuales trabajaremos tienen una cantidad mnima de
RAM. Los microcontroladores compatibles con la plataforma C vienen con un minimo de
128 bytes de RAM. Esta memoria, debe compartirse entre las variables inicializadas y no
inicializadas (secciones .data y .bss), la asignacin dinmica de memoria, y la pila que se
usa para alojar variables locales (automticas) durante el llamado a subrutinas.
A diferencia de arquitecturas ms grandes, no hay un manejo de memoria realizado por
hardware, el cual ayudara a separar las regiones mencionadas anteriormente evitando
que se sobrescriban unas en otras.
La disposicin general de RAM es primero alojar las variables .data al comienzo de la RAM
interna, siguiendo con .bss. La pila se inicializa con el valor tope de la RAM interna,
despazndose (creciendo) hacia abajo. La porcin disponible llamada "heap", dedicada
para la asignacin dinmica de memoria se ubicar al final de la seccin .bss. Por lo tanto,
no hay riesgo de que la memoria dinmica en algn momento colisione con las variables
RAM (a menos de que hayan errores en la implementacin de la asignacin de lugares).
Pero an hay riesgo de que la seccin heap y de pila puedan colisionar si hay grandes
requerimientos tanto de espacio de memoria dinmica como de espacio en pila. sta
adems puede darse con requerimientos de espacios que no sean grandes pero que las
asignaciones se encuentren fragmentadas de tal modo que a lo largo del tiempo, nuevos
requerimientos de memoria dinmica no entren en las regiones de memoria libres.
Grandes asignaciones de espacio de Pila pueden aumentar en una funcin en C
conteniendo grandes (en tamao de bytes) y/ numerosas variables locales o cuando se
llama recursivamente a una funcin.
Pgina 19 de 22
Pgina 20 de 22
11.
Nmeros Mgicos
A veces queremos escribir cosas como IVT[6] o estado = 8; Pero esos nmeros
carecen de sentido para alguien que desea interpretar el programa, o bien para uno
mismo luego de un tiempo de haber escrito el programa. A estos nmeros, que no se
entiende de dnde salieron, se los llama nmeros mgicos y conviene evitarlos.
Usemos enumeraciones y constantes en lugar de nmeros mgicos!!
12.
Enumeraciones
13.
Pgina 21 de 22
Pgina 22 de 22