36
\$\begingroup\$

What happens in an embedded processor when execution reaches that final return statement Does everything just freeze as it is; power consumption etc, with one long eternal NOP in the sky? or are NOPs continuously executed, or will a processor shut down altogether?

Part of the reason I ask is I am wondering if a processor needs to power down before it finishes execution and if it does how does it ever finish execution if it has powered down before hand?

\$\endgroup\$
4
  • 23
    \$\begingroup\$ It depends on your beliefs. Some say it will reincarnate. \$\endgroup\$
    – Telaclavo
    Commented Apr 28, 2012 at 11:48
  • 10
    \$\begingroup\$ Is it for a missile? \$\endgroup\$ Commented Apr 28, 2012 at 13:10
  • 7
    \$\begingroup\$ some systems support the HCF (Halt and Catch Fire) instruction. :) \$\endgroup\$ Commented Apr 28, 2012 at 22:30
  • 1
    \$\begingroup\$ it will branch to self destruction routine \$\endgroup\$
    – user924
    Commented May 27, 2012 at 16:10

9 Answers 9

45
\$\begingroup\$

This is a question my dad always used to ask me. "Why doesn't it just run through all the instructions and stop at the end?"

Let's take a look at a pathological example. The following code was compiled in Microchip's C18 compiler for the PIC18:

void main(void)
{

}

It produces the following assembler output:

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

As you can see, main() is called, and at the end contains a return statement, although we didn't explicitly put it there ourselves. When main returns, the CPU executes the next instruction which is simply a GOTO to go back to the beginning of the code. main() is simply called over and over again.

Now, having said this, this is not the way people would do things usually. I have never written any embedded code which would allow main() to exit like that. Mostly, my code would look something like this:

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

So I would never normally let main() exit.

"OK ok" you saying. All this is very interesting that the compiler makes sure there's never a last return statement. But what happens if we force the issue? What if I hand coded my assembler, and didn't put a jump back to the beginning?

Well, obviously the CPU would just keep executing the next instructions. Those would look something like this:

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

The next memory address after the last instruction in main() is empty. On a microcontroller with FLASH memory, an empty instruction contains the value 0xFFFF. On a PIC at least, that op code is interpreted as a 'nop', or 'no operation'. It simply does nothing. The CPU would continue executing those nops all the way down the memory to the end.

What's after that?

At the last instruction, the CPU's instruction pointer is 0x7FFe. When the CPU adds 2 to its instruction pointer, it gets 0x8000, which is considered an overflow on a PIC with only 32k FLASH, and so it wraps around back to 0x0000, and the CPU happily continues executing instructions back at the beginning of the code, just as if it had been reset.


You also asked about the need to power down. Basically you can do whatever you want, and it depends on your application.

If you did have an application that only needed to do one thing after power on, and then do nothing else you could just put a while(1); at the end of main() so that the CPU stops doing anything noticeable.

If the application required the CPU to power down, then, depending on the CPU, there will probably be various sleep modes available. However, CPUs have a habit of waking up again, so you'd have to make sure there was no time limit to the sleep, and no Watch Dog Timer active, etc.

You could even organise some external circuitry that would allow the CPU to completely cut its own power when it had finished. See this question: Using a momentary push button as a latching on-off toggle switch.

\$\endgroup\$
20
\$\begingroup\$

For compiled code, it depends on the compiler. The Rowley CrossWorks gcc ARM compiler that I use jumps to code in the crt0.s file that has an infinite loop. The Microchip C30 compiler for the 16-bit dsPIC and PIC24 devices (also based on gcc) resets the processor.

Of course, most embedded software never terminates like that, and executes code continuously in a loop.

\$\endgroup\$
0
13
\$\begingroup\$

There are two points to be made here:

  • An embedded program, strictly speaking, cannot "finish".
  • There is very rarely a need to run an embedded program for some time and then "finish".

The concept of a program shutdown doesn't normally exist in an embedded environment. At a low level a CPU will execute instructions while it can; there is no such thing as a "final return statement". A CPU may stop execution if it encounters an unrecoverable fault or if explicitly halted (put into a sleep mode, a low power mode, etc), but note that even sleep modes or unrecoverable faults do not generally guarantee that no more code is going to be executed. You can wakeup from sleep modes (that's how they're normally used), and even a locked up CPU can still execute a NMI handler (this is the case for Cortex-M). A watchdog will still run, too, and you may not be able to disable it on some microcontrollers once it's enabled. The details vary greatly between architectures. You will need to read relevant manuals really carefully if you want to ensure certain behavior (see below why you shouldn't attempt to do that anyway).

In case of firmware written in a language such as C or C++, what happens if main() exits is determined by the startup code. For example, here is the relevant part of the startup code from the STM32 Standard Peripheral Library (for a GNU toolchain, comments are mine):

Reset_Handler:  
  /*  ...  */
  bl  main    ; call main(), lr points to next instruction
  bx  lr      ; infinite loop

This code will enter an infinite loop when main() returns, although in a non-obvious way (bl main loads lr with the address of the next instruction which is effectively a jump to itself). No attempts are made to halt the CPU or make it enter a low-power mode, etc. If you have a legitimate need for any of that in your application you will have to do it yourself.

Note that as specified in the ARMv7-M ARM A2.3.1, the link register is set to 0xFFFFFFFF on reset, and a branch to that address will trigger a fault. So the designers of Cortex-M decided to treat a return from the reset handler as abnormal, and it's hard to argue with them.

Speaking of a legitimate need to stop the CPU after the firmware is finished, it's hard to imagine any that wouldn't be better served by a powerdown of your device. (If you do disable your CPU "for good" the only thing that can be done to your device is a power cycle or external hardware reset.) You can deassert an ENABLE signal for your DC/DC converter or turn your power supply off in some other way, like an ATX PC does.

\$\endgroup\$
6
  • 1
    \$\begingroup\$ "You can wakeup from sleep modes (that's how they're normally used), and even a locked up CPU can still execute a NMI handler (this is the case for Cortex-M). " <-- sounds like the awesome part of a book or movie plot. :) \$\endgroup\$
    – Mark Allen
    Commented Apr 27, 2012 at 20:40
  • \$\begingroup\$ The "bl main" will load "lr" with the address of the following instruction (the "bx lr"), will it not? Is there any reason to expect "lr" to contain anything else when the "bx lr" is executed? \$\endgroup\$
    – supercat
    Commented Apr 27, 2012 at 21:17
  • \$\begingroup\$ @supercat: you're right of course. I edited my answer to remove the error and expand it a little. Thinking on this, the way they implement this loop is pretty strange; they could have easily done loop: b loop. I wonder if they actually meant to do a return but forgot to save lr. \$\endgroup\$
    – Thorn
    Commented Apr 28, 2012 at 14:16
  • \$\begingroup\$ It is curious. I would expect that a lot of ARM code would exit with LR holding the same value it held on entry, but don't know that it's guaranteed. Such a guarantee wouldn't often be useful, but upholding it would require adding an instruction to routines which copy r14 to some other register and then call some other routine. If lr is considered "unknown" on return, one could "bx" the register holding the saved copy. That would cause very odd behavior with the indicated code, though. \$\endgroup\$
    – supercat
    Commented Apr 29, 2012 at 0:26
  • \$\begingroup\$ Actually I'm pretty sure that non-leaf functions are expected to save lr. These usually push lr onto the stack in the prolog and return by popping the saved value into pc. This is what e.g. a C or C++ main() would do, but the developers of the library in question obviously didn't do anything like this in Reset_Handler. \$\endgroup\$
    – Thorn
    Commented Apr 29, 2012 at 3:24
9
\$\begingroup\$

When asking about return, you're thinking too high level. The C code is translated into machine code. So, if you instead think about the processor blindly pulling instructions out of memory and executing them, it has no idea which one is the "final" return. So, processors have no inherent end, but instead it's up to the programmer to handle the end case. As Leon points out in his answer, compilers have programmed a default behavior, but often times the programmer may want their own shutdown sequence (I have done various things like entering a low power mode and halting, or waiting for a USB cable to get plugged in and then rebooting).

Many microprocessors have halt instructions, which stops the processor without affecting perhiperals. Other processors may rely on "halting" by simply just jumping to the same address repeatedly. There are may options, but it's up to the programmer because the processor will simply keep reading instructions from meory, even if that memory wasn't intented to be instructions.

\$\endgroup\$
7
\$\begingroup\$

The issue is not embedded (an embedded system can run Linux or even Windows) but stand-alone or bare-metal: the (compiled) application program is the only thing that is running on the computer (It does not matter if it is a microcontroller or microprocessor).

For most languages the language does not define what happens when the 'main' terminates and there is no OS to return to. For C it depends on what is in the startupfile (often crt0.s). In most cases the user can (or even must) supply the startup code, so the final answer is: whatever you write is the startup code, or what happens to be in the startup code you specify.

In practice there are 3 approaches:

  • take no special measures. what happens when the main returns is undefined.

  • jump to 0, or use any other means to restart the application.

  • enter a tight loop (or disabling interrupts and executing a halt instruction), locking up the processor forever.

What is appropriate depends on the application. A fur-elise greeting card and a brake-control-system (just to mention two embedded systems) should probably restart. The downside of restarting is that the problem might go unnoticed.

\$\endgroup\$
5
\$\begingroup\$

I was looking at some ATtiny45 disassembled (C++ compiled by avr-gcc) code the other day and what it does at the end of the code is jump to 0x0000. Basically doing a reset/restart.

If that last jump to 0x0000 is being left out by the compiler/assembler, all bytes in program memory are interpreted as 'valid' machine code and it runs all the way until the program counter rolls over to 0x0000.

On AVR a 00 byte (de default value when a cell is empty) is a NOP = No Operation. So it runs really quickly, doing nothing but just taking some time.

\$\endgroup\$
1
\$\begingroup\$

Generally compiled main code is afterwards linked with startup code (it might be integrated into toolchain, provided by chip vendor, written by you etc.).

Linker then places all application and startup code in memory segments, so answer to you questions depends on: 1. code from startup, because it can for example:

  • end with empty loop (bl lr or b .), which will be similar to "program end", but interrupts and peripherals enabled previously will still operate,
  • end with jump to beginning of program (either completely re-run startup or jsut to main).
  • simply ignore "what will be next" after call to main returns.

    1. In the third bullet, when program counter simply increments after returning from main behavior will depend on you linker (and/or linker script used during linking).
  • If other function/code is placed after your main it will be executed with invalid/undefined argument values,

  • If following memory starts with bad instruction exception migh be generated and MCU will eventually reset (if exception generates reset).

If watchdog is enabled, it will eventually reset MCU despite all endless loops you are in (of course if it will be not reloaded).

\$\endgroup\$
-1
\$\begingroup\$

The best way to stop an embedded device is to wait forever with NOP instructions.

The second way is to closing the device by using device itself. If you can control a relay with your instructions, you can just open the switch which is powering your embedded device and huh your embedded device is gone with no power consumption.

\$\endgroup\$
1
  • \$\begingroup\$ That really doesn't answer the question. \$\endgroup\$
    – Matt Young
    Commented Dec 23, 2014 at 13:48
-4
\$\begingroup\$

It was clearly explained in the manual. Typically an general exception will be thrown by the CPU because it will access a memory location which is outside the stack segment. [ memory protection exception ].

What did you meant by the embedded system? Microprocessor or microcontroller ?Either ways , it's defined on the manual.

In x86 CPU we turn off the computer by sending the command to the ACIP controller. Entering the System Management Mode. So that controller is a I/O chip and you don't need to manually turn it off.

Read the ACPI specification for more information.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ -1 : the TS did not mention any specific CPU, so don't assume too much. Different systems handle this case in very different ways. \$\endgroup\$ Commented Apr 28, 2012 at 13:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.