Temario Las Variables en Embebidos
Transcripción
Temario Las Variables en Embebidos
Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Construcciones y Formas del Lenguaje C para Embebidos Sistemas Embebidos 2010 C para Embebidos ARM Las Variables en Embebidos Ing. Juan Manuel Cruz ([email protected]) Profesor Invitado Seminario de Electrónica: Sistemas Embebidos (Cód: 66.48) Ingeniería en Electrónica – FI – UBA Buenos Aires, 7 de Septiembre de 2010 07/09/2010 Temario Construcciones y Formas del Lenguaje C para Embebidos Consideraciones para codificar en C sobre procesadores ARM M Mapa de d memoria i Consideraciones adicionales y conclusiones 07/09/2010 Ing. Juan Manuel Cruz Ing. Juan Manuel Cruz 3 Las Variables en Embebidos Residen en un ambiente muy diferente al de una PC Pueden residir en memoria de lectura solamente y/o en posiciones de memoria fija Pueden cambiar de valor debido a eventos del hardware (asincrónicos al flujo del programa) Escribir en ellas pueden ser ilegal y causar excepciones de sistema Pueden cambiar de valor al ser leídas Ing. Juan Manuel Cruz 2 El lenguaje C posee elementos para señalar éstas características al compilador, permitiéndole hacer hipótesis correctas sobre el acceso a las mismas y la correcta generación de secuencias de código Se recurre a palabras claves especiales que califican el tipo de la variable (se definen claramente las clases del almacenamiento en el contexto del embebido más que en el contexto general de la informática) 07/09/2010 Ing. Juan Manuel Cruz 4 1 Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Las Variables en Embebidos Calificador de tipo “const” Algunas palabras claves: Calificador de tipo “const” Declaración de variable en ROM y de puntero en RAM que apunta a ROM Calificador de tipo p “volatile” Representar periféricos mediante “structs” Declaración de variable en RAM y de p puntero en ROM que apunta a RAM 07/09/2010 Ing. Juan Manuel Cruz 5 Calificador de tipo “const” 07/09/2010 La palabra clave de calificador de tipo se interpone sintácticamente entre la clase del almacenamiento y el identificador static const int id; Declara id como un identificador del tipo entero, con clase de almacenamiento estática (inicializado una vez retiene su valor al reentrar al bloque y su alcance se restringe al resto del archivo fuente) calificada del tipo constante 07/09/2010 Ing. Juan Manuel Cruz Ing. Juan Manuel Cruz Ing. Juan Manuel Cruz 7 Declaración de variable en ROM y de puntero en ROM que apunta a ROM int id; int * const ptr = &id; Calificador de tipo “const” Un calificador del tipo restringe la manera en que se puede usar un identificador static const int id = 4; const int *ptr = &id; 6 const int id = 4; const int * const ptr = &id; Declaración de p puntero en ROM q que apunta p a posición de memoria fija int *const ptr = (int *)0x40000000; 07/09/2010 Ing. Juan Manuel Cruz 8 2 Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Representar periféricos mediante “structs” Calificador de tipo “volatile” El calificador de tipo volátil indica que la variable puede cambiar independiente del flujo del programa (típicamente por eventos en el hardware del periférico del microcontrolador) volatile int id; Al calificar una variable como volátil el compilador no optimizará las referencias a la misma asegurándonos que mantendrá su valor inalterable desde la última asignación (optimización que el compilador intentará hacer a variables de otro tipo) 07/09/2010 Ing. Juan Manuel Cruz 9 07/09/2010 Es necesario declarar como volátil los registros de periféricos de I/O cuando su contenido depende de eventos de hardware Declaración de puntero a registro de periférico (posiciones de memoria fijas) volatile unsigned int *const ptr = (unsigned int *) 0xE0000000; Ing. Juan Manuel Cruz Ing. Juan Manuel Cruz 11 Se requieren funciones separadas de Set y Clear Bit pues los pines de I/O y los valores de los registros de periféricos pueden cambiar por eventos de hardware y sólo deberán escribirse los bits que es necesario modificar. Esto puede hacerse teniendo la capacidad de manipular bit. El puerto de I/O permite escribir un valor del 32-bits de ser requerido La declaración de estructura correspondiente es: typedef struct { volatile unsigned int IOPIN; volatile unsigned int IOSET; volatile unsigned int IODIR; volatile unsigned int IOCLR; } GPIO; Note que la variable puede calificarse simultáneamente como volatile y const, indicando un registro cuyo cambios de valor se deben a eventos del hardware y qué no debe asignarse (registro del resultado de un conversor A/D) 07/09/2010 Ing. Juan Manuel Cruz Representar periféricos mediante “structs” Calificador de tipo “volatile” Los registros entrada salida de propósito general de un puerto de 32 bits de un microcontrolador NXP ARM son: 10 07/09/2010 Ing. Juan Manuel Cruz 12 3 Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Representar periféricos mediante “structs” GPIO es un tipo de estructura que se usa para declarar puertos. Por ejemplo, el micro tiene 4 puertos declarados como: Consideraciones para la depuración GPIO Port0, Port1, Port2, Port3 Los punteros a Port0 pueden declararse como: GPIO *const ptr_Port0 = (GPIO *) 0xE0028000; /* address of Port 0 */ ptr_Port0 es un puntero constante a una variable del tipo GPIO (estructura que contiene 4 variables que representan puertos de I/O), apunta a la dirección baja de la estructura (del puerto de I/O mapeado en memoria), la conversión a puntero tipo GPIO es necesario para mantener la consistencia Podemos usar el puntero para acceder al port0 de GPIO como: ptr_Port0 -> IOSET = 0xD0D0FEED Las otras variables pueden leerse o escribirse de manera similar typedef struct {...} Volatile GPIO Ing. Juan Manuel Cruz 13 Consideraciones para codificar en C sobre procesadores ARM Existen muchas consideraciones básicas de programación para procesadores ARM, la documentación del compilador usado aporta gran detalle sobre diversos aspectos importantes y sutiles a considerar y debe leerse detenidamente (aspectos del trabajo con la arquitectura y con el compilador ARM) Es útil analizar el lenguaje assembly generado por el compilador para aprender sobre la eficiencia y lo que hace el compilador Ing. Juan Manuel Cruz 14 La eficiencia de depuración y de código a veces chocan. El compilador da opciones “código vs info. de depuración” (aprox. al 15%, uno debe ver sus efectos y usar la más apropiada) De haber efectos secundarios el código que se ejecuta en modo depuración no podrá ejecutarse en otros modos; es mejor estar en modo de depuración para que el código que se ejecuta en el sistema sea lo más parecido al código que se verificó Al optimizar pueden surgir diferencias al código usado en la depuración con efectos secundarios imprevistos. Si el modo de depuración ofrece recursos (Semi-Hosting) no disponibles en su sistema; se deberá quitar/reemplazar esas llamadas por código propio (la ejecución condicional de ARM está deshabilitada para todas las opciones de depuración) 07/09/2010 Ing. Juan Manuel Cruz 15 Consideraciones de Lazos La consideraciones de codificación son muy importantes cuando se procura optimizar el rendimiento y ocupar cantidades li it d de limitadas d memoria i embebida. b bid El costo t de d desbordar d b d una configuración de memoria dada suele ser alto (saltar al chip más próximo y de no existir forzaría el rediseño del sistema) 07/09/2010 Ing. Juan Manuel Cruz La estructura GPIO completa también puede declararse volátil: 07/09/2010 Los programas embebidos siguen la regla 90/10: el 90% del tiempo se ejecuta el 10% del código (Hennessy & Patterson). Pasan la mayor parte del tiempo ejecutando Lazos (usualmente saltos hacia atrás), por lo que es importante comprender y optimizar comportamiento de lazos Una simple optimización es escribir for de conteo regresivo que terminen en cero, en lugar de contar hasta un valor máx. for (i = 1; i < n; i++), i++) el compilador genera una instr. instr de comparación entre i y n y luego una de salto por menor que cerrando el lazo for (i=n; i != 0; i--), el compilador puede setear el flag de cero al hacer el decremento (SUBS) y luego BNE finaliza el lazo; se ahorra tiempo, tamaño de código y se libera un registro pues la variable n es un valor de inicialización para el puntero del lazo en lugar de tener que mantenerse en un registro a fin de compararla con la variable i en cada iteración del lazo 07/09/2010 Ing. Juan Manuel Cruz 16 4 Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Asignación de Registros y Alias de Punteros En ARM las variables pueden alocarse en registros en lugar de memoria (con gran ahorro en rendimiento y tamaño de código) Por la consistencia de valor de la variable, el compilador debe asegurarse que el valor de la variable en memoria no cambia por una referencia de puntero en otros puntos del programa (pointer aliasing), por lo tanto una variable alocada en un registro debe: Tipos de Datos y Alineación Natural en ARM Ing. Juan Manuel Cruz 17 int prog1 (int var1) { somefunction (&var1); El paso de la dirección de var1 y la posterior referencia a var1 en memoria pues el compilador no puede garantizar que un valor de registro no se cambiará. Sin embargo, mediante el uso de una variable intermedia local: int prog2 (int var2) { int local1 = var2; somefunction (&local1); var2 = local1; Toda referencia posterior a var2 se hará por registro ya que su dirección no fue tomada, el compilador puede alocarla en un registro y accederla con instrucciones MOVs en lugar de usar instrucciones de Stack, Load o Store 07/09/2010 Ing. Juan Manuel Cruz 07/09/2010 Ing. Juan Manuel Cruz 19 Tipos de Datos y Alineación Natural en ARM Las variables globales no pueden alocarse en registros pues sus valores no son privados a un módulo determinado Ejemplo de asignación de alias de puntero: Ing. Juan Manuel Cruz El código ARM puede acceder a datos más eficientemente si corresponde a su tamaño natural, los límites de la alineación para los tipos de variable C son: Al crear un puntero a una variable el compilador no está seguro si el valor de la misma no es cambiado desde fuera del módulo, por lo tanto debe ubicar la variable en memoria (no en registro) Asignación de Registros y Alias de Punteros Entre la arquitectura de un microcontrolador y los tipos de variables en C allí pueden existir alguna alineación natural, por ejemplo, la capacidad para manipular “naturalmente” los tipos byte o word (en una sola instrucción y eficientemente) a) Ser una variable local o un parámetro de función b) No tienen su dirección tomada (& var_name) o asignada a otra variable 07/09/2010 18 Como ARM es una arquitectura de 32 bits, se espera que sea más eficiente en el manejo de su tipo de tamaño "natural” La arquitectura es más eficiente usando enteros pues ARMv4 y derivadas pueden cargar valores con y sin signo de 8 y 16 bits (aunque todos los registros ARM y la ALU son de 32-bits) El tratamiento de no enteros como char y short es: a)) Los L valores l sin i signo i se expanden d con ceros b) Los valores signados se expanden con signo Por lo que toda variable char & short se deberá convertir después de cada operación para comprobar si ha sobrepasado su límite, siendo mucho más eficiente usar int para variables locales y convertir el valor retornado de ser necesario (el uso de ints reduce tamaño de código y aumenta el rendimiento) 07/09/2010 Ing. Juan Manuel Cruz 20 5 Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Tipos de Datos y Alineación Natural en ARM ARM Thumb® Procedure Call Standard (ATPCS) Aquí hay un ejemplo de eficiencia utilizando el tipo int: short somefunction (short i) { i = i + 1; return i; } El código generado será: ADD a1, a1, #1 MOV a1, a1, LSL #16 /* put sign bit in MSB */ MOV a1, a1, ASR #16 /* sign extend */ MOV pc, lr /* return */ int somefunction (int i) {i = i +1; return i;} El código generado es: ADD a1, a1, #1 MOV pc, lr /* return */ Use enteros cuando sea posible para evitar las conversiones de shorts & chars. Como cada regla tiene excepciones puede haber un balance entre el tamaño del código y del espacio variables 07/09/2010 Ing. Juan Manuel Cruz 21 ARM Thumb® Procedure Call Standard (ATPCS) Comprender y aprovechar las ventajas de esta convención pueden conducir a programas más eficientes. En particular, se incluyen los registros utilizados por el compilador: Ing. Juan Manuel Cruz Ing. Juan Manuel Cruz 23 Las implicancias de usar ATPCS a) Registros r0 a r3 (a1 a a4) para pasar valores de parámetro a rutinas y retornar valores. La rutina llamada no debe restaurarlos a su regreso b) Registros r4 a r11 (v1 a v8) usados para variables locales (alocar registros). La rutina llamada debe restaurar el estado de estos registros antes de regresar (salvar en la pila & restaurar) c) Hasta el registro 14 variables locales (r0 a r11, r12 y r14) de funciones d) más variables locales producirán el vuelco a memoria (caro reemplazo de accesos de registro muy eficiente) debe evitarse mientras se sea posible 07/09/2010 Ing. Juan Manuel Cruz ARM Thumb® Procedure Call Standard (ATPCS) El compilador de C para ARM usa un convenio estándar para el paso de parámetros y el uso de registro llamado ATPCS 07/09/2010 22 Algunas reglas simples al escribir funciones para obtener código más pequeño y más rápido (procure reemplazar accesos a memoria externa por registro internos). Las pautas simples para las funciones incluyen: a) Pueden pasarse hasta 4 words como argumentos a función en registros (r0 ..r3). Más de 4 argumentos se pasarán por la pila vía acceso a memoria b) Las funciones con 4 o menos argumentos ahorran espacio del código y tiempo de ejecución, limítese a funciones simples c) Si hay más de 4 argumentos, agrúpelos en una estructura y pase un puntero a la estructura (es muy ineficiente pasar una estructura) d) Limite las variables locales a menos de 10 (para 4 argumentos) e) Evite las funciones variadic con número variable de parámetros f) Para retornar valores también se usan ro .. r3 (más valores se retornan en la pila) 07/09/2010 Ing. Juan Manuel Cruz 24 6 Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Alineamiento de estructuras y Uso de Memoria _packed & Bit-Fields El orden en q que se declaran las variables en una estructura afecta el número de bytes de almacenamiento requerido y el medio de acceso (el tipo de instrucciones generada) Para dos declaraciones de una estructura de 2 chars, chars un short y un int: struct {char c; short s; char d; int i;} bytes: c x s s d x x x i i i i (12 bytes en total, 4 x: 4 bytes de relleno) struct {char c; char d; short s; int i} bytes: c d s s i i i i (8 bytes en total, 0 bytes de relleno pues los campos están de acuerdo con el método de alineación del compilador). Puede ser provechoso prestar atención al arreglo de estructuras para ahorrar bytes Ing. Juan Manuel Cruz 25 _packed & Bit-Fields La regla g básica es que q valores enteros se alinean naturalmente con la arquitectura ARM de 32-bits y el pensamiento debe entrar en usar variables no-alineadas más cortas para asegurar que cualquier ganancia no se torne en un mayor procesamiento y en accesos ineficientes 07/09/2010 a) Read Only Memory (ROM, flash): para código b) RAM: para datos (heap, stack) o código Con bit-fields bit fields ocurre algo similar (se compacta a costa de más código y más bajo rendimiento). Puede usarse en estructuras para crear campos de longitud definida por el usuario. Pueden ser contiguos mientras no excedan el tamaño básico que los contiene (el tamaño de un contenedor entero es 32 bits) Ing. Juan Manuel Cruz Ing. Juan Manuel Cruz 27 El mapa de memoria representa la dirección de arranque y la longitud de las diversas zonas de memoria que corresponde a: Typedef _packed struct { char c; int i; short s; char d;} 07/09/2010 Ing. Juan Manuel Cruz bytes: c i i i i s s d, comenzando a un límite de 4 u 8 bytes. Ahorran espacio pero a los accesos a las variables no alineadas el compilador debe agregar desplazamientos y uniones para formar al int i en un registro (muy ineficiente pues crea más instrucciones y reduce el rendimiento). Debe justificarse su uso struct bitter { int a:10; int b:20; int c:30;} bits of Word 1: aaaaa.aaaaa.bbbbb.bbbbb.bbbbb.bbbbb.xx bits of Word 2: ccccc.ccccc.ccccc.ccccc.ccccc.ccccc.xx Los bits x son de relleno. Como los bit-fields residen en memoria su acceso es mediante punteros, de acceso ineficiente y naturaleza no alineada (es más efectivo el enmascaramiento lógico de variables enteras) Mapa de memoria Estructuras definidas con el calificador _packed packed alinean sus miembros a 1byte (sin ningún relleno) Por ejemplo: El compilador alinea la dirección de inicio del primer miembro de una estructura a la longitud de acceso más grande (4 u 8 bytes) y luego alinea al resto a la máxima alineación requerida por su longitud (un campo de tipo char se alinea al próximo byte disponible mientras un tipo del int se alinea al próximo word), insertando bytes de relleno en los huecos de alineación 07/09/2010 26 c) Periféricos: registros de periféricos mapeados en memoria (su contenido se modifica por eventos de hardware y pueden haber requisitos especiales de acceso) d) Vectores de Interrupción: posiciones de memorias fijas para los manejadores de interrupciones y excepciones Estas áreas pueden ser re-mapeadas luego del booteo (para re-programar la memoria Flash) 07/09/2010 Ing. Juan Manuel Cruz 28 7 Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Mapa de memoria Mapa de memoria El mapa de memoria representa la dirección de arranque y la longitud de las diversas zonas de memoria que corresponde a: a) Read Only Memory (ROM, flash): para código b) RAM: para datos (heap, stack) o código c) Periféricos: registros de periféricos mapeados en memoria (su contenido se modifica por eventos de hardware y pueden haber requisitos especiales de acceso) d) Vectores de Interrupción: posiciones de memorias fijas para los manejadores de interrupciones y excepciones Estas áreas pueden ser re-mapeadas luego del booteo (para re-programar la memoria Flash) 07/09/2010 Ing. Juan Manuel Cruz 29 Mapa de memoria a) Bloque de Booteo de 8K ubicado en 2GB, maneja los eventos de reset del sistema e incluye un programa monitor de depuración en tiempo real (al tope de memoria interna para que no cambie de derivado en derivado) b) Los vectores de la Interrupción son re-mapeados luego del booteo para ser invocado una vez verificado que el código del usuario existe y es válido (checksum); sino el boot loader intenta cargar un programa en flash c) La distribución de memoria se presenta al linker vía un archivo especial llamado scatter load (permite poner en memoria zonas complejas y ser invocado por el compilador mediante directivas de assembler) Las zonas de memoria poseen propiedades específicas incluyen: Ing. Juan Manuel Cruz El diseño de mapa de memoria se mantiene entre derivados permitiendo el re-uso de software. La ubicación de las rutinas de servicio de interrupción y las convenciones de la interrupción se mantienen tanto como sea posible 07/09/2010 Ing. Juan Manuel Cruz 31 Distribuir la imagen del ejecutable en zonas adecuadas de memoria para aprovechar un mapa de memoria complejo, controlar el orden de la carga de variables/módulos y relacionar las propiedades de variables a zonas de memoria específicas Ing. Juan Manuel Cruz Distribución de carga Algunas observaciones sobre el mapa de memoria del micro Philips LPC2294: 07/09/2010 30 a) Sólo-Lectura (RO): Código y datos que sólo serán leídos, no modificados b) Lectura-Escritura (RW); Código y datos que pueden leerse y reescribirse c) Cero-Inicializado (ZI): Datos inicializados en Cero Pueden convivir varias zonas con atributos diferentes. Puede copiarse código y datos de ROM a RAM en tiempo de ejecución. Los datos pueden ser inicializados en cero (variables statics), etc. Deben setearse direcciones de Stack y heap y re-mapear Vectores antes de la ejecución 07/09/2010 Ing. Juan Manuel Cruz 32 8 Sistemas Embebido 2010 - C para Embebidos ARM 07/09/2010 Consideraciones adicionales y conclusiones Distribución de carga Para hacer todos el anterior se cera un archivo de distribución de carga y es pasado al Linker: El archivo contiene nombres de las zonas, atributos, direcciones de comienzo y opcionalmente longitudes Dentro de cada zona, pueden disponerse que áreas o secciones de programas vengan primero (o último) Pueden alocarse secciones en direcciones fijas para poder encontrar ciertos tipos de información a una dirección dada del microcontrollador (información de fabricación) Dado el mapa de memoria para el Philips el producto de LPC2294, un mismo (mismo) simple esparza el archivo de carga aparecería como sigue: 07/09/2010 Ing. Juan Manuel Cruz 33 Distribución de carga Evite la división siempre p que q p posible (use ( desplazamientos p si su core ARM no posee división que el hardware) Las instrucciones ARM son in-interrumpibles: ‘Load and Store Multiple' puede aumentar mucho la latencia de la interrupción en sistemas de memoria lenta; use un flag del compilador para limitar el número de registros guardado o cargados en una instrucción Las librerías de Tiempo Real pueden ser muy grandes; reemplace las funciones normalmente usadas con sus propias funciones evitar arrastrar tanto código Hay más consideraciones a tener en cuenta listadas en la documentación del compilador (estudie el manual y los ejemplos de prueba). Con el compilador y estudiando el assembly pueden surgir más consideraciones. Finalmente, una gran cita atribuida a B. Kernighan (vía la página informativa de J. Ganssle): “Depurar es el doble de duro que escribir el código en primer lugar. Por consiguiente, si usted escribe el código tan hábilmente como le sea posible, por definición usted no es lo bastante astuto como para ponerlo a punto". 07/09/2010 Ing. Juan Manuel Cruz 35 Referencias FLASH_IMAGE _ 0X00000000 { FLASH_Code_Data 0X00000000 0x10000 { vectors.o (Vect, +FIRST) ; Interrupt & Exception Vectors * (+RO) ; All Read-Only Code and Data } RAM_Code_Data 0X40000000 0x4000 { * (+RW, +ZI) ; All Read-Write Code and Data } } Representing p g a Microcontroller in C - Ata. R. Kahn http://ing.de.soft1.googlepages.com/ C para MCS-51 – J. M. Cruz Este archivo posiciona la imagen del código en cero absoluto, especifica que la interrupción y vectores de la excepción se carguen primero y a continuación el código y datos Read Only (ROM, Flash) Los datos de lectura-escritura (RAM) se posiciona en 0X40000000 y se especifica que todos ellos (lectura-escritura y cero-inicializado) se cargue allí 07/09/2010 Ing. Juan Manuel Cruz Ing. Juan Manuel Cruz 34 07/09/2010 Ing. Juan Manuel Cruz 36 9