Multiple HCSR-04 Ultrasonic sensors on teensy 3

Status
Not open for further replies.

esapode688

Well-known member
I was wondering if anyone ever needed or ever thought to use multiple HCSR04 or SRF04 ultrasonic sensors on a Teensy 3- 3.1 or on any other 32 Bit microcontrollers.

I'm talking about these sensors: http://www.ezdenki.com/graphics/hc-sr04-s.jpg


There is already someone that made a very fast working library for these sensors: https://code.google.com/p/arduino-new-ping/

But it works only on 8 bit microcontrollers.

Did anyone wrote a better library?

or any ideas?
 
Arduino New Ping uses interrupt and timer to precisely read out intervals for calculations (if I remember well!) so it's possible substitute the timer and interrupt routine with Teensy3 equivalent, if I remember well the author has Teensy3 in it's milestone so can happen somewhere in the future.
I just taked a fast look to the library so it's possible create a simple schetch that doesn't have the pretention of been a library but can show the simple theory since the most of the magic it's in the electronics soldered in the sensors. I should have an HC05 laying out somewhere, not the same thing but pretty similar, so I can try in these upcoming days.
 
Yeah i Know that can easily done with this code.

But this code doesn't easily allow you to do other tasks while reading the sensor.

That's the magic of new ping.

Actually....

@Paul if you are reading

Would you be happy to write instructions about the timer differences between 8 bit and 32 bit Uc and a tutorial on how to port any code to 32 bit micro?

I think It would a lot of people to get a teensy 3 :)


Code:
/*
 HC-SR04 Ping distance sensor:
 VCC to arduino 5v 
 GND to arduino GND
 Echo to Arduino pin 7 
 Trig to Arduino pin 8
 
 This sketch originates from Virtualmix: http://goo.gl/kJ8Gl
 Has been modified by Winkle ink here: http://winkleink.blogspot.com.au/2012/05/arduino-hc-sr04-ultrasonic-distance.html
 And modified further by ScottC here: http://arduinobasics.blogspot.com.au/2012/11/arduinobasics-hc-sr04-ultrasonic-sensor.html
 on 10 Nov 2012.
 */


#define echoPin 7 // Echo Pin
#define trigPin 8 // Trigger Pin
#define LEDPin 13 // Onboard LED

int maximumRange = 200; // Maximum range needed
int minimumRange = 0; // Minimum range needed
long duration, distance; // Duration used to calculate distance

void setup() {
 Serial.begin (9600);
 pinMode(trigPin, OUTPUT);
 pinMode(echoPin, INPUT);
 pinMode(LEDPin, OUTPUT); // Use LED indicator (if required)
}

void loop() {
/* The following trigPin/echoPin cycle is used to determine the
 distance of the nearest object by bouncing soundwaves off of it. */ 
 digitalWrite(trigPin, LOW); 
 delayMicroseconds(2); 

 digitalWrite(trigPin, HIGH);
 delayMicroseconds(10); 
 
 digitalWrite(trigPin, LOW);
 duration = pulseIn(echoPin, HIGH);
 
 //Calculate the distance (in cm) based on the speed of sound.
 distance = duration/58.2;
 
 if (distance >= maximumRange || distance <= minimumRange){
 /* Send a negative number to computer and Turn LED ON 
 to indicate "out of range" */
 Serial.println("-1");
 digitalWrite(LEDPin, HIGH); 
 }
 else {
 /* Send the distance to the computer using Serial protocol, and
 turn LED OFF to indicate successful reading. */
 Serial.println(distance);
 digitalWrite(LEDPin, LOW); 
 }
 
 //Delay 50ms before next reading.
 delay(50);
}
 
I may have to play with some of these at some point as they are cheap and might be useful on one of my robots...

On the Teensy, there are probably lots of ways to deal with these sensors. One approach I would look at is to use the Pin Change interrupt, with a real simple function, that if the pin is logically going high, remember the microseconds, If it going low again, get the microseconds and subtract from it your saved value to get the pulse width. If you are simply doing this on demand, then I would have my function, that would do the pulse on the appropriate pin to start the ping operation and enable the interrupt (and set a state saying that it is still pending). Then have a function/method that can query to say it is done...

Or if I want to do this on a regular basis, I would probably add a simple Interval Timer, that round robins start a ping on each of the sensors, probably with only one active at a time, as to keep from having them interfere with each other. However if they don't interfere, could potentially have multiple active at once... The code in the interval timer would simply remember the pulse widths, which the main program could access.

But as I mentioned on the Teensy you could do this many different ways.

Kurt
 
But this code doesn't easily allow you to do other tasks while reading the sensor.
This because there's delays in the main loop. If you use an ISR based trigger you can avoid the big delay for ping (but remains 12uS for the ping sequence that can avoided with a bit of more code).
For testing an easy solution you can use the IntervalTimer of teensy3.

Here's an example (but NOT tested since I still not got my sensor):

Code:
#include "IntervalTimer.h"

#define  TRIGPIN  10
#define  ECHOPIN  9


volatile unsigned long duration,oldDuration = 0;
volatile boolean measured = false;
float distance = 0;

IntervalTimer pingTimer;

void setup(){
  Serial.begin(38400);

  pinMode(TRIGPIN,OUTPUT);
  pinMode(ECHOPIN,INPUT);
  pingTimer.begin(pingFunction, 3000);//in uSecs
}


void pingFunction(){
  duration = 0;
  digitalWriteFast(TRIGPIN,LOW);
  delayMicroseconds(2);
  digitalWriteFast(TRIGPIN,HIGH);
  delayMicroseconds(10);
  digitalWriteFast(TRIGPIN,LOW);
  //
  duration = pulseIn(ECHOPIN,HIGH);
  if (duration > 0 && (duration != oldDuration)){
    measured = true;
    oldDuration = duration;
  } 
  else {
    measured = false;
  }
}


void loop(){
  if (measured){
    distance = duration/58.2;
    Serial.println();
    Serial.print(distance,2);
    Serial.print("Cm");
    Serial.println();
  }
}

It's not as elegant as use directly Teensy3 ISR's but should work and it's a start. You can also avoid pulseIn by study PulsePosition library that Paul released with last IDE release candidate.
If you simplify code to use a single ISR you can connect several sensors to a MUX and get data from many sensors.
 
I just took a quick look at the code. It's only using Timer2 for a periodic interrupt. Ought to be pretty simple to convert this one for IntervalTimer....
 
Photos....

NewPing_photo.jpg


NewPing_screenshot.png
 
Excuse my ignorance, but how does using a timer help the responsiveness of the program?

I tried reading through some of the NewPing code & comments, but didn't get it.

Does it send a pulse, and using very short intervals in the timer, check to see if the pulse has returned?

Initially I was thinking that the circuits on board changed the pulse train to a HIGH or LOW (which I thought they did), and that drives the interrupt pin, which then computes the transit time.

In any case, thanks for fixing this.
 
Does it send a pulse, and using very short intervals in the timer, check to see if the pulse has returned?

Yes, I believe that's basically how it works.

Honestly, I didn't dig too deeply into how Tim designed his library. He already had the AVR-specific timer stuff isolated to just a few functions, and he already had #ifdef checks for differing timers on various AVR chips. His code has pretty good (and actually correct) comments about how the timer was being configured.

I mostly just focused on replacing the AVR timer stuff with IntervalTimer. I didn't need to look at how the rest of the library really works.

A really ideal way to read these sensors would involve using the timer input capture feature. I can tell you with certainty that's *not* how he's doing things, nor is anyone else's library I've seen. But on Arduino Uno, there's only one timer with input capture, and only 1 pin supports that feature. Also, it seems relatively few Arduino library authors understand how the input capture and waveform-generating compare registers really work.... which actually makes porting code fairly easy. Most just use the timer overflow interrupt and then build everything on top of a CPU-hogging periodic interrupt, which can be easily ported by using IntervalTimer.
 
Ok, that makes sense. I actually found a relevant example that made it more obvious to me.

How many timer input capture pins are there on the teensy 3.0/3.1? And it sounds like the t3 can also automatically do the time measurement in hardware. Is that right?

I've used the interrupt pins on an Arduino Mega before to detect light levels from a Light-To-Frequency sensor, which was pretty demanding for the interrupt processing. But the ultrasonic sensors should not be demanding in the least. But not sure how you'd get a lot (ie 15 as in NewPing's example) of ultrasonic sensors working, without multiplexing.
 
I'm the author of NewPing. Paul's changes work for the standard ping() method but not for ping_timer(). I've been working with Paul via email and I've got things mostly sorted out now. I've been delaying a new release of NewPing for a long time now, but this seems like a good opportunity to release it. I'm still working on a few things, but Teensy 3.x support for both the ping() and ping_timer() methods will be fully supported in the next release. The pin interrupt method may or may not be included in this release, depending on how much time I have. But, I'd like to release it even if that's not finished yet.

On a side note, anyone (including Paul) know why the "begin" function's second parameter (microseconds) with Teensy 3.x is a float? For example, to setup a timer for once every millisecond, why not itimer.begin(function, 1000);? Paul did itimer.begin(function, 1000.0); instead (notice the float) and I can't see why or if there's even a difference.

Tim
 
I've tried using the new-NewPing library but I'm getting this errors:

NewPing/NewPing.cpp.o: In function `NewPing::timer_stop()':
NewPing.cpp:187: undefined reference to `NewPing::itimer'
NewPing/NewPing.cpp.o: In function `IntervalTimer::begin(void (*)(), float)':
/Applications/Arduino.app/Contents/Resources/Java/hardware/teensy/cores/teensy3/IntervalTimer.h:74: undefined reference to `NewPing::itimer'
collect2: error: ld returned 1 exit status

Any suggestions on how to solve them?
Thanx!
 
My guess is that there is a problem with Paul's NewPing library. For the fun of it I downloaded it and tried to compile some of the examples. Several failed. So I took a quick look. The problem is that the member itimer is defined as a static member and not defined anywhere. When I added the lines:
Code:
#if defined(__arm__) && defined(TEENSYDUINO)
#include <IntervalTimer.h>
IntervalTimer NewPing::itimer;
#endif
in NewPing.cpp right after NewPing.h was included, the examples compiled.
 
Hi Folks, I am trying to get the NewPingEventTimer example to work on a teensy3.1 using version 1.20 of teensyduino. The NewPingExample code works fine, so I think my hookup is OK. Also, the teensy IntervalTimer example works fine (blinking an led), so the timers seem OK. I have looked at the library, and it seems it should work. I have run the examples on an Uno, and they all work fine, so my SR04 seems OK. I commented out the printing in the echoCheck routine and just set a variable there, but it never changes. It appears that the echoCheck routine is being invoked, but the check_timer test is never passing. Any clues about running this down would be appreciated.

Thanks, Duane

UPDATE: After dinking a bit with the NewPing examples, I finally changed the library NewPing.cpp like this:

elif defined(__arm__) && defined(TEENSYDUINO)
itimer.begin(userFunc, frequency);
// itimer.begin(userFunc, (float)1000000.0 / (float)frequency);

In other words, I called itimer.begin() passing the frequency value directly since it is specified in microseconds. I also changed the Serial.print foo in the echoCheck() routine in the example to just set a value. The printing works fine on an Arduino, but does not seem to work on a teensy3. It seems questionable to call something like Serial.print from an interrupt routine anyway. Now it seems to work -- nota bene:I have not carefully checked the accuracy of the measurement. I can post my version of the example if anyone is interested. My next step will be to try to use an input capture on the echo pin since that seems a more elegant way to do this, and I need to learn how to use this chip anyway.
 
Last edited:
Hi Folks, I am trying to get the NewPingEventTimer example to work on a teensy3.1 using version 1.20 of teensyduino. The NewPingExample code works fine, so I think my hookup is OK. Also, the teensy IntervalTimer example works fine (blinking an led), so the timers seem OK. I have looked at the library, and it seems it should work. I have run the examples on an Uno, and they all work fine, so my SR04 seems OK. I commented out the printing in the echoCheck routine and just set a variable there, but it never changes. It appears that the echoCheck routine is being invoked, but the check_timer test is never passing. Any clues about running this down would be appreciated.

Thanks, Duane

UPDATE: After dinking a bit with the NewPing examples, I finally changed the library NewPing.cpp like this:

elif defined(__arm__) && defined(TEENSYDUINO)
itimer.begin(userFunc, frequency);
// itimer.begin(userFunc, (float)1000000.0 / (float)frequency);

In other words, I called itimer.begin() passing the frequency value directly since it is specified in microseconds. I also changed the Serial.print foo in the echoCheck() routine in the example to just set a value. The printing works fine on an Arduino, but does not seem to work on a teensy3. It seems questionable to call something like Serial.print from an interrupt routine anyway. Now it seems to work -- nota bene:I have not carefully checked the accuracy of the measurement. I can post my version of the example if anyone is interested. My next step will be to try to use an input capture on the echo pin since that seems a more elegant way to do this, and I need to learn how to use this chip anyway.

This was corrected in the official version of NewPing back in June of last year. Having a few forks of my library in multiple locations that are not updated or even working is not a great idea.

The current location for the official build and release of NewPing is https://code.google.com/p/arduino-new-ping/. Because Google Code is going defunct, it will be moving at some point. If I could get GitHub working, I'd move it there. But I've yet to figure out why GitHub doesn't work for me (version control repository is a foreign concept to me).

Tim
 
Hey there!
I can't get this to work when I run a modified example of the 15 new pings into a only 3 of them on a teensy 3.1. It does compile but I'm always getting 0 in each sensors as reading on the Serial. Each of them work on their when using the simple example. I'm using the last version of the library that Teckel recomends aswell.
Here's the code:
#include <NewPing.h>

#define SONAR_NUM 3 // Number or sensors.
#define MAX_DISTANCE 50 // Maximum distance (in cm) to ping.
#define PING_INTERVAL 33 // Milliseconds between sensor pings (29ms is about the min to avoid cross-sensor echo).

unsigned long pingTimer[SONAR_NUM]; // Holds the times when the next ping should happen for each sensor.
unsigned int cm[SONAR_NUM]; // Where the ping distances are stored.
uint8_t currentSensor = 0; // Keeps track of which sensor is active.

NewPing sonar[SONAR_NUM] = { // Sensor object array.
NewPing(0, 1, MAX_DISTANCE), // Each sensor's trigger pin, echo pin, and max distance to ping.
NewPing(2, 3, MAX_DISTANCE),
NewPing(4, 5, MAX_DISTANCE)
};

void setup() {
Serial.begin(115200);
pingTimer[0] = millis() + 75; // First ping starts at 75ms, gives time for the Arduino to chill before starting.
for (uint8_t i = 1; i < SONAR_NUM; i++) // Set the starting time for each sensor.
pingTimer = pingTimer[i - 1] + PING_INTERVAL;
}

void loop() {
for (uint8_t i = 0; i < SONAR_NUM; i++) { // Loop through all the sensors.
if (millis() >= pingTimer) { // Is it this sensor's time to ping?
pingTimer += PING_INTERVAL * SONAR_NUM; // Set next time this sensor will be pinged.
if (i == 0 && currentSensor == SONAR_NUM - 1) oneSensorCycle(); // Sensor ping cycle complete, do something with the results.
sonar[currentSensor].timer_stop(); // Make sure previous timer is canceled before starting a new ping (insurance).
currentSensor = i; // Sensor being accessed.
cm[currentSensor] = 0; // Make distance zero in case there's no ping echo for this sensor.
sonar[currentSensor].ping_timer(echoCheck); // Do the ping (processing continues, interrupt will call echoCheck to look for echo).
}
}
// The rest of your code would go here.
}

void echoCheck() { // If ping received, set the sensor distance to array.
if (sonar[currentSensor].check_timer())
cm[currentSensor] = sonar[currentSensor].ping_result / US_ROUNDTRIP_CM;
}

void oneSensorCycle() { // Sensor ping cycle complete, do something with the results.
for (uint8_t i = 0; i < SONAR_NUM; i++) {
Serial.print(i);
Serial.print("=");
Serial.print(cm);
Serial.print("cm ");
}
Serial.println();
}
 
Status
Not open for further replies.
Back
Top