One common way of going from "one" instance to "more" instances is to use arrays. In its simplest form, each variable which is associated with one instance should be turned from a scalar (e.g.: float myvar = 0;
) to an array (e.g.: float myvar[3] = {0, 1, 2};
). Better even than having a 3
hardcoded in there, one would normally have a constant expression or a #define for the number of items. Then, when accessing each instance, you'd do that in a for
loop.
So the code you pasted could become:
#define ULTRASONIC_DISTANCE
#ifdef ULTRASONIC_DISTANCE
#define ULTRASONIC_DISTANCE_DIGITAL_OUT // undefine this to use ANALOG out instead
#include <libraries/PulseIn/PulseIn.h>
enum { kNumSensors = 3 };
PulseIn pulseIn[kNumSensors];
unsigned int gPulseTriggerIntervalMs = 60; // how often to send out a trigger.
unsigned int gPulseTriggerIntervalSamples; // Set in setup() based on the above
int gPulseMinLength = 7; //to avoid spurious readings
float gPulseRescale = 58; // taken from the datasheet
#ifdef ULTRASONIC_DISTANCE_DIGITAL_OUT
unsigned int gPulseTrigDigitalOutPin[kNumSensors] = {0, 2, 4}; //channel to be connected to the module's TRIGGER pin - check the pin diagram in the IDE
#else // ULTRASONIC_DISTANCE_DIGITAL_OUT
unsigned int gPulseTrigAnalogOutPin[kNumSensors] = {0, 1, 2}; //channel to be connected to the module's TRIGGER pin
#endif // ULTRASONIC_DISTANCE_DIGITAL_OUT
unsigned int gPulseEchoDigitalInPin[kNumSensors] = {1, 3, 5}; //channel to be connected to the modules's ECHO pin via resistor divider. Check the pin diagram in the IDE
int gPulseTriggerCount [kNumSensors] = {0, 0, 0};
#endif // ULTRASONIC_DISTANCE
don't worry about the use of enum { kNumSensors = 3 };
if you are unfamiliar with that syntax ... C and C++ are a bit confusing in this respect.
The rest of the code:
#ifdef ULTRASONIC_DISTANCE
gPulseTriggerIntervalSamples = context->digitalSampleRate * (gPulseTriggerIntervalMs / 1000.f);
#ifdef ULTRASONIC_DISTANCE_DIGITAL_OUT
pinMode(context, 0, gPulseTrigDigitalOutPin, OUTPUT); // writing to TRIGGER pin
#endif // ULTRASONIC_DISTANCE_DIGITAL_OUT
pinMode(context, 0, gPulseEchoDigitalInPin, INPUT); // reading from ECHO pin
pulseIn.setup(context, gPulseEchoDigitalInPin, HIGH); //detect HIGH pulses on the ECHO pin
#endif // ULTRASONIC_DISTANCE
becomes
#ifdef ULTRASONIC_DISTANCE
gPulseTriggerIntervalSamples = context->digitalSampleRate * (gPulseTriggerIntervalMs / 1000.f);
for(unsigned int n = 0; n < kNumSensors; ++n)
{
#ifdef ULTRASONIC_DISTANCE_DIGITAL_OUT
pinMode(context, 0, gPulseTrigDigitalOutPin[n], OUTPUT); // writing to TRIGGER pin
#endif // ULTRASONIC_DISTANCE_DIGITAL_OUT
pinMode(context, 0, gPulseEchoDigitalInPin[n], INPUT); // reading from ECHO pin
pulseIn.setup(context, gPulseEchoDigitalInPin[n], HIGH); //detect HIGH pulses on the ECHO pin
}
#endif // ULTRASONIC_DISTANCE
And
#ifdef ULTRASONIC_DISTANCE
// this has to be at the bottom in case you use analogOut instead of digitalOut
for(unsigned int n = 0; n < context->digitalFrames; ++n){
gPulseTriggerCount++;
bool state;
if(gPulseTriggerCount == gPulseTriggerIntervalSamples){
gPulseTriggerCount = 0;
state = HIGH;
} else {
state = LOW;
}
#ifdef ULTRASONIC_DISTANCE_DIGITAL_OUT
digitalWrite(context, n, gPulseTrigDigitalOutPin, state); //write the state to the trig pin
#else // ULTRASONIC_DISTANCE_DIGITAL_OUT
// the below assumes analogFrames <= digitalFrames
unsigned int frame = n / (context->digitalFrames / context->analogFrames);
analogWrite(context, frame, gPulseTrigAnalogOutPin, state); //write the state to the trig pin
#endif // ULTRASONIC_DISTANCE_DIGITAL_OUT
int pulseLength = pulseIn.hasPulsed(context, n); // will return the pulse duration(in samples) if a pulse just ended
float duration = 1e6 * pulseLength / context->digitalSampleRate; // pulse duration in microseconds
static float distance = 0;
if(pulseLength >= gPulseMinLength){
// rescaling according to the datasheet
distance = duration / gPulseRescale;
// send to Pd
libpd_float("distance", distance);
}
}
#endif // ULTRASONIC_DISTANCE
should be
#ifdef ULTRASONIC_DISTANCE
// this has to be at the bottom in case you use analogOut instead of digitalOut
for(unsigned int n = 0; n < context->digitalFrames; ++n){
for(unsigned int p = 0; p < kNumSensors; ++p)
{
gPulseTriggerCount[p]++;
bool state;
if(gPulseTriggerCount[p] == gPulseTriggerIntervalSamples){
gPulseTriggerCount[p] = 0;
state = HIGH;
} else {
state = LOW;
}
#ifdef ULTRASONIC_DISTANCE_DIGITAL_OUT
digitalWrite(context, n, gPulseTrigDigitalOutPin[p], state); //write the state to the trig pin
#else // ULTRASONIC_DISTANCE_DIGITAL_OUT
// the below assumes analogFrames <= digitalFrames
unsigned int frame = n / (context->digitalFrames / context->analogFrames);
analogWrite(context, frame, gPulseTrigAnalogOutPin[p], state); //write the state to the trig pin
#endif // ULTRASONIC_DISTANCE_DIGITAL_OUT
int pulseLength = pulseIn[p].hasPulsed(context, n); // will return the pulse duration(in samples) if a pulse just ended
float duration = 1e6 * pulseLength / context->digitalSampleRate; // pulse duration in microseconds
if(pulseLength >= gPulseMinLength){
// rescaling according to the datasheet
float distance = duration / gPulseRescale;
// send to Pd
libpd_start_message(2);
libpd_add_float(p);
libpd_add_float(distance);
libpd_finish_list("distance");
}
}
}
#endif // ULTRASONIC_DISTANCE
(note here that libpd_float()
has been replaced by 4 lines which create a list of two floats. The first float is the sensor number, the second float is the distance.
All of the above untested.
To further dispel confusion, the conditional around ULTRASONIC_DIGITAL_OUT
could probably be removed as I'd expect most people to use the digital outputs instead of the analog outs.
However ...
robinm After noticing that they tend to become noisy when together in PureData, and reading some older posts on this forum of people solving this problem using a render.cpp file together with a Pure Data patch, I am positive that this way might make the reading a bit cleaner (it's not terrible right now but there are outliers quite frequently, some of them are negative numbers).
The most likely cause for negative numbers and "noise" is that the sensors are not placed in the same location and they are not well decoupled physically so that triggers from one are picked up by others (crosstalk) (unless there is some coding issue where variables are shared in a way that they shouldn't). Unfortunately the code above won't fix that and I don't think you strictly need a C++ implementation to get around that. One way to try and minimise the problem would be to have one single sensor active at any single time:
send sensor 0 trigger ... wait for sensor 0 echo (or max timeout), then
send sensor 1 trigger ... wait for sensor 1 echo (or max timeout), then
send sensor 2 trigger ... wait for sensor 2 echo (or max timeout)
This approach has two drawbacks: it sets a hard max limit on the measurable distance (because of the timeout) and it reduces the sampling frequency for each sensor, but it could be the only solution if the sensors locations cannot be changed to remove crosstalk. The third part of C++ code above would become more complex with these changes.
If you look at examples/PureData/ultrasonic-distance-sensor
, the basic patch for one sensor looks like this (I removed the comments for clarity):

and here is the multi-sensor version with throttling (done with a counter and [select]
) to remove crosstalk:
