Exit function back to loop without running the calling function

I think my lack of programming knowledge is going to show on this one - let's say I have a function C that's called by function B that's called by function A that's called by loop(). What is the correct way to exit function C to loop()?

I originally just returned from function C, and that's when I found out that just took me back to function B (duh...). Problem is I don't want B to run all over again. How do I do this?

Here's some demonstrative code I was testing:

// Test -- Button hold blocking

// Pushbutton Pins
#define PREVIOUS_BUTTON_PIN 30
#define NEXT_BUTTON_PIN 40
#define MENU_BACK_BUTTON_PIN 32
#define SELECT_BUTTON_PIN 38

// Relay Connection Pins
#define CIRCFANPIN 26 // Pin circ fan control relay is connected to
#define VENTFANPIN 28 // Pin vent fan control relay is connected to
#define HUMIDIFIERPIN 24 // Pin humidifier control relay is connected to
#define HEATERPIN 22 // Pin heater control relay is connected to

bool previous_btn() {
	return (digitalRead(PREVIOUS_BUTTON_PIN) == LOW) ? true : false;
}

bool next_btn() {
	return (digitalRead(NEXT_BUTTON_PIN) == LOW) ? true : false;
}

bool menu_back_btn() {
	return (digitalRead(MENU_BACK_BUTTON_PIN) == LOW) ? true : false;
}

bool select_btn() {
	return (digitalRead(SELECT_BUTTON_PIN) == LOW) ? true : false;
}

void setup() {
	// Set all button pins to input
	pinMode(PREVIOUS_BUTTON_PIN, INPUT_PULLUP);
	pinMode(NEXT_BUTTON_PIN, INPUT_PULLUP);
	pinMode(MENU_BACK_BUTTON_PIN, INPUT_PULLUP);
	pinMode(SELECT_BUTTON_PIN, INPUT_PULLUP);

	// Set relay pins to output
	pinMode(CIRCFANPIN, OUTPUT);
	pinMode(VENTFANPIN, OUTPUT);
	pinMode(HUMIDIFIERPIN, OUTPUT);
	pinMode(HEATERPIN, OUTPUT);

	digitalWrite(CIRCFANPIN, HIGH);
	digitalWrite(VENTFANPIN, HIGH);
	digitalWrite(HUMIDIFIERPIN, HIGH);
	digitalWrite(HEATERPIN, HIGH);
}

void loop() {
	if (previous_btn()) {
		while (previous_btn()) {

		}
		first_loop();
	}
}
	
void first_loop() {
	digitalWrite(CIRCFANPIN, LOW);

	if (previous_btn()) {
		while (previous_btn()) {

		}
		second_loop();
	}
	first_loop();
}

void second_loop() {
	digitalWrite(VENTFANPIN, LOW);

	if (previous_btn()) {
		while (previous_btn()) {

		}
		third_loop();
	}
	second_loop();
}

void third_loop() {
	digitalWrite(HUMIDIFIERPIN, LOW);

	if (previous_btn()) {
		while (previous_btn()) {

		}
		fourth_loop();
	}
	third_loop();
}
	
void fourth_loop() {
	digitalWrite(HEATERPIN, LOW);

	if (previous_btn()) {
		while (previous_btn()) {

		}
		digitalWrite(CIRCFANPIN, HIGH);
		digitalWrite(VENTFANPIN, HIGH);
		digitalWrite(HUMIDIFIERPIN, HIGH);
		digitalWrite(HEATERPIN, HIGH);

		return;
	}

	fourth_loop();
}

In this example, fourth_loop is like function C, third_loop is B, second_loop is A, first_loop has no corollary to my above example, and loop is, well, loop.

Thanks Delta_G. I guess I should've been able to come up with that. Easy to implement, but that still seems bulky to me though - is there no better/more efficient way to do this?

Man guess I hit a sensitive spot haha - relax man, no need to get so defensive. In my opinion it seems bulky, in yours it doesn't. Not a big deal. I was just curious if others had any ideas. For now, I'm going to procced by using your suggestion because it's simple and I understand it. I definitely appreciate the help/time you're donating.

Ya, I actually was somewhat mindlessly coding this I guess - it never occurred to me that this would be functions calling themselves as opposed to just looping. It was actually going to be another post of mine, but I haven't searched the forums yet to see what other have done and best ways to proceed. This one's actually going to be a pain for me in my non-example code: big rewrite coming up.

Those calls to the next loop are all at the ends of those functions. Why can't they return to loop and let loop call the next one?

Do you mean at the end of the if statements?

My only thought right now for how to do this is to have some sort of flag set up in the if-statements that can be used in the loop to direct which function to go to. I'm still trying to wrap my head around it though.

No, please see #4.

What do you mean #4?

I just had a thought about this - I can use a while loop to within each of the functions to block and keep me there until the button is pressed. That way no need to call recursively, it'll just loop in the while

Who got defensive? All I said was that adding a couple of lines isn't bulky. It's just a couple of lines.

Sorry. Must've misinterpreted.

Or was it the rest? Yeah, I do get a little tired of giving someone an answer only to have them ask me if it is really the answer. That's a little more than annoying. If you want to ask if anyone else has a way then go for it. But to direct a question at me to ask if the answer I gave you is really the answer is not something I can understand the sense in.

...or not.

Anyways, this incorporates your suggestion and my thought.

// Test -- Button hold blocking

// Pushbutton Pins
#define PREVIOUS_BUTTON_PIN 30
#define NEXT_BUTTON_PIN 40
#define MENU_BACK_BUTTON_PIN 32
#define SELECT_BUTTON_PIN 38

// Relay Connection Pins
#define CIRCFANPIN 26 // Pin circ fan control relay is connected to
#define VENTFANPIN 28 // Pin vent fan control relay is connected to
#define HUMIDIFIERPIN 24 // Pin humidifier control relay is connected to
#define HEATERPIN 22 // Pin heater control relay is connected to

bool previous_btn() {
	return (digitalRead(PREVIOUS_BUTTON_PIN) == LOW) ? true : false;
}

bool next_btn() {
	return (digitalRead(NEXT_BUTTON_PIN) == LOW) ? true : false;
}

bool menu_back_btn() {
	return (digitalRead(MENU_BACK_BUTTON_PIN) == LOW) ? true : false;
}

bool select_btn() {
	return (digitalRead(SELECT_BUTTON_PIN) == LOW) ? true : false;
}

void setup() {
	// Set all button pins to input
	pinMode(PREVIOUS_BUTTON_PIN, INPUT_PULLUP);
	pinMode(NEXT_BUTTON_PIN, INPUT_PULLUP);
	pinMode(MENU_BACK_BUTTON_PIN, INPUT_PULLUP);
	pinMode(SELECT_BUTTON_PIN, INPUT_PULLUP);

	// Set relay pins to output
	pinMode(CIRCFANPIN, OUTPUT);
	pinMode(VENTFANPIN, OUTPUT);
	pinMode(HUMIDIFIERPIN, OUTPUT);
	pinMode(HEATERPIN, OUTPUT);

	digitalWrite(CIRCFANPIN, HIGH);
	digitalWrite(VENTFANPIN, HIGH);
	digitalWrite(HUMIDIFIERPIN, HIGH);
	digitalWrite(HEATERPIN, HIGH);
}

void loop() {
	bool flag = true;
	if (previous_btn()) {
		while (previous_btn()) {

		}
		first_loop();
	}
}

void first_loop() {
	if (flag == true) {
		digitalWrite(CIRCFANPIN, LOW);
		while (~previous_btn()) { // Looping
			while (previous_btn()) { // Blocking

			}
			second_loop();
		}
	}
	else {
		return;
	}
}

void second_loop() {
	if (flag == true) {

	digitalWrite(VENTFANPIN, LOW);

	while (~previous_btn()) { // Looping
		while (previous_btn()) { // Blocking

		}
		third_loop();
	}
	}
	else {
		return;
	}
}

void third_loop() {
	if (flag == true) {

		digitalWrite(HUMIDIFIERPIN, LOW);

		while (~previous_btn()) { // Looping
			while (previous_btn()) { // Blocking

			}
			fourth_loop();
		}
	}
	else {
		return;
	}
}

void fourth_loop() {
	if (flag == true) {

		digitalWrite(HEATERPIN, LOW);

		while (~previous_btn()) { // Looping
			while (previous_btn()) { // Blocking

			}
			digitalWrite(CIRCFANPIN, HIGH);
			digitalWrite(VENTFANPIN, HIGH);
			digitalWrite(HUMIDIFIERPIN, HIGH);
			digitalWrite(HEATERPIN, HIGH);
			flag == false;
		}
	}
	else {
		return;
	}
}

EDIT: Dammit, I thought the scope of a variable declared in loop would extend to the functions called by loop. Guess I'll need a little tweak.

You could return to loop and let loop call the next function.

It looks like loop needs to have some logic to decide which one of these functions to call and then call it.

Could you expand on this a little? This is exactly the part that I'm struggling with when I think about this. After distilling this down a little, I think my trouble is with figuring out how to have loop run one and only one specific function everytime (up to some trigger point of course, but that part I can manage.)

Edit: See my next post for why I'd want the above. Our logic isn't aligned right now.

The logic is a little different than that though. first_loop sets a pin, but only calls second_loop once the button is pressed again. Then second_loop sets a pin, but only calls third_loop once the button is pressed again. And so on.

So the problem with the code you wrote is that second_loop will run after first_loop, regardless of whether the button is pressed.

I doubt you can get your finger off of it fast enough.

Ya I actually dealt with that with a delay originally, but then moved on to blocking with a while loop so there's no time limit to a button press:

while (~previous_btn()) { // Looping
			while (previous_btn()) { // Blocking

			}
			second_loop();
		}

Edit: After reading the above, I thought I could clarify a little. second_loop only runs if first_loop is currently running AND the button is pressed again. Then third_loop only runs if second_loop is running AND the button is pressed. Same with fourth_loop.

EDIT: Oh shoot, I see what you're saying now - ya that was an error on my part. That code isn't right and explains why it wasn't working when I tested. It's missing an if statement before the blocking while loop.

My code example above was just showing the blocking I was using for the button press not the pin selection. Here's an excerpt from my originally posted code:

void first_loop() {
	digitalWrite(CIRCFANPIN, LOW);

	if (previous_btn()) {
		while (previous_btn()) {

		}
		second_loop();
	}
	first_loop();
}

Ya that state machine sounds like what I need. No, switch/case is over my head. I'll take a look into it but if/else should work for now. Appreciate the help.

After a little research, switch/case looks slick for this. Going to try with that.

I really don't know what you're doing but inferring from the names of the buttons and the actions taken, I suggest you use a state machine to handle this. I wrote this as a basic guide to how I'd do it (though I'm sure others will point and laugh...) I'm afraid there's no "simple" way to do this.

If you're nesting menus (as I suspect as there's a menu-back button) then this logic becomes a sub-level the menu layers above it that will have their own SMs. In that case this statemachine is called not from loop but from a statemachine that is called by loop (or one that is called by one called from loop... depending on how far down the menu rabbit hole you go. When that happens you're going to want to split these up into different .cpp files or something...

Compiles but probably won't work first time out without some debugging...

// Pushbutton Pins
#define PREVIOUS_BUTTON_PIN     30
#define NEXT_BUTTON_PIN         40
#define MENU_BACK_BUTTON_PIN    32
#define SELECT_BUTTON_PIN       38

// Relay Connection Pins
#define CIRCFANPIN              26 // Pin circ fan control relay is connected to
#define VENTFANPIN              28 // Pin vent fan control relay is connected to
#define HUMIDIFIERPIN           24 // Pin humidifier control relay is connected to
#define HEATERPIN               22 // Pin heater control relay is connected to


#define NO_BTN              0x00
#define SEL                 0x01
#define NEXT                0x02
#define PREV                0x04
#define MENUB               0x08

#define STATE_IDLE          0
//
#define STATE_CIRCFAN       1
#define STATE_VENTFAN       2
#define STATE_HUMID         3
#define STATE_HEATER        4
//

//menu select action functions for each item
void Heater( byte Action );
void Humidifier( byte Action );
void VentilationFan(  byte Action );
void CirculationFan( byte Action );

typedef void (*fnPtr)(byte);

fnPtr 
    Function;

void setup() 
{
    // Set all button pins to input
    pinMode(PREVIOUS_BUTTON_PIN, INPUT_PULLUP);
    pinMode(NEXT_BUTTON_PIN, INPUT_PULLUP);
    pinMode(MENU_BACK_BUTTON_PIN, INPUT_PULLUP);
    pinMode(SELECT_BUTTON_PIN, INPUT_PULLUP);

    // Set relay pins to output
    pinMode(CIRCFANPIN, OUTPUT);
    pinMode(VENTFANPIN, OUTPUT);
    pinMode(HUMIDIFIERPIN, OUTPUT);
    pinMode(HEATERPIN, OUTPUT);

    digitalWrite(CIRCFANPIN, HIGH);
    digitalWrite(VENTFANPIN, HIGH);
    digitalWrite(HUMIDIFIERPIN, HIGH);
    digitalWrite(HEATERPIN, HIGH);
    
    Function = &CirculationFan;

}//setup

void loop()
{
    //all we do from loop is call the menu state machine
    MenuStateMachine();
    
}//loop


//Menu States:
//  0   Circulation Fan
//  1   Vent Fan
//  2   Humidifier
//  3   Heater
//
//  Previous moves backward through the states
//  Next moves forward through the states
//  Select toggles the option in the current state
//  Not sure what "Menu back" is for
//

void MenuStateMachine( void )
{
    static bool
        bBtnWaitFlag = false;
    unsigned long
        timeNow;
    static unsigned long
        timeBtn = millis();
    byte
        btns;
    static byte
        laststateMenu = STATE_CIRCFAN;
    static byte
        stateMenu = STATE_IDLE;

    switch( stateMenu )
    {
        case    STATE_IDLE:
            //read the buttons every 50mS
            timeNow = millis();
            if( (timeNow - timeBtn) < 50 )
                return;
            timeBtn = millis();

            btns = ReadButtons();

            //if we're waiting to see no buttons pressed, check that condition
            if( bBtnWaitFlag )
                bBtnWaitFlag = (btns == 0)?true:false;
                
            if( bBtnWaitFlag )             
            {
                //any buttons pressed this pass?
                if( btns )
                {
                    if( btns & SEL )
                    {
                        //select
                        //call the handler for the current item
                        //pass it SEL; if the only thing done is to toggle a
                        //state (on/off) the parm isn't needed...
                        Function( SEL );
                        
                    }//if
                    else if( btns & NEXT )
                    {
                        //next
                        stateMenu = BumpState( NEXT, laststateMenu );     
                    }//else
                    else if( btns & PREV )
                    {
                        //previous
                        stateMenu = BumpState( PREV, laststateMenu );                     
                    }//else
                    else if( btns & MENUB )
                    {
                        //???
                    }//else

                    //after acting on a press, wait until we see all buttons released
                    //before running through this again
                    bBtnWaitFlag = true;
                    
                }//if
                
            }//else            
        break;
        
        case    STATE_CIRCFAN:
            //setup menu on screen
            //UpdateDisplay( STATE_CIRCFAN );
            
            //set fn pointer to circ fan handler
            Function = &CirculationFan;
            laststateMenu = STATE_CIRCFAN;
            stateMenu = STATE_IDLE;
            
        break;

        case    STATE_VENTFAN:
            //setup menu on screen
            //UpdateDisplay( STATE_VENTFAN );
            
            //set fn pointer to ventilation fan handler
            Function = &VentilationFan;
            laststateMenu = STATE_VENTFAN;
            stateMenu = STATE_IDLE;
            
        break;

        case    STATE_HUMID:
            //setup menu on screen
            //UpdateDisplay( STATE_HUMID );
            
            //set fn pointer to humidifier handler
            Function = &Humidifier;
            laststateMenu = STATE_HUMID;
            stateMenu = STATE_IDLE;
        
        break;

        case    STATE_HEATER:
            //setup menu on screen
            //UpdateDisplay( STATE_HEATER );
            
            //set fn pointer to heater handler
            Function = &Heater;
            laststateMenu = STATE_HEATER;
            stateMenu = STATE_IDLE;
        
        break;
        
    }//switch
    
}//MenuStateMachine

byte BumpState( byte Dir, byte lastStateMenu )
{
    byte
        idx;
    idx = lastStateMenu;
    
    //moving up?
    if( Dir == NEXT )
    {
        //if moving up we can't move past the last state
        //another option would be to have it circulate back to "STATE_CIRCFAN"
        if( lastStateMenu < STATE_HEATER )
            idx++;
    }//if
    else
    {
        //if moving down we can't move past the first state
        //another option would be to have it circulate back to "STATE_HEATER"
        if( lastStateMenu > STATE_CIRCFAN )
            idx--;
    }//else

    return( idx );
    
}//BumpState

void Heater( byte Action )
{
    static bool
        bHeaterState = 0;

    if( Action == SEL )
    {
        bHeaterState ^= 1;
        digitalWrite(CIRCFANPIN, (bHeaterState == 1)?LOW:HIGH );
        
    }//if
    
}//Heater

void Humidifier( byte Action )
{
    static bool
        bHumidifierState = 0;

    if( Action == SEL )
    {
        bHumidifierState ^= 1;
        digitalWrite(HUMIDIFIERPIN, (bHumidifierState == 1)?LOW:HIGH );
        
    }//if
    
}//Humidifier

void VentilationFan(  byte Action )
{
    static bool
        bVentilationFanState = 0;

    if( Action == SEL )
    {
        bVentilationFanState ^= 1;
        digitalWrite(VENTFANPIN, (bVentilationFanState == 1)?LOW:HIGH );
        
    }//if
    
}//VentilationFan

void CirculationFan( byte Action )
{
    static bool
        bCirculationFanState = 0;

    if( Action == SEL )
    {
        bCirculationFanState ^= 1;
        digitalWrite(CIRCFANPIN, (bCirculationFanState == 1)?LOW:HIGH );
        
    }//if
    
}//CirculationFan



byte ReadButtons( void )
{
    byte
        retval;
    retval = 0;

    retval |= ( (digitalRead(SELECT_BUTTON_PIN) == LOW) ? SEL:NO_BTN );
    retval |= ( (digitalRead(NEXT_BUTTON_PIN) == LOW) ? NEXT:NO_BTN );
    retval |= ( (digitalRead(PREVIOUS_BUTTON_PIN) == LOW) ? PREV:NO_BTN );
    retval |= ( (digitalRead(MENU_BACK_BUTTON_PIN) == LOW) ? MENUB:NO_BTN );
    
    return retval;
        
}//ReadButtons

Wow, thanks Blackfin. I'm going to take a look through that. Yes, there will be a menu system I'm working through. I combined a couple things I was doing here just as a subsystem test.

Here's a state machine version of what I originally was attempting:

// Test -- Button hold blocking with state machine

// Pushbutton Pins
#define PREVIOUS_BUTTON_PIN 30
#define NEXT_BUTTON_PIN 40
#define MENU_BACK_BUTTON_PIN 32
#define SELECT_BUTTON_PIN 38

// Relay Connection Pins
#define CIRCFANPIN 26 // Pin circ fan control relay is connected to
#define VENTFANPIN 28 // Pin vent fan control relay is connected to
#define HUMIDIFIERPIN 24 // Pin humidifier control relay is connected to
#define HEATERPIN 22 // Pin heater control relay is connected to

int case_num = 0;

bool previous_btn() {
  return (digitalRead(PREVIOUS_BUTTON_PIN) == LOW) ? true : false;
}

bool next_btn() {
  return (digitalRead(NEXT_BUTTON_PIN) == LOW) ? true : false;
}

bool menu_back_btn() {
  return (digitalRead(MENU_BACK_BUTTON_PIN) == LOW) ? true : false;
}

bool select_btn() {
  return (digitalRead(SELECT_BUTTON_PIN) == LOW) ? true : false;
}

void setup() {
  // Set all button pins to input
  pinMode(PREVIOUS_BUTTON_PIN, INPUT_PULLUP);
  pinMode(NEXT_BUTTON_PIN, INPUT_PULLUP);
  pinMode(MENU_BACK_BUTTON_PIN, INPUT_PULLUP);
  pinMode(SELECT_BUTTON_PIN, INPUT_PULLUP);

  // Set relay pins to output
  pinMode(CIRCFANPIN, OUTPUT);
  pinMode(VENTFANPIN, OUTPUT);
  pinMode(HUMIDIFIERPIN, OUTPUT);
  pinMode(HEATERPIN, OUTPUT);

  digitalWrite(CIRCFANPIN, HIGH);
  digitalWrite(VENTFANPIN, HIGH);
  digitalWrite(HUMIDIFIERPIN, HIGH);
  digitalWrite(HEATERPIN, HIGH);
}

void loop() {

  switch (case_num) {
  case 0:
    
    if (previous_btn()) {
      while (previous_btn()) {

      }
      case_num = 1;
      first_loop();
    }
    break;

  case 1:

    first_loop();
    break;
  case 2:
    
    second_loop();
    break;
  case 3:

    third_loop();
    break;
  case 4:

    fourth_loop();
    break;
  }

}

void first_loop() {
    digitalWrite(CIRCFANPIN, LOW);

    if (previous_btn()) {
      while (previous_btn()) { // Blocking

      }
      case_num = 2;
      second_loop();
    }
  
}

void second_loop() {
    digitalWrite(VENTFANPIN, LOW);

    if (previous_btn()) {
      while (previous_btn()) { // Blocking

      }
      case_num = 3;
      third_loop();
    }
  
}

void third_loop() {

    digitalWrite(HUMIDIFIERPIN, LOW);

    if (previous_btn()) {
      while (previous_btn()) { // Blocking

      }
      case_num = 4;
      fourth_loop();
    }
  
}

void fourth_loop() {

    digitalWrite(HEATERPIN, LOW);

    if (previous_btn()) {
      while (previous_btn()) { // Blocking
      }
      digitalWrite(CIRCFANPIN, HIGH);
      digitalWrite(VENTFANPIN, HIGH);
      digitalWrite(HUMIDIFIERPIN, HIGH);
      digitalWrite(HEATERPIN, HIGH);
      case_num = 0;
    }
}

Wow Blackfin, I can't believe you did this just now. Definitely will help me moving forward. Also, I like your notation at the closing brackets - makes it easier to track where we are in the code.

This would've taken me a looong time.

I just made a quick corrective change to that snippet. I moved the typedef of the function pointer and its declaration above setup() and added an initialization in setup() to point to the Circulation fan handler. Be sure to update any copy you're walking through.