Skip to content

Instantly share code, notes, and snippets.

@toozie21
Last active June 12, 2023 17:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save toozie21/30f882f09a4c670291a093ef1fc5899a to your computer and use it in GitHub Desktop.
Save toozie21/30f882f09a4c670291a093ef1fc5899a to your computer and use it in GitHub Desktop.
Skippy and the Skulls Arduino Nano project file
#include <EEPROM.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Adafruit_PWMServoDriver.h>
#include <SoftwareSerial.h>
#include <DFMiniMp3.h>
#include "TalkingSkull.h"
#include <avr/pgmspace.h>
//https://github.com/arduino/Arduino/issues/8324#issuecomment-449172747
//good website on configuring the timer stuff:
//https://www.arduinoslovakia.eu/application/timer-calculator
void setup()
{
//input from the MP3 player for it is is still playing a song
pinMode(BUSY, INPUT_PULLUP);
//Button to press for a random song
pinMode(PLAY_BTN,INPUT_PULLUP);
Serial.begin(9600);
Serial.println(compile_date);
Serial.println(F("Presenting: Skippy and the Skulls!!!\n"));
//enable the PWM for the servos, then immediatly put to sleep to save on the motors
pwm.begin();
pwm.setPWMFreq(FREQUENCY);
default_servos();
pwm.sleep();
//init the SD card
if (!SD.begin(10))
{
Serial.println(F("Initing SD card failed! EXITing"));
delay(100);
resetApp(); //call reset
}
//init the MP3 player
mp3.begin();
delay(300);
mp3.setVolume(EEPROM.read(VOLUME_ADDR));
//timer used for servo movement updates
setupTimer1();
//default the auto-play functionality to false (false==debug mode)
auto_play = false;
}
void loop()
{
//The location for read/write in the file
unsigned long file_loc = 0;
//char input
char input;
//which music file we selected
char music_file = 0;
//which recored file
char rec_file = 0;
//whether we stil have any servo data to play
bool have_servo_data = true;
//whether there is still any MP3 playing
bool have_mp3_data = true;
//The main menu. This is where all the decisions are made
menu(music_file, rec_file, input);
//at this point, we are ready to do something, so turn on the servos
pwm.wakeup();
//Move the servos to their different "default" positions
default_servos();
//turn on interrupts, used for timings
sei(); // allow interrupts
//If we are reading (playing)
if(input == 'r')
{
//Adjust the file name to something the MP3 library understands
//and play it
if(isDigit(music_file))
{
mp3.playMp3FolderTrack(int(music_file)-48); // sd:/mp3/0001.mp3
}
else
{
mp3.playMp3FolderTrack(int(music_file)-87); // sd:/mp3/0001.mp3
}
//give the track time to queue up
delay(500);
//while the servos have data to play, or the MP3 player is playing
while(have_servo_data || have_mp3_data)
{
if(have_servo_data)
{
TCNT1 = 0;
TIMER1_OF = false;
//This is an ugly series of if-statements. I check to see
//what option you selected to play. (from 1-8). 8 means
//that I am playing ALL the servos
//Skippy's head (by himself, with everything)
if((rec_file == '1') || (rec_file == '8'))
{
myFile.seek(file_loc + HEAD_OFFSET);
replay_servo(roll_servo);
replay_servo(pitch_servo);
replay_servo(yaw_servo);
}
//Skippy's jaw (by himself, with everything)
if((rec_file == '2') || (rec_file == '8'))
{
myFile.seek(file_loc + JAW1_OFFSET);
replay_servo(jaw_1_servo);
}
//Joe's jaw (by himself, with Stush, with Stush/Rosie, with everything)
if ((rec_file == '3') || (rec_file == '6') || (rec_file == '7') || (rec_file == '8'))
{
myFile.seek(file_loc + JAW2_OFFSET);
replay_servo(jaw_2_servo);
}
//Stush's jaw (by himself, with Joe, with Joe/Rosie, with everything)
if ((rec_file == '4') || (rec_file == '6') || (rec_file == '7') || (rec_file == '8'))
{
myFile.seek(file_loc + JAW3_OFFSET);
replay_servo(jaw_3_servo);
}
//Rosie's jaw (by herself, with Joe/Stush, with everything)
if ((rec_file == '5') || (rec_file == '7') || (rec_file == '8'))
{
myFile.seek(file_loc + JAW4_OFFSET);
replay_servo(jaw_4_servo);
}
file_loc += 14;
}
//when there is no more servo file data to play
if (!myFile.available() && have_servo_data)
{
have_servo_data = false;
}
if (digitalRead(BUSY) && have_mp3_data)
{
have_mp3_data = false;
}
while(!TIMER1_OF){__asm__("nop");}
mp3.loop(); //this is necessary for thet MP3 library to work
}
}
//If we are writing new data
else if(input == 'w')
{
//If we are going to 'init' a file, we don't need any servos
//initing, sets up the file with the right amount of movements so
//that we can play back files that aren't "complete."
if (rec_file == '8')
{
pwm.sleep();
}
//Adjust the file name to something the MP3 library understands
//and play it
if(isDigit(music_file))
{
mp3.playMp3FolderTrack(int(music_file)-48); // sd:/mp3/0001.mp3
}
else
{
mp3.playMp3FolderTrack(int(music_file)-87); // sd:/mp3/0001.mp3
}
//give the track time to queue up
delay(500);
//while the MP3 is still playing
while(have_mp3_data)
{
TCNT1 = 0;
TIMER1_OF = false;
//A clunky set of if-statements to record a particular movement. I "seek"
//into the file to record the particular servo data at the right locations.
//This is important for being able to play back servo movements that don't
//have all the data written yet.
if(rec_file == '1') //Skippy's head
{
myFile.seek(file_loc + HEAD_OFFSET);
collect_data(roll_ADC, roll_servo, MIN_PULSE_WIDTH_ROLL, MAX_PULSE_WIDTH_ROLL);
collect_data(pitch_ADC, pitch_servo, MIN_PULSE_WIDTH_PITCH, MAX_PULSE_WIDTH_PITCH);
collect_data(yaw_ADC, yaw_servo, MIN_PULSE_WIDTH_YAW, MAX_PULSE_WIDTH_YAW);
}
else if(rec_file == '2') //Skippy's Jaw
{
myFile.seek(file_loc + JAW1_OFFSET);
//jaw_1
collect_data(jaw_ADC, jaw_1_servo, MIN_PULSE_WIDTH_JAW_1, MAX_PULSE_WIDTH_JAW_1);
}
else if(rec_file == '3') //Joe's Jaw
{
myFile.seek(file_loc + JAW2_OFFSET);
//jaw2
collect_data(jaw_ADC, jaw_2_servo, MIN_PULSE_WIDTH_JAW_2, MAX_PULSE_WIDTH_JAW_2);
}
else if(rec_file == '4') //Stush's Jaw
{
myFile.seek(file_loc + JAW3_OFFSET);
//jaw3
collect_data(jaw_ADC, jaw_3_servo, MIN_PULSE_WIDTH_JAW_3, MAX_PULSE_WIDTH_JAW_3);
}
else if(rec_file == '5') //Rosie's jaw
{
myFile.seek(file_loc + JAW4_OFFSET);
//jaw4
collect_data(jaw_ADC, jaw_4_servo, MIN_PULSE_WIDTH_JAW_4, MAX_PULSE_WIDTH_JAW_4);
}
else if(rec_file == '6') //Joe and Stush jaw
{
myFile.seek(file_loc + JAW2_OFFSET);
//jaw2
collect_data(jaw_ADC, jaw_2_servo, MIN_PULSE_WIDTH_JAW_2, MAX_PULSE_WIDTH_JAW_2);
//jaw3
collect_data(jaw_ADC, jaw_3_servo, MIN_PULSE_WIDTH_JAW_3, MAX_PULSE_WIDTH_JAW_3);
}
else if(rec_file == '7') //Joe, Stush, and Rosie's jaws
{
//all 3 backup jaws
myFile.seek(file_loc + JAW2_OFFSET);
//jaw2
collect_data(jaw_ADC, jaw_2_servo, MIN_PULSE_WIDTH_JAW_2, MAX_PULSE_WIDTH_JAW_2);
//jaw3
collect_data(jaw_ADC, jaw_3_servo, MIN_PULSE_WIDTH_JAW_3, MAX_PULSE_WIDTH_JAW_3);
//jaw4
collect_data(jaw_ADC, jaw_4_servo, MIN_PULSE_WIDTH_JAW_4, MAX_PULSE_WIDTH_JAW_4);
}
else if(rec_file == '8') //This "inits" a file. This needs to be done before rec. data
{
//init the file for future writing of servo data
write_default_servos();
}
//advance the file "seek" address
file_loc += 14;
//check to see if the MP3 is done playing
if (digitalRead(BUSY) && have_mp3_data)
{
have_mp3_data = false;
}
while(!TIMER1_OF){__asm__("nop");}
mp3.loop(); //Needed for the MP3 library
}
//add some buffer to the end of the file just in case
if(rec_file == '8')
{
for(int i = 0; i < 10; i++)
{
write_default_servos();
}
}
}
//Move all the servos back to their default locations
default_servos();
//Put the servo's back to sleep
pwm.sleep();
//close the file
myFile.close();
Serial.println(F("Closed file and reseting.\n\n\n"));
delay(100);
//restart the app completely over
resetApp(); //call reset
}
//Function to replay the actual servo data
void replay_servo(int PWM_pin)
{
//read the data from the file and move the servo
//appropriately
uint16_t pulselength = 0;
pulselength = myFile.read();
pulselength = (pulselength << 8) + myFile.read();
pwm.setPWM(PWM_pin, 0, pulselength);
}
void write_default_servos()
{
myFile.write(DEF_PULSE_WIDTH_ROLL >> 8);
myFile.write(DEF_PULSE_WIDTH_ROLL);
myFile.write(DEF_PULSE_WIDTH_PITCH >> 8);
myFile.write(DEF_PULSE_WIDTH_PITCH);
myFile.write(DEF_PULSE_WIDTH_YAW >> 8);
myFile.write(DEF_PULSE_WIDTH_YAW);
myFile.write(MIN_PULSE_WIDTH_JAW_1 >> 8);
myFile.write(MIN_PULSE_WIDTH_JAW_1);
myFile.write(MIN_PULSE_WIDTH_JAW_2 >> 8);
myFile.write(MIN_PULSE_WIDTH_JAW_2);
myFile.write(MIN_PULSE_WIDTH_JAW_3 >> 8);
myFile.write(MIN_PULSE_WIDTH_JAW_3);
myFile.write(MIN_PULSE_WIDTH_JAW_4 >> 8);
myFile.write(MIN_PULSE_WIDTH_JAW_4);
}
//This is setting the different servo's physically to their default locations
void default_servos()
{
pwm.setPWM(yaw_servo, 0, DEF_PULSE_WIDTH_YAW);
delay(10);
pwm.setPWM(pitch_servo, 0, DEF_PULSE_WIDTH_PITCH);
delay(10);
pwm.setPWM(roll_servo, 0, DEF_PULSE_WIDTH_ROLL);
delay(50);
pwm.setPWM(jaw_1_servo, 0, MIN_PULSE_WIDTH_JAW_1);
delay(50);
pwm.setPWM(jaw_2_servo, 0, MIN_PULSE_WIDTH_JAW_2);
delay(50);
pwm.setPWM(jaw_3_servo, 0, MIN_PULSE_WIDTH_JAW_3);
delay(50);
pwm.setPWM(jaw_4_servo, 0, MIN_PULSE_WIDTH_JAW_4);
delay(100);
}
//This is how to read the two ADCs and record the data to file
void collect_data(int ADC_input, int PWM_pin, uint16_t MIN_PULSE_WIDTH, uint16_t MAX_PULSE_WIDTH)
{
uint16_t val = 0;
uint16_t pulselength = 0;
val = analogRead(ADC_input); // read the input pin
if (ADC_input == yaw_ADC)
{
pulselength = map(val, 1023, 0, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
}
else
{
pulselength = map(val, 0, 1023, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
}
//move the particular servo and write the data to file
pwm.setPWM(PWM_pin, 0, pulselength);
myFile.write(pulselength >> 8);
myFile.write(pulselength);
}
//This displays the main menu
void display_menu(int table_size, const char* const* table)
{
char buffer[50];
for (int i = 0; i < table_size; i++)
{
// Necessary casts and dereferencing, just copy.
strcpy_P(buffer, (char *)pgm_read_word(&(table[i])));
Serial.println(buffer);
}
}
//The menu itself
void menu(char &music_file, char &rec_file, char &input)
{
char serial_rx;
//figure out what we are doing (recording or playback)
Serial.println(F("Are you reading (r) or writing (w)?"));
while (1)
{
Serial.flush();
//Wait to read from serial port or a push button press
while((Serial.available() == 0) && (!auto_play))
{
//if someone pressed the button
if(!digitalRead(PLAY_BTN))
{
delay(10);
if(!digitalRead(PLAY_BTN)){auto_play = true;}
delay(100);
//digital debounce
while(!digitalRead(PLAY_BTN))
{
NOP;
delay(10);
}
}
}
input = char(Serial.read());
//if we received proper data
if((input == 'w') || (input == 'r') || auto_play)
{
if(auto_play) input = 'r';
Serial.println(input);
break;
}
else
{
//if we have bad serial data, just restart the app
resetApp();
}
while(Serial.available() != 0){Serial.read();}
}
//if the button was pressed
if(auto_play)
{
//select the next song in the list
uint16_t choice = (EEPROM.read(SONG_ADDR) + 1) % song_total;
delay(15);
music_file = song_list[choice];
EEPROM.write(SONG_ADDR, choice);
delay(15);
Serial.println(music_file);
//setup to play all the motions
rec_file = '8';
}
else
{
//otherwise display the menu to choose a song/motion
display_menu(int(sizeof(menu_table)/2), menu_table);
}
delay(100);
//if a button was not pressed, go through the process of figuring
//out what song the user wants to play
if(!auto_play)
{
while(1)
{
while(Serial.available() != 0){Serial.read();}
Serial.flush();
while(Serial.available() == 0){NOP;}
serial_rx = char(Serial.read());
Serial.println(serial_rx);
music_file = serial_rx;
break;
}
if(music_file == 'g')
{
if(input == 'r')
{
Serial.print(F("The current vol is "));
Serial.println(EEPROM.read(VOLUME_ADDR));
Serial.println("");
//reset the app
delay(100);
resetApp();
}
else
{
uint16_t new_val = 0;
Serial.flush();
if (Serial.available() > 0){Serial.read();}
Serial.print(F("What vol do you want?"));
String inString = "";
while (Serial.available() == 0) {;}
while (Serial.available() > 0)
{
int inChar = Serial.read();
if (isDigit(inChar))
{
// convert the incoming byte to a char and add it to the string:
inString += (char)inChar;
delay(200);
}
}
new_val = inString.toInt();
EEPROM.write(VOLUME_ADDR, new_val);
Serial.print(F("Vol set to "));
Serial.println(EEPROM.read(VOLUME_ADDR));
Serial.println("");
//reset the app
delay(100);
resetApp();
}
}
//display the menu for picking the movement
display_menu(int(sizeof(menu_table2)/2), menu_table2);
delay(100);
while(1)
{
while(Serial.available() != 0){Serial.read();}
Serial.flush();
while(Serial.available() == 0){NOP;}
serial_rx = char(Serial.read());
if(isDigit(serial_rx))
{
Serial.println(serial_rx);
rec_file = serial_rx;
break;
}
}
}
//ugly way of preloading a variable name to get it
//in the format the MP3 library wants to see.
char filename[6] = {"0.txt"};
//put the number in the first slow
filename[0] = music_file;
Serial.print(F("filename is "));
Serial.println(filename);
Serial.println(F("Checking on file"));
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
if(input == 'r')
{
myFile = SD.open(filename);
}
else
{
//If the user is writing to a file, and they picked "all"
//the movements, we plan on overwriting the file.
if (rec_file == '8')
{
//check to see if the file already exists
if(SD.exists(filename)) //if exists, make sure the user wants to overwrite it
{
char input2;
Serial.println(F("Your file already exists, erase it? (y/n)"));
while (1)
{
Serial.flush();
while(Serial.available() == 0){NOP;}
input2 = char(Serial.read());
if((input2 == 'y') || (input2 == 'Y'))
{
//remove the file to write a new one
SD.remove(filename);
break;
}
if((input2 == 'n') || (input2 == 'n'))
{
//reset the app
delay(100);
resetApp();
}
while(Serial.available() != 0){Serial.read();}
}
}
//open file to write (does not append
myFile = SD.open(filename, O_WRITE | O_CREAT);
}
else //open file to read from
{
myFile = SD.open(filename, O_RDWR);
}
}
//if we cannot open the file AND it was b/c the user
//pressed the button (gets a random file), loop back
//through again since the user is not using the menu
//system. This will get stuck permanently if there
//are NO files for it to find
if ((!myFile) && (auto_play))
{
Serial.println(F("Random file doesn't exist, retrying"));
menu(music_file, rec_file, input);
}
else if(!myFile) //if the file can't be opened for some reason
{
// if the file didn't open, print an error and bail
Serial.println(F("Error opening file. EXITing"));
delay(100);
resetApp();
}
}
//timer for setting up interval tp update/play servo movements
void setupTimer1()
{
cli(); // stop interrupts
// Clear registers
TCCR1A = 0; //set entire TCCR1A register to 0
TCCR1B = 0; //same for TCCR1B
TCNT1 = 0; //initialize counter value to 0
// 10 Hz (16000000/((6249+1)*256))
OCR1A = 3124;//6249;
// CTC
TCCR1B |= (1 << WGM12);
// Prescaler 256
TCCR1B |= (1 << CS12);
// Output Compare Match A Interrupt Enable
TIMSK1 |= (1 << OCIE1A);
TIMER1_OF = false;
sei(); // allow interrupts
}
ISR(TIMER1_COMPA_vect)
{
TIMER1_OF = true;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment