State Machine or another better way?

I have been trying for days now to get this to work and I can’t get a timer to work reliably, i.e. everytime for the same amount of time. I have tried ‘millis’, and I get random timeouts. I tried ‘elapsedMillis’, and it only times out like it should on the first test, then no ‘timeout’ at all on subsequent tests. I don’t know how many different ways I have tried it, (if,else ()statements just weren’t cutting it, so I went with while,()). This is the best that I have got so far, all outputs work like they should but I need a timer to shut them off if no input is seen on “cadcell” within a certain time. If there is a better way that doesn’t use ‘while()’ I might be able to fit it on an ATtiny45, which was my original hope.

const int burner = 2;
const int cadcell = 3;
const int oilvalve = 7;
const int airvalve = 8;
const int ignitor = 9;
const int blower = 10;
    
void setup(){
  pinMode(burner, INPUT);
  pinMode(cadcell, INPUT);
  pinMode(oilvalve, OUTPUT);
  pinMode(airvalve, OUTPUT);
  pinMode(ignitor, OUTPUT);
  pinMode(blower, OUTPUT);
}
void loop()   
  {while (digitalRead (burner) == HIGH && (digitalRead (cadcell)) == LOW) {startup();}
  {while (digitalRead (burner) == HIGH && (digitalRead (cadcell)) == HIGH) {running();}
  {while (digitalRead (burner) == LOW && (digitalRead (cadcell)) == HIGH) {purge();}}}}



void startup (){                    //begin heating cycle
    {digitalWrite (blower, HIGH);    //turn on blower....duh...
    delay (3000);                    //wait for draft to be established
    digitalWrite (oilvalve, HIGH);  //open fuel valve 
    digitalWrite (airvalve, HIGH);   //open air valve to siphon fuel
    digitalWrite (ignitor, HIGH);}   //ignite mixture
   }      // I need a timer to turn the preceeding outputs off.  This is the safety shutoff in the event 
         //that the mixture does not ignite

void running()        
   {digitalWrite (blower, HIGH);
    digitalWrite (oilvalve, HIGH);
    digitalWrite (airvalve, HIGH);
    digitalWrite (ignitor, LOW);}
   
void purge()
    {digitalWrite (oilvalve, LOW);    //turn off fuel valve
    delay (2000);                      //wait for air to siphon out escess fuel
    digitalWrite (airvalve, LOW);      //turn off air vavle
    digitalWrite (ignitor, LOW);       //and ignitor
    delay (5000);                      //wait for all combustion to finish, and purge chamber
    digitalWrite (blower, LOW);        //turn off blower
}

Then I stumbled upon State Machine, and this seems like it could solve my problem since it a lot like industrial automation.

I came up with a State Diagram, tell me if this looks right, or doable. The only thing I am still not clear on i show to get it to change states by itself after a given amount of time ("TIMER)?

You haven’t described what you are trying to do, and without that it’s hard to give advice.

You have two or three lines in loop() like this

{while (digitalRead (burner) == HIGH && (digitalRead (cadcell)) == LOW) {startup();}

They can only be creating loops within loop() which is a recipe for confusion.

The concept of a State Machine is that your code knows at any moment what state it is in (because it is recorded in some variable) and there are clearly identified processes for changing state either when something happens (like a button being pressed, a temperature being reached, or a time expiring).

You should look at the “blink without delay” example to learn how to manage timing in an organized way. With that technique you can easily manage several overlapping or successive time intervals.

…R

gregs78cam: I might be able to fit it on an ATtiny45, which was my original hope.

The sketch you've shown is tiny and I don't see why it wouldn't fit on an ATtiny.

I don't understand what your sketch is intended to do, and without that I can't advise you how to do it. Trying control structures at random is not a very good design method, though.

I am trying to control an oil burner in a better way using software instead of hardware. I thought that with the comments in the code, and the State Diagram it would be obvious what I am trying to do. I am controlling 4 digital Outputs, using 2 possibly 3 digital inputs.

I have spent hours looking at 'blink without delay', I understand how it keeps counting milliseconds until they = the interval and then they become the previous millis and more counting until current -previous is greater than interval. I can make it turn the outputs off, but then it runs the 'startup' loop again and turns them all back on. I guess I can add a loop counter to stop it. Next question then, using this counting millis scheme, how do set more than one length of interval?

This is why I was thinking about using a SM, does the State Diagram look complete?

gregs78cam: I am trying to control an oil burner in a better way using software instead of hardware. I thought that with the comments in the code, and the State Diagram it would be obvious what I am trying to do. I am controlling 4 digital Outputs, using 2 possibly 3 digital inputs.

I have spent hours looking at 'blink without delay', I understand how it keeps counting milliseconds until they = the interval and then they become the previous millis and more counting until current -previous is greater than interval. I can make it turn the outputs off, but then it runs the 'startup' loop again and turns them all back on. I guess I can add a loop counter to stop it. Next question then, using this counting millis scheme, how do set more than one length of interval?

This is why I was thinking about using a SM, does the State Diagram look complete?

Nope not really

Have a look at this library for State Machines

http://playground.arduino.cc/Code/SMlib

Craig

gregs78cam: I am trying to control an oil burner in a better way using software instead of hardware.

I'd tread carefully on an application like that, assuming the consequences might be less than desirable if the software malfunctions. I forget where I saw it (maybe here) but the difference between hardware and software is that if you use hardware long enough, it breaks; if you use software long enough, it works.

But yes a state machine is probably a good way to go; if it's done well it's easier to understand and error-proof.

There are a couple current threads running on the topic, you may want to check them out if you have not already: http://forum.arduino.cc/index.php?topic=202678.0 http://forum.arduino.cc/index.php?topic=202015.0

Thank you, I have read up on those already also.

I revised my attached State Diagram a bit. Here is my attempt at code driven by the diagram:

#include <SM.h>


SM Burner(Idle);//create simple statemachine ?and start state?

const int burner = 2;
const int cadcell = 3;
const int oilvalve = 7;
const int airvalve = 8;
const int ignitor = 9;
const int blower = 10;

long programTick =0;


void setup(){
  pinMode(burner, INPUT);
  pinMode(cadcell, INPUT);
  pinMode(oilvalve, OUTPUT);
  pinMode(airvalve, OUTPUT);
  pinMode(ignitor, OUTPUT);
  pinMode(blower, OUTPUT);
}
void loop(){
  EXEC(Burner);//run statemachine
}
State Idle(){
  if(digitalRead (burner) == HIGH) Burner.Set(Startup);
    }

State Startup(){
    digitalWrite (blower, HIGH);
    digitalWrite (oilvalve, HIGH);
    digitalWrite (airvalve, HIGH);
    digitalWrite (ignitor, HIGH);
  if(Burner.Timeout(15000)){
    if(digitalRead (cadcell) == LOW);{
      digitalWrite (oilvalve, LOW);
      if(Burner.Timeout(2000));
      digitalWrite (airvalve, LOW);
      digitalWrite (ignitor, LOW);
      if(Burner.Timeout(5000));
      digitalWrite (blower, LOW);
    Burner.Set(Fault);}}
  else {Burner.Set(Running);}
    }

State Fault(){
if(Burner.Timeout(15000));

 if (programTick < 3) {  
  programTick ++;
   Burner.Set(Idle); 
  }  
  if (programTick == 3) {  
   programTick = 0;  
   Burner.Set(Lockout);}
 }  

State Running(){
    digitalWrite (blower, HIGH);
    digitalWrite (oilvalve, HIGH);
    digitalWrite (airvalve, HIGH);
    digitalWrite (ignitor, LOW);
  if(digitalRead (burner) == LOW) Burner.Set(Shutdown);
  if(digitalRead (cadcell) == LOW) Burner.Set(Shutdown);}

State Shutdown(){
    digitalWrite (oilvalve, LOW);    //turn off fuel valve
    if(Burner.Timeout(2000));                      //wait for air to siphon out excess fuel
    digitalWrite (airvalve, LOW);      //turn off air vavle
    digitalWrite (ignitor, LOW);       //and ignitor
    if(Burner.Timeout(5000));                      //wait for all combustion to finish, and purge chamber
    digitalWrite (blower, LOW);        //turn off blower
    Burner.Set(Idle);}


State Lockout(){
}

It seems to compile fine except for the timer0_millis. I guess SMLib does not ?recognize? these timers. And I don’t quite have a grasp on how to implement the timeout. As you can see I removed all delays and replaced with timeout. Am I doing this right? If not how?

I get the following errors:

  This report would have more information with
  "Show verbose output during compilation"
  enabled in File > Preferences.
Arduino: 1.0.5 (Windows XP), Board: "Teensy 2.0"
sketch_dec05b.cpp.o: In function `global constructors keyed to Burner':
C:\Documents and Settings\Greg\Desktop\Arduino-1.0.5/sketch_dec05b.ino:80: undefined reference to `timer0_millis'
sketch_dec05b.cpp.o: In function `SM::Timeout(unsigned long)':
C:\Documents and Settings\Greg\Desktop\Arduino-1.0.5/sketch_dec05b.ino:80: undefined reference to `timer0_millis'
C:\Documents and Settings\Greg\Desktop\Arduino-1.0.5/sketch_dec05b.ino:80: undefined reference to `timer0_millis'
C:\Documents and Settings\Greg\Desktop\Arduino-1.0.5/sketch_dec05b.ino:80: undefined reference to `timer0_millis'
C:\Documents and Settings\Greg\Desktop\Arduino-1.0.5/sketch_dec05b.ino:58: undefined reference to `timer0_millis'
sketch_dec05b.cpp.o:C:\Documents and Settings\Greg\Desktop\Arduino-1.0.5/sketch_dec05b.ino:58: more undefined references to `timer0_millis' follow

Which MCU are you compiling for, an ATtiny?

Right now I am just trying to get it to run on a Teensy2.0. At this point I have too many I/Os for the ATtiny45's that I have. If it could run on an ATtiny, I can just set it up to reset by power switch.

The SM library accesses the timer0_millis variable. I don't have a Teensy (they do look cool, though!) but I suspect that variable is not implemented. I can create a similar issue when compiling for an ATtiny.

I believe the author intends to remove the dependency on timer0_millis, there was some discussion in another thread.

Any way to make this work? Or do I have to start over on another route?

Implementing state machines with switch/case is fairly straightforward and intuitive.

Also study example_1 and example_2 that come with the SM library. They do the same thing, example_1 does it without the library, but it is very similar in that it implements the function-per-state model. The timeout method would need to be implemented as well, though.

I didn't try to figure out what you were trying to do from your original State Machine diagram because the diagram may have been wrong.

A State Machine diagram is only a diagram. It may or may not represent what you want to happen and it may or may not represent it correctly.

In view of your comment in Reply #3 "I can make it turn the outputs off, but then it runs the 'startup' loop again and turns them all back on. " I suspect you don't really understand how to organize your code and, if so, you need to learn that before you delve into State Machines and their diagrams.

The first thing to do (IMHO) is to write down on a piece of paper (or in a text editor) exactly what you want your machine to do - with no reference at all to Arduinos or program code.

The next thing is to make sure (using the many tutorials available) that you are able to write short and clear Arduino sketches that do the sort of things that need to be done but with switches and LEDs instead of the real hardware.

Then you need to be sure you can write a short clear sketch that is able to get whatever information is needed from the real hardware - AND that it always gets the correct values.

After that it's time to think of writing a single sketch that draws everything together and at this stage a State Machine Diagram will help to clarify your thinking. I doubt very much that the artistic style of the State Machine Diagram matters at all.

...R

Well the State diagram shows almost exactly what I want it to do. And I do have it on a breadboard with switches and LEDs. I used the elapsedMillis → timeouts sketch as a guide.

Since this thing isn’t very time sensitive, I went ahead and used delays, in a switch/case sketch. I actually ended up cutting the delays in half and adding a 250ms delay at the end to help with debugging. I may change them a bit later. It works perfectly after some debugging. It’s amazing the headache that one or two } or { in the wrong spot can be.

#include <elapsedMillis.h>
#include <ctype.h>


const int burner = 2;
const int cadcell = 3;
const int oilvalve = 7;
const int airvalve = 8;
const int ignitor = 9;
const int blower = 10;

enum State {Idle, Startup, Fault, Running, Shutdown, Lockout} state;

int progTick = 0;
elapsedMillis timeout;

void setup(){
  pinMode(burner, INPUT);
  pinMode(cadcell, INPUT);
  pinMode(oilvalve, OUTPUT);
  pinMode(airvalve, OUTPUT);
  pinMode(ignitor, OUTPUT);
  pinMode(blower, OUTPUT);
  Serial.begin(9600);
  state = Idle;
}
void loop(){
  switch (state){
    case Idle:        Serial.println("Idle");
                      if(digitalRead (burner) == HIGH) {state = Startup;}
              break;
              
    case Startup:      Serial.println("Startup"); 
                       digitalWrite (blower, HIGH);
                       delay (1000);
                       digitalWrite (oilvalve, HIGH);
                       digitalWrite (airvalve, HIGH);
                       digitalWrite (ignitor, HIGH);
                       delay (4000);
                       if(digitalRead (cadcell) == HIGH){state = Running;}
                                                      else
                                                      {state = Fault;}
                                                        break;
              
                            

    case Fault:        Serial.println("Fault");
                       digitalWrite (oilvalve, LOW);
                          delay (1000);
                          digitalWrite (airvalve, LOW);
                          digitalWrite (ignitor, LOW);
                          delay (2500);  
                          digitalWrite (blower, LOW);delay (5000);
                           if (progTick < 3) {  
                                progTick ++;
                                state = Idle;}
                           if (progTick == 3) {                              
                               state = Lockout;}
                               break;  

    case Running:      Serial.println("Running");
                       digitalWrite (blower, HIGH);
                       digitalWrite (oilvalve, HIGH);
                       digitalWrite (airvalve, HIGH);
                       digitalWrite (ignitor, LOW);
                       if(digitalRead (burner) == LOW) {state = Shutdown;}
           
                       if(digitalRead (cadcell) == LOW) {state = Fault;}
               break;

    case Shutdown:      Serial.println("Shutdown");
                        digitalWrite (oilvalve, LOW);    //turn off fuel valve
                        delay (1000);                      //wait for air to siphon out excess fuel
                             digitalWrite (airvalve, LOW);      //turn off air vavle
                             digitalWrite (ignitor, LOW);       //and ignitor
                             delay (2500);                      //wait for all combustion to finish, and purge chamber
                                  digitalWrite (blower, LOW);        //turn off blower
                                  state = Idle;
               break;


    case Lockout:     Serial.println("Locked Out");
               break;
  }delay(250);}

It is rather nice when you get it working!

A couple of suggestions: I'd be inclined to make the lockout code shut everything down too. As you have it now, it's not necessary, but later on, if you add other conditions that cause lockout, there's a risk that you won't remember to run the shutdown code before switching to lockout. You can put it in it's own function and have the fault, shutdown and lockout states call it.

Should there be a time limit on how long you remember faults? As you have it, you can have a fault today, another tomorrow and another a year from now and the system will lockout, which seems perhaps unduly cautious.

Yes it is very nice. 16 hours 3 rewrites to change structure, and 1 rewrite because the IDE froze up before I saved it.

I will think about putting the shutdown procedure in the lockout state. And I don' t know about clearing faults history. I could try it.

I have uploaded a new version og the smlib.

The Timeout() and Statetime() functions now use the millis() function for compability reasons so your program should compile fine. Sorry for the inconvenience