Is there a way to continuously listen for keypad input while changing states?

The code below is is supposed to act as a state machine. All it does is light up the LED corresponding to the appropriate state. When the user clicks the overRide button, the progression of the state changes, so the state will jump to another state indicated by the if statement instead of the natural progression. The program should be continuously listening for the keypad input. Such that if "*" is pressed three times consecutively or "E", the program will terminate. If those buttons are not pressed continuously, at least the program will change the state accordingly based on the if statements in each case of the switch statement.

I am having issues trying to figure out how to use millis(). I tried using delay() but it is not giving me the results that I want. The program does go into override mode, but it doesn't do it until it has entered the next state. Further more only 13/16 buttons will work in getting the program into override mode. I was trying to see if there was a way that I could press a button from the keypad during one of the states and have the button set overRide = true; so that overRide can be implemented during state it was clicked.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>

/**********************/
/* code for kepad 
/**********************/

#include <Keypad.h>

#define KEYPAD_ROW0 22
#define KEYPAD_ROW1 23
#define KEYPAD_ROW2 24
#define KEYPAD_ROW3 25
#define KEYPAD_COL0 26
#define KEYPAD_COL1 27
#define KEYPAD_COL2 28
#define KEYPAD_COL3 29


const byte keypad_rows = 4; //four rows
const byte keypad_cols = 4; //three columns

char keypad_keys[keypad_rows][keypad_cols] = {
  {'1','2','3','A'},
  {'4','5','6','B'},
  {'7','8','9','C'},
  {'*','0','#','D'}
};

byte keypad_rowPins[keypad_rows] = {KEYPAD_ROW0, KEYPAD_ROW1, KEYPAD_ROW2, KEYPAD_ROW3}; //connect to the row pinouts of the keypad
byte keypad_colPins[keypad_cols] = {KEYPAD_COL0, KEYPAD_COL1, KEYPAD_COL2, KEYPAD_COL3}; //connect to the column pinouts of the keypad

Keypad customKeypad = Keypad( makeKeymap(keypad_keys), keypad_rowPins, keypad_colPins, keypad_rows, keypad_cols );

/****************************/
/* code for LCD display 
/***************************/


#include <LiquidCrystal.h>

LiquidCrystal 
lcd(4, 6, 10, 11, 12, 13);

//needed for temporarily holding text to be sent to LCD
char lcd_text_string[40];
int lcd_current_row;
int lcd_current_col;


// code for buzzer 
const int BUZZER = 49;

// code for LEDs
const int GREEN  = 32;
const int YELLOW = 34;
const int RED    = 36;
const int BLUE   = 38;


//current state 
int current_state = 1;

//next state 
int next_state = 0; 

//OVERIDE KEY
bool overRide = false;

// hold keypad inputs 
char userChar;
char keypad_input[3] = {0,0,0};

//counts the number of times character appears
int count = 0;

//Termination count 
int terminate = 0;

void setup() {
  
  // put your setup code here, to run once:
  Serial.begin(115200);

  // set up for buzzer
  pinMode(BUZZER, OUTPUT);

  //setup for LEDS 
  pinMode(GREEN, OUTPUT);
  pinMode(YELLOW, OUTPUT);
  pinMode(RED, OUTPUT);
  pinMode(BLUE, OUTPUT);

}

void loop() {
  
  char customKey = customKeypad.getKey();
  if(customKey) // checks if key is pressed during a state 
  {
    userChar = customKey;
    lcd.print(userChar);
    setOverRide();
  }
  else 
  {
    overRide = false; 
  }

  stateMachine();
}
  

//This function contains all the states 
void stateMachine(){
  switch(current_state)
  {
    case 1:
      lcd.print("now in STATE: 1");
      digitalWrite(GREEN, LOW);
      digitalWrite(YELLOW, LOW);
      digitalWrite(RED, HIGH);
      digitalWrite(BLUE, LOW);
      delay(3000);
      lcd.clear();
      if( overRide == false)
      {
        next_state = 2;
      }
      else if( overRide == true)
      {
        lcd.print("OVERRIDE MODE");
        next_state = 9;
      }
      current_state = next_state;
      break;
    case 2:
      lcd.print("now in STATE: 2");
      digitalWrite(GREEN, HIGH);
      digitalWrite(YELLOW, LOW);
      digitalWrite(RED, LOW);
      digitalWrite(BLUE, LOW);      
      if( overRide == false)
      {
        delay(7000);
        lcd.clear();
        next_state = 3;
      }
      else if( overRide == true)
      {
        delay(2000);
        lcd.clear();
        lcd.print("OVERRIDE MODE");
        next_state = 7;
      }
      current_state = next_state;
      break;      
    case 3:
      lcd.print("now in STATE 3");
      digitalWrite(GREEN, LOW);
      digitalWrite(YELLOW, HIGH);
      digitalWrite(RED, LOW);
      digitalWrite(BLUE, LOW);
      delay(2000); 
      lcd.clear();
      if( overRide == false)
      {
        next_state = 4;
      }
      else if( overRide == true)
      {
        lcd.print("OVERRIDE MODE");
        delay(2000);
        next_state = 9;
      }
      current_state = next_state;
      break;       
    case 4:
      lcd.print("now in STATE 4");
      digitalWrite(GREEN, LOW);
      digitalWrite(YELLOW, LOW);
      digitalWrite(RED, HIGH);
      digitalWrite(BLUE, LOW);
      delay(3000);  
      lcd.clear();
      if( overRide == false)
      {
        next_state = 5;
      }
      else if( overRide == true)
      {
        lcd.print("OVERRIDE MODE");
        delay(2000);
        next_state = 9;
      }
      current_state = next_state;
      break;        
    case 5:
      lcd.print("now in STATE 5");
      digitalWrite(GREEN, LOW);
      digitalWrite(YELLOW, LOW);
      digitalWrite(RED, HIGH);
      digitalWrite(BLUE, LOW);  
      if( overRide == false)
      {
        delay(7000);
        lcd.clear();
        next_state = 6;
      }
      else if( overRide == true)
      {
        delay(2000);
        lcd.clear();
        lcd.print("OVERRIDE MODE");
        delay(2000);
        next_state = 9;
      }
      current_state = next_state;
      break;       
    case 6:
      lcd.print("now in STATE 6");
      digitalWrite(GREEN, LOW);
      digitalWrite(YELLOW, LOW);
      digitalWrite(RED, HIGH);
      digitalWrite(BLUE, LOW);
      delay(2000);  
      lcd.clear();
      if( overRide == false)
      {
        next_state = 1;
      }
      else if( overRide == true)
      {
        delay(2000);
        lcd.print("OVERRIDE MODE");
        delay(2000);
        next_state = 9;
      }
      current_state = next_state;
      break;         
    case 7:
      lcd.print("now in STATE 7");
      digitalWrite(GREEN, LOW);
      digitalWrite(YELLOW, HIGH);
      digitalWrite(RED, LOW);
      digitalWrite(BLUE, LOW);
      delay(2000);  
      lcd.clear();
      next_state = 9;
      current_state = next_state;
      break;        
    case 8:
      lcd.print("now in STATE 8");
      digitalWrite(GREEN, LOW);
      digitalWrite(YELLOW, LOW);
      digitalWrite(RED, HIGH);
      digitalWrite(BLUE, LOW);
      delay(2000);  
      lcd.clear();
      next_state = 9;
      current_state = next_state;
      break;
    case 9:
      lcd.print("now in STATE 9");
      digitalWrite(GREEN, LOW);
      digitalWrite(YELLOW, LOW);
      digitalWrite(RED, HIGH);
      digitalWrite(BLUE, HIGH);
      overRide = false;
      next_state = 1;        
      current_state = next_state;
      for(int i = 0; i < 5; i++)
      {
        tone(BUZZER, 1000);
        delay(1000);
        noTone(BUZZER);
        delay(1000);
      }
      lcd.clear();
      break;
  }
}


bool setOverRide() // returns the value of the boolean oveRide
{
  lcd.print("OVERIDE FUNC");
  Serial.println(userChar);
  delay(1000);
  lcd.clear();
  overRide = true;
  keypad_input[count]= userChar;
  count++;
  if( count == 3 )
  {
    if(keypad_input[0] == keypad_input[1] && keypad_input[1] == keypad_input[2])
    {
      terminate = true;
    }
    else 
    {
      count = 0;
    }
  }
  return overRide;
}

the "classic" state machine handles multiple stimuli in each state.

your code really just implements a sequencer, a state machine that moves from state to state in sequence, based on a timeout.

a more conventional state machine would treat the timeout as a stimuli and conditionally change states based on the stimuli (e.g timout, some keypad input)

your state machine is full of delays... it needs to be rewritten to take time as an input to transition states

1 Like

Make a function called eg pollButtons
Put your button checking code in the function. Call the function in loop and in every state. Your buttons will thus be checked each time. Get rid of all delays/blocking code as nothing happens including checking buttons while this runs

That's a good idea, but I also need it to stay in a state for however many seconds depending on what button was pressed. Would millis() be a good option here? or just checking for the button pressed with the button polling function before delaying for how many ever seconds it is required to until moving to the next state. Also the button press can also affect how long the state machine remains in a certain state.

I use a button polling function to poll any buttons and then I keep a global variable of buttonState often in an array. Then in my code I can do whatever conditional on the buttonstate. I always use millis timing if at all possible

Don't mix delay() and state machinery!

In a state that must hang out for a while, every time it is executed it should just see if it is time to change state.

If the state did something, then needed to wait, introduce a new state that is just the waiting. Frequently checking if it is time to transition, end of the delay or hanging out time.

So a step in the FSM finite state machine should never take any significant time, such time is dependent on your circumstances, but no time at all to a first approximation.

The waiting should be done by checking current millis() against a millis() grabbed at the start of the delay period.

In the classic manner like "blink without delay".

a7

Your mistake in setOverRide() is recording three consecutive characters and then checking if the three are the same. This will fail if you enter "2***5EEE4" because it will only look at three character groups. It will see that "2**" are not all the same so it will reset and then see that "*5E" are not all the same so it will reset and see that "EE6" are not all the same.

When the OTHER person with the same question asked, a good answer seemed to be separate counters for * and E.

bool setOverRide() // returns the value of the boolean oveRide
{
  static int AsteriskCount = 0;
  static int E_count = 0;

  lcd.print("OVERIDE FUNC");

  if (userChar == '*')
    AsteriskCount++;
  else
    AsteriskCount = 0;

  if (userChar == 'E')
    E_count++;
  else
    E_count = 0;

  if (AsteriskCount == 3 || E_count == 3)
  {
    AsteriskCount = 0;
    E_count = 0;
    terminate = true;
  }

  return true;
}

Yes, that is a better alternative. I will definitely apply that. thanks!

kinda crude but at least partially table driven using state and stimuli

// state machine with button and timer stimuli

enum { Off = HIGH, On = LOW };

byte pinsLed [] = { 10, 11, 12 };
byte pinsBut [] = { A1, A2, A3 };
#define N_BUT   sizeof(pinsBut)

byte butState [N_BUT];

bool           tmrActive;
unsigned long  tmrPeriod;
unsigned long  msecLst;
unsigned long  msec;


// -----------------------------------------------------------------------------
void
startTmr (
    unsigned long period)
{
    Serial.println (__func__);
    msecLst   = msec;
    tmrPeriod = period;
    tmrActive = true;
}

// -----------------------------------------------------------------------------
enum Stim  { Stm0, Stm1, Stm2, Stm3, StmLast };
enum State { Sta0, Sta1, Sta2, Sta3, StaLast };

State stateStim [StaLast][StmLast] = {
    { Sta1, Sta2, Sta3, Sta0 },
    { Sta1, Sta3, Sta2, Sta0 },
    { Sta3, Sta2, Sta1, Sta0 },
    { Sta2, Sta3, Sta3, Sta0 },
};

State state = Sta0; 

void
stateMach (
    int   stimulus)
{
    Serial.print   ( "stateMach: ");
    Serial.println (stimulus);

    state = stateStim [state][stimulus];

    for (unsigned n = 0; n < sizeof(pinsLed); n++)
        digitalWrite (pinsLed [n], Off);

    switch (state)  {
    case Sta0:
        break;

    case Sta1:
        digitalWrite (pinsLed [0], On);
        startTmr (1000);
        break;

    case Sta2:
        digitalWrite (pinsLed [1], On);
        startTmr (1500);
        break;

    case Sta3:
        digitalWrite (pinsLed [2], On);
        startTmr (2000);
        break;

    default:
        break;
    }
}


// -----------------------------------------------------------------------------
#define NoBut  -1
int
chkButtons ()
{
    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        byte but = digitalRead (pinsBut [n]);

        if (butState [n] != but)  {
            butState [n] = but;

            delay (10);     // debounce

            if (On == but)
                return n;
        }
    }
    return NoBut;
}

// -----------------------------------------------------------------------------
void
loop ()
{
    msec = millis();

    if (tmrActive && (msec - msecLst) >= tmrPeriod)  {
        Serial.println ("loop: tmr");
        tmrActive = false;
        stateMach (Stm3);
    }

    int but = chkButtons ();
    if (NoBut != but)
        stateMach ((Stim)but);
}

// -----------------------------------------------------------------------------
void
setup ()
{
    Serial.begin (9600);

    for (unsigned n = 0; n < sizeof(pinsBut); n++)  {
        pinMode (pinsBut [n], INPUT_PULLUP);
        butState [n] = digitalRead (pinsBut [n]);
    }

    for (unsigned n = 0; n < sizeof(pinsLed); n++)  {
        digitalWrite (pinsLed [n], Off);
        pinMode      (pinsLed [n], OUTPUT);
    }
}

There is only one lamp at most on at any time.

That turns all the lamps off.

Subsequent logic may turn one lamp on. Sometimes that is the very lamp that was turned off microseconds ago.

An unwarranted and undesirable glitch has been created. It may not matter - you would not see it. But it is easy to imagine that it might.

In this case the fix is easy.

Do not turn off all the LEDs.

In the case, note which lamp is to be turned on.

After the switch/case, if a lamp is to be on, turn it on. It may already be on.
Turn off the other two lamps, both of which may already be off.

If no lamp is to be on, turn off all the lamps, one of which may be on.

edit: fixed unsigned integer math error

void
stateMach (
    int   stimulus)
{
    Serial.print   ( "             state machine: state  ");
    Serial.print (stateTags[state]);
    Serial.print   ( "        stimulus ");
    Serial.print (stimTags[stimulus]);
    Serial.println();

    state = stateStim [state][stimulus];

//   GACK!  unsigned char theLamp = -1;   // maybe no lamp

signed char theLamp = -1;  // no lamp yet

    switch (state)  {
    case Sta0:
        break;

    case Sta1:
        theLamp = 0;
        startTmr (5000);
        break;

    case Sta2:
        theLamp = 1;
        startTmr (7500);
        break;

    case Sta3:
        theLamp = 2;
        startTmr (10000);
        break;

    default:
        break;
    }

    if (theLamp >= 0) { 
    // turn off the other two
        ++theLamp; if (theLamp >=3) theLamp -= 3;
        digitalWrite(pinsLed[theLamp], Off);
        ++theLamp; if (theLamp >=3) theLamp -= 3;
        digitalWrite(pinsLed[theLamp], Off);
    // and turn on the one we want now
        ++theLamp; if (theLamp >=3) theLamp -= 3;
        digitalWrite(pinsLed[theLamp], On);
    }
    else {  // no lamp turn them all off
        digitalWrite(pinsLed[0], Off);
        digitalWrite(pinsLed[1], Off);
        digitalWrite(pinsLed[2], Off);
    }
}

a7

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.