버저, 피에조 스피커로 음악 만들기
버저 (Buzzer)
먼저 버저는 패시브 버저와 액티브 버저로 나눌 수 있습니다.
액티브 버저는 단일한 음을 만드는 간단한 장치입니다. 사용방법도 쉬워서 단순히 전원을 넣어주면 소리가 나고 전원을 끊으면 소리가 끊깁니다. 사용자가 출력되는 소리의 주파수를 선택할 수는 없습니다. 아래와 같이 연결해주면 됩니다.
- S(Signal 혹은 +) 라인 ==> 아두이노의 디지털 8번 핀
- GND (혹은 -) ==> 아두이노의 GND
그리고 아래 코드로 테스트 해볼 수 있습니다.
int speakerPin = 8; void setup () { pinMode (speakerPin, OUTPUT); } void loop () { analogWrite (speakerPin, 255); delay (50); analogWrite (speakerPin, 0); delay (10); }
반면에 패시브 버저는 다양한 음을 출력할 수 있습니다. 대신 AC sound signal을 만들어 줘야 합니다. 아두이노에서 이런 사운드 신호를 만들기 위해서는 디지털 핀으로 빠르게 on/off 를 반복해 주는데 on/off 가 유지되는 시간에 의해 다른 소리가 생성되게 됩니다.
피에조 스피커나 패시브 버저를 다음과 같이 연결해 줍니다.
- S(Signal 혹은 +) ==> 아두이노 디지털 9번 핀
- GND (혹은 -) ==> 아두이노의 GND
그리고 아래 코드로 테스트 해볼 수 있습니다. 연결된 디지털 핀으로 on/off 를 빠르게 반복해 줍니다. 주의해서 보실 점은 off 상태를 유지하는 시간이 출력하고자 하는 음에 따라 달라진다는 점입니다.
int buzzer = 9 ;/ / setting controls the digital IO foot buzzer void setup () { pinMode (buzzer, OUTPUT) ;/ / set the digital IO pin mode, OUTPUT to output } void loop () { unsigned char i, j ;/ / define variables while (1) { for (i = 0; i <80; i + +) / / outputs a frequency sound { digitalWrite (buzzer, HIGH) ;/ / send voice delay (1) ;/ / Delay 1ms digitalWrite (buzzer, LOW) ;/ / do not send voice delay (1) ;/ / delay ms } for (i = 0; i <500; i + +) / / output to another frequency sound { digitalWrite (buzzer, HIGH) ;/ / send voice delay (2) ;/ / delay 2ms digitalWrite (buzzer, LOW) ;/ / do not send voice delay (2) ;/ / delay 2ms } } }
이제 이걸 응용해서 다양한 사운드를 만들 수 있습니다. 조금 복잡하지만 arduino.cc 에서 제공하는 아래 예제를 보시죠. 아래 예제에서는 tone(음의 높낮이), beat(음의 지속시간) 를 통해 음악을 만들어 냅니다.
tone은 아두이노의 디지털 핀을 빠르게 on/off 할 때 on/off 각각의 상태를 유지하는 시간입니다. beat는 tone을 만드는 on/off 반복상태를 얼마나 오래 지속할 것인가를 결정합니다. 입력값에 따라 특정 음을 만들도록 이런 작업을 해주는 함수가 playTone() 입니다.
그리고 음악을 만들기 위해 미리 일련의 값들을 설정해 둔 것이 melody[], beats[] 배열입니다. loop 함수 안에서 배열에 저장된 음을 playTone() 함수로 재생합니다.
/* Play Melody * ----------- * * Program to play a simple melody * * Tones are created by quickly pulsing a speaker on and off * using PWM, to create signature frequencies. * * Each note has a frequency, created by varying the period of * vibration, measured in microseconds. We'll use pulse-width * modulation (PWM) to create that vibration. * We calculate the pulse-width to be half the period; we pulse * the speaker HIGH for 'pulse-width' microseconds, then LOW * for 'pulse-width' microseconds. * This pulsing creates a vibration of the desired frequency. * * (cleft) 2005 D. Cuartielles for K3 * Refactoring and comments 2006 clay.shirky@nyu.edu * See NOTES in comments at end for possible improvements */ // TONES ========================================== // Start by defining the relationship between // note, period, & frequency. #define c 3830 // 261 Hz #define d 3400 // 294 Hz #define e 3038 // 329 Hz #define f 2864 // 349 Hz #define g 2550 // 392 Hz #define a 2272 // 440 Hz #define b 2028 // 493 Hz #define C 1912 // 523 Hz // Define a special note, 'R', to represent a rest #define R 0 // SETUP ============================================ // Set up speaker on a PWM pin (digital 9, 10 or 11) int speakerOut = 9; // Do we want debugging on serial out? 1 for yes, 0 for no int DEBUG = 1; void setup() { pinMode(speakerOut, OUTPUT); if (DEBUG) { Serial.begin(9600); // Set serial out if we want debugging } } // MELODY and TIMING ======================================= // melody[] is an array of notes, accompanied by beats[], // which sets each note's relative length (higher #, longer note) int melody[] = { C, b, g, C, b, e, R, C, c, g, a, C }; int beats[] = { 16, 16, 16, 8, 8, 16, 32, 16, 16, 16, 8, 8 }; int MAX_COUNT = sizeof(melody) / 2; // Melody length, for looping. // Set overall tempo long tempo = 10000; // Set length of pause between notes int pause = 1000; // Loop variable to increase Rest length int rest_count = 100; //<-BLETCHEROUS HACK; See NOTES // Initialize core variables int tone_ = 0; int beat = 0; long duration = 0; // PLAY TONE ============================================== // Pulse the speaker to play a tone for a particular duration void playTone() { long elapsed_time = 0; if (tone_ > 0) { // if this isn't a Rest beat, while the tone has // played less long than 'duration', pulse speaker HIGH and LOW while (elapsed_time < duration) { digitalWrite(speakerOut,HIGH); delayMicroseconds(tone_ / 2); // DOWN digitalWrite(speakerOut, LOW); delayMicroseconds(tone_ / 2); // Keep track of how long we pulsed elapsed_time += (tone_); } } else { // Rest beat; loop times delay for (int j = 0; j < rest_count; j++) { // See NOTE on rest_count delayMicroseconds(duration); } } } // LET THE WILD RUMPUS BEGIN ============================= void loop() { // Set up a counter to pull from melody[] and beats[] for (int i=0; i<MAX_COUNT; i++) { tone_ = melody[i]; beat = beats[i]; duration = beat * tempo; // Set up timing playTone(); // A pause between notes... delayMicroseconds(pause); if (DEBUG) { // If debugging, report loop, tone, beat, and duration Serial.print(i); Serial.print(":"); Serial.print(beat); Serial.print(" "); Serial.print(tone_); Serial.print(" "); Serial.println(duration); } } }
위 예제처럼 아두이노의 디지털 핀을 빠르게 on/off 반복시키는 것은 PWM의 원리와 같습니다. 그래서 PWM – analogWrite() 함수를 이용해서도 똑같이 구현을 할 수 있습니다. 아래는 PWM 을 이용한 소스코드 입니다.
/* Play Melody * ----------- * * Program to play melodies stored in an array, it requires to know * about timing issues and about how to play tones. * * The calculation of the tones is made following the mathematical * operation: * * timeHigh = 1/(2 * toneFrequency) = period / 2 * * where the different tones are described as in the table: * * note frequency period PW (timeHigh) * c 261 Hz 3830 1915 * d 294 Hz 3400 1700 * e 329 Hz 3038 1519 * f 349 Hz 2864 1432 * g 392 Hz 2550 1275 * a 440 Hz 2272 1136 * b 493 Hz 2028 1014 * C 523 Hz 1912 956 * * (cleft) 2005 D. Cuartielles for K3 */ int ledPin = 13; int speakerOut = 9; byte names[] = {'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C'}; int tones[] = {1915, 1700, 1519, 1432, 1275, 1136, 1014, 956}; byte melody[] = "2d2a1f2c2d2a2d2c2f2d2a2c2d2a1f2c2d2a2a2g2p8p8p8p"; // count length: 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 // 10 20 30 int count = 0; int count2 = 0; int count3 = 0; int MAX_COUNT = 24; int statePin = LOW; void setup() { pinMode(ledPin, OUTPUT); } void loop() { analogWrite(speakerOut, 0); for (count = 0; count < MAX_COUNT; count++) { statePin = !statePin; digitalWrite(ledPin, statePin); for (count3 = 0; count3 <= (melody[count*2] - 48) * 30; count3++) { for (count2=0;count2<8;count2++) { if (names[count2] == melody[count*2 + 1]) { analogWrite(speakerOut,500); delayMicroseconds(tones[count2]); analogWrite(speakerOut, 0); delayMicroseconds(tones[count2]); } if (melody[count*2 + 1] == 'p') { // make a pause of a certain size analogWrite(speakerOut, 0); delayMicroseconds(500); } } } } }
아두이노에서는 이것을 더 간단히 처리할 수 있도록 tone() 함수를 지원합니다.
/* Melody Plays a melody circuit: * 8-ohm speaker on digital pin 8 created 21 Jan 2010 modified 30 Aug 2011 by Tom Igoe This example code is in the public domain. http://arduino.cc/en/Tutorial/Tone */ #include "pitches.h" // notes in the melody: int melody[] = { NOTE_C4, NOTE_G3,NOTE_G3, NOTE_A3, NOTE_G3,0, NOTE_B3, NOTE_C4}; // note durations: 4 = quarter note, 8 = eighth note, etc.: int noteDurations[] = { 4, 8, 8, 4,4,4,4,4 }; void setup() { // iterate over the notes of the melody: for (int thisNote = 0; thisNote < 8; thisNote++) { // to calculate the note duration, take one second // divided by the note type. //e.g. quarter note = 1000 / 4, eighth note = 1000/8, etc. int noteDuration = 1000/noteDurations[thisNote]; tone(8, melody[thisNote],noteDuration); // to distinguish the notes, set a minimum time between them. // the note's duration + 30% seems to work well: int pauseBetweenNotes = noteDuration * 1.30; delay(pauseBetweenNotes); // stop the tone playing: noTone(8); } } void loop() { // no need to repeat the melody. }
tone() 함수로 더 간결하게 소스를 작성할 수 있습니다. 단, 이렇게 음을 출력하는 작업은 다른 복잡한 작업과 같이 배치해서 실행할 경우 원하는 만큼 정확히 동작하지 않을 수도 있다는 점을 참고하세요.
참고자료 : 아두이노로 멜로디 만들기, 아두이노 tone(), 패시브 버저, 패시브버저와 액티브버저의 차이