Run state machine if condition met

Hi,

I'm having trouble figuring out how to integrate a sensor into my state machine.

An ultrasonic distance sensor should signal and listen until something comes within range (this part works fine). Once something is in range of the sensor, a state machine turns on one series of nitiniol springs and piezo buzzers, lets them rest, then turns on a second series and lets them rest before listening to the sensor again.

My current code is here: Sonar-actuated-SMA-with-Sound-Effects/Placeholder_1_7.ino at master · silicontongue/Sonar-actuated-SMA-with-Sound-Effects · GitHub

Initially I made everything part of the state machine. State 0 was the sensor loop, State 1 was a conditional statement for when something was in range of the sensor...

 switch (state)
  { 
    case S_sensorRead:
      digitalWrite(trigPin, LOW);
      delayMicroseconds(2);
      digitalWrite(trigPin, HIGH);
      delayMicroseconds(5);
      digitalWrite(trigPin, LOW); //sends out ping
        
      duration = pulseIn(echoPin, HIGH); //reads the echo
    
      cm = microsecondsToCentimeters(duration); //time to distance

      Serial.print(cm);
      Serial.print("cm");
      Serial.println();

      ts = millis();

      state = S_sensorTrigger;

      break;

    case S_sensorTrigger:
    
      if ((cm <= 40) && (cm >= 2)) {
        Serial.print("starting state machine");
        state = S_seriesAOn;
      }
      else {
        state = S_sensorRead;
      }

      break;

and the rest of the states continued from there, but I couldn't seem to get past the first state. If I omit the else statement, it gets hung up on the second state indefinitely. Any ideas?

In my current code, I've tried pulling the sensor activity out of the state machine and putting it at the beginning of the loop. I just can't figure out how to actually call the state machine when a condition is met. Am I on the right track with this?

A final issue, there's obviously an issue with my white noise generator. Either I'm not calling it properly, or what was working as stand alone code doesn't fit with this code.
What I'm trying to do is have the buzzers emit white noise whenever a spring is on. Calling generateNoise(frequency) has worked when I've tested that portion of the code on its own, but it isn't working here. Could the microdelay be causing an issue or is there just a better way to call it?

Any advice on this is greatly appreciated!

Please insert your full code; it's small enough. Not everybody is prepared to download. And if you provide a link, please make it clickable by using url tags (similar to code tags).

** **[url=** **
url here
** **]** **
some text
** **[/url]** **
.

Alternative is to simply attach to the post.

You have a static 'state' variable that is initialised to 'S_seriesAOn'. So regardless of the sensor reading, it will start the statemachine the first time. You can solve this by creating a statename (e.g. something like S_stopped) that is not used in the switch/case and using that as the initial value.

  if ((cm <= 40) && (cm >= 2))   //if sensor is triggered, start the state machine
  {
    Serial.print("starting state machine");
    state = S_seriesAOn;
  }

This will reset the state to S_seriesAOn as long as the distance is in the specified range (regardless of the next state that is set in the switch/case).

I think that your original approach where you tested the sensor inside the statemachine was far better.

The reading of the sensor can be outside the switch/case but the test will be inside a case.

Here's my full code, running everything as part of the state machine.

  switch (state)
  { 
    case S_sensorRead:
      digitalWrite(trigPin, LOW);
      delayMicroseconds(2);
      digitalWrite(trigPin, HIGH);
      delayMicroseconds(5);
      digitalWrite(trigPin, LOW); //sends out ping
        
      duration = pulseIn(echoPin, HIGH); //reads the echo
    
      cm = microsecondsToCentimeters(duration); //time to distance

      Serial.print(cm);
      Serial.print("cm");
      Serial.println();

      ts = millis();

      state = S_sensorTrigger;

      break;

    case S_sensorTrigger:
    
      if ((cm <= 40) && (cm >= 2)) {
        Serial.print("starting state machine");
        state = S_seriesAOn;
      }
      else {
        state = S_sensorRead;
      }

      break;

Thanks so much for the reply sterretje. I see what you mean about it just staying in S_seriesAOn when the condition is met. Glad you pointed that out.

So, I set it to read the sensor outside the switch/case, put the conditional in the switch/case, and added S_stopped as the initial state.

It works, but the sensor is running the whole time. Is there a way to stop the sensor read when the switch/case is running? I'd like to have it complete all the cases each time something is in range of the sensor.

Still looking into the sound issue...

In that case you need to place the sensor reading also in the in the stopped state and your basically are back where you started :wink:

PS
If we're talking about full code, we mean full code :wink: Not a part of loop but everything:
variable declarations and the likes
setup()
loop()
any functions that you wrote

If it's too big, attach it or spread it over two posts.

Oh gosh, I didn't copy it all! Sorry. Here is the full code for real!

//state machine setup
const int S_stopped = 0;
const int S_sensorTrigger = 1;
const int S_seriesAOn = 2;
const int S_seriesAWait = 3;
const int S_seriesAOff = 4;
const int S_seriesAOffWait = 5;
const int S_seriesBOn = 6;
const int S_seriesBWait = 7;
const int S_seriesBOff = 8;
const int S_seriesBOffWait = 9;
 

//piezo buzzers set up
const int buzzerA = 5;
const int buzzerB = 6;
int frequency = 1;
unsigned long int reg;

//ultasonic sensor set up
const int trigPin = 7;
const int echoPin = 8;

//Nitinol springs set up
const int springA = 9;
const int springB = 10;


void setup() {
  Serial.begin(9600);

  pinMode(buzzerA, OUTPUT);
  pinMode(buzzerB, OUTPUT);
  
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);

  pinMode(springA, OUTPUT);
  pinMode(springB, OUTPUT);

  reg = 0x55aa55aaL; //seed for white noise bitstream
}

 
void loop()
{
  
 long duration, , cm;
 
  static int state = S_stopped ;
  static unsigned long ts;


      digitalWrite(trigPin, LOW);
      delayMicroseconds(2);
      digitalWrite(trigPin, HIGH);
      delayMicroseconds(5);
      digitalWrite(trigPin, LOW); //sends out ping
        
      duration = pulseIn(echoPin, HIGH); //reads the echo
    
      cm = microsecondsToCentimeters(duration); //time to distance

      Serial.print(cm);
      Serial.print("cm");
      Serial.println();
      
  switch (state)
  { 
    case S_stopped:

      state = S_sensorTrigger;

      break;

    case S_sensorTrigger:
    
      if ((cm <= 40) && (cm >= 2)) {
        Serial.print("starting state machine");
        state = S_seriesAOn;
      }
      else {
        state = S_stopped;
      }

      break;
    
    case S_seriesAOn:
      Serial.print("SpringA heating");
      digitalWrite( springA, HIGH);
      generateNoise(frequency);
 
      ts = millis();  // Remember the current time
 
      state = S_seriesAWait;  // Move to the next state
 
      break;
 
    case S_seriesAWait:
      // If x seconds have passed, then move on to the next state.
      if (millis() > ts + 12000)
      {
        state = S_seriesAOff;
      }
 
      break;
 
    case S_seriesAOff:
      Serial.print("SpringA cooling");
      digitalWrite( springA, LOW);
 
      ts = millis(); 
 
      state = S_seriesAOffWait;
 
      break;
 
    case S_seriesAOffWait:
 
      if (millis() > ts + 10000)
      {
        state = S_seriesBOn;
      }
 
      break;
 
    case S_seriesBOn:
      Serial.print("SpringB heating");
      digitalWrite( springB, HIGH);
      generateNoise(frequency);
      
 
      ts = millis();
 
      state = S_seriesBWait;
 
      break;
 
    case S_seriesBWait:
 
      if (millis() > ts + 12000)
      {
        state = S_seriesBOff;
      }
 
      break;

     case S_seriesBOff:
      Serial.print("SpringB cooling");
      digitalWrite( springB, LOW);

      ts = millis();
 
      state = S_seriesBOffWait;
 
      break;
 
    case S_seriesBOffWait:

      if (millis() > ts + 12000)
      {
       Serial.print("restart");
       state = S_stopped;
      }
 
      break;
 
  } // end of switch
 
} // end of loop

void generateNoise(int frequency) {
  unsigned long int newr;
  unsigned char lobit;
  unsigned char b31, b29, b25, b24;
   
  b31 = (reg & (1L << 31)) >> 31;
  b29 = (reg & (1L << 29)) >> 29;
  b25 = (reg & (1L << 25)) >> 25;
  b24 = (reg & (1L << 24)) >> 24;

  lobit = b31 ^ b29 ^ b25 ^ b24;
  newr = (reg << 1) | lobit;
  reg = newr;
  
  digitalWrite(buzzerA, reg & 1);
  digitalWrite(buzzerB, reg & 1);
  
  delayMicroseconds(frequency);    // Changing this value changes the frequency.
}

long microsecondsToCentimeters(long microseconds) {
  return microseconds / 29 / 2;
}

sterretje:
In that case you need to place the sensor reading also in the in the stopped state and your basically are back where you started :wink:

Actually, having the sensor run the whole time doesn't seem to be a problem. (Phew) It looked like that was hanging things up, but once I changed the sensor code to run without delays it's been working fine.

You want as much as possible for the input(s) and the output(s) to be independent. They should run on their own in loop() with process task(s) like the state machine to connect between.

Any task can be a 1-shot (doesn't restart itself) triggered by another.

You can change inputs, outputs and processes with minimal or no recoding of the rest of the sketch.
You can develop tasks in small controlled sketches to move directly to the main project.
You can build a better code toolbox using IPO tasks.

GoForSmoke:
You can build a better code toolbox using IPO tasks.

What is IPO?

Aha! I got it working. Found this example code that generates white noise without the microSecondDelay on this post.

#define speakerPin 8

unsigned long lastClick;

void setup() {
  // put your setup code here, to run once:
   pinMode(speakerPin,OUTPUT);
   lastClick = micros();   
}


/* initialize with any 32 bit non-zero  unsigned long value. */
#define LFSR_INIT  0xfeedfaceUL
/* Choose bits 32, 30, 26, 24 from  http://arduino.stackexchange.com/a/6725/6628
 *  or 32, 22, 2, 1 from 
 *  http://www.xilinx.com/support/documentation/application_notes/xapp052.pdf
 *  or bits 32, 16, 3,2  or 0x80010006UL per http://users.ece.cmu.edu/~koopman/lfsr/index.html 
 *  and http://users.ece.cmu.edu/~koopman/lfsr/32.dat.gz
 */  
#define LFSR_MASK  ((unsigned long)( 1UL<<31 | 1UL <<15 | 1UL <<2 | 1UL <<1  ))

unsigned int generateNoise(){ 
  // See https://en.wikipedia.org/wiki/Linear_feedback_shift_register#Galois_LFSRs
   static unsigned long int lfsr = LFSR_INIT;  /* 32 bit init, nonzero */
   /* If the output bit is 1, apply toggle mask.
                                    * The value has 1 at bits corresponding
                                    * to taps, 0 elsewhere. */

   if(lfsr & 1) { lfsr =  (lfsr >>1) ^ LFSR_MASK ; return(1);}
   else         { lfsr >>= 1;                      return(0);}
}


void loop() {
      /* ... */
      if ((micros() - lastClick) > 50 ) { // Changing this value changes the frequency.
        lastClick = micros();
        digitalWrite (speakerPin, generateNoise());
      }

}

Tweaked it to fit my code, and added this to the beginning of the loop.

   if ((aState == HIGH) || (bState == HIGH)) {
     if ((micros() - lastClick) > 40 ) { // Changing this value changes the frequency.
       lastClick = micros();
       digitalWrite (buzzerA, generateNoise());
       digitalWrite(buzzerB, generateNoise());
     }
   }

Sounds a little more modulated than just running the white noise code on its own...but I think it'll work.

sterretje:
What is IPO?

Input

Process

Output

But not as this then this then this. All at the same time as and when ready.