산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

아두이노 로봇 프로젝트: 나만의 '상자 속 괴물' 만들기

부품 및 소모품

부품 수량 비고
나무 상자/크레이트 1 직접 제작 또는 구매
아두이노 우노 (Arduino UNO) 1 메인 컨트롤러
아다프루트 웨이브 쉴드 (Adafruit Wave Shield) 1 오디오 재생용
SainSmart 4채널 릴레이 모듈 1 모터, 조명, 연무기 제어
자동차 와이퍼 모터 1 뚜껑 개폐용 (12V, 고토크)
빨간 LED 크리스마스 라이트 (100구) 1 내부 조명 효과
연무기 (Smoke Machine) 1 연기 효과용
파워 앰프/스피커 1 사운드 출력용

프로젝트 개요

버전 4 업데이트 사항

아두이노 코드를 전면 재작성했습니다. 주요 변경 사항:

최신 코드는 GitHub 저장소에서 확인하세요.

몬스터 인 어 박스 (Monster In A Box)

할로윈 소품을 좋아해 작년 다양한 '상자 속 괴물' 제작 사례를 보고 직접 만들기로 했습니다. 컨셉은 간단합니다: 무언가 크고 무서운 괴물을 간신히 가두고 있는 듯한 상자를 만들어, 움직임·소리·빛·연기·놀라움으로 탈출 직전의 긴장감을 연출하는 것입니다.

상자 제작: 크기와 분위기

괴물이 들어갈 만큼 충분히 크고(약 90×75×75cm), 낡고 허술해 보여야 합니다. 중고 가게를 돌다 마음에 드는 게 없어 직접 제작했습니다. 홈디포에서 6피트 소나무 펜스 판재 15개를 구해 테이블 톱으로 재단하고, 18게이지 브래드 네일러로 조립했습니다. 하지만 모터 진동으로 브래드가 버티지 못해 나중에 1인치 드라이월 나사로 안쪽에서 보강했습니다.

목재 에이징은 홍차·식초·스틸울 반응을 이용했고, 온라인에서 찾거나 직접 만든 스텐실과 라벨로 장식했습니다.

아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기

두뇌: 아두이노 + 웨이브 쉴드 + 릴레이

상자는 '수면'과 '각성' 두 가지 상태를 가집니다.

아두이노 우노가 웨이브 쉴드(수면/포효 사운드 저장)와 4채널 릴레이 모듈(모터, 빨간 LED, 연무기 제어)을 오케스트라 지휘하듯 제어합니다. 어린 아이들을 위해 일시정지 버튼도 추가했습니다.

아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기

뚜껑 개폐 메커니즘: 와이퍼 모터 + 캠(Cam)

공압 실린더나 회전 캠 방식 중 비용과 난이도를 고려해 캠 방식을 선택했습니다. 폐차장에서 15달러에 중고 와이퍼 모터를 구했습니다(12V 구동, 적절한 RPM, 높은 토크). 모터에 12인치 직경의 1/2인치 합판으로 만든 캠을 장착하고, 톱으로 무작위 너비·깊이의 '이빨'을 깎아 불규칙한 바운스 효과를 냈습니다. 뚜껑 아래에는 롤러(나일론 스페이서 + 캐리지 볼트)를 달아 캠 위를 굴러가게 했습니다. ATX 파워서플라이 12V 레일을 릴레이로 제어해 모터를 구동합니다. 롤러 위치를 세 번 조정 끝에 완벽하게 정렬되었습니다.

아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기
아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기
아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기

불꽃 숨결: 연기 & 빨간 조명

저가 400W 연무기는 본래 무선 리모컨 방식입니다. 수신기 내부 트리거 버튼 패드에 납땜해 릴레이 채널로 제어하도록 해킹했습니다. 진공청소기 호스를 연결해 뚜껑 바로 아래에서 연기 기둥이 나오게 했으며, 이는 시각적 효과뿐 아니라 내부 전자장치 결로 방지에도 도움이 됩니다.

빨간 LED 100구는 연장 코드를 잘라 릴레이 채널에 직렬 연결해 아두이노로 온/오프 제어합니다.

아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기
아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기
아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기

소리: 웨이브 쉴드 + 베이스 앰프

웨이브 쉴드의 3.5mm 출력 잭을 30W 베이스 기타 앰프에 연결했습니다. PC 스피커 등 어떤 앰프든 가능하지만, 저음이 잘 나오는 기기가 훨씬 박진감 넘칩니다.

아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기

체인 & 자물쇠로 사실감 더하기

할로윈 가게 플라스틱 체인과 3D 프린트 자물쇠로 상자를 감싸 분위기를 살렸습니다.

아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기

할로윈 당일 운용

상자를 마당에 놓고 PIR 센서를 인근 덤불에 숨겼습니다. 스피커 선을 길게 끌어 센서 위치를 실험해 1.5m 거리에서 작동하도록 튜닝했습니다. 어둠이 짙어지자 상자 디테일이 안 보여 스트로브 플라이트로 조명하니 효과가 극대화되었습니다. 아이들이 으르렁거리는 상자를 보고 호기심 혹은 공포로 다가오다 괴물이 깨어나면 비명·환호·박수가 터졌습니다. 대성공이었습니다.

개선 사항 (Enhancements)

아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기
아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기

향후 계획

깃허브 저장소

최신 코드와 배선도: GitHub

소스 코드

메인 스케치 (Monster In A Box Sketch)

/* 웨이브 쉴드 사용 핀: 2,3,4,5,10,11,12,13
   13,12,11은 SD 카드 SPI 전용. 10은 SD CS, 2-5는 DAC 기본값.
   라이브러리 수정 없이는 변경 불가. 따라서 6,7,8,9 및 아날로그 핀(A0-A5, 디지털 14-20) 사용 가능. */
#include "MonsterSounds.h"

#define RESERVED_00 0  // Serial RX
#define RESERVED_01 1  // Serial TX
#define RESERVED_02 2  // Wave Shield
#define RESERVED_03 3  // Wave Shield
#define RESERVED_04 4  // Wave Shield
#define RESERVED_05 5  // Wave Shield
#define FOG_MACHINE 6  // 릴레이: 연무기
#define RED_LEDS    7  // 릴레이: 빨간 LED
#define LID_BOUNCER 8  // 릴레이: 뚜껑 모터
#define RESERVED_09 9  // 예비 릴레이
#define RESERVED_10 10 // Wave Shield
#define RESERVED_11 11 // Wave Shield
#define RESERVED_12 12 // Wave Shield
#define RESERVED_13 13 // Wave Shield
#define PIR_SENSOR  A0 // PIR 입력
#define MOTION_LED  A1 // 모션 감지 표시 LED
#define PAUSED_LED  A2 // 일시정지 표시 LED
#define READY_LED   A3 // 각성 대기 표시 LED
#define PAUSE_BUTTON A4 // 일시정지 버튼 (풀업)
#define DEBUG_BUTTON A5 // 디버그 버튼 (풀업)

// 효과 타이머 설정 (초 단위, 수정 가능)
#define WAKE_DELAY         30 // 각성 간 최소 간격(초)
#define WAKE_DELAY_DEBUG   10 // 디버그 모드 시 간격
#define SLEEP_SOUND_DELAY  1  // 수면 사운드 간격(초)
#define WAKE_MIN           3  // 최소 각성 지속(초)
#define WAKE_MAX           5  // 최대 각성 지속(초)
#define RED_LIGHT_EXTRA_TIME 1  // 빨간 조명 추가 시간(초)
#define SMOKE_EXTRA_TIME      2 // 연무기 추가 시간(초)

// 밀리초 변환 (수정 금지)
#define WAKE_DELAY_MILLIS         WAKE_DELAY * 1000
#define WAKE_DELAY_DEBUG_MILLIS   WAKE_DELAY_DEBUG * 1000
#define SLEEP_SOUND_DELAY_MILLIS  SLEEP_SOUND_DELAY * 1000
#define WAKE_MIN_MILLIS           WAKE_MIN * 1000
#define WAKE_MAX_MILLIS           WAKE_MAX * 1000
#define RED_LIGHT_EXTRA_TIME_MILLIS RED_LIGHT_EXTRA_TIME * 1000
#define SMOKE_EXTRA_TIME_MILLIS   SMOKE_EXTRA_TIME * 1000

MonsterSounds sounds;
static unsigned long timeSinceLastSnore = 0;
static unsigned long wakeAllowedTimer = 0;
static unsigned long lidBounceTimer = 0, lidBounceDuration = 0;
static unsigned long smokeTimer = 0, smokeDuration = 0;
static unsigned long redLightTimer = 0, redLightDuration = 0;

enum States {
  STATE_INITIALIZE,
  STATE_PAUSED,
  STATE_SLEEPING,
  STATE_READY_TO_WAKE,
  STATE_AWAKE
};
States state = STATE_INITIALIZE;

void setup() {
  Serial.begin(9600);
  pinMode(LID_BOUNCER, OUTPUT);
  pinMode(RED_LEDS, OUTPUT);
  pinMode(FOG_MACHINE, OUTPUT);
  pinMode(PAUSED_LED, OUTPUT);
  pinMode(MOTION_LED, OUTPUT);
  pinMode(READY_LED, OUTPUT);
  stopAllEffects();
  pinMode(PIR_SENSOR, INPUT);
  pinMode(PAUSE_BUTTON, INPUT_PULLUP);
  pinMode(DEBUG_BUTTON, INPUT_PULLUP);
  sounds.initialize();
  sounds.playSystemReady();
  delay(1000);
  Serial.println("\n*** System Ready ***\n");
}

void loop() {
  boolean pauseSwitchClosed = digitalRead(PAUSE_BUTTON) == LOW;
  boolean motionDetected = digitalRead(PIR_SENSOR) == HIGH;
  digitalWrite(MOTION_LED, digitalRead(PIR_SENSOR));

  switch (state) {
    case STATE_INITIALIZE:
      if (pauseSwitchClosed) goToPause(); else goToSleep();
      break;
    case STATE_PAUSED:
      if (!pauseSwitchClosed) { goToSleep(); digitalWrite(PAUSED_LED, LOW); }
      else digitalWrite(PAUSED_LED, HIGH);
      break;
    case STATE_SLEEPING:
      if (pauseSwitchClosed) goToPause();
      else if (isAllowedToWake()) goToReadyToWake();
      else processSleeping();
      break;
    case STATE_READY_TO_WAKE:
      if (pauseSwitchClosed) { goToPause(); digitalWrite(READY_LED, LOW); }
      else if (motionDetected) { goToAwake(); digitalWrite(READY_LED, LOW); }
      else processSleeping();
      break;
    case STATE_AWAKE:
      if (pauseSwitchClosed) goToPause();
      else if (processAwakeAnimation()) goToSleep();
      break;
    default: Serial.println("UNKNOWN STATE"); break;
  }
}

// 상태 전이 함수들
inline void goToPause() {
  Serial.println("PAUSED");
  state = STATE_PAUSED;
  stopAllEffects();
  sounds.stopAll();
}
inline void goToSleep() {
  Serial.println("GOING TO SLEEP");
  state = STATE_SLEEPING;
  wakeAllowedTimer = millis();
}
inline void goToReadyToWake() {
  Serial.println("READY TO WAKE");
  state = STATE_READY_TO_WAKE;
}
inline void goToAwake() {
  Serial.println("AWAKE");
  state = STATE_AWAKE;
  wakeMonster();
}

// 수면 처리: 코골이 재생
inline void processSleeping() {
  if ((millis() - timeSinceLastSnore) > SLEEP_SOUND_DELAY_MILLIS) {
    sounds.playSnore();
    timeSinceLastSnore = millis();
  }
}

// 각성 허용 여부 판단 (최소 수면 시간 경과 + 디버그 스위치 고려)
inline boolean isAllowedToWake() {
  boolean isDebug = digitalRead(DEBUG_BUTTON) == LOW;
  unsigned long requiredDelay = isDebug ? WAKE_DELAY_DEBUG_MILLIS : WAKE_DELAY_MILLIS;
  boolean allowed = (millis() - wakeAllowedTimer) > requiredDelay;
  if (allowed) digitalWrite(READY_LED, HIGH);
  return allowed;
}

// 괴물 깨우기: 효과 시작 + 포효 사운드
void wakeMonster() {
  int activityDuration = random(WAKE_MIN_MILLIS, WAKE_MAX_MILLIS);
  Serial.print(" wake duration: "); Serial.print(activityDuration); Serial.println(" ms");
  bounceLid(activityDuration);
  flashRedLight(activityDuration + RED_LIGHT_EXTRA_TIME_MILLIS);
  activateSmoke(activityDuration + SMOKE_EXTRA_TIME_MILLIS);
  sounds.playRoar();
}

// 각성 애니메이션 진행 관리 (매 루프 호출, 모두 끝나면 true 반환)
boolean processAwakeAnimation() {
  printTimersToLog();
  boolean done1 = false, done2 = false, done3 = false;
  if (millis() - lidBounceTimer > lidBounceDuration) { bounceLid(0); done1 = true; }
  if (millis() - redLightTimer > redLightDuration) { flashRedLight(0); done2 = true; }
  if (millis() - smokeTimer > smokeDuration) { activateSmoke(0); done3 = true; }
  return done1 && done2 && done3;
}

// 개별 효과 제어 (duration=0 이면 정지)
inline void bounceLid(unsigned long duration) {
  if (duration <= 0) { energizeRelay(LID_BOUNCER); lidBounceDuration = 0; }
  else { de_energizeRelay(LID_BOUNCER); lidBounceTimer = millis(); lidBounceDuration = duration; }
}
inline void flashRedLight(unsigned long duration) {
  if (duration <= 0) { energizeRelay(RED_LEDS); redLightDuration = 0; }
  else { de_energizeRelay(RED_LEDS); redLightTimer = millis(); redLightDuration = duration; }
}
inline void activateSmoke(unsigned long duration) {
  if (duration <= 0) { energizeRelay(FOG_MACHINE); smokeDuration = 0; }
  else { de_energizeRelay(FOG_MACHINE); smokeTimer = millis(); smokeDuration = duration; }
}
inline void stopAllEffects() {
  bounceLid(0); flashRedLight(0); activateSmoke(0);
}

// 디버그용 타이머 로그 출력 (1초마다)
inline void printTimersToLog() {
  static unsigned long timeofLastTimerLog = 0;
  if (millis() - timeofLastTimerLog >= 1000) {
    Serial.print(" lid: "); Serial.print((millis()-lidBounceTimer) > lidBounceDuration ? 0 : (lidBounceDuration-(millis()-lidBounceTimer)));
    Serial.print(" lights: "); Serial.print((millis()-redLightTimer) > redLightDuration ? 0 : (redLightDuration-(millis()-redLightTimer)));
    Serial.print(" smoke: "); Serial.println((millis()-smokeTimer) > smokeDuration ? 0 : (smokeDuration-(millis()-smokeTimer)));
    timeofLastTimerLog = millis();
  }
}

// 릴레이 제어: 상시 개방(NO) 기준
// energizeRelay = HIGH -> NO 단자 열림 (회로 차단)
// de_energizeRelay = LOW -> NO 단자 닫힘 (회로 연결)
inline void energizeRelay(int channel) { digitalWrite(channel, HIGH); }
inline void de_energizeRelay(int channel) { digitalWrite(channel, LOW); }

MonsterSounds.h

/* 아두이노 핀 참고: 13,12,11=SD SPI, 10=SD CS, 2-5=DAC 기본.
   수정하려면 라이브러리 소스(PORTD 등 하드웨어 핀 명시) 변경 필요.
   따라서 6,7,8,9, A0-A5(디지털 14-20) 사용 가능. */
#include 
#include 
#include 
#include 

static const char roar0[] = "ROAR0000.wav";
static const char roar1[] = "ROAR0001.wav";
static const char roar2[] = "ROAR0002.wav";
static const char * const roarSounds[] = {roar0, roar1, roar2};

static const char sleep0[] = "SNORE000.wav";
static const char sleep1[] = "SNORE001.wav";
static const char sleep2[] = "SNORE002.wav";
static const char * const sleepSounds[] = {sleep0, sleep1, sleep2};

int previousRoarSound = -1;

class MonsterSounds {
private:
  SdReader card;
  FatVolume vol;
  FatReader root;
  FatReader file;
  WaveHC wave;
  void playfile(char *name);
public:
  void initialize();
  void playSystemReady();
  void playRoar();
  void playSnore();
  void stopAll();
};

MonsterSounds.ino

void MonsterSounds::initialize() {
  Serial.println("Initializing Sounds...");
  if(!card.init()) Serial.println(F("Card init. failed!"));
  if(!vol.init(card)) Serial.println(F("No partition!"));
  if(!root.openRoot(vol)) Serial.println(F("Couldn't open dir"));
  Serial.println(F("Files found:"));
  root.ls();
  randomSeed(analogRead(0));
}
void MonsterSounds::playSystemReady() { this->playfile("WELCOME0.WAV"); }
void MonsterSounds::playRoar() {
  int index = random(3);
  while (index == previousRoarSound) index = random(3);
  previousRoarSound = index;
  this->playfile(roarSounds[index]);
}
void MonsterSounds::playSnore() {
  if (!wave.isplaying) { // 기존 소리 재생 중이면 코골이 재생 안 함
    int index = random(3);
    this->playfile(sleepSounds[index]);
  }
}
void MonsterSounds::stopAll() { wave.stop(); }

void MonsterSounds::playfile(char *name) {
  PgmPrint("Playing sound: "); Serial.println(name);
  if (wave.isplaying) wave.stop();
  if (!file.open(root, name)) { PgmPrintln("File not found "); return; }
  if (!wave.create(file)) { PgmPrintln("Not a valid WAV"); return; }
  wave.play();
}

회로도

아두이노 로봇 프로젝트: 나만의  상자 속 괴물  만들기

제조공정

  1. 뇌파계(EEG): 설계, 제조, 새로운 응용 분야
  2. 주스 박스 혁명: 역사, 디자인, 환경 영향
  3. 자판기: 366억 달러 규모의 미국 거대 산업
  4. 현대 동전 교환기 전문 가이드: 설계, 구성요소, 미래 트렌드
  5. 재봉틀의 진화와 제조: 19세기 혁신에서 현대 자동화까지
  6. 팬티스타킹: 우아한 여성용 호즈리의 모든 것
  7. 집에서 직접 만드는 DIY 숙제 작성 머신 (펜 플로터)
  8. 기프트두이노(Giftduino): 아두이노 기반 DIY 선물 상자 만들기
  9. 아두이노로 초음파 부양 장치 만들기: 단계별 가이드
  10. 컴팩트 기상 관측소: 아두이노 나노 & ESP8266, BMP280 기압계 & OLED/노키아 LCD