Arduino Robot Plays Music on Wine Glasses

by CyberLab in Circuits > Arduino

2505 Views, 16 Favorites, 0 Comments

Arduino Robot Plays Music on Wine Glasses

glassbit.png
🤖 Arduino РОБОТ играет музыку на бокалах!!! 🎼

Hello everyone, today I will tell you about how I assembled an arduino robot that plays music on wine glasses.

Supplies

4445.750.jpg
4401.jpg

And so, let's take everything in order. To implement the project, I will need: wine glasses, a stepper motor, a car relay, a4988 stepper motor driver - an arduino nano will manage the whole thing.

ноты.jpg

You can write the melody yourself or find a ready-made one on the musicboxmaniacs website. If you use a ready-made melody, then first you need to remove polyphony from it - this means that there should not be more than one note in one musical measure. After that, transfer it manually in the following order: the lowest note has a value of 0, and so on up to the highest. I have only 7 glasses at my disposal, which means that there should not be more than 7 notes. For simple melodies this will be enough. And there is also an empty bar - its value in the array is 255. The sound of the glasses is adjusted to the desired note with the help of water. To lower the tone, you need to add water to it. For fine tuning, you can use the Piano Tuner smartphone app from play Google.

glass-harp.jpg

The algorithm of the code is as follows: the array stores notes numbered in ascending order from the lowest equal to 0 and to the highest equal to 6. Each note corresponds to its own glass. An empty measure is numbered with a value of 255. The melody is clocked by a timer and the tempo of the music can be set in the code settings. To do this, you need to specify the required number of ticks per second. But keep in mind, there is a limit to everything and a moment will come when the motor will not keep up with the tempo of the music and will start skipping notes. Especially when moving from the highest pitch to the lowest, and vice versa. By the way, the more glasses there are, the fewer steps you need to scroll between notes. With each measure, the current and next note is selected from the array, which is converted into the corresponding number of steps and sent for execution to the stepper motor. The algorithm reads notes in advance by 1 step, which makes it possible to move the hammer to the next glass in advance. All settings are in the header of the sketch and are written in the define directives.

#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; //если достигли конца массива, то сбрасываем в начало
}