Functions Limit?

Problem:
I'm working on a large project and I wrote a lot of functions to keep a better overview. It seems that my Arduino restarts when I do certain actions.

I thought the error was in the code and I used comments to work through each function bit by bit. I recognised that it started to behave bad as soon as I used a certain function. But it didn't even matter what the function was doing!!! In the end it was just this:

function(){
  delay(10);
}

Solution:
I removed almost every function and just wrote the code into the loop() which was in the functions. I haven't had any problems since. But now my code is just looking really bad and I constantly lose the overview.

Question:
Is there a limit to the amount of functions I'm allowed to declare?

No, you can have as many functions as you have memory for. But there is a very finite limit to how much FLASH and RAM you have to work with. Eventually, you will run out of one or the other, and your program will most likely behave erratically.

Regards,
Ray L.

Careful, Ray:
While there is no limit to the number of functions you can have, there is a limit to the amount of "nesting" available to you. The Arduino's stack space is very limited, and every function call consumes a few bytes + any automatic variables. So while you can call thousands of functions inside the loop() function, you cannot have a function call a function which calls a function... a thousand times. This is also why recursive algorithms so beloved by computer scientists are not a good idea on the Arduino.

I'd suggest a few debug statements sprinkled in with the function calls, using the free memory function:

http://playground.arduino.cc/Code/AvailableMemory

My guess is that the heap and the stack are colliding at some point during the program. The stack grows downward from the top of SRAM and the heap from the bottom up. If they meet, it forms a 2nm black hole into which your code collapses and goes supernova.

KeithRB:
Careful, Ray:
While there is no limit to the number of functions you can have, there is a limit to the amount of "nesting" available to you. The Arduino's stack space is very limited, and every function call consumes a few bytes + any automatic variables. So while you can call thousands of functions inside the loop() function, you cannot have a function call a function which calls a function... a thousand times. This is also why recursive algorithms so beloved by computer scientists are not a good idea on the Arduino.

My answer is correct as it stands. There is no limit to the number of functions you can have, other than memory limitations. If your functions use a lot of memory, you can have fewer of them. Recursive functions consume RAM, specifically stack space, which is what limits depth of recursion. There is no limit to the number of stupid things you can do IN one, or a few functions that will make you run out of memory. You can't have a function that recurses deeply, or declares an array of 100,000 ints, or a hundred other things, because you WILL run out of memory quickly. But that is not a language limitation, it's simply bad programming.

Regards,
Ray L.

You're both correct. Recursive function calls, unless your doing something trivial, will eventually blow the stack away and they should be avoided. That said, Ray is correct in that you can call functions all day long provided they don't run out of stack space doing something silly (e.g., allocating a huge temporary array). Because the stack is cleaned when the function call returns, in theory I can call a function a million times and never run out of stack space.

My question - is that really how you are declaring your function - without a return type?

cr0sh:
My question - is that really how you are declaring your function - without a return type?

Nothing at all wrong with that. If you don't declare a return type, the return type is implicitly void. One can argue (and I'm sure someone will...) that it's not good style, and it's certainly not ANSI compliant, but it is perfectly legal c syntax.

Regards,
Ray L.

Is that C++? In C, if you omit the return specifier, int is assumed. In C99, the return specifier is required.

Functions are written in C / C++ but they are compiled and never underestimate the smartness of the compilers.

Check this recursive function

int calls = 0;

void f()
{
  Serial.println(calls++);
  delay(100); // to empty serial;
  f();
}

void setup() 
{
  Serial.begin(115200);
  f();
}

void loop() 
{
}

RayLivingston:
Nothing at all wrong with that.

On this forum, that is extremely bad advice.

function(){
  delay(10);
}

void setup( void ) {}
void loop( void ) {}
sketch_jan13a:2: error: ISO C++ forbids declaration of 'function' with no type
sketch_jan13a.ino: In function 'int function()':
sketch_jan13a.ino:4: warning: no return statement in function returning non-void

After adding a return to eliminate the second error...

sketch_jan13a:2: error: ISO C++ forbids declaration of 'function' with no type

Soooooo..... Where's the beef? The Arduino compiler options are set to enforce ISO/ANSI. I specifically stated this style did NOT comply with ANSI (and hence ISO). but it is nonetheless valid c language syntax.

Nobody here is going to have a serious problem because of this, specifically because the compiler will tell them it's not allowed in this environment, and why, just as it did for you. Turn off ANSI/ISO enforcement, and it will compile and run just fine.

Regards,
Ray L.

The "beef" is your lack of empathy for the intended audience.

You can write recursive functions that never go more than a few calls deep and that can be Arduino-safe. But you'd better know exactly what you're doing and why it HAS to work that way and not just run a few tests of expected values unless you like chasing down bugs.

An example of how to do that would be generating a divergent series with a built-in not-large limit.

t0b1z:
Solution:
I removed almost every function and just wrote the code into the loop() which was in the functions. I haven't had any problems since. But now my code is just looking really bad and I constantly lose the overview.

Question:
Is there a limit to the amount of functions I'm allowed to declare?

Don't do that. You should be able to have lots of functions. I doubt you are using recursion*. However this is all speculation, eh? Post your code, the one that crashes.

http://snippets-r-us.com/

  • Because you were able to rewrite without using lots of functions.

I did add some lines of code in the last 3 hours so here is a version without a lot of functions, that produces the same error (After I added RTC-Support).

FYI: The Annikken Andee is a Bluetooth Shield which lets me communicate with my iPhone and the Arduino. This also might be the source of the errors. The Arduino-Restart occurs, when I press a certain button on my iPhone. But maybe someone finds something regardless. :slight_smile:

I switched to the Arduino Mega and I tried to optimise the variables, so memory shouldn't be a problem.

#include <Andee.h>
#include <SPI.h>
#include <LiquidCrystal.h>
#include <Wire.h>
#include "ds3231.h" //Library for the DS3231 RTC-Chip

uint8_t time[8];
char recv[128];
unsigned int recv_size = 0;

const unsigned int MAIN = 0;
const unsigned int COL_CHANGE = 1;

const byte PIN_G = 3;
const byte PIN_R = 5;
const byte PIN_B = 6;

byte r = 0;
byte g = 0;
byte b = 0;

AndeeHelper displayTime;
AndeeHelper displayAlarm;
AndeeHelper alarmInputButton;
AndeeHelper changeColorButton;

AndeeHelper redSlider;
AndeeHelper greenSlider;
AndeeHelper blueSlider;

bool mainState = true;

boolean sleeping = true;

bool alarm = false;

char timeString[30]; 
char alarmString[30]; 

int timeHour; 
int timeMinute;
int timeSecond;

int  alarmHour = 8; 
int  alarmMinute = 0;
int  alarmSecond = 0;

bool timeNotSet = true;

LiquidCrystal lcd(9, 10, 2, 4, 7, 8);

byte clock[8] = {
  B00000,
  B00000,
  B01110,
  B10101,
  B10111,
  B10001,
  B01110,
  B00000
};

void setup(){
  Serial.begin(9600);
  
  Wire.begin();
  DS3231_init(DS3231_INTCN);
  memset(recv, 0, 128);

  lcd.begin(16, 2);
  lcd.createChar(0, clock);

  Andee.begin();

  displayTime.setId(0);
  displayTime.setType(DATA_OUT);
  displayTime.setLocation(0,0,FULL);
  displayTime.setTitle("Time");
  displayTime.setColor(THEME_MIDNIGHT_DARK);

  displayAlarm.setId(1);
  displayAlarm.setType(DATA_OUT);
  displayAlarm.setLocation(1,0,FULL);
  displayAlarm.setTitle("Alarm");
  displayAlarm.setColor(THEME_MIDNIGHT_DARK);

  alarmInputButton.setId(2);
  alarmInputButton.setType(TIME_IN); // Sets object as a time input button
  alarmInputButton.setLocation(2,0,FULL);
  alarmInputButton.setTitle("Set Alarm");
  alarmInputButton.setColor(THEME_MIDNIGHT_DARK);

  changeColorButton.setId(3);
  changeColorButton.setType(BUTTON_IN); // Sets object as a time input button
  changeColorButton.setLocation(3,0,FULL);
  changeColorButton.setTitle("Change LED-WakeUp-Color");
  changeColorButton.requireAck(true);
  changeColorButton.setColor(THEME_MIDNIGHT_DARK);

  // Draw red slider
  redSlider.setId(4);  
  redSlider.setType(SLIDER_IN); // Set object as a slider
  redSlider.setLocation(0, 0, FULL); // Sliders can only be full size
  redSlider.setTitle("Red Channel");
  redSlider.setSliderMinMax(0, 255);
  redSlider.setSliderInitialValue(0);  
  redSlider.setSliderNumIntervals(256); // Set as discrete slider
  redSlider.setSliderReportMode(ON_VALUE_CHANGE); // Update values as you're moving
  redSlider.setSliderColor(THEME_RED); // Slider bar colour
  redSlider.setColor(THEME_RED_DARK); // Slider background colour  

  // Draw green slider
  greenSlider.setId(5);  
  greenSlider.setType(SLIDER_IN); // Set object as a slider
  greenSlider.setLocation(1, 0, FULL); // Sliders can only be full size
  greenSlider.setTitle("Green Channel");
  greenSlider.setSliderMinMax(0, 255);
  greenSlider.setSliderInitialValue(0);  
  greenSlider.setSliderNumIntervals(256); // Set as discrete slider
  greenSlider.setSliderReportMode(ON_VALUE_CHANGE); // Update values as you're moving
  greenSlider.setSliderColor(THEME_GREEN); // Slider bar colour
  greenSlider.setColor(THEME_GREEN_DARK); // Slider background colour  

  // Draw blue slider
  blueSlider.setId(6);  
  blueSlider.setType(SLIDER_IN); // Set object as a slider
  blueSlider.setLocation(2, 0, FULL); // Sliders can only be full size
  blueSlider.setTitle("Blue Channel");
  blueSlider.setSliderMinMax(0, 255);
  blueSlider.setSliderInitialValue(0);  
  blueSlider.setSliderNumIntervals(256); // Set as discrete slider
  blueSlider.setSliderReportMode(ON_VALUE_CHANGE); // Update values as you're moving
  blueSlider.setSliderColor(THEME_BLUE); // Slider bar colour
  blueSlider.setColor(THEME_BLUE_DARK); // Slider background colour 

}

void loop(){

 //////////CLOCK///////////
  char in;
  unsigned long now = millis();
  struct ts t;

  DS3231_get(&t);

  sprintf(timeString, "%02d:%02d:%02d", t.hour, t.min, t.sec);

  /////////////////////////// 


  //Update State
  if(mainState == true)
  {
    //UpdateMain
    displayTime.update();
    displayAlarm.update();

    alarmInputButton.update();

    changeColorButton.setTitle("Change LED-WakeUp-Color");
    changeColorButton.update();

    //Show LED Color
    analogWrite(PIN_G, 0);
    analogWrite(PIN_R, 0);
    analogWrite(PIN_B, 0);
  }
  if(mainState == false)
  {
    redSlider.update();
    greenSlider.update();
    blueSlider.update();
    changeColorButton.setTitle("BACK");
    changeColorButton.update();

    //Show LED Color
    analogWrite(PIN_G, redSlider.getSliderValue(INT));
    analogWrite(PIN_R, greenSlider.getSliderValue(INT));
    analogWrite(PIN_B, blueSlider.getSliderValue(INT));
  }

  if( alarmInputButton.isPressed() )
  { 
    alarmInputButton.setDefaultTime(timeHour, timeMinute, 0); // Format: hour, minute, second
    alarmInputButton.ack();
    alarmInputButton.getTimeInput(&alarmHour, &alarmMinute, &alarmSecond); 
  }

  
  if((t.hour == 0 && t.min == 0 && t.sec == 00 || timeNotSet) && Andee.isConnected()){
    Andee.getDeviceTime(&timeHour, &timeMinute,&timeSecond);
    sprintf(timeString, "T%02d%02d%02d%01d%01d%d",t.sec, t.min, t.hour, t.wday, t.mday, t.mon, t.year);

    struct ts t;

    t.sec = timeSecond;
    t.min = timeMinute;
    t.hour = timeHour;
    t.wday = 1;
    t.mday = 01;
    t.mon = 01;
    t.year = 2014;
    DS3231_set(t);

    timeNotSet = false;
  }


  displayTime.setData(timeString);


  sprintf(alarmString, "%02d:%02d", alarmHour, alarmMinute);
  displayAlarm.setData(alarmString);


  if( changeColorButton.isPressed() )
  { 
    changeColorButton.ack(); 
    if(mainState == true)
    {
      mainState = false;
      Andee.clear();
    }
    else
    {
      mainState = true;
      Andee.clear();
      alarmInputButton.setId(2);
      alarmInputButton.setType(TIME_IN); // Sets object as a time input button
      alarmInputButton.setLocation(2,0,FULL);
      alarmInputButton.setTitle("Set Alarm");
      alarmInputButton.setColor(THEME_MIDNIGHT_DARK);
    }
  }

  //Check Alarm
  if(timeHour == alarmHour && timeMinute == alarmMinute && alarm == true)
  {
    sleeping = false;
  }

  //checkWakeUp();
  if(sleeping == false)
  {  

    for (int brightness=0; brightness<240; brightness++){

      r += (float)redSlider.getSliderValue(FLOAT) / 240.00; 
      g += (float)greenSlider.getSliderValue(FLOAT) / 240.00; 
      b += (float)blueSlider.getSliderValue(FLOAT) / 240.00;

      analogWrite(PIN_R,r);
      analogWrite(PIN_G,g);
      analogWrite(PIN_B,b);

      delay(250);
    }

    sleeping = true;
    r = 0;
    g = 0;
    b = 0;

  }


  lcd.setCursor(4, 0);
  lcd.print(timeString);
  lcd.setCursor(4, 1);
  lcd.print(alarmString);

  if(analogRead(5) > 1000)
  {
    lcd.setCursor(9, 1);
    lcd.print(" ");
    lcd.write((uint8_t)0);
  }
  else{
    lcd.setCursor(9, 1);
    lcd.print("  ");
  }

  delay(500);

}

AndeeLightAlarmRelease.ino (6.78 KB)

Need the file Andee.h to compile it. Please supply.

And ds3231.h.

t0b1z:
I'm working on a large project and I wrote a lot of functions to keep a better overview. It seems that my Arduino restarts when I do certain actions.

What actions? Surely that is a clue.

Here's a useful function.

int freeRam () {
  extern int __heap_start, *__brkval; 
  int v; 
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}

How about some comments so us non-mind readers can understand what it does?