Всем привет! Сегодня я расскажу про то, как я собрал ардуино робота, играющего музыку на винных бокалах. Если вам интересно, то прошу под кат
Бокалы
Я перепробовал звучание разных бокалов, которые нашёл у себя в доме и, как оказалось, современные бокалы звучат как-то очень слабо, возможно, в этом есть китайский след. А вот старые хрустальные фужеры звучат приятно и громко. Только у меня их оказалось всего 7 шт., что в последствии выльется в ограничение проигрываемых нот. Не хватило всего 1 бокала для проигрывания полноценной мелодии от начала до конца. Пришлось воспроизводить только небольшие кусочки мелодий.
Сразу оговорюсь, я не музыкант и у меня нет музыкального слуха. Поэтому подгонка тональности бокалов к нотам оказалась для меня самой сложной задачей из этого проекта. Так что заранее прошу меня простить за ошибки в звучании нот.
Молоточек
Ударный молоточек сделал из автомобильного реле, купленного в ближайшем авто-магазине. Удалил с него корпус и спилил лишние контакты, а на якорь припаял медную проволоку диаметром 1 мм. Этот медный провод я извлек из силового кабеля, который используется для бытовой проводки 220 В. В качестве самого молотка колотушки я использовал кусочек деревянного, мебельного шканта.
Просверлил в нём отверстие и нанизал его на кусок провода из меди, после чего для надежности капнул сверху клеем «Момент».
Для управления этой барабанной палочкой я использовал старый, советский транзистор KT972, управление которым осуществляется с контроллера через резистор от 270 до 510 Ом. Для гашения отрицательных импульсов самоиндукции, параллельно катушке, припаял выпрямительный диод 1N4007. Такой можно найти в любой, отслужившей свой срок, светодиодной лампочке или в старой бытовой технике. Транзистор можно заменить на любой другой, в том числе и на MOSFET, с минимальным током перехода не менее 1 А.
Соленоид с молоточком разместил на пластиковой рейке. После чего его нужно сбалансировать, так как дисбаланс будет вызывать вибрацию. Для этого, на противоположной стороне рейки, прикрутил металлическую гайку, предварительно подобрав её по весу. Далее, приклеиваем рейку на распечатанную 100 лет назад на 3D принтере шестеренке.
Чтобы выравнять ударный механизм по высоте с бокалами, я приклеил ШД на пластиковую банку через прокладку из вспененного полиэтилена. Прокладка нужна, чтобы банка не усиливала шум мотора.
Шаговый двигатель
ШД использовал HANPOSE 17HS4401. Для максимальной скорости вращения, попробовал полношаговый режим, но в этом режиме есть один недостаток: мотор очень громко работает и его жужжание очень сильно выделяется на на фоне звука бокалов. Пришлось пожертвовать скоростью и использовать режим полушага(1/2), а это в свою очередь 400 шагов на один оборот вала. В таком режиме шаговый двигатель начал работать заметно тише, но и почти в 2 раза медленнее, хотя этого вполне хватает для проигрывания спокойной музыки.
В проекте использовался драйвер шагового двигателя A4988, но где-то на форумах я прочитал про тихий драйвер TMC-2100, к сожалению, на момент сборки проекта у меня такого не нашлось и пришлось подстраиваться к пониженной скорости мотора. Отказавшись от библиотеки «A4988.h», мне удалось с выжать с ШД максимум.
Подсветка
Подсветка сделана всего на одном светодиоде ws2812, который я отрезал от светодиодной ленты.
Цвет светодиода изменяется рандомно, в пределах семи основных цветов: от белого до красного. Для управления им я использовал библиотеку NeoPixel. Для снижения боковой засветки, на светодиод я приклеил пластиковое кольцо.
Схема
Соединил все компоненты по нарисованной мной схеме. Установил переключатели на драйвере шагового двигателя в режим полудуплекса. На соленоид и мотор подал 15 В., так как молоточек для соленоида тяжеловат и, иногда, при 12 В он не до конца притягивает якорь. Ещё, для уменьшения щелчков якоря, я приклеил на сердечник электромагнита кусочек ткани.
Используемые в схеме компоненты:
Arduino Nano
Шаговый двигатель 17HS4401
Драйвер шагового двигателя A4988
Плата расширения для драйвера шагового двигателя
Провода соединительные
Светодиод WS2812
Транзистор Mosfet
Реле автомобильное на 30 А
Музыка
Мелодию можно написать самому или найти готовую на сайте musicboxmaniacs. Если вы будете использовать готовую мелодию, то для начала у неё нужно убрать многоголосность — это значит, что в одном музыкальном такте не должно быть более одной ноты. После чего, перенести её в ручную следующем порядке: самая низкая нота имеет значение 0 и так далее до самой высокой. В моем распоряжении всего 7 бокалов, а это значит, что и нот не должно быть больше 7. Для простых мелодий этого будет достаточно. И ещё есть пустой такт — его значение в массиве равно 255. Звук бокалов подстраивается под нужную ноту при помощи воды. Для понижения тона нужно подливать в него воду. Для точной подстройки можно воспользоваться приложением для смартфона «Тюнер пианино» из play Google.
Скетч для ардуино
Алгоритм работы кода следующий: в массиве хранятся ноты, пронумерованные в порядке возрастания от самой низкой равной 0 и до самой высокой равной 6. Каждой ноте соответствует свой бокал. Пустой такт нумеруется значением 255.
Тактирование мелодии происходит по таймеру и темп музыки можно задавать в настройках кода. Для этого нужно указать требуемое количество тактов за одну секунду. Но имейте ввиду, всему есть предел и настанет момент, когда мотор не будет успевать за темпом музыки и начнет пропускать ноты. Особенно при переходе с самой высокой тональности на самую низкую, и наоборот. Кстати, чем больше будет бокалов, тем меньше шагов нужно прокручивать между нотами.
С каждым тактом из массива выбирается текущая и следующая нота, которая преобразуется в соответствующее количество шагов и отправляется на выполнение в шаговый двигатель. Алгоритм считывает ноты с опережением на 1 шаг, что дает возможность заранее переместить молоточек к следующему бокалу .
Все настройки находятся в шапке скетча и прописаны в директивах define.
#include "Adafruit_NeoPixel.h"
#include "CyberLib.h"
#define DEBUG false //false true включить режим отладки
#define DIR_1 D8_High
#define STEP_1 D9_High
#define HAMMER_1 D10_High
#define DIR_0 D8_Low
#define STEP_0 D9_Low
#define HAMMER_0 D10_Low
#define WS2812_PIN 11 // выход для подключения ws2812
#define step_num 400 // количество шагов на 1 оборот ШД. включен полушаговый режим для снижения шума
#define note_num 7 // Количество нот-бокалов
#define step_note step_num/note_num // количество шагов двигателя между нотами. вычисляет автоматически
#define step_duration 950 // длительность шага влияет на скорость ШД: чем ниже значение, тем быстрее, но есть вероятность пропуска шагов
#define ratio 1.3 // коэффициент делитель длительности отрицательного импульса шага
#define kick_duration 8 // длительность удара молоточка в мс.
#define tact 6 // количество тактов за 1 сек., но нужно учитывать скорость перемещения ШД от ноты к ноте
#define tact_us 1000000/tact // длительность такта в мкс. вычисляет автоматически
#define NUM_PIX 1 // количество светодиодов в ленте
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_PIX, WS2812_PIN, NEO_GRB + NEO_KHZ800);
volatile uint8_t tact_num=0, int_state=0;
uint8_t sound=0;
int next_sound=0;
const uint8_t music[] PROGMEM = {255,255,0,1,2,255,2,255, 2,3,4,255,4,255,4,5, 3,255,3,255,2,1,1,2,
255,255,0,1,2,255,2,255, 2,3,4,255,4,255,4,5, 3,255,3,255,2,1,2,255,255,255 };
/* const uint8_t music[] PROGMEM = { 255,255,2,255,2,255,2,255, 255,255,2,255,2,255,2,255, 255,255,2,255,4,255,0,255,
255,1,2,255,255,255,255,255, 255,255,3,255,3,255,3,255, 255,2,3,255,2,255,2,255,
1,2,255,1,255,1,255,2, 255,1,255,255,255,4,255,255, 255,2,255,2,255,2,255,255,
255,2,255,2,255,2,255,255, 255,2,255,4,255,0,255,255, 1,2,255,255,255,255,255,255,
255,3,255,3,255,3,255,255, 2,3,255,2,255,2,255,255, 2,4,255,4,255,3,255,1,
255,0,255,255,255,255,255,255 };
const uint8_t music[] PROGMEM = { 255,255, 5,3,0,3,5,3,0,3, 5,2,0,2,5,2,0,2, 4,2,0,2,4,2,0,2, 4,2,0,2,4,2,0,2,
5,3,0,3,5,3,0,3, 5,3,0,3,5,3,0,3, 5,2,0,2,5,2,0,2, 5,2,0,2,5,2,0,2,
5,3,1,3,5,3,1,3, 5,3,1,3,4,3,1,3, 6,4,2,4,6,4,2,4, 6,2,0,2,5,2,0,2,
5,3,0,3,5,3,0,3, 6,3,0,3,6,3,0,3, 255,255,255,255 }; */
// const uint8_t music[] PROGMEM = { 255,0,1,2,3,4,5,6,5,4,3,2,1,0,255 }; // для расстановки бокалов
void setup()
{
D10_Out;
HAMMER_1; D8_Out; D9_Out; DIR_0; STEP_0; //Настраиваем пин D8, D9 и D10 на выход
D12_In; D12_High;
pinMode(12, INPUT_PULLUP);
randomSeed(analogRead(0)); // получаем начальное значение случайного числа
strip.begin();
strip.setBrightness(255); // яркость светодиода на максимум
strip.setPixelColor(0,0,0,0);
strip.show();
while(D12_Read){} // ждем нажатия кнопки
#if DEBUG // для отладки кода
Serial.begin(115200);
#endif
StartTimer1(tempo, tact_us); // запуск таймера, первый параметр это обработчик прерывания
}
void loop()
{
if( int_state ) // если было прерывание по таймеру
{
cli(); // запрет прерываний, чтобы избежать нарушений последовательности тактов
uint8_t tmpS = pgm_read_byte_near(&music[tact_num]); // текущая нота
uint8_t tmpN;
if(tact_num != sizeof(music)-1)
{
tmpN = pgm_read_byte_near(&music[tact_num+1]); // следующая нота
} else tmpN = pgm_read_byte_near(&music[0]); // иначе в начало массива
sei();
if(tmpN != 255) next_sound = tmpN; // проверяем следующий сэмпл на пустой такт
if(tmpS != 255) // если не пустой такт, то ударяем молоточком и перемещаемся на следующую ноту
{
HAMMER_0;
sound = tmpS;
uint8_t r=random(2); if(r) r=255;
uint8_t g=random(2); if(g) g=255;
uint8_t b=random(2); if(b) b=255;
if(r==0 && g==0) b=255;
strip.setPixelColor(0,r,g,b);
strip.show();
delay_ms(kick_duration); // длительность импульса удара. Можно регулировать силу удара
HAMMER_1;
}
#if DEBUG //для отладки кода
Serial.println(tact_num);
Serial.println();
#endif
steps((next_sound-sound) * step_note); // перемещаем на следующий бокал
int_state=0; // сбросить флаг прерывания
}
}
//******************выполнение шагов************************
void steps(int shag)
{
if(shag>0) { DIR_1; } else { DIR_0; shag=abs(shag); } // проверка направления вращения, убираем знак(-)
for(uint16_t i = 0; i < shag; i++) // Выполняем заданное количество шагов
{
STEP_1;
delay_us(step_duration); // длительность шага, влияет на скорость вращения
STEP_0;
// delay_us(step_duration/ratio);
}
}
//****************обработчик прерывания таймера1***********
void tempo()
{
int_state=1; // флаг прерывания установлен
tact_num++; // добавим 1 такт
if( tact_num > sizeof(music)-1) tact_num=0; //если достигли конца массива, то сбрасываем в начало
}
Заключение
Спасибо, что дочитали до конца!
Надеюсь, эта статья вам понравилась и вы сможете воспользоваться моей наработкой в своих проектах.
Если у Вас остались вопросы и замечания, то можете задать их в комментариях. Я с удовольствием на них отвечу.