0

I am reading sound input every 60 seconds and I would like to calculate the average of sound during that period using non-blocking code; what should I do to change the way that I calculate it in separated function as I need to do something else with other sensors.

int calculateSoundValue(){
    int average = analogRead(0);
    // loop to calculate the average simple formula sum div nbr of calculated times
    return average;
}

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

}
void loop() {
  // I donùt wanna calculate the average inside a loop but instead inside a function 
  int value = calculateSoundValue();
  Serial.println(value);

  delay(600);
}

UPDATE

Is this what you mean ?

unsigned long previousMillis = 0;
const long interval = 6000;  
int totalSamples = 0; 
int count = 0

    int calculateSoundValue(){
        int sound = analogRead(0);
        totalSamples+= sound;
        count += 1;
        return totalSamples/count;
    }

void setup() {
  // set the digital pin as output:
  pinMode(ledPin, OUTPUT);
}

void loop() {

  unsigned long currentMillis = millis();
int totalSamples = 0; 
int count = 0
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;

  }
      int value = calculateSoundValue();
      Serial.println(value);
}
  • 2
    Do you know how average is calculated? – Avamander Mar 14 '16 at 20:15
  • 1
    Do you know what sound is? – Majenko Mar 14 '16 at 21:02
  • sleep(600) doesn't look like a minute, did you mean delay()? To do stuff with other sensors, you would have to have non-blocking code when you do the averaging on the reading. – RSM Mar 14 '16 at 21:06
  • 1
    You don't need fancy software to tell you what the average sound over a minute will be... The answer is zero - or as near as makes no odds. – Majenko Mar 14 '16 at 21:40
  • 2
    @RSM It's the classic X-Y Problem - the OP thinks they know what they want and how to achieve it, but can't work out how to implement that method - however the underlying method, and their basic understanding of it all, is completely wrong, so even if they manage to implement it as they think it should be it still isn't going to work. They're basically asking the wrong question. – Majenko Mar 15 '16 at 12:09
2

I assume what you actually want is to measure sound intensity. This is significantly more involved than just averaging the samples you read. I have written a few weeks ago a program that does exactly that: it measures the sound intensity and sends those measurements through the serial port. It is a pretty short program (43 lines of code) you may be able to adapt to your needs, so I am now sharing it as a GitHub gist: sound-meter.ino.

In order for this not to be a link-only answer, I'll try to explain the program's inner working, and go through the theory of measuring sound intensity on an Arduino. But first let me put here a disclaimer: a “real” sound level meter should have a frequency response that complies with some standards, and it should be calibrated to absolute levels. This program only provides uncalibrated readings with whatever frequency response its analog hardware has.

Signal conditioning

Four measuring sound on an Arduino, you obviously need a microphone, but you also need some kind of interface circuit between the microphone and the Arduino. The purpose of the interface is to “condition the signal”, i.e. to make sure the Arduino gets a voltage in the proper range for it's analog-to-digital converter (ADC). It's typically an op-amp based circuit that provides some amplification and also adds a DC bias to avoid clipping the negative side of each oscillation. Ideally, the DC bias should be around Vcc/2, i.e. 2.5 V on an Arduino Uno.

See this SparkFun microphone breakout for an example on how such a circuit may be built (click on “Schematic”).

ADC sampling

Once you have a properly conditioned analog signal, you have to sample it fast enough to catch all its spectral content. The Nyquist–Shannon sampling theorem tells us the sampling rate should be at least twice the highest frequency in the signal. For audio signals, sampling rates of the order of 8 kS/s (kilosamples per second) are typical of low quality, telephone-like applications, whereas high quality audio would run at 44.1 to 48 kS/s.

The ADC of the Arduino Uno, in its default configuration, takes 104 µs to convert one sample. If you just call analogRead() in a tight loop, this gives a sampling rate of about 9.6 kS/s, which is fine for this kind of application, unless we have to measure noise above 4.8 kHz. The problem with this approach is that analogRead() is a blocking function. Calling it in a tight loop means the program will spend most of its time just waiting for the ADC. If you do anything else in the loop, not only will the sampling rate be lowered, it will also become unsteady, which is generally not desirable.

In order to avoid blocking the CPU, we have to forgo analogRead(), and instead get our hands dirty with low-level configuration of the ADC. The ADC of the Arduino Uno offers a so-called “free running mode”, which is well suited for this kind of application. In this mode, the ADC starts a new conversion as soon as the previous one is done, ensuring a very consistent sampling rate of 9.6 kS/s. It can also trigger an interrupt after each conversion, in order for the interrupt service routine to retrieve and process the sample. See the datasheet of the ATmega328P for details on configuring the ADC.

Removal of the DC bias

Once we have the digitized samples, the first thing to do is to remove the DC bias that was added by the interface circuit. If the bias is stable and known (maybe determined by experiment), this only involves subtracting a constant. If it is not known, it can be estimated by running the samples through a digital low-pass filter with a cutoff frequency below the range of interest. Subtracting the output of a low-pass filter is essentially building a high-pass filter. As an alternative, a numerical derivative (difference between consecutive samples) can be used as a crude high-pass filter. This, however, will bias the sensitivity of the detection towards the high end of the spectrum. Depending on the noise you want to detect, and on the background noise, this could be acceptable for your application.

In the program I am sharing, I assume the DC bias is known: it's the dc_offset constant at the beginning of the program.

Instantaneous intensity

With the DC bias removed, the signal we have is a digital image of the sound picked by the microphone. It could be tempting to just average it as is. This, however, would just give zero, as the positive fluctuations of the signal would cancel the negative ones.

The are several options to solve this problem. We could do a peak detection instead of an average, or we could take the absolute value before averaging. The most popular option is probably to compute the squares of the samples. Since the square of a real number is always positive, this avoids the averaging-out problem. But the square has also a deeper physical meaning: it is proportional to the sound intensity, i.e. the energy carried by the sound wave.

Low-pass filter

The intensity computed above is a very fast varying quantity. If the signal is a simple tone of constant frequency and volume, the instantaneous intensity fluctuates between zero and some maximum at twice the tone frequency. In order to recover the “constant volume” property of the sound, we need to perform some sort of averaging or, in other words, we need a low-pass filter.

There are many ways to implement a digital low-pass filter. The most intuitive may be to group the samples into batches, and then compute the average of each batch. However, this is neither the best nor the most efficient to implement. When I need a quick and simple low pass filter, I generally use an exponential moving average. This is the discrete-time equivalent of an analog RC filter, and it can be implemented very efficiently by remembering the previous output and updating it as

output += input - output / N;

where, for efficiency reasons, N should be a power of two. The filter's time constant is N/fs, where fs is the sampling frequency. In the program I use N = 256, which makes the division virtually free. This gives a time constant of 26.6 ms. You can tune the time constant to your needs simply by changing N, but make sure it is still a power of two.

Decimation

If you only want to trigger an event when the sound intensity exceeds a defined level, you may not need this. But if you have other processing to do with those measurements, it is probably not useful to process more than one intensity reading per time constant of the previous filter. That's why the program has a decimation step. This is simply the ISR periodically signaling the main program that an intensity reading is available. Notice that, since the sample count is an 8-bit variable, it automatically counts modulo 256. If you want to increase the decimation factor, you should make sample_count wider and mask out the bits you do not need.

| improve this answer | |
1

You want to find the average of doing an analogRead over a minute, is that it?

How about this?

unsigned long total;
unsigned int count;

unsigned long whenStarted;

const unsigned long INTERVAL = 60000;  // one minute

void setup ()
{
  Serial.begin (115200);
  Serial.println (F("Starting ..."));
  whenStarted = millis ();
}  // end of setup

void loop ()
{
  // one more sample
  total += analogRead (0);
  count++;

  if (millis () - whenStarted >= INTERVAL) // is a minute up?
    {
    Serial.print (F("Average = "));
    Serial.println (total / count);
    total = 0;
    count = 0;
    whenStarted = millis ();
    }
}  // end of loop
| improve this answer | |
  • Thanks! can I put total += analogRead (0); count++; on q function and replace it like I did – user3378649 Mar 14 '16 at 23:19
  • 2
    ...but as already pointed out, averaging the readings won't accomplish anything useful – Chris Stratton Mar 14 '16 at 23:48
  • Thanks! can I put total += analogRead (0); count++; on q function and replace it like I did - I don't know what you mean by that. – Nick Gammon Mar 15 '16 at 0:24
0

The short answer is use millis() to keep track of time. You can also put all the time related stuff inside a class to have a nicely organized code. You can get more info about time management on the Arduino here. https://learn.adafruit.com/multi-tasking-the-arduino-part-1/ditch-the-delay

| improve this answer | |
  • 1
    You probably mean micros(), not millis(): If you want to sample the ADC at rates relevant to audio signals, millis() will not provide fine enough resolution. – Edgar Bonet Mar 16 '16 at 14:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.