35 votos

¿Qué ocurre cuando termina un programa incrustado?

¿Qué ocurre en un procesador embebido cuando la ejecución llega a ese return declaración ¿Se congela todo tal cual; el consumo de energía, etc., con un largo y eterno NOP en el cielo? o ¿se ejecutan NOPs continuamente, o se apaga un procesador por completo?

Parte de la razón por la que pregunto es que me pregunto si un procesador necesita apagarse antes de terminar la ejecución y, si lo hace, ¿cómo puede terminar la ejecución si se ha apagado antes?

23 votos

Depende de sus creencias. Algunos dicen que se reencarnará.

10 votos

¿Es para un misil?

7 votos

Algunos sistemas admiten el HCF (Halt and Catch Fire) instrucción. :)

43voto

Armandas Puntos 552

Esta es una pregunta que siempre me hacía mi padre. " ¿Por qué no recorre todas las instrucciones y se detiene al final? "

Veamos un ejemplo patológico. El siguiente código fue compilado en el compilador C18 de Microchip para el PIC18:

void main(void)
{

}

Produce la siguiente salida de ensamblador:

addr    opco     instruction
----    ----     -----------
0000    EF63     GOTO 0xc6
0002    F000     NOP
0004    0012     RETURN 0
.
. some instructions removed for brevity
.
00C6    EE15     LFSR 0x1, 0x500
00C8    F000     NOP
00CA    EE25     LFSR 0x2, 0x500
00CC    F000     NOP
.
. some instructions removed for brevity
.
00D6    EC72     CALL 0xe4, 0            // Call the initialisation code
00D8    F000     NOP                     //  
00DA    EC71     CALL 0xe2, 0            // Here we call main()
00DC    F000     NOP                     // 
00DE    D7FB     BRA 0xd6                // Jump back to address 00D6
.
. some instructions removed for brevity
.

00E2    0012     RETURN 0                // This is main()

00E4    0012     RETURN 0                // This is the initialisation code

Como puedes ver, se llama a main(), y al final contiene una sentencia return, aunque no la hayamos puesto explícitamente. Cuando main retorna, la CPU ejecuta la siguiente instrucción que es simplemente un GOTO para volver al principio del código. main() es simplemente llamada una y otra vez.

Ahora bien, dicho esto, esta no es la forma en que la gente suele hacer las cosas. Nunca he escrito ningún código incrustado que permita a main() salir así. Mayormente, mi código se vería algo así:

void main(void)
{
    while(1)
    {
        wait_timer();
        do_some_task();
    }    
}

Así que normalmente nunca dejaría salir a main().

"Vale, vale", dices. Todo esto es muy interesante que el compilador se asegura de que nunca hay una última declaración de retorno. ¿Pero qué pasa si forzamos la situación? ¿Qué pasa si codifico a mano mi ensamblador, y no pongo un salto de vuelta al principio?

Bueno, obviamente la CPU seguiría ejecutando las siguientes instrucciones. Estas se verían algo así:

addr    opco     instruction
----    ----     -----------
00E6    FFFF     NOP
00E8    FFFF     NOP
00EA    FFFF     NOP
00EB    FFFF     NOP
.
. some instructions removed for brevity
.
7EE8    FFFF     NOP
7FFA    FFFF     NOP
7FFC    FFFF     NOP
7FFE    FFFF     NOP

La siguiente dirección de memoria después de la última instrucción de main() está vacía. En un microcontrolador con memoria FLASH, una instrucción vacía contiene el valor 0xFFFF. En un PIC al menos, ese código op se interpreta como un 'nop', o 'ninguna operación'. Simplemente no hace nada. La CPU continuaría ejecutando esos nops hasta el final de la memoria.

¿Qué hay después de eso?

En la última instrucción, el puntero de instrucción de la CPU es 0x7FFe. Cuando la CPU añade 2 a su puntero de instrucción, obtiene 0x8000, lo que se considera un desbordamiento en un PIC con sólo 32k de FLASH, y por lo tanto se envuelve de nuevo a 0x0000, y la CPU continúa felizmente la ejecución de instrucciones de vuelta al principio del código, como si se hubiera reiniciado.


También has preguntado por la necesidad de apagar. Básicamente puedes hacer lo que quieras, y depende de tu aplicación.

Si tuvieras una aplicación que sólo necesitara hacer una cosa después del encendido, y luego no hacer nada más podrías simplemente poner un while(1); al final de main() para que la CPU deje de hacer nada notable.

Si la aplicación requiere que la CPU se apague, entonces, dependiendo de la CPU, probablemente habrá varios modos de suspensión disponibles. Sin embargo, las CPUs tienen el hábito de despertarse de nuevo, por lo que tendrías que asegurarte de que no hubiera un límite de tiempo para la suspensión, y que no estuviera activo el Watch Dog Timer, etc.

Incluso se podría organizar algún circuito externo que permitiera a la CPU cortar completamente su propia energía cuando hubiera terminado. Ver esta pregunta: Utilización de un pulsador momentáneo como interruptor basculante de encendido y apagado .

20voto

Mark Biek Puntos 41769

Para el código compilado, depende del compilador. El compilador Rowley CrossWorks gcc ARM que utilizo salta al código en el archivo crt0.s que tiene un bucle infinito. El compilador Microchip C30 para los dispositivos dsPIC y PIC24 de 16 bits (también basado en gcc) reinicia el procesador.

Por supuesto, la mayoría del software embebido nunca termina así, y ejecuta el código continuamente en un bucle.

9voto

hazzen Puntos 7315

Al preguntar por return estás pensando en un nivel demasiado alto. El código C se traduce en código máquina. Así que, si en lugar de eso piensas en el procesador sacando ciegamente las instrucciones de la memoria y ejecutándolas, no tiene ni idea de cuál es la "final" return . Así pues, los procesadores no tienen un fin inherente, sino que es el programador quien debe ocuparse del caso final. Como señala Leon en su respuesta, los compiladores han programado un comportamiento por defecto, pero muchas veces el programador puede querer su propia secuencia de apagado (yo he hecho varias cosas como entrar en un modo de bajo consumo y parar, o esperar a que se enchufe un cable USB y luego reiniciar).

Muchos microprocesadores tienen instrucciones de parada, que detienen el procesador sin afectar a los perhiperales. Otros procesadores pueden confiar en la "detención" simplemente saltando a la misma dirección repetidamente. Hay muchas opciones, pero depende del programador porque el procesador simplemente seguirá leyendo instrucciones de la memoria, incluso si esa memoria no estaba pensada para recibir instrucciones.

7voto

GetFree Puntos 495

La cuestión no es embebida (un sistema embebido puede ejecutar Linux o incluso Windows) sino independiente o bare-metal: el programa de aplicación (compilado) es lo único que se ejecuta en el ordenador (no importa si es un microcontrolador o un microprocesador).

Para la mayoría de los lenguajes, el lenguaje no define lo que sucede cuando el 'main' termina y no hay un sistema operativo al cual regresar. En el caso de C, depende de lo que haya en el archivo de inicio (a menudo crt0.s). En la mayoría de los casos el usuario puede (o incluso debe) suministrar el código de inicio, por lo que la respuesta final es: lo que usted escriba es el código de inicio, o lo que resulte estar en el código de inicio que usted especifique.

En la práctica hay tres enfoques:

  • no tomar ninguna medida especial. lo que sucede cuando el principal devuelve es indefinido.

  • saltar a 0, o utilizar cualquier otro medio para reiniciar la aplicación.

  • entrar en un bucle cerrado (o desactivar las interrupciones y ejecutar una instrucción de parada), bloqueando el procesador para siempre.

Lo adecuado depende de la aplicación. Una tarjeta de felicitación de pieles y un sistema de control de frenos (por mencionar dos sistemas integrados) probablemente deberían reiniciarse. El inconveniente de reiniciar es que el problema puede pasar desapercibido.

5voto

jason Puntos 147

El otro día estuve mirando un código desensamblado del ATtiny45 (C++ compilado por avr-gcc) y lo que hace al final del código es saltar a 0x0000. Básicamente haciendo un reset/reinicio.

Si ese último salto a 0x0000 es omitido por el compilador/ensamblador, todos los bytes en la memoria del programa son interpretados como código máquina "válido" y se ejecuta todo el camino hasta que el contador del programa se desplaza a 0x0000.

En el AVR un byte 00 (valor por defecto cuando una celda está vacía) es un NOP = No Operation. Por lo tanto, se ejecuta muy rápidamente, sin hacer nada, pero sólo tomando un poco de tiempo.

i-Ciencias.com

I-Ciencias es una comunidad de estudiantes y amantes de la ciencia en la que puedes resolver tus problemas y dudas.
Puedes consultar las preguntas de otros usuarios, hacer tus propias preguntas o resolver las de los demás.

Powered by:

X