Adafruit_RGB_LCD SHIELD FINITE STATE MACHINE PROJ

Hi so... I am working on a personal project outside of both school and work where I am trying to get the Adafruit RGB LCD Shield to print different phrases on the two lines which change every 5 seconds to something different (I am changing the phrase every 4 seconds by erasing the previous phrase and replacing with a new one) . However I want there to be freedom for the program to interrupt the "phrase state" machine instruction which is constantly cycling every 4 or however long seconds by the push of the select button on the shield. Seems simple but so far the best ideas i can get are from Dan M. and the automatic pet feeder with regards to finite state machine

In order to do this I am trying hard to learn state machine but failing thus far. I have Dan's asteroids program working fine on my device and I'm trying to learn state machine from that but its a bit off from what I'm really seeking from my code. Not that I should be touching his stuff anyways to begin with (I'm a huge ahole). But I have been trying to use it as learning material. Maybe there is other better example code out there I could learn from beyond that but I haven't been able to find it.

I am hoping for maybe some advice on how i can implement this? Don't expect code to be written for me but i am up against a wall at this point and kind of just guessing in terms of messing with millis(); time and other function doing random subtractions and the like to try and achieve the interruptible delay i am looking for.

Can you post the code that cycles phrases every 4 seconds? Please don't forget to use code tags when posting; it's the </> button above a reply window.

Finite state machines are pretty easy to implement once you know how ( :wink: ) but I wonder if you need one. The most important thing are

  1. Don't use for-loops or while-loops.
  2. Don't use delay.

You probably need a counter that is incremented when a button becomes pressed; therefore I would look at the state change detection example that comes with the IDE (it unfortunately uses delays but they are relatively short). The counter will be an index into an array of texts.

1 Like

presumably you have code that uses millis() to update the display after some interval and keeps track of the next phrase to display.

and you want to add code that checks a button changes the mode.

the button press can be used to change a "mode" state variable which is used to loop() to determine what to do which can be to call a sub-function to update the display or do something else, call some other sub-function. this can be done in a switch statement in loop()

so loop calls a function to check for button pressed and uses a "mode" variable set in the button routine to decide what to do

1 Like

It sounds like you only have two states:

  1. Wait for four seconds (unless a button is pressed first).
  2. Switch to the next phrase.

A State Machine might be overkill for that. :slight_smile:

I would try something like:

void loop()
{
  static unsigned long startOfTimer = 0;
  unsigned long currentMillis = millis();

  if (currentMillis - startOfTimer >= 4000)
  {
    startOfTimer = currentMillis;
    // Switch to new phrase
  }

  static bool buttonWasPressed = false;
  bool buttonIsPressed = digitalRead(ButtonPin) == LOW;
  if (buttonIsPressed != buttonWasPressed)
  {
    // State Change Detected
    buttonWasPressed = buttonIsPressed;
    if (buttonIsPressed)
    {
      // Switch to new phrase
      startOfTimer = currentMillis();
    }
}
1 Like

I want to put in the work myself before saying one thing or another but this could be what I need to get around enum and multiple states along with its own digital read method as seen in the dogbowl code.

I am going to attempt to implement this into the adafruit RGBLCD library example code I have for sure. The only thing I'm wondering off the top of my head is where the comment
//Switch to new phrase is
My thought process is to call a method outside of the local "if" brackets there where you have that comment, but still stay inside the larger void loop function in order to do this I have code that is like:

Switchtonewphrase()   //this is inside the larger loop function
{
int a = 0;
lcd.clear()j;

if (int a=0){
// insert here a statement to start lcd at the (0,0) coord i know what it is
// the lcd.printline for the 1st row
// the lcd.printline for the 2nd row

}
a= a+1;  //increment here AFTER ALL STATEMENTS TO CHECK HOW MANY a ITERATIONS
}

//then have more if statements to satistfy a+1 increments?  will this allow for cycling of the phrase //for every 4 seconds?



//I have a lot of work to do but need to try some more things.

to be clear i do have two states one for when the initial program starts, gives the user instructions on to push the select button on the adafruit RGBLCD upon start up. this instruction set should be interruptible and should cycle every 4 seconds and repeat instructions on how to push the select button over and over again until select button is pressed.

the next state is after select button is pressed. I am giving more details on the instructions on the entire program as a whole with regards to pushing other buttons (left right up down) what those buttons are now able to do and the code for this state is the same as the first state in terms of the millis and interruptible other button pushes besides select (up down left right) . Hope this makes sense. I didn't expect someone to actually write code. please dont stress over it

I think what you mean is:

static int a = 0;
lcd.clear()j;

if (a == 0) {

If you plan to as a bunch of:

if (a == 0) {
}
else if (a == 1) {
}
else if (a == 2) {
}
else if (a == 3) {
}
else if (a == 4) {
}
else {
  a = 0;
}

you should probably use a switch/case instead"

  switch (a) {
  default: 
    a = 0;

  case 0:
      break;

  case 1:
      break;

  case 2:
      break;

  case 3:
      break;

  case 4:
      break;
  }
1 Like

I tried a short example.

Its a simple state machine with 3 states.

Switch with the select button between 3 functions/menus (updateXXX)
in Function updateFixtext() one text after the other is shown.
The other two update functions (updateRuntime, updateRandom) are more or less empty dummies (and hopefully obvious, what they do).

If a menu needs a "timer" it uses its own millis() construct based on the idea of "Blink without delay".

The state change detection and debounce is based on the IDE example for discrete pins, but adopted to read from the MCP230017.

I don't have an Adafruit keypad available. I'm using the "LCD RGB Keypad for RPi" which might be quite similar (beside the cheap RGB circuit) and my LCD library. May be some pin definitions must be corrected.

But it should only show, how to combine several things:

/*******************************************************************************
   LCD RGB KEYPAD FOR RPi

   Example for MCP23017 - 16-Bit I/O Expander with I2C Interface

   LCD          MCP23017
   --------------------------
   VSS    GND   GND
   VDD    5V    Vin
   V0           -             contrast - connect to the wiper of a potentiometer, other legs to GND
   RS           GPIOB7
   RW           -             unused
   E            GPIOB5
   DB0          -
   DB1          -
   DB2          -
   DB3          -
   D4           GPIOB4
   D5           GPIOB3
   D6           GPIOB2
   D7           GPIOB1
   LEDA   GND   -             Backlight circuitry allways on
   LEDK   5V

   based on the idea of: https://werner.rothschopf.net/microcontroller/202105_arduino_liquid_crystal_mcp23017_en.htm
   by noiasca

   *******************************************************************************/
#include <Wire.h>
#include <NoiascaLiquidCrystal.h>           // use the adopted library downloaded from https://werner.rothschopf.net/202009_arduino_liquid_crystal_intro.htm
#include <NoiascaHW/lcd_mcp23017.h>         // include the proper IO interface

const byte cols = 16;        // columns/characters per row
const byte rows = 2;         // how many rows
const byte addr = 0x20;      // set the LCD address to 0x20

const byte rs = 7 + 8;       // GPIOB7
const byte rw = 255;         // not used
const byte en = 5 + 8;       // GPIOB5
const byte d4 = 4 + 8;       // GPIOB4
const byte d5 = 3 + 8;       // GPIOB3
const byte d6 = 2 + 8;       // GPIOB2
const byte d7 = 1 + 8;       // GPIOB1
const byte bl = 255;         // not used

const byte rgbRed = 6;       // GPIOA6
const byte rgbGreen = 0 + 8; // GPIOB0
const byte rgbBlue = 7;      // GPIOA7

const byte btnSelect = 0;    // GPIOA0 Buttons
const byte btnRight = 1;
const byte btnDown = 2;
const byte btnUp = 3;
const byte btnLeft = 4;

const byte button[] {btnLeft, btnUp, btnDown, btnRight, btnSelect};  // the 5 buttons on the LCD RGB KEYPAD connected to GPIO A
const byte OFF = HIGH;                // The buttons are low active, therefore we need to inverse them
const byte ON  = !OFF;

LiquidCrystal_MCP23017_custompin lcd(addr, rs, rw, en, d4, d5, d6, d7, bl, POSITIVE, cols, rows);
//LiquidCrystal_MCP23017_custompin_base lcd(addr, rs, rw, en, d4, d5, d6, d7, bl, POSITIVE, cols, rows); // this is just for internal tests

struct Data {
  char line[rows][cols + 1 + 8];  // a buffer for each line[row], with the length of cols + null + some spare place if UTF-8 is needed
};

const Data data[] {
  {"Text A 0", "Text A 1"},
  {"123456789A123456", "123456789B123456"},
  {"Latin", "Ä ä Ö ö Ü ü ß"},
  {"Symbols", "αβμΣ°÷∞←→"},
  {"Text E 0", "Text E 1"}
};

const size_t noOfData = sizeof (data) / sizeof(data[0]); // how many elements in data?
enum class State { FIXTEXT, RUNTIME, RANDOM} state;      // what kind of data should be shown
size_t currentData = 0;                                  // which data should be shown next time
const byte discretePin = A0;                             // just a pin on the Arduino to read from - not used

// loop through the array of data
void updateFixtext()
{
  static uint32_t previousMillis = 0;
  static size_t currentLine = 0;
  if (millis() - previousMillis > 4000)
  {
    previousMillis = millis();
    Serial.println(data[currentLine].line[0]);
    lcd.clear();
    lcd.print(data[currentLine].line[0]);
    lcd.setCursor(0, 1);
    lcd.print(data[currentLine].line[1]);
    // increase line for next iteration
    currentLine++;
    currentLine = currentLine % noOfData;
  }
}

//show runtime on LCD
void updateRuntime()
{
  static uint32_t previousMillis = 0;
  if (millis() - previousMillis > 1000)
  {
    previousMillis = millis();
    Serial.println(F("runtime"));
    Serial.println(millis() / 1000);
    lcd.clear();
    lcd.print(F("runtime"));
    lcd.setCursor(0, 1);
    lcd.print(millis() / 1000);
  }
}

//show a random value on LCD
void updateRandom()
{
  static uint32_t previousMillis = 0;
  if (millis() - previousMillis > 2000)
  {
    previousMillis = millis();
    Serial.println(F("Random:"));
    Serial.println(random(42));  // just print a Random number
    lcd.clear();
    lcd.print(F("Random:"));
    lcd.setCursor(0, 1);
    lcd.print(random(42));
  }
}

// Debounce and Change state detection for the keypad
bool wasPressedKeypad()
{
  static uint32_t previousMillis = 0;
  static bool previousState = OFF;
  bool currentState = lcd.digitalRead(btnSelect);
  bool result = false;
  if (currentState != previousState && millis() - previousMillis > 40)  // Debounce
  {
    previousMillis = millis();
    previousState = currentState;
    if (currentState == LOW) {
      result = true;
      Serial.println(F("pressed"));
    }
  }
  
  return result;
}

// just an example how to debounce pins on the Arduino
bool wasPressedDiscrete()
{
  static uint32_t previousMillis = 0;
  static bool previousState = LOW;
  bool currentState = digitalRead(A0);
  bool result = false;
  if (currentState != previousState && millis() - previousMillis > 40)
  {
    previousMillis = millis();
    if (currentState == LOW) result = true;
    previousState = currentState;
  }
  return result;
}

void runFSM()
{
  switch (state)
  {
    case State::FIXTEXT :
      updateFixtext();
      if (wasPressedKeypad())   
        state = State::RUNTIME;
      break;
    case State::RUNTIME :
      updateRuntime();
      if (wasPressedKeypad())   
        state = State::RANDOM;
      break;
    case State::RANDOM :
      updateRandom();
      if (wasPressedKeypad())   
        state = State::FIXTEXT;
      break;
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("\nStart"));
  Wire.begin();                        // start I2C library
  lcd.begin();                         // initialize the LCD
  lcd.setCursor(1, 0);                 // set the cursor to a specific position
  lcd.print("Hello, world!");
  lcd.setCursor(0, 1);
  lcd.print("αβμΣ°÷∞←→äöüßÄÖÜ");     // show some special character entered in UTF-8

  for (auto & i : button)
    lcd.setPinMode(i, INPUT_PULLUP);   // activate the internal pullups for the defined buttons
  lcd.setPinMode(rgbBlue, OUTPUT);     // set the RGB pins to outputs
  lcd.setPinMode(rgbRed, OUTPUT);
  lcd.setPinMode(rgbGreen, OUTPUT);
  lcd.digitalWrite(rgbBlue, OFF);      // swith a RGB pin
  lcd.digitalWrite(rgbRed, OFF);
  lcd.digitalWrite(rgbGreen, ON);
}

void loop()
{
  runFSM();
}

p.s.: this is the LCD keypad I'm using:
https://werner.rothschopf.net/microcontroller/202105_arduino_liquid_crystal_mcp23017_en.htm

1 Like

I've set everything back to the way it was when i started from my adafruit example.

I need to understand if I'm even able to use the Metro M3 Adafruit version Arduino Uno the same way you are using the R-Pi I have to look into that myself but I have no idea, if anyone knows let me know. Yes does also use the MCP23017 but I don't have the R-Pi only the Arduino. I need to research. I am trying to avoid the pain as this is purely hobby proj.

This is my example code I've commented out a bit because I'm trying to integrate a bit with your logic and attempted to do something with the Millis last weekend but am hitting myself up against the wall. Dan's asteroids and hunt the whumpus didn't teach me too well about state machine as I had hope which caused me to uninstall everything and start fresh.

/*********************

Example code for the Adafruit RGB Character LCD Shield and Library

This code displays text on the shield, and also reads the buttons on the keypad.
When a button is pressed, the backlight changes color.

**********************/

// include the library code:
#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>

// The shield uses the I2C SCL and SDA pins. On classic Arduinos
// this is Analog 4 and 5 so you can't use those for analogRead() anymore
// However, you can connect other I2C sensors to the I2C bus and share
// the I2C bus.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();

// These #defines make it easy to set the backlight color
#define RED 0x1
#define YELLOW 0x3
#define GREEN 0x2
#define TEAL 0x6
#define BLUE 0x4
#define VIOLET 0x5
#define WHITE 0x7

//void setup() {
// Debugging output
// Serial.begin(9600);
// set up the LCD's number of columns and rows:
// lcd.begin(16, 2);

// Print a message to the LCD. We track how long it takes since
// this library has been optimized a bit and we're proud of it :slight_smile:
// int time = millis();
// lcd.print("Hello, world!");
// time = millis() - time;
// Serial.print("Took "); Serial.print(time); Serial.println(" ms");
// lcd.setBacklight(WHITE);
//}

void setup()
{
Serial.begin(9600);
Serial.println(F("\nStart"));
Wire.begin(); // start I2C library
lcd.begin(16,2); // initialize the LCD
lcd.setCursor(0, 0); // set the cursor to a specific position
lcd.print("Hello, world!");
lcd.setCursor(0, 1);
lcd.print("or something"); // show some special character entered in UTF-8

// for (auto & i : button)
// lcd.setPinMode(i, INPUT_PULLUP); // activate the internal pullups for the defined buttons
// lcd.setPinMode(rgbBlue, OUTPUT); // set the RGB pins to outputs
// lcd.setPinMode(rgbRed, OUTPUT);
// lcd.setPinMode(rgbGreen, OUTPUT);
// lcd.digitalWrite(rgbBlue, OFF); // swith a RGB pin
// lcd.digitalWrite(rgbRed, OFF);
// lcd.digitalWrite(rgbGreen, ON);
}

uint8_t i=0;
//void loop() {
// // set the cursor to column 0, line 1
// (note: line 1 is the second row, since counting begins with 0):
//lcd.setCursor(0, 1);
// print the number of seconds since reset:
// lcd.print(millis()/1000);

uint8_t buttons = lcd.readButtons();

if (buttons) {
lcd.clear();
lcd.setCursor(0,0);
if (buttons & BUTTON_UP) {
lcd.print("UP ");
lcd.setBacklight(RED);
}
if (buttons & BUTTON_DOWN) {
lcd.print("DOWN ");
lcd.setBacklight(RED);
}
if (buttons & BUTTON_LEFT) {
lcd.print("LEFT ");
lcd.setBacklight(RED);
}
if (buttons & BUTTON_RIGHT) {
lcd.print("RIGHT ");
lcd.setBacklight(RED);
}
if (buttons & BUTTON_SELECT) {
lcd.print("SELECT ");
lcd.setBacklight(RED);
}
}
}

I need to read a bit and what I'll probably do is try to just utilize the pure logic of your methods but as far as the hardware coding modifications from R-Pi to Arduino I am a bit lost need to do more research.

I have code working, stolen, compiles, uploads. Is the reason the other buttons do not work due to there being no code to simply provide functionality to those buttons ? Disgraceful question. I will add more read button code to the read button method.

This is what I have done this evening

//Example code for the Adafruit RGB Character LCD Shield and Library

//This code displays text on the shield, and also reads the buttons on the keypad.
//When a button is pressed, the backlight changes color.

//**********************/

// include the library code:

#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>
#include <NoiascaLiquidCrystal.h>
#include <NoiascaHW/lcd_mcp23017.h>


  const byte cols = 16;        // columns/characters per row
const byte rows = 2;         // how many rows
const byte addr = 0x20;      // set the LCD address to 0x20

const byte rs = 7 + 8;       // GPIOB7
const byte rw = 255;         // not used
const byte en = 5 + 8;       // GPIOB5
const byte d4 = 4 + 8;       // GPIOB4
const byte d5 = 3 + 8;       // GPIOB3
const byte d6 = 2 + 8;       // GPIOB2
const byte d7 = 1 + 8;       // GPIOB1
const byte bl = 255;         // not used

const byte rgbRed = 6;       // GPIOA6
const byte rgbGreen = 0 + 8; // GPIOB0
const byte rgbBlue = 7;      // GPIOA7

const byte btnSelect = 0;    // GPIOA0 Buttons
const byte btnRight = 1;
const byte btnDown = 2;
const byte btnUp = 0x08;
const byte btnLeft = 4;

const byte button[] {BUTTON_LEFT, btnUp, BUTTON_DOWN, BUTTON_RIGHT, btnSelect};  // the 5 buttons on the LCD RGB KEYPAD connected to GPIO A
const byte OFF = HIGH;                // The buttons are low active, therefore we need to inverse them
const byte ON  = !OFF;

LiquidCrystal_MCP23017_custompin lcd(addr, rs, rw, en, d4, d5, d6, d7, bl, POSITIVE, cols, rows);
//LiquidCrystal_MCP23017_custompin_base lcd(addr, rs, rw, en, d4, d5, d6, d7, bl, POSITIVE, cols, rows); // this is just for internal tests

struct Data {
  char line[rows][cols + 1 + 8];  // a buffer for each line[row], with the length of cols + null + some spare place if UTF-8 is needed
};

const Data data[] {
  {"Text A 0", "Text A 1"},
  {"123456789A123456", "123456789B123456"},
  {"Latin", "Ä ä Ö ö Ü ü ß"},
  {"Symbols", "αβμΣ°÷∞←→"},
  {"Text E 0", "Text E 1"}
};

const size_t noOfData = sizeof (data) / sizeof(data[0]); // how many elements in data?
enum class State { FIXTEXT, RUNTIME, RANDOM} state;      // what kind of data should be shown
size_t currentData = 0;                                  // which data should be shown next time
const byte discretePin = A0;                             // just a pin on the Arduino to read from - not used

// loop through the array of data
void updateFixtext()
{
  static uint32_t previousMillis = 0;
  static size_t currentLine = 0;
  if (millis() - previousMillis > 4000)
  {
    previousMillis = millis();
    Serial.println(data[currentLine].line[0]);
    lcd.clear();
    lcd.print(data[currentLine].line[0]);
    lcd.setCursor(0, 1);
    lcd.print(data[currentLine].line[1]);
    // increase line for next iteration
    currentLine++;
    currentLine = currentLine % noOfData;
  }
}

//show runtime on LCD
void updateRuntime()
{
  static uint32_t previousMillis = 0;
  if (millis() - previousMillis > 1000)
  {
    previousMillis = millis();
    Serial.println(F("runtime"));
    Serial.println(millis() / 1000);
    lcd.clear();
    lcd.print(F("runtime"));
    lcd.setCursor(0, 1);
    lcd.print(millis() / 1000);
  }
}

//show a random value on LCD
void updateRandom()
{
  static uint32_t previousMillis = 0;
  if (millis() - previousMillis > 2000)
  {
    previousMillis = millis();
    Serial.println(F("Random:"));
    Serial.println(random(42));  // just print a Random number
    lcd.clear();
    lcd.print(F("Random:"));
    lcd.setCursor(0, 1);
    lcd.print(random(42));
  }
}

// Debounce and Change state detection for the keypad
bool wasPressedKeypad()
{
  static uint32_t previousMillis = 0;
  static bool previousState = OFF;
  bool currentState = lcd.digitalRead(btnSelect);
  bool result = false;
  if (currentState != previousState && millis() - previousMillis > 40)  // Debounce
  {
    previousMillis = millis();
    previousState = currentState;
    if (currentState == LOW) {
      result = true;
      Serial.println(F("pressed"));
    }
  }

  return result;
}

// just an example how to debounce pins on the Arduino
bool wasPressedDiscrete()
{
  static uint32_t previousMillis = 0;
  static bool previousState = LOW;
  bool currentState = digitalRead(A0);
  bool result = false;
  if (currentState != previousState && millis() - previousMillis > 40)
  {
    previousMillis = millis();
    if (currentState == LOW) result = true;
    previousState = currentState;
  }
  return result;
}

void runFSM()
{
  switch (state)
  {
    case State::FIXTEXT :
      updateFixtext();
      if (wasPressedKeypad())
        state = State::RUNTIME;
      break;
    case State::RUNTIME :
      updateRuntime();
      if (wasPressedKeypad())
        state = State::RANDOM;
      break;
    case State::RANDOM :
      updateRandom();
      if (wasPressedKeypad())
        state = State::FIXTEXT;
      break;
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("\nStart"));
  Wire.begin();                        // start I2C library
  lcd.begin();                         // initialize the LCD
  lcd.setCursor(1, 0);                 // set the cursor to a specific position
  lcd.print("Hello, world!");
  lcd.setCursor(0, 1);
  lcd.print("αβμΣ°÷∞←→äöüßÄÖÜ");     // show some special character entered in UTF-8

  for (auto & i : button)
    lcd.setPinMode(i, INPUT_PULLUP);   // activate the internal pullups for the defined buttons
  lcd.setPinMode(rgbBlue, OUTPUT);     // set the RGB pins to outputs
  lcd.setPinMode(rgbRed, OUTPUT);
  lcd.setPinMode(rgbGreen, OUTPUT);
  lcd.digitalWrite(rgbBlue, OFF);      // swith a RGB pin
  lcd.digitalWrite(rgbRed, ON);
  lcd.digitalWrite(rgbGreen, OFF);
}

void loop()
{
  runFSM();
}

a) please read in the forum guides how to post code and modify your post to make your code readable.
b) I'm totally lost what information you want to get. What do you expect from your code and what doesn't work currently?

1 Like

That square block is the interpretation of the forum software of the [] in your code :wink: As requested, please use code tags which will prevent this from happening. You can edit your post, select all code and click the </> button to apply code tags and next save your post.

1 Like

// include the library code:
#include <Wire.h>
#include <Adafruit_RGBLCDShield.h>
#include <utility/Adafruit_MCP23017.h>
#include <NoiascaLiquidCrystal.h>
#include <NoiascaHW/lcd_mcp23017.h> 

//  Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();
 // uint8_t i=0;
//  uint8_t buttons = lcd.readButtons();


const byte cols = 16;        // columns/characters per row
const byte rows = 2;         // how many rows
const byte addr = 0x20;      // set the LCD address to 0x20

const byte rs = 7 + 8;       // GPIOB7
const byte rw = 255;         // not used
const byte en = 5 + 8;       // GPIOB5
const byte d4 = 4 + 8;       // GPIOB4
const byte d5 = 3 + 8;       // GPIOB3
const byte d6 = 2 + 8;       // GPIOB2
const byte d7 = 1 + 8;       // GPIOB1
const byte bl = 255;         // not used

const byte rgbRed = 6;       // GPIOA6
const byte rgbGreen = 0 + 8; // GPIOB0
const byte rgbBlue = 7;      // GPIOA7

const byte btnSelect = 0;    // GPIOA0 Buttons
const byte btnRight = 1;    // A1
const byte btnDown = 2;     //A2
const byte btnUp = 3;       //A3
const byte btnLeft = 4;     //A4

const byte button`[]` {btnLeft, btnUp, btnDown, btnRight, btnSelect};  // the 5 buttons on the LCD RGB KEYPAD connected to GPIO A
const byte OFF = HIGH;                // The buttons are low active, therefore we need to inverse them
const byte ON  = !OFF;

LiquidCrystal_MCP23017_custompin lcd(addr, rs, rw, en, d4, d5, d6, d7, bl, POSITIVE, cols, rows);
//LiquidCrystal_MCP23017_custompin_base lcd(addr, rs, rw, en, d4, d5, d6, d7, bl, POSITIVE, cols, rows); // this is just for internal tests

struct Data {
  char line[rows][cols + 1 + 8];  // a buffer for each line[row], with the length of cols + null + some spare place if UTF-8 is needed
};

const Data data`[]` {
  {"Text A 0", "Text A 1"},
  {"123456789A123456", "123456789B123456"},
  {"Latin", "Ä ä Ö ö Ü ü ß"},
  {"Symbols", "αβμΣ°÷∞←→"},
  {"Text E 0", "Text E 1"}
};

const size_t noOfData = sizeof (data) / sizeof(data[0]); // how many elements in data?
enum class State {FIXTEXT, RUNTIME, RANDOM, newstate} state;      // what kind of data should be shown
size_t currentData = 0;                                  // which data should be shown next time
const byte discretePin = A0;                             // just a pin on the Arduino to read from - not used

// loop through the array of data
void updateFixtext()
{
  static uint32_t previousMillis = 0;
  static size_t currentLine = 0;
  if (millis() - previousMillis > 4000)
  {
    previousMillis = millis();
    Serial.println(data[currentLine].line[0]);
    lcd.clear();
    lcd.print(data[currentLine].line[0]);
    lcd.setCursor(0, 1);
    lcd.print(data[currentLine].line[1]);
    // increase line for next iteration
    currentLine++;
    currentLine = currentLine % noOfData;
  }
}

//show runtime on LCD
void updateRuntime()
{
  static uint32_t previousMillis = 0;
  if (millis() - previousMillis > 1000)
  {
    previousMillis = millis();
    Serial.println(F("runtime"));
    Serial.println(millis() / 1000);
    lcd.clear();
    lcd.print(F("runtime"));
    lcd.setCursor(0, 1);
    lcd.print(millis() / 1000);
  }
}

//show a random value on LCD
void updateRandom()
{
  static uint32_t previousMillis = 0;
  
  
  if (millis() - previousMillis > 2000)
  {
    previousMillis = millis();

    
    Serial.println(F("Random:"));
    Serial.println(random(42));  // just print a Random number
    lcd.clear();
    lcd.print(F("Random:"));
    lcd.setCursor(0, 1);
    lcd.print(random(42));


  }
}




void newstates()
  {

  static uint32_t previousMillis = 0;  
  if (millis() - previousMillis > 8000)
  {
    previousMillis = millis();
     lcd.clear();
      lcd.print("poopy Button hit");
      lcd.setCursor(0,1);
      lcd.print("poopy time:");

      delay(4000);  

      int randNumber = random(2);
    
//   lcd.setBacklight(RED);
   
               if (randNumber == 1)
               {
                lcd.clear();
                lcd.print("big"); 
            
            }

            
            if (randNumber == 0)
               {
                lcd.clear();
                lcd.print("poopy"); 
            
            }


  }
  }




// Debounce and Change state detection for the keypad
bool wasPressedKeypad()
{
  static uint32_t previousMillis = 0;
  static bool previousState = OFF;
  bool currentState = lcd.digitalRead(btnSelect);
  bool result = false;
  if (currentState != previousState && millis() - previousMillis > 40)  // Debounce
  {
    previousMillis = millis();
    previousState = currentState;
    if (currentState == LOW) {
      result = true;
      Serial.println(F("pressed"));
    }
  }
  
  return result;
}





// Debounce and Change state detection for the keypad
bool wasLeftPressed()
{
  static uint32_t previousMillis = 0;
  static bool previousState = OFF;
  bool currentState = lcd.digitalRead(btnLeft);
  bool result = false;
  if (currentState != previousState && millis() - previousMillis > 40)  // Debounce
  {
    previousMillis = millis();
    previousState = currentState;
    if (currentState == LOW) {
      result = true;
      Serial.println(F("pressed"));
    }
  }
  
  return result;
}










// just an example how to debounce pins on the Arduino
//bool wasPressedDiscrete()
//{
//  static uint32_t previousMillis = 0;
//  static bool previousState = LOW;
//  bool currentState = digitalRead(A0);
//  bool result = false;
//  if (currentState != previousState && millis() - previousMillis > 40)
//  {
//    previousMillis = millis();
//   if (currentState == LOW) result = true;
//   previousState = currentState;
//  }
//  return result;
//}

void runFSM()
{
  switch (state)
  {
    case State::FIXTEXT :
      updateFixtext();
      if (wasPressedKeypad())   
        state = State::RUNTIME;
      break;
    case State::RUNTIME :
      updateRuntime();
      if (wasPressedKeypad())   
        state = State::RANDOM;
      break;
    case State::RANDOM :
      updateRandom();
      if (wasPressedKeypad())   
        state = State::FIXTEXT;
      break;
    case State::newstate :
      newstates();
    if (wasPressedKeypad())
        state = State::newstate;
        break;
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println(F("\nStart"));
  Wire.begin();                        // start I2C library
  lcd.begin();                         // initialize the LCD
  lcd.setCursor(1, 0);                 // set the cursor to a specific position
  lcd.print("Hello, world!");
  lcd.setCursor(0, 1);
  lcd.print("αβμΣ°÷∞←→äöüßÄÖÜ");     // show some special character entered in UTF-8

  for (auto & i : button)
    lcd.setPinMode(i, INPUT_PULLUP);   // activate the internal pullups for the defined buttons
  lcd.setPinMode(rgbBlue, OUTPUT);     // set the RGB pins to outputs
  lcd.setPinMode(rgbRed, OUTPUT);
  lcd.setPinMode(rgbGreen, OUTPUT);
  lcd.digitalWrite(rgbBlue, OFF);      // swith a RGB pin
  lcd.digitalWrite(rgbRed, ON);
  lcd.digitalWrite(rgbGreen, OFF);
}

void loop()
{
  runFSM();
}

And again no code tags :wink: Another way

  1. In the IDE, use tools -> autoformat; this will properly indent the code.
  2. In the IDE, use edit -> copy for forum; this will place your text on the clipboard.
  3. Paste in a reply and post.

Code tags prevent the misinterpretation of code by te forum software, make it easier to read and easier to copy, so please, please, please

1 Like
    case State::newstate :
      newstates();
      if (wasPressedKeypad())
        state = State::newstate;
      break;

Finite statemachines only change state if you tell them to do so. Let's assume that in the above, you are in State::newstate. If the button is not pressed, nothing changes; if the button is pressed, you change to the same state so it's actually doing nothing.

You will need to change from one (or all) other states to State::newstate. Let's keep it simple for the explanation and after State::RANDOM you want to go to State::newstate

    case State::RANDOM :
      updateRandom();
      if (wasPressedKeypad())
        state = State::newstate;
      break;
    case State::newstate :
      newstates();
      if (wasPressedKeypad())
        state = State::FIXTEXT;
      break;

Note: enums are often written in all capitals as they are constant values; your newstate is lower case. Change it to NEWSTATE; this allows for consistency in your code.

1 Like

@red18594
like advised several times now: please read how to post code in code tags.
Go back to your old post, edit it and add code tags.
After you have edited your post - your code should appear nicely in code tags.
Posting code without code tags make it unreadable and details might get lost. Therefore - post code in code tags. If you don't know how - read the forum how to post code in code tags.

P.S.: not to forget: please post your code in code tags.

1 Like

..i've learned how to put code in code tags.

What is TIXTEXT ?

I am trying to activate the "newstate" enum state added does it have to be uppercase?

I am trying to activate "newstates()" method which prints lines on the lcd screen.

Sorry, that was a typo. FIXTEXT
I've corrected the original reply.

1 Like

To be fair it was a very dumb question. Thank you so much I was able to figure things out partially due to experimentation and partially due to your help I now am able to implement a fourth state. Now after a lot of pain I've learned how to create cycling states thru noiasca's hard work.

I have a short simple additional question at this point. Am grateful for anyone who answers: for the below line:

enum class State {FIXTEXT=0, RUNTIME=1, RANDOM=2, QUOTES=3} state;      

I am interested in being able to cycle these modes. Am I allowed to simply do this as is and how would i cycle this in terms of just altering the state or State value to go up or down in a line of code? (don't look at the whole wasleftpressed method or the wasselectpressed method), do i need to cast this?

All I would like to know from the forum is how to I write that line of code to assign the states numerical value and is incrementing and decrementing the state as simple as doing something such as

state= state +1;
state = state-1;

where applicable.

I am trying to do as much as I can without asking you guys too much. Appreciate any guidance. I am miles ahead of where I was before thanks to your help.

EDIT: I am trying to decide at this exact moment if I want to just cycle the states thru manual handing off in the RUNFSM method (THIS WOULD ENTAIL MY UNDERSTANDING IS NOT HAVING INTEGERS AT ALL) or if I want to even bother to implement the numerical idea. So don't go too crazy. I think having numerical values for state or States would help me but i have to think about it.