Gesture Controlled Music Box (The Beatles - Yellow Submarine)
by hannu_hell in Circuits > Sensors
1059 Views, 11 Favorites, 0 Comments
Gesture Controlled Music Box (The Beatles - Yellow Submarine)
The Beatles is one of my favorite band and there's no doubt that their songs have brought happiness to many around the world. I have themed this build on the famous yellow submarine cartoon from the Beatles which I first saw when I was just a kid. To be honest when I first saw the cartoon I had some distressing thoughts initially as the Blue Meanies scared me a bit back then but my fear eventually subsided and I got to enjoy the music and the story. This build is a tribute to the Beatles and their timeless music and besides that I get to listen to their songs anytime I want played by this music box. I have painted the box to resemble the artwork from the yellow submarine cartoon which portrays Pepper land and its colorful characters.
The music box can be controlled using hand gestures with the use of the PAJ7620U2 gesture sensor and is embedded in the yellow submarine which can open with the push of a button. The 3D model was designed using Fusion360 and some parts were printed with translucent green resin and others with plain white filament. The functions of the music box are controlled using a Seeeduino Nano micro controller and uses a DFPlayer mini MP3 player to play the audio files. Additionally I have used Neo-pixels for lighting up the box with multiple colors which I think is very appropriate given the colorful nature of the yellow submarine cartoon. The lights also help in visualizing the gestures corresponding to the actions performed by the micro controller.
I will try to document the build process as best I can in the following steps and hope you like it much as I enjoyed building it.
Supplies
- Seeeduino Nano x 1 link
- PAJ7620U2 Gesture Sensor x 1 link
- Push Button x 1 link
- 1KOhm through hole resistor x 1 link
- ON/OFF Switch x 1 link
- Limit Switch x 1 link
- Micro SD Card x 1
- Female Pin Header (15 sockets) x 2 link
- Female Pin Header (8 sockets) x 2 link
- 8Ohm 0.5W Speaker x 1 link
- WS2812B Neo-pixel strip (30 LEDs) x 1 link
- WS2812B Round PCB with soldered SMD resistor x 1 link
- 5V (1500mAH) Lipo Battery x1
- Acrylic Paint (Different Colors)
- SS rod (Diameter-4mm, Length-125mm) x 1 link
- LM4UU Linear Bearing (Inner Diameter-4mm, Outer Diameter-8mm, Length-12mm) x 2 link
- M2.5 (Length-10mm) hex screw and nut x 7
- M3 (Length-10mm) hex screw x 8
- M3 Brass Threaded inserts x 8 link
- DFPlayer mini MP3 module x 1 link
- USB-C (4 Pin) breakout PCB x 1 link
- 3D Printer (FDM and Resin)
- 3D Printing Filament (white)
- 3D Printing Resin (Translucent Green) x 1
- Soldering Station
- 28 AWG Wires
- Super Glue
- Hot glue gun and hot glue sticks
- L293D Motor Driver x 1 link
- N20 Motor (6V, 300RPM) x 1 link
- N20 Motor Mount x 1 link
- N20 (Hole-3mm, Modulus-0.5, Teeth-15) D-shaped Plastic Gear x 1 link
- Custom PCB x 1 link
3D Model
There are 3 assemblies in this design which are the Box assembly, Rack and Pinion assembly and the Submarine assembly. The Box assembly consists of the box body, the two top panels, the two side panels and the speaker stand. The Rack and pinion assembly consists of the Rack, the Base of the Rack, and the Pinion. The Submarine assembly consists of the Top and Bottom halves of the submarine. The top panels and side panels of the box were printed with translucent green resin and the rest of the parts were printed in plain white filament using a FDM printer.
Submarine Design
The submarine was designed by importing the image as a canvas and then modeled on from there. The form modeling tool was used after applying symmetry to it from the mid axis of the submarine. First by creating a box shape and from there on pulled on the edges and faces to bring out the required shape of the submarine. Once the desired shape was achieved a solid body was created and then shelled with a wall thickness of 3 mm. Finally, the solid body was split into two by first creating a copy and then extruding the cut from the front plane on the first body and then the remaining body was extruded on the other half. This leaves you with two halves of the submarine.
An rectangular extrusion was made from the top half of the submarine to mount the gesture sensor. In addition to that circular cutouts were made on both halves of the submarine. The circular cutout on the top half of the submarine is fitted to the stand on the Base of the Rack and the circular cutout on the bottom half of the submarine was fitted into the stand on the Rack. They can be glued in place only after assembling the Rack and Pinion assembly which will be discussed in at a later stage.
Rack and Pinion Design
The rack was designed using the utilities -> Scripts -> Add-ons in Fusion360 to add a gear tooth profile and then using the rectangular pattern to extend the to the length. Once the rack was created the pinion was created using the same tool and then spaced with a tolerance of 0.1mm. The Base of the rack prevents the rack from moving sideways and it also has holes on either end to fit in the 125mm SS rod which goes through the linear bearings on the bottom of the rack. This arrangement restricts the motion of the rack to only slide in one direction.
The pinion has a tooth profile cutout of the the small plastic gear which fits to the motor shaft as the small plastic gear is stronger than the normal 3D printed material it would provide more strength.
Box Design
The box has cutouts to fit in the the Rack and Pinion Assembly and the Speaker Stand. There are four cutouts to fit in the 4 strips of Neo-pixels on each side. The holes on the top allow for fitting in the threaded inserts to screw the top panels. The small USB-C breakout board fits on the slot on the right side of the box which is connected to the charging wires of the Lipo battery. The front and back sides of the box has cutouts to fit in the side panels which will be glued later on.
3D Printing
You would need to 3D print the two top panels and the two side panels using a resin printed with translucent green resin. You could also use clear resin which would also show through the lights with a diffused effect. I also printed the submarine on a resin printer with the same resin. The resin prints allowed for a more cleaner and detail finishing although the process could be more cumbersome. The rest of the parts were printed using a FDM printer with plain white filament.
Here I would like to briefly outline the steps I carried out on the resin printed parts just to give an overview. Its best to print the top and side panels on vertically on the print bed and add supports, as initially I tried to print it flat on the bed and it resulted in a warped print. Apart from that the top and bottom halves of the submarine also need to be raised up 4mm from the print bed and add supports. They can be placed upside down on the bed as that would leave less support marks on the outer surfaces. For the next step make sure to wear proper PPE before handing resin as it is hazardous to skin. You would need to remove the support material from the prints and then wash them in Isopropyl alcohol. I used an old toothbrush after dipping them in the alcohol to clean it well. Once cleaned, leave them to dry for and them cure them using a curing station that emits UV light or you could leave them under the sunlight. Make sure that they are properly cured before touching them. You can then move on to sand out the rough edges or any marks left from removing the support material. After this I gave them another was in the alcohol and then dried. At this stage if you are satisfied with your prints your done, but my prints looked a bit dull, so I went onto apply a coat of resin with a paint brush on the surface of the prints and then UV cured them.
Painting the Box
As I was going for the looks from the yellow submarine cartoon, I wanted to paint the box with scenes and colors and characters from it. You can sketch out the artwork on the box lightly with a pencil before painting and have an idea of the colors you want to use. I also sketched out the chief Blue meanie and Jeremy Hillary Boob (Yes, that is Nowhere Man's name) who speaks mostly in rhyme. Nowhere Man is one of my favorite characters in the cartoon and he helps the Beatles defeat the Blue Meanies by covering the Chief Blue Meanie with flowers.
Submarine Assembly
Solder four wires of length around 13cm to the pins named SDA, SCL, VCC, and GND. You can leave out the INT pin as we won't be using it. Mount the PAJ7620U2 Gesture Sensor to the top half of the submarine with one 2.5M hex screw and nut through the mounting hole and the wires through the slot cutout on the mounting area. Next solder wires to the round pcb with one WS2812B Neo-pixel (5V, GND, IN pins) leave out the OUT. You can then super glue round WS2812B single Neo-pixel to the back of the submarine. The wires from both the Neo-pixel and the gesture sensor should come out from the back of the submarine.
Rack and Pinion Assembly
Fit in the two linear bearings to the holes at the back of the Rack and then place it inside the Base of the Rack. Push the 125mm SS rod through the hole of the Base of the Rack and guide it through the linear bearings to the hole on the other side of the Base of the Rack. Fit in the plastic gear on the shaft of the N20 motor and then the pinion to the plastic gear. Check the distance and see if the meshing between the Rack and Pinion are good. After determining the correct distance, you can mount the N20 motor in the motor mounting area and attach the motor mounting bracket with two 2.5M hex screws and nuts. At this stage you can power on the motor by supplying 5V to the motor to check if there is any problem with the motion. The pinion should mesh well with the rack.
You can now super glue the top half of the submarine to the stand on the Base of the Rack after checking that the two halves of the submarine come together in the right height. You can mark down with a pencil on the stand before gluing. You can also glue the bottom half of the submarine to the stand on the Rack similarly.
Solder two wires to the NO (Normally Open) and C (Common) leads on the limit switch. Make sure the wires have at least a length of 13cm. Then you can move the bottom half of the submarine so that when it closes it triggers the limit switch. The limit switch is placed on the left end of the Base of the Rack so you can position it at the end and make a mark when the Rack moves inward and pushes on the limit switch. Once you are certain of the position you can super glue it in place.
Box Assembly
Wire the led strips in a square pattern with the first LED strip facing the front of the box. Solder wires to the IN, 5V, and GND of the first LED strip with atleast a length of 13mm for it to reach the PCB which we will mount later on. The direction arrows on the LED strip indicate the signal flow direction. The OUT should connect to the IN on the next strip and the 5V and GND are connected together. After wiring them you can place the LED strips in the slots inside the box.
Use a soldering iron to heat and insert the threaded inserts to the holes on the top of the box.
Solder the charging wires to the small USB-C PCB and fit it inside the slot on the box and place the battery inside the battery compartment.
Super glue the speaker stand to the cutout hole in the box and then use some hot glue to glue the speak on top of the speak stand with the wires from the speaker facing to the front of the box.
You can now super glue in the side panels to the cutouts on the front and back of the box.
Electronics
Lets breakdown the control flow of how the music box works. As you power on the music box with the ON/OFF switch, the rack moves to the closing position allowing the bottom half of the submarine to close until the limit switch is triggered. Once triggered it moves outwards a bit to release the triggering of the limit switch. As you turn on the music box the mini MP3 player is activated and starts playback of all the mp3 files on a loop. In order to control the play, pause, next track, previous track, increase volume, decrease volume and random play and sequential play functions we need to move the bottom half of the submarine outward to reveal the gesture sensor. By pushing on the push button the bottom half of the submarine moves outward and from there on you can control the music box with the gestures. You have to use a gesture to close back the submarine. All the gestures for controlling it will be shown later on.
The PAJ7620U2 Gesture Sensor uses I2C protocol for communication with the micro controller. The connections to the Seeeduino Nano are mentioned below.
- SCL - A5
- SDA - A4
- VCC - 5V
- GND - GND
The DFPlayer Mini MP3 player uses Serial communication with the micro controller. The connections to the Seeeduino Nano are mentioned below.
- VCC - 5V
- RX - D11 (1K resistor in series)
- TX - D10
- GND - GND
The SPK1 and SPK2 pins on the DFPlayer are connected to the leads from the speaker.
The data pin of the Neo-pixels on the led strips are connected to D3 on the Seeeduino Nano and the data pin of the single Neo-pixel on the submarine is connected to D6. 5V and GND are connected to 5V and GND on the Seeeduino Nano.
The NO (Normally Open) pin on the limit switch is connected to D4 and the Push Button is connected to D2 on the Seeeduino Nano. The Discharge wires of the battery are connected to the ON/OFF switch and pushed in place on the front top panel along with the Push Button. The Push Button wires will be soldered to the respective pads on the PCB.
To L293D motor driver is used to control the N20 motor which drives the rack and pinion mechanism. The connections to the Seeeduino Nano are mentioned below.
- ENABLE1 - D5
- INPUT1 - D7
- INPUT2 - D8
- VS & VSS - 5V
- GND - GND
The OUT1 & OUT2 Pins are connected to the motor leads.
In order to simplify the circuit a PCB was used to for the some of the connections. After soldering the header pins you can plug in the MP3 module and Seeeduino Nano to the sockets on the header. The L293D is soldered onto the PCB and the rest of the pads on the PCB allow for external connections.
Many thanks to Seeed Studio for providing the Seeeduino Nano and PCB for this project. Seeed Studio Fusion is a global one-stop online platform for PCB manufacturing, assembly, and hardware customization. Whether you need prototyping, mass production, custom solutions for open-source products, or the transformation of your creative ideas into profitable products, Seeed Studio Fusion can meet your requirements.
Format Music and Control Gestures
Micro SD Card
Prepare the micro SD card and load up all the tracks in the root folder and rename the tracks in the format 0001.mp3, 0002.mp3..0040.mp3 and so on and insert in the DFPlayer Mini MP3 player.
Gestures
The following gestures are used to control the functions of the music box.
- Play/Pause - Wave Slowly Up and Down
- Play Next Track - Swipe Right or Clockwise
- Play Previous Track - Swipe Left or AntiClockwise
- Increase Volume - Swipe Up
- Decrease Volume - Swipe Down
- Close Submarine - Wave Slowly Left and Right
- Play Random all tracks / Play sequentially all tracks - Wave Slowly Forwards and Backwards
Programming
You will need the Arduino IDE to program the Seeeduino Nano and if you have already worked with Arduino this will be exactly the same. Down the zip library from here for the gesture sensor and install by going to Sketch->Include Library->Add .Zip Library. Now we need to install the library for the DFPlayer mini MP3 module and you can do similiarly by downloading the library from here. Next we need to download the library to control the Neo-pixels. You can download it from here and install it.
The code is pretty self explanatory but if you have any questions feel free to ask.
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <DFRobot_PAJ7620U2.h>
#include <FastLED.h>
DFRobot_PAJ7620U2 paj;
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
#define LIMIT_SWITCH 4
#define BUTTON_PIN 2
#define NEO_A_DATA 3
#define NEO_B_DATA 6
#define BRIGHTNESS 64
#define COLOR_ORDER GRB
#define LED_TYPE WS2812B
#define UPDATES_PER_SECOND 100
CRGB neoAleds[28];
CRGB neoBleds[1];
CRGBPalette16 currentPalette;
TBlendType currentBlending;
extern CRGBPalette16 myRedWhiteBluePalette;
extern const TProgmemPalette16 myRedWhiteBluePalette_p PROGMEM;
const int in1 = 7;
const int in2 = 8;
const int en = 5;
bool closed = false;
bool opened = true;
bool gesture_ready = false;
bool play_status = true;
bool random_status = false;
bool currentRandomStatus = false;
bool previousRandomStatus = false;
int current_volume = 2;
void setup() {
FastLED.addLeds<LED_TYPE, NEO_A_DATA, COLOR_ORDER>(neoAleds, 28).setCorrection( TypicalLEDStrip );
FastLED.addLeds<LED_TYPE, NEO_B_DATA, COLOR_ORDER>(neoBleds, 1).setCorrection( TypicalLEDStrip );
FastLED.setBrightness(BRIGHTNESS);
currentPalette = RainbowColors_p;
currentBlending = LINEARBLEND;
mySoftwareSerial.begin(9600);
Serial.begin(115200);
myDFPlayer.begin(mySoftwareSerial);
delay(200);
myDFPlayer.volume(current_volume);
myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);
myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);
pinMode(LIMIT_SWITCH, INPUT_PULLUP);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(en, OUTPUT);
pinMode(in1, OUTPUT);
pinMode(in2, OUTPUT);
digitalWrite(en, HIGH);
myDFPlayer.enableLoopAll();
myDFPlayer.start();
}
void loop() {
if (currentRandomStatus != previousRandomStatus){
if (random_status){
myDFPlayer.randomAll();
}
if (!random_status){
myDFPlayer.enableLoopAll();
}
currentRandomStatus = previousRandomStatus;
myDFPlayer.start();
}
if(!gesture_ready){
neoBleds[0] = CRGB::Black;
static uint8_t startIndex = 0;
startIndex = startIndex + 1;
FillLEDsFromPaletteColors(startIndex);
FastLED.show();
FastLED.delay(1000 / UPDATES_PER_SECOND);
}
if (closed==false && opened && gesture_ready==false){
sub_close();
if (digitalRead(LIMIT_SWITCH) == LOW){
stop_move();
delay(200);
sub_open();
delay(500);
stop_move();
closed = true;
opened = false;
}
}
if (digitalRead(BUTTON_PIN) == LOW){
if (closed && opened==false){
sub_open();
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
static uint8_t thrustIndex = 0;
thrustIndex = thrustIndex + 1;
uint8_t brightness = 255;
for (int j=0; j<300; j++){
neoBleds[0] = ColorFromPalette( currentPalette, thrustIndex, brightness, currentBlending);
thrustIndex += 3;
FastLED.show();
FastLED.delay(1000 / UPDATES_PER_SECOND);
}
gesture_ready = true;
stop_move();
opened = true;
}
}
if (gesture_ready){
paj.begin();
paj.setGestureHighRate(false);
current_volume = myDFPlayer.readVolume();
DFRobot_PAJ7620U2::eGesture_t gesture = paj.getGesture();
if(gesture != paj.eGestureNone ){
if (gesture == paj.eGestureRight || gesture == paj.eGestureClockwise){
next_track_lights();
myDFPlayer.next();
}
else if (gesture == paj.eGestureLeft || gesture == paj.eGestureAntiClockwise){
previous_track_lights();
myDFPlayer.previous();
}
else if (gesture == paj.eGestureUp){
volume_up_lights();
Serial.println(current_volume);
switch(current_volume){
case(0): {
myDFPlayer.volume(2);
break;
}
case(2): {
myDFPlayer.volume(6);
break;
}
case(6): {
myDFPlayer.volume(10);
break;
}
case(10): {
myDFPlayer.volume(16);
break;
}
case(16): {
myDFPlayer.volume(21);
break;
}
case(21): {
myDFPlayer.volume(26);
break;
}
case(26): {
myDFPlayer.volume(30);
break;
}
case(30): {
myDFPlayer.volume(30);
break;
}
default:;
}
Serial.println(current_volume);
}
else if (gesture == paj.eGestureDown){
volume_down_lights();
Serial.println(current_volume);
switch(current_volume){
case(0): {
myDFPlayer.volume(0);
break;
}
case(2): {
myDFPlayer.volume(0);
break;
}
case(6): {
myDFPlayer.volume(2);
break;
}
case(10): {
myDFPlayer.volume(6);
break;
}
case(16): {
myDFPlayer.volume(10);
break;
}
case(21): {
myDFPlayer.volume(16);
break;
}
case(26): {
myDFPlayer.volume(21);
break;
}
case(30): {
myDFPlayer.volume(26);
break;
}
default:;
}
}
else if (gesture == paj.eGestureWaveSlowlyUpDown){
play_status = !play_status;
pause_play_lights();
if (play_status){
myDFPlayer.start();
} else {
myDFPlayer.pause();
}
}
else if (gesture == paj.eGestureWaveSlowlyForwardBackward){
random_status = !random_status;
currentRandomStatus = !previousRandomStatus;
play_mode_lights();
myDFPlayer.stop();
}
else if (gesture == paj.eGestureWaveSlowlyLeftRight || gesture == paj.eGestureWaveSlowlyDisorder){
gesture_ready = false;
closed = false;
opened = true;
}
}
}
}
void FillLEDsFromPaletteColors( uint8_t colorIndex)
{
uint8_t brightness = 255;
for( int i = 0; i < 28; ++i) {
neoAleds[i] = ColorFromPalette( currentPalette, colorIndex, brightness, currentBlending);
colorIndex += 3;
}
}
void pause_play_lights(){
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int m=0; m<2; m++){
for (int j=0; j<8; j++){
neoAleds[j] = CRGB::Cyan;
FastLED.show();
}
FastLED.delay(100);
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int k=14; k<22; k++){
neoAleds[k] = CRGB::Cyan;
FastLED.show();
}
FastLED.delay(100);
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
}
for (int n=0; n<28; n++){
neoAleds[n] = CRGB::Cyan;
FastLED.show();
}
}
void play_mode_lights(){
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int j=0; j<28; j++){
neoAleds[j] = CRGB::Cyan;
FastLED.delay(20);
FastLED.show();
}
FastLED.delay(100);
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int j=28; j>=0; j--){
neoAleds[j] = CRGB::Cyan;
FastLED.delay(20);
FastLED.show();
}
FastLED.delay(100);
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int j=0; j<28; j++){
neoAleds[j] = CRGB::Cyan;
FastLED.delay(20);
FastLED.show();
}
}
void next_track_lights(){
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int j=0; j<28; j++){
neoAleds[j] = CRGB::Cyan;
FastLED.delay(20);
FastLED.show();
}
}
void previous_track_lights(){
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int j=28; j>=0; j--){
neoAleds[j] = CRGB::Cyan;
FastLED.delay(20);
FastLED.show();
}
}
void volume_up_lights(){
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int j=0; j<8; j++){
neoAleds[j] = CRGB::Cyan;
FastLED.show();
}
FastLED.delay(200);
for (int k=8; k<14; k++){
neoAleds[k] = CRGB::Cyan;
FastLED.show();
}
FastLED.delay(200);
for (int l=14; l<22; l++){
neoAleds[l] = CRGB::Cyan;
FastLED.show();
}
FastLED.delay(200);
for (int m=22; m<28; m++){
neoAleds[m] = CRGB::Cyan;
FastLED.show();
}
}
void volume_down_lights(){
for (int i=0; i<28; i++){
neoAleds[i] = CRGB::Black;
FastLED.show();
}
for (int j=22; j<28; j++){
neoAleds[j] = CRGB::Cyan;
FastLED.show();
}
FastLED.delay(200);
for (int k=14; k<22; k++){
neoAleds[k] = CRGB::Cyan;
FastLED.show();
}
FastLED.delay(200);
for (int l=8; l<14; l++){
neoAleds[l] = CRGB::Cyan;
FastLED.show();
}
FastLED.delay(200);
for (int m=0; m<8; m++){
neoAleds[m] = CRGB::Cyan;
FastLED.show();
}
}
void sub_close(){
digitalWrite(in1, LOW);
digitalWrite(in2, HIGH);
}
void sub_open(){
digitalWrite(in1, HIGH);
digitalWrite(in2, LOW);
}
void stop_move(){
digitalWrite(in1, LOW);
digitalWrite(in2, LOW);
}
Conclusion
Some of the things which I think I should highlight during the build is about the sensitivity of the gesture sensor. I initially programmed it to play next and previous tracks by swiping my hand left and right, but probably because my hand moved not exactly staright it was triggering the clockwise and anticlockwise motion sometimes. There I have used an || (OR) statement to check both cases and carry out actions where different gestures trigger similar fucntions. The downside to this might be that some simple gestures are not available for some commands but I figured it was more feasible this way. Besides the most complex gesture of all is switching from playing all tracks randomly to sequentially which was to wave slowly forwards and backwards. Other than that the gestures are pretty sensitive and works well.
The Neo-pixels add so much color which does actually looks as if the submarine is cruising on a "sky of blue and sea of green".