# Help with Arduino Code to Run 3-Axis Talking Skull



## Brianaala (Nov 4, 2018)

Hi folks,
I'm trying to write Arduino code for essentially a 3-Axis skull. I followed the "Jawduino" build from ButtonBanger.com but I tweaked it to run from a PIR sensor. Now I'm trying to add extra servo sequences (just random motions), but I'm having an issue running the servos at the same time as the talking sequence. I don't know how to code several servos to run at once. I'm also using the variable speed servo library which allows me to slow down individual servo speeds. 
I did find code to run several things at once by pulling the functions out of the loop and just checking Millis().
I was thinking I might be able to code the talking portion as a call to a function and pull that out of the loop too (the code is really only about 2 lines). If I can figure out a way to do that I know that I can start the sequence with an input from the PIR --> play a random audio file from the MP3 --> trigger read/write from the audio signal to Jaw Servo and randomize other servos simultaneously. At the very least I could have one Arduino run the PIR, Jaw Servo and MP3 (that's what I have now) and run another Arduino to randomize the other servos (according to the code that I found).Any help would be great!


----------



## HalloweenRick (Nov 25, 2005)

I see that you asked this on the other halloween forum, but the Arduino CAN run other outputs- its just that you may need a shield to run the servos like this one from Adafruit:
https://www.adafruit.com/product/1411









Unfortunately, buttonbanger.com is down so we can't see the Jawduino code you are working off of. Could you post it here at and at the other halloween forum please? Try to use the code tag function if you can when you post it.


----------



## Brianaala (Nov 4, 2018)

*Trouble with Arduino code*

Thanks HalloweenRick. Yes I have posted it around looking for any help. The code below is what I have so far, as I mentioned this works to get the skull talking but I am not able to get the random head turn motion to work while the talking loop is running. I think the answer is to get the code out of the loop and run it as a function call at given millis() intervals to give the illusion of several things at once.


```
//      Hard settings
int trackDelay = 1000; //1000 is one second, 5000 is five seconds, etc. this is the minimum delay between retrigger of the prop.
int trackCount = 002; //this should be the total number of tracks in folder 01 and must include any preceding zeros to three places i.e. 2 tracks is 002, 5 tracks is 005, 12 tracks is 012
#include <VarSpeedServo.h> //this library should allow servos to operate at variable speeds
#include "SoftwareSerial.h" //I think this allows the serial monitor
#include "DFRobotDFPlayerMini.h" //library to operate the mp3 player
VarSpeedServo jaw;//Jaw servo
VarSpeedServo turn;//Head turn servo
SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
int turnpos; //The position of the head turn servo
unsigned long startMillis;  //some global variables available anywhere in the program
unsigned long currentMillis;
const unsigned long period = 1000;  //the value is a number of milliseconds
int ledPin = 5;

//                  PIR Sensor Setup

int calibrationTime = 1;       // The time we give the sensor to calibrate (10-60 secs according to the datasheet) total time in seconds is 2*calibrationTime
int pirPin = 2;                 // The PIR sensor's output
long unsigned int lowIn;        // The time when the sensor outputs a low impulse

long unsigned int pause = 5000;  // 5 seconds the sensor has to be low 
                                 // before we assume all motion has stopped
boolean lockLow = true; //Not sure what this does
boolean takeLowTime; //Not sure what this does


//                  MP3 Setup

int audioVal;//read the volume of audio input from VU
int mode = 1;
int busy;//mp3 busy pin
int track = 001;//starts at track #1

//                  PIR/MP3 Function Setup

void setup()
{
  
 //           PIR Sensor Calibration
 
 void calibrateTime(); 
 pinMode(pirPin, INPUT);        // Digital Pin 3
 digitalWrite(pirPin, LOW);      // Turn off internal pull up resistor
  // Give the sensor some time to calibrate 
  calibrateTime();               // 60 seconds calibration time 
            

  //          Jaw Servo/MP3 Play

  jaw.attach(6);  //Attaches pin 6 to jaw servo
  turn.attach(5); //Attach pin 5 to turn Servo
  mySoftwareSerial.begin(9600);// start serial port at 9600 bps:
  
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));
  
  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while(true);
  }
  Serial.println(F("DFPlayer Mini online."));
  
  myDFPlayer.setTimeOut(500); //Set serial communication time out 500ms
  
  //----Set volume----
  myDFPlayer.volume(27);  //Set volume value (0~30).
    
  //----Set different EQ----
 myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);
   
  //----Set device we use SD as default----

  myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);

 
  Serial.println(myDFPlayer.readFileCounts()); //read all file counts in SD card
  Serial.print("Current File: ");
  Serial.println(myDFPlayer.readCurrentFileNumber()); //read current play file number
  Serial.print("File count in folder:(folder 1 specified) ");
  Serial.println(myDFPlayer.readFileCountsInFolder(01)); //read file counts in folder SD:/01
  myDFPlayer.play();  //Play the first mp3
   startMillis = millis();  //initial start time
}

void loop()
{ 
 

 //         MP3 Servo Motion 

 audioVal = 300;
  if(analogRead(1) < 341) audioVal += 333;//if A1 recieves signal open jaw 30
  if(analogRead(2) < 341) audioVal += 333;//if A2 recieves signal open jaw 60
  if(analogRead(3) < 341) audioVal += 333;//if A3 recieves signal open jaw 90

 if(audioVal > 0) 
    {
    jaw.writeMicroseconds(audioVal);//move Jaw with sound
        }
  //{turnpos = random(120);//generate random value for turn servo
  //     turn.write(turnpos,40); //move turn servo at particular speed 
  //  currentMillis = millis();  //get the current "time" (actually the number of milliseconds since the program started)
  //if (currentMillis - startMillis >= period)  //test whether the period has elapsed
  //{
    digitalWrite(turnpos, !digitalRead(turnpos));  //if so, change the state of the LED.  Uses a neat trick to change the state
    startMillis = currentMillis;  //IMPORTANT to save the start time of the current LED state.
  //}  
   //   }
  mp3();
}

void mp3()
{
  
//          PIR SENSOR START  
 
  
     
busy=analogRead(4);
  
           if (analogRead(4) <= 300) //busy pin from mp3 player ignore sensor
      {
        digitalWrite(ledPin, HIGH);
        Serial.print ("Busy: ");
        Serial.println (busy);
      }
     else if (analogRead(4) >= 301)//if not busy then:
        {
          digitalWrite(ledPin, LOW);
           mode= mode+1; 
           Serial.print("Mode: ");
           Serial.println(mode);
                   if (mode==1) //mp3 mode is 1 then:
           {track=random(001,trackCount); //play random track
           myDFPlayer.playFolder(01, track);}
           if (mode==2) 
           {delay(trackDelay);}//probably have to change this to millis
          // if (mode==3)
        //   {mode=0;}
        }
 /* if (myDFPlayer.available()) 
      {
       printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states.
      }*/
      if((digitalRead(pirPin) == HIGH) and (analogRead(4) > 300))    // The PIR saw something & MP3 player isn't playing
     {   
       if(lockLow)                      // Starts True
       {  
         lockLow = false;               // Makes sure we wait for a transition to LOW before any further output is made:         
         //delay(50);
       }         
       takeLowTime = true;              // No default
      
     // stopMp3();                       // Stop the MP3 player                  
//      playTrack(1);                    // Play one Random Track
       myDFPlayer.next();  //Play the next mp3
       //for(int i = 0; i < 4; i++)      // Wait 4 seconds For MP3 to start.
       {
         delay(1000);
       }

      //   while(analogRead(4) <= 300)                 // Wait for MP3 player to finish current track
      //   {
      //    if((digitalRead(pirPin) == HIGH))  // Visualize sensor busyPin or pirPin
         //  {
         //    digitalWrite(ledPin, HIGH);
             

      //    }
       //    else
           //{
          //   digitalWrite(ledPin, LOW);
             
         //  }
       //    delay(50);                
      //   }

      stopMp3();                       // Stop the MP3 player  
     }

     if(digitalRead(pirPin) == LOW)
     {       
  //     digitalWrite(ledPin, LOW);  //the led visualizes the sensors output pin state
       
       if(takeLowTime)             // If PIR was just High
       {
         lowIn = millis();          // Save the time of the transition from High to LOW
         takeLowTime = false;       // Make sure this is only done at the start of a LOW phase
       }
       
       //if the sensor is low for more than the given pause, 
       //we assume that no more motion is going to happen
       if(!lockLow && millis() - lowIn > pause)
       {  
           //makes sure this block of code is only executed again after 
           //a new motion sequence has been detected
           lockLow = true;                        
          // delay(50);
           
       }
     }
    
}

    //          Calibrate the PIR


  void calibrateTime()
   {
    //  for(int i = 0; i < calibrationTime; i++)
    
      //{
      //  digitalWrite(ledPin, HIGH);
      //  delay(500);                  // wait for a second
     //   digitalWrite(ledPin, LOW);
     //   delay(500);                  // wait for a second
     // }
      
    //  digitalWrite(ledPin, HIGH);
   }
void printDetail(uint8_t type, int value){
  switch (type) {
    
         }
      break;
    default:
      break;
  }
}


void stopMp3()
{
    
}
```


----------



## David_AVD (Nov 9, 2012)

As I said on the other forum, it can be done but the standard Arduino libraries may not be up to the task. Combining all of the required functionality into one piece of code is possible, but is certainly a more advanced style of programming. I don't have anything suitable at hand for you sorry.


----------



## Hippie Crane (Sep 17, 2019)

dont ever use delay with multitasking code, you'll want a main variable to store the millis time at the begging of the main loop, and you want a variable for each servo to store the time when a function was executed for each servo


----------



## corey872 (Jan 10, 2010)

The arduino technically doesn't 'multitask' the way a typical CPU does, but with a 16MHz clock, it can do a lot in 'the blink of an eye'. So to us, it seems like multitasking. ie a normal 'frame rate' of 1/30 second is over 530 thousand CPU cycles for the arduino.

I think you're on the right track - definitely need to get rid of 'delay' because that stops the code cold at that point and simply waits. Millis is the way to go.

Here is some reading that might help:

https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview

One thing - I notice you have the serial port active. When I ran my 'plasmaduino' with a heavy task load - ie keeping timers running at ~1 and 15 KHz, watching for interrupts, 4-5 inputs, a couple modes on the output and some math along the way - I ran into some trouble when the serial port was active. (jittery output, occasional lock-ups, etc)

I never really worked on this a bunch to see if the issue was specifically the parallel port trying to write in addition to all the other tasks, or possibly just EMF from the plasma discharge doing funny things to the attached USB cord (read 'antenna'). But if you run into similar jitter/stutter issues, you might try disabling that if not needed.


----------



## DarkOne (Oct 16, 2012)

With how cheap arduino clones are, I'd recommend using one for the 3 axis motion and another for the jawduino. Have you ever looked at the pololu maestro controllers? They are programmable using their software where you move the servos onscreen and save frames, then play them back. That's what I used the last couple years. This year a 12 channel controller was hooked up to 2 skulls. I had 16 ft of wire running between the two and had no problems.


----------



## Batbuddy (Sep 3, 2014)

I have been working on this very thing for the last several years(in my spare time) and I have it 99% done. I will share the code in another thread. https://www.hauntforum.com/showthread.php?p=920614#post920614


----------



## Brianaala (Nov 4, 2018)

Wow, thanks everyone! I will work on the code and keep you posted. I think that the illusion of many things "at once" would be good enough for what I'm going for. I will take out the delays and try to work with millis() commands. Thank you so much for the link Batbuddy! I will do my best to understand what you are doing with yours !


----------



## corey872 (Jan 10, 2010)

It seems like what you want to do would be trivial for a single arduino. I know multiple arduinos may work, but to me, that is just introducing multiple points of failure, additional power supplies, more wiring, additional points to upgrade programming, etc.

If i had to offer advice [as an admittedly novice programmer] I would say:

1) Save your 'working' code as a copy then go through and clean it up for starters. Admittedly, my 'development' code is likely no better - but what you posted above...WOW... sort of surprised that even compiles! Just some examples:

you have a line 

```
void calibrateTime();
```
 in the setup function and it looks like the function is sort of set up after that. But then another calibrateTime function is also listed. Ideally the function should be listed outside the setup() function and should have brackets after, not ";"


```
void calibrateTime() {

//stuff here;

}
```
It also seems like this same function appears later in the code, but then everything within it is commented out.

Then there are weird variable calls:


```
long unsigned int pause = 5000;
```
If you're assigning a value of '5000' not sure why this needs to be 'unsigned long' (the way its normally written) - though I guess long unsigned also compiles. 'pause' is typically reserved for a function ie pause(), though here seems to be used as a variable name?

I also see where the function stopMP3() is called, but then the actual function is empty:


```
void stopMp3()
{
    
}
```
Optionally, the code from Batbuddy looks pretty clean - might be able to just make some small adjustments there and get what you need.

2) Once you get things cleaned up, start with one simple movement and when that is working, add another - and make sure it still works. Then move on to more. This lets you identify any trouble spots as they crop up without having to look at the entire code for some tiny missing comma or ';'.

Hope this helps!


----------



## Batbuddy (Sep 3, 2014)

corey872 said:


> Optionally, the code from Batbuddy looks pretty clean - might be able to just make some small adjustments there and get what you need.
> ...


 Thanks for the compliment! I tried really hard to make it understandable.
By all means feel free to use what I have and modify to your liking. Just keep us in the loop as you get stuff working.


----------



## Brianaala (Nov 4, 2018)

Batbuddy said:


> I have been working on this very thing for the last several years(in my spare time) and I have it 99% done. I will share the code in another thread. https://www.hauntforum.com/showthread.php?p=920614#post920614


Thank you so much Batbuddy!! You mention that you are 99% done with the code; what still needs tweaking? I am absolutely stunned that you have managed to figure this out! I will try to help you as much as I possibly can to try and complete this project; but I am a novice on the programming side. Thank you so much for sharing!


----------



## Batbuddy (Sep 3, 2014)

I said 99% because I am not sure if I got it so that everything works like it should. Basically I need Beta testers in order to verify it is working. I haven't worked on it actively this year and have soo many projects going at the moment that I don't have time to put the test skull and stuff to verify everything. I would love it if you were willing to put it all together and do a trial run. If the code needs some tweaking I can probably handle that, I just don't have time to reassemble the hardware at the moment.


----------



## Brianaala (Nov 4, 2018)

You have no idea how excited I am! I was actually halfway through building the circuit to match your online plans in order to just test the code and see what it did/ didn't do! I will ABSOLUTELY beta test this and give you as much feedback as I can!


----------



## Batbuddy (Sep 3, 2014)

Sounds great!


----------



## Brianaala (Nov 4, 2018)

Hi Batbuddy,
ok I have some basic questions about the functionality: for testing (servos not attached to anything), can I run the _3Axis code without running the ServoLimits code? Or does it save the UL &LL values in EEPROM?


----------



## Brianaala (Nov 4, 2018)

The reason I'm asking is because I wanted to try this on my circuit that does not have a 10K pot on it (essentially just connect the extra servos and see how it does).


----------



## Batbuddy (Sep 3, 2014)

You could run it without doing the servo limit code, but you would need to modify the variables in the _3axis code, otherwise the code will load some random data from the EEprom and that could wreck your servos, so not a good idea. What you could do is run this modified Servo limit code (found below) and it will load basic limits that will run the servos at maximum travel. This will not be good for servos that are installed on anything but if they are able to move freely then it will be fine and then nothing will break. Use the serial monitor to see what is happening. Have the Momentary button attached to pin D2. If you have no button then a careful and quick touch to ground with a jumper wire connected to pin 2 will work in a pinch. Hope this helps...

```
/* Copyright (c) 2017 Kenny Chapman
 If you require a license, see
 www.batbuddy.org/contactus.php
 Servo limits for 3 axis skull control
 Servos should be connected to pins 3, 4, 5, 6, for Nod, Turn, Tilt, Jaw, respectively.
 A 10K potentiometer should be connected between ground and pin A5.
 A momentary pushbutton should be connected between ground and pin 2
*/
#include <EEPROMex.h>


#include <Servo.h>
const int maxAllowedWrites = 12;
Servo nod;
Servo turn;
Servo tilt;
Servo jaw;
int nodPos = 1000;
int turnPos = 1000;
int tiltPos = 1000;

int jawUL = 2500;
int jawLL = 1000;

int nodLL = 750; //Lower limit of Nod servo
int nodC = 1500; //Center of Nod servos' sweep
int nodUL = 2500; //Upper limit of Nod Servo

int turnLL = 750; //Lower limit of Turn servo
int turnC = 1500; //Center of Turn servos' sweep
int turnUL = 2500; //Upper limit of Turn Servo 

int tiltLL = 750; //Lower limit of Tilt servo
int tiltC = 1500; //Center of Tilt servos' sweep
int tiltUL = 2500; //Upper limit of Tilt Servo

int ejawUL = 0;
int ejawLL = 0;

int enodLL = 750; 
int enodC = 1500; 
int enodUL = 2500; 

int eturnLL = 750; 
int eturnC = 1500; 
int eturnUL = 2500;  

int etiltLL = 750; 
int etiltC = 1500; 
int etiltUL = 2500; 

int oldSwitchState = HIGH;
unsigned long switchPressTime;
const unsigned long debounceTime = 100; //10 Mseconds of debounce time for button
const int potPin = A5;  // analog pin used to connect the potentiometer
int val;
int newVal = 0;
int eState = 0;
const int bPin = 2; //Button pin assigned
int mode=0;
void setup() {
  Serial.begin(9600);
  nod.attach(3);  //Attaches pin 3 to Nod servo
 turn.attach(4);  //Attaches pin 4 to turn servo 
 tilt.attach(5);  //Attaches pin 5 to tilt servo
pinMode(bPin, INPUT_PULLUP);    // initialize the pushbutton pin as an input
enodLL  = EEPROM.getAddress(sizeof(int));
enodC = EEPROM.getAddress(sizeof(int));
enodUL = EEPROM.getAddress(sizeof(int));

eturnLL = EEPROM.getAddress(sizeof(int));
eturnC = EEPROM.getAddress(sizeof(int));
eturnUL = EEPROM.getAddress(sizeof(int));

etiltLL = EEPROM.getAddress(sizeof(int));
etiltC = EEPROM.getAddress(sizeof(int));
etiltUL = EEPROM.getAddress(sizeof(int));

ejawUL = EEPROM.getAddress(sizeof(int));
ejawLL = EEPROM.getAddress(sizeof(int));



EEPROM.setMaxAllowedWrites(maxAllowedWrites);
}

void loop() {
 int switchState = digitalRead (bPin);  
if (millis () - switchPressTime >= debounceTime)
       {
       switchPressTime = millis ();  // when we closed the switch 
       oldSwitchState =  switchState;  // remember for next time 
      
       if (switchState == LOW)
          {mode= mode+1; 
          
          delay(15); 
            if (mode==7)
            {
              mode = 0;} 
            }
       }
  
  if (mode==1)        {
            Serial.println ("Servo Limits");
            Serial.print ("nodLL: ");
            Serial.println (nodLL);
            Serial.print ("nodC: ");
            Serial.println (nodC);
            Serial.print ("nodUL: ");
            Serial.println (nodUL);
            Serial.print ("turnLL: ");
            Serial.println (turnLL);
            Serial.print ("turnC: ");
            Serial.println (turnC);
            Serial.print ("turnUL: ");
            Serial.println (turnUL);
            Serial.print ("tiltLL: ");
            Serial.println (tiltLL);
            Serial.print ("tiltC: ");
            Serial.println (tiltC);
            Serial.print ("tiltUL: ");
            Serial.println (tiltUL);
            Serial.print ("JawUL: ");
            Serial.println (jawUL);
            Serial.print ("JawLL: ");
            Serial.println (jawLL);
            
           mode = 2;
          
           Serial.println ("Write Servo limits to EEPROM?");
           
            delay(150); }
           

  
  // has it changed since last time?
  
    if (mode==3)
          { writeEEprom();
           mode = 4; 
           delay(500); 
          }   
    if (mode==5)
          {
          Serial.println ("Safe to exit Setup.");
          mode = 6;  
          }
}
void writeEEprom(){   
            EEPROM.updateInt(enodLL,nodLL); //write to eeprom function here
            EEPROM.updateInt(enodC,nodC);
            EEPROM.updateInt(enodUL,nodUL);
            
            EEPROM.updateInt(eturnLL,turnLL);
            EEPROM.updateInt(eturnC,turnC);
            EEPROM.updateInt(eturnUL,turnUL);
            
            EEPROM.updateInt(etiltLL,tiltLL);
            EEPROM.updateInt(etiltC,tiltC);
            EEPROM.updateInt(etiltUL,tiltUL);

            EEPROM.updateInt(ejawUL,jawUL);
            EEPROM.updateInt(ejawLL,jawLL);
            
             Serial.println ("Writing EEPROM.");
             delay(1000);
          }
```
 P.S. if you get a warning message "Exceeded maximum number of writes!" No worries, it is just a safety feature to protect from ruining your EEprom memory.


----------



## Brianaala (Nov 4, 2018)

Hi Batbuddy, ok so just adding in the limits to the code, that's kinda what I figured. Thank you very much for taking the time to do that! I have decided to build the entire circuit on a separate breadboard to run both codes and fully test the code and the circuit. Which has led me to one other (very naive) question; I know that pots are just variable resistors, in your diagram you have two leads going to the 10K pot, usually the ground is connected to the far left (when facing the knob) does it matter which lead is connected to the A5 pin? I presume the far right (because it's usually designated as output)? 
Thank you again for all of your hard work and your saintly patience! Both are really appreciated!


----------



## Batbuddy (Sep 3, 2014)

The code I enclosed in the previous Post is a simplified Servo limit setting code and you will still need to run it an then upload the regular skull code after. ( not sure if I made that clear before) The bread board is a great idea! I am happy to answer any questions. Direct answer. you can hook either lead of the pot to the Ground and A5. No polarity here. Long explanation. Potentiometers have three leads, usually. for a given value, in this case 10K, max (10K) resistance is found between the end connections. The middle one sweeps across a carbon trace and the resistance increases as it moves farther from the end contact. Therefore you want to be connected to the middle and one of the end contacts/leads of the Pot. not both ends. Hope this helps. More explanation available here. https://electronicsclub.info/variableresistors.htm


----------



## Brianaala (Nov 4, 2018)

Thanks! The "long explanation" helped me a lot! I tried to run the servo limits code and use the pot but it didn't seem to have any effect on the servos so I need to go through my wiring on the BB to make sure everything is squared away. Also I need to hook up a speaker and run the serial so I can follow it better. I'll keep you posted! Happy Thanksgiving!


----------



## Nanorrock46 (Jan 17, 2017)

I was looking around on Instructables for servo motor controls. I ran into this and thought of this project. It allows you to program servo movement. I did not follow the link for more detail at the bottom but it does create movement files and maybe you can record then take the files and modify for your project.


----------

