2009-09-02 10 views
8

Estoy trabajando en un dominio de sistema integrado. Me gustaría saber cómo se ejecuta un código desde un microcontrolador (uC no necesita ser subjetivo, en general), comenzando desde un archivo C. También me gustaría saber cosas como código de inicio, archivo de objeto, etc. No pude encontrar ninguna documentación en línea con respecto a las cosas anteriores. Si es posible, proporcione enlaces que explican esas cosas desde cero. Gracias de antemano por su ayudaEjecución de código en sistemas integrados

+0

Ayudaría indicar qué tipo de microcontrolador. –

+0

Estoy trabajando en el controlador 8051. sé un poco acerca de cómo se obtienen y se ejecutan los códigos de operación en lenguaje ensamblador). pero me gustaría saber cómo un proyecto con múltiples archivos C se ejecuta en un uC. – inquisitive

+2

¡los archivos C no se ejecutan! :-) Se compilan para objetos y se vinculan a una imagen ejecutable final, que se carga en Flash o RAM y se ejecuta desde allí. –

Respuesta

36

Al ser un arquitecto de microprocesadores, he tenido la oportunidad de trabajar a un nivel muy bajo para el software. Básicamente, el nivel bajo incrustado es muy diferente de la programación general de PC solo en el nivel específico del hardware.

de bajo nivel de software embebido puede ser dividido en los siguientes:

  1. Reset vector - esto se suele escribir en el montaje. Es lo primero que se ejecuta en la puesta en marcha y se puede considerar código específico del hardware. Por lo general, realizará funciones simples como configurar el procesador en un estado estable predefinido mediante la configuración de registros y demás. Luego saltará al código de inicio. El vector de reinicio más básico simplemente salta directamente al código de inicio.
  2. Código de inicio - este es el primer código específico de software que se ejecuta. Su trabajo es básicamente configurar el entorno de software para que el código C pueda ejecutarse en la parte superior. Por ejemplo, el código C supone que hay una región de memoria definida como pila y montón. Por lo general, se trata de construcciones de software en lugar de hardware. Por lo tanto, esta pieza de código de inicio definirá los punteros de pila y los punteros de pila y demás. Esto generalmente se agrupa bajo 'c-runtime'. Para el código de C++, los constructores también son llamados. Al final de la rutina, se ejecutará main(). editar: Las variables que se deben inicializar y también ciertas partes de la memoria que necesitan borrado se hacen aquí. Básicamente, todo lo que se necesita para mover las cosas a un 'estado conocido'.
  3. Código de aplicación - esta es su aplicación C real a partir de la función main(). Como puede ver, muchas cosas están bajo la sombra y ocurren incluso antes de que se llame su primera función principal. Este código generalmente puede escribirse como hardware-agnóstico si hay un buen hardware abstraction layer disponible. El código de la aplicación definitivamente hará uso de muchas funciones de la biblioteca. Estas bibliotecas generalmente están vinculadas estáticamente en sistemas integrados.
  4. Bibliotecas - estas son sus bibliotecas C estándar que proporcionan funciones C primitivas. También hay bibliotecas específicas de procesador que implementan cosas como soporte de punto flotante de software. También puede haber bibliotecas específicas de hardware para acceder a los dispositivos de E/S y para stdin/stdout. Un par de bibliotecas C comunes son Newlib y uClibc.
  5. Interrupt/Exception manejador - estas son rutinas que se ejecutan en momentos aleatorios durante la ejecución normal del código como resultado de cambios en los estados del hardware o del procesador. Estas rutinas también suelen escribirse en ensamblaje, ya que deben ejecutarse con una sobrecarga de software mínima para dar servicio al hardware real llamado.

Espero que esto sea un buen comienzo. Siéntase libre de dejar comentarios si tiene otras consultas.

+0

¡Golpe en el objetivo! Gracias, Sybreon. Ahora, ¿qué pasa con la asignación de memoria? Suponiendo que mi uC tiene una memoria flash, las variables estáticas y globales utilizadas por el programa se almacenarían en RAM (.bss y sección Datos) por el código de inicio del sistema, las variables locales en la pila (nuevamente RAM) y el código permanece en flash (ROM) La ejecución real ocurre al ejecutar cada instrucción desde el flash. Estoy en lo correcto? – inquisitive

+0

@Guru_newbie: "La ejecución real ocurre al ejecutar cada instrucción desde el flash". Algunos procesadores ejecutan el código directamente desde el flash y otros no. Creo que el 8051 ejecutará el código desde flash. Los procesadores integrados de mayor tamaño (32 bits), como una PC, copiarán el código de la aplicación en la RAM y lo ejecutarán desde la RAM. – simon

+0

@sybreon: El paso 2 también configurará variables estáticas en la RAM y también copiará los datos de inicialización. – simon

5

En general, está trabajando a un nivel mucho más bajo que las computadoras de propósito general.

Cada CPU tendrá cierto comportamiento en el encendido, como borrar todos los registros y configurar el contador de programas en 0xf000 (todo lo que aquí se muestra no es específico, como es su pregunta).

El truco está en asegurar que su código esté en el lugar correcto.

El proceso de compilación suele ser similar al de las computadoras de uso general en que usted traduce C en código de máquina (archivos de objetos). A partir de ahí, debe vincular ese código con:

  • código de inicio del sistema, a menudo en ensamblador.
  • bibliotecas de tiempo de ejecución (incluidos los bits necesarios de C RTL).

El código de puesta en marcha del sistema generalmente solo inicializa el hardware y configura el entorno para que su código C pueda funcionar. Las bibliotecas de tiempo de ejecución en sistemas integrados a menudo hacen que las cosas grandes y voluminosas (como el soporte de punto flotante o printf) sean opcionales para evitar la saturación del código.

El enlazador en sistemas integrados también suele ser mucho más simple, ya que proporciona código de ubicación fija en lugar de binarios reubicables. Lo usa para asegurarse de que el código de inicio sea (por ejemplo) 0xf000.

En los sistemas integrados, generalmente quiere que el código ejecutable esté allí desde el principio para que pueda grabarlo en EPROM (o EEPROM o Flash u otro dispositivo que mantenga el contenido apagado).

Por supuesto, tenga en cuenta que mi última incursión fue con los procesadores 8051 y 68302. Puede ser que los sistemas "integrados" hoy en día sean cajas completas de Linux con todo tipo de hardware maravilloso, en cuyo caso no existiría una diferencia real entre el propósito general y el integrado.

Pero lo dudo. Todavía hay una necesidad de hardware de baja especificación que necesite sistemas operativos personalizados y/o código de aplicación.

SPJ Embedded Technologies tiene downloadable evaluation de su 8051 entorno de desarrollo que parece ser lo que usted quiere. Puede crear programas de hasta 2K de tamaño, pero parece pasar por todo el proceso (compilación de enlaces, generación de archivos HEX o BIN para descargar en el hardware de destino, incluso un simulador que da acceso a las cosas en el chip y dispositivos externos))

El producto sin evaluación cuesta 200 euros pero, si todo lo que quiere es un poco de juego, simplemente descargo la evaluación, aparte del límite de 2K, es el producto completo.

+0

Gracias por la respuesta rápida, pax. Si es posible, puede intentar proporcionar cualquier buen enlace disponible para explicar el proceso anterior (independientemente de cuál sea el uC real) – inquisitive

1

que tienen experiencia con los microcontroladores AVR, pero creo que esto será más o menos la misma para todos ellos:

La compilación va a lo largo de las mismas líneas como con un código normal C.Se compila en los archivos del objeto, estos se vinculan entre sí, pero en lugar de dar salida a algún formato complejo como ELF o PE, la salida simplemente se coloca en una dirección fija en la memoria del uC sin ningún encabezado.

El código de inicio (si el compilador genera alguno) se agrega de la misma forma que el código de inicio para computadoras "normales"; se agrega código antes del código principal() y quizás después.

Otra diferencia es la vinculación: everythig tiene que estar vinculado estáticamente, porque los microcontroladores no tienen un sistema operativo para manejar el enlace dinámico.

+0

Gracias Cube. Ahora entiendo que se creará un ejecutable en la PC host y se colocará en la memoria no volátil de la uC. Me gustaría saber cómo comienza la ejecución real en el objetivo real a partir de entonces. Cualquier documentación en línea reg este o un estudio de caso es preferible. – inquisitive

+0

No es muy preciso acerca de ELF/PE.Muchos enlazadores para sistemas embebidos emiten ELF, es solo que el código binario dentro de él es de dirección fija, no de posición independiente. Entonces, es posible generar un archivo hexadecimal (S-record de Motorola o Intel Hex) o un volcado binario directo (suponiendo que conozca la dirección de inicio) para cargarlo en Flash. –

2

Me da la impresión de que estás más interesado en lo que Sybreon llama "paso 2". Pueden pasar muchos lotes, y varían mucho según la plataforma. Por lo general, esto se maneja mediante una combinación de gestor de arranque, paquete de soporte de la placa, C Runtime (CRT) y, si tiene uno, el sistema operativo.

Normalmente, después del vector de reinicio, algún tipo de gestor de arranque se ejecutará desde el flash. Este gestor de arranque podría simplemente configurar hardware y saltar al CRT de tu aplicación, también en flash. En este caso, el CRT probablemente borre el .bss, copie los datos a la RAM, etc. En otros sistemas, el gestor de arranque puede distribuir la aplicación desde un archivo codificado, como un ELF, y el CRT simplemente configura otro tiempo de ejecución (montón, etc.). Todo esto sucede antes de que el CRT llame a la aplicación principal().

Si su aplicación está enlazada estáticamente, las directivas del vinculador especificarán las direcciones donde se inicializan .data/.bss y la pila. Estos valores están vinculados al CRT o codificados en el ELF. En un entorno enlazado dinámicamente, la carga de la aplicación generalmente es manejada por un sistema operativo que vuelve a orientar el ELF para que se ejecute en cualquier memoria que el sistema operativo designe.

Además, algunos destinos ejecutan aplicaciones desde el flash, pero otros copiarán el .exe ejecutable de la memoria flash a la RAM. (Esto suele ser una compensación de velocidad/huella, ya que la RAM es más rápida/más ancha que el flash en la mayoría de los objetivos).

1

Puede consultar el muy detallado GNU ARM Tutorial de Jim Lynch.

+0

A tener en cuenta: ARM se encuentra entre los sistemas integrados más complejos. El código de inicio es especialmente complejo en comparación con los pequeños usuarios de computadoras, p. Ej. AVR –

2

Ok, voy a dar a este un tiro ...

En primer lugar arquitecturas. Von Neumann vs. Harvard. La arquitectura de Harvard tiene memoria separada para código y datos. Von Neumann no. Harvard se usa en muchos microcontroladores y es con lo que estoy familiarizado.

Por lo tanto, comenzando con su arquitectura básica de Harvard tiene memoria de programa. Cuando el microcontrolador se inicia por primera vez, ejecuta las instrucciones en la ubicación de memoria cero. Por lo general, este es un comando JUMP to address donde se inicia el código principal.

Ahora, cuando digo instrucciones me refiero a códigos de operación. Los códigos de operación son instrucciones codificadas en datos binarios, generalmente 8 o 16 bits. En algunas arquitecturas, cada código de operación está codificado para significar cosas específicas, en otros, cada bit puede ser significativo (es decir, el bit 1 significa verificación de carga, el bit 2 significa marca de verificación cero, etc.). Entonces hay códigos de operación y luego parámetros para los códigos de operación. Una instrucción JUMP es un código de operación y una dirección de memoria de 8, 16 o 32 bits a la que el código 'salta'. Es decir, el control se transfiere a las instrucciones en esa dirección. Lo logra manipulando un registro especial que contiene la dirección de la próxima instrucción que se ejecutará. Por lo tanto, para saltar a la ubicación de memoria 0x0050 reemplazaría el contenido de ese registro con 0x0050. En el siguiente ciclo de reloj, el procesador leería el registro y ubicaría la dirección de memoria y ejecutaría allí las instrucciones.

Las instrucciones de ejecución provocan cambios en el estado de la máquina. Hay un registro de estado general que registra información acerca de lo que hizo el último comando (es decir, si es una adición, si se requirió una ejecución, hay un poco para eso, etc.). Hay un registro 'acumulador' donde se coloca el resultado de la instrucción. Los parámetros para las instrucciones pueden ir en uno de varios registros de propósito general, o el acumulador, o en direcciones de memoria (datos O programa). Diferentes códigos de operación solo se pueden ejecutar en datos en ciertos lugares. Por ejemplo, puede agregar datos de dos registros de propósito general y mostrar el resultado en el acumulador, pero no puede tomar datos de dos ubicaciones de memoria de datos y hacer que el resultado aparezca en otra ubicación de la memoria de datos. Tendría que mover los datos que desea a los registros de propósito general, hacer la suma y luego mover el resultado a la ubicación de memoria que desee. Es por eso que el montaje se considera difícil. Hay tantos registros de estado como la arquitectura está diseñada para.Arquitecturas más complejas pueden tener más para permitir comandos más complejos. Los más simples pueden no.

También hay un área de memoria conocida como la pila. Es solo un área en memoria para algunos microcontroladores (como el 8051). En otros puede tener protecciones especiales. Hay un registro llamado puntero de pila que registra en qué ubicación de memoria está la 'parte superior' de la pila. Cuando "empuja" algo en la pila desde el acumulador, la dirección de memoria "superior" se incrementa y los datos del acumulador se colocan en la dirección anterior. Al recuperar o extraer datos de la pila, se hace lo inverso y los indicadores de la pila se reducen y los datos de la pila se ponen en el acumulador.

Ahora también he aclarado cómo se "ejecutan" las instrucciones. Bueno, esto es cuando te pones a la lógica digital - tipo de cosas VHDL. Multiplexores y decodificadores y tablas de verdad y tal. Ese es el verdadero significado del diseño, más o menos. Entonces, si quiere 'mover' el contenido de una ubicación de memoria al acumulador, debe resolver la lógica de direccionamiento, borrar el registro del acumulador, Y hacerlo con los datos en la ubicación de la memoria, etc. Es abrumador cuando se colocan todos juntos pero si has hecho partes separadas (como direccionamiento, medio sumador, etc.) en VHDL o en cualquier forma de lógica digital, es posible que tengas una idea de lo que se requiere.

¿Cómo se relaciona esto con C? Bueno, un compilador tomará las instrucciones C y las convertirá en una serie de códigos de operación que realizan las operaciones solicitadas. Todo eso es básicamente datos hexadecimales: uno y ceros que se colocan en algún punto de la memoria del programa. Esto se hace con las directivas compilador/vinculador que indican qué ubicación de memoria se usa para qué código. Está escrito en la memoria flash del chip, y luego, cuando el chip se reinicia, va a la ubicación de la memoria de códigos 0x0000 y JUMP a la dirección de inicio del código en la memoria del programa, y ​​luego comienza a taparse en los códigos de operación.

+0

Al reiniciar, el procesador comienza la ejecución en el vector de reinicio que puede o no estar en la ubicación 0x0000. Debe mirar la hoja de datos del procesador específico para la ubicación del vector de reinicio. – tkyle

0

Puede consultar el enlace https://automotivetechis.wordpress.com/.

La siguiente secuencia de vistas generales de la secuencia de ejecuciones de instrucciones del controlador:

1) asigna memoria primaria para la ejecución del programa.

2) Copia el espacio de direcciones de la memoria secundaria a la primaria.

3) Copia las secciones .text y .data del ejecutable en la memoria primaria.

4) Copia los argumentos del programa (por ejemplo, argumentos de línea de comando) en la pila.

5) Inicializa registros: establece el esp (puntero de pila) para que apunte a la parte superior de la pila, borra el resto.

6) Salta a la rutina de inicio, que: copia los argumentos de main() fuera de la pila y salta a main().

Cuestiones relacionadas