Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Manufacturing Technology >> Производственный процесс

Создайте своего монстра в коробке: проект робототехники на Arduino

Компоненты и материалы

Деревянный ящик/коробка×1
Arduino UNO×1
Adafruit Wave Shield×1
Модуль реле 4-канальный SainSmart×1
Мотор стеклоочистителя×1
Красные светодиодные гирлянды (100 шт.)×1
Дымовая машина×1
Усилитель/колонка с питанием×1

О проекте

Обновление до версии 4

Код Arduino полностью переписан. Основные изменения:

Актуальный код доступен на GitHub.

Монстр в коробке

Я обожаю хэллоуинские реквизиты и, вдохновившись чужими проектами «Монстр в коробке», решил построить свой. Идея проста: создать иллюзию, что в контейнере скрывается страшное существо, готовое вырваться на свободу. Эффект достигается сочетанием движения, звука, света, дыма и внезапности.

Что внутри коробки?!

Коробка должна быть достаточно большой, чтобы казаться способной скрыть что-то огромное и страшное. Вид старого, изношенного ящика, едва сдерживающего монстра, усиливает эффект. Не нашел подходящего варианта в секонд-хендах — собрал сам.

Использовал 15 досок из сосны (180 см) из Home Depot. Собрал коробку примерно 90×75×75 см. Доски распилил на циркулярной пиле, сцепил пневматическим гвоздезабивателем 18G. Позже понял, что гвозди не выдерживают нагрузок от ударов крышки — усилил конструкцию сухими шурупами 25 мм (закрутил изнутри, чтобы головки не виднелись).

Дерево искусственно состарил: настоем черного чая, уксуса и стальной шерсти. Украсил шаблонами и этикетками, найденными в сети или напечатанными самостоятельно.

Создайте своего монстра в коробке: проект робототехники на Arduino

Мозги!!!

У реквизита два режима: «Спит» и «Бодрствует». В режиме сна коробка тихая, издает только храп и легкое рычание. Пробуждение происходит при срабатывании PIR-датчика движения. Монстр рявкает, выдыхает дым, крышка прыгает, внутри загорается красный свет.

Управление: Arduino Uno + Wave Shield (звук) + модуль реле (исполнительные механизмы). На Wave Shield загружены файлы со звуками сна и рёва. PIR-датчик переключает режимы. В режиме бодрствования Arduino через реле включает мотор крышки, красную подсветку, дымогенератор и запускает звуки. Добавлена кнопка «Пауза» для временного отключения при появлении маленьких детей.

Создайте своего монстра в коробке: проект робототехники на Arduino

Оживает!

Подъемник крышки — устройство, которое быстро поднимает и опускает крышку, имитируя попытки монстра выбраться. Популярны пневматические приводы и вращающиеся эксцентрики. Пневматика давала бы более случайные движения, но я выбрал вращающийся эксцентрик — проще и дешевле.

Купил б/у мотор стеклоочистителя на свалке за $15. Продавец удивился, когда я сказал, что марка машины не важна. После объяснений он сам захотел собрать такой реквизит :)

Эти моторы работают от 12 В, имеют хороший крутящий момент и подходящую скорость. Важно: ищите мотор с остатками проводной разъемной колодки — так легче подпаять свои провода. Нужен также кривошип и шейка; если есть только главная шейка, эксцентрик будет проскальзывать.

Мотор закреплен на боковой стенке внутри коробки. Учтите, что вращающийся эксцентрик не должен задевать провода и другие детали. На YouTube много уроков по работе с такими моторами.

Создайте своего монстра в коробке: проект робототехники на Arduino

Эксцентрик диаметром 30 см вырезал из фанеры 12 мм. Сначала вырезал идеальный круг, установил на мотор, подогнал высоту мотора так, чтобы крышка поднималась примерно на 5 см. Затем лобзиком вырезал на эксцентрике зубчики случайной ширины и глубины.

Создайте своего монстра в коробке: проект робототехники на Arduino

Ролик на нижней стороне крышки закреплен на двух металлических уголках, втулке из нейлона 25 мм и каретном болте. Ролик касается зубчиков эксцентрика.

Создайте своего монстра в коробке: проект робототехники на Arduino

Питание мотора — 12 В от блока ATX через реле под управлением Arduino. Выравнивание потребовало нескольких попыток (хорошо, три раза), но в итоге крышка начала прыгать как надо!

Дыхание огня: дым и свет

Дымогенератор 400 Вт (радиоуправляемый). Для управления от Arduino разобрал пульт, припаял два провода к контактам кнопки «пуск» и подключил их к каналу реле. Простой хак, подсмотренный на YouTube. Также надел старый шланг от пылесоса, чтобы дым выходил плотным потоком из-под крышки — эффект лучше, и конденсат не скапливается на электронике.

Подсветка: обычная гирлянда из 100 красных светодиодов, подключенная через удлинитель, перерезанный и включенный в цепь реле. Arduino включает/выключает гирлянду.

Создайте своего монстра в коробке: проект робототехники на Arduino
Создайте своего монстра в коробке: проект робототехники на Arduino
Создайте своего монстра в коробке: проект робототехники на Arduino

Голос снаружи

Wave Shield имеет 3,5-мм джек для наушников. Подключил его патч-кордом к 30-ваттному бас-гитарному комбо (взял в репетиционной базе). Подойдут любые активные колонки, включая компьютерные, но с хорошим низами звук впечатлянее.

Создайте своего монстра в коробке: проект робототехники на Arduino

Прикупи цепями

Купил пластиковые цепи в магазине хэллоуин-аксессуаров и напечатал на 3D-принтере накладной замок. Обернул коробку — добавило глубины и реализма иллюзии.

Создайте своего монстра в коробке: проект робототехники на Arduino

Хэллоуин!!!

Выставил коробку во дворе, PIR-датчик спрятал в кустах рядом. Соединил датчик с коробкой длинным динамиковым проводом, экспериментировал с позиционированием: монстр просыпался, когда кто-то подходил примерно на 1,5 м.

С наступлением темноты детали оформления коробки стали плохо видны. Подсветил стробоскопическим прожектором — эффект получился отличным.

Дети, подходящие за конфетами, замечали рычащую коробку: некоторые с любопытством, другие — с испугом. Когда монстр оживал с ревом, они вскрикивали, смеялись, некоторые даже аплодировали. Огромный успех!

Улучшения

Надоело прятать датчик в кустах — вклеил его в пластиковую крысу, которую можно класть вдоль дорожки или под кустом. На задней стенке коробки установил разъем RJ11 для подключения «крысиного» датчика по 15-метровому телефонному шнуру — намного удобнее, чем два провода динамикового кабеля.

Создайте своего монстра в коробке: проект робототехники на Arduino
Создайте своего монстра в коробке: проект робототехники на Arduino

На той же панели добавил переключатель и индикаторные светодиоды. Переключатель ставит все функции на паузу без выключения питания. Главный выключатель — сетевой фильтр внутри коробки — неудобен для доступа, а здесь используется только сигнальное напряжение (5 В). Эта кнопка спасала, когда нужно было успокоить монстра для малышей или показать внутренности любопытным.

Пластиковые цепи мешали, когда требовалось залезть в коробку вечером. Решил: на концах цепей закрепил черные карабины с пружиной (тяжелоте на стяжки). Теперь цепи мгновенно отцепляются от кольцевых крючков, вкрученных в нижние углы коробки. Не хай-тек, но очень удобно.

Планы на будущее

Репозиторий на GitHub

Актуальный код и схемы подключения — здесь на GitHub.

Код

Monster In A Box Sketch (C/C++)

/* Wave Shield использует пины: 2, 3, 4, 5, 10, 11, 12, 13
   Пины 13, 12, 11 — SPI для SD-карты (аппаратный SPI).
   По умолчанию библиотека использует пин 10 (CS SD) и пины 2,3,4,5 (DAC).
   Изменение требует правки библиотеки (ссылки на порты PORTD и т.д.).
   Свободны пины 6, 7, 8, 9 и 6 аналоговых входов (цифровые 14-20).
   https://learn.adafruit.com/adafruit-wave-shield-audio-shield-for-arduino/faq */
#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     // Реле: красные светодиоды
#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     // Переопределение WAKE_DELAY в режиме отладки
#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();
  Serial.println("*** System Ready ***");
}

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 isAllowed = (millis() - wakeAllowedTimer) > requiredDelay;
  if (isAllowed) digitalWrite(READY_LED, HIGH);
  return isAllowed;
}

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();
}

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;
}

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);
}

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();
  }
}

inline void energizeRelay(int channel) { digitalWrite(channel, HIGH); }   // NO — разомкнут
inline void de_energizeRelay(int channel) { digitalWrite(channel, LOW); } // NO — замкнут

MonsterSounds.h (C/C++)

/* ПИНЫ ARDUINО
   Пины 13, 12, 11 — всегда используются SD-картой (аппаратный SPI).
   Еще 5 пинов для DAC и SD, по умолчанию: 10 (CS SD) и 2,3,4,5 (DAC).
   Изменение требует правки библиотеки (обращение к портам PORTD и т.д.).
   Свободны: 6, 7, 8, 9 и 6 аналоговых входов (цифровые 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 (C/C++)

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();
}

Схемы

Создайте своего монстра в коробке: проект робототехники на Arduino

Производственный процесс

  1. ЭЭГ-аппараты: дизайн, производство и новые применения
  2. Революция соковых коробок: история, дизайн и экологический след
  3. Торговые автоматы: индустрия США на $36,6 млрд
  4. Экспертное руководство по современным обменным автоматам: дизайн, компоненты и будущие тренды
  5. Эволюция и производство швейных машин: от изобретений XIX века к современной автоматизации
  6. Колготки: полное руководство по элегантному женскому хоузири
  7. Сборка домашней машины для написания домашних заданий (плоттер-писатель)
  8. Giftduino: идеальный набор Arduino для подарков — проект для хоббистов и педагогов
  9. Создание ультразвукового левитатора на Arduino: пошаговое руководство
  10. Компактная метеостанция: Arduino Nano + ESP8266, BMP280, OLED / Nokia 5110 LCD