Yet another arduino sound localizer (2 microphones, angle of arrival)

Hi everyone!

I'm doing a project for Biomedical Signal Acquisition, with a friend.
The goal is to localize sound inside a hospital room with, for example, 6 sleeping people.
WHY? During night, people with respiratory diseases might cough. Monitoring their cough history might help in diagnostics and therapy success assessment. BUT, in order to achieve proper monitoring, the monitoring system should be able to attribute sounds to the specific patient that coughed. Thus, it is need to estimate the angle of arrival of a given sound.

To achieve that, an electronic circuit to acquire signals from 2 microphones into the arduino was built.
In summary, the two microphone signals are: 1) amplified and then 2) compared. (I will get graphics of the circuit for anyone interested in this.)
In the end, what the circuit achieves is: each time there is some sound in the room, a transient square wave spanning the time the sound is being made is fed into the arduino - this happens for each microphone, of course. Because both microphones' circuits are built exactly the same, and they're only 10cm apart, they should have virtually the same properties in terms of time-frequency responses each time some sound is made. Thus, the two signals should only differ in their phase, since sound will arrive at different times at each microphone.

Having these two digital signals going into two digital pins in arduino, I can simply record times of activation of each mic and calculate their difference time of arrival. EZ, right?

So, right now, my friend already has the two signals going great in the oscilloscope (it's either 0V when it's silence, or 5V when someone coughs, claps, etc.), but I'll be only able to test things Wednesday, when I arrive to the lab.

Here's the code I have.

/////ARDUINO SCRIPT FOR DIFFERENCE TIME OF ARRIVAL CALCULATION (USING 2 MICROPHONES) ON MEGA2560/////


//// Loop time-control variables
unsigned long currentMicros;
unsigned long previousMicros;
unsigned long halfPeriod = 50;          // in microseconds
unsigned long elapsedMicros;


//// Signal processing variables
double value1;                          // records value in mic1
double value2;                          // records value in mic2
boolean state = false;                  // records whether an ongoing transient is happening (be it either on mic1 or mic2)
unsigned long firstTime;                // records the time of the first transient (in any mic)
int firstMic;                           // records which mic is activated first
unsigned long timeDiff;                 // records timeDifference between arrival of signals
double angle;                           // records angle of arrival



//// SETUP
void setup() {
  
  // set pins DIGITAL53 & DIGITAL51 as inputs (for ATmega2560: D53->PB0, D51->PB2)
  DDRB=B11111010;
  
  // initialize serial communication at 9600 bits per second [debugging purposes]
  Serial.begin(9600);

  // start control loop variable
  previousMicros = micros();
  
}



//// LOOP
void loop() {
  
  while(1){ // eliminate loop jitter
    
    currentMicros = micros();
    elapsedMicros = currentMicros - previousMicros;
    
    // this imposes the desired loop speed
    if (elapsedMicros >= halfPeriod){
      
      value1 = PINB&B00000001;
      value2 = PINB&B00000100;

      // if one of the mics is activated
      if(state) {

        // check which mic is activated
        switch (firstMic) {
          case 1:
            if(value2>0.9) {
              timeDiff = firstTime - micros();
              state=false;
            }
            break;
          case 2:
            if(value1>0.9) {
              timeDiff = micros() - firstTime;
              state=false;
            }
            break;
        }

        // if we had activation (state is true) and we had a subsequent second transient (state goes to false) and thus we can calculate the angle
        if(state==false) {
          angle = (57.29578*timeDiff*0.000001*343)/(0.1);
          delay(0.2);
        }

        // resets 'state' variable after some time (1 second) without identifying second transient (this avoids stupid situations)
        if(micros()-firstTime>1000) {
          state = false;
        }

      } else { // if we're not in activated state

        // we check for mic1
        if(value1>0.9) {
          firstTime = micros();
          state = true;
          firstMic = 1;
        }

        // we check for mic2
        if(value2>0.9) {
          firstTime = micros();
          state = true;
          firstMic = 2;
        }
      }
      previousMicros = micros();   
    }
  }
}

As is obvious from the code, I try to sample the input signals at some specified rate (right now, I'm thinking of reading up values from the pins each 50microseconds), so I can have some sort of a systematic reading error in both microphones. But I'm not sure whether I'll accomplish what I want with this code.

Basically, although I already read through some posts and tested myself some aspects of this issue, I'm still insecure regarding some options made: 1) the way I read the digital values (type of variables used and port manipulation), 2) the way I control the loop to read values and do things (previousMicros, elapsed, etc) and 3) other general aspects of the code that I might not have thought of.

If this project turns out nicely, I would very much appreciate to share the full report and code here on the forum.

Stay cool, and thanks so much to the whole forum staff and community!
Great work! :wink:

Since you have two nice square waves to work with, I would write the program to use one interrupt for one microphone and a second interrupt for the other microphone and record the time each interrupt occurred. Then in the main loop, find which interrupt occurred first and the time of the second interrupt and that would give you a number for the arrival time of the sound. No other variable are involved..

Paul

Confused.

How are you differentiating a person who is coughing vs. all the other noises that occur in a hospital room with 6 people in it?

Arduino is not well suited to do sound discerning on its own.

As is obvious from the code, I try to sample the input signals at some specified rate (right now, I'm thinking of reading up values from the pins each 50microseconds), so I can have some sort of a systematic reading error in both microphones. But I'm not sure whether I'll accomplish what I want with this code.

To minimize the (randomly distributed within the loop interval) timing error, I would poll the two digital inputs as fast as possible, using direct port reads. This is actually faster and less complicated than using interrupts.

Worry about correlating the two input times later to determine the angle to the source, the far-field derivation of which can be found here.

Here is a very simple example of fast polling of a digital input using a state machine to detect falling edges.

// example of non blocking input polling by state change
char PD2_state = 0;
unsigned long PD2_time = 0;
void setup() {
  Serial.begin(115200);
  pinMode(2, INPUT_PULLUP); //PORTD, pin 2, digital pin 2
  Serial.println("detecting falling edges on pin 2");
}

void loop() {
  if (PD2_state != (PIND & (1 << 2))) { //Check PD2 state, fast read
    PD2_state = PIND & (1 << 2); //changed. Save current state
    if (PD2_state == 0) {  //falling edge?
      PD2_time = micros(); //save time of falling edge
      Serial.println(PD2_time); //do something, like print the time
    }
  }
}

Note, to detect a rising edge, make the following change:

 if (PD2_state == (1<<2) {  //rising edge?

I suspect the OP will eventually give up because of echos from the smooth, very solid walls of the room.

Paul

Or put drapes on the walls. Great learning experience!

jremington:
Or put drapes on the walls. Great learning experience!

In your home or in a motel, but no such thing in a hospital room because all the surfaces must be disinfected at least weekly. That includes floors and ceilings and walls and doors, etc.

Paul

Indeed, an excellent learning opportunity.

Paul_KD7HB:
Since you have two nice square waves to work with, I would write the program to use one interrupt for one microphone and a second interrupt for the other microphone and record the time each interrupt occurred. Then in the main loop, find which interrupt occurred first and the time of the second interrupt and that would give you a number for the arrival time of the sound. No other variable are involved..

Paul

Yes, this is something to consider (my professor also suggested this, in a brief conversation we had in the beggining). But, because I'm not very confident with Arduino yet, I thought of making just a simple fast digital read. If I don't find a solution with that approach, I will surely look into this one!

Thanks so much!

Slumpert:
Confused.

How are you differentiating a person who is coughing vs. all the other noises that occur in a hospital room with 6 people in it?

Arduino is not well suited to do sound discerning on its own.

That's a relevant question that I didn't address in my first post.
Basically, the project comprises two challanges:

  1. an arduino processing angles of arrivals of every transient sound recorded (what I'm currently working at)
  2. a Teensy (or Processing software) making some sort of cough recognition task to record and register only the time and place of coughing sounds to the database

jremington:
To minimize the (randomly distributed within the loop interval) timing error, I would poll the two digital inputs as fast as possible, using direct port reads. This is actually faster and less complicated than using interrupts.

Worry about correlating the two input times later to determine the angle to the source, the far-field derivation of which can be found here.

Here is a very simple example of fast polling of a digital input using a state machine to detect falling edges.

// example of non blocking input polling by state change

char PD2_state = 0;
unsigned long PD2_time = 0;
void setup() {
  Serial.begin(115200);
  pinMode(2, INPUT_PULLUP); //PORTD, pin 2, digital pin 2
  Serial.println("detecting falling edges on pin 2");
}

void loop() {
  if (PD2_state != (PIND & (1 << 2))) { //Check PD2 state, fast read
    PD2_state = PIND & (1 << 2); //changed. Save current state
    if (PD2_state == 0) {  //falling edge?
      PD2_time = micros(); //save time of falling edge
      Serial.println(PD2_time); //do something, like print the time
    }
  }
}



Note, to detect a rising edge, make the following change:



if (PD2_state == (1<<2) {  //rising edge?

Looks promising. Will get back with news ASAP.

jremington:
Here is a very simple example of fast polling of a digital input using a state machine to detect falling edges.

// example of non blocking input polling by state change

char PD2_state = 0;
unsigned long PD2_time = 0;
void setup() {
 Serial.begin(115200);
 pinMode(2, INPUT_PULLUP); //PORTD, pin 2, digital pin 2
 Serial.println("detecting falling edges on pin 2");
}

void loop() {
 if (PD2_state != (PIND & (1 << 2))) { //Check PD2 state, fast read
   PD2_state = PIND & (1 << 2); //changed. Save current state
   if (PD2_state == 0) {  //falling edge?
     PD2_time = micros(); //save time of falling edge
     Serial.println(PD2_time); //do something, like print the time
   }
 }
}



Note, to detect a rising edge, make the following change:



if (PD2_state == (1<<2) {  //rising edge?

(...)

peconsti:
Looks promising. Will get back with news ASAP.

I ran the sketch both with falling and rising edge options. None of them outputted results with simple square-wave digital signals I could test with.
Besides that, I think I don't understand what the following segment does: PIND & (1 << 2). Understanding it would help with debugging.

Thanks so much for the replies so far.

PS: I AM WORKING WITH ATMEGA2560

Okay, I did my work.
Basically, in order to read digital pins 2 and 3, for example, I have to use PORTE and not PORTD. I guess that applies to other models of arduino, but not ATmega2560.

The following code is what I have now, based on the recommendations by jremington. Tomorrow I'm testing it and giving news.

// NEW SKETCH BASED ON STATE POLLING OF TWO MICROPHONES

// Microphone 1 variables (digital pin2 [PE4 on ATmega2560])
char PE4_state = 0;
unsigned long PE4_rise_time = 0;

// Microphone 2 variables (digital pin3 [PE5 on ATmega2560])
char PE5_state = 0;
unsigned long PE5_rise_time = 0;

// Working variables
unsigned long time_difference;
double angle;


// SETUP
void setup() {
  Serial.begin(115200);
  pinMode(2, INPUT); //PORTE, pin 2, digital pin 2
  pinMode(3, INPUT); //PORTE, pin 3, digital pin 3
}


// LOOP
void loop() {

  // Microphone 1 fast poll
  if (PE4_state != (PINE & (1 << 4))) { //Check PE4 state, fast read
    PE4_state = (PINE & (1 << 4)); //changed. Save current state
    if (PE4_state == (1<<4)) {  //rising edge?
      PE4_rise_time = micros(); //save time of falling edge
    }
  }

  // Microphone 2 fast poll
  if (PE5_state != (PINE & (1 << 5))) { //Check PE5 state, fast read
    PE5_state = (PINE & (1 << 5)); //changed. Save current state
    if (PE5_state == (1<<5)) {  //rising edge?
      PE5_rise_time = micros(); //save time of falling edge
    }
  }

  // If both microphones are activated, proceed to angle of arrival calculations
  if(((PE4_state!=0) & (PE5_state!=0))) {
    // Calculations go here
    time_difference = PE4_rise_time - PE5_rise_time;
    angle = (57.29578*time_difference*0.000001*343)/(0.1);
    Serial.println(angle);
  }
 
}
// If both microphones are activated, proceed to angle of arrival calculations

If one microphone is activated, it is very likely that the other will be too. So, you might make some sort of "reasonableness" test on the time difference of arrival before bothering with the angle calculation.

For a valid trigger, the time difference of arrivals can't be longer than (distance between microphones)/(speed of sound).

jremington:

// If both microphones are activated, proceed to angle of arrival calculations

If one microphone is activated, it is very likely that the other will be too. So, you might make some sort of "reasonableness" test on the time difference of arrival before bothering with the angle calculation.

For a valid trigger, the time difference of arrivals can't be longer than (distance between microphones)/(speed of sound).

Yup, also thought about that. I think I'll try to write that code on the spot, to see what works best.
Thanks so much, jremington!

If the microphone module emits a HIGH signal in response to say, a cough, how long does that signal remain HIGH?

Let us know how you get on with the project.