All that’s left now is to combine everything from the previous steps into one working whole. Motor control, music playing, button & light level sensing, and the startup mode choice combine into the following program for the Phatt Bass. Video below!
// Button and sensor pins
#define BUTTON_PIN 4
#define LDR_PIN 33
// Motor control pins
#define HEADTAIL_MOTOR_PIN_1 12
#define HEADTAIL_MOTOR_PIN_2 14
#define HEADTAIL_MOTOR_PWM_PIN 13
#define MOUTH_MOTOR_PIN_1 27
#define MOUTH_MOTOR_PIN_2 26
#define MOUTH_MOTOR_PWM_PIN 25
// Motor PWM settings
#define PWM_FREQUENCY 1000
#define PWM_RESOLUTION 8
#define HEADTAIL_MOTOR_PWM_CHANNEL 0
#define MOUTH_MOTOR_PWM_CHANNEL 1
#define HEADTAIL_MOTOR_PWM_DUTY_CYCLE 255 // Proxy for motor speed, up to 2^resolution
#define MOUTH_MOTOR_PWM_DUTY_CYCLE 255 // Proxy for motor speed, up to 2^resolution
// Music settings
#define TRACK_NUMBER 1
#define VOLUME 30 // Up to 30
#define MP3_PLAYER_BAUD_RATE 9600
bool sensorMode = false;
double lastSensorLightLevel = 0;
// Setup and run the program
void setup() {
// Set up button and LDR pins
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(LDR_PIN, INPUT_PULLUP);
// Set up motor control pins
pinMode(HEADTAIL_MOTOR_PIN_1, OUTPUT);
pinMode(HEADTAIL_MOTOR_PIN_2, OUTPUT);
pinMode(HEADTAIL_MOTOR_PWM_PIN, OUTPUT);
pinMode(MOUTH_MOTOR_PIN_1, OUTPUT);
pinMode(MOUTH_MOTOR_PIN_2, OUTPUT);
pinMode(MOUTH_MOTOR_PWM_PIN, OUTPUT);
// Set up PWM
ledcSetup(HEADTAIL_MOTOR_PWM_CHANNEL, PWM_FREQUENCY, PWM_RESOLUTION);
ledcSetup(MOUTH_MOTOR_PWM_CHANNEL, PWM_FREQUENCY, PWM_RESOLUTION);
ledcAttachPin(HEADTAIL_MOTOR_PWM_PIN, HEADTAIL_MOTOR_PWM_CHANNEL);
ledcAttachPin(MOUTH_MOTOR_PWM_PIN, MOUTH_MOTOR_PWM_CHANNEL);
ledcWrite(HEADTAIL_MOTOR_PWM_CHANNEL, HEADTAIL_MOTOR_PWM_DUTY_CYCLE);
ledcWrite(MOUTH_MOTOR_PWM_CHANNEL, MOUTH_MOTOR_PWM_DUTY_CYCLE);
// Set up serial comms to MP3 player
Serial2.begin(MP3_PLAYER_BAUD_RATE);
while (!Serial2);
// Check startup mode. If the button is held down at startup time, we go into
// "sensor mode" to use the LDR to trigger the fish, otherwise we go into
// normal mode where a button press triggers it.
if (isButtonPushed()) {
// Button held in on startup, so going into sensor mode.
// Wait for button to be unpushed, then wait 5 seconds for the user to move away
sensorMode = true;
while (isButtonPushed());
delay(5000);
// Record the current light level, so we don't trigger immediately
lastSensorLightLevel = getLightLevel();
} else {
// Normal mode, nothing else to do here
}
}
// Main program loop
void loop() {
// Wait for a trigger condition, either a change in light level or
// a button push depending on our mode.
if (sensorMode) {
double lightLevel = getLightLevel();
if (lightLevel < lastSensorLightLevel - 0.04 || lightLevel > lastSensorLightLevel + 0.04) {
trigger();
}
lastSensorLightLevel = lightLevel;
delay(200);
} else if (isButtonPushed()) {
trigger();
}
}
// Return true if button is pushed
boolean isButtonPushed() {
return digitalRead(BUTTON_PIN) == 0;
}
// Return a normalised light level 0-1
double getLightLevel() {
int measuredLevel = analogRead(LDR_PIN);
return constrain(1 - measuredLevel / 2500.0, 0.0, 1.0);
}
// Trigger the song and the lip-sync actions
void trigger() {
// Start playing MP3
changeVolume(VOLUME);
playTrack(TRACK_NUMBER);
// Lip-sync!
lipsync();
// Stop once complete
stop();
}
// Main lip-sync function, operating the motors in time to music. The music is already playing
// at this point so we just have to move motors accordingly.
void lipsync() {
delay(3000); // *sirens*
headOut();
delay(1000);
mouthOpenFor(1000); // Listen
delay(1000);
mouthOpenFor(500); // to the
delay(300);
mouthOpenFor(300); // phatt
delay(200);
flapMouthFor(1750, 250); // bass... bass... bass... bass...
tailOut();
mouthOpenFor(300); // bass...
headTailRest();
mouthOpenFor(300); // bass...
tailOut();
mouthOpenFor(300); // bass...
headTailRest();
delay(300);
flapTailFor(5400, 200); // *early 2000s techno noises*
headOut();
delay(200);
mouthOpenFor(600); // phatt
delay(600);
mouthOpenFor(600); // bass
for (int i = 0; i < 10; i++) { // rest of music
flapTailFor(400, 200);
flapHeadFor(400, 200);
}
}
// Play a specific track number
void playTrack(int tracknum) {
sendCommandToMP3Player(0x03, tracknum);
}
// Flap the head in and out for a defined time (in millis), moving it at the defined interval (in millis).
// runtime should be a multiple of interval * 2, otherwise the number of mouth movements will be rounded down.
// Used to bop to music
void flapHeadFor(int runtime, int interval) {
int runs = runtime / interval;
for (int i = 0; i < runs; i++) {
headOut();
delay(interval);
headTailRest();
delay(interval);
}
}
// Flap the tail in and out for a defined time (in millis), moving it at the defined interval (in millis).
// runtime should be a multiple of interval * 2, otherwise the number of mouth movements will be rounded down.
// Used to bop to music
void flapTailFor(int runtime, int interval) {
int runs = runtime / interval;
for (int i = 0; i < runs; i++) {
tailOut();
delay(interval);
headTailRest();
delay(interval);
}
}
// Flap the head out for a defined time (in millis), then back in for the same time. Used to bop to music.
void flapHead(int interval) {
headOut();
delay(interval);
headTailRest();
delay(interval);
}
// Flap the tail out for a defined time (in millis), then back in for the same time. Used to bop to music.
void flapTail(int interval) {
tailOut();
delay(interval);
headTailRest();
delay(interval);
}
// Bring the fish's head out
void headOut() {
digitalWrite(HEADTAIL_MOTOR_PIN_1, LOW);
digitalWrite(HEADTAIL_MOTOR_PIN_2, HIGH);
}
// Bring the fish's tail out
void tailOut() {
digitalWrite(HEADTAIL_MOTOR_PIN_1, HIGH);
digitalWrite(HEADTAIL_MOTOR_PIN_2, LOW);
}
// Put the fish head and tail back to the neutral position
void headTailRest() {
digitalWrite(HEADTAIL_MOTOR_PIN_1, LOW);
digitalWrite(HEADTAIL_MOTOR_PIN_2, LOW);
}
// Flap the fish's mouth for a defined time (in millis), opening and closing it at the defined interval (in millis).
// runtime should be a multiple of interval * 2, otherwise the number of mouth movements will be rounded down.
// Used to simulate singing or rapid speech.
void flapMouthFor(int runtime, int interval) {
int runs = runtime / interval;
for (int i = 0; i < runs; i++) {
mouthOpen();
delay(interval);
mouthClose();
delay(interval);
}
}
// Open the fish's mouth for a defined time (in millis), then close it. Used to simulate speaking a word.
void mouthOpenFor(int runtime) {
mouthOpen();
delay(runtime);
mouthClose();
}
// Open the fish's mouth
void mouthOpen() {
digitalWrite(MOUTH_MOTOR_PIN_1, LOW);
digitalWrite(MOUTH_MOTOR_PIN_2, HIGH);
}
// Close the fish's mouth
void mouthClose() {
digitalWrite(MOUTH_MOTOR_PIN_1, HIGH);
digitalWrite(MOUTH_MOTOR_PIN_2, LOW);
}
// Rest the fish's mouth
void mouthRest() {
digitalWrite(MOUTH_MOTOR_PIN_1, LOW);
digitalWrite(MOUTH_MOTOR_PIN_2, LOW);
}
// Stop the motors & music
void stop() {
headTailRest();
mouthRest();
sendCommandToMP3Player(0x16, 0);
}
// Set volume to specific value
void changeVolume(int thevolume) {
sendCommandToMP3Player(0x06, thevolume);
}
// Send a command to the MP3-TF-16P. Some commands support one or two bytes of data
void sendCommandToMP3Player(byte command, int dataBytes) {
byte commandData[10];
byte q;
int checkSum;
commandData[0] = 0x7E; //Start of new command
commandData[1] = 0xFF; //Version information
commandData[2] = 0x06; //Data length (not including parity) or the start and version
commandData[3] = command; //The command
commandData[4] = 0x01; //1 = feedback
commandData[5] = highByte(dataBytes); //High byte of the data
commandData[6] = lowByte(dataBytes); //low byte of the data
checkSum = -(commandData[1] + commandData[2] + commandData[3] + commandData[4] + commandData[5] + commandData[6]);
commandData[7] = highByte(checkSum); //High byte of the checkSum
commandData[8] = lowByte(checkSum); //low byte of the checkSum
commandData[9] = 0xEF; //End bit
for (q = 0; q < 10; q++) {
Serial2.write(commandData[q]);
}
delay(100);
}
*DJ voice* Y’all know what time it is… PUT YOUR HANDS IN THE AIR!!!
Add a Comment