Welcome to Hackster!
Hackster is a community dedicated to learning hardware, from beginner to pro. Join us, it's free!
Nghia Tran
Published

30 Songs Music Box with Arduino

Make a crazy music box

IntermediateShowcase (no instructions)Over 1 day4
30 Songs Music Box with Arduino

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
pca9685 16-channel Servo Driver
×1
3x4 Phone-style Matrix Keypad
×1
VANYUST S22 Portable Charger, 5000mAh
×1
Terminal Block,2 Pack 6 Circuits 20-30A 200v-450v Dual Row
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Music Box 30 Schematic

Wiring diagram of music box

Code

MusicBox30_v1

C/C++
Arduino C code that read 3x4 matrix keypad and control individual servo to move actuator to opened or closed positions.
/*************************************************** 
  Filename: This code perform following functions:
  1. Read 3x4 matrix keypad PID3845
  2. Control individual 30 servos to open or close position via I2C bus 
  using 2 PCA9685 drivers

  Author: Nghia Tran
  Date Jan 8, 2024
  
  This code is base on examples from Adafruit for multi-servo 
  channels driver PCA9685 
   ------> http://www.adafruit.com/products/815
    
  These drivers use I2C to communicate, 2 pins are required to  
  interface.

 ****************************************************/

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
#include "Adafruit_Keypad.h"

Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x40); //I2C address of board 1
Adafruit_PWMServoDriver pwm2 = Adafruit_PWMServoDriver(0x41); //I2C address of board 2

#define NUM_MUSICBOX 30
#define BUF_LEN 5

// Depending on your servo make, the pulse width min and max may vary, you 
// want these to be as small/large as possible without hitting the hard stop
// for max range. You'll have to tweak them as necessary to match the servos you
// have!
#define SERVOMIN  400 //Actuator closed position // Servo Min: 150 This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  470 //Actuator open position   // Servo Max: 600 This is the 'maximum' pulse length count (out of 4096)
#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

// define your specific keypad here via PID
#define KEYPAD_PID3845    //   * PID3845 3x4 Matrix Keypad
// define your pins here
// can ignore ones that don't apply
#define R1    2
#define R2    3
#define R3    4
#define R4    5
#define C1    8
#define C2    9
#define C3    10
#define C4    11
// leave this import after the above configuration
#include "keypad_config.h"
//initialize an instance of class NewKeypad
Adafruit_Keypad customKeypad = Adafruit_Keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS);

// our servo # counter
uint8_t servonum = 0;
uint8_t boxplaying = 0;
int servo_oe1_pin = 12;
int servo_oe2_pin = 13;
uint8_t key_press = 0;
uint8_t key = 0;
uint8_t num_digit = 0;
int song_num = 0;
uint8_t key_buf[BUF_LEN];
uint8_t buf_index = 0;
boolean new_song = false;
uint8_t song_current = 0;
boolean stop_song = false;
boolean start_song = false;


void setup() {
  Serial.begin(9600);
  Serial.println("8 channel Servo test!");
  customKeypad.begin();
  pinMode(servo_oe1_pin, OUTPUT);  // This pin control output enable of PCA9685 board
  pinMode(servo_oe2_pin, OUTPUT);  // This pin control output enable of PCA9685 board
  pinMode(servo_oe1_pin, HIGH);     // Disable all motors
  pinMode(servo_oe2_pin, HIGH);     // Disable all motors);     // Disable all motors
  
  pwm1.begin();
  pwm2.begin();
   /*
   * In theory the internal oscillator (clock) is 25MHz but it really isn't
   * that precise. You can 'calibrate' this by tweaking this number until
   * you get the PWM update frequency you're expecting!
   * The int.osc. for the PCA9685 chip is a range between about 23-27MHz and
   * is used for calculating things like writeMicroseconds()
   * Analog servos run at ~50 Hz updates, It is importaint to use an
   * oscilloscope in setting the int.osc frequency for the I2C PCA9685 chip.
   * 1) Attach the oscilloscope to one of the PWM signal pins and ground on
   *    the I2C PCA9685 chip you are setting the value for.
   * 2) Adjust setOscillatorFrequency() until the PWM update frequency is the
   *    expected value (50Hz for most ESCs)
   * Setting the value here is specific to each individual I2C PCA9685 chip and
   * affects the calculations for the PWM update frequency. 
   * Failure to correctly set the int.osc value will cause unexpected PWM results
   */
  pwm1.setOscillatorFrequency(27000000);
  pwm1.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates
  pwm2.setOscillatorFrequency(27000000);
  pwm2.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates

  delay(10);
}
//********************************
void setPCA9685Enable(void){
  digitalWrite(servo_oe1_pin, LOW);
  digitalWrite(servo_oe2_pin, LOW);
}


//********************************
void setPCA9685Disable(void){
  digitalWrite(servo_oe1_pin, HIGH);
  digitalWrite(servo_oe2_pin, HIGH);
}


//*********************************
void setLockAll(void){
  setPCA9685Enable();
  delay(10);
  for (servonum = 0; servonum < 16; servonum++){
    for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
     pwm1.setPWM(servonum, 0, pulselen);
    }
    for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
     pwm2.setPWM(servonum, 0, pulselen);
    }
    delay(10);
  }
  setPCA9685Disable();
}

//*******************************************************************************
// setPlayBox
// input: box number
// execute: Move actuator to open position
// If box number from 1-15, access servo driver PCA9685 board #1, I2C address 0x40
// If box number from 16-30, access servo driver PCA9685 board #2, I2C address 0x41
//**********************************************************************************
void setPlayBox(uint8_t boxnum){

   delay(100);
   if(boxnum < 16){
      servonum = boxnum - 1;
      pwm1.setPWM(servonum, 0, SERVOMAX);
   }
   else if(boxnum < 31){
      servonum = boxnum - 16;
      pwm2.setPWM(servonum, 0, SERVOMAX);
   }

  delay(100);
  setPCA9685Enable();
  delay(100);
  
  if(boxnum < 16){
    servonum = boxnum - 1;
    for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
      pwm1.setPWM(servonum, 0, pulselen);
      delay(3); //Move slow
    }
  }
  else if(boxnum < 31){
    servonum = boxnum - 16;
    for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
      pwm2.setPWM(servonum, 0, pulselen);
      delay(3); //Move slow
    }
  }
  else{
    }
  delay(500);
  //setPCA9685Disable(); //Don't do this. It makes servo go crazy
  delay(100);
}
//**********************************************
// SetStopBox
// input: box number
// execute: Move actuator to closed position
// If box number from 1-15, access servo driver PCA9685 board #1, I2C address 0x40
// If box number from 16-30, access servo driver PCA9685 board #2, I2C address 0x41
// If box number is 88 following by a "#", all servo move to openned positions
// If box number is 99 following by a "#", all servo move to closed positions
//**********************************************
void setStopBox(uint8_t boxnum){
  
  delay(100);
  setPCA9685Enable();
  delay(100);
  if(boxnum < 16){
    servonum = boxnum - 1;
    for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
     pwm1.setPWM(servonum, 0, pulselen);
     delay(3); //Move slow
    }
  }
  else if(boxnum < 31){
    servonum = boxnum - 16;
    for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
     pwm2.setPWM(servonum, 0, pulselen);
     delay(3);  //Move slow
    }
  } 

  // if boxnum is 99, set all motors to lock position
  else if(boxnum == 99){
    // Drive each servo one at a time using setPWM()
    for (servonum = 0; servonum <= 15; servonum++){
      for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
        pwm1.setPWM(servonum, 0, pulselen);
        delay(3);
      }
        
      delay(100);   
      Serial.println(servonum);
      for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
        pwm2.setPWM(servonum, 0, pulselen);
        delay(3);
      }
       
      delay(100);
    }
  }

  // if boxnum is 88, set all motors to open position
  else if(boxnum == 88){
    // Drive each servo one at a time using setPWM()
    for (servonum = 0; servonum <= 15; servonum++){
      for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
        pwm1.setPWM(servonum, 0, pulselen);
        delay(3); //Move slow
      }
        
      delay(100);   
      Serial.println(servonum);
      for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
        pwm2.setPWM(servonum, 0, pulselen);
        delay(3); //Move slow
      }
       
      delay(100);
    }
  }
  else{
    }
  delay(500);
  //setPCA9685Disable();
  delay(100);
}

//***************************************************************
//* Main loop here
//***************************************************************
void loop() {
  // put your main code here, to run repeatedly:
  customKeypad.tick();
  
  while(customKeypad.available()){
    keypadEvent e = customKeypad.read();
    key_press = (uint8_t)e.bit.KEY;
    //Serial.println(key_press);
    Serial.println((char)e.bit.KEY);
    if(e.bit.EVENT == KEY_JUST_PRESSED) Serial.println(" pressed");
    else if(e.bit.EVENT == KEY_JUST_RELEASED){
      Serial.println(" released");
      if(key_press == 35){ //If key press is "#", stop play song
        //Stop play
        //clear key buffer
        if(num_digit == 0){
          for(int i = 0; i < BUF_LEN; i++){
            key_buf[i] = 0;
          }
        }
        else if (num_digit == 1){
          song_num = key_buf[0] - 48;
          Serial.println(song_num);
        }
        else if (num_digit == 2){
          song_num = 10*(key_buf[0] - 48) + (key_buf[1] - 48);
          Serial.println(song_num);
        }
        new_song = true;
        stop_song = true;
        start_song = false;
        num_digit = 0;
      }
      else if(key_press == 42){ //If key press is "*", start play song
//         for(int i = 0; i < BUF_LEN; i++){
//           Serial.print(key_buf[i]);
//           Serial.println();
//        }
        if (num_digit == 1){
          song_num = key_buf[0] - 48;
          Serial.println(song_num);
        }
        else if (num_digit == 2){
          song_num = 10*(key_buf[0] - 48) + (key_buf[1] - 48);
          Serial.println(song_num);
        }
        num_digit = 0;
        new_song = true;
        stop_song = false;
        start_song = true;
      }    
      else if(key_press >=48 && key_press <= 57){ //Key 0-9
        if(num_digit == 0){
          key_buf[0] = key_press;
          num_digit++;
        }
        else if(num_digit == 1){
          key_buf[1] = key_press;
          num_digit++;
         }
        else if(num_digit >= 2){
          num_digit = 2;
        }
//        Serial.println(num_digit);
      }
    }
  }

  if(new_song == true){
    Serial.print("Song number: ");
    Serial.println(song_num);
    if(song_num != song_current){
       if(start_song == true){
          // Stop current play
          //setStopBox(song_current);
          Serial.print("Stop playing song: ");
          Serial.println(song_current);
          setStopBox(song_current);
          delay(1000);
          Serial.print("Star playing song: ");
          Serial.println(song_num);
          setPlayBox(song_num);
          delay(1000);
          song_current = song_num;
          new_song = false;
       }
       else{
          Serial.print("Stop playing song: ");
          Serial.println(song_num);
          setStopBox(song_num);
          //song_current = 0;
          new_song = false;
       }
        
    }
    else{
       Serial.print("Play same song ");
       if(start_song == false){
          Serial.print("Stop playing song: ");
          Serial.println(song_num);
          setStopBox(song_current);
          song_current = 0;
       }
       new_song = false;
    }
  }

  delay(30);
    
}

Credits

Nghia Tran
6 projects • 3 followers
Contact

Comments

Please log in or sign up to comment.