Multithreading with Electron in a for-loop

Hi all, I’m working on a candy machine for halloween. Basically when someone touches the button, it dispenses a piece of candy, then runs through a 10 for loop as a ‘cooldown’ (the seconds count down on an LCD) to make sure kids don’t spam it. The problem is, I have two dispensers, so if one dispenser is in its ‘cooldown’ phase, if the other dispenser’s button is pressed, I want candy to dispense out of it. It’s ok if the cooldown is terminated in the process. I’ve tried countless ways with booleans, to if-statements inside of the for loop but I can’t test to see if the second button is touched while one dispenser’s cooldown phase is running. Here is my for-loop cooldown:

for(int i=0;i<11;i++) {
                    lcd.clear();
    lcd.print("  Cooldown: " + String(10-i));
    delay(1000);
}
                defaultMessage();

Can you do multithreading in for-loops? Any ideas?

By making use of millis timers, you can create non-blocking delays, so both can run simultaneously. Alternatively, software timers can be used that trigger each second, and are topped when reaching the cool down time.

5 Likes

@Moors7

So I tried doing it like this inside of the if-statement for the button assuming that it would do what the for-loop did but I’ve run into a problem.

long lastPublish = 0;
int delaySeconds = 1000;   

 if (millis()-lastPublish > delaySeconds) {
        lastPublish = millis();
        lcd.clear();
        lcd.print("Cooldown: " + (abs(String(10-lastPublish/1000)));
                    } 

On the lcd it displays a number and doesn’t change. On the lcd it says “Cooldown: 4” .

(abs(String(10-lastPublish/1000)))

what does the abs() function return when you pass it an object of type String?

side note:

long lastPublish = 0;

for your timer to work properly, you want to use unsigned long type for these values

2 Likes

@BulldogLowell @Moors7

It turns out this works, but after you press the button it’s not being displayed on the lcd.

int mill = millis();
  mill = 0;
  if (millis()-lastPublish > delaySeconds) {
        // Remember when we published
        lastPublish = millis();
        lcd.clear();
        int time = 11-lastPublish/1000;
        if(time > -1){
        lcd.print("Test:" + String(time));
        }
                    }

That’s the cool down part, but this is the whole button touch part. For some reason the cool down isn’t displayed and the lcd is going blank:

if(but1state == HIGH){//fist motor
        digitalWrite(1, HIGH);
        lcd.clear();
        lcd.print("    Candy!");
        Particle.publish("Touched", 0, 60, PRIVATE);
        Serial.print("********************Touched!");
                myStepperRight.step(stepsDown);
                lcd.clear();
                lcd.print(" Candy coming!");
            delay(1800);
                    myStepperRight.step(-stepsUp);
                    delay(200);
                    //this is where the cool down is:
 int mill = millis();
      mill = 0;
      if (millis()-lastPublish > delaySeconds) {
            // Remember when we published
            lastPublish = millis();
            lcd.clear();
            int time = 11-lastPublish/1000;
            if(time > -1){
            lcd.print("Test:" + String(time));
            }
                        

    }

Any ideas why the cool down is skipped?

I can’t get my head around this:

 int mill = millis();  // initialize variable mill to millis()
      mill = 0;  // forget all that and just change it to zero

does this even compile:

lcd.print("Test:" + String(time));

I’m pretty sure you cannot concat a String onto a string literal like that.

you could try to use printf() if possible:

lcd.printf("Test: %d", time);

don’t forget the Time Library is available to you…

1 Like

@BulldogLowell @Moors7
It compiles fine. The time is just from the millis(). Is that why it doesn´t print? I forgot to mention that I created a blank lcd program, and the only thing I had it printing, was the cooldown, and it prints on the lcd just fine. So in the program, it just must be skipping over it for some reason. I have no idea why it would be skipping…

It looks like you never close the original if statement for the button press. I'm assuming you are trying to do everything inside of that original if statement which will only run while the button is pressed.
Maybe closing that bracket will do what you are trying to do, it would be a lot easier to use two FSM (state machines) to handle the two sperate dispensers.
Its 3:30 and i need sleep i[ll explain more fully tomorrow

I always have a check to see if mills() < lastPublish to handle the case wherein millis() has rolled over. This will happen approximately every 49 days and I believe I’ve had devices operate without rebooting for that period of time. Without the check, the code will only work for the first 49 days and then never work again until the device reboots.

Although a common misconception, this is not true if you are using unsigned subtraction.

static uint32_t lastMillis;
uint32_t currentMillis = millis();
if(currentMillis - lastMillis > INTERVAL)  // unsigned subtraction
{
  // doTimedEvent();
  lastMillis = currentMillis;
}
2 Likes

Interesting clarification. Thank you. However, I often see, as above, integer variables being used and I’m assuming (yep, bad practice) that if either variable is signed, the operation will be a signed. I’ll be careful to follow your technique in the future and keep my code a bit cleaner.

Well, when an expression contains both signed and unsigned int values, the signed int will be automatically converted to unsigned int, therefore the result will not be less than 0.

you can test that:

  uint32_t x = 32;
  int32_t y = 33;
  Serial.println((x-y));

unlike e ... (x-y) != -1

:wink:

@JackD12 you should read this:
https://www.embeddedrelated.com/showarticle/723.php
The example about the couch is pretty relevant.
You might want something like this

#include "elapsedMillis.h";

elapsedMillis elapsedTime;

typedef enum{
    WAITING = 0,
    DISPENSING_1,
    DISPENSING_2,
    COOLDOWN_INIT,
    COOLDOWN_PRINT,
    COOLDOWN_WAIT,
    COOLDOWN_END
} dispensor_state_t;

dispensor_state_t rightDispensor = WAITING;
int countDown=11;

void button1StateMachine(){
  switch(rightDispensor){
    case WAITING:
      // BUTTON DEBOUNCING SHOULD BE HERE, ALSO MAYBE USE ACTIVE LOW
      if(but1State == HIGH){
        rightDispensor=DISPENSING_1;
      }
      break;
    case DISPENSING_1:
      myStepperRight.step(stepsDown);
      lcd.clear();
      lcd.print(" Candy coming!");
      elapsedTime=0;
      rightDispensor=DISPENSING_2;
      break;
    case DISPENSING_2:
      if(elapsedTime > 1800){ // if it has been longer than 1800 ms set the countDown to 11 seconds and move to the cooldown print state
        myStepperRight.step(-stepsUp);
        elapsedTime=0;
        rightDispensor=COOLDOWN_INIT;
      }
      break;
    case COOLDOWN_INIT:
      if(elapsedTime > 200){
        rightDispensor=COOLDOWN_PRINT;
      }
    case COOLDOWN_PRINT:
      char buffer[20];
      sprintf(buffer, "Test %d", countDown);
      lcd.clear();
      lcd.print(buffer);
      // if countdown is not at zero move to wait, otherwise go back to the original state waiting for a new button press
      if(countDown>0){
        elapsedTime=0;
        rightDispensor=COOLDOWN_WAIT;
      }
      else{
        rightDispensor=COOLDOWN_END;
      }
      break;
    case COOLDOWN_WAIT:
      if(elapsedTime > 1000){ // if it has been 1 second decrease coundown time by one and go back to print
        countDown-=1;
        rightDispensor=COOLDOWN_PRINT;
      }
      break;
    case COOLDOWN_END:
      countDown=11;
      rightDispensor=WAITING;
      break;
  }
}

void setup(){
}

void loop(){
  button1StateMachine();
  //button2StateMachine();
}
1 Like