micros() overflow

Issue #71 resolved
Tin Nguyen created an issue

Hi Tim

Thank you for your library. It’s really helpful for me.

In your source code, I found sometimes you use

if (micros() > _max_time)

and

_max_time = micros() + _maxEchoTime + MAX_SENSOR_DELAY

so I’m wondering what if when assigning _max_time, micros() was in high value, and after that, it overflown, so when it did the comparison, micros()will be smaller than _max_time and it won’t exit the loop until micros() > _max_time or echo return LOW. I’m having a bug that make it suddenly stop working in some certain time and than start working again after a certain time. I’m trying to troubleshoot this problem and not sure if it’s because of this bug.

Thanks again.

Comments (14)

  1. Tim Eckel repo owner

    I’ve run projects with multi-year run times without a problem, and micros() rolls over every 70 minutes. But let me break it down to show how NewPing works…

    Lets say micros() is 4,294,967,250 _maxEchoTime is 14,250 and MAX_SENSOR_DELAY is 5,800

    4,294,967,250 + 14,250 + 5,800 = 4,294,987,300

    But at 4,294,967,296 it rolls over to 0, so 4,294,987,300 is now 20,005. So, _max_time is set to 20,005. So when micros() rolls over to 0, it’s less than _max_time, for only 20 milliseconds.

    Basically, if it happened to hit an extreme edge case, the worst that could happen is report a NO_ECHO (zero value), not hang. Again, I’ve run the same projects for multiple years without every a hang.

    It is possible (however, that your code is causing an overflow or hang situation, unrelated to the NewPing library. You could post your full code if you’d like.

  2. Tin Nguyen reporter

    My code is very simple. I just wrote a function to return the Min ping from 2 sensors using ping_median().

    unsigned int GetMicrosNewPing()
    {
      unsigned int microsecond = MAX_MICROS;
      unsigned long temp;
      for (byte i = 0; i < SENSOR_NUM; i++)
      {
        temp = Sonar[i].ping_median(ITER);
        if (temp > 0)
        {
          microsecond = min(microsecond, temp);
          // Serial.println("Micros " + String(i) + ": " + String(temp));
        }
      };
      return constrain(microsecond, MIN_MICROS, MAX_MICROS);
    }
    

    What I’m wondering about is in this section, if it just somehow hit the exact moment when micros() = 4,294,947,245 then max_time = 4,294,947,245 + 14,250 + 5,800 = 4,294,967,295 and after that micros start rollover back to 0.

    #else
        #if DO_BITWISE == true
            while (*_echoInput & _echoBit)                // Wait for the ping echo.
        #else
            while (digitalRead(_echoPin))                 // Wait for the ping echo.
        #endif
                if (micros() > _max_time) return NO_ECHO; // Stop the loop and return NO_ECHO (false) if we're beyond the set maximum distance.
    #endif
    

    So if somehow the _echoBit got stuck in 1 then it wouldn’t exit the loop until 70 minutes later or wouldn’t exit at all. It might be extremely rare though.

    In my case, I was not so sure about what’s the cause. So I made another device to test it out. And it just never happened again, only happened twice. Maybe it’s the hardware issues also.

    So just in case, I adjusted your code a little, by adding 1 more variant _at_time, and subtract it to get duration before comparing.

    #else
      if (*_echoInput & _echoBit)
        return false;                               // Previous ping hasn't finished, abort.
      _max_time = _maxEchoTime + MAX_SENSOR_DELAY;  // Maximum time we'll wait for ping to start (most sensors are <450uS, the SRF06 can take up to 34,300uS!)
      _at_time = micros();
      while (!(*_echoInput & _echoBit))  // Wait for ping to start.
        if (micros() - _at_time > _max_time)
          return false;                               // Took too long to start, abort.
    #endif
    

    It might affect the performance a little bit, but let me see how it behaves.

    But thank you anyways.

  3. Tim Eckel repo owner

    That’s not how the code is written, as if (micros() > _max_time) means that it could never be in a situation where it would wait 70 minutes. As stated, if you did happen to get extremely lucky and hit an edge case (which would be extremely rare), the most that could happen is a NO_ECHO would be returned, which would be filtered out by the ping_median anyway.

    The NewPing code has been used by hundreds of thousands of people for many years. It’s also commonly used in projects which run for years. If there was a problem with the library like this, it would have surfaced a very long time ago by many people. Instead of trying to find something wrong with the library, I’d suggest stating what you’re doing and what is happening instead, as it’s not the library.

  4. Tim Eckel repo owner

    If didn’t include the rest of your code where the problem could exist, but if your code is written in blocking mode (not event driven), you could make the following change which will prevent a possible rollover issue:

    unsigned int GetMicrosNewPing() {
      unsigned int microsecond = MAX_MICROS;
      unsigned long temp;
    
      extern unsigned long timer0_overflow_count;
      if (micros() > 4000000000) timer0_overflow_count = 0;
    
      for (byte i = 0; i < SENSOR_NUM; i++) {
        temp = Sonar[i].ping_median(ITER);
        if (temp > 0) {
          microsecond = min(microsecond, temp);
          // Serial.println("Micros " + String(i) + ": " + String(temp));
        }
      }
      return constrain(microsecond, MIN_MICROS, MAX_MICROS);
    }
    

    But again, it would be nice to see the entire program and for you to report what’s happening instead of taking a deep-dive into library code.

  5. Tin Nguyen reporter

    My sketch looks like this

    #include "NewPing.h"
    
    // DEFINE PIN
    const byte trigPin_1 = 4;
    const byte echoPin_1 = 2;
    const byte trigPin_2 = 8;
    const byte echoPin_2 = 7;
    const byte sirenPin = 6;
    const byte flashPin = 5;
    const byte flashThresholdPin = A0;
    const byte sirenMaxVolumePin = A1;
    
    // DEFINE CONSTANT
    const byte SENSOR_NUM = 2;
    const byte ITER = 3;
    const unsigned int MAX_DURATION = 800;        // ms
    const unsigned int MIN_DURATION = 40;         // ms
    const unsigned int MAX_VOLUME_DURATION = 400; // ms
    
    const unsigned int SPEED_OF_SOUND = 343;    // um/us
    const unsigned long MAX_DISTANCE = 5000000; // um
    const unsigned long MIN_DISTANCE = 100000;  // um
    const unsigned int MAX_MICROS = MAX_DISTANCE * 2 / SPEED_OF_SOUND;
    const unsigned int MIN_MICROS = MIN_DURATION * 2 / SPEED_OF_SOUND;
    
    // DEFINE GLOBAL VARIABLES
    bool flashState;
    unsigned long flashMilis;
    byte lastVolume;
    
    NewPing Sonar[SENSOR_NUM] = {      // Sensor object array.
        NewPing(trigPin_1, echoPin_1), // Each sensor's trigger pin, echo pin
        NewPing(trigPin_2, echoPin_2)};
    
    void setup()
    {
      pinMode(trigPin_1, OUTPUT);
      pinMode(echoPin_1, INPUT);
      pinMode(trigPin_2, OUTPUT);
      pinMode(echoPin_2, INPUT);
      pinMode(sirenPin, OUTPUT);
      pinMode(flashPin, OUTPUT);
      flashState = 0;
      flashMilis = 0;
      digitalWrite(flashPin, LOW);
      digitalWrite(sirenPin, LOW);
    
      Serial.begin(9600);
    }
    
    void loop()
    {
      byte sirenMaxVolume;
      byte realVolume;
      unsigned int flashThreshold;
      unsigned int sirenThreshold;
      unsigned int flashDuration;
      unsigned int realMicros;
    
      // Read input
      flashThreshold = map(analogRead(flashThresholdPin), 0, 1023, MIN_MICROS, MAX_MICROS);
      sirenThreshold = flashThreshold >> 1;
      sirenMaxVolume = map(analogRead(sirenMaxVolumePin), 0, 1023, 0, 255);
      // Serial.println("Siren: " + String(sirenMaxVolume));
    
      /*
      Serial.println("flashThreshold: " + String(flashThreshold));
      Serial.println("sirenThreshold: " + String(sirenThreshold));
      Serial.println("sirenMaxVolume: " + String(sirenMaxVolume));
      */
    
      // Mearsure micros
      realMicros = GetMicrosNewPing();
      // Serial.println("realMicros: " + String(realMicros));
    
      // FLASHING
      if (realMicros < flashThreshold)
      {
        flashDuration = map(constrain(realMicros, MIN_MICROS, flashThreshold), MIN_MICROS, flashThreshold, MIN_DURATION, MAX_DURATION);
        // Serial.println("flashDuration: " + String(flashDuration));
    
        Flashing(flashDuration);
      }
      else
      {
        flashState = LOW;
        digitalWrite(flashPin, LOW);
      }
    
      // SIREN
      if (realMicros < sirenThreshold)
      {
        realVolume = map(constrain(realMicros, MIN_MICROS, sirenThreshold), sirenThreshold, MIN_MICROS, 0, sirenMaxVolume);
        // Serial.println("realVolume: " + String(realVolume));
    
        Sirening(realVolume, sirenMaxVolume);
      }
      else
      {
        // lastVolume = 0;
        digitalWrite(sirenPin, LOW);
      }
    }
    
    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    unsigned int GetMicrosNewPing()
    {
      unsigned int micros = MAX_MICROS;
      unsigned long temp;
      for (byte i = 0; i < SENSOR_NUM; i++)
      {
        temp = Sonar[i].ping_median(ITER);
        if (temp > 0)
        {
          micros = min(micros, temp);
          // Serial.println("Micros " + String(i) + ": " + String(temp));
        }
      };
      return constrain(micros, MIN_MICROS, MAX_MICROS);
    }
    
    void Flashing(unsigned int duration)
    {
      unsigned long stateTime = millis() - flashMilis;
    
      if (stateTime > duration)
      {
        flashMilis = millis();
        // Switch state
        if (flashState == HIGH)
          flashState = LOW;
        else
          flashState = HIGH;
        digitalWrite(flashPin, flashState);
      }
    }
    
    void Sirening(byte sirenVolume, byte sirenMaxVolume)
    {
    
      if (lastVolume == 0 && sirenVolume > 0)
      {
        analogWrite(sirenPin, sirenMaxVolume);
        delay(MAX_VOLUME_DURATION);
      }
    
      lastVolume = sirenVolume;
      analogWrite(sirenPin, sirenVolume);
    }
    

    Here is the simple diagram

    1 knob is used to control the range of the sensors, the other is used to control the volume of the buzzer.

    When I modified your library, everything is working fine as. Just for testing purpose though.

    I will temporary call it PingPing.h

    // ---------------------------------------------------------------------------
    // NewPing Library - v1.9.4 - 01/26/2022
    //
    // AUTHOR/LICENSE:
    // Created by Tim Eckel - eckel.tim@gmail.com
    // Copyright 2022 License: Forks and derivitive works are NOT permitted without
    // permission. Permission is only granted to use as-is for private and
    // commercial use. If you wish to contribute, make changes, or enhancements,
    // please create a pull request. I get a lot of support issues from this
    // library, most of which comes from old versions, buggy forks or sites without
    // proper documentation, just trying to wrangle this project together.
    //
    // LINKS:
    // Project home: https://bitbucket.org/teckel12/arduino-new-ping/wiki/Home
    // Support: https://bitbucket.org/teckel12/arduino-new-ping/issues?status=new&status=open
    //
    // DISCLAIMER:
    // This software is furnished "as is", without technical support, and with no 
    // warranty, express or implied, as to its usefulness for any purpose.
    //
    // BACKGROUND:
    // When I first received an ultrasonic sensor I was not happy with how poorly
    // it worked. Quickly I realized the problem wasn't the sensor, it was the
    // available ping and ultrasonic libraries causing the problem. The NewPing
    // library totally fixes these problems, adds many new features, and breaths
    // new life into these very affordable distance sensors. 
    //
    // FEATURES:
    // * Works with many different ultrasonic sensors: SR04, SRF05, SRF06, DYP-ME007, URM37 & Parallax PING)))™.
    // * Compatible with the entire Arduino line-up (and clones), Teensy family (including $19.80 96Mhz 32 bit Teensy 3.2) and non-AVR microcontrollers.
    // * Interface with all but the SRF06 sensor using only one Arduino pin.
    // * Doesn't lag for a full second if no ping/echo is received.
    // * Ping sensors consistently and reliably at up to 30 times per second.
    // * Timer interrupt method for event-driven sketches.
    // * Built-in digital filter method ping_median() for easy error correction.
    // * Uses port registers for a faster pin interface and smaller code size.
    // * Allows you to set a maximum distance where pings beyond that distance are read as no ping "clear".
    // * Ease of using multiple sensors (example sketch with 15 sensors).
    // * More accurate distance calculation (cm, inches & uS).
    // * Doesn't use pulseIn, which is slow and gives incorrect results with some ultrasonic sensor models.
    // * Actively developed with features being added and bugs/issues addressed.
    //
    // CONSTRUCTOR:
    //   NewPing sonar(trigger_pin, echo_pin [, max_cm_distance])
    //     trigger_pin & echo_pin - Arduino pins connected to sensor trigger and echo.
    //       NOTE: To use the same Arduino pin for trigger and echo, specify the same pin for both values.
    //     max_cm_distance - [Optional] Maximum distance you wish to sense. Default=500cm.
    //
    // METHODS:
    //   sonar.ping([max_cm_distance]) - Send a ping and get the echo time (in microseconds) as a result. [max_cm_distance] allows you to optionally set a new max distance. 
    //   sonar.ping_in([max_cm_distance]) - Send a ping and get the distance in whole inches. [max_cm_distance] allows you to optionally set a new max distance.
    //   sonar.ping_cm([max_cm_distance]) - Send a ping and get the distance in whole centimeters. [max_cm_distance] allows you to optionally set a new max distance.
    //   sonar.ping_median(iterations [, max_cm_distance]) - Do multiple pings (default=5), discard out of range pings and return median in microseconds. [max_cm_distance] allows you to optionally set a new max distance.
    //   NewPing::convert_in(echoTime) - Convert echoTime from microseconds to inches (rounds to nearest inch).
    //   NewPing::convert_cm(echoTime) - Convert echoTime from microseconds to centimeters (rounds to nearest cm).
    //   sonar.ping_timer(function [, max_cm_distance]) - Send a ping and call function to test if ping is complete. [max_cm_distance] allows you to optionally set a new max distance.
    //   sonar.check_timer() - Check if ping has returned within the set distance limit.
    //   NewPing::timer_us(frequency, function) - Call function every frequency microseconds.
    //   NewPing::timer_ms(frequency, function) - Call function every frequency milliseconds.
    //   NewPing::timer_stop() - Stop the timer.
    //
    // HISTORY:
    // 01/26/2022 v1.9.4 - Added esp32 to the compatible architectures (note:
    //   non-timer methods only)
    // 01/20/2022 v1.9.3 - Default to disable timer methods for non-AVR
    //   microcontrollers and on the ATmega4809 used in the Nano Every.
    // 07/15/2018 v1.9.1 - Added support for ATtiny441 and ATtiny841
    //   microcontrollers.
    // 12/09/2017 v1.9.0 - Added support for the ARM-based Particle devices. If
    //   other ARM-based microcontrollers adopt a similar timer method that the
    //   Particle and Teensy 3.x share, support for other ARM-based microcontrollers
    //   could be possible. Attempt to add to Arduino library manager. License
    //   changed.
    // 07/30/2016 v1.8 - Added support for non-AVR microcontrollers. For non-AVR
    //   microcontrollers, advanced ping_timer() timer methods are disabled due to
    //   inconsistencies or no support at all between platforms. However, standard
    //   ping methods are all supported. Added new optional variable to ping(),
    //   ping_in(), ping_cm(), ping_median(), and ping_timer() methods which allows
    //   you to set a new maximum distance for each ping. Added support for the
    //   ATmega16, ATmega32 and ATmega8535 microcontrollers. Changed convert_cm()
    //   and convert_in() methods to static members. You can now call them without
    //   an object. For example: cm = NewPing::convert_cm(distance);
    //
    // 09/29/2015 v1.7 - Removed support for the Arduino Due and Zero because
    //   they're both 3.3 volt boards and are not 5 volt tolerant while the HC-SR04
    //   is a 5 volt sensor.  Also, the Due and Zero don't support pin manipulation
    //   compatibility via port registers which can be done (see the Teensy 3.2).
    //
    // 06/17/2014 v1.6 - Corrected delay between pings when using ping_median()
    //   method. Added support for the URM37 sensor (must change URM37_ENABLED from
    //   false to true). Added support for Arduino microcontrollers like the $20
    //   32 bit ARM Cortex-M4 based Teensy 3.2. Added automatic support for the
    //   Atmel ATtiny family of microcontrollers. Added timer support for the
    //   ATmega8 microcontroller. Rounding disabled by default, reduces compiled
    //   code size (can be turned on with ROUNDING_ENABLED switch). Added
    //   TIMER_ENABLED switch to get around compile-time "__vector_7" errors when
    //   using the Tone library, or you can use the toneAC, NewTone or
    //   TimerFreeTone libraries: https://bitbucket.org/teckel12/arduino-toneac/
    //   Other speed and compiled size optimizations.
    //
    // 08/15/2012 v1.5 - Added ping_median() method which does a user specified
    //   number of pings (default=5) and returns the median ping in microseconds
    //   (out of range pings ignored). This is a very effective digital filter.
    //   Optimized for smaller compiled size (even smaller than sketches that
    //   don't use a library).
    //
    // 07/14/2012 v1.4 - Added support for the Parallax PING)))� sensor. Interface
    //   with all but the SRF06 sensor using only one Arduino pin. You can also
    //   interface with the SRF06 using one pin if you install a 0.1uf capacitor
    //   on the trigger and echo pins of the sensor then tie the trigger pin to
    //   the Arduino pin (doesn't work with Teensy). To use the same Arduino pin
    //   for trigger and echo, specify the same pin for both values. Various bug
    //   fixes.
    //
    // 06/08/2012 v1.3 - Big feature addition, event-driven ping! Uses Timer2
    //   interrupt, so be mindful of PWM or timing conflicts messing with Timer2
    //   may cause (namely PWM on pins 3 & 11 on Arduino, PWM on pins 9 and 10 on
    //   Mega, and Tone library). Simple to use timer interrupt functions you can
    //   use in your sketches totally unrelated to ultrasonic sensors (don't use if
    //   you're also using NewPing's ping_timer because both use Timer2 interrupts).
    //   Loop counting ping method deleted in favor of timing ping method after
    //   inconsistent results kept surfacing with the loop timing ping method.
    //   Conversion to cm and inches now rounds to the nearest cm or inch. Code
    //   optimized to save program space and fixed a couple minor bugs here and
    //   there. Many new comments added as well as line spacing to group code
    //   sections for better source readability.
    //
    // 05/25/2012 v1.2 - Lots of code clean-up thanks to Arduino Forum members.
    //   Rebuilt the ping timing code from scratch, ditched the pulseIn code as it
    //   doesn't give correct results (at least with ping sensors). The NewPing
    //   library is now VERY accurate and the code was simplified as a bonus.
    //   Smaller and faster code as well. Fixed some issues with very close ping
    //   results when converting to inches. All functions now return 0 only when
    //   there's no ping echo (out of range) and a positive value for a successful
    //   ping. This can effectively be used to detect if something is out of range
    //   or in-range and at what distance. Now compatible with Arduino 0023.
    //
    // 05/16/2012 v1.1 - Changed all I/O functions to use low-level port registers
    //   for ultra-fast and lean code (saves from 174 to 394 bytes). Tested on both
    //   the Arduino Uno and Teensy 2.0 but should work on all Arduino-based
    //   platforms because it calls standard functions to retrieve port registers
    //   and bit masks. Also made a couple minor fixes to defines.
    //
    // 05/15/2012 v1.0 - Initial release.
    // ---------------------------------------------------------------------------
    
    #ifndef PingPing_h
    #define PingPing_h
    
    #if defined(ARDUINO) && ARDUINO >= 100
    #include <Arduino.h>
    #else
    #include <WProgram.h>
    #if defined(PARTICLE)
    #include <SparkIntervalTimer.h>
    #else
    #include <pins_arduino.h>
    #endif
    #endif
    
    #if defined(__AVR__)
    #include <avr/io.h>
    #include <avr/interrupt.h>
    #endif
    
    // Shouldn't need to change these values unless you have a specific need to do so.
    #define MAX_SENSOR_DISTANCE 500  // Maximum sensor distance can be as high as 500cm, no reason to wait for ping longer than sound takes to travel this distance and back. Default=500
    #define US_ROUNDTRIP_CM 57       // Microseconds (uS) it takes sound to travel round-trip 1cm (2cm total), uses integer to save compiled code space. Default=57
    #define US_ROUNDTRIP_IN 146      // Microseconds (uS) it takes sound to travel round-trip 1 inch (2 inches total), uses integer to save compiled code space. Defalult=146
    #define ONE_PIN_ENABLED true     // Set to "false" to disable one pin mode which saves around 14-26 bytes of binary size. Default=true
    #define ROUNDING_ENABLED false   // Set to "true" to enable distance rounding which also adds 64 bytes to binary size. Default=false
    #define URM37_ENABLED false      // Set to "true" to enable support for the URM37 sensor in PWM mode. Default=false
    
    // Probably shouldn't change these values unless you really know what you're doing.
    #define NO_ECHO 0                // Value returned if there's no ping echo within the specified MAX_SENSOR_DISTANCE or max_cm_distance. Default=0
    #define MAX_SENSOR_DELAY 5800    // Maximum uS it takes for sensor to start the ping. Default=5800
    #define ECHO_TIMER_FREQ 24       // Frequency to check for a ping echo (every 24uS is about 0.4cm accuracy). Default=24
    #define PING_MEDIAN_DELAY 29000  // Microsecond delay between pings in the ping_median method. Default=29000
    #if URM37_ENABLED == true
    #undef US_ROUNDTRIP_CM
    #undef US_ROUNDTRIP_IN
    #define US_ROUNDTRIP_CM 50   // Every 50uS PWM signal is low indicates 1cm distance. Default=50
    #define US_ROUNDTRIP_IN 127  // If 50uS is 1cm, 1 inch would be 127uS (50 x 2.54 = 127). Default=127
    #endif
    
    // Conversion from uS to distance (round result to nearest cm or inch).
    #define PingPingConvert(echoTime, conversionFactor) (max(((unsigned int)echoTime + conversionFactor / 2) / conversionFactor, (echoTime ? 1 : 0)))
    
    // Detect non-AVR microcontrollers (Teensy 3.x, Arduino DUE, etc.) and don't use port registers or timer interrupts as required.
    #if (defined(__arm__) && (defined(TEENSYDUINO) || defined(PARTICLE)))
    #define PING_OVERHEAD 1
    #define PING_TIMER_OVERHEAD 1
    #define TIMER_ENABLED true
    #define DO_BITWISE true
    #elif defined(__AVR__)
    #define PING_OVERHEAD 5         // Ping overhead in microseconds (uS). Default=5
    #define PING_TIMER_OVERHEAD 13  // Ping timer overhead in microseconds (uS). Default=13
    #define TIMER_ENABLED true
    #define DO_BITWISE true
    #else
    #define PING_OVERHEAD 1
    #define PING_TIMER_OVERHEAD 1
    #define TIMER_ENABLED false
    #define DO_BITWISE false
    #endif
    
    // Disable the timer interrupts when using ATmega128, ATmega4809 and all ATtiny microcontrollers.
    #if defined(__AVR_ATmega128__) || defined(__AVR_ATmega4809__) || defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny441__) || defined(__AVR_ATtiny84__) || defined(__AVR_ATtiny841__) || defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) || defined(__AVR_ATtiny261__) || defined(__AVR_ATtiny461__) || defined(__AVR_ATtiny861__) || defined(__AVR_ATtiny43U__)
    #undef TIMER_ENABLED
    #define TIMER_ENABLED false
    #endif
    
    // Define timers when using ATmega8, ATmega16, ATmega32 and ATmega8535 microcontrollers.
    #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega16__) || defined(__AVR_ATmega32__) || defined(__AVR_ATmega8535__)
    #define OCR2A OCR2
    #define TIMSK2 TIMSK
    #define OCIE2A OCIE2
    #endif
    
    class PingPing {
    public:
      PingPing(uint8_t trigger_pin, uint8_t echo_pin, unsigned int max_cm_distance = MAX_SENSOR_DISTANCE);
      unsigned int ping(unsigned int max_cm_distance = 0);
      unsigned long ping_cm(unsigned int max_cm_distance = 0);
      unsigned long ping_in(unsigned int max_cm_distance = 0);
      unsigned long ping_median(uint8_t it = 5, unsigned int max_cm_distance = 0);
      static unsigned int convert_cm(unsigned int echoTime);
      static unsigned int convert_in(unsigned int echoTime);
    #if TIMER_ENABLED == true
      void ping_timer(void (*userFunc)(void), unsigned int max_cm_distance = 0);
      boolean check_timer();
      unsigned long ping_result;
      static void timer_us(unsigned int frequency, void (*userFunc)(void));
      static void timer_ms(unsigned long frequency, void (*userFunc)(void));
      static void timer_stop();
    #endif
    private:
      boolean ping_trigger();
      void set_max_distance(unsigned int max_cm_distance);
    #if TIMER_ENABLED == true
      boolean ping_trigger_timer(unsigned int trigger_delay);
      boolean ping_wait_timer();
      static void timer_setup();
      static void timer_ms_cntdwn();
    #endif
    #if DO_BITWISE == true
      uint8_t _triggerBit;
      uint8_t _echoBit;
    #if defined(PARTICLE)
    #if !defined(portModeRegister)
    #if defined(STM32F10X_MD)
    #define portModeRegister(port) (&(port->CRL))
    #elif defined(STM32F2XX)
    #define portModeRegister(port) (&(port->MODER))
    #endif
    #endif
      volatile uint32_t *_triggerOutput;
      volatile uint32_t *_echoInput;
      volatile uint32_t *_triggerMode;
    #else
      volatile uint8_t *_triggerOutput;
      volatile uint8_t *_echoInput;
      volatile uint8_t *_triggerMode;
    #endif
    #else
      uint8_t _triggerPin;
      uint8_t _echoPin;
    #endif
      unsigned int _maxEchoTime;
      unsigned long _max_time;
      unsigned long _at_time;
    };
    
    #endif
    

    PingPing.cpp

    // ---------------------------------------------------------------------------
    // Created by Tim Eckel - eckel.tim@gmail.com
    //
    // See NewPing.h for license, purpose, syntax, version history, links, etc.
    // ---------------------------------------------------------------------------
    
    #include "PingPing.h"
    
    // ---------------------------------------------------------------------------
    // PingPing constructor
    // ---------------------------------------------------------------------------
    
    PingPing::PingPing(uint8_t trigger_pin, uint8_t echo_pin, unsigned int max_cm_distance) {
    #if DO_BITWISE == true
      _triggerBit = digitalPinToBitMask(trigger_pin);  // Get the port register bitmask for the trigger pin.
      _echoBit = digitalPinToBitMask(echo_pin);        // Get the port register bitmask for the echo pin.
    
      _triggerOutput = portOutputRegister(digitalPinToPort(trigger_pin));  // Get the output port register for the trigger pin.
      _echoInput = portInputRegister(digitalPinToPort(echo_pin));          // Get the input port register for the echo pin.
    
      _triggerMode = (uint8_t *)portModeRegister(digitalPinToPort(trigger_pin));  // Get the port mode register for the trigger pin.
    #else
      _triggerPin = trigger_pin;
      _echoPin = echo_pin;
    #endif
    
      set_max_distance(max_cm_distance);  // Call function to set the max sensor distance.
    
    #if (defined(__arm__) && (defined(TEENSYDUINO) || defined(PARTICLE))) || DO_BITWISE != true
      pinMode(echo_pin, INPUT);      // Set echo pin to input (on Teensy 3.x (ARM), pins default to disabled, at least one pinMode() is needed for GPIO mode).
      pinMode(trigger_pin, OUTPUT);  // Set trigger pin to output (on Teensy 3.x (ARM), pins default to disabled, at least one pinMode() is needed for GPIO mode).
    #endif
    
    #if defined(ARDUINO_AVR_YUN)
      pinMode(echo_pin, INPUT);  // Set echo pin to input for the Arduino Yun, not sure why it doesn't default this way.
    #endif
    
    #if ONE_PIN_ENABLED != true && DO_BITWISE == true
      *_triggerMode |= _triggerBit;  // Set trigger pin to output.
    #endif
    }
    
    // ---------------------------------------------------------------------------
    // Standard ping methods
    // ---------------------------------------------------------------------------
    
    unsigned int PingPing::ping(unsigned int max_cm_distance) {
      if (max_cm_distance > 0)
        set_max_distance(max_cm_distance);  // Call function to set a new max sensor distance.
    
      if (!ping_trigger())
        return NO_ECHO;  // Trigger a ping, if it returns false, return NO_ECHO to the calling function.
    
    #if URM37_ENABLED == true
    #if DO_BITWISE == true
      while (!(*_echoInput & _echoBit))  // Wait for the ping echo.
    #else
      while (!digitalRead(_echoPin))  // Wait for the ping echo.
    #endif
        if (micros() - _at_time > _max_time)
          return NO_ECHO;  // Stop the loop and return NO_ECHO (false) if we're beyond the set maximum distance.
    #else
    #if DO_BITWISE == true
      while (*_echoInput & _echoBit)  // Wait for the ping echo.
    #else
      while (digitalRead(_echoPin))  // Wait for the ping echo.
    #endif
        if (micros() - _at_time > _max_time)
          return NO_ECHO;            // Stop the loop and return NO_ECHO (false) if we're beyond the set maximum distance.
    #endif
    
      return (micros() - _at_time - (_max_time - _maxEchoTime) - PING_OVERHEAD);  // Calculate ping time, include overhead.
    }
    
    boolean PingPing::ping_trigger() {
    #if DO_BITWISE == true
    #if ONE_PIN_ENABLED == true
      *_triggerMode |= _triggerBit;  // Set trigger pin to output.
    #endif
    
      *_triggerOutput &= ~_triggerBit;  // Set the trigger pin low, should already be low, but this will make sure it is.
      delayMicroseconds(4);             // Wait for pin to go low.
      *_triggerOutput |= _triggerBit;   // Set trigger pin high, this tells the sensor to send out a ping.
      delayMicroseconds(10);            // Wait long enough for the sensor to realize the trigger pin is high. Sensor specs say to wait 10uS.
      *_triggerOutput &= ~_triggerBit;  // Set trigger pin back to low.
    
    #if ONE_PIN_ENABLED == true
      *_triggerMode &= ~_triggerBit;  // Set trigger pin to input (when using one Arduino pin, this is technically setting the echo pin to input as both are tied to the same Arduino pin).
    #endif
    
    #if URM37_ENABLED == true
      if (!(*_echoInput & _echoBit))
        return false;                               // Previous ping hasn't finished, abort.
      _max_time = _maxEchoTime + MAX_SENSOR_DELAY;  // Maximum time we'll wait for ping to start (most sensors are <450uS, the SRF06 can take up to 34,300uS!)
      _at_time = micros();
      while (*_echoInput & _echoBit)  // Wait for ping to start.
        if (micros() - _at_time > _max_time)
          return false;  // Took too long to start, abort.
    #else
      if (*_echoInput & _echoBit)
        return false;                               // Previous ping hasn't finished, abort.
      _max_time = _maxEchoTime + MAX_SENSOR_DELAY;  // Maximum time we'll wait for ping to start (most sensors are <450uS, the SRF06 can take up to 34,300uS!)
      _at_time = micros();
      while (!(*_echoInput & _echoBit))  // Wait for ping to start.
        if (micros() - _at_time > _max_time)
          return false;                               // Took too long to start, abort.
    #endif
    #else
    #if ONE_PIN_ENABLED == true
      pinMode(_triggerPin, OUTPUT);  // Set trigger pin to output.
    #endif
    
      digitalWrite(_triggerPin, LOW);   // Set the trigger pin low, should already be low, but this will make sure it is.
      delayMicroseconds(4);             // Wait for pin to go low.
      digitalWrite(_triggerPin, HIGH);  // Set trigger pin high, this tells the sensor to send out a ping.
      delayMicroseconds(10);            // Wait long enough for the sensor to realize the trigger pin is high. Sensor specs say to wait 10uS.
      digitalWrite(_triggerPin, LOW);   // Set trigger pin back to low.
    
    #if ONE_PIN_ENABLED == true
      pinMode(_triggerPin, INPUT);      // Set trigger pin to input (when using one Arduino pin, this is technically setting the echo pin to input as both are tied to the same Arduino pin).
    #endif
    
    #if URM37_ENABLED == true
      if (!digitalRead(_echoPin))
        return false;                               // Previous ping hasn't finished, abort.
      _max_time = _maxEchoTime + MAX_SENSOR_DELAY;  // Maximum time we'll wait for ping to start (most sensors are <450uS, the SRF06 can take up to 34,300uS!)
      _at_time = micros();
      while (digitalRead(_echoPin))  // Wait for ping to start.
        if (micros() - _at_time > _max_time)
          return false;                                                                                                  // Took too long to start, abort.
    #else
      if (digitalRead(_echoPin))
        return false;                               // Previous ping hasn't finished, abort.
      _max_time = _maxEchoTime + MAX_SENSOR_DELAY;  // Maximum time we'll wait for ping to start (most sensors are <450uS, the SRF06 can take up to 34,300uS!)
      _at_time = micros();
      while (!digitalRead(_echoPin))  // Wait for ping to start.
        if (micros() - _at_time > _max_time)
          return false;  // Took too long to start, abort.
    #endif
    #endif
    
      _max_time = _maxEchoTime;  // Ping started, set the time-out.
      return true;               // Ping started successfully.
    }
    
    unsigned long PingPing::ping_cm(unsigned int max_cm_distance) {
      unsigned long echoTime = PingPing::ping(max_cm_distance);  // Calls the ping method and returns with the ping echo distance in uS.
    #if ROUNDING_ENABLED == false
      return (echoTime / US_ROUNDTRIP_CM);  // Call the ping method and returns the distance in centimeters (no rounding).
    #else
      return PingPingConvert(echoTime, US_ROUNDTRIP_CM);                                                                 // Convert uS to centimeters.
    #endif
    }
    
    unsigned long PingPing::ping_in(unsigned int max_cm_distance) {
      unsigned long echoTime = PingPing::ping(max_cm_distance);  // Calls the ping method and returns with the ping echo distance in uS.
    #if ROUNDING_ENABLED == false
      return (echoTime / US_ROUNDTRIP_IN);  // Call the ping method and returns the distance in inches (no rounding).
    #else
      return PingPingConvert(echoTime, US_ROUNDTRIP_IN);                                                                 // Convert uS to inches.
    #endif
    }
    
    unsigned long PingPing::ping_median(uint8_t it, unsigned int max_cm_distance) {
      unsigned int uS[it], last;
      uint8_t j, i = 0;
      unsigned long t;
      uS[0] = NO_ECHO;
    
      while (i < it) {
        t = micros();                  // Start ping timestamp.
        last = ping(max_cm_distance);  // Send ping.
    
        if (last != NO_ECHO) {                           // Ping in range, include as part of median.
          if (i > 0) {                                   // Don't start sort till second ping.
            for (j = i; j > 0 && uS[j - 1] < last; j--)  // Insertion sort loop.
              uS[j] = uS[j - 1];                         // Shift ping array to correct position for sort insertion.
          } else
            j = 0;       // First ping is sort starting point.
          uS[j] = last;  // Add last ping to array in sorted position.
          i++;           // Move to next ping.
        } else
          it--;  // Ping out of range, skip and don't include as part of median.
    
        if (i < it && micros() - t < PING_MEDIAN_DELAY)
          delay((PING_MEDIAN_DELAY + t - micros()) / 1000);  // Millisecond delay between pings.
      }
      return (uS[it >> 1]);  // Return the ping distance median.
    }
    
    // ---------------------------------------------------------------------------
    // Standard and timer interrupt ping method support functions (not called directly)
    // ---------------------------------------------------------------------------
    
    void PingPing::set_max_distance(unsigned int max_cm_distance) {
    #if ROUNDING_ENABLED == false
      _maxEchoTime = min(max_cm_distance + 1, (unsigned int)MAX_SENSOR_DISTANCE + 1) * US_ROUNDTRIP_CM;  // Calculate the maximum distance in uS (no rounding).
    #else
      _maxEchoTime = min(max_cm_distance, (unsigned int)MAX_SENSOR_DISTANCE) * US_ROUNDTRIP_CM + (US_ROUNDTRIP_CM / 2);  // Calculate the maximum distance in uS.
    #endif
    }
    
    #if TIMER_ENABLED == true && DO_BITWISE == true
    
    // ---------------------------------------------------------------------------
    // Timer interrupt ping methods (won't work with ATmega128, ATtiny and most non-AVR microcontrollers)
    // ---------------------------------------------------------------------------
    
    void PingPing::ping_timer(void (*userFunc)(void), unsigned int max_cm_distance) {
      if (max_cm_distance > 0)
        set_max_distance(max_cm_distance);  // Call function to set a new max sensor distance.
    
      if (!ping_trigger())
        return;                             // Trigger a ping, if it returns false, return without starting the echo timer.
      timer_us(ECHO_TIMER_FREQ, userFunc);  // Set ping echo timer check every ECHO_TIMER_FREQ uS.
    }
    
    boolean PingPing::check_timer() {
      if (micros() - _at_time > _max_time) {  // Outside the time-out limit.
        timer_stop();                         // Disable timer interrupt
        return false;                         // Cancel ping timer.
      }
    
    #if URM37_ENABLED == false
      if (!(*_echoInput & _echoBit)) {  // Ping echo received.
    #else
      if (*_echoInput & _echoBit) {                   // Ping echo received.
    #endif
        timer_stop();                                                                            // Disable timer interrupt
        ping_result = (micros() - _at_time - (_max_time - _maxEchoTime) - PING_TIMER_OVERHEAD);  // Calculate ping time including overhead.
        return true;                                                                             // Return ping echo true.
      }
    
      return false;  // Return false because there's no ping echo yet.
    }
    
    // ---------------------------------------------------------------------------
    // Timer2/Timer4 interrupt methods (can be used for non-ultrasonic needs)
    // ---------------------------------------------------------------------------
    
    // Variables used for timer functions
    void (*intFunc)();
    void (*intFunc2)();
    unsigned long _ms_cnt_reset;
    volatile unsigned long _ms_cnt;
    #if defined(__arm__) && (defined(TEENSYDUINO) || defined(PARTICLE))
    IntervalTimer itimer;
    #endif
    
    void PingPing::timer_us(unsigned int frequency, void (*userFunc)(void)) {
      intFunc = userFunc;  // User's function to call when there's a timer event.
      timer_setup();       // Configure the timer interrupt.
    
    #if defined(__AVR_ATmega32U4__)                 // Use Timer4 for ATmega32U4 (Teensy/Leonardo).
      OCR4C = min((frequency >> 2) - 1, 255);       // Every count is 4uS, so divide by 4 (bitwise shift right 2) subtract one, then make sure we don't go over 255 limit.
      TIMSK4 = (1 << TOIE4);                        // Enable Timer4 interrupt.
    #elif defined(__arm__) && defined(TEENSYDUINO)  // Timer for Teensy 3.x
      itimer.begin(userFunc, frequency);              // Really simple on the Teensy 3.x, calls userFunc every 'frequency' uS.
    #elif defined(__arm__) && defined(PARTICLE)     // Timer for Particle devices
      itimer.begin(userFunc, frequency, uSec);              // Really simple on the Particle, calls userFunc every 'frequency' uS.
    #else
      OCR2A = min((frequency >> 2) - 1, 255);  // Every count is 4uS, so divide by 4 (bitwise shift right 2) subtract one, then make sure we don't go over 255 limit.
      TIMSK2 |= (1 << OCIE2A);                 // Enable Timer2 interrupt.
    #endif
    }
    
    void PingPing::timer_ms(unsigned long frequency, void (*userFunc)(void)) {
      intFunc = PingPing::timer_ms_cntdwn;  // Timer events are sent here once every ms till user's frequency is reached.
      intFunc2 = userFunc;                  // User's function to call when user's frequency is reached.
      _ms_cnt = _ms_cnt_reset = frequency;  // Current ms counter and reset value.
      timer_setup();                        // Configure the timer interrupt.
    
    #if defined(__AVR_ATmega32U4__)                 // Use Timer4 for ATmega32U4 (Teensy/Leonardo).
      OCR4C = 249;                                  // Every count is 4uS, so 1ms = 250 counts - 1.
      TIMSK4 = (1 << TOIE4);                        // Enable Timer4 interrupt.
    #elif defined(__arm__) && defined(TEENSYDUINO)  // Timer for Teensy 3.x
      itimer.begin(PingPing::timer_ms_cntdwn, 1000);  // Set timer to 1ms (1000 uS).
    #elif defined(__arm__) && defined(PARTICLE)     // Timer for Particle
      itimer.begin(PingPing::timer_ms_cntdwn, 1000, uSec);  // Set timer to 1ms (1000 uS).
    #else
      OCR2A = 249;                             // Every count is 4uS, so 1ms = 250 counts - 1.
      TIMSK2 |= (1 << OCIE2A);                 // Enable Timer2 interrupt.
    #endif
    }
    
    void PingPing::timer_stop() {    // Disable timer interrupt.
    #if defined(__AVR_ATmega32U4__)  // Use Timer4 for ATmega32U4 (Teensy/Leonardo).
      TIMSK4 = 0;
    #elif defined(__arm__) && (defined(TEENSYDUINO) || defined(PARTICLE))  // Timer for Teensy 3.x & Particle
      itimer.end();
    #else
      TIMSK2 &= ~(1 << OCIE2A);
    #endif
    }
    
    // ---------------------------------------------------------------------------
    // Timer2/Timer4 interrupt method support functions (not called directly)
    // ---------------------------------------------------------------------------
    
    void PingPing::timer_setup() {
    #if defined(__AVR_ATmega32U4__)  // Use Timer4 for ATmega32U4 (Teensy/Leonardo).
      timer_stop();                  // Disable Timer4 interrupt.
      TCCR4A = TCCR4C = TCCR4D = TCCR4E = 0;
      TCCR4B = (1 << CS42) | (1 << CS41) | (1 << CS40) | (1 << PSR4);  // Set Timer4 prescaler to 64 (4uS/count, 4uS-1020uS range).
      TIFR4 = (1 << TOV4);
      TCNT4 = 0;                                                                                                             // Reset Timer4 counter.
    #elif defined(__AVR_ATmega8__) || defined(__AVR_ATmega16__) || defined(__AVR_ATmega32__) || defined(__AVR_ATmega8535__)  // Alternate timer commands for certain microcontrollers.
      timer_stop();                      // Disable Timer2 interrupt.
      ASSR &= ~(1 << AS2);               // Set clock, not pin.
      TCCR2 = (1 << WGM21 | 1 << CS22);  // Set Timer2 to CTC mode, prescaler to 64 (4uS/count, 4uS-1020uS range).
      TCNT2 = 0;                         // Reset Timer2 counter.
    #elif defined(__arm__) && (defined(TEENSYDUINO) || defined(PARTICLE))
      timer_stop();  // Stop the timer.
    #else
      timer_stop();                            // Disable Timer2 interrupt.
      ASSR &= ~(1 << AS2);                     // Set clock, not pin.
      TCCR2A = (1 << WGM21);                   // Set Timer2 to CTC mode.
      TCCR2B = (1 << CS22);                    // Set Timer2 prescaler to 64 (4uS/count, 4uS-1020uS range).
      TCNT2 = 0;                               // Reset Timer2 counter.
    #endif
    }
    
    void PingPing::timer_ms_cntdwn() {
      if (!_ms_cnt--) {           // Count down till we reach zero.
        intFunc2();               // Scheduled time reached, run the main timer event function.
        _ms_cnt = _ms_cnt_reset;  // Reset the ms timer.
      }
    }
    
    #if defined(__AVR_ATmega32U4__)  // Use Timer4 for ATmega32U4 (Teensy/Leonardo).
    ISR(TIMER4_OVF_vect) {
      intFunc();  // Call wrapped function.
    }
    #elif defined(__AVR_ATmega8__) || defined(__AVR_ATmega16__) || defined(__AVR_ATmega32__) || defined(__AVR_ATmega8535__)  // Alternate timer commands for certain microcontrollers.
    ISR(TIMER2_COMP_vect) {
      intFunc();  // Call wrapped function.
    }
    #elif defined(__arm__)
                     // Do nothing...
    #else
    ISR(TIMER2_COMPA_vect) {
      intFunc();  // Call wrapped function.
    }
    #endif
    
    #endif
    
    // ---------------------------------------------------------------------------
    // Conversion methods (rounds result to nearest cm or inch).
    // ---------------------------------------------------------------------------
    
    unsigned int PingPing::convert_cm(unsigned int echoTime) {
    #if ROUNDING_ENABLED == false
      return (echoTime / US_ROUNDTRIP_CM);  // Convert uS to centimeters (no rounding).
    #else
      return PingPingConvert(echoTime, US_ROUNDTRIP_CM);                                                                 // Convert uS to centimeters.
    #endif
    }
    
    unsigned int PingPing::convert_in(unsigned int echoTime) {
    #if ROUNDING_ENABLED == false
      return (echoTime / US_ROUNDTRIP_IN);  // Convert uS to inches (no rounding).
    #else
      return PingPingConvert(echoTime, US_ROUNDTRIP_IN);                                                                 // Convert uS to inches.
    #endif
    }
    

  6. Tim Eckel repo owner

    Heh, it seems that what you’re trying to do is create a pull request for a change. If that’s the case, just create a pull request with your proposed changes.

  7. Tin Nguyen reporter

    I'm not an expert at this so I’m not sure if it will be fine to be modified like that. I’m still doing testing though. But if it’s a good proposal for you than I hope it can help. I just attached the library in case you want to see what I did.

  8. Tim Eckel repo owner

    It’s almost impossible to find all your changes with the formatting all different. It basically shows almost every line being different.

  9. Tim Eckel repo owner

    I believe I’ve gone through your code and found all the changes. I’ll be creating a pull request shortly.

  10. Tin Nguyen reporter

    I was trying to create a pull request but I don’t know why I couldn’t. It said “You do not have permission to create a pull request”

  11. Log in to comment