Talking Skull

Status
Not open for further replies.

sfugarino

Member
Trying to make a talking skull and though a teensy with an audio adapter with the audio library might be the best way to go. I was wondering, however, can the PWM audio output could be used to drive a servo? Also thought about streaming to the DAC and using that as analog input to a second controller and letting that controller move the servos. But if the PWM output would work, that might be better. I'm a programmer, but a novice when it comes to electronics
 
Your best path is to just use the normal Servo library. It works great together with the audio adaptor. It can control up to 12 RC servo motors.

If you really want to use PWM instead of the Servo, that is possible. RC Servo motors need a very slow PWM signal, so the audio PWM output would be much too fast. You could make it work with analogWriteFrequency() and analogWrite(). You'll need to do a little math to translate Servo motor angles to the analogWrite() numbers...

If you use the Teensy DAC pin to output audio instead of the audio adaptor board, you will have to make a small edit in the Servo library code. By default, the DAC output uses the PDB timer. The audio adaptor does *not* use the PDB timer, but the built-in DAC does. The Servo library can use either the PDB or LPTMR timers. It's a simple edit to make it use LPTMR instead of PDB, if you're using the built-in DAC which needs the PDB timer.
 
Thanks Paul for the reply.

If I use the normal servo library, how does it work with the audio adapter. I'm wanting the skull's jaw to move in sync with the audio.

Can you be a little more specific in regards to the other two options? How could I make the PWM audio output work with analogWriteFrequency() and analogWrite()?

As far as the DAC goes, I was going to connect the DAC pin to the analog input of an arduino or teensy, would the clock matter in that case? I thought I could just have the code on the second controller read the analog input and convert the level to a valid PWM value.
 
If I use the normal servo library, how does it work with the audio adapter. I'm wanting the skull's jaw to move in sync with the audio.

They work great together!

RC Servo motors have some latency, so you might need to start the motor moving a little early to get them to appear in sync.

Can you be a little more specific in regards to the other two options? How could I make the PWM audio output work with analogWriteFrequency() and analogWrite()?

I recommend using the Servo library.

If you *really* want to use PWM, you'll have to research RC Servo motor specs and figure out details like PWM carrier frequency and duty cycle. You can find info about those motors on many sites, and documentation on the Teensyduino functions is here:

http://www.pjrc.com/teensy/td_pulse.html

If you decide to take this path, you've got to research the details and figure things out. I can't guide you step-by-step.


As far as the DAC goes, I was going to connect the DAC pin to the analog input of an arduino or teensy, would the clock matter in that case? I thought I could just have the code on the second controller read the analog input and convert the level to a valid PWM value.

I don't understand what you mean by a second controller.

A single Teensy 3.2 can easily play audio and control servo motors. You should only need the Teensy, motors, an adequate power supply, and an amplifier + speaker connected to the DAC signal.
 
They work great together!

I don't understand what you mean by a second controller.

A single Teensy 3.2 can easily play audio and control servo motors. You should only need the Teensy, motors, an adequate power supply, and an amplifier + speaker connected to the DAC signal.

I have a bunch of arduino pro minis and thought it would make the whole process easy. I'm not wanting to write specific servo code for a specific song. I'm wanting something general. I want to move the jaw based on sound level or something similar. Kind of like these guys did with a adafruit sound shield. http://www.instructables.com/id/Talking-Arduino-Halloween-Skeleton/?ALLSTEPS. I suppose I could run a wire between the dac and a analog input on the teensy. Just thought I'd apply a little separation of concerns. Have the teensy worry about the audio and the mini move the servos. I told you, I'm a programmer but not an embedded programmer. LOL
 
Using 2 boards will only make this more complicated. The Servo library works great while the Audio library is playing sounds.

If you want to put those pro minis to use, you certainly can, but there's little advantage to using them. They're only going to make this fairly simple project more complex, as managing more than 1 processor usually does.
 
Oh, I see your concern based on that instructable page....

Teensy is so much better, so much easier than the wave shield. Really, it's a day/night difference. Just open File > Examples > Audio > SamplePlayer and give it a try. The sounds play automatically in the background while your sketch keeps running. Several can play at the same time, through mixers and other objects, automatically streaming to the DAC or audio shield. Really, it's that simple and easy. Hard as that may be to believe, simply try it and you'll see. Modern 32 bit microcontrollers with sophisticated libraries are so much more powerful than that old 8 bit stuff.
 
I actually don't have the audio shield yet. Should get here tomorrow. I was just trying to get a jump on it. I wonder if I can use the AudioAnalyzePeak class to get values for the servo. Have to map them, but seems like that might be a way to do what I want to do.
 
How's this looking?

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Servo.h>

// GUItool: begin automatically generated code
AudioPlaySdWav playSdWav1; //xy=190,172
AudioMixer4 mixer1; //xy=408.99993896484375,81.33999633789062
AudioOutputI2S i2s1; //xy=411,157.00999450683594
AudioAnalyzePeak peak1; //xy=565.6600036621094,152.00999450683594
AudioOutputAnalog dac1; //xy=573,80.66999816894531
AudioConnection patchCord1(playSdWav1, 0, i2s1, 0);
AudioConnection patchCord2(playSdWav1, 0, mixer1, 0);
AudioConnection patchCord3(playSdWav1, 1, i2s1, 1);
AudioConnection patchCord4(playSdWav1, 1, mixer1, 1);
AudioConnection patchCord5(mixer1, dac1);
AudioConnection patchCord6(mixer1, peak1);
AudioControlSGTL5000 sgtl5000_1; //xy=212.32998657226562,109.33000183105469
// GUItool: end automatically generated code
Servo jawServo;

int pirPin = 23;

void setup() {
// put your setup code here, to run once:
pinMode(pirPin, INPUT);
jawServo.attach(20);
attachInterrupt(pirPin, onPirHigh, RISING);
}

void loop() {

}

void onPirHigh() {

if(!playSdWav1.isPlaying()) {
playFile("skull.wav");
}
}

void playFile(const char *filename)
{

// Start playing the file. This sketch continues to
// run while the file plays.
playSdWav1.play(filename);

// A brief delay for the library read WAV info
delay(5);

// Simply wait for the file to finish playing.
while (playSdWav1.isPlaying()) {
if (peak1.available()) {

float monoPeak = peak1.read() * 100.0;
long pos = map((long)monoPeak, 0.0, 100, 0, 90);
jawServo.write(pos);
delay(15);
}
}
}
 
Last edited:
If you're not using the DAC pin, remove dac1.

If you must have DAC pin output, you will need to edit the Servo library code slightly.
 
If you're not using the DAC pin, remove dac1.

If you must have DAC pin output, you will need to edit the Servo library code slightly.


I was actually not going to use the DAC, just haven't removed it. I though that I could add a mixer and connect that peak analyzer. I then would use the value of the peak analyzer to determine how to move the servo. Is there some conflict with the DAC and the servo library?
 
This code actually works. A PIR sensor is used to trigger start. The jaw movement isn't perfect, but is very close. I suppose if one used the FFT filter it could be even better. Works well enough as is for a Halloween prop. By the way, I installed my servo backwards. The jaw is open completely at 0 degrees and closed at 120. The map function took care of this.

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <Servo.h>

#define SDCARD_CS_PIN 10
#define SDCARD_MOSI_PIN 7
#define SDCARD_SCK_PIN 14

// GUItool: begin automatically generated code
AudioPlaySdWav playSdWav1; //xy=190,172
AudioMixer4 mixer1; //xy=408.99993896484375,81.33999633789062
AudioOutputI2S i2s1; //xy=411,157.00999450683594
AudioAnalyzePeak peak1; //xy=565.6600036621094,152.00999450683594
AudioConnection patchCord1(playSdWav1, 0, i2s1, 0);
AudioConnection patchCord3(playSdWav1, 1, i2s1, 1);

AudioConnection patchCord2(playSdWav1, 0, mixer1, 0);
AudioConnection patchCord4(playSdWav1, 1, mixer1, 1);
AudioConnection patchCord6(mixer1, peak1);

AudioControlSGTL5000 sgtl5000_1; //xy=212.32998657226562,109.33000183105469

// GUItool: end automatically generated code
Servo jawServo;

int pirPin = 3;
int servoPin = 4;
int pirState = LOW;

void setup() {

Serial.begin(115200);
AudioMemory(8);
// Comment these out if not using the audio adaptor board.
// This may wait forever if the SDA & SCL pins lack
// pullup resistors
sgtl5000_1.enable();
sgtl5000_1.volume(0.8);

SPI.setMOSI(SDCARD_MOSI_PIN);
SPI.setSCK(SDCARD_SCK_PIN);
if (!(SD.begin(SDCARD_CS_PIN))) {
// stop here, but print a message repetitively
while (1) {
Serial.println("Unable to access the SD card");
delay(500);
}
}

// put your setup code here, to run once:
pinMode(pirPin, INPUT);
jawServo.attach(servoPin);
jawServo.writeMicroseconds(2000);
// attachInterrupt(pirPin, onPirHigh, RISING);
}

void loop() {

int val = digitalRead(pirPin); // read input value
if (val == HIGH) { // check if the input is HIGH

if (pirState == LOW) {
// we have just turned on
Serial.println("Motion detected!");
pirState = HIGH;
playFile("SDTEST2.wav");

}
}
}

void onPirHigh() {

if(!playSdWav1.isPlaying()) {

}
}

void playFile(const char *filename)
{

Serial.println(filename);
playSdWav1.play(filename);

// A brief delay for the library read WAV info
delay(5);

// Simply wait for the file to finish playing.
while (playSdWav1.isPlaying()) {
Serial.println("playing");
if (peak1.available()) {
Serial.println("peak avaiable");
float monoPeak = peak1.read() * 100.0;
long pos = map((long)monoPeak, 0, 100, 2000, 992);
Serial.println(pos);
jawServo.writeMicroseconds(pos);
delay(15);
}
}
jawServo.writeMicroseconds(2000);
pirState = LOW;
Serial.println("");
}
 
If you use the Teensy DAC pin to output audio instead of the audio adaptor board, you will have to make a small edit in the Servo library code. By default, the DAC output uses the PDB timer. The audio adaptor does *not* use the PDB timer, but the built-in DAC does. The Servo library can use either the PDB or LPTMR timers. It's a simple edit to make it use LPTMR instead of PDB, if you're using the built-in DAC which needs the PDB timer.

Could you elaborate on how one would edit the Servo library to switch the timer from PDB to LPTMR (whatever those are)? For this stupid hand prop I've been working on forever I have two servos that deploy the wings on the prop and two RAW audio clips stored on a Prop Shield LC, played back to its amplifier via the DAC on a Teensy 3.2. My servos stopped working once I incorporated the DAC code to play my audio files, so I'm assuming that's due to this timer issue.

I found the Servo library elements in the Arduino package. I see Servo.h and folders for avr, sam, and samd, each with Servo.cpp and ServoTimers.h files. I have no idea what I'm looking for in those files.

Thanks.

Shawn Marshall
 
I see Servo.h and folders for avr, sam, and samd, each with Servo.cpp and ServoTimers.h files. I have no idea what I'm looking for in those files.

You're probably looking at the wrong files. Those are probably the files for Arduino's boards.

Teensy's Servo lib is in hardware/teensy/avr/libraries/Servo within your Arduino folder. On a Windows PC, the default folder is C:\Program Files (x86)\Arduino.

When you find the right one, look for these sections in Servo.cpp:

Code:
// ******************************************************************************
// Teensy 3.0 implementation, using Programmable Delay Block
// ******************************************************************************


Code:
// ******************************************************************************
// Teensy-LC implementation, using Low Power Timer
// ******************************************************************************

You basically want to cause it to use the Teensy LC code, which will work on Teensy 3.x.
 
You're probably looking at the wrong files. Those are probably the files for Arduino's boards.

Teensy's Servo lib is in hardware/teensy/avr/libraries/Servo within your Arduino folder. On a Windows PC, the default folder is C:\Program Files (x86)\Arduino.

When you find the right one, look for these sections in Servo.cpp:

Code:
// ******************************************************************************
// Teensy 3.0 implementation, using Programmable Delay Block
// ******************************************************************************


Code:
// ******************************************************************************
// Teensy-LC implementation, using Low Power Timer
// ******************************************************************************

You basically want to cause it to use the Teensy LC code, which will work on Teensy 3.x.

Hi Paul:

Thanks for the reply; I appreciate it.

I found the correct Servo.cpp file you've specified, and I see those two blocks you've identified.

As as a noob who's never poked around in library files (or code this complex), my best educated guess would be that I cut "defined(__MK20DX256__) " from the #elif line above the //Teensy 3.0 implementation, using Programmable Delay Block and paste that into the #elif line above the // Teensy-LC implementation, using Low Power Timer.

Like this?

Code:
#elif defined(__arm__) && (defined(__MK20DX128__) || defined(__MK64FX512__) || defined(__MK66FX1M0__))
// ******************************************************************************
// Teensy 3.0 implementation, using Programmable Delay Block
// ******************************************************************************

STUFF

#elif defined(__arm__) && (defined(__MKL26Z64__)|| defined(__MK20DX256__))
// ******************************************************************************
// Teensy-LC implementation, using Low Power Timer
// ******************************************************************************

MORE STUFF

Best.

Shawn Marshall
 
Well, I went ahead and changed the code based on my educated guess, and it seems to have worked. The LEDs chase, the audio plays, and the servos work the way they should. I guess I'm smarter than I thought.

I do have a question as to what I'd do if I had a different project (that doesn't use the DAC) in which I wanted to go back to the original Servo.cpp file that drives the servos with the PDB timer. I've posted that question on another thread.

Thanks.

Shawn
 
Status
Not open for further replies.
Back
Top