1

How can I measure the distance of an HR-S04 ultrasonic sensor with millis and micros?

2 Answers2

3

In response to @Majenko comment on the original question, here is some code that I use which uses the Timer1 input capture function to read an ultrasonic sensor. It also uses the Timer1 PWM generator to create the trigger pulses. The entire code is interrupt driven and nothing blocks the main program.

It's not built as an official style library with all the extra parts, but the .h and .cpp can be put into a folder in the libraries folder as in the manual installation process. It's really just a personal library that I use on some of my robots, but I'm happy to share.

The code as written supports the ATMEGA1284P and ATMEGA328P chips. There are a couple of lines to comment and un-comment to change between the two.

It exposes a singleton instance much like the Serial class, so there's nothing to instantiate. The pins are all set by hardware, you can't change them.

Depending on how much total range you want, you can set the prescaler to a much lower value. If you run with no prescaler then the resolution would be something on the order of nearly 100 times what you can get with micros (62.5ns vs 4us resolution). But that would be shorter distances like 1.2m and less.

https://github.com/delta-G/PingTimer

#include "PingTimer.h"

int distance;

void setup() {

Serial.begin(9600); ping.begin(); ping.sendPing();

}

void loop() {

// if the ping sensor has new data save it to distance and print it. if(ping.hasNewData()){ distance = ping.getDistanceMM(); Serial.print("Current Distance: "); Serial.println(distance); ping.sendPing();
}

// Do other things not blocked by ping sensor

}

PingTimer.h

/*

PingTimer -- uses Timer1 Input Capture to read a HC-SR04 Copyright (C) 2019 David C.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.

 */

#ifndef PINGTIMER_H_ #define PINGTIMER_H_

#include "Arduino.h"

// Ping pin on OC1A -- PB1 on 328P. (UNO pin 9) //-- PD5 on 1284P (pin 13) #define PING_PIN_MASK (1 << 5) #define PING_PIN_PORT PORTD

// Echo pin on ICP1 -- PB0 on 328P (UNO pin 8) //-- PD6 on 1284P (pin 14) #define ECHO_PIN_MASK (1 << 6) #define ECHO_PIN_PORT PIND

class PingTimer { private: volatile uint16_t timerVal; volatile boolean overflowed; volatile boolean newData;

void initTimer();

void startPulse();


public: PingTimer(); void begin();

void sendPing();
void echoHandler();
void overflowHandler();

boolean hasNewData();
boolean hasOverflowed();
uint16_t getTimerVal();

int16_t getDistanceMM();

};

extern PingTimer ping;

#endif /* PINGTIMER_H_ */

And the PingTimer.cpp

/*

PingTimer -- uses Timer1 Input Capture to read a HC-SR04 Copyright (C) 2019 David C.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.

 */

#include "PingTimer.h"

PingTimer ping;

PingTimer::PingTimer(){ newData = false; overflowed = false; timerVal = 0; }

void PingTimer::begin() { initTimer(); // ATMEGA328P // pinMode(8, INPUT); // pinMode(9, OUTPUT); // ATMEGA1284P pinMode(14, INPUT); pinMode(13, OUTPUT); }

void PingTimer::initTimer(){ cli(); TCCR1A = 0; // no PWM // Noise Cancel On, RISING Edge , No prescaler, 4ms range. TCCR1B |= ((1 << ICNC1) | (1 << ICES1) | (1 << CS11) | (0 << CS10)); // make sure analog comparator isn't selected as capture input ACSR &= ~(1 << ACIC); // Turn off Timer interrupts TIMSK1 = 0; // setup COMPA for 10us to time the trigger pulse // and COMPB for 12ms for out of range signal (a little over 4 meters) OCR1AH = 0; OCR1AL = 20; // With prescaler at 8 each tick is 0.5 us. OCR1BH = 0xB5; OCR1BL = 0xB0; // 0xB5B0 is 46512. That's a little over 4 meters with prescaler at 8. sei(); }

// This is a public facing function to call the startPulse so it can stay private // since it twiddles registers void PingTimer::sendPing() { startPulse(); }

void PingTimer::startPulse(){ // set pin high and setup PWM unit to // turn the pin off 10us later cli(); TCNT1H = 0x00; TCNT1L = 0x00; //Set up to get one pulse of PWM // Clear OC1A on compare match. 10bit fast PWM TCCR1A = ((1 << COM1A1) | (1 << WGM11) | (1 << WGM10)); // Setup input capture interrupt to catch the rising // of the echo pin and start the timer // Noise Cancel On, RISING Edge ,prescaler at 8 gives a 32ms timer = 5.5 meter range TCCR1B = ((1 << ICNC1) | (1 << ICES1) | (1 << CS11) | (0 << CS10) | (1 << WGM12)); // Turn on interrupt Input Capture TIFR1 = ((1 << ICF1) | (1 << TOV1)); TIMSK1 = ((1 << ICIE1)); overflowed = false; newData = false; sei(); }

// Called from ISR void PingTimer::echoHandler() { if (ECHO_PIN_PORT & ECHO_PIN_MASK) { //Rising Pin // Kill the OC1A PWM unit and return to normal mode TCCR1A = 0; // Noise Cancel On, FALLING Edge ,prescaler at 8 gives a 32ms timer = 5.5 meter range TCCR1B = (1 << ICNC1) | (0 << ICES1) | (1 << CS11) | (0 << CS10); // Reset Timer1 TCNT1H = 0; TCNT1L = 0; // Reset Input Capture and Timer Overflow Interrupts TIFR1 = (1 << ICF1) | (1 << TOV1); // Turn on interrupts Input Capture and Timer Overflow TIMSK1 = (1 << ICIE1) | (1 << TOIE1); } else { // Falling pin // Read the ICR1 Register uint8_t low = ICR1L; uint8_t high = ICR1H; timerVal = ((high << 8) | low) - 4; // Noise canceler adds 4 clock cycles

    //  turn off the timer interrupts
    TIMSK1 = 0;
    newData = true;
}

}

// Called from ISR Timer 1 OVF void PingTimer::overflowHandler(){ //turn off timer interrupts TIMSK1 = 0; newData = true; overflowed = true; }

boolean PingTimer::hasNewData(){ cli(); boolean retval = newData; newData = false; sei();

return retval;

}

boolean PingTimer::hasOverflowed(){ cli(); boolean retval = overflowed; overflowed = false; sei();

return retval;

}

uint16_t PingTimer::getTimerVal() { uint16_t retval = 0; cli(); if (overflowed) { retval = -1; } else { uint16_t copy = timerVal; retval = copy; } sei(); return retval; }

int16_t PingTimer::getDistanceMM() {

int16_t retval = 0;
uint16_t copy = getTimerVal();
if (copy == (uint16_t) -1) {
    retval = -1;
} else {
    // 16Mhz is 62.5 ns/clock
    // We've got the prescaler dividing by 8
    uint32_t nanoSecs = copy * 62.5 * 8;
    //speed of sound in mm/ns
    uint16_t mm = nanoSecs * 0.000344;
    retval = mm / 2;  // half a round trip
}
return retval;

}

ISR(TIMER1_CAPT_vect){ ping.echoHandler(); }

ISR(TIMER1_OVF_vect){ ping.overflowHandler(); }

ISR(TIMER1_COMPB_vect){ ping.overflowHandler(); }

Delta_G
  • 3,391
  • 2
  • 13
  • 24
1

I have noticed that many people on the net have problems using millis with the sensor to take measurements etc. so I created this sketch to help anyone.

You are free to ask for any clarification about it!

/*
 *  HC-SR04 sensor measuring
 *
 *  Non-blocking HC-SR04 sensor measuring
 *
 *  The circuit:
 *  - Echo pin connected to pin 2
 *  - Trigger pin connected to pin 3
 *  - VCC pin connected to VCC (5v)
 *  - GND gger pin connected to GND
 *
 *  created 3 Jul 2021
 *  by alessandromrc
 */

#define trigPin 3 #define echoPin 2

unsigned long timerStart = 0; unsigned long StartTime = micros(); bool timer = false; const unsigned long HIGH_TRIGGER = 10; const unsigned long LOW_TRIGGER = 2;

float timeDuration, distance;

enum SensorStatus { TRIG_LOW, TRIG_HIGH, ECHO_HIGH };

SensorStatus sensorStatus = TRIG_LOW;

bool isTimerReady(const unsigned long Sec) { return (micros() - timerStart) < Sec; }

void setup(void) { Serial.begin(9600); pinMode(trigPin, OUTPUT); pinMode(echoPin, INPUT); }

void loop(void) { switch (sensorStatus) { case TRIG_LOW: { digitalWrite(trigPin, LOW); timerStart = micros(); if (isTimerReady(LOW_TRIGGER)) { sensorStatus = TRIG_HIGH; } } break;

case TRIG_HIGH: { digitalWrite(trigPin, HIGH); timerStart = micros(); if (isTimerReady(HIGH_TRIGGER)) { sensorStatus = ECHO_HIGH; } } break;

case ECHO_HIGH: { if (!timer) { Serial.print("Microseconds Passed in initialization: "); Serial.println(micros() - StartTime); timer = !timer; Serial.println("Starting to Measure"); } digitalWrite(trigPin, LOW); timeDuration = pulseIn(echoPin, HIGH); Serial.print("Measured: "); Serial.print(timeDuration * 0.034 / 2); Serial.println(" cm"); sensorStatus = TRIG_LOW; } break; } }

Github Repository