Try to build a state machine but have problems...

Hi, I'm more or less a newbie in regards to Arduino programming and need your help.

I want to measure things like

  • Temperatur (maybe two sensors)
  • control a small dosing pump
  • show values of temperature, dosing infomation on a TFT
  • display various menue to modify variables like dosing parameter
  • Touchscreen input

Ok, during temperature measuring the dosing pump should be handled and the Display has also to show parameter and menue.

I heard from "Multitasking an Arduino" and thought "That's it". But I'm struggling.

I found some code snippets and wrote "Modules" single INO-Files for

  • handle the Pump
  • temperature measuring
  • handle the Touch- and tft-Screen
  • wrote a menue to display text (variables) and description.

Now I try to combine those "Modules" handled by a state machine but have no idea.

I read all about LED-Blinking and servo handling in regards to state machine but have no Idea ho a structure has to look like.

In my opinion definetly ok if

  • temerature is updated every 1 second
  • display update 1 seceond if something changed
  • touch- TFT-screen "If touched" or 0.5 sec.
  • pump (maybe time related and/or depending on temperatur)

I appreciate your suggestions!

At the moment I have NO IDEA how to deal that.

Sorry for my poor english.

Br from Germany

Guido

Herzlichst Willkommen!

It seems that you are trying to do a lot without much idea of how you are going to achieve it. Many times the advice has been given here (not least by me) that to eat an elephant you must do it in small bites.

So I would suggest that you break up this mega-project of yours into smaller chunks and make sure you can do each part before trying to replicate NASA headquarters using an Arduino UNO. :slight_smile:

What you are trying to do is, in itself, not difficult, but the entire project is difficult for you, a beginner. So do it slowly and vielleicht wirst du Erfolg haben.

Oh, and watch all my videos on YouTube, they are designed for noobs who want to progress their projects, URL in the footer of this post! And subscribe too.

Sorry for my bad German.

Ralph aus England :slight_smile:

Have a look at Planning and Implementing a Program

"state machine" is just a fancy name for a simple concept - using variables to keep track of the progress of things. For example

if (personInRoom == true() {
    if (lightOn == false) {
        turnLightOn();
    }
}

The first thing to do is to write down in plain language (not code) and in great detail the sequence of things that should happen.

...R

Du kannst in der Abteilung "Deutsch" auch auf Deutsch fragen :slight_smile:

First figure out what can run in parallel. If you already wrote sketches for these parts, convert them into functions (setupXYZ(), handleXYZ()...), which can be added to and called in the setup() and loop() of a new "main" sketch. You also can put each function in its own *.cpp and *.h file, but I'd keep that option for later.

You also can insert the setup code parts directly into setup(), for better overview (duplicate pins...).

The rest depends on you specific code and requirements.

Hi Ralf,

thanks for reply. Maybe I explained it not correct.

I wrote a procedure for reading temperature and it works well. Then I wrote a procedure to handle a dosing pump and it wokrs well. The same to display menues, and to read and react to input by touch screen.

Every single "program" works. Now I need to programm a "control structure" which runs in a kind of loop and starts from time to time each procedure and reacts to input by touch screen or if a temperature has changed.

This should run nearly simultaneously. So I thought a kind of State machine can be the right way.

Robin,

thanks,

I think your post is a good example. I'll replace "person in room == true" by a time based check like

if (wakeUpPumpNextTime >= lastWakeUpPumpTime + pumpInterval() {
if (pumpOn == false) {
pumpon();
}
if (readTempNextTime >= lastReadTempNextTime + TempInterval(){
if (temperatureRead == false) {
readTemp();
{
...

And so on...

Thanks to both of you

I'll try this tomorrow...
}

Thanks DrDiettrich,

this is exactly what I try to do. I converted all single sketches into functions and declare all variables, binding libs and stuff

In section setup(), I declare all needed stuff but in loop() section I have some issues to do things nearly simultaneously.

Robins tippss are fine. i'll give em a try.

Thanks to all. I'll keep you guys up to date

Br

Guido

Guido_Ov:

if (wakeUpPumpNextTime >=  lastWakeUpPumpTime + pumpInterval() {

Just a small point. This would be better

if (currentTime -  lastWakeUpPumpTime >= pumpInterval() {
    lastWakeUpPumpTime += pumpInterval()
    // other stuff
}

because it will work even if lastWakeUpPumpTime rolls over to 0.

See Several Things at a Time

...R

Guido_Ov:
this is exactly what I try to do. I converted all single sketches into functions and declare all variables, binding libs and stuff

In section setup(), I declare all needed stuff but in loop() section I have some issues to do things nearly simultaneously.

True parallel execution is impossible on a single-core controller. Don't expect something like threads or other woodoo, it's much simpler.

Instead call all your worker functions in loop(), and let the functions return as soon as possible. This way every function will execute thousands of times every second, what's sufficient for most tasks.

E.g. when you want to measure a temperature every second, check the current time and read a new value only if time has come to do so. Otherwise return immediately, so that the next task (function) can execute.

Guido_Ov:
but in loop() section I have some issues to do things nearly simultaneously.

I missed that earlier.

The "Planning" tutorial that I linked to earlier illustrates the same technique as in the demo Several Things at a Time which is the same concept as DrDiettrich mentioned.

...R

Maybe a simple example might help. Assume a room with an air conditioner. During the day from 06:00 to 20:00 you want to keep the temperature between 18 and 22 degrees (C). During the night you don't care if it goes to minus 10 or to plus 35.

// statemachine states
#define ST_IDLE 0
#define ST_WARMUP 1
#define ST_COOLDOWN 2

// output pins
#define HEATER 4
#define COOLER 5

// variables
int currentState = ST_IDLE;
int currentTemperature;

void setup() {
  // put your setup code here, to run once:

}

void loop() {
  currentTemperature = readTemp();

  switch (currentState)
  {
    case ST_IDLE:
      doIdle();
      break;
    case ST_WARMUP:
      doWarmup();
      break;
    case ST_COOLDOWN:
      doCooldown();
      break;
  }
}

void doIdle()
{
  // the current time is only used in this function
  // so it is declared and set here
  // you can also make it global and set it in loop() if you need the time elsewhere
  // the 'int' type is just an exampe 
  int currentTime = readTime();

  // check the time limits
  if (currentTime < 06: 00 || currentTime > 20: 00)
  {
    // nothing to do; stay in current state
    return;
  }

  // if too cold
  if (currentTemperature < 18)
  {
    // switch to warm up
    currentState = ST_WARMUP;
  }

  // if too hot
  if (currentTemperature > 22)
  {
    // switch to cool down
    currentState = ST_COOLDOWN;
  }
}

void doWarmup()
{
  // in case cooler is on, switch it off
  digitalWrite(COOLER, LOW);

  // switch heater on
  digitalWrite(HEATER, HIGH);

  if (currentTemperature > 20)
  {
    // switch heater off
    digitalWrite(HEATER, LOW);
    currentState = ST_IDLE;
  }
}

void doCooldown()
{
  // in case heater is on, switch it off
  digitalWrite(HEATER, LOW);

  // switch cooler on
  digitalWrite(COOLER, HIGH);

  if (currentTemperature < 20)
  {
    // switch cooler off
    digitalWrite(COOLER, LOW);
    currentState = ST_IDLE;
  }
}

This is just an example to give you the idea. Each state has an associated function. Those functions can change the 'state of the program' if certain conditions are met.

Hope this helps :wink:

Thank you to all of you guys. Great support!

I know that Arduino is not able to do real multitasking. It is just nearly... but this is ok for me.

I need to activate the pump from time to time (60s to maybe 30s). Read temperature every 2 seconds and to check for Touch input from time to time as well as update the TFT.

I think that is not really a problem.

My personal issue is to get an overview about all those "prevMillis", "Interval" and other variables and when to change what... :wink:

Let me say "Brain jogging". This is my training I've to do.

I do not program every day... :slight_smile:

Thanks again

Guido

Use this pattern for all intervals:

const long pumpInterval = 60000; //every 60 seconds
const long pumpDuration = 5000; //pump how long?
unsigned long pumpStart;
bool pumpOn = false;
...
//when start pump
pumpStart = millis();
pumpOn = true;
...
//when stop pump
pumpOn = false;
...
//check for start pump
if (!pumpOn && millis()-pumpStart >= pumpInterval) startPump();
if (pumpOn && millis()-pumpStart >= pumpDuration) stopPump();

For the temperature add tempStart and tempInterval, no tempDuration. tempOn is optional, if you ever want to turn off temperature measuring.

Guido_Ov:
My personal issue is to get an overview about all those "prevMillis", "Interval" and other variables and when to change what... :wink:

Have you studied the link I gave you in Reply #8. If there is something you don't understand please let me know.

...R

Hey Arduino specs,

I'm very happy, my state machine works fine now. Touch screen, Menue, Pump and Temperatur are running and doing things...

Thanks to Robin2 your links are great and helped a lot.

Thanks to all others! I got a bunch of idaes how to solve issues. Thank you very much.

My next issue is, that, I try to read temperature from 6 IR-Sensors MLX90614 but in combination with state machine and it won't run.

This is the code I found somewhere here:

#include <i2cmaster.h>

void setup()
{
  Serial.begin(115200);
  
  // Setup I2CBus
  i2c_init(); //Initialise then,
  PORTC = (1 << PORTC4) | (1 << PORTC5); //enable pullups
 delay(2000);
}

void loop()
{
  float temperature1 = readDevice(0x5A<<1);   // addresses from the scanner
  float temperature2 = readDevice(0x5B<<1);  
  float temperature3 = readDevice(0x5C<<1);
  float temperature4 = readDevice(0x5D<<1);
  float temperature5 = readDevice(0x5E<<1);
  float temperature6 = readDevice(0x5F<<1);
  
  // Print Data To Screen...
  Serial.println("Temperature Readings (*c):");
  Serial.print("> FWL (90): ");
  Serial.println(temperature1);
  Serial.print("> FWM (91): ");
  Serial.println(temperature2);
  Serial.print("> FWR (92): ");
  Serial.println(temperature3);
  Serial.print("> RWL (93): ");
  Serial.println(temperature4);
  Serial.print("> RWM (94): ");
  Serial.println(temperature5);
  Serial.print("> RWR (95): ");
  Serial.println(temperature6);

// delay(2000); 
}

/////////////////////////////////////////////////////////
//
// helper functions
//

float readDevice(int address){
//  Serial.println("Read Device");
  int dev = address;
  int data_low = 0;
  int data_high = 0;
  int pec = 0;
//  Serial.print("Device Address :");
//  Serial.println(dev);
  
  // RAW READ
  i2c_start_wait(dev + I2C_WRITE);
  i2c_write(0x07);
  i2c_rep_start(dev + I2C_READ);  
  data_low = i2c_readAck(); //Read 1 byte and then send ack
  data_high = i2c_readAck(); //Read 1 byte and then send ack
  pec = i2c_readNak();
  i2c_stop();

  
  
  //This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for temps
  double tempFactor = 0.02; // 0.02 degrees per LSB (measurement resolution of the MLX90614)
  double tempData = 0x0000; // zero out the data
  int frac; // data past the decimal point
    
  // This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
  tempData = (double)(((data_high & 0x007F) << 8) + data_low);
  tempData = (tempData * tempFactor)-0.01;

  //Process tempData
  float objTemp = tempData - 273.15;
  delay(500);
  return objTemp;

}

So far this Project runs fine. But when I try to run this code in my state driven project, It won't work.

I tried this:

int mSensAddress[6] = {0x5A,0x5B,0x5C,0x5D,0x5E,0x5F};	// Addresses of IR Sensors for I2C bus
float mTemp[6];						// Array to store 6 temmp values
int mSensors = 6;					// For Next counter

int dev;						// temporarilly stores a single mSensAddress based on i
int data = 0;
int pec = 0;
unsigned long prevmTireTempMillis = 0;
unsigned long mTireTempBaseInterval = 1000;
byte mTempState = 0;

void Setup(){

   Serial.begin(115200);

// Init I2C Bus
    i2c_init();                               // Initialize the I²C bus
    PORTC = (1 << PORTC4) | (1 << PORTC5);    // Enable pullups
    
    delay(500);
}


void loop (){

getTemp();

}

void getTemp() {
  Serial.println ("getTemp process");
  Serial.print ("State :"); 
  Serial.println (mTempState);
    
 if (mTempState == 0) {
  // if the Temperature process is stopped, we must wait for the interval to expire before turning it on 
   if (currentMillis - prevmTempMillis >= mTempBaseInterval) {
      prevmTempMillis += mTempBaseInterval;  
      mTempState = ! mTempState;
   }
    if (mTempState == 1){
        Serial.println ("TempState = 1");
        // Do Temperature stuff
              
        // Calling sensor
        for (int i=0; i < mSensors;i++) {

            int dev = mSensAddress[i];
            int data_low = 0;
            int data_high = 0;
            int pec = 0;
            Serial.print("Device Address :");
            Serial.println(dev);
            
            // RAW READ
            i2c_start_wait(dev + I2C_WRITE);
            i2c_write(0x07);
            i2c_rep_start(dev + I2C_READ);  
            data_low = i2c_readAck(); //Read 1 byte and then send ack
            data_high = i2c_readAck(); //Read 1 byte and then send ack
            pec = i2c_readNak();
            i2c_stop();
             
            //This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for temps
            double tempFactor = 0.02; // 0.02 degrees per LSB (measurement resolution of the MLX90614)
            double tempData = 0x0000; // zero out the data
            int frac; // data past the decimal point
              
            // This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
            tempData = (double)(((data_high & 0x007F) << 8) + data_low);
            tempData = (tempData * tempFactor)-0.01;
          
            //Process tempData
            mTemp[i] = tempData - 273.15;
            delay(500);

        }

   }
     mTempState = ! mTempState;  

 }
}

What's wrong in my suggestion?

Guido_Ov:
So far this Project runs fine. But when I try to run this code in my state driven project, It won't work.

I tried this:

What's wrong in my suggestion?

You have not told us what you mean by "it won't work". What actually happens? What should happen?

You have not told us what your variation does or why you tried that?

It is much easier to help if you let us know how you are thinking about the project.

...R

Robin2,

you are right. Sorry it was a bit late... :wink:

The Idea is to start a function called "getTemp();" if the state StateTemp = 1.

In this function I set some variables and the "For next loop" shall "walk" through the adress array "mSensAddress_", read the temperature of each sensor and stores those values in array "mTemp*"._
Then it is easy to find the right temperature to display.
On serial Monitor I get:
Device Address : 90
That means to me, thr code runs until this:
_
```_
        // Calling sensor
        for (int i=0; i < mSensors;i++) {

int dev = mSensAddress[i];
            int data_low = 0;
            int data_high = 0;
            int pec = 0;
            Serial.print("Device Address :");
            Serial.println(dev);
_
*_ _*... and this sequence which should communicate via I2C won't work...*_ _*_
          // RAW READ
            i2c_start_wait(dev + I2C_WRITE);
            i2c_write(0x07);
            i2c_rep_start(dev + I2C_READ); 
            data_low = i2c_readAck(); //Read 1 byte and then send ack
            data_high = i2c_readAck(); //Read 1 byte and then send ack
            pec = i2c_readNak();
            i2c_stop();
_
```*_
I placed som Serial.println(data_low); but no information on serial monitor...
Guido

Guido_Ov:
you are right. Sorry it was a bit late... :wink:

Maybe it's too early for me but I can't make sense of this. And you are still using the words "won't work" without any detailed explanation of what they mean.

Have you written a short program to read the temp sensor in the simplest possible way? If so post that program.

...R

Robin,

this code works fine. Every 500 millis the program displays a Block of 6 temperature values of my six sensores.
I renamed them with six unique addresses that the I²C can talk to each one.

#include <i2cmaster.h>

void setup()
{
  Serial.begin(115200);
  
  // Setup I2CBus
  i2c_init(); //Initialise then,
  PORTC = (1 << PORTC4) | (1 << PORTC5); //enable pullups
 delay(2000);
}

void loop()
{
  float temperature1 = readDevice(0x5A<<1);   // addresses from the scanner
  float temperature2 = readDevice(0x5B<<1);  
  float temperature3 = readDevice(0x5C<<1);
  float temperature4 = readDevice(0x5D<<1);
  float temperature5 = readDevice(0x5E<<1);
  float temperature6 = readDevice(0x5F<<1);
  
  // Print Data To Screen...
  Serial.println("Temperature Readings (*c):");
  Serial.print("> FWL (90): ");
  Serial.println(temperature1);
  Serial.print("> FWM (91): ");
  Serial.println(temperature2);
  Serial.print("> FWR (92): ");
  Serial.println(temperature3);
  Serial.print("> RWL (93): ");
  Serial.println(temperature4);
  Serial.print("> RWM (94): ");
  Serial.println(temperature5);
  Serial.print("> RWR (95): ");
  Serial.println(temperature6);

// delay(2000); 
}

/////////////////////////////////////////////////////////
//
// helper functions
//

float readDevice(int address){
//  Serial.println("Read Device");
  int dev = address;
  int data_low = 0;
  int data_high = 0;
  int pec = 0;
//  Serial.print("Device Address :");
//  Serial.println(dev);
  
  // RAW READ
  i2c_start_wait(dev + I2C_WRITE);
  i2c_write(0x07);
  i2c_rep_start(dev + I2C_READ);  
  data_low = i2c_readAck(); //Read 1 byte and then send ack
  data_high = i2c_readAck(); //Read 1 byte and then send ack
  pec = i2c_readNak();
  i2c_stop();

  
  
  //This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for temps
  double tempFactor = 0.02; // 0.02 degrees per LSB (measurement resolution of the MLX90614)
  double tempData = 0x0000; // zero out the data
  int frac; // data past the decimal point
    
  // This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
  tempData = (double)(((data_high & 0x007F) << 8) + data_low);
  tempData = (tempData * tempFactor)-0.01;

  //Process tempData
  float objTemp = tempData - 273.15;
  delay(500);
  return objTemp;

}

[\code]


Ok, now I'm trying to adapt this to my state driven program.

All the setup stuff of the code above I copied into setup section of my main program.

All this works until it runs into section

 // RAW READ

i2c_start_wait(dev + I2C_WRITE);

Here the programm stops.

I placed a Serial.print("i2c_start_wait_done"); after the command i2c_start_wait(dev + I2C_WRITE);

But I didn't get a print on serial monitor.

That means to me the command i2c_start_wait(dev + I2C_WRITE); was not processed...

Guido_Ov:
this code works fine.

Glad you got a solution.

...R

Robin,

unfortunately it won't work...

This code as single project runs and read from 6 sensors.

#include <i2cmaster.h>

void setup()
{
 Serial.begin(115200);
 
 // Setup I2CBus
 i2c_init(); //Initialise then,
 PORTC = (1 << PORTC4) | (1 << PORTC5); //enable pullups
delay(2000);
}

void loop()
{
 float temperature1 = readDevice(0x5A<<1);   // addresses from the scanner
 float temperature2 = readDevice(0x5B<<1);  
 float temperature3 = readDevice(0x5C<<1);
 float temperature4 = readDevice(0x5D<<1);
 float temperature5 = readDevice(0x5E<<1);
 float temperature6 = readDevice(0x5F<<1);
 
 // Print Data To Screen...
 Serial.println("Temperature Readings (*c):");
 Serial.print("> FWL (90): ");
 Serial.println(temperature1);
 Serial.print("> FWM (91): ");
 Serial.println(temperature2);
 Serial.print("> FWR (92): ");
 Serial.println(temperature3);
 Serial.print("> RWL (93): ");
 Serial.println(temperature4);
 Serial.print("> RWM (94): ");
 Serial.println(temperature5);
 Serial.print("> RWR (95): ");
 Serial.println(temperature6);

// delay(2000); 
}

/////////////////////////////////////////////////////////
//
// helper functions
//

float readDevice(int address){
//  Serial.println("Read Device");
 int dev = address;
 int data_low = 0;
 int data_high = 0;
 int pec = 0;
//  Serial.print("Device Address :");
//  Serial.println(dev);
 
 // RAW READ
 i2c_start_wait(dev + I2C_WRITE);
 i2c_write(0x07);
 i2c_rep_start(dev + I2C_READ);  
 data_low = i2c_readAck(); //Read 1 byte and then send ack
 data_high = i2c_readAck(); //Read 1 byte and then send ack
 pec = i2c_readNak();
 i2c_stop();


 double tempFactor = 0.02; // 0.02 degrees per LSB (measurement resolution of the MLX90614)
 double tempData = 0x0000; // zero out the data
 int frac; // data past the decimal point
   
 tempData = (double)(((data_high & 0x007F) << 8) + data_low);
 tempData = (tempData * tempFactor)-0.01;

 //Process tempData
 float objTemp = tempData - 273.15;
 delay(500);
 return objTemp;

}

But if I try to adapt the code to my state driven project, it stops at the beginning of RAW READ.

The command i2c_start will not processed...

I programmed this. The idea is to use a For next loop six times an get adresses from array mSensAddress and store results (temperature) in array mTemp
My idea is this:
```
*int mSensAddress[6] = {0x5A,0x5B,0x5C,0x5D,0x5E,0x5F}; // Addresses of IR Sensors for I2C bus
float mTemp[6]; // Array to store 6 temmp values
int mSensors = 6; // For Next counter

int dev; // temporarilly stores a single mSensAddress based on i
int data = 0;
int pec = 0;
unsigned long prevmTireTempMillis = 0;
unsigned long mTireTempBaseInterval = 1000;
byte mTempState = 0;

void Setup(){

Serial.begin(115200);

// Init I2C Bus
    i2c_init();                              // Initialize the I²C bus
    PORTC = (1 << PORTC4) | (1 << PORTC5);    // Enable pullups
   
    delay(500);
}

void loop (){

getTemp();

}

void getTemp() {
  Serial.println ("getTemp process");
  Serial.print ("State :");
  Serial.println (mTempState);
   
if (mTempState == 0) {
  // if the Temperature process is stopped, we must wait for the interval to expire before turning it on
  if (currentMillis - prevmTempMillis >= mTempBaseInterval) {
      prevmTempMillis += mTempBaseInterval; 
      mTempState = ! mTempState;
  }
    if (mTempState == 1){
        Serial.println ("TempState = 1");
        // Do Temperature stuff
             
        // Calling sensor
        for (int i=0; i < mSensors;i++) {

int dev = mSensAddress[i];
            int data_low = 0;
            int data_high = 0;
            int pec = 0;
            Serial.print("Device Address :");
            Serial.println(dev);
           
            // RAW READ
            i2c_start_wait(dev + I2C_WRITE);
            i2c_write(0x07);
            i2c_rep_start(dev + I2C_READ); 
            data_low = i2c_readAck(); //Read 1 byte and then send ack
            data_high = i2c_readAck(); //Read 1 byte and then send ack
            pec = i2c_readNak();
            i2c_stop();
           
            //This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for temps
            double tempFactor = 0.02; // 0.02 degrees per LSB (measurement resolution of the MLX90614)
            double tempData = 0x0000; // zero out the data
            int frac; // data past the decimal point
             
            // This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
            tempData = (double)(((data_high & 0x007F) << 8) + data_low);
            tempData = (tempData * tempFactor)-0.01;
         
            //Process tempData
            mTemp[i] = tempData - 273.15;
            delay(500);

}

}
    mTempState = ! mTempState;

}
}
_
```*_
Do you have an idea what I'm doing wrong?
Do I need to slow down the For next loop? To give the i2c more time to communicate?
Thanks in advance
Guido