Adding timing to sequenced events using millis()

Hi everyone, I'm working on a project that uses a WS2182b 120 LED strip, an ultrasonic sensor and an Arduino Uno.

The sensor is used to determine how many times the hand is within 5 cm of the sensor, and when this number reaches 2 it will implement a sequence that steadily increments the leds 1 by 1 up the strip for 4 seconds, remain completely lit up for 7 seconds before turning off backwards down 1 by 1 for 8 seconds, all using the same colour. So far it kinda works, but I've encountered 2 main problems.

  1. Sometimes the sensor doesn't seem to recognise that my hand is in the range, or it completely skips the first case and goes directly to the second.

  2. When entering the 3rd case (when the counter = 2) for the first time it rapidly increments and decrements the leds several times before incrementing it, flashing between 2 colours (the one I've chosen and a pink) for about 10 seconds before decrementing again. The 2nd scenario then repeats. I've managed to make it work using delays and slightly altering the Cylon example in the FastLED library, but when switching to millis() it doesn't seem to function the way I want it to.

I'm quite new to Arduino, so I apologise in advance if there are some really simple things that I've overlooked! Any help or guidance will be greatly appreciated, thank you

This is the code that I'm using:

#include <FastLED.h>
//define the time intervals
#define BREATH_IN 2000
#define HOLD_BREATH 6000
#define BREATH_OUT 13000
unsigned long time_1 = 0;
unsigned long time_2 = 0;
unsigned long time_3 = 0;

//set up LED
#define DATA_PIN 3
#define NUM_LEDS 120
CRGB leds[NUM_LEDS];


// set up sensor
#define TRIG_PIN 12
#define ECHO_PIN 13
//In centimeters
#define MIN_DISTANCE 3
#define MAX_DISTANCE 25
//calculates ultrasonic sensor distance
float duration, distance;
float dist_div = (MAX_DISTANCE-MIN_DISTANCE)/NUM_LEDS;



void setup()
{
  FastLED.addLeds<WS2812, 3, GRB>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip );
 
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  //set up strip
  Serial.println("resetting");
  LEDS.addLeds<WS2812,DATA_PIN,RGB>(leds,NUM_LEDS);
  LEDS.setBrightness(30);
  Serial.begin (9600); //start serial monitor for debugging if needed
}
void loop()
{
  static uint8_t hue = 0;
  static int counter = 0;
  Serial.print("x");
  //set up ultrasonic sensor
  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  duration = pulseIn(ECHO_PIN, HIGH);
  distance = (duration*.0343)/2;
  Serial.println(millis());
  delay(1000);
  
  if (distance < 5)
  {
    counter ++; //if the hand is within 5cm of the sensor, increment the counter by 1
    Serial.print (counter); 
  }
  if (counter > 3)
  {
    counter = 0; 
  }
  switch (counter)
  {
    case 0:
    {
      FastLED.clear();
      Serial.print(counter);
    }
    break;
     case 1: //commence tactile interface pattern
    {
      fill_solid(leds, 120, CHSV (32,200,200)); //green
      Serial.print(counter);
    }
    break;
    case 2: //commence 4-7-8 breathing pattern
    {
      if (millis() >= time_1 + BREATH_IN)//start breath in sequence
      {
       time_1 += BREATH_IN; //update time
       for(int i = 0; i < NUM_LEDS; i++) // increment led strip one led at a time
       {
       leds[i] = CHSV(128, 150, 200); //set the colour to aqua
      // Show the leds
       FastLED.show(); //show the leds
      }}

      if (millis()>= time_2 + HOLD_BREATH) //start the hold in sequence
      {
        time_2 += HOLD_BREATH; //update time
       fill_solid(leds, 120, CHSV(128,150,200)); // fill the entire strip with one colour
      // Show the leds
       FastLED.show();
      }

      if (millis()>= time_3 + BREATH_OUT)//start the breath out sequence
      {
        time_3 += BREATH_OUT; //update time
        for(int i = (NUM_LEDS)-1; i >= 0; i--) 
        {
         leds[i].nscale8(256); //set the leds to black, incrementing backwards 1 led at a time
         FastLED.show();
        }
      }
    }
    break;
  }
    FastLED.show();
  }

Your delay() will certainly have a bad effect on the function of your program.
Paul

You need to structure your code a bit more like a state machine.

  • only read your sensor every 1000 msec (but don't use delay)
  • only update 1 LED in your strip each time through loop (depending on elapsed time)
    Something like this (untested)
#include <FastLED.h>
//define the time intervals between updates
const unsigned long BREATH_IN_DELAY = 50;
const unsigned long BREATH_HOLD_DELAY =  6000; // total time to pause
const unsigned long BREATH_OUT_DELAY = 75;

enum {IN, HOLD, OUT};
int breathState = IN;
unsigned long lastBreathTime; // last time of update
int breathIdx;  // current LED to change

enum { CLEAR, INTERFACE, BREATH };
int state;
bool prevHandPresent;

//set up LED
#define DATA_PIN 3
#define NUM_LEDS 120
CRGB leds[NUM_LEDS];

// set up sensor
#define TRIG_PIN 12
#define ECHO_PIN 13


void setup()
{
  Serial.begin (9600); //start serial monitor for debugging if needed
  Serial.println("resetting");

  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  //set up strip
  LEDS.addLeds<WS2812, DATA_PIN, RGB>(leds, NUM_LEDS);
  LEDS.setCorrection( TypicalLEDStrip );
  LEDS.setBrightness(30);
}

void loop()
{
  static unsigned long lastUpdateTime;

  if ( millis() - lastUpdateTime >= 1000 ) {
    lastUpdateTime = millis();
    // read the sensor
    digitalWrite(TRIG_PIN, LOW);
    delayMicroseconds(2);
    digitalWrite(TRIG_PIN, HIGH);
    delayMicroseconds(10);
    digitalWrite(TRIG_PIN, LOW);
    unsigned long duration = pulseIn(ECHO_PIN, HIGH);
    float distance = (duration * .0343) / 2;

    bool handPresent = distance <= 5.0;

    if (handPresent && !prevHandPresent ) {
      // hand just became present, increment state and announce it
      state = (state + 1) % 3;
      switch ( state ) {
        case CLEAR:
          Serial.println(F("clear"));
          FastLED.clear();
          break;

        case INTERFACE:
          Serial.println(F("interface"));
          fill_solid(leds, NUM_LEDS , CHSV (32, 200, 200)); //green
          break;

        case BREATH:
          Serial.println(F("breath"));
          FastLED.clear();
          breathIdx = 0;
          breathState = IN;
          lastBreathTime = millis();
          break;
      }
    }
    prevHandPresent = handPresent;
  }

  // react to our current state every time through loop
  switch (state) {
    case CLEAR:
      // nothing more to do
      break;

    case INTERFACE:
      // nothing more to do
      break;

    case BREATH:
      switch (breathState) {
        case IN:
          //commence 4-7-8 breathing pattern
          if (millis() - lastBreathTime >= BREATH_IN_DELAY) {
            lastBreathTime += BREATH_IN_DELAY; //update time
            leds[breathIdx++] = CHSV(128, 150, 200); //set the colour to aqua
            if ( breathIdx >= NUM_LEDS ) {
              // all done, move on to next state
              breathState++;
            }
          }
          break;
        case HOLD:
          if (millis() - lastBreathTime >= BREATH_HOLD_DELAY ) {
            // done holding, next state
            breathState++;
            lastBreathTime = millis();
            breathIdx = NUM_LEDS - 1; // start at the end
          }
          break;

        case OUT:
          if (millis() - lastBreathTime >= BREATH_OUT_DELAY) {
            lastBreathTime += BREATH_OUT_DELAY;
            leds[breathIdx--] = CHSV(0, 0, 0); //set the colour to black
            if ( breathIdx < 0 ) {
              // add done, back to the beginning
              breathState = IN;
            }
          }
          break;
      }
      FastLED.show();
      break;
  }
}
1 Like

Thank you guys both for your fast replies. @blh64 your code works perfectly! It does exactly what I needed it to do, thank you so much!

Also just so I could properly understand what you did, could you please briefly explain/or point me in the right direction, to how you got the 4-7-8 second timings for each stage? I don't understand how the time intervals of 50, 6000, and 75 defined at the beginning of the code correspond to it. For case IN, does it mean every 50 ms it increments 1 LED?

Also, for other people reading this who might need the same effect, for case OUT, instead of:

if ( breathIdx < 0 ) {
              // add done, back to the beginning
              breathState = IN;

I changed it to:

if ( breathIdx == 0 ) 
            {
              // add done, back to the beginning
              breathState = IN;
            }

just so it could keep looping the effect.

With that change, the breath out routine never sets LEDS[0] to black before starting breath in again. But, since the delay is 50 ms, it will be hard to notice.

As for the other code, yes, those time intervals specify how much time needs to elapse before changing the next led. You can speed up or slow down the process by adjusting them

I just had a few problems with it not looping back when it was set as < 0, but since it'd be hard to notice, it shouldn't be too big of a deal I think.

Alright, that makes sense. I think I'm beginning to understand your code a bit better, I definitely need some more practice before I am fully confident.

I really appreciate all your help, I hope you have a great day/night!