Last active
June 12, 2023 17:22
-
-
Save toozie21/30f882f09a4c670291a093ef1fc5899a to your computer and use it in GitHub Desktop.
Skippy and the Skulls Arduino Nano project file
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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