[아두이노] NEOPIXEL+피에조부조 이퀄라이저바 응용

IOT/아두이노|2019. 4. 17. 09:00

[아두이노] NEOPIXEL+피에조부조 이퀄라이저바 응용



@codingart님의 led+스피커을 결합하여 사운드와 LED가 동시 출력하는 포스트를 보면서 가상시뮬레이터에서 구현해보고 싶어져서 이렇게 인용하여 실험을 한번 해 보았습니다. 그냥 그대로 표현하기가 좀 그래서 최근에 배웠던 NeoPixel로 응용해 보면 좋을 것 같아서 한번 적용해 보았습니다. 동작 구현은 멜로디 음계에 따라서 NeoPixel로 그래픽 이퀄아이저바 형태로 동시에 출력하는 로직으로 표현 했습니다. 즉, @codingart의 LED 부분을 NeoPixel로 대체하고 추가로 음계를 그래픽바로 응용했네요. 참고로 오늘 실험한 회로도와 코딩 부분은 위 참고자료 중 지금까지 연재한 내용에서 피에조부저 곰세마리 멜로디 코딩과 사운드센서 그래픽 이퀄아지어 표현을 합쳐서 만들 거라서 수정부분은 거의 없습니다. NeoPixel의 부분에서 한개 짜리를 바 형태로 출력 코딩을 약간 변경 했네요.

1. 회로도 구성


  • 준비물 : 피에조부조 1개, NeoPixel 8개, 아두이노우노
  • 내용 : 피에조부저은 출력핀을 제어 할 적당한 위치에 배치하고 NeoPixel은 바 형태로 만들어서 제어 할 위치에 배치하자.


위 그림은 12번 핀을 피에조부저 멜로디가 출력하는 핀으로 사용했으며 NeoPixel을 바 형태로 8개를 연결하고 3번핀을 통해서 제어하도록 선은 연결했네요. 참고로 NeoPixel의 자체의 input핀과 output핀을 정확히 확인하시고 선을 연결해야 합니다.

이제 기본 회로도 구성이 완료 되었으니깐 코딩을 해 볼까요.

2. 코딩 과정


  • 내용 : 피에조부저에 멜로디를 출력하면서 동시에 NeoPixel Bar에 멜로디 음계을 LED 길이로 출력해 보자.
    추가) NeoPixel의 Color는 랜덤함수를 사용하여 좀 더 화려하게 표현하자.

1) 피에조부조 멜로디



예전에 포스트 한 곰세마리 멜로디를 기억하실 지 모르겠네요. 기억이 안나거나 처음 접하시는 분들은 위 링크된 포스트를 참고해 주시고 따로 세부적으로 반복 설명은 하지 않겠습니다. 이미 다 설명한 내용이니깐요. 그러면 곰세마리 멜로디를 기본 베이스로 출발 하도록 하겠습니다.

[ 기본 소스 ]

#define NOTE_C5  523    //도
#define NOTE_D5  587    //레
#define NOTE_E5  659    //미
#define NOTE_F5  698    //파
#define NOTE_G5  784   //솔
#define NOTE_A5  880   //라
#define NOTE_B5  988   //시
#define NOTE_C6  1047 //도

int tonepin = 12;

int melody[] = {
NOTE_C5,NOTE_C5,NOTE_C5,NOTE_C5,NOTE_C5,
NOTE_E5,NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_G5,NOTE_G5,NOTE_E5,
NOTE_C5,NOTE_C5,NOTE_C5,

NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,

NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,NOTE_A5,NOTE_G5,
NOTE_C6,NOTE_G5,NOTE_C6,NOTE_G5,
NOTE_E5,NOTE_D5,NOTE_C5
};

int noteDurations[]={
4,8,8,4,4,
4,8,8,4,4,
8,8,4,8,8,4,
4,4,2,
4,4,4,4,
4,4,2,
4,4,4,4,
4,4,2,
4,4,4,4,
8,8,8,8,2,
4,4,4,4,
4,4,2
};


void setup() { 
} 
void loop() {
  for (int i = 0; i < 49; i++) {
    
    int Durations = 1000/noteDurations[i];    // 음계의 음길이 계산
    tone(tonepin, melody[i], Durations);    
    int pauseBetweenNotes = Durations *1.3 ;
    delay(pauseBetweenNotes);
    noTone(tonepin);
  }

}

위 소스에서 NeoPixel의 코딩만 삽입하시면 됩니다.

2) NEOPIXEL 그래픽 이퀄라이저바에 사용 될 코드 추출



위 링크된 포스트를 참고하겠습니다. 링크 포스트에서는 "가변저항기와 NeoPixel"이라는 소제목의 가상시뮬레이터 실험이 있습니다. 가변저항기 값에 따라 한개의 NeoPixel의 Color를 만들어 냈던 실험이였는데 오늘은 이 원리를 이용하여 NeoPixel을 바 형태로 바꾸서 음계를 LED 길이 형태로 출력하기 위해서 우선 사용될 부분을 가져오도록 하겠습니다.

[ 기본 소스 ]

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(1, neopixelPin, NEO_GRB + NEO_KHZ800);
 
void setup()
{
  neopixel.begin();   
  randomSeed(analogRead(A1));
}

void loop()
{
  int SoundSenser=0; 
  int SoundColor=0; 
    
  SoundSenser = analogRead(A0);
  if(SoundSenser<26) SoundSenser=26;
  if(SoundSenser>300) SoundSenser=300;
  
  SoundColor= map(SoundSenser,26,300,0,255);
  neopixel.setPixelColor(0, random(SoundColor), random(SoundColor), random(SoundColor));    
  neopixel.show();
  delay(50);    
  neopixel.clear(); 
}

위 소스에서 필요한 부분만 빼내도록 하죠.

8개의 NeoPixel를 사용하고 3번핀이 제어핀이니깐 아래와 같이 기본 선언을 해야 겠죠.

#include <Adafruit_NeoPixel.h>

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(8, neopixelPin, NEO_GRB + NEO_KHZ800);

Seteup() 세팅

void setup()
{
  neopixel.begin();   
  randomSeed(analogRead(A1));
}

NeoPixel를 시작하겠다고 선언해주고 NeoPixel의 칼러를 랜덤함수를 이용 할 예정이라서 랜덤함수 초기화 함수를 선언했네요. 이 두 줄은 그대로 사용합니다.

loop() 가져올 내용

neopixel.setPixelColor(네오픽셀위치, random(255), random(255), random(255));    
neopixel.show();
neopixel.clear(); 
  • setPixelColor() : NeoPixel Color 세팅
  • show() : NeoPixel로 출력
  • clear() : NeoPixel 세팅 된 값을 초기화

이 세가지 함수만 가져다 쓸 예정입니다. 이 세 함수로 그래픽바 출력이 이뤄지겠죠.

3) 피에조부저의 음계를 만들고 NeoPixel Bar로 출력

이부분이 핵심 코딩 부분입니다. 음계를 어떻게 NeoPixel Bar로 출력을 할까요. 오늘 실험에서는 음계의 값을 기준으로 제어를 하도록 하겠습니다.

음계를 배열변수로 선언 해볼까요.

const int scaleval[8] = {NOTE_C5,NOTE_D5,NOTE_E5,NOTE_F5,NOTE_G5,NOTE_A5,NOTE_B5,1047};

scalval[8] 배열변수에 5옥타브의 음계 "도레미파솔라시도"를 저장했습니다.

음계를 NeoPixel Bar로 출력합시다.

어떻게 NeoPixel의 길이로 출력을 할까요. 여기서, 상상력이 많이 막히실 꺼에요. 마이크사운드감지센서에서 기억하실 지 모르겠지만 입력소리를 영역으로 나눠서 구분 할 수 있다고 했습니다. 음을 영역으로 나누면 나눈 영역이 하나의 NeoPixel의 영역으로 간주 할 수 있게 됩니다. 해당 음계의 영역에 매칭되는 NeoPixel의 위치에 불이 들어오게 하면 쉽게 해결 되겠죠. 그래서, 음계를 scaleval[8] 배열변수로 만들었지요.

곰곰히 음계배열변수에 살펴보세요. '도'은 0번째 배열에 들어 있고, '레'는 1번째 배열에 들어 있습니다. 각 음계의 배열의 순차적으로 들어 있는데 8개NeoPixel를 이 배열에 매칭 시키면 어떻게 될까요.

여기서, '도'은 NeoPixel 0번째에만 불이 들어오고 '미'은 NeoPixel 2번째까지 불이 들어오게 만들어 코딩하면 음계가 NeoPixel의 그래픽 길이로 출력이 가능해 집니다.

출력 코딩을 어떻게 할까요. 기초 코딩에서 배열변수를 배웠습니다. 배열변수가 나오면 기본적으로 for문을 떠올리시라고 했죠. for문을 활용하면 쉽게 해결이 됩니다.

코딩은, 아래와 같습니다.

 for(int i=0;i<8;i++){
    if(scaleval[i]<=soundval){
      neopixel.setPixelColor(i, random(255), random(255), random(255));    
      neopixel.show();
    } 
    else break;
 }

이 코딩을 어떻게 나왔는지 순차적으로 코딩하는 과정을 볼까요.

for문으로 우선 8개의 NeoPixel에 불이 들어오게 만들어 봅시다.

 for(int i=0;i<8;i++){   
      neopixel.setPixelColor(i, random(255), random(255), random(255));    
      neopixel.show();
            delay(50);
  }
    neopixel.clear(); 

1초(1000)인 delay 시간값 50을 기준으로 setPixelColor()함수로 순차적으로 NeoPixel의 색이 세팅되고 show()함수로 NeoPixel로 출력이 됩니다.

한개의 음계의 값만큼 NeoPixel에 불이 들어오게 만들어 볼까요.

기초 코딩에서 배운 if문으로 조건문을 만들면 됩니다.

 const int scaleval[8] = {NOTE_C5,NOTE_D5,NOTE_E5,NOTE_F5,NOTE_G5,NOTE_A5,NOTE_B5,1047};
 for(int i=0;i<8;i++){   
    if(scaleval[i]<=입력음계){
      neopixel.setPixelColor(i, random(255), random(255), random(255));    
      neopixel.show();          
        } 
        else break;
  }
    neopixel.clear(); 

if문으로 8개의 음계를 for문으로 비교하게 됩니다. 입력한 음계랑 비교해서 "작거나 같다"면 해당 i번째의 NeoPixel의 Color가 만들어 지도록 조건문을 다시면 됩니다. 즉, 입력한 음계까지만 NeoPixel에 불이 들어오게 하는 것이죠.

여기서, else 문에서 break 명령을 붙였는데 이건 더이상 수행하지 말고 for문을 빠져 나오라는 문장입니다. Switch(선택제어문)에서 배우셨죠. 반복 수행을 더 할 필요가 없기 때문에 for문을 빠져나오는게 더 효율적인 코딩이겠죠. 참고로 생략은 가능합니다. 어짜피 if문에 입력 음계까지만 세팅이 이뤄지고 나머지 음계 i번째는 조건을 만족하지 않기 때문에 if문 안의 문장을 수행하지 않습니다. 8번 반복이니깐 else 이하 코딩은 생략하셔도 별로 지장은 없네요.

이제 입력음계까지 NeoPixel Bar 출력을 할 수 있겠죠.

4) 사용자 정의 함수로 만들기

loop 함수에다가 그대로 코딩하면 loop 함수의 가독성은 떨어집니다. 그렇기 때문에 NeoPixel Bar 출력을 사용자 정의 함수로 빼면 loop()함수 내부가 좀 깨끗해지겠죠.

void nepixelbar(int soundval){
  for(int i=0;i<8;i++){
    if(scaleval[i]<=soundval){
      neopixel.setPixelColor(i, random(255), random(255), random(255));    
      neopixel.show();
    }
    else break;
  }
}

이렇게 해서 soundval 값이 입력되면 그 입력된 값에 따라서 NeoPixel Bar가 출력이 됩니다.

여기서 표현은 두가지가 있습니다. neopixel.show() 명령문을 for문 안에다 표현해도 되고 for문 밖에다 표현해도 됩니다. 약간 차이가 있을 뿐 원하는 스타일로 코딩 하시면 됩니다.

5) 곰세마리멜로디 코딩에 NeoPixel 코딩을 합치기

void loop() {
  for (int i = 0; i < 49; i++) {
    
    int Durations = 1000/noteDurations[i];    // 음계의 음길이 계산
    tone(tonepin, melody[i], Durations);    
    int pauseBetweenNotes = Durations *1.3 ;
    delay(pauseBetweenNotes);
    noTone(tonepin);
  }
}

음계는 melody[i]가 됩니다. 그러면 for문에서 소리가 나기 직전 앞에다가 직접 만든 nepixelbar()함수를 삽입하면 NeoPixel에 불이 들어오는 동시에 음의 소리가 나겠죠.

    int Durations = 1000/noteDurations[i];    
    
    nepixelbar(melody[i]); //삽입 위치
    
    tone(tonepin, melody[i], Durations);  

여기서, 한 음계의 NeoPixel로 출력 했을때 출력 시간은 곰세마리 멜로디의 한음이 끝 날때까지 유지하고 초기화 해야 다음 음계에 NeoPixel로 출력이 가능해 집니다. 초기화 neopixel.clear() 함수를 소리가 끝나는 마지막에 삽입해야 겠지요.

  noTone(tonepin);
    
  neopixel.clear(); //삽입 위치

종합해보면.

#include <Adafruit_NeoPixel.h>

#define NOTE_C5  523    //도
#define NOTE_D5  587    //레
#define NOTE_E5  659    //미
#define NOTE_F5  698    //파
#define NOTE_G5  784   //솔
#define NOTE_A5  880   //라
#define NOTE_B5  988   //시
#define NOTE_C6  1047 //도

const byte neopixelPin = 3; 
Adafruit_NeoPixel neopixel = Adafruit_NeoPixel(8, neopixelPin, NEO_GRB + NEO_KHZ800);

const int tonepin = 12;

const int scaleval[8] = {NOTE_C5,NOTE_D5,NOTE_E5,NOTE_F5,NOTE_G5,NOTE_A5,NOTE_B5,1047};
//멜로디
const int melody[] = {
NOTE_C5,NOTE_C5,NOTE_C5,NOTE_C5,NOTE_C5,
NOTE_E5,NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_G5,NOTE_G5,NOTE_E5,
NOTE_C5,NOTE_C5,NOTE_C5,

NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,
NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,

NOTE_G5,NOTE_G5,NOTE_E5,NOTE_C5,
NOTE_G5,NOTE_G5,NOTE_G5,NOTE_A5,NOTE_G5,
NOTE_C6,NOTE_G5,NOTE_C6,NOTE_G5,
NOTE_E5,NOTE_D5,NOTE_C5
};

//음길이
const int noteDurations[]={
4,8,8,4,4,
4,8,8,4,4,
8,8,4,8,8,4,
4,4,2,
4,4,4,4,
4,4,2,
4,4,4,4,
4,4,2,
4,4,4,4,
8,8,8,8,2,
4,4,4,4,
4,4,2
};

void setup() { 
  neopixel.begin(); 
  randomSeed(analogRead(A1));
} 
void loop() {
  //연주
  for (int i = 0; i < 49; i++) {    
    int Durations = 1000/noteDurations[i];    // 음계의 음길이 계산
    
    nepixelbar(melody[i]);
    
    tone(tonepin, melody[i], Durations);    
    int pauseBetweenNotes = Durations *1.3 ;
    delay(pauseBetweenNotes);
    noTone(tonepin);
    
    neopixel.clear();     
  }

}

void nepixelbar(int soundval){
  for(int i=0;i<8;i++){
    if(scaleval[i]<=soundval){
        neopixel.setPixelColor(i, random(255), random(255), random(255));    
        eopixel.show();
    }
    else break;
  }
}

3. 결과


[출력 이미지 샷]


[구현 과정 동영상]


마무리


@codingart의 작품을 가상시뮬레이터로 응용해서 표현을 했네요. 기초 코딩 문법을 소개 한 뒤에 의욕이 앞선 나머지 오늘 포스팅 내용이 코딩에 너무 치우쳐서 전개한 듯 싶네요. 사용 된 기초 코딩 문법은 배열변수, IF문, FOR문 입니다. 중요한 것은NeoPixel에 불이 어떻게 들어오는지에 대한 원리를 이해하시면 상상력을 더하면 NeoPixel에 불이 들어오게 하는 방식을 다양하게 표현을 할 수 있게 됩니다.

그리고, 하나의 원리 배우면 표현을 "이걸로 뭘 하지! 뭘 표현할 수 있지! 이것을 써먹을 수 있는 곳은 어디 없을까!"라고 계속 자신에게 질문을 던져주세요. 이런 트래이딩 학습을 계속 하시게 되면 처음에는 좀 힘들지만 나중에 다양한 표현이 가능해집니다.

저도 처음에 C언어 기초 문법을 공부할 때 학습 방법입니다. 코딩은 같은 표현도 다양하게 표현이 가능이 가능합니다.

c=a+b;
printf("%d",c);

이런 표현이 있다면

printf("%d",a+b);

이렇게 직접적으로 표현 할 수 있고

c=a+b;
printf("%d + %d = %d",a,b,c);

이렇게 표현할 수 있습니다. 다 같은 덧셈의 원리를 이용하지만 표현하는 것은 코딩하시는 여러분의 상상력에 달려 있습니다.
비유가 적절했는지 모르겠지만 이렇게 정해진 코딩과 표현은 없습니다.

저는 C언어를 입문할 때 하나의 원리를 배우면 그 표현을 다양한 형태로 5가지 이상으로 표현하는 학습을 하였습니다. 처음에는 약간 힘들지만 이렇게 코딩하다 보면 어느순간에 새로운 코딩 원리를 발견하고 배우게 되면 이 원리에 대해서 다양하게 접근하고 표현하는 상상이 자연스럽게 되어 지더군요.

여러분들도 오늘 포스팅한 내용을 그대로 보시지 마시고 한번 여기서 더 추가하고 코딩을 변경해보고 새로운 표현이 없을지 상상력을 발휘 해보셨으면 합니다.


댓글()