Go Down

Topic: Multiple HC-SR04 with as few pins as possible (Read 520 times) previous topic - next topic

sparknburn

Hi,

I want to connect multiple ultra sonic sensors using as few pins as possible on the arduino. Since they can't be triggered at the same time I thought about connecting one pin each to the trigger pins and then join the echo pins (see attached image). Then I can trigger one and wait for the reply, then the next one and so on. That obviously doesn't work since the echo pins are low (or maybe floating) when not measuring. Basically I want an OR gate for the echo pins. Is there any way to solve this with a pull-up resistor or similar?

I don't want to use a single pin that I switch between input/output since that's quite cumbersome is my case.

DaveEvans

Basically I want an OR gate for the echo pins.
Well, you could buy one. Or try a diode OR gate if you have a couple of diodes and a resistor.

zhomeslice

#2
Aug 13, 2017, 07:44 am Last Edit: Aug 13, 2017, 07:45 am by zhomeslice
Hi,

I want to connect multiple ultra sonic sensors using as few pins as possible on the arduino. Since they can't CAN be triggered at the same time ....
I have succeeded in triggering all at the same time using interrupts  :)
Code: [Select]
#define TriggerPin  2                  // output pin all trigger pins are linked to.
#define SamplesToAverage 1             // number of ping samples to average together for use
#define DelayBetweenPings 15            // delay after ping data is completely recieved all have reported in and the next trigger pulse in miliseconds (5 seems to be great)

unsigned long Timer, zTimer, xTimer; // Delay timers
volatile unsigned long SampleAge[20];
volatile int PinArray[20] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1};            // List of pins that could be used as ping inputs:
unsigned long PingTime[20] ;
volatile int ToCompleteCtr;
volatile  unsigned long PingTimeX[20];
volatile  int PingSamplesX[20];
volatile  unsigned long edgeTime[20];
volatile  uint8_t PCintLast[3];
volatile  uint8_t mask[3];
int PinMask[3];
float Measurements[20] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
byte *MeasurementsB;

bool SerialEvent = false;                   // Detects serial imput  1
int I2CsendDataGo = 0;
uint8_t dataB[32] ;
int c, Pin;
int x = 0;
float inches, cm;
int DegSpread = 0;                  
static int SensorCount = 0;        
volatile byte PinList[25];            

void setup() {

  Serial.begin(115200);
  Serial.println("Send 1 for Readable Data, Send 0 For Interger Data");
  SensorCount = 0;
  pinMode(TriggerPin, OUTPUT);
  // enable interrupt for pin...
  // Select the pins you want to use for input
  // pciSetup(0); // Serial Communication
  // pciSetup(1); // Serial Communication
  // pciSetup(2); //This is my trigger pin
  pciSetup(3);
  pciSetup(4);
  pciSetup(5);
  pciSetup(6);
  pciSetup(7);
  pciSetup(8);
  pciSetup(9);
 // pciSetup(10);
 // pciSetup(11); // SPI communications
 // pciSetup(12); // SPI communications
 // pciSetup(13); // SPI communications
 // pciSetup(14); // A0
 // pciSetup(15); // A1
 // pciSetup(16); // A2
 // pciSetup(17); // A3
 // pciSetup(18); // A4  i2c Communication
 // pciSetup(19); // A5  i2c Communication
 // pciSetup(20); // A6  for some clones
 // pciSetup(21); // A7  for some clones

  
}

void loop() {
  Timer = millis(); // timing events!
  PingIt(); // Manage ping data
  if (SerialEvent) SendSerialData();
}

void RadarOut( int Degrees) {
  static int x = 0;
  static bool xz = true;
  static byte Sensor = 0;
  int deg = Degrees / SensorCount;
  Sensor = 0;
  while (Sensor < SensorCount) {
    if (Degrees > 0) {
      Serial.print((Sensor * deg) );
      if (Degrees != SensorCount) {
        Serial.print("*");
      }
      Serial.print("=");

    } else {
      Serial.print("Pin#");
      Serial.print(PinList[Sensor]);
      Serial.print("=");

    }

    Serial.print(Measurements[Sensor]);
    Serial.print("cm ");
    Sensor++;
  }
}

void PingTrigger(int Pin) {
  digitalWrite(Pin, LOW);
  delayMicroseconds(1);
  digitalWrite(Pin, HIGH); // Trigger another pulse
  delayMicroseconds(5);
  digitalWrite(Pin, LOW);
}

bool AllClear() {
  return (!(PinMask[0] & PIND) && !(PinMask[1] & PINB) && !(PinMask[2] & PINC) && !ToCompleteCtr); //  all the input pins are LOW
}

void PingIt() {
  static int SampleCt = 0;
  static bool DelayLatch = true;
  if ( AllClear()) { // Wait
    if (DelayLatch) {
      xTimer = Timer + DelayBetweenPings;
      DelayLatch = false;
    }
    if ((Timer - xTimer) >= 0 ) {
      for (int i = 0; i < 20; i++) {
      if (SampleCt >= SamplesToAverage) {
        byte Sensor = 0;
        
          // Serial.print(i);
          if ((PinArray[i] != -1) ) {
            //Serial.println("");
          //  Serial.println(PingTimeX[i]);
            if (PingSamplesX[i] > 0) {
              
            //  Serial.println(PingSamplesX[i]);
            //  Serial.println("\t");
            //  Serial.println(PingTimeX[i]);
              PingTime[i] =  (unsigned long) (PingTimeX[i] / PingSamplesX[i]); // average
              Measurements[Sensor] = (float) (microsecondsToCentimeters(PingTime[i]));
            }
            PingTimeX[i] = 0;
            PingSamplesX[i] = 0;
            Sensor++;
          }
        }
        SampleCt = 0;
        SampleAge[i] = micros();
      }

      DelayLatch = true;
      SampleCt++;
      PingTrigger(TriggerPin); // Send another ping
      zTimer = Timer;
    }
  }
}

float microsecondsToInches(long microseconds)
{
  return (float) microseconds / 74 / 2;
}

float microsecondsToCentimeters(long microseconds)
{
  return (float)microseconds / 29 / 2;
}


void SendSerialData()
{
  SerialEvent = false;
  int val = Serial.read() - '0';
  while (Serial.available())Serial.read();
  if (val == 0) {
    DataMessage();
    Serial.write(dataB, 30); // respond with message
  } else  if (val == 1) {
    RadarOut( DegSpread);
    Serial.println();
  }
}


uint8_t DataMessage() {
  int x = 0;
  for (int i = 0; i < 15; i++ ) {
    dataB[x] = (uint8_t)(((uint8_t)Measurements[i]) >> 8);
    x++;
    dataB[x] = (uint8_t)Measurements[i];
    x++;
  }
}

// port change Interrupt
ISR(PCINT0_vect) { //this ISR pins 8~13
  static uint8_t pin;
  static unsigned long cTime;
  cTime = micros();         // micros() return a uint32_t
  pin = PINB; //  get the state of all pins bit 0 = pin 8, bit 1 = pin 9 dtc.
  ToCompleteCtr++;
  sei();                    // re enable other interrupts
  CheckTimers(8, 13,  1, pin, cTime);
}

ISR(PCINT1_vect) { //this ISR s A0~A5
  static uint8_t pin;
  static unsigned long cTime;
  cTime = micros();         // micros() return a uint32_t
  pin = PINC; //  get the state of all pins bit 0 = pin 8, bit 1 = pin 9 dtc.
  ToCompleteCtr++;
  sei();                    // re enable other interrupts
  CheckTimers(14, 19,  2, pin, cTime);
}

ISR(PCINT2_vect) { //this ISR pins 0-7
  static uint8_t pin;
  static unsigned long cTime;
  cTime = micros();         // micros() return a uint32_t
  pin = PIND; //  get the state of all pins bit 0 = pin 8, bit 1 = pin 9 dtc.
  ToCompleteCtr++;
  sei();                    // re enable other interrupts
  CheckTimers(0, 7,  0, pin, cTime);
}

void CheckTimers(uint8_t StartPin, uint8_t EndPin, uint8_t group, uint8_t  pin, unsigned long cTime )
{
  volatile uint16_t dTime;
  mask[group] = pin ^ PCintLast[group];
  PCintLast[group] = pin;        
  for (uint8_t ii = 0; ii <= (EndPin - StartPin); ii++) {
    if (mask[group] >> ii & 1) { // pin has changed
      if ((pin >> ii & 1))edgeTime[(ii + StartPin)] = cTime;
      else { // Pulse Went low calculate the duratoin
        dTime = cTime - edgeTime[(ii + StartPin)]; // Calculate the change in time
        PingTimeX[(ii + StartPin)] = PingTimeX[(ii + StartPin)] + dTime;
        PingSamplesX[(ii + StartPin)]++;
      }
    }
  }
  ToCompleteCtr--;  //when all interupts are complete this will return to zero
}

// Install Pin change interrupt for a pin, can be called multiple times
void pciSetup(byte pin)
{
  if (pin <= 7)PinMask[0] =  bitWrite(PinMask[0], pin, 1); // PIND for pins 0~7
  else if (pin > 13) PinMask[2] =  bitWrite(PinMask[2] , pin - 14, 1); // PINC for A0~A5 Starts on Pin 14
  else PinMask[1] =  bitWrite(PinMask[1] , pin - 8, 1); // PINB for pins 8~13
  pinMode(pin, INPUT);// enable interrupt for pin...
  *digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin));  // enable pin
  PCIFR  |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
  PCICR  |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
  PinArray[pin] = 1;
  PinList[SensorCount] = pin;
  SensorCount++;
}

void serialEvent() {
  SerialEvent = true;
}

this is my setup
each echo pin goes to a unique input and pin 2 is tied to all trigger pins
all triggers activate at once



my code can handle multiple pin changes at the same time and log the start time. when each echo pin restores the interrupts trigger again and store the end time giving the trip distance.
amazingly accurate. my setup uses 8 sensors I can't see why you couldn't use more you can attach to any pin including analog for example if you want to use  analog pin 3 unremark: pciSetup(17); // A3

data is stored in an array available for you to access at will. and this is a non blocking script so other code can run at the same time.
HC

sparknburn

Well, you could buy one. Or try a diode OR gate if you have a couple of diodes and a resistor.
I guess something like this?


I have a couple of diodes and resistors lying around, I might actually try that.

I have succeeded in triggering all at the same time using interrupts  :)
...
each echo pin goes to a unique input and pin 2 is tied to all trigger pins
all triggers activate at once
I was thinking about doing it like but, but I was a bit afraid of getting weird results if the echo from sensor A ends up in the receiver for sensor B. Do you notice any issues with crosstalk between the sensors?

zhomeslice

#4
Aug 13, 2017, 04:52 pm Last Edit: Aug 13, 2017, 05:23 pm by zhomeslice Reason: Adding code for circuit I designed
I guess something like this?


I have a couple of diodes and resistors lying around, I might actually try that.

I was thinking about doing it like but, but I was a bit afraid of getting weird results if the echo from sensor A ends up in the receiver for sensor B. Do you notice any issues with crosstalk between the sensors?
So lets look at what could change with the wiring that may resolve you concern
If we were to hook the first sensor  trigger pin to the arduino pin 2 as I suggested but hook the second sensor trigger pin to the echo pin of first sensor. continue this for each sensor 3's trigger to 2's echo, 4's trigger to 3's echo.  

Datasheet: https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf
The trigger pin only fires after the pulse to it falls the pulse has a minimum "Using IO trigger for at least 10us high level signal" but I didn't see a maximum limit on the datasheet.  so the above method would fire each sensor in sequence. and you timing of the rise an fall of the echo pin is what you would use to detect distance. using your simple diode circuit you could achieve multiple sensors with 1 trigger pin and 1 input pin.



.........................

The more I type the more I'm still favoring the all at once method because the actual sensor only senses a 15° window. meaning that any pulse that is able to reflect back would be a indication of an object that is within 15° . Since the start pulse is exactly the same on all devices the result is more accurate even if it is a reflection of a reflection is detected (something is there but its  just stealthy) that actually returns a signal. My thought is that this is more like a submarine that sends out 1 pulse and listens in all directions for each resulting echo if 2 sensors pick up the same echo you can triangulate the exact position on a flat plane

Z

Here is some simple code I put together for the above circuit :) i created (did not test)
Code: [Select]

#define EchoInputCount 3
#define TriggerPin  2
// echo pin will be interrupt 1 on pin 3
#define DelayBetweenPings 50 // it works to about 5 milliseconds between pings

volatile  unsigned long PingTime[EchoInputCount];
volatile int Counter = 0;
volatile  unsigned long edgeTime;
volatile  uint8_t PCintLast;
int PinMask = B1000; // pin 3
float Measurements[EchoInputCount];

void PintTimer( )
{
  uint8_t pin;
  static unsigned long cTime;
  cTime = micros();         // micros() return a uint32_t
  pin = PIND >> 3 & 1;      // Quickly get the state of  pin 3
  if (pin)edgeTime = cTime; //Pulse went HIGH store the start time
  else { // Pulse Went low calculate the duratoin
    PingTime[Counter % EchoInputCount] = cTime - edgeTime; // Calculate the change in time  NOTE: the "% EchoInputCount" prevents the count from overflowing the array look up % remainder calculation
    Counter++;
  }
}
void debug()
{
  char S[20];
  static unsigned long PingTimer;
  if ((unsigned long)(millis() - PingTimer) >= 100) {
    PingTimer = millis();
    for(int c = 0; c < EchoInputCount; c++){
      Serial.print(dtostrf(Measurements[c], 6, 1, S));
    }
    Serial.println();
  }
}

float microsecondsToInches(long microseconds)
{
  return (float) microseconds / 74 / 2;
}

float microsecondsToCentimeters(long microseconds)
{
  return (float)microseconds / 29 / 2;
}


void PingTrigger(int Pin)
{
  Counter = 0;
  digitalWrite(Pin, LOW);
  delayMicroseconds(1);
  digitalWrite(Pin, HIGH); // Trigger another pulse
  delayMicroseconds(10);
  digitalWrite(Pin, LOW);
}

void PingIt()
{
  unsigned long PT[EchoInputCount];
  static unsigned long PingTimer;
  if ((unsigned long)(millis() - PingTimer) >= DelayBetweenPings) {
    PingTimer = millis();
    cli ();         // clear interrupts flag
    for(int c = 0; c < EchoInputCount; c++){
       PT[c] = PingTime[c];
    }
    sei ();         // set interrupts flag
    for(int c = 0; c < EchoInputCount; c++){
         Measurements[c] = (float) (microsecondsToCentimeters(PT[c]));
    }
    //      Measurements = (float) (microsecondsToInches(PT));
    PingTrigger(TriggerPin); // Send another ping
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println("Ping Test");
  pinMode(3, INPUT);
  pinMode(2, OUTPUT);
  attachInterrupt(1, PintTimer, CHANGE );
}

void loop()
{
  PingIt(); // Manage ping data
  debug(); // display ping data (at a slower rate due to serial limitations)
}

HC

zhomeslice

#5
Aug 16, 2017, 03:36 am Last Edit: Aug 16, 2017, 07:27 am by zhomeslice
Working code with 4 Sensors into 1 input using this schematic as a guide



Code: [Select]

#define EchoInputCount 4
#define TriggerPin  2
// echo pin will be interrupt 1 on pin 3
#define DelayBetweenPings 50 // it works to about 5 milliseconds between pings

volatile  unsigned long PingTime[EchoInputCount];
volatile int Counter = EchoInputCount;
volatile  unsigned long edgeTime;
volatile  uint8_t PCintLast;
int PinMask = B1000; // pin 3
float Measurements[EchoInputCount];
unsigned long TimeoutTimer;
void PintTimer( )
{
  uint8_t pin;
  static unsigned long cTime;
  cTime = micros();         // micros() return a uint32_t
  pin = PIND >> 3 & 1;      // Quickly get the state of  pin 3
  if (pin)edgeTime = cTime; //Pulse went HIGH store the start time
  else { // Pulse Went low calculate the duratoin
    PingTime[Counter % EchoInputCount] = cTime - edgeTime; // Calculate the change in time  NOTE: the "% EchoInputCount" prevents the count from overflowing the array look up % remainder calculation
    Counter++;
  }
}
void debug()
{
  char S[20];
  static unsigned long PingTimer;
  if ((unsigned long)(millis() - PingTimer) >= 1) {
    PingTimer = millis();
    for (int c = 0; c < EchoInputCount; c++) {
      Serial.print(dtostrf(Measurements[c], 6, 1, S));
    }
    Serial.println();
  }
}

float microsecondsToInches(long microseconds)
{
  return (float) microseconds / 74 / 2;
}

float microsecondsToCentimeters(long microseconds)
{
  return (float)microseconds / 29 / 2;
}

void PingTrigger(int Pin)
{

  digitalWrite(Pin, LOW);
  delayMicroseconds(1);
  digitalWrite(Pin, HIGH); // Trigger another pulse
  delayMicroseconds(10);
  digitalWrite(Pin, LOW);
}

void PingIt()
{
  unsigned long PT[EchoInputCount];
  static unsigned long PingTimer;
  if (Counter >= EchoInputCount) {
    if ( ((unsigned long)(millis() - PingTimer) >= DelayBetweenPings)) {
      PingTimer = millis();
      cli ();         // clear interrupts flag
      for (int c = 0; c < EchoInputCount; c++) {
        PT[c] = PingTime[c];
      }
      sei ();         // set interrupts flag
      for (int c = 0; c < EchoInputCount; c++) {
        if (PT[c] < 23200) Measurements[c] = (float) (microsecondsToCentimeters(PT[c]));
      }
      //      Measurements = (float) (microsecondsToInches(PT));
      debug();
      delay(10);
      PingTrigger(TriggerPin); // Send another ping
      Counter = 0;
      TimeoutTimer = millis();

    }
  }
}

void setup()
{
  Serial.begin(115200);
  Serial.println("Ping Test");
  pinMode(3, INPUT);
  pinMode(2, OUTPUT);
  attachInterrupt(1, PintTimer, CHANGE );

}

void loop()
{
  PingIt(); // Manage ping data

  if ( ((unsigned long)(millis() - TimeoutTimer) >= 1000)) {
    PingTrigger(TriggerPin); // Send another ping
    Counter = 0;
    TimeoutTimer = millis();
  }

}




HC

arielnh56

I worked on a solution for this myself very similar to what you described in the first post. I initiated the trigger through a pin expander chip (PCF8574) and muxed the results through an 8 way OR gate. Unfortunately some sensors out there never release the Echo ping when they don't see a response so they lock up the whole setup. I got around this by running the echoes through tri-state buffers which are turned on individually by holding the Trig down for 50ms - the trigger actuates on the falling edge, holding it down longer that 10us is not harmful. Timing on I2C is too slow/inaccurate for measurement, so the echo signal goes through a hardware interrupt ping and timing is measured asynchronously using interrupt callbacks.

This is documented at https://hackaday.io/project/19950-hc-sr04-i2c-octopus-octosonar

Code at https://github.com/arielnh56/OctoSonar

It's all open sourced for DIY, or completed 8 port and 16 port boards on Tindie https://www.tindie.com/stores/arielnh56/

zhomeslice

#7
Nov 01, 2017, 04:23 am Last Edit: Nov 01, 2017, 04:25 am by zhomeslice
I worked on a solution for this myself very similar to what you described in the first post. I initiated the trigger through a pin expander chip (PCF8574) and muxed the results through an 8 way OR gate. Unfortunately some sensors out there never release the Echo ping when they don't see a response so they lock up the whole setup. I got around this by running the echoes through tri-state buffers which are turned on individually by holding the Trig down for 50ms - the trigger actuates on the falling edge, holding it down longer that 10us is not harmful. Timing on I2C is too slow/inaccurate for measurement, so the echo signal goes through a hardware interrupt ping and timing is measured asynchronously using interrupt callbacks.

This is documented at https://hackaday.io/project/19950-hc-sr04-i2c-octopus-octosonar

Code at https://github.com/arielnh56/OctoSonar

It's all open sourced for DIY, or completed 8 port and 16 port boards on Tindie https://www.tindie.com/stores/arielnh56/
Interesting and different. The versions I have done required little to no extra hardware.
I have done several versions of the HC-SR04 ping sensor. The inline one is a way to use 2 pine with 1 for input and 1 for pint initialization allowing for multiple ping sensors I am not sure of a limiting factor. I could see 20 to 30 HC-SR04's. The drawback is that it takes some time waiting for the timeout when no ping is detected on the HC-SR04 before triggering the next HC-SR04.



My favorite by far is this one  where I have code allowing for any of the Inputs to take a reading at any time. The ping is generated at the same time for all HC-SR04 sensors. and the timing occurs when each input pin rises and again when it falls. I have enabled interrupts on all pins associated with each HC-SR04 this could handle up to  17 HC-SR04's on clone pro Mini with 7 Analog inputs but I have only tested it with 8 HC-SR04's. The accuracy and speed are amazing! 



Quote
Unfortunately some sensors out there never release the Echo ping when they don't see a response so they lock up the whole setup.
I got around this by setting up each input with its own hardware interrupt as well as I repeatedly pulse the trigger on every HC-SR04 every 50ms. If the echo hasn't returned nothing happens the trigger is ignored.
because each input is connected to its own HC-SR04 the timing is based on the rise and fall of that input and not on the trigger pulse so in reality the trigger pulse could be generated by a 555 timer or at random the trigger pulse is irrelevant to the timing it only necessary to get things started.
so with every pin an interrupt input

This is older Version of my code and is better suited for this forum. It is not as complex as my class version.
This version does wait till all HC-SR04's have restored but since they all trigger at the same time the delay is usually minimal.
See Attached File
Z
HC

Go Up