BangLED - Understanding Sound Sensor

사운드 센서는 소리를 전기 신호로 변환하는 장치를 일컫는 것으로, 결국 Microphone Amplifier 즉 마이크를 뜻한다. BangLED 에 사용될 마이크는 다소 비용은 있지만 크기가 크면 팔찌에 장착하기 어려움으로 아래 사진과 같은 초소형 ADMP401 브레이크아웃 보드를 사용하기로 한다.

Breakout Board for ADMP401 MEMS Microphone

1. Soldering

3 Pin Header 를 일반적으로 납땜하여 테스트하나, 향후 BangLED 제작시를 고려하여 다소 어렵더라도 직접 단선을 연결하여 테스트하도록 한다. 왼쪽부터 VCC, GND, AUD 로 각각 연결한뒤 AUD 는 아두이노의 A0 와 연결한다.

ADMP401을 비롯한 MAX4466 등의 Mic 는 5V에서는 다른 액츄에이터(LED나 모터 등)과 함께 사용할때 문제가 있는 경우가 있다. 가급적이면 3.3v 를 사용하도록 하자.

2. Before Test

진폭

소리는 초등학교때 배웠다시피 아래 그림과 같이 파동으로 이루어져있다. 진폭의 크기와 파장의 길이에 따라 소리의 크기와 높낮이가 결정된다.

고음과 저음

파장이 길면 저음, 파장이 짧으면 고음.

음량의 차이

진폭이 크면 음량이 크고, 작으면 음량이 작음.

이러한 소리의 특성때문에 analogRead() 같은 함수를 사용하면 소리의 높낮이가 마치 계속 변화하는 것처럼 나타난다. 따라서, 약간의 테크닉이 필요하다.

더 읽어보기

3. Test

const int sampleWindow = 50; // Sample window width in mS (50 mS = 20Hz)
unsigned int sample;

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


void loop() 
{
   unsigned long startMillis= millis();  // Start of sample window
   unsigned int peakToPeak = 0;   // peak-to-peak level

   unsigned int signalMax = 0;
   unsigned int signalMin = 1024;

   // collect data for 50 mS
   while (millis() - startMillis < sampleWindow)
   {
      sample = analogRead(0);
      if (sample < 1024)  // toss out spurious readings
      {
         if (sample > signalMax)
         {
            signalMax = sample;  // save just the max levels
         }
         else if (sample < signalMin)
         {
            signalMin = sample;  // save just the min levels
         }
      }
   }
   peakToPeak = signalMax - signalMin;  // max - min = peak-peak amplitude
   double volts = (peakToPeak * 3.3) / 1024;  // convert to volts

   Serial.println(volts);
} 

위의 코드는 50ms 동안 가장 높은 진폭과 낮은 진폭을 읽어내어, 그 두 값의 차이를 소리의 크기로 받아들여, Voltage 로 변환한 것이다.

4. NeoPixels Visualizor

#include <Adafruit_NeoPixel.h>

#define N_PIXELS   20  // Number of pixels you are using
#define MIC_PIN    A0  // Microphone is attached to Trinket GPIO #2/Gemma D2 (A1)
#define LED_PIN    6  // NeoPixel LED strand is connected to GPIO #0 / D0
#define DC_OFFSET  0  // DC offset in mic signal - if unusure, leave 0
#define NOISE      250  // Noise/hum/interference in mic signal
#define SAMPLES    60  // Length of buffer for dynamic   level adjustment
#define TOP        (N_PIXELS +1) // Allow dot to go slightly off scale

Adafruit_NeoPixel strip = Adafruit_NeoPixel(N_PIXELS, LED_PIN, NEO_GRB + NEO_KHZ800);

byte
peak      = 0,      // Used for falling dot
dotCount  = 0,      // Frame counter for delaying dot-falling speed
volCount  = 0;      // Frame counter for storing past volume data

int
vol[SAMPLES],       // Collection of prior volume samples
lvl       = 10,     // Current "dampened" audio level
minLvlAvg = 0,      // For dynamic adjustment of graph low & high
maxLvlAvg = 512;

// Initiate Variables for Sound Leveling
uint8_t  i;
uint16_t minLvl, maxLvl;
int      n, height;
uint8_t bright = 255;

void setup() {
  memset(vol, 0, sizeof(vol));
  strip.begin();
  Serial.begin(9600);
}

void loop() {
  ReadSound();  
  ShowStrips();
  DynamicLeveling();
}

void ReadSound() {
  n   = analogRead(MIC_PIN);                 // Raw reading from mic 
  n   = abs(n - 512 - DC_OFFSET);            // Center on zero
  n   = (n <= NOISE) ? 0 : (n - NOISE);      // Remove noise/hum
  lvl = ((lvl * 7) + n) >> 3;    // "Dampened" reading (else looks twitchy)

  // Calculate bar height based on dynamic min/max levels (fixed point):
  height = TOP * (lvl - minLvlAvg) / (long)(maxLvlAvg - minLvlAvg);

  if(height < 0L)       height = 0;      // Clip output
  else if(height > TOP) height = TOP;
  if(height > peak)     peak   = height; // Keep 'peak' dot at top
}

void DynamicLeveling () {
  vol[volCount] = n;                      // Save sample for dynamic leveling
  if(++volCount >= SAMPLES) volCount = 0; // Advance/rollover sample counter

  // Get volume range of prior frames
  minLvl = maxLvl = vol[0];
  for(i=1; i<SAMPLES; i++) {
    if(vol[i] < minLvl)      minLvl = vol[i];
    else if(vol[i] > maxLvl) maxLvl = vol[i];
  }

  if((maxLvl - minLvl) < TOP) maxLvl = minLvl + TOP;
  minLvlAvg = (minLvlAvg * 63 + minLvl) >> 6; // Dampen min/max levels
  maxLvlAvg = (maxLvlAvg * 63 + maxLvl) >> 6; // (fake rolling average)
}  

void ShowStrips() {
  // Color pixels based on rainbow gradient
  for(i=0; i<N_PIXELS; i++) {  
    if(i >= height)
      strip.setPixelColor(i, 0, 0, 0);
    else
      strip.setPixelColor(i,Wheel(map(i,0,(strip.numPixels()-1),150,30)));
  } 

  strip.show();
}

// Input a value 0 to 255 to get a color value.
// The colors are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } 
  else if(WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } 
  else {
    WheelPos -= 170;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

Gemma Powered Neopixel LED Sound Reactive Drum

5. Fast Fourier Transform

추가로 궁금한 사항은 하단의 링크로 들어가서 계속하자.

더 읽어보기

comments powered by Disqus