[아두이노] Maker UNO 사용기 : 피에조 부저(piezo buzzer) 사용하기.

    이전 포스팅에서 Cytron 사의 코딩 교육용 아두이노 호환 보드인 Maker UNO를 살펴봤습니다.

    내장된 푸쉬 버튼을 토글 버튼으로 사용할 수 있도록 코드를 작성했습니다.

    이제 toogleStatus 변수를 가지고 음악을 재생할지, 정지할지 정할 수 있겠네요.


    피에조 부저 (Piezo Buzzer)

    방금 만든 토글 스위치가 활성화(SET) 되었을 때 음악을 재생할 함수 playBuzzer()을 호출합니다.

    피에조 부저는 tone() 함수를 사용하여 재생할 수 있습니다.

    www.arduino.cc/reference/en/language/functions/advanced-io/tone/

     

    tone() - Arduino Reference

    Notes and Warnings If you want to play different pitches on multiple pins, you need to call noTone() on one pin before calling tone() on the next pin.

    www.arduino.cc

    tone() 함수에 사용되는 파라미터는 부저가 연결된 핀 번호인 pin, 재생할 음을 나타내는 주파수인 frequency, 음을 재생할 시간인 duration이 있습니다.

    Maker UNO의 피에조 부저는digital 8 pin을 통해 사용 가능합니다.

     

    먼저 말씀 드리지만, 원하는 음악을 재생하는 것은 dog 노가다입니다.

    분명히 말씀드렸습니다.

     

    www.youtube.com/watch?v=0Fzd6vQHYm0

    재생할 음악과 악보는 위의 youtube 영상을 참고했습니다.

    짐승을 부려먹는 짐승들

    가장 먼저 곡을 연주하는 속도인 bpm이 보이네요.

    1분에 4분 음표가 190회 연주되는 속도입니다.

    그런데 굳이 bpm까지 계산해서 완벽하게 재생해보고 싶진 않네요.

    이미 토글 스위치를 제작하는 데에서 시간을 너무 뺏겼기 때문입니다.

     

    먼저 아두이노에서 제공하는 부저 음을 따옵니다.

    #define NOTE_Ti0  31
    
    #define NOTE_Do1  33
    #define NOTE_Di1 35
    #define NOTE_Re1  37
    #define NOTE_Ri1 39
    #define NOTE_Mi1  41
    #define NOTE_Fa1  44
    #define NOTE_Fi1 46
    #define NOTE_Sol1  49
    #define NOTE_Si1 52
    #define NOTE_La1  55
    #define NOTE_Li1 58
    #define NOTE_Ti1  62
    
    #define NOTE_Do2  65
    #define NOTE_Di2 69
    #define NOTE_Re2  73
    #define NOTE_Ri2 78
    #define NOTE_Mi2  82
    #define NOTE_Fa2  87
    #define NOTE_Fi2 93
    #define NOTE_Sol2  98
    #define NOTE_Si2 104
    #define NOTE_La2  110
    #define NOTE_Li2 117
    #define NOTE_Ti2  123
    
    #define NOTE_Do3  131
    #define NOTE_Di3 139
    #define NOTE_Re3  147
    #define NOTE_Ri3 156
    #define NOTE_Mi3  165
    #define NOTE_Fa3  175
    #define NOTE_Fi3 185
    #define NOTE_Sol3  196
    #define NOTE_Si3 208
    #define NOTE_La3  220
    #define NOTE_Li3 233
    #define NOTE_Ti3  247
    
    #define NOTE_Do4  262
    #define NOTE_Di4 277
    #define NOTE_Re4  294
    #define NOTE_Ri4 311
    #define NOTE_Mi4  330
    #define NOTE_Fa4  349
    #define NOTE_Fi4 370
    #define NOTE_Sol4  392
    #define NOTE_Si4 415
    #define NOTE_La4  440
    #define NOTE_Li4 466
    #define NOTE_Ti4  494
    
    #define NOTE_Do5  523
    #define NOTE_Di5 554
    #define NOTE_Re5  587
    #define NOTE_Ri5 622
    #define NOTE_Mi5  659
    #define NOTE_Fa5  698
    #define NOTE_Fi5 740
    #define NOTE_Sol5  784
    #define NOTE_Si5 831
    #define NOTE_La5  880
    #define NOTE_Li5 932
    #define NOTE_Ti5  988
    
    #define NOTE_Do6  1047
    #define NOTE_Di6 1109
    #define NOTE_Re6  1175
    #define NOTE_Ri6 1245
    #define NOTE_Mi6  1319
    #define NOTE_Fa6  1397
    #define NOTE_Fi6 1480
    #define NOTE_Sol6  1568
    #define NOTE_Si6 1661
    #define NOTE_La6  1760
    #define NOTE_Li6 1865
    #define NOTE_Ti6  1976
    
    #define NOTE_Do7  2093
    #define NOTE_Di7 2217
    #define NOTE_Re7  2349
    #define NOTE_Ri7 2489
    #define NOTE_Mi7  2637
    #define NOTE_Fa7  2794
    #define NOTE_Fi7 2960
    #define NOTE_Sol7  3136
    #define NOTE_Si7 3322
    #define NOTE_La7  3520
    #define NOTE_Li7 3729
    #define NOTE_Ti7  3951
    
    #define NOTE_Do8  4186
    #define NOTE_Di8 4435
    #define NOTE_Re8  4699
    #define NOTE_Ri8 4978

    각 음계에 대해 주파수를 미리 정의해놓은 파일이 있습니다.

    제가 악보를 초등학교 저학년때 피아노 학원 이후로 본 적이 없기 때문에 제 입맛에 맞게 좀 수정했습니다.

    이걸 메인 코드가 아닌 헤더 파일로 만들어 참조할 것입니다.

    아두이노 IDE 우측 상단에 아래 방향 화살표 아이콘을 클릭하여 새 탭을 추가합니다.

    "pitches.h"라고 저장합니다.

    메인 코드 맨 윗줄에 헤더 파일로 추가할 것입니다.

    아래 코드는 완성 코드이며, 이전 포스팅에서 작성한 토글 스위치 코드를 포함하고 있습니다.

    #include "pitches.h"
    #define pushButton 2
    #define piezoBuzzer 8
    
    int currentNote = 0;
    int quarter = 250;        // 1/4 : 4분 음표
    int quaver = quarter/2;   // 1/8 : 8분 음표
    int semiquav = quaver/2;  // 1/16 : 16분 음표
    int REST = 0;    // 쉼표는 음을 재생하지 않습니다.
    int noteFreq[] = {
      // (intro)
      NOTE_Do4, NOTE_Do6, NOTE_Ti5, NOTE_Do6, NOTE_Sol5, NOTE_Sol5,
      // (intro)
      NOTE_Mi5, NOTE_Fa5, NOTE_Sol5, NOTE_Mi5, NOTE_Do5, NOTE_Do5, NOTE_Li4, NOTE_La4,
      // 돌리랑 도트가
      NOTE_Fa5, NOTE_Fa5, NOTE_Fa5, NOTE_Do5, NOTE_Do5, NOTE_Do5,
      // 제일 좋아
      NOTE_La4, NOTE_La4, NOTE_Re5, NOTE_Do5, REST,
      // 돌리랑 도트가
      NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_Do5,
      // 제일 좋아
      NOTE_La4, NOTE_La4, NOTE_Re5, NOTE_Do5, REST,
      // 마차를 끌고
      NOTE_Fa4, NOTE_Fa4, NOTE_Fa4, NOTE_La4, NOTE_La4,
      // 모래언덕을 지나
      NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_La5, NOTE_Fa5,
      // 네~ (지나
      NOTE_Sol5, REST, NOTE_La5, NOTE_Fa5,
      // 네~)
      NOTE_Sol5, NOTE_Sol5, NOTE_Fi5, NOTE_Sol5,
      // 돌리랑 도트가
      NOTE_Fa5, NOTE_Fa5, NOTE_Fa5, NOTE_Do5, NOTE_Do5, NOTE_Do5,
      // 제일 좋아
      NOTE_La4, NOTE_La4, NOTE_Re5, NOTE_Do5, REST,
      // 돌리랑 도트가
      NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_Do5, NOTE_Do5,
      // 제일 좋아
      NOTE_La4, NOTE_La4, NOTE_Re5, NOTE_Do5, REST,
      // 이빨은 작아도
      NOTE_Re5, NOTE_Re5, NOTE_Re5, NOTE_Li4, NOTE_Li4, NOTE_Li4,
      // 먹는걸 좋아해
      NOTE_Sol4, NOTE_Sol4, NOTE_Sol4, NOTE_Do5, NOTE_Do5, NOTE_Fa5,
      // (outro)
      NOTE_Fa4, NOTE_Fa5, NOTE_Mi5, NOTE_Fa5, NOTE_Do5, NOTE_Do5,
      // (outro)
      NOTE_La4, NOTE_Li4, NOTE_Do5, NOTE_La4, NOTE_Fa4, NOTE_Do4, NOTE_Li3, NOTE_Sol3,
      // (outro)
      NOTE_La3, REST
    };
    
    int noteDuration[] = {
      // (intro)
      quarter, quaver+semiquav, semiquav, quaver+semiquav, quarter, semiquav,
      // (intro)
      quaver+semiquav, semiquav, quaver+semiquav, semiquav, quaver+semiquav, semiquav, quaver+semiquav, semiquav,
      // 돌리랑 도트가
      quaver+semiquav, semiquav, quarter, quaver+semiquav, semiquav, quarter,
      // 제일 좋아
      quaver, quaver, quaver, quaver+quarter, quarter,
      // 돌리랑 도트가
      quaver+semiquav, semiquav, quarter, quaver+semiquav, semiquav, quarter,
      // 제일 좋아
      quaver, quarter, quaver, quarter, quarter,
      // 마차를 끌고
      quaver+semiquav, semiquav, quarter, quarter, quarter,
      // 모래언덕을 지나
      quaver, quaver, quaver, quaver, quarter, quaver, quaver,
      // 네~ (지나
      quarter+quarter, quarter, quaver, quaver,
      // 네~)
      quarter+quarter, quarter, quaver+semiquav, semiquav,
      // 돌리랑 도트가
      quaver+semiquav, semiquav, quarter, quaver+semiquav, semiquav, quarter,
      // 제일 좋아
      quaver, quaver, quaver, quaver+quarter, quarter,
      // 돌리랑 도트가
      quaver+semiquav, semiquav, quarter, quaver+semiquav, semiquav, quarter,
      // 제일 좋아
      quaver, quarter, quaver, quarter, quarter,
      // 이빨은 작아도
      quaver+semiquav, semiquav, quarter, quaver+semiquav, semiquav, quarter,
      // 먹는걸 좋아해
      quaver+semiquav, semiquav, quarter, quarter, quaver, quaver,
      // (outro)
      quarter, quaver+semiquav, semiquav, quaver+semiquav, quarter, semiquav,
      // (outro)
      quaver+semiquav, semiquav, quaver+semiquav, semiquav, quaver+semiquav, semiquav, quaver+semiquav, semiquav,
      // (outro)
      quarter, quarter+quarter+quarter
    };
    
    unsigned long prevPlayTime = 0;
    unsigned long playDuration = 0;
    void playBuzzer() {
      int melodySize = sizeof(noteFreq)/sizeof(int);
      if(millis() < prevPlayTime + playDuration) {
        return;
      }
      noTone(piezoBuzzer);
    
      if(currentNote >= melodySize)
        currentNote = 0;
    
      tone(piezoBuzzer, noteFreq[currentNote], noteDuration[currentNote]);
      prevPlayTime = millis();
      playDuration = noteDuration[currentNote] * 1.3;
    
      currentNote ++;
    }
    
    bool globalFlag = false;
    bool toggleStatus = false;
    void detectButton() {
      int readPin = digitalRead(pushButton);
      if(readPin == LOW) {
        if(globalFlag == false)
          globalFlag = true;
      }
    
      else {
        if(globalFlag == true) {
          if(toggleStatus == true)
            toggleStatus = false;
          else
            toggleStatus = true;
          globalFlag = false;
        }
      }
    }
    
    void setup()
    {
      Serial.begin(115200);
      pinMode(pushButton, INPUT_PULLUP);
      pinMode(piezoBuzzer, OUTPUT);
    }
    
    void loop()
    {
      detectButton();
      if(toggleStatus)
        playBuzzer();
      Serial.println(toggleStatus);
      // delay(100);
    }

    두 가지 배열(array)이 필요합니다.

    재생할 음악의 음계를 저장할 배열(noteFreq[])과 재생 시간을 저장할 배열(noteDuration[])을 각각 제작해야 합니다.

    당연히 두 배열의 인덱스 갯수와 배열 길이는 같아야 합니다.

    (쉼표는 음 재생이 필요 없으니 0값의 주파수를 재생합니다)

    이 규칙에 맞게 본인이 재생하고 싶은 음계와 음계의 길이를 배열로 작성합니다.

     

    음악을 재생할 함수를 살펴 보겠습니다.

    // 시간 계산을 위한 변수
    // 이 시간을 통해 음악이 아직 재생 중인지, 재생이 끝났는지를 계산합니다.
    unsigned long prevPlayTime = 0;
    unsigned long playDuration = 0;
    void playBuzzer() {
      int melodySize = sizeof(noteFreq)/sizeof(int);    // 배열의 길이를 계산
      if(millis() < prevPlayTime + playDuration) {
        return;
      }
      noTone(piezoBuzzer);
    
      if(currentNote >= melodySize)
        currentNote = 0;    // 배열 길이만큼 tone() 함수를 반복.
    
      // 아래 세 줄이 실질적으로 음을 재생하는 부분입니다.
      tone(piezoBuzzer, noteFreq[currentNote], noteDuration[currentNote]);
      prevPlayTime = millis();
      playDuration = noteDuration[currentNote] * 1.3;
    
      currentNote ++;
    }

    이건 제가 인터넷을 보고 베꼈습니다.

    다들 이렇게 하더라구요?

    각 음을 재생할 때 마다 그 시간을 계산하여 현재 음악 연주가 끝났는지, 아직 재생중인지를 구분합니다.

    돌리랑~ 도트가~ 제일 좋아~

     

    댓글

    Designed by JB FACTORY