SOLVED Stuck state machine?

The first state machine "ODStateMachine" appears to get stuck in the "else" (pump 1 & 2 off) statement of the "ST_ODP1_RUNNING" state once the optical density drops below the set threshold.

What I am seeing:
Once the ODVal drops below threshold, pumps 1 & 2 shut off as expected and the optical density is continuing to be read at it's defined intervals but because pump1 is not turning back on prior each OD reading, the sensor is effectively reading the same sample repeatedly--thus keeping pump1 off.

What I'm trying to accomplish:
When the ODVal is below the threshold to not take another OD reading until the ODStateMachine has reset/pump1 has started its run time anew.

This is only through the ODStateMachine portion:

// Adjustable time points
#define ODP1PUMP_INTERVAL       60000ul        // 1 minutes
#define ODP1OD_INTERVAL         30000ul         // 0.5 min
const byte OD_HIGH_THRESH = 400;               // arbitrary (will change for in-sito testing)

#define ByProductPUMP3_INTERVAL  60000ul       // 1 min
#define ByProductPUMP3_RUNTIME   30000ul        // 30 sec
#define ByProductPUMP4_INTERVAL  120000ul       // 2 min
#define ByProductPUMP4_RUNTIME   60000ul        // 1 min

//#include <SD.h>          //SD card library
#include <SPI.h>
#include <Wire.h>
#include <RTClib.h>      //Real Time Clock library
#include <hd44780.h>     //LCD library
#include <hd44780ioClass/hd44780_I2Cexp.h> // include i/o class header

hd44780_I2Cexp lcd; // declare lcd object: auto locate & config display for hd44780 chip

// LCD
const byte LCD_VCC = 13;

// Optical Density Sensor
const byte ODAnalogIN = A14;
const byte ODVCC = 51;
const byte ODGND = 50;

// Motors & Motor Drivers
const byte P1A = 35;
const byte P1B = 33;
const byte P1PWM = 8;
const byte P2A = 39;
const byte P2B = 41;
const byte P2PWM = 45;
const byte P3A = 38;
const byte P3B = 40;
const byte P3PWM = 46;
const byte P4A = 34;
const byte P4B = 32;
const byte P4PWM = 7;
const byte Driver1STBY = 36;
const byte Driver1VCC = 52;
const byte Driver2STBY = 37;
const byte Driver2VCC = 53;

// GATE VALVES
const byte GV1 = 15;  // TO chamber             (CHECK PIN#)
const byte GV2 = 29;  // TO Hydrocyclone
const byte GV3 = 23;  // TO chamber from resin column
const byte GV4 = 22;  // TO MeOH collection
const byte GV5 = 25;  // FROM flush reservoir
const byte GV6 = 24;  // FROM MeOH reservoir
const byte GV7 = 16;  // TO flush collection    (PIN# SUBJECT TO CHANGE)


void setup()
{
  Serial.begin(9600);
  //LDC set up
  pinMode(LCD_VCC, OUTPUT);
  digitalWrite (LCD_VCC, HIGH);

  // if (! rtc.begin())    //DS3231 is intialized
  // {
  //   while (1);
  // }
  //rtc.adjust(DateTime(F(_DATE_), F(_TIME_)));        //auto update from computer time
  //  rtc.adjust(DateTime(2019, 10, 29, 10, 01, 30));  //set date-time manually; yr, mo, day, hr, min, sec

  // initialize LCD with number of columns and rows:
  lcd.begin(20, 4);

  // Print initial message to LCD
  lcd.print("RUNNING ");

  //set up OD sensor
  pinMode (ODVCC, OUTPUT);
  pinMode (ODGND, OUTPUT);
  digitalWrite (ODVCC, HIGH);
  digitalWrite (ODGND, LOW);

  //set up Gate Valves
  pinMode (GV1, OUTPUT);
  pinMode (GV2, OUTPUT);
  pinMode (GV3, OUTPUT);
  pinMode (GV4, OUTPUT);
  pinMode (GV5, OUTPUT);
  pinMode (GV6, OUTPUT);
  pinMode (GV7, OUTPUT);

  //set up motor control pins
  pinMode (P1A, OUTPUT);
  pinMode (P1B, OUTPUT);
  pinMode (P1PWM, OUTPUT);
  pinMode (P2A, OUTPUT);
  pinMode (P2B, OUTPUT);
  pinMode (P2PWM, OUTPUT);
  pinMode (P3A, OUTPUT);
  pinMode (P3B, OUTPUT);
  pinMode (P3PWM, OUTPUT);
  pinMode (P4A, OUTPUT);
  pinMode (P4B, OUTPUT);
  pinMode (P4PWM, OUTPUT);

  pinMode (Driver1STBY, OUTPUT);
  pinMode (Driver1VCC, OUTPUT);
  pinMode (Driver2STBY, OUTPUT);
  pinMode (Driver2VCC, OUTPUT);
  digitalWrite (Driver1STBY, HIGH);
  digitalWrite (Driver1VCC, HIGH);
  digitalWrite (Driver2STBY, HIGH);
  digitalWrite (Driver2VCC, HIGH);

  //set motor speeds to 0% duty
  analogWrite(P1PWM, 0);
  analogWrite(P2PWM, 0);
  analogWrite(P3PWM, 0);
  analogWrite(P4PWM, 0);

}//setup

void loop()
{
  //  DateTime nowTime = rtc.now();
  //main loop runs very fast, peeking into each state machine function to see if any need attention
  ODStateMachine();           // Pump1, GV1 & GV2
  ByProductStateMachine();    // Pump3 & Pump4, GV3-GV7

}//loop

//ODP1 state names
#define ST_ODP1_INIT                0
#define ST_ODP1_INTERVAL_TIMING     1
#define ST_ODP1_RUNNING             2
//
void ODStateMachine( void )
{
  int
  ODVal;
  static byte
  stateODP1 = ST_ODP1_INIT;
  static unsigned long
  timeODP1_RunPeriod,
  timeODP1_RunTimer,
  timeODP1_ODTimer;
  unsigned long
  timeNow;
  timeNow = millis();

  switch ( stateODP1 )
  {
    case    ST_ODP1_INIT:
      //out of reset we start here and just initialize our start timer
      timeODP1_RunTimer = timeNow;
      //then move to the interval timing state
      stateODP1 = ST_ODP1_INTERVAL_TIMING;

      break;

    case    ST_ODP1_INTERVAL_TIMING:
      //has the interval time elapsed?
      if ( (timeNow - timeODP1_RunTimer) >= ODP1PUMP_INTERVAL )
      {
        //yes; start the pump, and...
        digitalWrite(GV1, LOW);    // OPEN
        digitalWrite(GV2, LOW);    // CLOSED
        digitalWrite(P1A, LOW);     // Motor Counter Clockwise
        digitalWrite(P1B, HIGH);
        analogWrite(P1PWM, 130);    //set motor speed

        //...initialize the optical density check timer, and...
        timeODP1_ODTimer = timeNow;

        //...save the motor start time to ensure the correct period is produced after the first pass, and...
        timeODP1_RunPeriod = timeNow;

        //...set state to running
        stateODP1 = ST_ODP1_RUNNING;

        Serial.println( "Pump1 running..." );

      }//if

      break;

    case    ST_ODP1_RUNNING:
      //pump is running now; check for optical density check intervals
      if ( (timeNow - timeODP1_ODTimer) >= ODP1OD_INTERVAL )
      {
        //check the density
        ODVal = analogRead( ODAnalogIN );
        Serial.print( "Optical density val: " );
        Serial.println( ODVal );

        //LCD Print values
        lcd.setCursor(0, 1);
        lcd.print("OD Val: ");
        //delay(1);
        lcd.setCursor(8, 1);
        lcd.print("    ");
        lcd.setCursor(8, 1);
        lcd.print( ODVal );

        //if LOWER than the threshold, keep running and cycle GV1 CLOSED & GV2 OPEN
        if ( ODVal >= OD_HIGH_THRESH )  //ODVal = returned voltage
        {
          //threshold reached. pump1 continues to run, fluid to hydrocyclone, pump2 on to replace lost volume
          digitalWrite (P1A, LOW);    // PUMP 1 ON
          digitalWrite (P1B, HIGH);
          analogWrite (P1PWM, 130);
          digitalWrite (GV1, HIGH);   // CLOSED
          digitalWrite (GV2, HIGH);   // OPEN
          digitalWrite (P2A, LOW);    // PUMP 2 ON
          digitalWrite (P2B, HIGH);
          analogWrite (P2PWM, 130);   // PUMP 2 speed--will change to match flow of PUMP 1

          Serial.println( "Pump2 running..." );

          //repeat again
          timeODP1_RunTimer = timeODP1_RunPeriod;     //set our period based on the last time we started the motor
          stateODP1 = ST_ODP1_INTERVAL_TIMING;        //go back to interval timing

        }//if
        else
        {
          // PUMP 1 & 2 OFF, GV1 & GV2 CLOSED
          digitalWrite (P1A, LOW);  // PUMP 1 OFF
          digitalWrite (P1B, LOW);
          analogWrite (P1PWM, 0);
          digitalWrite (P2A, LOW);  // PUMP 2 OFF
          digitalWrite (P2B, LOW);
          analogWrite (P2PWM, 0);
          digitalWrite (GV1, HIGH); // CLOSED
          digitalWrite (GV2, LOW);  // CLOSED

          Serial.println( "Pump 1 & Pump 2 off..." );
          //OD reading still showing too much density; set up for another check in a couple of minutes
          timeODP1_ODTimer = timeNow;

        }//else
      }//if

      break;

  }//switch
}//ODStateMachine

(full code is attached due to being > 9000 characters)

sketch_sep04_g.ino (11.7 KB)

Hello EFox,

With 29 posts I would have thought by now you'd know the forum rules. Please post your code in the the forum in code tags so that people wanting to help you don't have to down load it and open it in the IDE.

Thank you.

PerryBebbington:
Hello EFox,

With 29 posts I would have thought by now you'd know the forum rules. Please post your code in the the forum in code tags so that people wanting to help you don't have to down load it and open it in the IDE.

Thank you.

Until I have >100 posts, forum rules dictate that I not post over 9000 characters.

Actually, you can never post over 9000 characters

Until I have >100 posts, forum rules dictate that I not post over 9000 characters.

Apologies if your code is over 9000 characters. Please make this clear when posting. Thanks.

EFox:
The first state machine "ODStateMachine" appears to get stuck in the "ST_ODP1_RUNNING" state once the optical density drops below the set threshold.

IMHO that is not nearly enough info to enable someone to find a problem in 11k of code that the person has never seen before. Trying to figure out program code can be very time consuming - and my attention span rarely exceeds 5 minutes.

...R

PerryBebbington:
Apologies if your code is over 9000 characters. Please make this clear when posting. Thanks.

Will do moving forward. I am still pretty green when it comes to writing code so I do have a lot of comments in it.

Reading other people's code and spotting mistakes is not my strong point; it looks fine to me. Hopefully someone better at this will point out the 'obvious' mistake.

For posting neat, easy to read, well commented code with a clear statement of what should happen and what happens instead ++Karma;

Robin2:
IMHO that is not nearly enough info to enable someone to find a problem in 11k of code that the person has never seen before. Trying to figure out program code can be very time consuming - and my attention span rarely exceeds 5 minutes.

...R

Absolutely understand you. I thought the next two parts of the post shed light upon where I thought my issues lie. Will edit/expand the OP.

You have some serial prints, unless someone spots the mistake I suggest you add lots more to narrow down what is happening.

The code sort of looks like it has my fingerprints on it but I don't really remember it well.

Maybe the OP could re-state his requirements in exquisite detail to help everyone get up to speed.

Blackfin:
The code sort of looks like it has my fingerprints on it but I don't really remember it well.

You would be correct--you have helped me in the past.

Blackfin:
Maybe the OP could re-state his requirements in exquisite detail to help everyone get up to speed.

Focusing on the ODStateMachine:

The objective is for Pump1 to circulate a cellular fluid through an optical density sensor then the line is "wyed" to two gate valves. Gate Valve1 sends the fluid back to the cellular growth chamber it came from and GV2 sends it through a hydrocyclone that separates the majority of the cells from the fluid--the cell free fluid is returned to the chamber and the cell laden fluid is discarded into a secondary container.

When the optical density is above the threshold and thus the cellular fluid is being separated by the hydrocyclone, some volume in the original chamber will lost and needs to be replaced. That replacement volume is done via pump2. As a result, pump2 only operates when the optical density is above the threshold.

When the ODVal drops below the threshold, pumps 1 & 2 stop (as expected & desired). However, it appears to me that the ODStateMachine is then not exiting the "ST_ODP1_RUNNING" state--the serial monitor is continuing to print "pump 1 & pump 2 off..." as well as the ODVal reading. The issue is that the optical density is still being taken at it's defined interval (in this case 30 seconds) even though pump1 is off. As a result, it is reading the density of the same sample that last led it to stop the pumps thus keeping pump1 off. Lather, rinse, repeat.

What state should it change to? When you're below the threshold, you close your valves and stop your pumps and set the ODTimer to now. When the OD state machine runs again, nothing has changed, so it does the same thing, as you observe.

You're missing some code to actually change out of that state I think.

EFox:
You would be correct--you have helped me in the past.

Focusing on the ODStateMachine:

The objective is for Pump1 to circulate a cellular fluid through an optical density sensor then the line is "wyed" to two gate valves. Gate Valve1 sends the fluid back to the cellular growth chamber it came from and GV2 sends it through a hydrocyclone that separates the majority of the cells from the fluid--the cell free fluid is returned to the chamber and the cell laden fluid is discarded into a secondary container.

When the optical density is above the threshold and thus the cellular fluid is being separated by the hydrocyclone, some volume in the original chamber will lost and needs to be replaced. That replacement volume is done via pump2. As a result, pump2 only operates when the optical density is above the threshold.

When the ODVal drops below the threshold, pumps 1 & 2 stop (as expected & desired). However, it appears to me that the ODStateMachine is then not exiting the "ST_ODP1_RUNNING" state--the serial monitor is continuing to print "pump 1 & pump 2 off..." as well as the ODVal reading. The issue is that the optical density is still being taken at it's defined interval (in this case 30 seconds) even though pump1 is off. As a result, it is reading the density of the same sample that last led it to stop the pumps thus keeping pump1 off. Lather, rinse, repeat.

I see what you're saying. So after turning off pumps 1 and 2, do you want to return to the ST_ODP1_INTERVAL_TIMING state? If so, you should be able to change:

         //OD reading still showing too much density; set up for another check in a couple of minutes
          timeODP1_ODTimer = timeNow;

to

         timeODP1_RunTimer = timeODP1_RunPeriod;     //set our period based on the last time we started the motor
          stateODP1 = ST_ODP1_INTERVAL_TIMING;        //go back to interval timing

That should allow a 1-minute pause followed by a 30-sec period where pump 1 runs (GV1 open, GV2 closed) before OD readings are made.

wildbill:
What state should it change to? When you're below the threshold, you close your valves and stop your pumps and set the ODTimer to now. When the OD state machine runs again, nothing has changed, so it does the same thing, as you observe.

You're missing some code to actually change out of that state I think.

I believe it should (would like for it to) go back to the initialization state once the "else" statement is completed (shut off pumps 1 & 2 and close GV2, open GV1).

Blackfin:
I see what you're saying. So after turning off pumps 1 and 2, do you want to return to the ST_ODP1_INTERVAL_TIMING state? If so, you should be able to change:

         //OD reading still showing too much density; set up for another check in a couple of minutes

timeODP1_ODTimer = timeNow;




to



timeODP1_RunTimer = timeODP1_RunPeriod;    //set our period based on the last time we started the motor
          stateODP1 = ST_ODP1_INTERVAL_TIMING;        //go back to interval timing




That should allow a 1-minute pause followed by a 30-sec period where pump 1 runs (GV1 open, GV2 closed) before OD readings are made.

Beautiful! Thank you so much. Had a hunch it was a line or two I was missing but just couldn't figure it out. +karma