which is more appropriate, if, for or while?

Hi,

I'm doing my best to read through and understand the reference page but I'm still puzzled over which is more appropriate to use in a certain context, can anyone assist in pointing me in the right direction?

My task is to create a programmable relay controller.
The visual structure is intended to run like this;

My equipment at present consists of a mega, 16key keypad and 20x4 web4robot I2C LCD.

My sketch in progress is;

  #include <LCDi2cW.h>
  #include <Wire.h>                      // i2c Library
  #include <EEPROM.h>                    // EEPROM Library
 
  LCDi2cW lcd = LCDi2cW(4,20,0x4C,0);    // Define size of LCD and i2c address

  #define r_del 20;                        //Repeating key delay
  #define k_del 500;                       //1st keypress delay (delay before repeating)
  
  int             key_delay = k_del;       //Delay coutner for key debounce/ repeat
  char            last_k = 0;              //Last key pressed
  unsigned long   last_t = 0;              //Time last keypress made
  char            k;                       //For storing keypress
   
  //-------------------------------------------------------------------------------------------------
  // Get key routine.
  char get_key()
   {
    char k=0;                                                                  // Default value, no key pressed
    unsigned long t = millis();                                                // Time now
    k=lcd.keypad();
    if (k == last_k and k != 0)                                                // Are we keeping a key pressed?
      {
        if (t-last_t < key_delay) 
        {                                                                      // If so, has required time passed?
        k=0;                                                                   // Not yet, so return 0 for no keypress
        } 
    else 
      {
        last_t = t;                                                            // Required time passed, so save the current time
        key_delay = r_del;                                                     // and set delay to repeating key time
      }
      } 
    else 
      {
      key_delay = k_del;                                                       // Not keeping a key pressed, so set delay to 1st keypress timeout
      last_t = t;                                                              // and save the time
      last_k = k;                                                              // and the key just pressed (or 0 if no keys down)
      }
      return (k);                                                              // Return the key press info or 0 for no key pressed
   }
  //-------------------------------------------------------------------------------------------------
  
    byte      keyPress, oldkeyPress=0, Zones, Zone1_state=HIGH, Zone2_state=LOW, Zone3_state=LOW;
    boolean   backlightState;
 
    byte      Zone1=24;                 // Digital pin assignment
    byte      Zone2=25;                 // Digital pin assignment
    byte      Zone3=26;                 // Digital pin assignment
    int       keyInput;          
   
  //-------------------------------------------------------------------------------------------------
  void setup() 
   { 
    Wire.begin();                        // Initialise the i2c bus
    lcd.init();                          // Initialise the LCD
    lcd.clear();                         // Clear the LCD
    lcd.on();                            // Turn on LCD backlight
    backlightState=true;

    pinMode(Zone1, OUTPUT);
    pinMode(Zone2, OUTPUT);
    pinMode(Zone3, OUTPUT);
    
    digitalWrite(Zone1, LOW);
    digitalWrite(Zone2, LOW);
    digitalWrite(Zone3, LOW);
  }
  //-------------------------------------------------------------------------------------------------
  void loop()
  {
     keyPress=get_key();
     Zones=get_key();
     
//------<start Switch section>------
     switch (keyPress)
     {
    //............................
            case 13:
            {
              lcd.clear();
              A_screen();              //print A key's screen
              delay(2000);             //wait 2secs for a key to be pressed, if no key pressed, return to main screen & loop
                if(keyPress == 1)      //if key 1 pressed, do this...
                {
                  lcd.clear();
                  lcd.print("1");
                  delay(1000);
                  lcd.clear();
                  break;
                 }
                if(keyPress == 2)      //if key 2 pressed, do this...
                {
                  lcd.clear();
                  lcd.print("2");
                  delay(1000);
                  lcd.clear();
                  break;
                 }
              lcd.clear();
              break;
            }
    //............................       
            case 14:
            {
              lcd.clear();
              B_screen();              //print B key's screen
              delay(2000);             // wait for 2 secs for a key to be pressed, if no key pressed, return to main screen & loop
                while (keyPress<200)
                {
                 case 1:
                  lcd.clear();
                  lcd.print("1");
                  delay(1000);
                  lcd.clear();
                  break;
                 
                 case 2:
                  lcd.clear();
                  lcd.print("2");
                  delay(1000);
                  lcd.clear();
                  break;
                } 
              lcd.clear();
              break;
            }
    //.............................        
            case 15:
            {
              lcd.clear();
              C_screen();              //print C key's screen
              delay(2000);             //wait for 2 secs for a key to be pressed, if no key pressed, return to main screen & loop
                for(keyPress = 2; keyPress<200; keyPress++)
                {
                  lcd.clear();
                  lcd.print("1");
                  delay(1000);
                  lcd.clear();
                  break;
                }
              lcd.clear();
              break; 
            }        
    //..............................
            case 16:
            {
              lcd.clear();
              D_screen();
              delay(5000);
              lcd.clear();
              break; 
            }
    //...............................
     }  
//------<end Switch section>------    

  MainScreen(); 
  ZonesActive();
    
  }         
  // End of loop function

At the moment I'm stuck on how to construct a loop within a loop or such, for example, if Key A is pressed, I want it to hold that screen for a period of time and wait for a key press, if a certain defined key press occurs, it triggers another action.
I think the correct method is using the "while" statement, but I'm not sure if I'm on the right track.
It also seems to repeat that loop and return a printed "1" even when no key is pressed, but I suspect that may be because I've confused my statements and their application.

Can someone point me in the right direction with which is more suitable, or a even to a thread or tut that may explain it a little better.

Cheers.

Search the forums for "State Machine".
This may be the way to go.

Thanks Larry,
this is what you mean for "state machine"?

@Onsan
Yes.
Nick has taught us great deal!

See this also:

Thanks, wikipedia dumbed it down enough to understand Nick!
Thanks for the redirection.

At the moment I'm stuck on how to construct a loop within a loop or such,

Basically you always use a while statement when you want a loop. An if statement will only execute one or other pieces of code once only. To repeat something or to hold until something happens you need a while statement.

Onsan:
Thanks, wikipedia dumbed it down enough to understand Nick!
Thanks for the redirection.

I thought my page was simpler than:

:stuck_out_tongue:

But to be fair I only mentioned state machines without really explaining what they are. The turnstile example isn't bad.

In your case (first post) each of the A, B, C, D could be the start of a "state" and then you have a "sub-state" which is the number they have currently entered, for example. You could use "#" to transition from one state to the next one.

sorry Nick, wasn't intending to offend, I couldn't picture the concept of what a state machine was until I read over the turnstile analogy, from there it helped to understand your write up.

I'm still getting my head around it, but my idea is as you suggested, using A->D as the start of 4 different states, then have sub-states assigned to each.
A question though, if I dedicate the buttons A->D to initiating each state, would it be simpler than using # to cycle through the various (initial) states?
or were you suggesting to use # to navigate through the sub-states?

Thanks for the "while" explanation Mike, that made sense.

Onsan:
sorry Nick, wasn't intending to offend, ...

I wasn't offended, I was just amused out how esoteric Wikipedia can be at times.

I'm still getting my head around it, but my idea is as you suggested, using A->D as the start of 4 different states, then have sub-states assigned to each.

You need to have an idea of what happens if an "unexpected" input is made, for example someone presses A then some digits then B. Does the B cancel the A and go back to the start? Assuming yes, it would be something like this:

enum {
  IDLE,
  
  A_SELECT_ZONE,    // A
  B_SELECT_ZONE,    // B
  C_SET_DATE,       // C
  D_VIEW_PROGRAMS,  // D
  
  
  A_SELECT_PROGRAM,
  A_SELECT_DAY,
  A_SET_ON_TIME,
  A_SET_OFF_TIME,
  A_SET_DOSING_TIME,
  
  // and so on
  
} states;

int state = IDLE;
unsigned long currentNumber;

void setup ()
  {
  // whatever
  }  // end of setup

void loop ()
  {
  char key = getKey ();    // key press from keypad
  if (key == NO_KEY)
    return;
    
  switch (key)
    {
    case 'A' : state = A_SELECT_ZONE;   
               currentNumber = 0; 
               break; 
               
    case 'B' : state = B_SELECT_ZONE;   
               currentNumber = 0; 
               break; 
               
    case 'C' : state = C_SET_DATE;      
               currentNumber = 0; 
               break; 
               
    case 'D' : state = D_VIEW_PROGRAMS; 
               currentNumber = 0; 
               break; 
               
    case '0' ... '9' : 
               currentNumber *= 10;  
               currentNumber += key - '0';
               break;
               
    case '#' : interpretNumber ();
               break;
               
    }  // end of switch
  
  }  // end of loop

This is only partly it, but it should show the idea. When you get a '#' you can work out what currentNumber means. For example, if the current state is C_SET_DATE and they hit #, then presumably we have a 6-digit date. We can validate it to be reasonable and if so, store it as the date, then move the state on to be C_SET_TIME, and so on.

ok, thanks, I'll try that approach.

Hi Nick,

I've been trying to apply the state machine you described but was having problems with the IDLE state, I altered the sketch to that which is below, it works as far as I've gone with the sketch, but am I on the right track? Is my implementation of the sketch in keeping with a state machine and if was to continue on this path would it work as such?

cheers.

  #include <Wire.h>
  #include <LCDi2cW.h>                    
  LCDi2cW lcd = LCDi2cW(4,20,0x4C,0); 
  char buttons[17] = {'0','1', '4', '7', '*', '2', '5','8','0','3','6','9','#','A','B','C','D' };
//--------------------------------------------   
  void setup()
  { 
    Wire.begin();
    lcd.init(); 
  }
//--------------------------------------------
  void loop()
  {
    IDLE();
    byte digit = lcd.keypad();
    char state = buttons[digit];
    lcd.setCursor(1,10);
    
    switch (state)
      {
        case 'A' :
        {
          lcd.clear();
          lcd.setCursor(0,2);
          lcd.print("Zone Programming");
          lcd.setCursor(2,1);
          lcd.print("Select Zone (1-15)");
          lcd.setCursor(3,4);
          lcd.print("then press #");
          delay(2000);
          lcd.clear();
          break;
         } 
         default:
         {
           IDLE();
         }
         break;
      }
      }
//----------------------------------------------  
  void IDLE()
     {
      lcd.setCursor(1,13);
      lcd.print("Zone(s)");
      lcd.setCursor(2,13);
      lcd.print("#1,#2");
      lcd.setCursor (1,0);
      lcd.print("24/07/2013");
      lcd.setCursor (2,0);
      lcd.print("0900hrs");
     }

Grumpy_Mike has given some ideas on when to use any given expression. Also consider:

for -- often used when a known number of iterations are to be made. For example, you often see something like:

   int i;

   for (i = 0; i < MAXVALS, i++) {
      // for statement block
   }

Most of the time, the number of passes through the loop (e.g., MAXVALS) is known on entry to the loop. Sometimes you'll see code like this initialize an array to some specific value. (memset() is a better way.)

while -- Even though most while loops can be written as a for loop, while loops are often used for searching something, often in a list or database. You frequently see something like:

   String found = "";
   int i = 0;

   while (found != target)
   {
      found = ReadItemFromList(i++);
   }

where target is the string you are looking for.

if -- a statement block you only want to execute if the logical test is true:

   If (currentValue == targetValue) 
   {
      // execute this statement block; otherwise skip these
   }

where the logical test evaluates to logic True or False.

That's not a state machine, you're just tying the state to a pressed key. State machine variables should be enum'd and changed based on the input AND the current state.

Yes, sorry, realise that now, will go back to the sketch board and apply it properly.

Thanks for the explanation on for/if/while Jack, I like the state machine approach suggested, it seems a very elegant solution to my project.

I like the state machine approach

Have a look at this, it is new on the web this week.

http://www.thebox.myzen.co.uk/Tutorial/State_Machine.html

Thanks for that.
I'm a little confused though, I thought a state machine really revolved around switch case, in this example he doesn't seem to use enum states, just if/else statements.
I do like the states described as it seems more succinct and a less convoluted way of structuring a state machine, is this a fair view?

Switch statements are a way to implement state machines, but they aren't the only way. (As an aside, switch statements and if/elseif are almost always interchangeable). State machines revolve around the concepts of inputs, output and states. The output is affected by either the current state (Moore) or both the output and input (Mealy). In both versions, the next state is determined by the current state and the input. enum and switch statements are simply tools used in implementing a state machine, but they aren't required.

Thanks for the info guys.

I've been working off the FSM library and have gotten this far;

  #include <FiniteStateMachine.h>
  #include <Wire.h>
  #include <LCDi2cW.h>                    
  LCDi2cW lcd = LCDi2cW(4,20,0x4C,0); 
  
  char buttons[17] = {'0','1', '4', '7', '*', '2', '5','8','0','3','6','9','#','A','B','C','D' };
  
  State Home   = State(HomeUpdate);  
  State A_menu = State(A_menu_Prog);   
  State B_menu = State(B_menu_OnOff); 
  State C_menu = State(C_menu_SetTime); 
  State D_menu = State(D_menu_Programmes);

//  State A_menu_Prog = State(A_menu_Date);
State A_menu_Date = State(A_menu_Date1);
//  State A_menu_Time = State();
//  State A_menu_Dosing = State();
//  
//  State B_menu_OnOff = State();
//  State B_menu_OnTime = State();
//  
//  State C_menu_SetTime = State();
  
  FSM stateMachine = FSM(Home);
  
  void setup()
  { 
    lcd.init();
  }

  void loop()
  {
    byte digit = lcd.keypad();
    char key = buttons[digit];
    lcd.clear();
    
    switch (key)
      {
      case 'A': stateMachine.transitionTo(A_menu);
      {
        switch (key)
        {
         case 1: stateMachine.transitionTo(A_menu_Date); 
         break;
        }
      }
        break;
      case 'B': stateMachine.transitionTo(B_menu); break;
      case 'C': stateMachine.transitionTo(C_menu); break;
      case 'D': stateMachine.transitionTo(D_menu); break;
      }
    stateMachine.update();
   }

  void HomeUpdate() 
    {
      lcd.setCursor (1,0);
      lcd.print("24/07/2013");
      lcd.setCursor (1,13);
      lcd.print("0900hrs");
      lcd.setCursor(2,2);
      lcd.print("Irrigating Zones");
      delay(1000);
    }
  void A_menu_Prog()
    {
      lcd.setCursor(0,2);
      lcd.print("Zone Programming");
      lcd.setCursor(2,1);
      lcd.print("Select Zone (1-15)");
      lcd.setCursor(3,4);
      lcd.print("then press #");
      delay(1000);
    }
  void A_menu_Date1()
  {
    lcd.setCursor(0,2);
    lcd.print("Set Date");
    delay(1000);
  }
  void A_menu_Time()
  {
    lcd.setCursor(0,2);
    lcd.print("Set Time");
    delay(1000);
  }
  void A_menu_Dosing()
  {
    lcd.setCursor(0,2);
    lcd.print("Set Dosing");
    delay(1000);
  }
  void B_menu_OnOff()
  {
    lcd.setCursor(0,2);
    lcd.print("Set On or Off");
    delay(1000);
  }
  void C_menu_SetTime()
  {
    lcd.setCursor(0,2);
    lcd.print("Set Time");
    delay(1000);
  }
  void D_menu_Programmes()
  {
    lcd.setCursor(0,2);
    lcd.print("View Programmes");
    delay(1000);
  }

I have a number of problems, but the issue I'm struggling getting around is once I'm in the A_menu state, I can't seem to make the transition to the next state A_menu_Date.
I've tried a number of different getKey ideas but I can't seem to get it to step outside of the original case statements and progress to the next level.
What am I missing here?

Between these two points in the code, key is never updated, and it will never be 'A' and 1 at the same time, so the second switch statement will never run. As logical as it seems, when dealing with state machines, don't try to do "nested" states. States that relate to each other like A_menu and A_menu_Date should be separate states, even if you only ever enter the A_menu_Date state from the A_menu state.

I think I understand, so to get to a "second level" state, I would need to create it on the same level as the others, but I would enter it by a series of key presses, such as 'A' + '1'.
I was thinking I could go from one state "level" to the next depending on the current state and the options available within that state.