Replace delays with timers

Hi guys,

I will start of by describing the current project I am working on.

I build a car equipped with a NodeMCU V2 board and 3 ultrasonic sensors.
This car shall be controlled by the Blynk App.

I designed an interface with 5 buttons.
4 buttons are designated for: forwards/backwards/left-turn/right-turn.

By pushing the 5th button the car should switch to an automatic mode. While in this Mode it does the following:

  1. Goes straight forwards until an obstacle appears.
  2. Reverse
  3. Check the left and right side for distances.
  4. Turn to the side with more free space and keep on driving.

The manual control of the car works just fine with the Blynk App.
The automatic mode works as well as long as I keep all the Blynk stuff out of it.
When I combine both parts the automatic mode isn´t working anymore.

The problem are the delay functions as I learned today.

Is there an easy way to replace those delay functions?

Here is my code:

#define BLYNK_PRINT Serial
#include <BlynkSimpleEsp8266.h>
#include <ESP8266WiFi.h>
#include <NewPing.h>

char auth[] = "XXX";
char ssid[] = "XXX";
char pass[] = "XXX";

#define MAX_DISTANCE 200 

//Left Sensor = Sensor_L
#define Trigger_Sensor_L 16
#define Echo_Sensor_L 13

//Middle Sensor = Sensor_M
#define Trigger_Sensor_M 12
#define Echo_Sensor_M 14

//Right Sensor = Sensor_R
#define Trigger_Sensor_R 16
#define Echo_Sensor_R 15

#define SONAR_NUM 3

//Right Motor = Motor_R
int Motor_R_Speed = 5;
int Motor_R_Direction = 2;

//Left Motor = Motor_L
int Motor_L_Speed = 4;
int Motor_L_Direction = 0;

NewPing sonar1(Trigger_Sensor_L, Echo_Sensor_L, MAX_DISTANCE);
NewPing sonar2(Trigger_Sensor_M, Echo_Sensor_M, MAX_DISTANCE);
NewPing sonar3(Trigger_Sensor_R, Echo_Sensor_R, MAX_DISTANCE);

unsigned int Sensor_L = 0;
unsigned int Sensor_M = 0;
unsigned int Sensor_R = 0;
int i = 0;
int Current_Distance_L = 0;
int Current_Distance_M = 0;
int Current_Distance_R = 0;

void setup() {
  Serial.begin(9600);
  Blynk.begin(auth, ssid, pass);
  pinMode(Motor_L_Speed, OUTPUT);
  pinMode(Motor_R_Speed, OUTPUT);
  pinMode(Motor_L_Direction, OUTPUT);
  pinMode(Motor_R_Direction, OUTPUT);
}

void straight()
{ analogWrite(Motor_L_Speed, 850);
  analogWrite(Motor_R_Speed, 850);
  digitalWrite(Motor_L_Direction, HIGH);
  digitalWrite(Motor_R_Direction, HIGH);
}

void backwards()
{ analogWrite(Motor_L_Speed, 1000);
  analogWrite(Motor_R_Speed, 1000);
  digitalWrite(Motor_L_Direction, LOW);
  digitalWrite(Motor_R_Direction, LOW);
}

void right()
{ analogWrite(Motor_L_Speed, 1000);
  analogWrite(Motor_R_Speed, 1000);
  digitalWrite(Motor_L_Direction, HIGH);
  digitalWrite(Motor_R_Direction, LOW);
}

void left()
{ analogWrite(Motor_L_Speed, 1000);
  analogWrite(Motor_R_Speed, 1000);
  digitalWrite(Motor_L_Direction, LOW);
  digitalWrite(Motor_R_Direction, HIGH);
}

void stopping()
{ analogWrite(Motor_L_Speed, 0);
  analogWrite(Motor_R_Speed, 0);
}

int readPing1() { 
  delay(40);
  unsigned int uS = sonar1.ping();
  int cm = uS / US_ROUNDTRIP_CM;
  return cm;
}
int readPing2() { 
  delay(40);
  unsigned int uS = sonar2.ping();
  int cm = uS / US_ROUNDTRIP_CM;
  return cm;
}
int readPing3() { 
  delay(40);
  unsigned int uS = sonar3.ping();
  int cm = uS / US_ROUNDTRIP_CM;
  return cm;
}

void find_path() {
  Current_Distance_L = readPing1();
  Current_Distance_R = readPing3();
  if (Current_Distance_L >= Current_Distance_R) {
    left();
    delay(500);
  } else {
    right();
    delay(500);
  }
}

void automatic() {
  Current_Distance_M = readPing2();
  straight();
  if (Current_Distance_M <= 9)
  { backwards();
    delay(500);
    find_path();
  }
}


void loop()
{ Blynk.run();
}

BLYNK_WRITE(V0)
{
  if (param[0])
    straight();
  else
    stopping();
}

BLYNK_WRITE(V1)
{
  if (param[0])
    right();
  else
    stopping();
}

BLYNK_WRITE(V2)
{
  if (param[0])
    backwards();
  else
    stopping();
}

BLYNK_WRITE(V3)
{
  if (param[0])
    left();
  else
    stopping();
}

BLYNK_WRITE(V4) {
  if (param[0])
    automatic();
  else
    stopping();
}

Is there an easy way to replace those delay functions?

You bet. Ctrl-A (select everything). Ctrl-X (delete it). Start over, writing non-blocking code.

Well, maybe not easy. But, it WILL be necessary.

delay() does NOT belong in readPingN(), unless you rename the functions to stupidDelayBeforeReadPingN().

void automatic() {
  Current_Distance_M = readPing2();
  straight();
  if (Current_Distance_M <= 9)

Let's compare this to driving a car. What you have is:
Look out the windshield, to see how far away anything is.
Stomp the gas.
If there is anything close in front, slam the gear selector in reverse.
Close your eyes for half a second.
Decide which way you really should be going.

Your insurance rates must really suck.

No idea why you want those delays in the first place. That's indeed blocking. You may want to go left or right for some time before turning straight again?

Look at blink without delay, on how to do timing using millis(). That way you call the function time and again but only when it's time to do something it will be done. Other functions can continue to do their jobs in the meantime.

Have a look at how millis() is used to manage timing without blocking in Several things at a time.

...R

Most of the time readPing1() returns void. Only occasionally does it take the time to do a ping and send a real value. You should remember the value with a static variable and return that on the other times.

Also you have a lot of global variables which are only used inside that function. Make them private to the function so you don't accidentally change them elsewhere.

The idea with the static variable worked quite well. Even so the motor stutters. Does someone see room for improvement in the code?

#include <ESP8266WiFi.h>
#include <NewPing.h>

#define Trigger_Sensor 16
#define Echo_Sensor 13

#define MAX_DISTANCE 200

int Motor_Speed = 5;
int Motor_Direction = 0;

NewPing sonar(Trigger_Sensor, Echo_Sensor, MAX_DISTANCE);

unsigned long previousMillis = 0;
unsigned long interval = 40;
int Trigger_State = LOW;

void setup() {
  pinMode(Motor_Speed, OUTPUT);
  pinMode(Motor_Direction, OUTPUT);
}

void loop() {
  if (readPing() >= 5) {
    digitalWrite(Motor_Speed, HIGH);
    digitalWrite(Motor_Direction, HIGH);
  }
  else {
    digitalWrite(Motor_Speed, LOW);
    digitalWrite(Motor_Direction, HIGH);
  }
}

int readPing() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    static unsigned long previousMillis = currentMillis;
    if (Trigger_State = LOW)
      Trigger_State = HIGH;
    else
      Trigger_State = LOW;
    int uS = sonar.ping();
    int cm = uS / US_ROUNDTRIP_CM;
    return cm;
  }
}

if (Trigger_State = LOW) room for improvement right there

the static part was to go with an extra variable inside that function where you can store the returned value of cm, so if you don't get a new distance you can return the old one.

For previousMillis it just doesn't make sense. It's a global variable, and it's value is therefore remembered all time already. It would only make sense to make it static if you would also keep it local to that function.

Thank you all so far for your guidance. I will go with the local variables for now.

I added a second motor and wanted to turn the motor on if the distance to sensor2[right sensor] is bigger than the distance to sensor1[left sensor]. Both are connected to the same Trigger pin. Well it works but the other way round. If I hold my hand close to sensor2 the motor is spinning.

#include <ESP8266WiFi.h>
#include <NewPing.h>

//Left Sensor
#define Trigger_Sensor_L 16
#define Echo_Sensor_L 13

//Right Sensor
#define Trigger_Sensor_R 16
#define Echo_Sensor_R 15

#define MAX_DISTANCE 200

int Motor_Speed = 5;
int Motor_Direction = 0;

NewPing sonar1(Trigger_Sensor_L, Echo_Sensor_L, MAX_DISTANCE);
NewPing sonar2(Trigger_Sensor_R, Echo_Sensor_R, MAX_DISTANCE);

unsigned long interval = 80;
int Trigger_State_S_L_R = LOW;

void setup() {
  pinMode(Motor_Speed, OUTPUT);
  pinMode(Motor_Direction, OUTPUT);
}

void loop() {
  if (readPing2() >= readPing1()) {
    digitalWrite(Motor_Speed, HIGH);
    digitalWrite(Motor_Direction, HIGH);
  }
  else {
    digitalWrite(Motor_Speed, LOW);
    digitalWrite(Motor_Direction, HIGH);
  }
}

int readPing1() {
  unsigned long previousMillis_S_L = 0;
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis_S_L >= interval) {
    static unsigned long previousMillis_S_L = currentMillis;
    if (Trigger_State_S_L_R == LOW)
      Trigger_State_S_L_R = HIGH;
    else
      Trigger_State_S_L_R = LOW;
    int uS = sonar1.ping();
    int cm = uS / US_ROUNDTRIP_CM;
    return cm;
  }
}

int readPing2() {
  unsigned long previousMillis_S_R = 0;
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis_S_R >= interval) {
    static unsigned long previousMillis_S_R = currentMillis;
    if (Trigger_State_S_L_R == LOW)
      Trigger_State_S_L_R = HIGH;
    else
      Trigger_State_S_L_R = LOW;
    int uS = sonar2.ping();
    int cm = uS / US_ROUNDTRIP_CM;
    return cm;
  }
}

You're setting currentMillis in both functions, so after setting it in the first, the second will always be within the interval and not take a measurement. Use two variables for that, just like you did for previousMillis_S_L and previousMillis_S_R.

You may also change

static unsigned long previousMillis_S_R = currentMillis;

to the simpler

previousMillis_S_R = currentMillis;

which works just as well, or with the separate currentMillis variables that'd be someth8ing like

previousMillis_S_R = currentMillis_S_R;

the static unsigned long part will be ignored by the compiler as the variable has been declared already. Do look up what static really means in the context of a function!