Go Down

Topic: Replacing delay with milli (Read 134 times) previous topic - next topic

JoeEason

Hi,
I'm using LDRs to control a model railway. I have 2 lines parallel to each other (not enough space for a full loop). These run simultaneously but separately to one another. So when the trains reach the ends of the lines they stop and reverse direction.
I've got it to work with a single line but I'm still using the 'delay' function which means the train 9n the second line is not detected when it goes over the LDRs of the second line. I know there is the 'milli' function which can be used to replace 'delay' but I'm struggling with how to do it.

void loop() {
  // put your main code here, to run repeatedly:

int ldrStatus01 = analogRead(ldrPin01); //first line end one
int ldrStatus02 = analogRead(ldrPin02); //first line end two
int ldrStatus03 = analogRead(ldrPin03); // second line end one
int ldrStatus04 = analogRead(ldrPin04); // second line end two

if (ldrStatus01 >=400){
  digitalWrite(relayPin03, LOW);
  digitalWrite(relayPin04, LOW); //train one stops
  delay(2000);
  digitalWrite(relayPin01, HIGH);
  digitalWrite(relayPin02, HIGH); //polarity + direction reverses
  delay(2000);
  digitalWrite(relayPin03, HIGH);
  digitalWrite(relayPin04, HIGH); //train starts again
  delay(2000);
  Serial.print("train stops waits and reverses : ");
  Serial.println(ldrStatus01);
}
if (ldrStatus03 >=400){
  digitalWrite(relayPin03, LOW);
  digitalWrite(relayPin04, LOW);
  delay(2000);
  digitalWrite(relayPin01, LOW);
  digitalWrite(relayPin02, LOW);
  delay(2000);
  digitalWrite(relayPin03, HIGH);
  digitalWrite(relayPin04, HIGH);
  delay(2000);
  Serial.print("Train stops, waits and proceeds forwards : ");
  Serial.println(ldrStatus02);
}
if (ldrStatus03 >=400){
  digitalWrite(relayPin05, LOW); //train two stops
  digitalWrite(relayPin06, LOW);
  delay(2000);
  digitalWrite(relayPin07, HIGH); //polarity and direction reverses
  digitalWrite(relayPin08, HIGH);
  delay(2000);
  digitalWrite(relayPin05, HIGH); //train two starts again
  digitalWrite(relayPin06, HIGH);
  delay(2000);
  Serial.print("train stops waits and reverses : ");
  Serial.println(ldrStatus03);
}
if (ldrStatus04 >=400){
  digitalWrite(relayPin05, LOW);
  digitalWrite(relayPin06, LOW);
  delay(2000);
  digitalWrite(relayPin07, LOW);
  digitalWrite(relayPin08, LOW);
  delay(2000);
  digitalWrite(relayPin05, HIGH);
  digitalWrite(relayPin06, HIGH);
  delay(2000);
  Serial.print("Train stops, waits and proceeds forwards : ");
  Serial.println(ldrStatus04);
}
}

PaulS

Quote
I know there is the 'milli' function
No. There is the millis() function.

You will NOT directly substitute millis() for delay() in that code.

You will completely rewrite that code, and will use millis() to determine when events happen, such as a train blocking a sensor, and to determine what time it is now.

Your program is in one, or more, state(s) at any given time. At that time, it may, or may not, be time to change states. If it is, one or more things need to happen and one or more event times need to be updated.

Google "Arduino state machine" for more than you ever wanted to know.
The art of getting good answers lies in asking good questions.

JoeEason

Thanks for the quick reply, but 'you need to rewrite the full thing' and 'google it' wasn't quite what I was after...
I have a vague understanding of state machine mechanics and I know why the 'delay' function doesn't work. But I'm still a bit of a novice with arduino  coding I was maybe hoping someone might know a similar/ decent representation of a similar code or design that I could study and use to rewrite my code accordingly.
Ive only really dabbled in arduino coding and it's not a part of what I do day to day so I'm just self taught and struggle to find the time to do a lot of research into more complex techniques

Danois90

You must consider your sketch as a timeline which starts immediately after the arduino is powered on. Then you can have your things to happen each second, each minute, each hour and so on. It is quite simple:

Code: [Select]
#define ONE_SECOND 1000
#define ONE_MINUTE 60000U
#define ONE_HOUR 3600000UL

unsigned long lastEverySecond = 0;
unsigned long lastEveryMinute = 0;
unsigned long lastEveryHour = 0;

void loop()
{
  unsigned long now = millis();
  if (now - lastEverySecond >= ONE_SECOND)
  {
    lastEverySecond = now;
    //Done each second
  }
  if (now - lastEveryMinute >= ONE_MINUTE)
  {
    lastEveryMinute = now;
    //Done each minute
  }
  if (now - lastEveryHour >= ONE_HOUR)
  {
    lastEveryHour = now;
    //Done each hour
  }
}


As you can see, each task on the timeline is handled exactely the same: If current millis minus last millis the task was executed is equal to or greater than the tasks interval then perform the task again and store the current millis as the last time the task was performed.
Instead of mocking what's wrong, teach what's right! ;)
When you get help, remember to thank the helper and give some karma!
Please, do NOT send me any private messages!!

UKHeliBob

Here is some example code that I wrote to illustrate a state machine.  Read the comments and the messages it produces to see what is going on and why

Code: [Select]

/*
State Machine example
Test system :
2 inputs normally held HIGH using INPUT_PULLUP in pinMode()
2 digital outputs (active LOW)

States :
IDLE
    apparently do nothing.  Actually busy doing nothing
      move to LED_ZERO_ON_10_SECS if input 0 becomes LOW

LED_ZERO_ON_10_SECS
    turn LED 0 on for 10 seconds
      move to BLINK_LED_ONE if 10 second period elapses
      move to ALTERNATE_LEDS if input 1 becomes LOW during the period

BLINK_LED_ONE
    blink LED 1
      move to LED_ZERO_ON_10_SECS if input 0 becomes LOW
      move to IDLE if input 1 becomes LOW

ALTERNATE_LEDS
    turn each LED on and off in turn
    move to IDLE if input 1 becomes LOW
*/

//global variables
enum STATES   //give the states some names
{
  IDLE,
  LED_ZERO_ON_10_SECS,
  BLINK_LED_ONE,
  ALTERNATE_LEDS
};
byte currentState = IDLE;

const byte inputPins[] = {A1, A2};
const byte NUMBER_OF_INPUTS = sizeof(inputPins) / sizeof(inputPins[0]);
const byte ledPins[] = {3, 5};
const byte NUMBER_OF_LEDS = sizeof(ledPins) / sizeof(ledPins[0]);
byte currentInputStates[] = {HIGH, HIGH};
byte previousInputStates[] = {HIGH, HIGH};
byte currentLed = 0;    //index to the current LED when blinking them alternately
unsigned long currentTime;    //time at start of loop() for states using timing
unsigned long startTime;  //used in states requiring timing
unsigned long period;   //used when timing
boolean messageDone = false;    //flag to control message printing

void setup()
{
  Serial.begin(115200);
  for (int pin = 0; pin < NUMBER_OF_INPUTS; pin++)
  {
    pinMode(inputPins[pin], INPUT_PULLUP);
  }
  for (int pin = 0; pin < NUMBER_OF_LEDS; pin++)
  {
    pinMode(ledPins[pin], OUTPUT);
    digitalWrite(ledPins[pin], HIGH); //turn off the LEDs
  }
}

void loop()
{
  currentTime = millis();   //in case it is needed for the current or target state
  switch (currentState)     //execute code for the current state
  {
    case IDLE:
      if (!messageDone)
      {
        showState("In state IDLE.\nTake pin A1 LOW to move to LED_ZERO_ON_10_SECS");
      }
      previousInputStates[0] = currentInputStates[0];
      currentInputStates[0] = digitalRead(inputPins[0]);
      if (currentInputStates[0] != previousInputStates[0] && currentInputStates[0] == LOW)  //input 0 has gone LOW
      {
        startTime = currentTime;    //ready to start timing in target state
        currentState = LED_ZERO_ON_10_SECS;
        period = 10000;
        digitalWrite(ledPins[0], LOW);  //turn on LED 0
        Serial.println(F("Moving to state LED_ZERO_ON_10_SECS"));
        messageDone = false;
      }
      break;
    //
    case LED_ZERO_ON_10_SECS:
      if (!messageDone)
      {
        showState("In state LED_ZERO_ON_10_SECS.\nProgram will appear to pause for 10 seconds then move to BLINK_LED_ONE\nor take pin A2 LOW to move to alternate LED flashing");
      }
      if (currentTime - startTime >= period)  //has the perioed elapsed ?
      {
        digitalWrite(ledPins[0], HIGH);  //turn off LED 0
        startTime = currentTime;        //ready to start timing in target state
        currentState = BLINK_LED_ONE;   //state to move to
        period = 100;   //blink period for new state
        Serial.println(F("Time's up\nMoving to state BLINK_LED_ONE"));
        messageDone = false;
      }
      previousInputStates[1] = currentInputStates[1];
      currentInputStates[1] = digitalRead(inputPins[1]);
      if (currentInputStates[1] != previousInputStates[1] && currentInputStates[1] == LOW)  //input 1 has gone LOW
      {
        digitalWrite(ledPins[1], HIGH);  //turn off both LEDs
        digitalWrite(ledPins[0], HIGH);  //turn off both LEDs
        currentState = ALTERNATE_LEDS;
        Serial.println(F("Moving to state ALTERNATE_LEDS"));
        startTime = currentTime;
        period = 100;
        messageDone = false;
      }
      break;
    //
    case BLINK_LED_ONE:
      if (!messageDone)
      {
        showState("In state BLINK_LED_ONE\nTake pin A1 LOW to move to LED_ZERO_ON_10_SECS\nTake pin A2 LOW to move to IDLE");
      }
      if (currentTime - startTime >= period)  //time to change LED output
      {
        digitalWrite(ledPins[1], !digitalRead(ledPins[1]));
        startTime = currentTime;
      }
      previousInputStates[0] = currentInputStates[0];
      currentInputStates[0] = digitalRead(inputPins[0]);
      if (currentInputStates[0] != previousInputStates[0] && currentInputStates[0] == LOW)  //input 0 has gone LOW
      {
        digitalWrite(ledPins[0], LOW);  //turn on LED 0
        digitalWrite(ledPins[1], HIGH);  //turn off LED 1
        period = 10000;
        startTime = currentTime;    //ready to start timing in target state
        currentState = LED_ZERO_ON_10_SECS;
        Serial.println(F("Moving to state LED_ZERO_ON_10_SECS"));
        messageDone = false;
      }
      previousInputStates[1] = currentInputStates[1];
      currentInputStates[1] = digitalRead(inputPins[1]);
      if (currentInputStates[1] != previousInputStates[1] && currentInputStates[1] == LOW)  //input 1 has gone LOW
      {
        digitalWrite(ledPins[1], HIGH);  //turn off LED 1
        currentState = IDLE;
        Serial.println(F("Moving to state IDLE"));
        messageDone = false;
      }
      break;
    //
    case ALTERNATE_LEDS:
      if (!messageDone)
      {
        showState("In state ALTERNATE_LEDS\nTake pin A2 LOW to move to IDLE");
      }
      if (currentTime - startTime >= period)  //time to change LEDs
      {
        digitalWrite(ledPins[currentLed], HIGH);  //turn off the current LED
        currentLed++;
        currentLed %= 2;  //next LED or back to zero
        digitalWrite(ledPins[currentLed], LOW);  //turn on the new current LED
        startTime = currentTime;
      }
      previousInputStates[1] = currentInputStates[1];
      currentInputStates[1] = digitalRead(inputPins[1]);
      if (currentInputStates[1] != previousInputStates[1] && currentInputStates[1] == LOW)  //input 1 has gone LOW
      {
        digitalWrite(ledPins[1], HIGH);  //turn off both LEDs
        digitalWrite(ledPins[0], HIGH);  //turn off both LEDs
        currentState = IDLE;
        Serial.println(F("Moving to state IDLE"));
        messageDone = false;
      }
  }
}

void showState(const char * message)
{
  Serial.println();
  Serial.println(message);
  messageDone = true;
}



The code needs some improvements to deal with switch contact bounce but it will do for now
Please do not send me PMs asking for help.  Post in the forum then everyone will benefit from seeing the questions and answers.

Robin2

I am building a model railway (actually 2). The smaller one has 9 LDRs for train detection.


The demo Several Things at a Time illustrates the use of millis() to manage timing without blocking. It may help with understanding the technique.

Have a look at Using millis() for timing. A beginners guide if you need more explanation.


I use an array to hold the pin numbers for the LDRs and another array to hold their current states. I then read all the LDRs on every iteration of loop() with code like this

Code: [Select]
for (byte n = 0; n < numLDRs; n++) {
  ldrState[n] = digitalRead(ldrPin[n]);
}




...R
Two or three hours spent thinking and reading documentation solves most programming problems.

wildbill

You need a finite state machine (FSM) for each line. With a bit of care, you can use the same code for both and just keep two sets of pin and state data.

The trouble with FSMs is that although they are trivially simple once you understand them, for many people, there seems to be a significant mental hurdle to overcome before you actually comprehend what is going on. It's especially gnarly if it's explained in the abstract, unless you're a math major, you'll need to see some examples.

Here's an old thread on the subject that may help in that regard: https://forum.arduino.cc/index.php?topic=137520.0

Go Up