Extreme avoidance of delay() and how do analog pins work

I am new to Arudino and have been trying to learn from a) the SparkFun Inventor’s kit and its programming challenges, b) reading the 54 programming pages of this forum, and c) the websites of the work of Majenko and Nick Gammon. So avoiding delay() and state machines have been a way to learn (along with avoidance of (capital “S”) Strings in the IDE). The code below is a simple variation of the “autonomous” robot which is the final project in the Inventors Kit. Links to the SparkFun docs are in comments at the bottom of the code.

I wanted to have the “robot” flash lights when too close to an object, beep a “backup warning” (like a semi-truck!) and indicate its left turn when avoiding obstacles, all the while continuing to operate the motors. So this was an exercise in avoiding delay() and implementing a simple finite-state-machine.

The code works – although I need to fine tune the timing and pre-defined distances for each state. (it really needs an “if running on carpet()” function !!). But I have two questions arising from the experience.

First, there are still two delay() lines in the code. One at the end of loop() to force a wait of 50ms before another reading of the distance sensor and one in the getDistance() function to set the delay between the outgoing and return ultrasonic pulses. The first COULD be avoided by a Millis() routine like I have used to time the TURN state. But I am not sure about the second one and whether there is anything to be gained avoiding either of these. Do these two situation show that selective use of delay() is justified?

Second, for this project I have added four LEDs and a buzzer thus approaching the limit of my Uno’s available pins and I used two analog pins for digital purposes. No problem. All works. But when the Arudino is powered up and before the program loads the two LEDs on the analog pins light up. They go off as soon as the program loads (perhaps when set to OUTPUT?). I would be interested in a simple explanation of why they light up with no code loaded (or even a simple blank dummy program loaded).

[code]
/*
  Adapted from SparkFun Inventor’s Kit   Circuit 5C - Autonomous Robot
  Original SparkFun comments etc at bottom of code.
  
  Mods DW june/july 2022 - This revised code is completely free for any use. 
  1. Style changes (mostly as per Nick Gammon)
  2. Created state machine in main Loop and removed blocking delay()s, 
  3. condensed motor driving functions into one function w/extra parameters
  4. added sounds and lights for warning when close to obstacle, backup buzzer, ..
     .. and turn signal, all of which avoid delay() and operate while moving
  5. added an additional state CHECK to allow more turning if still blocked
  6. TBA.... more warnings using blue and green LEDs .......  
     
*/
// **************************** Pin constants
const int RT_A1 = 13;     //control pin A1 on the motor driver for the right motor
const int RT_A2 = 12;     //control pin A2 on the motor driver for the right motor
const int RT_APWM = 11;   //speed control pin on the motor driver for the right motor

const int LF_B1 = 8;      //control pin B1 on the motor driver for the left motor
const int LF_B2 = 9;      //control pin B2 on the motor driver for the left motor
const int LF_BPWM = 10;   //speed control pin on the motor driver for the left motor

const int TRIG_PIN   = 6; //pins on the distance sensor
const int ECHO_PIN   = 5;

const int YELLOW_LED_PIN = 4;
const int RED_LED_PIN    = 2;
const int BLUE_LED_PIN   = A4;   //future use
const int GREEN_LED_PIN  = A5;   //future use

const int SWITCH_PIN = 7; //switch to turn the robot on and off
const int BUZZER_PIN = 3;

// **************************** robot behaviour constants
const byte STOP = 0;           //To clarify code when motor is stopped
const byte DRIVE_SPEED =  100; //motor speed to drive forwards
const int BACKUP_SPEED = -100; //motor speed to drive backwards
const byte TURN_SPEED  =   75; //speed for turning
const byte ALERT_ON_DISTANCE   = 10;  //distance to stop when close to obstacle
const byte ALERT_OFF_DISTANCE  = 15;  //distance to turn off red LED alert
const byte START_TURN_DISTANCE = 20;  //distance to stop backing up and turn
const int  WARNING_INTERVAL    = 500;  //flash/buzzer interval

// **************************** robot behaviour variables
float distance       = 0;     //stores distance to obstacle measured by the sensor
int   turnTime       = 4000;  //initial time to turn to avoid obstacle
float currentMillis  = 0;     //used to time the turn and avoid using delay()
float previousMillis = 0;     //used to time the turn and avoid using delay()

bool firstTime   = true;      //prevents redundant calls to motor functions
bool redWarning  = false;     //flag to turn on red alert of close obstacle 
bool buzzWarning = false;     //flag to buzz when backing up
bool turnWarning = false;     //flag to turn on left turn indicator

// State Machine states put in enum for clarity of code
enum testState {FORWARD, BACKUP, TURN, CHECK} state;  

void setup()
{
  state = FORWARD;             //initialize first state at startup
  
  pinMode(TRIG_PIN, OUTPUT);   //pin to send ultrasonic pulses out from the distance sensor
  pinMode(ECHO_PIN, INPUT);    //pin to sense when the pulses reflect back to the distance sensor

  pinMode(SWITCH_PIN, INPUT_PULLUP);  //switch to turn robot on/off

  //set the motor control and other pins as outputs
  pinMode(RT_A1, OUTPUT);
  pinMode(RT_A2, OUTPUT);
  pinMode(RT_APWM, OUTPUT);

  pinMode(LF_B1, OUTPUT);
  pinMode(LF_B2, OUTPUT);
  pinMode(LF_BPWM, OUTPUT);
  
  pinMode(BLUE_LED_PIN, OUTPUT);      //for future use
  pinMode(GREEN_LED_PIN, OUTPUT);     //for future use
  pinMode(RED_LED_PIN, OUTPUT);       //flashes when obstacle too close
  pinMode(YELLOW_LED_PIN, OUTPUT);    //acts as left turn flashing signal

  pinMode(BUZZER_PIN, OUTPUT);        //for backup warning beep
}

void loop()
{
  distance = getDistance();           //get distance of any obstacle ahead

  if (digitalRead(SWITCH_PIN) == LOW) //if the switch is on
  { 
    switch (state)
    {
      case FORWARD:
        if (distance > ALERT_ON_DISTANCE)    //no close object so drive ahead
        {
         if (firstTime)                      //flag from initialization/previous state - start motors only once
          {
            motor(DRIVE_SPEED, RT_A1, RT_A2, RT_APWM);
            motor(DRIVE_SPEED, LF_B1, LF_B2, LF_BPWM);
            firstTime = false;
          }
        }
        else                                  //a close object is detected - stop motors
        {
          motor(STOP, RT_A1, RT_A2, RT_APWM);
          motor(STOP, LF_B1, LF_B2, LF_BPWM); 
          redWarning = true;                  //activate red flasher function (at end of switch(state))
          firstTime = true;                   //flag to run BACKUP state motor only once
          state = BACKUP;                     //switch to state BACKUP
        }
        break;
  
      case BACKUP:
        buzzWarning = true;                   //activate buzzer function (at end of switch(state))
        if (firstTime)                        //flag passed from FORWARD case to reverse motors only once
        {
          motor(BACKUP_SPEED, RT_A1, RT_A2, RT_APWM); //NB: BACKUP_SPEED is a negative number
          motor(BACKUP_SPEED, LF_B1, LF_B2, LF_BPWM);
          firstTime = false;
        }
    
        if (distance >= ALERT_OFF_DISTANCE)   //turn red flasher off if backed far enough away
          redWarning = false;

        if (distance >= START_TURN_DISTANCE)  //turn buzzer off if far enough away and stop motors..
        {                                     // .. then change to TURN state
          buzzWarning = false;
          
          motor(STOP, RT_A1, RT_A2, RT_APWM);
          motor(STOP, LF_B1, LF_B2, LF_BPWM);
          
          turnWarning = true;                 //turn on left turn indicator (at end of switch(state))
          firstTime = true;                   //flag to run TURN state motor only once
          turnTime = 4000;                    //set time for first turn attempt
          previousMillis = millis();          //start time for turn delay in TURN state
          state = TURN;
        }
        break;
  
      case TURN:
        currentMillis = millis();                       //start turn timer
        if (currentMillis - previousMillis <= turnTime) //still ticking
        {
          if (firstTime)                                //just start the motors once
          {
            motor( TURN_SPEED, RT_A1, RT_A2, RT_APWM);
            motor(-TURN_SPEED, LF_B1, LF_B2, LF_BPWM);
            firstTime = false;
          }
        }
        else                                    //time up
        {
          turnWarning = false;                  //turn off the direction indicator and stop motors .. 
          motor(STOP, RT_A1, RT_A2, RT_APWM);
          motor(STOP, LF_B1, LF_B2, LF_BPWM);
          state = CHECK;                        // .. and change state
        }
        break;

      case CHECK:
        if (distance < ALERT_ON_DISTANCE)     //if an(other) object is still detected after turning
        {
          turnWarning = true;                 //reset all the start conditions (as from BACKUP) ..
          firstTime = true;
          turnTime = 2000;                    //.. but with shorter turn time
          previousMillis = millis();
          state = TURN;                       //TURN and then CHECK again
        }
        else                                  //no obstacle after turn
        {
          firstTime = true;                   //reset starting conditions ..
          turnWarning = false;                // .. turn off turn signal .. 
          state = FORWARD;                    // .. and go forward again
        }
        break;
    } //  END of switch (state)

  // ********* These 3 functions operate the flashers and buzzer while robot is moving
  
    if (redWarning)         //flash the red "too close" warning LED
      redLEDWarning();
    else
      digitalWrite(RED_LED_PIN, LOW);
      
    if (buzzWarning)        //backup warning buzzer
      backupBuzzer();
    else
      noTone(BUZZER_PIN);
      
    if (turnWarning)        //turn-signal light
      turnSignal(); 
    else
      digitalWrite(YELLOW_LED_PIN, LOW);
  } //END of if switch is on
  else                              //if the switch is off then stop the motors
  {                        
    motor(STOP, RT_A1, RT_A2, RT_APWM);
    motor(STOP, LF_B1, LF_B2, LF_BPWM);
  }

  delay(50);                        //wait 50 milliseconds between readings
}   //  END of loop()

// ******************************* FUNCTIONS 

void redLEDWarning() 
{
  int interval = WARNING_INTERVAL;
  float CurrMillis = millis();
  static float PrevMillis = 0;
  if (CurrMillis - PrevMillis >= interval)
  {  
    if (digitalRead(RED_LED_PIN) == LOW) 
      digitalWrite(RED_LED_PIN, HIGH);
    else
      digitalWrite(RED_LED_PIN, LOW);       
    PrevMillis = CurrMillis;
  }
}

void backupBuzzer()
{
  int interval = WARNING_INTERVAL;
  float CurrMillis = millis();
  static float PrevMillis = 0;
  if (CurrMillis - PrevMillis >= interval)
  {  
    tone(BUZZER_PIN, 700, 100);
    PrevMillis = CurrMillis;
  }      
}

void turnSignal()
{
  int interval = WARNING_INTERVAL;
  float CurrMillis = millis();
  static float PrevMillis = 0;
  if (CurrMillis - PrevMillis >= interval)
  {
    if (digitalRead(YELLOW_LED_PIN) == LOW) 
      digitalWrite(YELLOW_LED_PIN, HIGH);
    else
      digitalWrite(YELLOW_LED_PIN, LOW);
     
    PrevMillis = CurrMillis; 
  }
}

void motor(int motorSpeed, int pin1, int pin2, int pwm) //function for driving the motors
{
  if (motorSpeed > 0)             //if the motor should drive forward (positive speed)
  {
    digitalWrite(pin1, HIGH);     //set pin 1 to high
    digitalWrite(pin2, LOW);      //set pin 2 to low
  }
  else if (motorSpeed < 0)        //if the motor should drive backward (negative speed)
  {
    digitalWrite(pin1, LOW);      //set pin 1 to low
    digitalWrite(pin2, HIGH);     //set pin 2 to high
  }
  else                            //the motor speed = 0, so it should stop
  {
    digitalWrite(pin1, LOW);      //set pin 1 to low
    digitalWrite(pin2, LOW);      //set pin 2 to low
  }
  analogWrite(pwm, abs(motorSpeed));  //motor direction is set, drive it at the passed speed
}

float getDistance()                       //returns the distance from the sensor
{
  float echoTime;                         //stores time for a ping to bounce off an object
  float calculatedDistance;               //stores the distance calculated from the echo time
  
  digitalWrite(TRIG_PIN, HIGH);           //send out an ultrasonic pulse that's 10ms long
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  echoTime = pulseIn(ECHO_PIN, HIGH);     //how long it took for the pulse to bounce back to the sensor

  calculatedDistance = echoTime / 148.0;  //distance of the object that reflected the pulse ..
                                          // ..(half the bounce time multiplied by the speed of sound)
  return calculatedDistance;              //send back the distance that was calculated
}


/*  This robot will drive around on its own and react to obstacles by backing up and turning to a new direction.
  This sketch was adapted from one of the activities in the SparkFun Guide to Arduino.
  Check out the rest of the book at
  https://www.sparkfun.com/products/14326

  This sketch was written by SparkFun Electronics, with lots of help from the Arduino community.
  This code is completely free for any use.

  View circuit diagram and instructions at: https://learn.sparkfun.com/tutorials/sparkfun-inventors-kit-experiment-guide---v41
  Download drawings and code at: https://github.com/sparkfun/SIK-Guide-Code

 */ 
[/code]
1 Like

That one should be easy to remove and could be worthwhile doing. Use the same techniques used to flash LEDs elsewhere in the code.

One option could be to build the led-flashing-code-technique into the getDistance() function. If the function is called before 50ms since the last reading, just return the previous reading again (make calculatedDistance static in order to do that).

I can't see another use of delay(). I can see a use of delayMicroseconds(10) which is used to generate the trigger pulse for the distance sensor. Removing that would be very difficult to achieve another way and definitely not worth it in my view.

We will need to see your schematic to help with that question. And perhaps some bright, clear close-up photos that let us see where each wire and component is connected.

Agree with @PaulRB ... although 50ms probably isn't going to cause you any issue, probably better to avoid delay() altogether if you can, and better to get in the habit of using millis(). Also agree that you'd be better off leaving the 10us delay alone.

One problem with the standard approach using pulseIn() is that it blocks till the echo is received or till it times out (default 1 second). The further the object is away from the sensor, the longer it takes.

  1. You can limit the timeout if that suites your needs; it means that the detection range will be shorter.
  2. You can use NewPing - Arduino Reference which is (or can be used in a way that is) non-blocking.

The HC-SR04 ultrasonic sensor has a maximum range of about 5 meters. That is a round-trip (10 m) speed of sound time of 29,154.52 microseconds so setting the timeout (third argument) to 30000 will limit the damage.

echoTime = pulseIn(ECHO_PIN, HIGH, 30000); //how long it took for the pulse to bounce back to the sensor

Thanks to everyone for the ideas and leads about the various delay() issues. I will try some or all of them as part of learning more.

@PaulRB I will post again on the analog pin issue once I have had a chance to reproduce it with a much simpler setup - one pin and one LED/resistor. That will make the issue clearer.

1 Like

@PaulRB to test my question about the use of analog pins in digital mode I reproduced the simple "blink" example from the IDE menu. But I used analog pin A5 instead of the built-in LED or an alternative digital pin (and changed the code!). I had a 330k resistor on the ground side of the Led and simply connected the positive side to A5.

It works and blinks just as it should once the code loads. But as before the led lights up when the power is connected to the Arudino and then goes off when the code loads - maybe at the point when the pin is set to OUTPUT. Then loading an empty program results in the led lighting up again.

The voltage on the pin prior to loading code is the expected 5v.

I am simply curious about the workings of analog pins. Is the 5v part of how they respond to voltages from sensors when used as analog? Is accessing 5v from an unused analog pin useful for other purposes? Etc.

Help or a reference would be appreciated.

It's NOT ALWYAS BAD to use delay() as long as your program doesn't need to be doing anything at that particular time. And you can't read a sensor or button, etc., during the delay.

It seems like you've got a lot of things happening "at the same time" so you probably don't want to pause everything for a delay...

If you know the processor doesn't need to do anything for a few milliseconds, delay() is OK. Or if you've got some kind of timing application where it just has to sit there and do nothing for hours, a very-long delay() is OK. But, if there are any button, or knobs, or user-inputs, you probably don't want to ignore those for hours...

I'm not sure what's going-on with the LEDs... At power-up the I/O pins should initialize as very-high impedance inputs so they can't source or sink current and they can't power an LED.

When you set them to output I assume (I don't remember but somebody here will know that) they are set low which means you would light an LED that's wired to +5V. If my assumption is wrong and they default to high it would light an LED wired to ground.

And if the input pull-up is enabled for that pin, it can light an LED dimly, depending on which way it's wired.

....

@PaulMB
Ok, I attach a photo of the extremely simple test referred to in my last response. It shows the same LED behavior as in my original more complex setup. This test works as it should with the basic blink example code but using pin A5 set to OUTPUT instead of using a digital pin.

My previous questions on this refer to the fact that when using the analog pin the LED lights up from the moment that the board is powered until the code loads. I want to understand more about how the analog pins work and why they have 5v available until set as a digital pin.

Should I have posted this separately to a more hardware related section? If so, I apologize. And thank-you to you and everyone who answered my other issues in this post about delay().

It's very odd. I even checked the schematic for the sparkfun redboard to see if there was a pull-up resistor on the A5 pin which might cause the led to go low at startup, but, as with regular Uno, there doesn't seem to be one.

Does the same thing happen with every/any other digital or analog pin?

@PaulRB
This only happens on any analog pin. For what it's worth the voltage is 4.79v when using the battery pack and much nearer 5v at 4.9x when just using USB power. I put that down to slightly depleted batteries.

I am just guessing, but could it be that this 5v is the reference voltage against which incoming sensor voltages are compared. I have seen code somewhere that allows that reference voltage to be changed. I could try to find that and see if the voltage before code loads changes.

That at least would explain why the voltage disappears when the program sets the pin to output.

It seems odd though that the various documentation on using analog pins for digital I/O don't mention this behavior.

Thx for your thoughts.

A pin is by default a high impedance INPUT after a reset which means that they can't provide significant current (to light a LED); the driver stage (marked in red in below image) to drive the pin is disabled and the input of the driver stage is LOW.

If you switch the pin to output, the driver stage is enabled and the input of the driver stage is passed to the pin. Hence your pin goes LOW.

Your board is a Sparkfun "RedBoard QWIIC", which has 2.2k pullup resistors on A4 and A5 so that they are immediately usable for connecting I2C devices via the QWIIC connector. See the schematic at https://cdn.sparkfun.com/assets/c/5/7/e/f/RedBoard-Qwiic.pdf

@westfw thanks for the solution to my confusion, and for the link to the schematic which might help me avoid other naive questions in future.

Ah, my bad. Earlier I posted a link to an older RedBoard schematic without QWIIC connector, and there were no pull-up resistors on that version. But now I notice the QWIIC connector in the photos. :man_facepalming:t2:

However...

only A4 & A5 should be affected, the other analog pins should not light your leds at startup. Even on A4 & A5, the LEDs should only glow dimly because the pull-up resistors are 2K2.

You can stop the LEDs attached to A4 & A5 from lighting up at startup by breaking links JP12 & JP14. If you later need to use the QWIIC connector, you can remake JP12 & JP14 with small blobs of solder.

@PaulRB Yes the leds only light on A4 and A5. My error.
They seem bright to me but dimness is somewhat subjective.
I don't need to avoid the behavior, it just triggered my curiosity about how analog pins work. Thanks to everyone here my curiosity has been answered.
It seems to me that the lesson learnt is that SparkFun's "compatible" with Arudino Uno doesn't mean "the same as" ..... I shall beware in future.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.