Elementos de C para Sistemas
Embebidos
Andrés Djordjalian <
[email protected]>
Seminario de Sistemas Embebidos
Facultad de Ingeniería de la U.B.A.
17:14
1 de 33
Estándares de C
Cada estándar es un “dialecto” diferente
O sea, en grandes términos es lo mismo, pero
existen diferencias que afectan la portabilidad.
“K&R C” (1978)
La primera estandarización no fue institucional,
sino que ocurrió cuando la comunidad adoptó
como estándar la descripción hecha por
Kernighan y Ritchie en su libro clásico de 1978.
“ANSI C” (o “ISO C” o “C90”) (1990)
Corresponde al estándar ISO/IEC 9899:1990
Es el más popular en la actualidad (el K&R C es obsoleto)
Es una descripción más exhaustiva que la del K&R C
“C99” (1999)
Es la revisión de 1999 del estándar anterior
No todos los compiladores actuales lo soportan al 100%
Tiene elementos tomados de C++
• …que los compiladores C/C++ suelen soportar aunque no sean
100% compatibles con C99
17:14
2 de 33
Tamaño de los Tipos de Datos
Los compiladores C tienen cierta libertad para elegir
el tamaño de los tipos de datos comunes
Lo suelen hacer en base a consideraciones de eficiencia, que
Tienen que respetar ciertos requisitos:
dependen de cada procesador
1. sizeof(char) <= sizeof(short) <= sizeof(int) <=
2. char por lo menos de tamaño suficiente para el conjunto de
3. short al menos de 16 bits
4. long al menos de 32 bits
sizeof(long)
caracteres básico
En programación de bajo nivel, a veces queremos
explicitar el tamaño exacto del dato
Por ejemplos, en casos como estos:
• Entrada/Salida
• Optimización de la utilización de memoria
• Rutinas en Assembly embebidas
¿Definir siempre un tamaño exacto? ¿O sólo a veces?
17:14
• Es un tema polémico.
3 de 33
Tipos de Datos de Tamaño Fijo
El C99 incluye los tipos de datos:
int8_t, int16_t, int32_t, int64_t (con signo)
uint8_t, uint16_t, uint32_t, uint64_t (sin signo)
int8_t es un entero de 8 bits incluyendo signo,
int16_t es uno de 16 bits, etc.
stdint.h
Ejemplo:
Estas definiciones están en un header llamado
#include <stdint.h>
int16_t g_posicion = 0;
int16_t leer_posicion(void) {
unsigned int intentos = 0;
etcétera…
Si el compilador no es C99 compatible, podemos usar
los mismos tipos definiéndolos con typedef
17:14
4 de 33
typedef
typedef le da un nombre al tipo de dato que
typedef <tipo de dato> <nombre>
indiquemos (que puede ser compuesto):
Ejemplos:
typedef signed char
typedef unsigned char
int8_t;
uint8_t;
Así, podemos definir esos tipos en stdint.h, y
usarlos aunque nuestro compilador no sea C99
compatible
Si stdint.h no existe, lo creamos
Pero muchos compiladores para embebidos ya lo traen
Usamos tipos de datos estándar de los cuales sabemos la
longitud para el procesador en cuestión
• Como con char en el ejemplo de arriba
Por otro lado, aprovechen las sentencias typedef
para clarificar el código
17:14
5 de 33
Conversión Automática de Tipos
C permite realizar operaciones
Ejemplo:
entre variables de diferente
tamaño, o combinando con y
sin signo
unsigned char a = 200;
int b = -20;
a = a+b;
Los compiladores generalmente cambian cada tipo a
uno de más bits que incluye todos los valores posibles
del original
En el ejemplo anterior, a es convertido a int durante la operación,
y el resultado es pasado de vuelta a unsigned char, quedando
igual a 180, como resulta lógico
Gracias a esto, el programador frecuentemente ni se da
cuenta de que hay una conversión de tipos
Es más, muchos compiladores convierten tipos aunque no sea
necesario, para simplificar la compilación
17:14
6 de 33
Ej.: Si a es unsigned int y
Ejemplo:
Conversión Automática de Tipos
Sin embargo, las reglas de C son más complejas que lo
mostrado en la diapositiva anterior, porque tienen en
cuenta la performance
la aritmética en long es
ineficiente en este procesador,
¿qué conviene hacer?
1. ¿Convertir todo a long?
2. ¿O usar int a pesar de perderse rango?
3. ¿O usar unsigned int a pesar de perderse el signo de b?
unsigned int a = 200;
int b = -20;
a = a+b;
Los compiladores generalmente optan por la número
3, pero estas situaciones no están bien definidas
Por eso, traen riesgos de errores y problemas de portabilidad
17:14
7 de 33
Conversión Automática de Tipos
Recomendaciónes:
1. No mezclar valores con signo con valores sin signo
Si se lo hace, prescindir de la conversión automática lo más
que se pueda
• Por ejemplo, si a es unsigned y b es signed, en lugar de
if (a+b < 0) { … };
Escribir:if ((b < 0) && (-b > a)) { … };
2. No hacer asignaciones que puedan perder
información
En el primer ejemplo, además
de no cumplir la regla anterior,
estábamos sumando un int y
poniendo el resultado en un
char. Evitemos eso.
Ejemplo:
unsigned char a = 200;
int b = -20;
a = a+b;
17:14
8 de 33
Operaciones Sobre Bits
Para operar sobre bits individuales, escribamos las
constantes en hexadecimal
Porque cada dígito hexa corresponde a 4 bits y se hace fácil de
leer
• Ej.,
0xC8 = 11001000b
0xC8 = 11001000b
Operadores de a bits:
AND: & • OR: | • NOT: ~ • XOR: ^
Operan “bit a bit”, así que no confundirlas con las operaciones
lógicas &&, || y !
Testeo de bits: if (0x0020 & reg) { … };
Encendido de bits: reg |= 0x0003;
Apagado de bits: reg &= 0x00FF;
“Toggling” de bits: reg ^= 0x0001;
Desplazamiento: << n y >> n
17:14
9 de 33
Punto Flotante
En muchas aplicaciones (científicas, de procesamiento
de señales, etc.) necesitamos representar números
reales con:
Rango amplio
Error de representación bajo en términos relativos
Parte fraccionaria (no entera)
Sin que se necesiten muchos bits para representarlos
La manera típica de hacerlo es mediante algún formato
de punto flotante (o floating point o coma flotante)
Consiste en descomponer el número así:
número = mantisa x base exponente
La base y la posición de la coma en la mantisa están
establecidos por el sistema de punto flotante que se
haya elegido
Noten que, por lo tanto, sólo se necesitan guardar los dígitos de la
mantisa y los del exponente, junto con sus respectivos signos, para
representar el número en memoria
10 de 33
17:14
Punto Flotante
número = mantisa x base exponente
Ej:
18,375 = 1,8375 x 101
-0,07514 = -7,5140 x 10-2
982312014 ≅ 9,8231 x 108
-0,00000000415147 ≅ -4,1514 x 10-9
Noten que no usamos más que cinco dígitos para la
mantisa y uno para el exponente (más sendos signos)
y sin embargo logramos gran rango y bajo error
relativo
Podíamos haber establecido otra posición para la
coma, por ej.:
18,375 = 183,75 x 10-1 ≅ 0,0184 x 10-3 ≅ etc.
Pero lo típico es usar un sistema normalizado con un y sólo un
dígito entero
17:14
11 de 33
Punto Flotante
El ejemplo es en base 10. En un circuito digital
normalmente se usa base igual a 2
¿Cómo se escribe 18,375 en binario?
18,37510 = 10010,0112
• Porque las posiciones a partir de la coma valen 2-1, 2-2, 2-3, etc.
– O sea, 0,5; 0,25; 0,125; etc.
Ejemplos en binario y base igual a 2:
18,375 = 1,0010011b x 2100b
-0,07514 ≅ -1,1110101b x 2-100b
982312014 ≅ 1,1101010b x 211101b
-0,00000000415147 ≅ -1,0001110b x 2-11100b
Noten que el 1 entero es redundante (sólo el 0 no lo
tiene), así que no hace falta guardarlo
…siempre que exista alguna convención para guardar un cero
Cuando no se lo guarda, a ese 1 se le llama implícito u oculto
17:14
12 de 33
Estándar IEEE 754
Se usa muy extensivamente. Define varios tipos de
punto flotante, principalmente:
1. Simple precisión
Normalmente es el típico tipo float de lenguaje C
Usa 32 bits en total para cada número:
1 para el signo
23 para la mantisa (sin contar el 1 implícito)
8 para el exponente (en lugar de ser signado tiene un bias de 127)
2. Doble precisión
Normalmente es el tipo double de lenguaje C
Usa 64 bits:
1 para el signo
52 para la mantisa (sin contar el 1 implícito)
11 para el exponente (con bias igual a 1023)
Los dos son normalizados y usan base igual a 2
17:14
13 de 33
Punto Flotante
Las operaciones con punto flotante no son tan básicas
como las de enteros. Ej.:
Para sumar, se alinean las mantisas, se suman, se toma el
exponente más grande, y se normaliza el resultado
Para multiplicar, se multiplican las mantisas, suman los
exponentes, y normaliza el resultado
Etcétera
Cuando un programador necesita números con parte
fraccionaria, frecuentemente los define como float
(o algo similar) y listo
Se logra un error de representación pequeño para un rango muy
grande, así que sólo excepcionalmente hay que analizar si es lo
suficientemente bueno (ej., aplicaciones científicas)
El compilador y las librerías implementan todo, usando
operaciones enteras o de la unidad de punto flotante (floating-
point unit o FPU) si es que se cuenta con este hardware
17:14
14 de 33
Números no enteros en Sist. Emb.
Sin embargo, muchas de esas veces no se requiere el
amplio rango y el bajo error relativo del punto fijo. Se
paga así un precio que, en sistemas embebidos, puede
ser caro
Comentarios de: Elementos de C para Sistemas Embebidos (0)
No hay comentarios