Creating a state machine to make ice

@florindopvh ,
I have deleted your Portuguese posts and any associated replies. This is the English forum, please post only in English. Continuing to post in a language other than English will most likely earn you a rest from the forum.

There are a number of posts previous to this that show some frustration. I have left them in place for now, but this kind of thing is not to continue. @florindopvh do understand that, as others have pointed out, people are trying to help you and when they ask questions they are doing so because they want extra information in order to help YOU. You are, of course, not compelled to provide all or any of the requested information, but equally, those who might help are not compelled to do so. Please consider the requests for more information and decide if you want to provide it or not. Helpers can then consider if you have provided enough information to continue to help you.

The debate on about the quality help, helpers, the forum and all that stuff ends here. The next person to post anything along those lines will get a 2 day ban from posting in the forum. From now on please only reply if you have information that helps the OP with their project, or, if you are the OP, in response to the help, information and requests made to you already.

For clarity, I am a moderator.

I apologize for my part, I only wrote it in Portuguese so that "maybe you can understand the work it took to copy, translate and answer things that are not relevant...

Regarding the HELP, I thank you again....

Regarding requests that are not relevant, I will follow your recommendation when I think I don't need to respond, I will simply ignore the annoying thing..

I hope once again I haven't been offensive to anyone.... that's not the point here... the question was I want to do something and I'm going to do it even if it's alone I'm almost there....

but I would also like to share it here because I saw that there are or were 4 more people here who had their topics closed without a solution. If we resolve this, it will stay here for the next ones and maybe even for different projects with the same architecture....

1 Like

There was so much out of context that I got lost, someone questioned the lack of pullup resistors in the code, below is the latest version of the code (gpt) with this already corrected...

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // LCD address and dimensions (16x2)

// Pin definitions
enum Pins {
  pinButton = 2, 
  pinReset = 3,
  pinWaterLevel = 4,
  pinTrayHigh = 7,
  pinTrayLow = 6,
  pinWaterFlow = 5,
  pinWaterPump = 8,
  pinIceSolenoid = 11,
  pinCompressor = 9,
  pinTrayMotor = 10,
  pinWaterShortageLED = 12,
  pinNoFlowLED = 13
};

#define stopTrayMotor digitalWrite(pinTrayMotor,LOW)

// Tray motor direction constants
const int CW = 1; // Clockwise direction
const int CCW = 2; // Counterclockwise direction
const char getIceSizeName[3][7] = { "Small ", "Medium", "Large "};
const byte Time[3] = {3, 6, 9};

// Maximum motor direction change timeout (half a second)
const unsigned long motorDirectionChangeTimeout = 500;

enum State {
  SETUP,
  CHECK_WATER_LEVEL,
  INITIAL_TRAY_POSITION,
  SELECT_TIME,
  FILLING,
  COMPRESSOR_ON,
  MAKING_ICE,
  LOWER_TRAY,
  RELEASE_ICE,
  ERROR_WATER,
  ERROR_FLOW,
  ERROR_TRAY
};

unsigned long stateStartTime;
unsigned long iceMakingTime = 0; // Default time of 10 seconds
bool motorDirectionChanged = true;
unsigned long lastMotorDirectionChangeTime = 0;
State currentState;
int buttonPressCount = 0; // Variável de seleção
bool CountedDown = false; // Variável de contagem inicializada fora do switch

void setup() {
  // Pin input/output configuration
  pinMode(pinButton, INPUT_PULLUP);
  pinMode(pinReset, INPUT_PULLUP);
  pinMode(pinWaterLevel, INPUT_PULLUP);
  pinMode(pinTrayHigh, INPUT_PULLUP);
  pinMode(pinTrayLow, INPUT_PULLUP);
  pinMode(pinWaterFlow, INPUT_PULLUP);

  pinMode(pinWaterPump, OUTPUT);
  pinMode(pinIceSolenoid, OUTPUT);
  pinMode(pinCompressor, OUTPUT);
  pinMode(pinTrayMotor, OUTPUT);
  pinMode(pinWaterShortageLED, OUTPUT);
  pinMode(pinNoFlowLED, OUTPUT);

  // LCD initialization
  lcd.init();
  lcd.backlight();

  // Start state machine at SELECT_TIME
  currentState = SELECT_TIME;
}

void loop() {
  switch (currentState) {
    case SELECT_TIME:
      lcd.clear();
      lcd.print("Cube Size:");
      stateStartTime = millis();
      while (millis() - stateStartTime < 15000) {
        if (digitalRead(pinButton) == LOW) {
          buttonPressCount++;
          if (buttonPressCount > 3) buttonPressCount = 1; // Closed cycle
          lcd.setCursor(0, 1);
          lcd.print(getIceSizeName[buttonPressCount - 1]);
        }
        delay(50); // Debounce delay
      }
      iceMakingTime = Time[buttonPressCount - 1];
      lcd.clear();
      lcd.print("Ice size: ");
      lcd.print(getIceSizeName[buttonPressCount - 1]);
      lcd.setCursor(0, 1);
      lcd.print(iceMakingTime);
      lcd.print(" seconds");
      delay(2000);
      iceMakingTime *= 1000;
      currentState = CHECK_WATER_LEVEL;
      break;

    case CHECK_WATER_LEVEL:
      if (digitalRead(pinWaterLevel) == HIGH) {
        lcd.clear();
        lcd.print("Water Shortage");
        digitalWrite(pinWaterShortageLED, HIGH);
        digitalWrite(pinWaterPump, LOW);
        digitalWrite(pinIceSolenoid, LOW);
        digitalWrite(pinCompressor, LOW);
        stopTrayMotor;
        while (digitalRead(pinReset));
        digitalWrite(pinWaterShortageLED, LOW);
      } else currentState = INITIAL_TRAY_POSITION;

      lcd.clear();
      lcd.print("COMPRESSOR ON");
      digitalWrite(pinCompressor, HIGH);
      delay(2000);
      break;

  case INITIAL_TRAY_POSITION:
  lcd.clear();
  lcd.print("RAISING");
  runTrayMotor(CW); // Raise tray (clockwise direction)
  stateStartTime = millis();
  CountedDown = false;

  // Wait for pinTrayLow to go HIGH (indicating tray is starting to rise)
  while (!digitalRead(pinTrayLow)) {
    if (millis() - stateStartTime > 6000) {
      currentState = ERROR_TRAY;
      return;
    }
    delay(100);
    displayCountdown(stateStartTime, 6000);

    // Check if pinTrayHigh is LOW before proceeding to FILLING
    if (!digitalRead(pinTrayHigh)) {
      stopTrayMotor;
      currentState = FILLING;
      return;
    }
  }

  // If pinTrayLow is HIGH but pinTrayHigh is not LOW yet, continue waiting
  while (digitalRead(pinTrayHigh)) {
    if (millis() - stateStartTime >= motorDirectionChangeTimeout) {
      currentState = ERROR_TRAY;
      return;
    }
    delay(100);
    displayCountdown(stateStartTime, motorDirectionChangeTimeout);
  }

  stopTrayMotor;
  currentState = FILLING;
  break;




    case FILLING:
      lcd.clear();
      lcd.print("FILLING");
      stateStartTime = millis();
      digitalWrite(pinWaterPump, HIGH);
      while (millis() - stateStartTime < 2000) {
        displayCountdown(stateStartTime, 2000);
      }
      digitalWrite(pinWaterPump, LOW);
      currentState = MAKING_ICE;
      break;

    case MAKING_ICE:
      stateStartTime = millis();
      lcd.clear();
      lcd.print("FREEZING");
      while (millis() - stateStartTime < iceMakingTime) {
        displayCountdown(stateStartTime, iceMakingTime);
      }
      currentState = LOWER_TRAY;
      break;

    case LOWER_TRAY:
  lcd.clear();
  lcd.print("LOWERING");
  runTrayMotor(CCW); // Lower tray (counterclockwise direction)
  stateStartTime = millis();
  CountedDown = false;

  // Wait for pinTrayHigh to go HIGH (indicating tray is starting to lower)
  while (!digitalRead(pinTrayHigh)) {
    if (millis() - stateStartTime > 6000) {
      currentState = ERROR_TRAY;
      return;
    }
    delay(100);
    displayCountdown(stateStartTime, 6000);
  }

  // Wait for pinTrayLow to go LOW (indicating tray is fully lowered)
  while (digitalRead(pinTrayLow)) {
    if (millis() - stateStartTime > 6000) {
      currentState = ERROR_TRAY;
      return;
    }
    delay(100);
    displayCountdown(stateStartTime, 6000);
  }

  stopTrayMotor;
  currentState = RELEASE_ICE;
  break;


    case RELEASE_ICE:
      stateStartTime = millis();
      lcd.clear();
      lcd.print("RELEASING ICE");
      digitalWrite(pinIceSolenoid, HIGH);
      while (millis() - stateStartTime < 2000) {
        displayCountdown(stateStartTime, 2000);
      }
      digitalWrite(pinIceSolenoid, LOW);
      currentState = CHECK_WATER_LEVEL; // Voltando para CHECK_WATER_LEVEL ao invés de INITIAL_TRAY_POSITION
      break;

    case ERROR_WATER:
      lcd.clear();
      lcd.print("Water Error");
      digitalWrite(pinWaterPump, LOW);
      digitalWrite(pinIceSolenoid, LOW);
      digitalWrite(pinCompressor, LOW);
      stopTrayMotor;
      while (digitalRead(pinReset));
      currentState = CHECK_WATER_LEVEL;
      break;

    case ERROR_FLOW:
      lcd.clear();
      lcd.print("No Water Flow");
      digitalWrite(pinNoFlowLED, HIGH);
      digitalWrite(pinWaterPump, LOW);
      digitalWrite(pinIceSolenoid, LOW);
      digitalWrite(pinCompressor, LOW);
      stopTrayMotor;
      while (digitalRead(pinReset));
      digitalWrite(pinNoFlowLED, LOW);
      currentState = CHECK_WATER_LEVEL;
      break;

    case ERROR_TRAY:
      lcd.clear();
      lcd.print("Tray Error");
      digitalWrite(pinWaterPump, LOW);
      digitalWrite(pinIceSolenoid, LOW);
      digitalWrite(pinCompressor, LOW);
      stopTrayMotor;
      while (digitalRead(pinReset));
      currentState = CHECK_WATER_LEVEL;
      break;

    default:
      currentState = CHECK_WATER_LEVEL;
      break;
  }
}

void displayCountdown(unsigned long startTime, unsigned long duration) {
  unsigned long remainingTime = (duration - (millis() - startTime)) / 1000;
  lcd.setCursor(0, 1);
  lcd.print("Time left: ");
  lcd.print(remainingTime);
  lcd.print(" sec");
}

void runTrayMotor(int direction) {
  if (direction == CW) {
    digitalWrite(pinTrayMotor, HIGH);
    motorDirectionChanged = false;
  } else if (direction == CCW) {
    digitalWrite(pinTrayMotor, HIGH);
    motorDirectionChanged = true;
  }
}

before forgetting .... the execution times are for simulation purposes

in the functional version the times are

small cube 720 sec

medium cube 900 sec
big cube 1020 sec

filling 15 sec

releasing 8 sec

obviously this is in my climate... for colder places I recommend doing the cubes with a stopwatch once and getting an idea of ​​the time it takes and adjusting the code

Good afternoon, project update, after struggling with the question of the direction of rotation of the motor... and understanding that the original board has half the intelligence that an Arduino has (if not less) I decided to do a test with the motor outside the control circuit,

direct connection of the motor, and to my surprise the reversal of direction happens ALONE, when the energized motor bumps into something and remains immobilized for some time it reverses the direction of rotation... here is a video of the test.

Therefore, the activation logic does not need anything special, it simply has to be

You want to go down, start the engine and wait for the lower switch to close contact....

If you want to go up, start the engine and wait for the upper switch to close contact....

the machine nor the code need to worry about inverting rotation/polarity or anything like that...

video in zip format below

motor .zip (6,0,MB)

I'll take your word for it and pass on watching it happen.

This kind of motor is not uncommon, the reversal accomplished variously and as you are seeing, makes it easier to build a dumb controller, or a simpler sketch if you bring a microprocessor into the mix.

What are your friend's new ideas about the code?

a7

FINISHED CODE BELOW EVERYTHING WORKING AND MORE....

As I dismantled the machine to look at the engine, I also identified a photoresistor that works as a light barrier... if the upper bowl gets too full, everything can go wrong inside the machine... so I added an analogue input...(I don't the GPT chat) is a UV emitting LED so it looks like this every time the ice tray goes up, it turns on the LED and sees if the barrier has not formed between the LED and the photoreceptor... if there are no obstructions the cycle continues.. I've already tested everything and it worked 100%.

If you want to test and simulate, here it is, the issue is that it is like real times... you will have to wait 12 minutes until the tray is lowered.

(Circuit design control ICE MACHINE - Tinkercad)

final version of the code:

imputs:
size selection button
reset button
level switch
flow switch
high tray switch
low tray switch
full ice detection photoresistor

outputs:
water pump drive
compressor drive
tray motor drive
reverse solenoid drive to release ice
UV LED drive for photobarrier

execution times are adjusted and correct for real operation.

refueling 15 seconds
small ice 12 min
medium ice 14 min
big ice 16 min
hot gas 8 seconds.

The only thing that remains to be adjusted is the photobarrier value yet to be analyzed
I will make changes to the schematic (I will try to make the background white) and completely redo the layout of the board to post here and declare the project complete

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // Endereço e dimensões do LCD (16x2)

// Definições dos pinos
enum Pins {
  pinButton = 2, 
  pinReset = 3,
  pinWaterLevel = 4,
  pinTrayHigh = 6,
  pinTrayLow = 7,
  pinWaterFlow = 5,
  pinWaterPump = 8,
  pinIceSolenoid = 11,
  pinCompressor = 9,
  pinTrayMotor = 10,
  pinPhotoresistor = A0, // Novo pino analógico para o fotoresistor
  pinSourceLED = 12 // LED da fonte de luz
};

#define stopTrayMotor digitalWrite(pinTrayMotor, LOW)

const char getIceSizeName[3][7] = { "PEQUENO ", "MEDIO", "GRANDE "};
const byte Time[3] = {720, 840, 960};

enum State {
  SETUP,
  SELECT_TIME,
  COMPRESSOR_ON,
  CHECK_WATER_LEVEL,
  INITIAL_TRAY_POSITION,
  FILLING,
  MAKING_ICE,
  LOWER_TRAY,
  RELEASE_ICE,
  ERROR_WATER,
  ERROR_FLOW,
  ERROR_TRAY,
  CUBA_CHEIA // Novo estado para cuba cheia
};

unsigned long stateStartTime;
unsigned long iceMakingTime = 0; // Tempo padrão de 10 segundos
State currentState;
int buttonPressCount = 0; // Variável de seleção
bool CountedDown = false; // Variável de contagem inicializada fora do switch

void setup() {
  // Configuração de entrada/saída dos pinos
  pinMode(pinButton, INPUT_PULLUP);
  pinMode(pinReset, INPUT_PULLUP);
  pinMode(pinWaterLevel, INPUT_PULLUP);
  pinMode(pinTrayHigh, INPUT_PULLUP);
  pinMode(pinTrayLow, INPUT_PULLUP);
  pinMode(pinWaterFlow, INPUT_PULLUP);
  pinMode(pinPhotoresistor, INPUT); // Pino do fotoresistor como entrada analógica
  pinMode(pinSourceLED, OUTPUT); // Pino do LED da fonte de luz como saída

  pinMode(pinWaterPump, OUTPUT);
  pinMode(pinIceSolenoid, OUTPUT);
  pinMode(pinCompressor, OUTPUT);
  pinMode(pinTrayMotor, OUTPUT);

  // Inicialização do LCD
  lcd.init();
  lcd.backlight();

  // Iniciar a máquina de estados no estado SELECT_TIME
  currentState = SELECT_TIME;
}

void loop() {
  static unsigned long waterFlowHighStartTime = 0;
  static bool waterFlowHigh = false;

  switch (currentState) {
    case SELECT_TIME:
      lcd.clear();
      lcd.print("SELECIONE:");
      stateStartTime = millis();
      while (millis() - stateStartTime < 15000) {
        if (digitalRead(pinButton) == LOW) {
          buttonPressCount++;
          if (buttonPressCount > 3) buttonPressCount = 1; // Ciclo fechado
          lcd.setCursor(0, 1);
          lcd.print(getIceSizeName[buttonPressCount - 1]);
        }
        delay(50); // Atraso de debounce
      }
      iceMakingTime = Time[buttonPressCount - 1];
      lcd.clear();
      lcd.print("TAMANHO DO CUBO: ");
      lcd.print(getIceSizeName[buttonPressCount - 1]);
      lcd.setCursor(0, 1);
      lcd.print(iceMakingTime);
      lcd.print(" TEMPO");
      delay(2000);
      iceMakingTime *= 1000;
      currentState = CHECK_WATER_LEVEL;
      break;

    case CHECK_WATER_LEVEL:
      if (digitalRead(pinWaterLevel) == HIGH) {
        lcd.clear();
        lcd.print("POUCA AGUA");
        digitalWrite(pinWaterPump, LOW);
        digitalWrite(pinIceSolenoid, LOW);
        digitalWrite(pinCompressor, LOW);
        stopTrayMotor;
        while (digitalRead(pinReset));
      } else currentState = INITIAL_TRAY_POSITION;

      lcd.clear();
      lcd.print("COMPRESSOR ON");
      digitalWrite(pinCompressor, HIGH);
      delay(2000);
      break;

    case INITIAL_TRAY_POSITION:
      lcd.clear();
      lcd.print("SUBINDO");
      runTrayMotor(); // Subir bandeja
      digitalWrite(pinSourceLED, HIGH); // Acionar LED da fonte de luz
      stateStartTime = millis();
      CountedDown = false;

      // Espera o pinTrayHigh ficar LOW
      while (digitalRead(pinTrayHigh) == HIGH) {
        if (millis() - stateStartTime > 10000) {
          currentState = ERROR_TRAY;
          return;
        }
        delay(100);
        displayCountdown(stateStartTime, 10000);

        // Verificar o fotoresistor (A0) para cuba cheia (lógica invertida)
        int photoresistorValue = analogRead(pinPhotoresistor);
        if (photoresistorValue > 800) { // Exemplo de valor limite a ser ajustado conforme necessário
          currentState = CUBA_CHEIA;
          return;
        }
      }

      stopTrayMotor;
      digitalWrite(pinSourceLED, LOW); // Desligar LED da fonte de luz ao terminar
      currentState = FILLING;
      break;

    case FILLING:
      lcd.clear();
      lcd.print("ABASTECENDO");
      stateStartTime = millis();
      waterFlowHighStartTime = 0;
      waterFlowHigh = false;

      digitalWrite(pinWaterPump, HIGH);
      while (millis() - stateStartTime < 15000) {
        displayCountdown(stateStartTime, 15000);

        if (digitalRead(pinWaterFlow) == HIGH) {
          if (!waterFlowHigh) {
            waterFlowHigh = true;
            waterFlowHighStartTime = millis();
          } else if (millis() - waterFlowHighStartTime > 3000) {
            digitalWrite(pinWaterPump, LOW);
            currentState = ERROR_FLOW;
            return;
          }
        } else {
          waterFlowHigh = false;
        }
      }
      digitalWrite(pinWaterPump, LOW);
      currentState = MAKING_ICE;
      break;

    case MAKING_ICE:
      stateStartTime = millis();
      lcd.clear();
      lcd.print("PRODUZINDO");
      while (millis() - stateStartTime < iceMakingTime) {
        displayCountdown(stateStartTime, iceMakingTime);
      }
      currentState = LOWER_TRAY;
      break;

    case LOWER_TRAY:
      lcd.clear();
      lcd.print("ABAIXANDO");
      runTrayMotor(); // Descer bandeja
      stateStartTime = millis();
      CountedDown = false;

      // Espera o pinTrayLow ficar LOW
      while (digitalRead(pinTrayLow) == HIGH) {
        if (millis() - stateStartTime > 10000) {
          currentState = ERROR_TRAY;
          return;
        }
        delay(100);
        displayCountdown(stateStartTime, 10000);
      }

      stopTrayMotor;
      currentState = RELEASE_ICE;
      break;

    case RELEASE_ICE:
      stateStartTime = millis();
      lcd.clear();
      lcd.print("SOLTANDO CUBOS");
      digitalWrite(pinIceSolenoid, HIGH);
      while (millis() - stateStartTime < 8000) {
        displayCountdown(stateStartTime, 8000);
      }
      digitalWrite(pinIceSolenoid, LOW);
      currentState = CHECK_WATER_LEVEL;
      break;

    case ERROR_WATER:
      lcd.clear();
      lcd.print("FALTA DE AGUA");
      digitalWrite(pinWaterPump, LOW);
      digitalWrite(pinIceSolenoid, LOW);
      digitalWrite(pinCompressor, LOW);
      stopTrayMotor;
      while (digitalRead(pinReset));
      currentState = CHECK_WATER_LEVEL;
      break;

    case ERROR_FLOW:
      lcd.clear();
      lcd.print("SEM FLUXO");
      digitalWrite(pinWaterPump, LOW);
      digitalWrite(pinIceSolenoid, LOW);
      digitalWrite(pinCompressor, LOW);
      stopTrayMotor;
      while (digitalRead(pinReset));
      currentState = CHECK_WATER_LEVEL;
      break;

    case ERROR_TRAY:
      lcd.clear();
      lcd.print("BANDEJA TRAVADA");
      digitalWrite(pinWaterPump, LOW);
      digitalWrite(pinIceSolenoid, LOW);
      digitalWrite(pinCompressor, LOW);
      stopTrayMotor;
      while (digitalRead(pinReset));
      currentState = CHECK_WATER_LEVEL;
      break;

    case CUBA_CHEIA:
      lcd.clear();
      lcd.print("MUITO GELO");
      digitalWrite(pinWaterPump, LOW);
      digitalWrite(pinIceSolenoid, LOW);
      digitalWrite(pinCompressor, LOW);
      stopTrayMotor;
      digitalWrite(pinSourceLED, LOW); // Desligar LED da fonte de luz
      while (digitalRead(pinReset));
      currentState = CHECK_WATER_LEVEL;
      break;

    default:
      currentState = CHECK_WATER_LEVEL;
      break;
  }
}

void displayCountdown(unsigned long startTime, unsigned long duration) {
  unsigned long remainingTime = (duration - (millis() - startTime)) / 1000;
  lcd.setCursor(0, 1);
  lcd.print("FALTAM: ");
  lcd.print(remainingTime);
  lcd.print(" sec");
}

void runTrayMotor() {
  digitalWrite(pinTrayMotor, HIGH);
}

Good morning everyone,
as promised here, this next post will be if any error is identified by you, or when the card is ready and working on the machine... until then



ainda funcionando perfeitamente so alegrias.

still working perfectly, so happy.

1 Like

I'm thinking about using a nextion screen in the next version, will it be possible? an esp 32 could also perhaps even monitor via cell phone using the wifi network

I'm sure it will be possible, I suggest you start here:

If you use my methods and get stuck I will try to help.

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