Go Down

Topic: What is the best way to connect 8 sensors to the Arduino Due? (Read 3175 times) previous topic - next topic

palomban

I'm trying to determine the best way to connect eight Vivotech HC-SR04 Ultrasonic Distance sensors to the Due.

A document that shows how to communicate with this sensor is here:
http://www.micropik.com/PDF/HCSR04.pdf

Eight green and eight red LEDs will also be connected to the Due as outputs. The immediate goal is to have a red LED light up when an object comes within 20cm of the corresponding sensor. The ultimate goal is to have code that makes intelligent decisions on which LED to light up based on the the distances of objects from each sensor. The decision making algorithm needs to know the readings from each sensor continuously or as much as possible. Obviously, this is much more advanced.

Here is the code I have now, which I haven't had a chance to test yet. I'm sure it is not the most efficient. Please help me improve this code, so that I can more easily update it for the decision making algorithm. (The code only accounts for 2 sensors right now to make the wiring easier)

Code: [Select]

#define trigPin 13
#define echoPin1 12
#define echoPin2 4
#define red1 11
#define green1 10
#define red2 3
#define green2 2

long threshold = 20; //Distance under which the red LEDs will turn on
long trigTime, duration, distance;


void setup() {
  pinMode(trigPin, OUTPUT);
  pinMode(red1, OUTPUT);
  pinMode(green1, OUTPUT);
  pinMode(red2, OUTPUT);
  pinMode(green2, OUTPUT);

  attachInterrupt(echoPin1, sensor1, RISING);
  attachInterrupt(echoPin2, sensor2, RISING);
}

void loop() {
  //Trigger sensors
  noInterrupts();
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  trigTime = micros();
  interrupts();
  while(micros() - trigTime < 100000) {
     //Wait for interrupts
     //Trigger sensors 10 times per second
  }
}


void sensor1(){
  //Calculate sound signal duration
  duration = micros() - trigTime;
  //Calculate distance
  distance = duration/58;
  //If object within threshold of sensor 1
  if (distance < threshold) {
    digitalWrite(red1,HIGH); //Turn red LED on
    digitalWrite(green1,LOW); //Turn green LED off
   }
  else {
   digitalWrite(red1,LOW); //Turn red LED off
   digitalWrite(green1,HIGH); //Turn green LED on
  }
}

void sensor2(){
  //Calculate sound signal duration
  duration = micros() - trigTime;
  //Calculate distance
  distance = duration/58;
  //If object within threshold of sensor 2
  if (distance < threshold) {
    digitalWrite(red2,HIGH); //Turn red LED on
    digitalWrite(green2,LOW); //Turn green LED off
   }
  else {
    digitalWrite(red2,LOW); //Turn red LED off
    digitalWrite(green2,HIGH); //Turn green LED on
  }
}

KenF

Do you just have the one trigger for ALL of the sensors, or do they all have their own trigger pin?

palomban

I can trigger all the sensors with the same pin. This is confirmed to work.

KenF

How does this look?
Code: [Select]

class SensorObject
  {public:
   int echoPin;
   int redPin;
   int greenPin;
   bool pinged;  //used to record if current ping has been recieved yet
   SensorObject(int echoPin, int redPin, int greenPin);
   void begin();
  };

SensorObject::SensorObject(int ep, int rp, int gp)
{
echoPin=ep;
redPin=rp;
greenPin=gp;
}
void SensorObject::begin()
{
pinMode(redPin, OUTPUT);
pinMode(greenPin, OUTPUT);
attachInterrupt(echoPin, pingReturn, RISING);
}

#define sensorCount 2
#define trigPin 13

SensorObject mySensors[sensorCount]={
  SensorObject(12,11,10),
  SensorObject(4,3,2)
  };

void setup()
{
for (int n=0; n< sensorCount; n++)
  mySensors[n].begin();
}

long threshold = 20; //Distance under which the red LEDs will turn on
long trigTime, duration, distance;

void loop() {
  //Trigger sensors
  noInterrupts();
for(int n=0;n<sensorCount;n++)
  mySensors[n].pinged=false;

  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  trigTime = micros();
  interrupts();
  while(micros() - trigTime < 100000) {
     //Wait for interrupts
     //Trigger sensors 10 times per second
  }
}

void pingReturn(){
  //Calculate sound signal duration
  duration = micros() - trigTime;
  //Calculate distance
  distance = duration/58;
 
//find out which sensor has recieved the "ping"
//(ignore any that have already responded for this ping)
for( int n=0;n<sensorCount;n++)
  if(digitalRead (mySensors[n].echoPin) && (!mySensors[n].pinged))
    {mySensors[n].pinged=true;
      if (distance < threshold)
      {
      digitalWrite(mySensors[n].redPin,HIGH); //Turn red LED on
      digitalWrite(mySensors[n].greenPin,LOW); //Turn green LED off
      }
  else
    {
    digitalWrite(mySensors[n].redPin,LOW); //Turn red LED off
    digitalWrite(mySensors[n].greenPin,HIGH); //Turn green LED on
    }
  }
}

palomban

Thank you KenF! I like how organized this code is and how easy it is to use an array of sensor objects. I do have a concern with the line "if(digitalRead (mySensors[n].echoPin) && (!mySensors[n].pinged))" in the pingReturn() interrupt, though. I'm not sure if the echoPin for the sensor that called the interrupt would be high at the time this if statement were called. Also, other echoPins may be high at the same time. The document that shows how to communicate with the sensors says that the echoPin stays high for a certain amount of time and this is another way to measure the distance read by the sensor. That is why in my code I call the interrupt with the RISING logic level. Calling the interrupt for HIGH resulted in flickering LEDs which I believe was due to the interrupt being called when it wasn't supposed to be.

I think I would add a newDistance and oldDistance variable to the sensor objects in order to implement the decision making algorithm. I'm not sure where I'd put the algorithm though. Maybe partially in the interrupt and partially in the loop.

I wonder if this sensor/interrupt relationship can be improved by implementing the Publish/Subscribe design pattern. I will look into that now.

raschemmel

If you're talking about sending multiple triggers from ultrasonic boards how are you going to distinguish
the echo of one from the other if you trigger them all at the same time . How does that work. One sensor
doesn't know the other exists and cannot distinguish it's own trigger from another sensor's. Did I misun-
understand what you were saying ?
Arduino UNOs, Pro-Minis, ATMega328, ATtiny85, LCDs, MCP4162, keypads,<br />DS18B20s,74c922,nRF24L01, RS232, SD card, RC fixed wing, quadcopter

palomban

If you're talking about sending multiple triggers from ultrasonic boards how are you going to distinguish
the echo of one from the other if you trigger them all at the same time . How does that work. One sensor
doesn't know the other exists and cannot distinguish it's own trigger from another sensor's. Did I misun-
understand what you were saying ?
Each sensor has one trigger and one echo pin. By connecting all 8 trigger pins to one Arduino output, all the sensors can be triggered at once. Each echo pin is attached to separate interrupt pins. This makes it possible to read from all the sensors ten times per second rather than read each about 1 time per second. The problem is an interrupt might be missed if another interrupt already running. That is one major problem I'm trying to solve. The decision making algorithm must react as quick as possible to the distance changes.

KenF

I'm not sure if the echoPin for the sensor that called the interrupt would be high at the time this if statement were called. Also, other echoPins may be high at the same time. p.
the "pinged" variable is set LOW for each sensor just before you send out a ping.  

When an interrupt is first called, there is no way of knowing which pin caused the interrupt.  So it searches through ALL the sensors echo pins to find the one that is CURRENTLY responding.

Having found the sensor that is currently responding it then sets its "pinged" variable (for this sensor) to HIGH so that any further interrupts ignore this sensor.


palomban

the "pinged" variable is set LOW for each sensor just before you send out a ping. 

When an interrupt is first called, there is no way of knowing which pin caused the interrupt.  So it searches through ALL the sensors echo pins to find the one that is CURRENTLY responding.

Having found the sensor that is currently responding it then sets its "pinged" variable (for this sensor) to HIGH so that any further interrupts ignore this sensor.


Thank you very much for the help KenF. I have changed the code to what I think should functionally work best for me. I'm pretty sure the way your code determined which sensor called the interrupt is inaccurate, because multiple echo pins can be high while pinged is set to false, if an interrupt is missed. I changed the code so that as little code as possible is in each interrupt. This way the interrupt should be exited before the other sound pings travel even just a centimeter.

If anyone can help me reduce the code to just one interrupt for all the sensors combined, that would be appreciated. It's a pain that the attachInterrupt() function can't take a char array or something for the ISR name.

Code: [Select]
#define trigPin 13 //The pin used to trigger the sensors
const long threshold = 20; //Distance under which the red warning LEDs will turn on
const int sensorCount = 2; //The number of sensors
long trigTime; //The time at which the sensors were most recently triggered

//A SensorObject will be created for each of the sensors
class SensorObject{
   public:
   int echoPin;
   int redPin;
   int greenPin;
   long distance;
   bool echoReceived;
   SensorObject(int echoPin, int redPin, int greenPin);
   void begin();
};

//SensorObject constructor
SensorObject::SensorObject(int ep, int rp, int gp){
  echoPin = ep;
  redPin = rp;
  greenPin = gp;
  distance = 0;
  echoReceived = false;
}

//Begin method used in setup() to set pin modes
void SensorObject::begin(){
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
}

//Create a global array of SensorObjects
SensorObject sensorArray[sensorCount] = {
  //SensorObject(echoPin, redPin, greenPin)
  SensorObject(12,11,10),
  SensorObject(4,3,2)
};

void setup(){
  for (int n = 0; n < sensorCount; n++)
    sensorArray[n].begin();
  attachInterrupt(sensorArray[0].echoPin, sensor1, RISING);
  attachInterrupt(sensorArray[1].echoPin, sensor2, RISING);
}

//Main loop runs approximately 10 times per second
void loop() { 
  //Trigger sensors (10 times per second)
  for(int n = 0; n < sensorCount; n++){
    sensorArray[n].echoReceived = false;
  }
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  trigTime = micros();
  interrupts();
 
  //Read sensors (10 times per second)
  while(micros() - trigTime < 100000) {/*Wait for interrupts for 100ms*/} 
 
  //Set LEDs (10 times per second)
  noInterrupts();
  setLEDs();
}

// This function updates the LEDs when called
void setLEDs(){
  for(int n = 0; n < sensorCount; n++){
    if((sensorArray[n].echoReceived == true) && (sensorArray[n].distance < threshold)){
      digitalWrite(sensorArray[n].redPin,HIGH); //Turn red LED on
      digitalWrite(sensorArray[n].greenPin,LOW); //Turn green LED off
    }
    else{
        digitalWrite(sensorArray[n].redPin,LOW); //Turn red LED off
        digitalWrite(sensorArray[n].greenPin,HIGH); //Turn green LED on
    }
  }
  //Insert decision algorithm here
}

//Sensor 1 interrupt
void sensor1(){
  //Calculate sound signal duration
  long duration = micros() - trigTime;
  //Calculate distance between sensor and object
  sensorArray[0].distance = duration/58;
  //Echo is received
  sensorArray[0].echoReceived = true;
}
 
//Sensor 2 interrupt
void sensor2(){
  //Calculate sound signal duration
  long duration = micros() - trigTime;
  //Calculate distance between sensor and object
  sensorArray[1].distance = duration/58;
  //Echo is received
  sensorArray[1].echoReceived = true;
}

KenF

I did a bit of reading up on this and it appears there's no simple solution.  An ISR cannot take parameters (so therefore it's not possible to specify which pin caused the interrupt).  It's also not possible to use a class member function as an ISR (unless it's static, which defeats the object of the exercise).

This is why my implementation goes through the process of checking each possible pin to ascertain which caused the interrupt.  Once found, it sets it's "pinged" attribute so that it can be ignored in subsequent interrupt calls.

Although, one slight bug I've noticed is that the declaration for "pinged" in the class definition should be volatile.  So where I have bool pinged this should read volatile bool pinged


dlloyd

I haven't thoroughly read this thread (I'm probably missing something) but just a quick comment that on the Arduino Due, the syntax is:

attachInterrupt(pin, ISR, mode)

As far as I know, every pin on the Due can have an interrupt attached to it.

KenF

As far as I know, every pin on the Due can have an interrupt attached to it.
This is true, but the interrupt for every one of the OPs sensors would be practically identical.  So although it's possible to have something like

Code: [Select]

void ISR1()
{

}

void ISR2()
{
//same as isr1 but for a different pin
}

void ISR3()
{
//same as isr1 but for a different pin
}

//etc....


The OP was looking for a way to tidy this up.

dlloyd

EDIT: Would need to simplify the code in the interrupt routine and do more in the main loop.

dlloyd

The Arduino Due is very fast, so the use of delayMicroseconds(10); can stall the MCU for most of the iteration time.

This example (untested) uses no interrupts and no delays.
The range is determined by measuring the echo pulse duration (pulse width).
To add additional sensors, just include the pin numbers.

Code: [Select]
const byte trigPin = 13;
const byte echoPin[] = {12, 4};     // add pins for additional sensors
const byte green[] = {10, 2};       // add pins for additional sensors
const byte red[] = {11, 3};         // add pins for additional sensors
const byte qty = sizeof(echoPin);
const double echoVelocity = 19.68503935; // velocity (39.3700787 inches/sec) / 2

unsigned long trigInterval = 100000, trigPreviousMicros;
unsigned long echoStart[qty], echoDuration[qty];
double echoRange[qty], threshold = 20; //Distance under which the red LEDs will turn on
boolean echoState[qty], echoStatePrevious[qty];

void setup() {
  pinMode(trigPin, OUTPUT);
  for (int i = 0; i < qty; i++) {
    pinMode(echoPin[i], INPUT);
    pinMode(red[i], OUTPUT);
    pinMode(green[i], OUTPUT);
    echoStart[i] = 0;
    echoDuration[i] = 0;
    echoRange[i] = 0.0;
    echoState[i] = LOW;
    echoStatePrevious[i] = LOW;
  }
}

void loop() {
  echoMeasure();
}

void echoMeasure() {
  if (micros() - trigPreviousMicros > trigInterval) {  // trigger sensors every 100ms
    digitalWrite(trigPin, HIGH);
    if ((micros() - trigPreviousMicros) > (trigInterval + 10)) {  // 10┬Ás duration
      digitalWrite(trigPin, LOW);
      trigPreviousMicros = micros();
    }
  }
  for (int i = 0; i < qty; i++) {
    echoState[i] = digitalRead(echoPin[i]);                         // read the inputs
    if ((echoState[i] == HIGH) && (echoStatePrevious[i] == LOW)) {  // if echo rising
      echoStart[i] = micros();
      echoStatePrevious[i] = echoState[i];
    }
    if ((echoState[i] == LOW) && (echoStatePrevious[i] == HIGH)) {  // if echo falling
      echoDuration[i] = micros() - echoStart[i];                    // echo duration (micros)
      echoRange[i] = echoDuration[i] * echoVelocity;                // echo range (inches)
      echoStatePrevious[i] = echoState[i];
    }
    if (echoRange[i] < threshold) {
      digitalWrite(red[i], HIGH);      // Turn red LED on
      digitalWrite(green[i], LOW);     // Turn green LED off
    }
    else {
      digitalWrite(red[i], LOW);       // Turn red LED off
      digitalWrite(green[i], HIGH);    // Turn green LED on
    }
  }
}

Go Up