빌리 배스 피쉬를 오디오 소스에 맞춰 움직이게 만들기 - 단계별 가이드
구성품 및 부품
| 부품 |
수량 |
| Arduino UNO |
1 |
| Adafruit Motor/Stepper/Servo Shield v2.3 |
1 |
| Amazon Alexa Echo Dot |
1 |
프로젝트 소개
약 한 달 전, 브라이언 케인이 아마존 알렉사 목소리로 노래하는 빅 마우스 빌리 배스 피쉬 영상을 공개해 인터넷을 뜨겁게 달궜습니다. 저도 그 작동 원리가 궁금해 직접 만들어보기로 했습니다.
처음엔 라즈베리 파이와 알렉사를 결합한 프로젝트라 생각했지만, 더 간단한 방법은 49달러 에코 닷의 오디오를 아두이노로 처리해 기존 모터를 구동하는 것이었습니다. 이미 아두이노와 모터 실드가 있었고, 중고 빌리 배스(약 15달러)와 새 에코 닷을 주문했습니다.
두 튜토리얼에서 영감을 얻었습니다: 아두이노로 서보를 소리에 맞춰 움직이기 (2012, sfool)와 빌리 배스 애니매트로닉스 이해하기 (2013, Dotten).
빌리 배스는 퍼비처럼 두 개의 저가 5V DC 장난감 모터로 구동됩니다. 문제는 이 모터들을 소리에 반응하게 만드는 것이었습니다. 시행착오 끝에 작동하는 해결책을 찾았습니다. 코드는 완벽하지 않지만, 모두가 더 나은 버전을 만들 수 있도록 공개합니다.
필요한 재료
- 빅 마우스 빌리 배스 노래하는 피쉬 (1개)
- Arduino Uno (1개)
- Arduino Uno 전원 공급장치 (1개)
- Adafruit Motor Shield v2 (1개)
- 패널 마운트 미니잭 (1개)
- 여러 색상의 연선
- Amazon Echo Dot (다른 오디오 소스도 가능) (1개)
- 소형 충전식 스피커 (1개)
- 3.5mm 오디오 케이블 (1개)
- 3.5mm Y-스플리터 케이블 (1개)
필요한 도구
- 소형 드라이버
- 납땜 인두 및 납
- 와이어 스트리퍼
- 니퍼
- 드릴 및 비트
1단계: 피쉬 준비하기
구입한 중고 피쉬는 도착 시 작동하지 않았습니다. 덕분에 망설임 없이 분해해 해킹할 수 있었습니다. 뒷면 나사를 풀고 케이블 하니스를 조심스럽게 분리해 뒷면을 완전히 분리하세요.
전자 부품 장착 공간을 확보하기 위해 빌리 배스 회로 기판과 피에조 부저를 나사로 풀어 제거합니다.
이제 4선 하니스 하나가 남습니다. 입 움직임을 위한 두 선(피쉬 내부로 연결)과 머리/꼬리 움직임을 위한 두 선(모터 방향에 따라 머리 또는 꼬리 동작)입니다.
마지막으로 같은 길이의 서로 다른 색상 전선 4개를 준비해 끝단을 벗겨 납땜하고, 하니스의 4개 소켓에 각각 꽂습니다. 다음 단계에서 아두이노 모터 실드에 연결합니다.
2단계: 모터 실드 준비하기
모터 실드에 연결할 선은 4개뿐이며, 그중 2개만 납땜하면 됩니다. 헤드/테일 모터 실험을 위해 2개를 더 연결할 수도 있습니다.
먼저 납땜부터 끝내겠습니다. 오디오 입력을 위해 서로 다른 색상의 전선 2개를 같은 길이로 잘라 끝단을 벗기고, 하나는 그라운드에, 하나는 아날로그 0번 핀에 납땜합니다. 기판을 관통하는 여분 선은 잘라냅니다.
이 선들을 패널 마운트 미니잭으로 연결합니다. 제가 사용한 잭은 3단 모노 스위치 잭이었는데, 오디오 커넥터의 팁과 접촉하는 단자에 아날로그 0번 선을, 베이스(슬리브)와 접촉하는 단자에 그라운드 선을 연결하면 모노 입력이 됩니다.
이제 피쉬 모터를 실드에 연결합니다. 입 모터 선(검정, 주황)을 모터 1 단자 블록에 연결하고 조입니다. 선택적으로 헤드/테일 모터 선(흰색, 빨강)을 모터 2 단자에 연결할 수 있으나, 현재 코드는 이 모터를 제어하지 않습니다.
마지막으로 모터 실드의 VIN 점퍼 핀에 점퍼 슬리브가 꽂혀 있는지 확인하세요. 이걸 놓치면 실드 전원이 안 들어와 당황할 수 있습니다. 이제 실드를 아두이노 우노 위에 얹고 전원을 넣으면 실드의 파워 LED가 켜집니다.
3단계: 코드 업로드하기
코드는 DC 모터 테스트 스케치(모터 실드 데모)와 Cenk Özdemir의 '소리를 서보로' 스케치(2012)를 결합한 지저분한 핵입니다.
현재 코드는 헤드/테일 모터를 애니메이션하지 않습니다. 작동하는 버전이 있었지만 입 움직임 반응성이 떨어져 포기했습니다. 입 반응성을 유지하면서 헤드/테일이 느리게 순환하도록 만들고 싶지만, 현재 제 아두이노 코딩 실력으론 한계입니다. 해결하거나 제안받으면 업데이트하겠습니다.
4단계: 모두 조립하기
코드 업로드와 오디오 케이블 연결 후, 오디오 소스(전 팟캐스트 재생하는 폰 사용)를 케이블 자유단에 연결해 피쉬 입이 움직이는지 확인하세요. 작동하면 피쉬 명판 적당한 곳에 작은 구멍을 뚫고 잭을 끼워 고정합니다.
알렉사(또는 원하는 소스) 소리를 피쉬에서 나게 하려면: 오디오 케이블 자유단을 Y-어댑터에 연결, 충전식 스피커를 다른 입력에 연결, Y-어댑터의 수플러그를 에코 닷 오디오 출력에 꽂습니다.
스피커 전원을 켜고 알렉사 볼륨과 스피커 볼륨을 올린 후, 알렉사에게 날씨, 농담, 뉴스 등을 물어보세요. 휴대용 스피커에서 알렉사 목소리가 나오면서 동시에 피쉬의 고무 입술이 말에 맞춰 움직입니다. 스피커를 피쉬 머리 가까이에 둘수록 효과가 더 기묘합니다.
모든 게 작동하면 스피커와 아두이노를 빌리 배스 인클로저 안에 넣고 닫는 일이 남습니다. 솔직히 이 부분은 아직 해결 중입니다.
5단계: 다음 단계는?
- 헤드와 테일 애니메이션 구현 (연결은 되어 있으나 입 반응성 저하 문제 해결 필요)
- 스피커를 아두이노에서 충전 가능하게 개선
- 빌리 배스 빨간 버튼(또는 대체 버튼)으로 '음성 모드'(입 움직임 중심)와 '음악 모드'(음악에 맞춰 몸 전체 흔들기) 전환 기능 추가
- 코드 정리 (매시업 과정에서 남은 불필요한 코드 제거)
- 더 작은 아두이노 사용 (Uno + 모터 실드는 오버스펙. Adafruit Feather + 2모터 실드 조합 고려)
코드
SoundToDC (Arduino)
/* DC 모터를 소리에 맞춰 움직이기. 퍼블릭 도메인.
Donald Bell, Maker Project Lab (2016) 작성.
Cenk Özdemir의 Sound to Servo (2012)와 Adafruit의 DCMotorTest 기반 */
#include <Wire.h>
#include <Adafruit_MotorShield.h>
#include "utility/Adafruit_MS_PWMServoDriver.h"
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *myMotor = AFMS.getMotor(1); // 입 모터 (M1)
Adafruit_DCMotor *myOtherMotor = AFMS.getMotor(2); // 꼬리/헤드 모터 (M2)
int SoundInPin = A0;
int LedPin = 12; // 입 움직임 시 LED 점등용
void setup() {
Serial.begin(9600);
AFMS.begin(); // 기본 주파수 1.6KHz
myMotor->setSpeed(0);
myMotor->run(FORWARD);
myMotor->run(RELEASE);
pinMode(SoundInPin, INPUT);
pinMode(LedPin, OUTPUT);
myOtherMotor->setSpeed(0);
myOtherMotor->run(FORWARD);
myOtherMotor->run(RELEASE);
}
void loop() {
uint8_t i;
int sensorValue = analogRead(SoundInPin);
int LEDValue = map(sensorValue, 0, 512, 0, 255);
sensorValue = map(sensorValue, 0, 512, 0, 180);
int MoveDelayValue = map(sensorValue, 0, 255, 0, sensorValue);
if (sensorValue > 10) { // 정적 노이즈 차단
delay(1);
myMotor->run(FORWARD);
for (i = 140; i < 255; i++) {
myMotor->setSpeed(i);
}
analogWrite(LedPin, sensorValue);
myMotor->run(RELEASE);
myOtherMotor->run(RELEASE);
delay(1);
}
analogWrite(LedPin, 0);
}