Struggling with millis() for two tasks

I've studied several very helpful tutorials about millis(), including the excelllent three-part series by UKHeliBob but I'm still struggling.

This is a simplified learning sketch. When I have it working I'll adapt and incorporate in my servo and motion sensor program. But plainly there's some basic logic I'm not getting yet. The regular flashes are OK but the red LED is not responding to the button. Right now, not at all. With earlier code it was coming on with even very brief presses.

Here's my current code, with detailed commenting:

// Flash the blue LED for 500 ms every 10 seconds
// If the button has been pressed for at least 1 s, light the red LED for 5s
// (Not fussed what happens to the blue LED during that time, but ideally
// would prefer to use millis for that 5s, rather than delay)
// Report events in the serial monitor during testing
// (It's a simplified learning sketch. When I have it working I'll adapt for
// a more complex servo and motion sensor program.)
// Button via 10k to 5V; input to D6 goes low when pressed
// Triggers red LED when input to D6 from button has remained low for >= 1s
// Report timestamps for testing

// Constants
const byte triggerPin = 6; // Low input from button to D6
const byte blueLEDPin = 7; // Blue LED flashes at 10s intervals
const byte redLEDPin = 8; // Large red LED, high for 5s when triggered
const unsigned long int interval = 10000; // 10s

// Variables
int triggerCounter = 0; // Counts number of triggers
int FlashCounter = 0; // Counts number of flashes
unsigned long previousMillis = 0; // Effectively the program start time
unsigned long triggerStart; // Time when D6 goes low


void setup()
{
  pinMode(triggerPin, INPUT); // D6
  pinMode(blueLEDPin, OUTPUT); // D7
  pinMode(redLEDPin, OUTPUT); // D8
  Serial.begin(9600);
  Serial.println("RegularBlinksPlusButtonTrigger");
  Serial.println("");
}

void loop()
{
  unsigned long currentMillis = millis(); // Get current time
  // Code to determine if D6 has been LOW for at least 1s
  if (digitalRead(triggerPin) == LOW)
  {
    // The button is pressed at this time
    triggerStart = millis();
    triggerCounter++;
    Serial.print("triggerCounter = ");
    Serial.println(triggerCounter);
    
    long duration = currentMillis - triggerStart;
    if (duration >= 1000)
    {
      Serial.print("duration of press so far was = ");
      Serial.println(duration);
      Serial.println("");
      digitalWrite(redLEDPin, HIGH);
      // Keep it high for 5s
      delay(5000);
      digitalWrite(redLEDPin, LOW); // Stays low until next trigger
    }
    triggerStart = currentMillis;
  } // End of trigger code

  // Check to see if it's time to flash the blue LED
  if (currentMillis - previousMillis >= interval)
  {
    // Flash blue LED for 500ms
    digitalWrite(blueLEDPin, HIGH);
    delay(500); // Brief flash. BUT SHOULD I USE MILLIS?
    digitalWrite(blueLEDPin, LOW);

    FlashCounter++; // Increment
    Serial.print("Blue LED flashed ");
    Serial.print(FlashCounter);
    Serial.println(" times");
    Serial.println("");
    previousMillis = currentMillis; // Reset
  } // End of regular flash code

} // End of Void Loop


And here's somee of the serial monitor output:

11:13:37.781 -> RegularBlinksPlusButtonTrigger
11:13:37.828 ->
11:13:48.296 -> Blue LED flashed 1 times
11:13:48.343 ->
11:13:52.703 -> triggerCounter = 1
11:13:52.750 -> triggerCounter = 2
11:13:52.750 -> triggerCounter = 3
11:13:52.750 -> triggerCounter = 4
11:13:52.796 -> triggerCounter = 5
11:13:52.796 -> triggerCounter = 6
11:13:52.843 -> triggerCounter = 7
11:13:52.843 -> triggerCounter = 8
11:13:58.328 -> Blue LED flashed 2 times
11:13:58.375 ->
11:14:05.843 -> triggerCounter = 9
11:14:05.890 -> triggerCounter = 10
11:14:05.890 -> triggerCounter = 11
11:14:05.937 -> triggerCounter = 12
11:14:05.937 -> triggerCounter = 13
11:14:05.984 -> triggerCounter = 14
11:14:05.984 -> triggerCounter = 15
11:14:06.031 -> triggerCounter = 16
11:14:08.375 -> Blue LED flashed 3 times
11:14:08.375 ->
11:14:11.515 -> triggerCounter = 17
11:14:11.562 -> triggerCounter = 18
11:14:11.562 -> triggerCounter = 19
11:14:11.609 -> triggerCounter = 20
11:14:11.609 -> triggerCounter = 21
11:14:11.656 -> triggerCounter = 22
11:14:11.656 -> triggerCounter = 23
11:14:11.703 -> triggerCounter = 24
11:14:11.703 -> triggerCounter = 25
11:14:11.750 -> triggerCounter = 26
11:14:11.750 -> triggerCounter = 27
11:14:11.796 -> triggerCounter = 28
11:14:11.796 -> triggerCounter = 29

triggerStart is constantly being reset to millis(), the same value as currentMillis(). currentMillis - triggerStart will never be > 0

2 Likes

Good catch. I typically look for that error but missed it this time.

consider
eliminates delays and fixes problem resetting triggerStart

// Flash the blue LED for 500 ms every 10 seconds
// If the button has been pressed for at least 1 s, light the red LED for 5s
// (Not fussed what happens to the blue LED during that time, but ideally
// would prefer to use millis for that 5s, rather than delay)
// Report events in the serial monitor during testing
// (It's a simplified learning sketch. When I have it working I'll adapt for
// a more complex servo and motion sensor program.)
// Button via 10k to 5V;
// input to D6 goes low when pressed
// Triggers red LED when input to D6 from button has remained low for >= 1s
// Report timestamps for testing
// Constants
#define MyHW
#ifdef MyHW
const byte triggerPin = A1;
const byte blueLEDPin = 13;
const byte redLEDPin  = 12;

#else
const byte triggerPin = 6; // Low input from button to D6
const byte blueLEDPin = 7; // Blue LED flashes at 10s intervals
const byte redLEDPin = 8; // Large red LED, high for 5s when triggered
#endif

enum { Off = HIGH, On = LOW };

unsigned long triggerStart; // Time when D6 goes low

unsigned long blueMsec;
byte          butState;

void setup ()
{
    pinMode (triggerPin, INPUT_PULLUP); // D6

    butState = digitalRead (triggerPin);

    pinMode (blueLEDPin, OUTPUT); // D7
    pinMode (redLEDPin,  OUTPUT); // D8

    digitalWrite (blueLEDPin, Off); // D7
    digitalWrite (redLEDPin,  Off); // D8

    Serial.begin (9600);
    Serial.println ("RegularBlinksPlusButtonTrigger");
    Serial.println ("");
}

void loop ()
{
    unsigned long currentMillis = millis (); // Get current time

    byte but = digitalRead (triggerPin);

    if (LOW == but) {        // button being pressed
        if (butState != but)  {
            butState = but;
            triggerStart = currentMillis;
        }

        if ( (currentMillis - triggerStart) >= 700)
        {
            digitalWrite (redLEDPin, ! digitalRead (redLEDPin));
            triggerStart = currentMillis;
        }
    }

    if (currentMillis > blueMsec)  {
        if (Off == digitalRead (blueLEDPin))
            blueMsec += 200;
        else
            blueMsec += 800;
        digitalWrite (blueLEDPin, ! digitalRead (blueLEDPin));
    }
}
1 Like

Thanks, got it. Just back in house at my Win 10 PC to report that I fixed it about half hour ago! (I'm using my ancient XP PC with no internet in my shed workshop. Earlier post was from my iPad. Transferring files is by USB stick but duly done that so here is my latest code, using a while() instead of if().

I'll study your code, although it includes stuff I'm not yet familiar with (like #define, #ifdef, #endif, #else, enum). But I see it uses case, which I do need to learn, as I suspected that would be the way to go.

void loop()
{
  unsigned long currentMillis = millis(); // Get current time
  // Code to determine if D6 has been LOW for at least 1s
  while (digitalRead(triggerPin) == LOW)
  {
    // The button is pressed at this time
    triggerStart = 0;
    long duration = millis() - currentMillis;
    if (duration >= 1000)
    {
      Serial.print("duration of press so far is = ");
      Serial.println(duration);
      Serial.println("");

      triggerCounter++;
      Serial.print("triggerCounter = ");
      Serial.println(triggerCounter);


      digitalWrite(redLEDPin, HIGH);
      // Keep it high for 5s
      delay(5000);
      digitalWrite(redLEDPin, LOW); // Stays low until next trigger
    }
    triggerStart = currentMillis;
  } // End of trigger code

  // Check to see if it's time to flash the blue LED
  if (currentMillis - previousMillis >= interval)
  {
    // Flash blue LED for 500ms
    digitalWrite(blueLEDPin, HIGH);
    delay(500); // Brief flash. BUT SHOULD I USE MILLIS?
    digitalWrite(blueLEDPin, LOW);

    FlashCounter++; // Increment
    Serial.print("Blue LED flashed ");
    Serial.print(FlashCounter);
    Serial.println(" times");
    Serial.println("");
    previousMillis = currentMillis; // Reset
  } // End of regular flash code

} // End of Void Loop

Terry

prevents any other processing (e.g. blue LED) while button is pressed. but this is why

works, because currentMillis is not being updated

1 Like

I placed the code from #4 into the wokwi simulator.

After reset:

Red lamp is on.

Blue lamp blinks and continues to do.

Pushbutton short press: toggle the Red lamp on and off. First button short press is ignored, that is to say the Red lamp stays on.

Pushbutton long press: Red light blinks throughout. Release of pushbutton leaves Red lamp on or off as it was at that very moment.

a7

1 Like

Great tool, must try it on my sketch. And I can use it here in my shed on my iPad.

Your post saved me what would probably have been a hour or two, as I had intended to try it even before fully understanding it.

Running yours - thanks for using my labels etc - gave me the results you described. Although did you change the blue LED action from mine (flashing once briefly every 10s)? The sim flashes on/off like the basic Blink sketch.
Look forward to more from gcjr.

BTW, did you have to enter all the code into Wokwi manually? I saw no Paste tool.

Always happy to turn someone on to the wokwi. I am their biggest fan. :expressionless:

FWIW it does work on the iPad, but they use their own text editing, I think, that makes it hard to cut and paste. And a bit frustrating to work the code in any way.

As for the sim I linked, it is exactly the code from #4 with only the definition of MyHW commented out, so we use your pins instead of the author's.

I just used the little tab to copy it here, and Control-V pasted it into the code window of a fresh wokwi UNO page after deleted the exiting minimal loop() and setup().

Since I made no changes, whatever the blue light does is what it does… hazard a guess to say it can be changed by some numbers or constants in the code, to (or back to) whatever you want.

I often make similar changes as ten seconds can become an eternity when watching something one is testing; putting the times into constants or affording other means to speed things up but retain the essential logic informing their activity helps. Here speeding up the blue blinks is handy, you can see it working, and remaining operational in very short time.

I did not yet try your code, but it would be a simple matter to as I have already done the "wiring".

HTH

a7

1 Like

// delay(80); // Brief flash. BUT SHOULD I USE MILLIS?

Yes. Any use of delay() anywhere, beyond very short lengths of time, will mess up the whole beauty of the millis() idiom.

This shows a literal way to turn on and off the blue light w/o delay()s. As coded, it can now be banked, that is to say ignored and used, provided it is called frequently.

The entire idea is that all you tasks are coded in a similar way - called frequently but far less frequently actually doing anything. Not time yet is the most common thing that happens in the section. Once in a while the light is turnt on or off.

This blue blinker uses the current value on the pin as the state of a little machine in order to decide what timer to consult and what action to take if it is time.

Why you hear us alla time talking about FSMs or finite state machines or just state machines. It is so beautiful no matter how you code it, and srsly unlocks the power many programs waste.

const byte triggerPin = 6; // Low input from button to D6
const byte blueLEDPin = 7; // Blue LED flashes at 10s intervals
const byte redLEDPin = 8; // Large red LED, high for 5s when triggered
const unsigned long int interval = 500; // 10s

// Variables
int triggerCounter = 0; // Counts number of triggers
int FlashCounter = 0; // Counts number of flashes
unsigned long previousMillis = 0; // Effectively the program start time
unsigned long triggerStart; // Time when D6 goes low


void setup()
{
  pinMode(triggerPin, INPUT); // D6
  pinMode(blueLEDPin, OUTPUT); // D7
  pinMode(redLEDPin, OUTPUT); // D8

  Serial.begin(9600);
  Serial.println("RegularBlinks Minus ButtonTrigger");
  Serial.println("");
}

# define blueOffTime    850
# define blueOnTime     150

unsigned long previousBlueOffTime;
unsigned long previousBlueOnTime; 

void loop()
{
  unsigned long currentMillis = millis(); // Get current time

  // Check to see if it's time to flash the blue LED
  if (digitalRead(blueLEDPin)) { // blue light is on

      if (currentMillis - blueOnTime > previousBlueOnTime) {
        digitalWrite(blueLEDPin, LOW);
        previousBlueOffTime = currentMillis;
      }
  }
  else { // blue light is off

      if (currentMillis - blueOffTime > previousBlueOffTime) {
        digitalWrite(blueLEDPin, HIGH);
        previousBlueOnTime = currentMillis;
      }

  } // End of regular flash code

} // End of Void Loop

 //   delay(80); // Brief flash. BUT SHOULD I USE MILLIS?

HTH

a7

Thanks a7. Another project onto my growing ‘Arduino To Do list’.

Yes, I see now that the the incorrect blue LED timing was by gcjr.

Anyway, I’ll be changing the spec myself shortly, to get closer to the intended program. The main change will be that as well as being used for the regular 500ms highs (at 10s intervals), the blue LED must now also be taken high twice (200ms high/200ms low) as soon as the trigger is validated, directly before the red LED is taken high for 5s. That emulates my CUBE camera button being pressed twice to start recording a video; only a single press is needed for a photo. I’m having to take unwanted photos to keep the camera powered up.

I may therefore be back for more help please!

P.S. I agree about 10s being tedious for testing, but it emulates 160s, just under the ‘keep alive’ time of the tiny CUBE camera, so I want to keep reasonably close to the real use case.

Terry

Sounds like fun and an excellent small set of steps and tasks for a beginning FSM project.

I recommend you avoid using any FSM library. I am biased to code I write, that’s where the fun is, so a grain of salt shoukd go with my observation that most libraries take more time to come to grips with and use than simply learning how to roll your own.

As always, YMMV. google

 arduino traffic lights FSM

and poke around a bit for something to get your teeth into.

a7

What CUBE camera is that you working with?

Does it matter where the two blue 200 ms pulses come in relation to a periodic blue 500 ms pulses? That is, what happens if there is a trigger during or very near to a periodic pulse?

Does the 5 second red pulse begin immediately at the falling edge of the second blue 200 ms pulse?

When should periodic blue pulses resume after a triggered sequence blue-blue-red?

I am curious mostly, but ask because... your code will end up answering these questions, and if you don't plan ahead, the answers it comes up with may not be what you want!

HTH

a7

Thanks a7, appreciate your continued interest and encouragement.

After the help here I'm pleased to say that I got the 'LED emulation' version working yesterday. I then made the changes described in para 3 of my post #11, and was delighted to get that version working on the bench last night. I ran it successfully overnight, although the only videos recorded in some 14 hours were of me. No fox, the project's target!

Here's an image of my Polaroid CUBE (old, original version) :

I'll post my code later today, as I'd like to get suggestions on improvements, format, etc.

As you'll see, I changed from using if() for my main 'trigger determining' logic when the penny dropped that I should be detecting an edge. The motion sensor input goes low for about 18s (it's user settable and that's the minimum) and I want to start recording my video of whatever activated it asap after that. Once the CUBE is doing that for about 30s (together with displaying the red LED as an indicator during testing), I don't care what happens to the KA section. So I'm currently still using delay()s for the video part without unwanted practical consequences. My tests (with much shorter duration) confirmed that the KA photos did not interfere with the videos.

I'm new to 'FSM' but it seems that my latest version actually uses it!
:slightly_smiling_face:

I do no argue with success, and I have no problem with getting something to work just fine with delay().

Sometimes a nail is sticking up out of the floor and whacking it with a hammer is the best course of action.

I would probably make the pulses with delay(), too. And deploy the working project.

Would you care to post your current working code?

a7

As promised in #14 earlier today, here it is:

// Monday 13 September 2021
// About to move 328 off UNO and onto independent veroboard so made some changes
// Yellow (not blue) LED now lit from D13, not D7
// Kept the serial output for testing
// But need to be able to test when independent.So added switch 
// to circuit case; pin D4 input from switch sets KA interval to 5s (for testing) or 160s
// Takes 'keep alive' (KA) photo with one servo press every 2m40s (160s)
// The low-going leading EDGE of the 20s outputfrom motion sensor is D6 input.
// That's the major change from LED emulation version
// It causes two servo presses on CUBE button, starting a video recording (and lighting red LED as an indicator, now from D13)

#include <VarSpeedServo.h> // Uses the VarSpeedServo library
VarSpeedServo cubeservo;

// Constants
const byte triggerPin = 6; // Low input from button to D6
const byte yellowLEDPin = 13; // LED flashes at intervals for KA photo
const byte redLEDPin = 8; // Lit while video recording
const byte servoOutPos = 30; // Trial & error
const byte servoInPos = 10; // Trial & error
const byte switchPin = 4; // For easier testing when on Veroboard not UNO
const int waitBeforeRelease = 200;
const int waitAfterRelease = 200;

// Variables
int triggerCounter = 0; // Counts number of triggers (MS lows)
int KACounter = 0; // Counts number of KA photos
bool trigState = HIGH;
bool prevtrigState = HIGH;
unsigned long previousMillis = 0; // Effectively the program start time
unsigned long interval; // 5s (testing) or 160s (2m40) KA photos interval

void setup()
{
  pinMode(switchPin, INPUT_PULLUP);
  pinMode(triggerPin, INPUT); // D6
  pinMode(redLEDPin, OUTPUT); // D8
  pinMode(yellowLEDPin, OUTPUT); // D13
  Serial.begin(115200);
  Serial.println("ServoCUBEvideosMS-3");
  Serial.println("");
  cubeservo.attach(9); // CUBE servo (white wire)
  cubeservo.write(servoOutPos);
  delay(500);

  // Test D4 to select value of interval
  if (digitalRead(switchPin) == HIGH)
  {
    interval = 160000; // 2m40s
  }
  else
  {
    interval = 5000; // 5s
  }
//    while(1 == 1); // For testing. Stops program entering loop
}

void loop()
{
  // Input to D6 is from a motion sensor (MS), which is normally H
  // It goes L when motion detected, returning H about 18-20s later.
  // Get the current input state
  trigState = digitalRead(triggerPin);
  // Compare with its previous state
  if (trigState != prevtrigState)
  {
    if (trigState == LOW) // Implies this is the low-going edge we want
    {
      triggerCounter++;
      Serial.print("triggerCounter = ");
      Serial.println(triggerCounter);
      // NOW TAKE ALL THE ACTIONS NEEDED AFTER TRIGGERING
      Serial.print("Started video ");
      Serial.println(triggerCounter);

      // Move servo arm in/out twice to press CUBE button twice
      singleCUBEServoPress(); //Single short sweep in and out
      singleCUBEServoPress(); //Single short sweep in and out

      digitalWrite(redLEDPin, HIGH); // Indicates recording in progress
      //Stays high during video recording, i.e for 30s
      //      delay(4000); // For testing
      delay(30000); // 30s video
      digitalWrite(redLEDPin, LOW); // Stays low until next video record

      // Now end video recording with a single CUBE button press
      singleCUBEServoPress(); //Single short sweep in and out
      Serial.print("Ended video ");
      Serial.println(triggerCounter);
    }
    // Nothing needed otherwise, i.e no else() required here??
    // But will include for reporting at least for now
  }
  //  else
  //  {
  //    // if the current state is HIGH then the button went from L to H:
  //    Serial.println("Input went from L to H");
  //  }

  // Brief delay (as seen in others' sketches), although could probably
  // be even shorter (1ms for stability?) as there is no bouncing involved
  delay(100);
  // Save current state for next time through the loop
  prevtrigState = trigState;
  // End of trigger detection and actions

  /////////////////////////////////////////////////////////////////////

  // CUBE camera inactivity for about 3 mins powers it off. So must take
  // 'keep alive' (KA) photo before this
  // Check to see if it's time for a KA photo
  unsigned long currentMillis = millis(); // Get current time
  if (currentMillis - previousMillis >= interval)
  {
    // Move servo arm in/out once to press CUBE button once
    singleCUBEServoPress(); //Single short sweep in and out - function below

    digitalWrite(yellowLEDPin, HIGH); // (was blue)
    delay(500); // Brief flash
    digitalWrite(yellowLEDPin, LOW);
    
    KACounter++; // Increment
    Serial.print("KA photo ");
    Serial.println(KACounter);
    Serial.println("");
    previousMillis = currentMillis; // Reset
  } // End of KA code

} // End of Void Loop

void singleCUBEServoPress()
{
  cubeservo.write(servoInPos); // Rotates to the 'In' position
  delay(waitBeforeRelease); // Duration of press; was 200
  cubeservo.write(servoOutPos); // Rotates to the 'Out' position
  delay(waitAfterRelease); // Duration of release/recovery; was 200
}

I'd welcome any suggestions on improvements, format, etc.

Terry

Haha, missed the promise. THX.

I put the code in the wokwi and wired up a servo that they have.

Here I added

unsigned long interval = 5000; // 5s (testing) or 160s (2m40) KA photos interval

even though your logic assures a value will be assigned, putting one in the declaration means no accident will ever land you up with a zero for the interval. A bigger deal in different circumstances and def a belt added to the suspenders. Cheap enough.

Where do the trigger signals come from and how long do they last?

By timing my press of the trigger just right, I can get three servo sweeps, one KA and the double tap for Video.

There does seem to be a bit of delay between the two, is this enough for the CUBE?

By pressing it for "not long enough" during the KA sweep, you miss the video start.

In a similar vein, after the video it seems possible that a KA photo will follow very closely. Again, this may be OK, don't have a CUBE to test!

This

 // Brief delay (as seen in others' sketches), although could probably
// be even shorter (1ms for stability?) as there is no bouncing involved
 delay(100);

delays the KA photo for 100 ms, which may be enough. If it isn't enough, merely increasing it would slow your whole loop down, so instead I suggest that right after the video end pulse, you scoot forward the KA timer

previousMillis = millis();

which will postpone the next KA photo for the interval it is timed against. Maybe the variable should be named intervalKA or something, just sayin'.

Lastly, sry, but the LED has to be KA blue! Who signed off on this late change? :expressionless:

a7

It shouldn't. At least not for long.
Simulators that forget to use current limiting resistors with LEDs are setting a bad example.
If it was a true simulator, then virtual smoke should pour from the LEDs and the Arduino :slight_smile:
Leo..

And yet it does. Just lazy - you can add resistors.

Not sure that's the first next thing I'd have the wokwi crew fix or add.

a7

I put the code in the wokwi and wired up a servo that they have.

Could you post the link please.

Where do the trigger signals come from and how long do they last?

See the comments at the start of void()

the LED has to be KA blue! Who signed off on this late change?

Yep, went against the grain, but my veroboard circuit already had only a yellow one available :slightly_smiling_face:

I'll study your detailed points fully later. How confident are you that the simulation accurately reflects the real circuit action? A servo alone would be sufficient. Those servo timings have proved consistently reliable for pressing the CUBE's stiff button.

Terry