17

What happens if there is a runtime error in a program? Will execution of the program just stop? Is there some way I can get the Arduino to tell me what the error is?

6 Answers 6

21

First, let's see a few examples of what can go wrong.

Uninitialized local variables

void setup() {
  int status;
  pinMode(13, OUTPUT);
  digitalWrite(13, status);
} 

As pointed out by Edgar Bonet in the comments, local variables like status in the code above are not implicitly initialized by the C++ compiler. So, the outcome of the code above is indeterminate. To avoid that, make sure you always assign values to your local variables.

Things are a bit different with global and static variables:

Global and static variables are guaranteed to be initialized to 0 by the C standard.

Source: AVR Libc Reference Manual - Frequently Asked Questions - Shouldn't I initialize all my variables?

That means you shouldn't worry about initializing them to 0 in your code. In fact, you should really avoid it, as the initialization may waste memory. Only initialize them to values other than 0.

Memory overflow

int array[10];
int v = array[100];
array[-100] = 10;

The first problem here is that you don't know what will be assigned to v, but worse is that you don't know what you messed up with the assignment to position -100 of array.

Jump to an illegal instruction

void doSomething( void ) { 
    for (int i = 0; i < 1000; i++); 
}

void setup () 
{
    void (*funcPtr)( void );

    funcPtr = &doSomething;
    funcPtr(); // calls doSomething();

    funcPtr = NULL;
    funcPtr(); // undefined behavior
}

The first call to funcPtr() will actually be a call to doSomething(). Calls like the second one may lead to undefined behavior.

Other bad things that may happen

Well, you can run out of RAM, for example. What else. In any case, I think your program will keep running, probably not the way you intended it to.

Kinds of Protection

In computer systems, problems like these are usually dealt with at various levels:

  1. By the compiler
  2. By the programming language runtime (as in Java for example).
  3. By the operating system or the processor (if your memory access a position outside the boundaries of the address space reserved to your program, the OS or the processor may have safety mechanisms to prevent that)

Arduinos only have limited protection of the compiler, and probably nothing else. The good news is that they aren't multi-tasked, so the only program being affected is yours. In any case, any of those bugs will lead to erratic behavior.

The Answers

The assumptions are the all of the problems I stated above are runtime problems.

What happens if there is a runtime error in a program?

The program will continue and what happens will depend on the side-effects of the runtime error. A call to the null function pointer will probably make the program jump to an unknown location.

Will execution of the program just stop?

No, it will keep going as if nothing extraordinary happened, probably doing what you didn't intend it to do. It may reset or act erratically. It may turn some inputs into outputs and burn a sensor or two (but that's highly unlikely).

Is there some way I get the Arduino to tell me what the error is?

I don't think so. As I said earlier, the protection mechanisms aren't there. There's no runtime support from the language, no OS, no hardware checks for out-of-bounds memory access (the bootloader doesn't count as either). You just have to be careful with your program and probably set your own safety nets.

The reason for the lack of protection is probably because Arduino controllers are too cheap, have too little memory, and should not run anything too important (yes, there seems to be a disclaimer by AVR somewhere for you not to use the MCUs normally used by Arduino in life support systems).

18
  • 1
    Great! The best answer I have seen on Arduino.SE so far! Feb 14, 2014 at 22:24
  • 1
    Thanks!! I think we should strive to give great answers as much as possible. But it worries me a little the fact that we don't have that many REAL EE EXPERTS that could look at answers like mine and find any glaring mistakes. Actually, that's the reason I posted the answer even though I don't know that much about AVR MCUs. That's to see if we get someone to correct it. We sure don't want smart pents like me saying stuff that isn't right and getting away with it. But that's probably a discussion for the Meta site.
    – Ricardo
    Feb 14, 2014 at 22:29
  • 5
    @Ricardo - One comment I would make is that non-explicitly initialized variables are not necessarily uninitialized. Variables defined outside of functions generally have what's termed "automatic storage duration", which then get default-initialized to zero. See en.cppreference.com/w/cpp/language/default_initialization for more information. The initialization behaviour is complex enough that it's probably dangerous to rely upon, but making blanket statements is probably not a great idea. Feb 24, 2014 at 4:38
  • 1
    Furthermore, the SRAM is initialized to 0 at reset or startup, so you can make some informed guesses about uninitialized variables, if you want to live dangerously. You shouldn't rely on this behaviour, but it is interesting. Feb 24, 2014 at 4:39
  • 1
    There is an interesting example of what happens when you run out of SRAM here: electronics.stackexchange.com/questions/42049/…. Basically, the stack clobbers part of the heap, or vice versa. This can do interesting things like corrupt some part of the stack-frame (breaking function returns, etc), or writing invalid data to variables. Feb 24, 2014 at 4:44
9

There are no runtime exceptions. There is only undefined behaviour.

Really, there are no exceptions at all. If you try to perform an invalid operation, it's results will be unknown.

There is no runtime checking at all, except what you implement. Your program is running on bare-metal hardware. It's the Desktop equivalent of running in ring-0 all the time, because the ATmega doesn't have rings.

6

There is one mechanism that can get MCU from erratic state and it's the watchdog timer. If you're implementing some code that will repeatedly run in a loop, that will not run anytime longer than some fixed time, you can set this time as watchdog period and enable the timer.

Then, you have to repeatedly reset the timer in the loop. If your code freezes at some condition loop that will never end, then the watchdog will count to zero and eventually reset the MCU.

This way you are losing data, but if you run the AVR WDT in interrupt mode, you can store some data before resetting the MCU.

So the watchdog timer can guard your code from occasional unintended endless loops.

Documentation: AVR132: Using the Enhanced Watchdog Timer

5

You'd need a hardware debugger for something like this. But usually you'll see the program not behaving as you expect it to and will have to look at that section of the code to identify the problem.

A common/quick/easy way to do this is to add print statements to print out the values of variables or just anything so you know the program gets to that point in the code without a problem. This will help you isolate the problem further.

I believe VisualMicro has some debugging functionality built in.

3

I would assume the AVR CPU doesn't have any error detection or recovery tools. It may just stop, or keep going ignoring the error and the consequences. Like sachleen said, you should add some debug statements in your program that print out data in the middle of an operation, to test if it's working. If you use an emulaor and set breakpoints, you could easily find a problem.

-1

I wrote an *.ino environment:

// Testing environment for Arduino sketch files.
// Aduino IDE 2.3.0
// Author   : Anders Munck
// Released : 2024 02 09

#include <iostream>

const bool LOW=false;
const bool HIGH=true;
const bool INPUT=false;
const bool OUTPUT=true;
const unsigned char LED_BUILTIN=13;

using namespace std;

void where(std::string hank)
{ cout <<"Where : "<<hank<<"\n"; }

int random(int max)
{ return std::rand(); }

unsigned long klokken=0;
unsigned long int millis()
{ klokken++;
  cout <<"When : "<< klokken <<"\n";
  return klokken;
}

class SERIAL
{ public:
    void begin(int Baud)
    { cout<<"Arduino sketch : Serial.begin()\n"; };
    void end()
    { cout<<"Arduino sketch : Serial.end()\n"; };
    void print(std::string A)
    { cout<<"Arduino sketch : print()\n"; };
    void println(std::string A)
    { cout<<"Arduino sketch : println()\n"; };
} Serial=SERIAL();

void digitalWrite(unsigned char A,bool S)
{ cout << "Arduino sketch : digitalWrite()\n"; }

void pinMode(unsigned char A,bool S)
{ cout << "Arduino sketch : pinMode()\n"; }

// Include Your Arduino sketch file here:
#include "signal.ino"

int main()
{ setup();
  for(int i=0;i<100;i++){ loop(); };
  return 0;
}

The purpose is to get run time error messages from the *.ino sketch.

You will not get usefull timing information this way, as the program is running on another processor.

I run it with a GPU C++ IDE.

Please regard my code as preliminary ad-hoc program; You may need to develop further for Your needs.

2
  • 1
    The downvote is not mine. You need to explain your code and what it does. Feb 16 at 11:12
  • 1
    the Question is 10 years old. just saying in case you didn't notice
    – Juraj
    Feb 16 at 12:15

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.