Furby - Hacked, Hot Wired and Channeling Cartman (or Anyone Else)
by Bobbs1971 in Circuits > Arduino
689 Views, 2 Favorites, 0 Comments
Furby - Hacked, Hot Wired and Channeling Cartman (or Anyone Else)
This instructable picks up where monkeywidget finished. I recommend reading the following pages before starting.
https://www.instructables.com/Furby-Brain-Surgery/
https://www.instructables.com/Control-a-Furby-with...
Amazingly, Google have a website that holds the Furby patent information, and it's gold dust
https://patents.google.com/patent/US6544098B1/en
The thing to remember is that this Furby is channeling Eric Cartman from South Park. Eric Cartman is not a nice person, so bear that in mind when watching the videos and listening to the sound files.
The good news is that despite the Furby's apparent complexity, this ends up being a fairly easy project.
However, it's not all good news. I couldn't get the microphone to work (I think it probably needs an amplifier), and not the infra red gear either, but these didn't really matter for what I had in mind.
**UPDATE **
Your Furby can have many other voices - see this website
Enter text into the website and record the results using Audacity. I also have another Furby possessed by the Yoda force ghost!
Let's go
Prepare the victim by reading monkeywidget's work on skinning the Furby and lobotomising it. We want the brain and speech chip out.
Supplies
A micro controller. I'm using a Teensy 3.2, but a small Arduino would probably work. There's a few modifications needed for the Arduino, so if that's your thing, take a look at this
https://create.arduino.cc/projecthub/amurison718/f...
MP3 player. The DFPlayer is used solely because of it's small size.
SD card - there are storage size restrictions on the DFPlayer
A 1st generation Furby, dead or alive
Wires
Hot Wiring It - Introduction
monkeywidget suggests wiring onto the interface between the PCB and CPU. I wasn't able to make the Furby work this way, possibly due to the victim's state of repair.
Instead I connected wires directly to the sensors. In the end, the only bit I use the PCB for was the H-bridge to control the motor.
I've added a Furby wiring diagram which got me out of some sticky problems.
Downloads
Hot Wiring It - On/off Switch
The most important modification is to add an off switch.
Cut the 5v wire between the battery and PCB and wire in a switch.
Why didn't they leave the factory with one fitted?
Hot Wiring It - Underwire
Gearbox encoder led
Remove the speaker by removing the screws and flip the the Furby forward to expose the bottom of the PCB. Then try to remove the two screws holding the PCB to the battery compartment. This is not easy. I used a sharp vegetable knife. Your fingers - your risk!
While your doing this, a spring and a rod will fall out. Don't lose them.
This is an infrared led, which is really handy as you can't tell if it's working. The positive terminal is already connected to 5v by the white wire in the photo.
To bypass the PCB, I've run a new (black) wire from the led's negative terminal to ground on the board and added a 33 ohm resistor.
Don't leave the resistor bare as it is in the image - insulate it. It's not covered for clarity.
Hot Wiring It - Underwire 2
H bridge
All the Furby's movements are controlled by a single motor and a gearbox containing the most amazing collection of gears and cams. The H bridge controls the motor functions (i.e. forwards and backwards) and is accessed by pins 3 and 4 (yellow and green wires).
Power
This probably isn't necessary as the power can be tapped from the battery compartment directly, but if like me, you didn't realise this at the time, power for the DFPlayer and microcontroller can be picked up from the PCB as shown in the image.
Why do they put 5v and ground next to each other? Surely it'd be better if they were as far apart as possible.
We're done with the underside now so the PCB can be screwed back onto the hinge and the speaker can be re-attached.
Hot Wiring It - Sensors
Mouth sensor
On the left hand side of the Furby (non motor side), are two plugs. The first contains two white wires for the mouth sensor. Desolder these and run another wire to connect the mouth sensor to the microcontroller. See first image.
Motor encoder detector
Seriously important sensor. There's probably a proper name for this component. Next to the two white wires are a yellow and a green wire. The yellow is 5v and green is a signal line. The second photo is a bit messy but what's happening is the yellow wire has been disconnected from the plug and looped up onto the other mouth sensor pin. A wire from the power switch is soldered to the motor encoded detector 5v terminal.
The green signal wire is disconnected from the plug and extended so it attaches to the microcontroller.
Light sensor
Behind the mouth / encoder plug is the IR detector / sender / light sensor plug. We're interested in the two green wires which lead up to the light dependant resistor in the Furby's forehead.
One of these wires can be connected to 5v, the other to the microcontroller. I also added a 33 ohm resistor in the wire connected to the microcontroller.
Hot Wiring It - Sensors 2
Belly Button
Run a 5v wire to the bottom of the belly button and extend the grey wire so it connects to the microcontroller. Done
Back Button
Tip - don't try to unsolder the connections to the back switch. Instead cut the wires at the PCB end and extend one wire to the microcontroller. The other goes to 5v
Cam sensor
This is a massively important sensor as you'll see when we get to the code. It tells the microcontroller where 'home' is for the gearbox. Again, cut the wires at the PCB end and extend one to the microcontroller. The other is connected to 5v
Hot Wiring It - Sensors 3
Tilt switch
I couldn't get the tilt switch to work, but the Furby is 20 years old, so perhaps it's not surprising. Instead I replaced it with a new one which I stuck upside down to get round a problem with INPUT_PULLDOWN and INPUT_PULLUP in the code.
Add Sounds Using the DFPlayer
I'm not a DFPlayer fan; if I can get any part of the libraries to work, that's a win. Have a read of
https://wiki.dfrobot.com/DFPlayer_Mini_SKU_DFR0299...
The website covers how to wire the DFPlayer. I didn't need the resistor on the RX pin with the Teensy, but an Arduino might.
Download some Cartman MP3 files. I used this website
https://www.101soundboards.com/boards/11249-eric-c...
Have a look at
for other voices
Preparing the SD card
This was painful. There's a fair bit on the interweb about this, and I found that it works with the following rules;
1) all folder names are 2 digits only, i.e. 01, 02, etc
2) all folders are in the root directory
3) all filenames begin with 3 digits and have the extender .MP3. The rest of the filename can be whatever you want as the player seems to ignore it, so 007 sick.MP3 is valid.
Preparing the speaker
We're going to wire the speaker directly to the DFPlayer - it's powerful enough to make a reasonable noise. First release the speaker plug by scrapping off all the glue and carefully levering it out with a small screwdriver. The orange pins can be freed from the plug by pressing down on the visible metal part as per the photo.
The grey wires are for the belly button, which is covered elsewhere.
The DFPlayer website covers wiring the thing so I won't repeat it.
Mounting the DFPlayer
I've mounted the player on the right hand side, behind the motor using a sticky pad. The image shows the speaker wires attached, but it needs 5v, ground and the UART wires attached.
Now Add Some Code - Part 1
Here's a walk through of the code
void setup()
1) Set pinModes
2) Switch motor pins off
3) Start up the DFPlayer
4) Play the start up noise
5) Initialise some variables
6) Set up the interrupts for the motor encoder and the cam home pin
7) Move the gearbox to the start position
void setup() { // Set up pins pinMode(mouthPin, INPUT_PULLDOWN); pinMode(bellyPin, INPUT_PULLDOWN); pinMode(backPin, INPUT_PULLDOWN); pinMode(lightPin, INPUT_PULLDOWN); pinMode(tiltPin, INPUT_PULLDOWN); pinMode(gearPin, INPUT_PULLDOWN); pinMode(camPin, INPUT_PULLDOWN); pinMode(led, OUTPUT); pinMode(mForPin, OUTPUT); pinMode(mBakPin, OUTPUT); digitalWrite(mForPin, LOW); digitalWrite(mBakPin, LOW); // Sound card set up Serial3.begin(9600); if (!myDFPlayer.begin(Serial3)) { Serial.println(F("Unable to begin:")); Serial.println(F("1.Please recheck the connection!")); Serial.println(F("2.Please insert the SD card!")); } else { Serial.println(F("DFPlayer Mini online.")); myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD); } myDFPlayer.playFolder(06, 001); // start up sound // get average light level getLightLevel(); // initialise misc variables annoy =1; touchBack =1; timethen = millis(); resetFlags(); findCamStart(); attachInterrupt(digitalPinToInterrupt(gearPin), gearPinChange, CHANGE); attachInterrupt(digitalPinToInterrupt(camPin), camHome, RISING); goToCamPos(100); // start position }
Some thoughts on the interrupts
gearPin (motor encoder)
To keep track of the gearbox's position an encoder is used to count the motor's rotations, and the key to precise motor control is to keep track of this value. If the code is off doing something there's a reasonable chance we'll miss some of the rotations, so an interrupt is attached to the pin.
The rotations are counted by a variable called camPos. This variable is defined as type volatile so it can be accessed by the interrupt. mDir keeps track of the motor's direction.
volatile int camPos; volatile int mDir;
The interrupt is short so it uses up as little time as posible
void gearPinChange() { // Interrupt to increase or decrease cam position depending on motor direction camPos += mDir; }
Despite all this, there's still a margin of error in counting the motor's rotations. The other interrupt is to mitigate this by detecting when the gearbox is at the start position.
void camHome() { camPos =0; }
Finding the gearbox start position
void findCamStart() { // find start position digitalWrite(mForPin, HIGH); digitalWrite(led, HIGH); while (digitalRead(camPin) == LOW) { } digitalWrite(mForPin, LOW); digitalWrite(led, LOW); camPos =0; }
The motor pins are switched on and the motor runs until the camPin goes low, indicating that the gearbox is in the start position.
Add More Code - Part 2
The main loop
1) Check the mouth sensor. Play a sound and move the mouth, then reset the timers
2) Check the back sensor. Play a sound and move the mouth, then reset the timers
3) Check the belly sensor. Play a sound and rock the Furby, then reset the timers
4) Check the tilt sensor. Play a sound and move the mouth, then reset the timers
5) Check the light sensor. Play a sound and move the mouth, then reset the timers
6) Check the sensor timer. Reset if more than two seconds have passed. Used to de-bounce the sensors
7) Check the main timer. If more than 60 seconds have passed, play a sound and move the mouth, then reset the timers.
void loop() { if (digitalRead(mouthPin) && !mouthFlag) { happy(); moveMouth(); timethen = millis(); timethen1 = millis(); mouthFlag = true; } if (digitalRead(backPin) && !backFlag) { backTouch(); moveMouth(); timethen = millis(); timethen1 = millis(); backFlag = true; } if (digitalRead(bellyPin) && !bellyFlag) { myDFPlayer.playFolder(07,001); //Laugh for (int i =0; i<5; i++) { rock(); } goToCamPos(100); // start position bellyFlag = true; timethen1 = millis(); timethen = millis(); } // Check tilt sensor if (digitalRead(tiltPin) && !tiltFlag) { cross(); moveMouth(); timethen = millis(); timethen1 = millis(); tiltFlag = true; } // check light sensor if ((analogRead(lightPin) <aveLight) && !lightFlag) { cross(); openMouth(); delay(1500); closeMouth(); timethen1 = millis(); timethen = millis(); lightFlag = true; } // Reset flags if ( millis() > timethen1 + 2000) { resetFlags(); } // Has Furby been left alone for 60 seconds? if ( millis() > timethen + 60000) { actions(); moveMouth(); timethen = millis(); // get average light level getLightLevel(); } }
Playing sounds using the DFPlayer
More DFPlayer fun. track holds a random number between 1 and 5 (the number of tracks in the directory). It's then cast as a string with "00" in front, and this string is held in trackNumber.
Finally trackNumber is cast as an integer parameter in .playFolder.
This was the only way I could get .playFolder to work.
void happy() { int track; String trackNumber; myDFPlayer.volume(20); //Set volume value (0~30) track = random(5)+1; trackNumber = "00" + (String)track; myDFPlayer.playFolder(04, trackNumber.toInt()); }
Making the Furby move
Movement is created by moving from one gearbox position to another. On my Furby, opening its mouth is achieved by moving from gearbox position zero to position 50.
void openMouth() { // cam position between 0 and 50 goToCamPos(50); }
The work is done by the goToCamPos routine. Depending on whether the gearbox is ahead of behind the target position, the motor runs forward or backwards by writing the the appropriate pins, and the mDir variable is set accordingly (see interrupts in part 1). When the gearbox is in the right position (give or take), the motor is switched off and mDir is set to zero.
void goToCamPos(int targetPos) { while (camPos <targetPos -2 || camPos >targetPos +2) { if (camPos >targetPos +2) { digitalWrite(mForPin, LOW); digitalWrite(mBakPin, HIGH); mDir =-1; } else if (camPos < targetPos -2) { digitalWrite(mForPin, HIGH); digitalWrite(mBakPin, LOW); mDir =1; } } digitalWrite(mForPin, LOW); digitalWrite(mBakPin, LOW); mDir =0; }
Bodywork
The microcontroller will be hidden in the Furby's tail, so some modifications are needed to the plastic shell.
1) make a hole on both sides at the back which allows the wires to exit the shell
2) make a hole in the side so the switch can pass through
3) hide the microcontroller in the Furby's tail
Bodywork - Velcro Time
You're probably going to want to open the Furby at some point and don't want to go through the pain involved in opening a normal Furby again. Step forward velcro!
1) sew velcro to the tail and back of the Furby's fur
2) using sticky velcro, attach some pads to the shell. You'll need to sew some to the inside of the fur.
3) More sticky velcro, this time on the ear bones, and sew some to the inside of the ears.
The Full Code and Sound Files
If it works, the full code and sound files are attached.
Downloads
Easter Egg - Furby IR
Something for further development. As mentioned at the start, I couldn't get the Furby IR to work, which is sort of true. I reckon I should run the following code as an interrupt and accessed it by declaring one of the variables as type volatile.
The code reads a Sony DVD player remote and writes the values to the serial monitor. If that's your bag, have a read of this
http://www.righto.com/2010/03/understanding-sony-i...
Here's a walk through of the code
Some global variables and compiler definitions
#define IRPin 22 int value; long pulseLength; long code[200]; int codePtr; long startTime; long endTime; long pulseTimeOut;
void setup() starts the serial communication, gets an average background level of IR, and sets the initial values for variables
void setup() { pinMode(IRPin, INPUT_PULLDOWN); Serial.begin(9600); for (int i =0; i<100; i++) { value += analogRead(IRPin); } value = value /100; pulseTimeOut = 26400; // 1200 us x 20 + 2400 us header codePtr =0; }<br>
Void loop walkthrough
When an increase in IR is detected, start logging the pulse length in an array called 'code'. Each item in the array is referred to by a variable called 'codePtr'.
Once 200 pulses have been recorded, try to interpret the signal. The controller sends 20 bytes, so 10 copies of the signal are logged which should be enough to decode.
void loop() { while (analogRead(IRPin) >value + 100) // ignore minor increases in background IR { startTime = micros(); while (analogRead(IRPin) >value ) { } endTime = micros(); pulseLength = endTime - startTime; code[codePtr] = pulseLength; codePtr +=1; if (codePtr >200) { parseSignal(); codePtr =0; break; } } }<br>
The signal looks like the image above. It starts with a long header, then a collection of long and short pulses that represent ones (long) and zeros (short). The code looks at the length of the pulses and determines which category they fit into.
Once the header is recognised, the next 20 bytes are translated into ones and zeros.
void parseSignal() { int i; int j; for (i =0; i < codePtr; i++) { // print code received Serial.print(code[i]); Serial.print(","); } Serial.println(); for (i =0; i < codePtr; i++) { if(code[i] >2000 && code[i] <3000) { // header found Serial.println("Header "); for (j=0; j <20; j++) { i++; if (code[i] >900 && code[i] <2000) { Serial.print("1,"); } else { Serial.print("0,"); } } i= codePtr; } } Serial.println(); } <br>