2

I am looking for a more elegant way to write a function that performs timer-based tasks as in the case of the code below, which essentially collects 5 sample values from a microphone module over a given period of time and then computes their average. It is important that it does not use the delay() function because other tasks are supposed to run in the background which should not be affected by the delay.

  int sensorPin = A0; 
  int ledPin = 13; //  pin for the LED
  int sensorValue = 0; // variable to store the value coming from the sensor
  unsigned long timer;
  int interv = 50;
  unsigned long sampleArray[] = {0, 0, 0, 0 , 0};
  unsigned long millisaver;


  void setup ()
  {
    pinMode (ledPin, OUTPUT);
    Serial.begin (9600);

  }

  void loop ()
  {

    if (millis() > 2000) {
      if (millis() < 2050) {
        millisaver = millis();
        timer = (int) millisaver;
        Serial.println(timer);
      }
      if ( millis() > timer && millis() < timer + interv ) {
        sampleArray[0] = sensorValue = analogRead (sensorPin);
      }
      else if ( millis() > timer+interv && millis() < timer + interv*2 ) {
        sampleArray[1] = sensorValue = analogRead (sensorPin);
      }
      else if ( millis() > timer+interv*2 && millis() < timer + interv*3 ) {
        sampleArray[2] = sensorValue = analogRead (sensorPin);

      }
      else if ( millis() > timer+interv*3 && millis() < timer + interv*4 ) {
        sampleArray[3] = sensorValue = analogRead (sensorPin);

      }
      else if ( millis() > timer+interv*4 && millis() < timer + interv*5 ) {
        sampleArray[4] = sensorValue = analogRead (sensorPin);
      }
      else if ( millis() > timer+interv*5) {
        unsigned long averageVal = (sampleArray[0] + sampleArray[1] + sampleArray[2] +sampleArray[3] + sampleArray[4])/5 ;
        Serial.println(averageVal);
        millisaver = millis();
        timer = (int) millisaver;
      }
    }
  }
  • 1
    Other than using interrupts? – Ignacio Vazquez-Abrams Aug 27 '16 at 9:22
  • Might be possible to adjust the ADC clock frequency to match 20 Hz and use the ADC interrupt for data collect. – Mikael Patel Aug 27 '16 at 9:28
  • yes, other than using interrupts, if possible. – Ginzburg Aug 27 '16 at 9:54
  • 1
    @Grovelli why not an interrupt? That is precisely what they're for. The Arduino libs are great, but at some point, you're going to want to understand the actual Mcu on the board. I know the data sheets are daunting, but it is well worth diving in. – RubberDuck Aug 27 '16 at 11:00
  • 1
    By the way, do you know what the average of a sound wave is over time? You don't need a microphone to discover that it's 0. – Majenko Aug 27 '16 at 11:07
3

For excact timing there is feature called ADC Auto Trigger mode. For example millis() are usually counted by timer 0 overflow (on AVR based Arduinos) every 1024us (I suppose it's because of PWM - otherwise it could be set exactly to 1ms). So if you don't mind that little offset, you can configure ADC with Timer 0 Overflow and use every 20 measure only. But don't forget that analogRead on another pin changes ADC channel and you will get something else if you don't check it.

As an auto trigger source can be used one of these:

  • Free Running Mode
  • Analog Comparator
  • External Interrupt 0
  • Timer/Counter 0 Compare Match A
  • Timer/Counter 0 Overflow
  • Timer/Counter 1 Compare Match B
  • Timer/Counter 1 Overflow
  • Timer/Counter 1 Capture Event

For ATMega328 it can be something like this:

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

    // ADC setup is done by arduino framework, but it's possible to change it slightly (for ATMega328) :
    ADCSRB  = _BV(ADTS2); // ADTS2..0 = 100, Timer 0 overflow as trigger source
    ADCSRA |= _BV(ADATE); // enable auto trigger mode    
    ADCSRA |= _BV(ADIF); // reset conversion interrupt flag (by writing log 1)

    analogRead(A0); // dummy read to set correct channel and to start auto trigger mode
}

int8_t sample_count = 0;
int8_t sample_index = 0;
uint16_t  samples[] = {0, 0, 0, 0, 0};
uint16_t sample_sum = 0;

void loop() {
    if (ADCSRA & _BV(ADIF)) {
        ADCSRA |= _BV(ADIF); // reset ADIF flag by writing logic 1 into it (mentioned in datasheet)
        if (++sample_count == 20) {
            sample_count = 0; // reset sample_count, as we wan't every 20th

            sample_sum -= samples[sample_index];
            samples[sample_index] = ADC; // read value
            sample_sum += samples[sample_index];

            if (++sample_index == 5) { // reset index and count and send average
                sample_index = 0;
                Serial.println(sample_sum);
            }
        }
    }
}
| improve this answer | |
  • Thank you for your detailed answer! I will research on the ADC auto trigger mode as I am frankly new to it. – Ginzburg Aug 27 '16 at 12:19
  • You probably shouldn't mess with TIMER0's timing. The Arduino libs use that timer for things like millis(). Which libraries use which timers is described here. arduino-info.wikispaces.com/Timers-Arduino – RubberDuck Aug 27 '16 at 18:16
  • @RubberDuck I didn't mess with TIMER0 timing. I did't touch it at all. I've just used it as auto trigger source to perform ADC conversion every 1.024ms and I'll use every twenty measure of these. – KIIV Aug 27 '16 at 18:24
  • My misunderstanding @KIIV – RubberDuck Aug 27 '16 at 18:25
2

You didn't say what you want to do with those samples. If you want to estimate the level of sound intensity, your whole approach is completely wrong. First, your sampling rate (20 Hz) is way too low: you should sample at least at twice the frequency of interest, typically at 8 kHz or more. Then you have to subtract the DC offset added by the microphone module, then square the result in order to get an instantaneous intensity, then low-pass filter to get a usable intensity reading. As Majenko said, if you average (or low-pass filter) the samples before squaring, you would detect zero sound.

I've written a sound meter program for an Arduino Uno that does exactly that, and it's available as a GitHub Gist: sound-meter.ino. It uses the auto-trigger mode as explained by KIIV, but with free running mode instead of a timer. This gives 9.6 kHz sampling rate.

| improve this answer | |
  • My apologies, I just realized low_pass() was not a standard function off a library. – Ginzburg Aug 27 '16 at 12:44
  • 1
    @Grovelli: It's an exponentially weighted moving average, the digital analog of an RC filter: one of the simplest low pass digital filters you can make. – Edgar Bonet Aug 27 '16 at 12:48
1

You need to make a function which is pretty much completely self contained. Within the function should be a counter so it knows which sample it is on, and a timestamp to know when it should next run. The function should also inform the caller when it has a full sample set to process.

I haven't tested this (don't know if it even compiles) but something like this might work:

bool getSample(uint8_t pin, unsigned long *samples) {
    static uint32_t ts = millis();
    static uint8_t sampleNumber = 0;

    if (millis() - ts >= 50) {
        ts += 50;
        samples[sampleNumber++] = analogRead(pin);
        if (sampleNumber == 5) {
            sampleNumber = 0;
            return true; // We have 5 samples
        }
    }
    return false; // We don't have 5 samples yet.
}

// ... later ...

if (getSample(sensorPin, sampleArray)) {
    // Process Samples
}
| improve this answer | |

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.