Introduction: The Mechanical Thing From Addams Family
Inspired from the Addams Family pet disembodied hand, this mechanical build of the THING brings much joy for me to recreate. From our childhood until now we enjoyed the TV series that depicted the fictional character the Thing as a pet and a member of the Addams Family which did many chores defying gravity at times while doing them. The Thing was later featured on the Netflix show Wednesday and I have tried to make it look more similar to that version. It can be you perfect companion if you decide to dress up as the Addams for Halloween or even let the Thing greet the kids around the block who come trick or treating at your place. Apart from that you could also set it on top of an antique box (depicted in the TV series as the resting place for it) and let the Thing do its thing.
This robot version of the Thing is powered by a Raspberry Pi Pico W and can be controlled from a remote using Bluetooth. It has movement in 16 Degrees of Freedom (DOF) and can articulate some postures of the Thing. The 3D models were created using Fusion360. Each finger has 3 DOF all moving in the up and down direction. The base of all the fingers are joined to the knuckles which can move left and right. All the joints are articulated with the use of servo motors. The Raspberry Pi Pico W is perfect for this project as it has 16 PWM independent channels for the 16 servos used in this build. Moreover, the Pico W is also able to use Bluetooth communication with the remote.
The Thing depicted in the TV series has movement somewhat like a spider but not exactly, as the fingers aren't precisely spaced apart and trying to recreate such a character is quite the challenge. The overall balance of the robot had to be considered in each stage of the design. Having said that it has its limitations and you will come to know them as you read through this instructable.
Supplies
Supplies to build the Robot
- Emax ES08MDII Digital Servo Motor x 16 link
- Breadboard Style PCB (You could even use a Perfboard and make the connections but I chose this as I would have to make lesser connections as you get a whole rail which can be supplied with 5V and GND for the servo connections similar to a breadboard) x 1 link
- Small Radial Bearings (4mm Inner Diameter, 7mm Outer Diameter) x 16 link
- SS Pins or Aluminum Pins (4mm Diameter x 10mm Length) x 15 link
- SS Pin or Aluminum pin (4mm Diameter x 20mm Length) x 1 link
- Raspberry Pi Pico W x 1 link
- Female Pin Header (20 Pins) x 2 link
- 1000uF Capacitor x 1 link
- TIP31C NPN Transistor x 1 link
- 5V mini Buck Convertor x 1 link
- 7.4V 1300mAH high discharge Lipo Battery (70mm x 35mm x 15mm, Weight<80g) x 1 link
- ON/OFF Switch x 1 link
- 28 AWG Wires x 1 link
- 3D Printer
- 3D printing filament (White)
- Acrylic Paint (Dark Brown, Light Brown, Black, Red, White) link
- Hot Glue Gun and Hot Glue Sticks
- 1.5M x 6mm hex screws and nuts x 32
- 2.5M x 10mm hex screws and nuts x 15
- LED strip (6 small green LEDs) x 1 link
- Zip ties x 10
- Soldering Station
- Multi-meter
- Micro-USB cable (Male) with open end wires (Length>=20cm) x 1 link
- Micro-USB port (Female) with open end wires (Length>=10cm) x 1 link
- NL-8H ball caster wheel (Base Diameter-8.3mm, Height-8mm) x 1 link
- Thrust Bearing (Outer Diameter-23mm, Inner Diameter-12mm, Height-7.5mm) x 1 link
Supplies to build the Remote Control
- Battery Holder with build in on/off switch - 4 AAA Batteries (Supply 6V) x 1 link
- Raspberry Pi Pico W x 1 link
- Push Buttons of different colors x 6 link
- Thumb stick with two independent potentiometers x 2 link
- Mini Buck Convertor x 1 link
- EC11 Rotary Encoder x 2 link
- Custom PCB x 1 link
- 220 Ohm through hole resistors x 6 link
- 5mm LEDs (4 Yellow, 1 Red and 1 White) link
- SSD 1306 OLED Display (128 x 64) x 1 link
- Female Pin Header (20 Pins) x 2 link
- LED light diffusing sheet (with one side stick on film) x 1 link
- Standoff (Outer Diameter-5mm, Inner Diameter-3mm, Height-5mm) x 4 link
- Female Pin Header (4 Pins) x 1 link
Step 1: Finger Design
The finger was designed in three sections. Each section is able to move up and down allowing for a range of motions. A realistic finger is able to move further downwards as opposed to upwards and hence all the joints connecting to the servos are fixed allowing for 40 degrees upward and 140 degrees downward considering the full range of a servo is from 0 to 180 degrees.
The mid section and the bottom sections are identical and the bottom section is fixed to the knuckles (base for assembling the fingers). The tip section of the fingers have cutouts at the tip for filling in with hot glue and stick cutout heat shrink pieces to create traction on the ground when it moves.
The finger nails are separate from the finger tip and are glued to the tip section of the finger. There will be an extra finger nail which is cracked which can be glued to any of the fingers except the thumb. The nail for the thumb is different from the rest so it cannot be seated in the allocated space. I chose to use the middle finger to glue the cracked finger nail to put more emphasis on it when it moves.
Step 2: Assembling Finger Sections
Before assembling the finger sections it might be best to paint the sections and let them dry. I actually spray painted gold color initially which I didn't like so I chose to paint them again with acrylic paint. You could assemble and then paint as well but then you might not be able to get a more uniform coating and might be difficult to access small areas. Various mixes of light and dark brown with some black were used to bring out the skin like color. You can always touch up on the paint after assembly.
The servo horns are fitted into the slots and then screwed in place and the small radial bearings pushed in the allocated holes before fitting in the sections of the fingers with the servos screwed in place. The servo wires are routed from the top of each section through the slots which go back to the wrist section which houses the electronics. You will need to extend the servo wires by soldering on some 28 AWG wires for them to reach the wrist section. It is important to align the servos in the correct position before fixing on the servo horns. The servos should be able to freely move 40 degrees upward and 140 degrees downward. The base section of the thumb will not be able to move as further down as the other fingers due to mechanical restrictions, but the same servo alignment can be used for the thumb as well. Once the servos are in place, the 4mm Diameter x 10 mm Length pins are slotted in from the opposite ends for stability of the joint.
The tip section of the thumb does not have slots to fill in glue but has cutout to slot for the ball caster wheel. The ball caster wheel will be a snug fit so there will be no need for gluing. The ball caster wheel allows for easy motion of the hand as it moves on the ground. Ideally I would prefer to be able to lift the thumb when moving however to maintain balance all the time the thumb and pinky needed to stay touching the ground but more on that later.
Step 3: Design of Knuckles
The base that supports the fingers or referred hereon as the knuckles is by far the biggest part to 3D print in this build. It houses the finger base sections and also connects to the palm of the hand. Once again its better to paint the part before assembling the fingers to the knuckles. The servos are slotted in the allocated space and screwed in before assembling each individual finger assembly to the knuckles.
The back of the knuckles has a wide area for attaching the front section of the palm of the hand. It also has a hole for the axial bearing which can take axial loads imposed on the knuckles from the weight of the palm and the thumb assembly attached to the palm.
The hole on top of the knuckles is to guide through the servo horn support bracket with the servo horn attached. This should be screwed in once the palm is in place which houses the servo for relative motion between the palm of the hand and the knuckles.
Step 4: Design of Hand and Palm Assembly
The palm of the hand comprises of the base section for the thumb. The front section of the palm also houses a servo which needs to be screwed in and assembled to the knuckles. Before screwing in the servo a small radial bearing needs to be pushed into the allocated hole. You should use the round servo horn provided with the servo to attach to the 3D printed servo bracket by aligning it in the middle and screwing in the 2.5mm diameter x 10mm length hex screws and attaching the nut from under. You have to make the holes on the servo horn a bit bigger by drilling into them to fit the 2.5mm hex screws. After this you can set the servo on the palm to a neutral position of 90 degrees and the fit the palm section into the hole with the axial bearing in the knuckles and push the servo bracket into the gear head of the servo. You can then go on to fix the bracket to the knuckles section by carefully drilling two holes on either side and fastening it in place with two 2.5mm diameter x 15mm length hex screws and nuts.
Once done with this you can push the 4mm diameter x 20mm length pin from the bottom of the palm section and screw in the small 3D printing plate to keep the pin from falling down.
Step 5: Battery Assembly
Once the palm and knuckles sections are assembled we can now go on to place the battery in the battery compartment and close it up. The output of the Lipo Battery is connected to the IN(input terminals) of the buck convertor. Turn the small screw on the buck convertor until you get a steady reading of 5V on your multi-meter. Solder the micro USB (male) cable open end wires to the OUT(output terminals) of the buck convertor and route them out for now. This will later plug in to the micro USB port on the lower wrist. I have used a micro USB cable instead of a normal wire as you will have the option to use an external power source to power the robot if the battery drains out.
Make sure when you place the battery in the battery compartment the charging wires both the balance charging wires and the main charging wires of the battery are protruding out for easy access to charge the batteries. You can now hot glue the battery in and slide the buck convertor on the side of the battery and use some hot glue to keep it in place.
Once done you can put the battery cover on and screw it in place with 2.5mmx10mm hex screws. After this you can glue the battery cover attachment on top of the battery cover by aligning the sides.
Step 6: Wrist Components
The wrist comprises of three components namely the lower wrist, upper wrist and the wrist cover panel. The lower wrist is screwed to the top of the palm with 2.5mmx10mm hex screws. The lower wrist is hollow inside and has cutouts to route the wires from the servos as all the electronics are housed inside this part. It also has the cutouts to fit the on/off switch and the micro USB port (female) to connect the battery or external power source. The upper wrist also shares a similar profile to the lower wrist.
You need to fix the lower wrist first and then install the electronics inside before screwing in the upper wrist on top of the lower wrist. The wrist cover fits on top of the upper wrist and has cutouts to access the Raspberry Pi Pico W for programming. The LED light diffusing sheet it cut to the shape of the wrist cover panel and pasted on top. I have used three 1.5mm diameter screws to fasten the wrist cover onto the upper wrist for added rigidity but it is optional.
Step 7: Installing Electronics
After fixing the lower wrist you can solder the pin headers onto the PCB breadboard and mount the Raspberry Pi Pico W. Solder the components according to the schematic diagram including the on/off switch and micro USB port. The PCB will be screwed to the stand which will be glued to the bottom of the lower wrist. All wires from the servos and the led strip should also be soldered onto the PCB by now. Finally you can attach the upper wrist to the lower wrist by screwing in the 2.5mm hex screws through the assigned holes.
By now you are almost done with the build. You will have to do some wire management as you might have guessed with all the servo connections it will be a bit messy, but the wrist sections will cover them however be careful not to expose any leads. Once all the electronics are in place you can put on the wrist cover panel. After the cover panel is fitted in you should still be able to plug in the USB cable to the Pico W from the top.
Attachments
Step 8: Remote Control Assembly
Raspberry Pi had recently announced that with their latest software updates we would now be able to use Bluetooth connectivity in Raspberry Pi Pico W. The remote control is connected to the robot via Bluetooth and requires a Pico W board for the remote as well. The remote has 6 programmable buttons, two thumb sticks, two rotary encoders and indication LEDs for the buttons. Since the Raspberry Pi Pico W has only 3 usable Analog-to-Digital Convertor (ADC) Pins only one potentiometer is read in the right thumb stick. The 6V supplied from the four AAA batteries are regulated to a steady 5V from the buck convertor which powers the remote.
A custom PCB is always helpful to make the circuit compact and neat and I would like to thank Seeed Studio for providing the custom PCB for the remote. Seeed Studio Fusion PCBA Service, available at Seeed Studio, offers comprehensive PCB manufacturing, prototype PCB assembly, and more. Their services cover everything from PCB fabrication, parts sourcing, assembly, to testing, ensuring top-notch quality throughout the process. Furthermore, once you've gauged the market interest and validated your prototype, Seeed Co-create Program can assist you in bringing your product to market with expert guidance and a well-established network of connections.
After soldering the components according to the schematic diagram the PCB can be placed in the case. The remote could also be used for any other projects using a Pico W. The remote casing is in two parts. You will need to glue the battery holder to the bottom part and then screw the top cover with the bottom cover sandwiching the custom PCB in between. The buck convertor housing compartment is opposite to the battery holder on the bottom cover of the remote. The wire leads are routed through the hole on the bottom of the buck convertor housing compartment to the PCB which can supply the required 5V for the circuit. Important fact to note is that the SSD 1306 oled display I had had the VCC and GND Pins in reverse to the ones I normally see. Here is the link to the gerber files to both versions so if you intend to build the remote control you may choose which ever PCB that fits your OLED display.
Attachments
Step 9: Motion Analysis
Since the robot has 16 degrees of freedom we can get a wide range of motions and with it also comes instability and imbalances. The fingers are not equally spaced apart and the wrist section needed to be balanced well on the thumb and pinky fingers of the hand when it is supported on its finger tips. Therefore it is necessary to get a rough idea of the center of gravity while placing the components to have a sense of how it would balance. The added weight from the battery on the thumb also had to be accounted for during the analysis of the motion of the robot. The order of motion of different sections of the hand is important to shift the moment force to achieve different postures. In an ideal situation the hand should be able to balance with four of its fingers while one is raised up but this is nearly impossible as the thumb always needed to be touching the ground to keep the balance. The pinky finger also is subject to similar restrictions during motion but while stationary the robot could be positioned in a way the pinky finger could be raised while the hand balanced on the other four fingers.
It is important to use a mathematical model to get a sense of the position of the fingers tips in coordinate space to speed up the development process and accuracy. An inverse kinematic model was drawn up and the angles of the three servos in each finger were back calculated given a certain position in the coordinate space. By getting the link lengths of each section of the finger and the end effector (the finger tip) position we can determine the angles the servos in each section of the finger needs to move to come to the desired position.
To move forwards you would need to position the front four fingers one by one further out and then return back to the original position while keeping the fingertips in touch with the ground. The same applies for moving back. You might need to move the pinky faster than the other three fingers as the pinky and thumb keeps the overall balance of the robot and creates a pivot point between the rest of the fingers and the wrist.
Below is a block of python code (can also be used with micro-python on raspberry Pi Pico W) which can be used to calculate the angles theta1(finger base servo), theta2(finger mid servo) and theta3(finger tip servo). The inputs are the finger section lengths (L1, L2 and L3), desired finger tip positions from the origin O(xe and ye) and the orientation of the finger tip with respect to the origin O (theta_4).
import math
xe = 30
ye = -90 # minus indicates downward from the coordinate space
theta_4 = 90 # finger tip is perpendicular to the ground
L1 = 42.3
L2 = 42.3
L3 = 56.8
x2 = xe - (L3*math.cos(math.radians(theta_4)))
y2 = ye + (L3*math.sin(math.radians(theta_4)))
print(x2, y2)
alpha = math.atan(y2/x2)
beta = math.acos((((L1**2)+(L2**2)-(x2**2)-(y2**2))/(2*L1*l2)))
theta_2 = 180-math.degrees(beta)
gamma = math.acos((((L1**2)+(x2**2)+(y2**2)-(L2**2))/(2*L1*(math.sqrt((x2**2)+(y2**2))))))
theta_1 = math.degrees(gamma)+math.degrees(alpha)
theta_3 = theta_4 + theta_1 - theta_2
print(f'alpha: {math.trunc(math.degrees(alpha))}\n')
print(f'theta_1: {theta_1}') # Finger Base servo angle
print(f'theta_2: {theta_2}') # Finger Mid servo angle
print(f'theta_3: {theta_3}') # Finger Tip servo angle
Step 10: Programming the Robot
First you will need to download the latest firmware for your Pico W board. Here is the instructions on how to get up and running from the official Raspberry Pi website. Then you need to install the micro-python servo library. I used Thonny IDE for programming the Pico W and if you intend to use it you can directly search for the library on tools->Manage Packages and download it onto the board. Here is a great article on getting started with Thonny for Raspberry Pi Pico. For Bluetooth connectivity there are two libraries (aioble and bluetooth) that are imported which are inbuilt in the latest firmware for the Pico W. We will be using the micro-python version of the asyncio library-uasyncio. It allows to create coroutines that are able to pause to allow another coroutine to run. We create a some tasks and handle it by gathering them in the main fucntion. It allows us to create cooperative multitasking programs where tasks must explicitly give up control of the processor to allow other tasks to run. Here is great article if you would like to know more about asyncio.
The robot can be placed in two orientations. It can be either placed with the finger tips touching the ground or upright supported on the base of the wrist. For the latter orientation you would need to clamp it down to a platform as it would not be able to balance it self on its own. Ideally I would like to have it on its own move from one orientation to the other, but that would be a very difficult task even if it could be done given the mechanical limitations and design criteria. The "trick_or_treat" function can be used in the upright position to hold a bowl of candy and articulate some movements. The "finger_count" function can also be used in this position
Before writing any code you would need to check the positions of the servos once again and note down the angles of the servos when all the fingers are straight. This is sort of a calibration that needs to be done and once done you can accurately get the positions you want to move using the mathematical model discussed in the previous step. The functions for movements are self explanatory and bytes of characters are sent via Bluetooth from the remote to trigger the functions.
from machine import Pin
import time
from servo import Servo
import aioble
import bluetooth
import machine
import uasyncio as asyncio
# Bluetooth UUIDS can be found online at https://www.bluetooth.com/specifications/gatt/services/
_REMOTE_UUID = bluetooth.UUID(0x1848)
_ENV_SENSE_UUID = bluetooth.UUID(0x1800)
_REMOTE_CHARACTERISTICS_UUID = bluetooth.UUID(0x2A6E)
# Pico onboard LED for bluetooth connectivity indication
led = machine.Pin("LED", machine.Pin.OUT)
connected = False
alive = False
wrist_led = Pin(16, Pin.OUT)
wrist_led.value(0)
# Finger Servo Pin Definitions
ib = 8
im = 11
it = 7
mb = 2
mm = 0
mt = 6
rb = 4
rm = 5
rt = 3
pb = 10
pm = 9
pt = 1
tb = 12
tm = 13
tt = 14
# Knuckles servo Pin definition
w = 15
# Create servo objects for all the joints
# Index Finger
i_b = Servo(pin_id=ib) # Index Finger Base Servo
i_m = Servo(pin_id=im) # Index Finger Mid Servo
i_t = Servo(pin_id=it) # Index Finger Tip Servo
# Middle Finger
m_b = Servo(pin_id=mb)
m_m = Servo(pin_id=mm)
m_t = Servo(pin_id=mt)
# Ring FInger
r_b = Servo(pin_id=rb)
r_m = Servo(pin_id=rm)
r_t = Servo(pin_id=rt)
# Pinky
p_b = Servo(pin_id=pb)
p_m = Servo(pin_id=pm)
p_t = Servo(pin_id=pt)
# Thumb
t_b = Servo(pin_id=tb)
t_m = Servo(pin_id=tm)
t_t = Servo(pin_id=tt)
# Knuckles
wrt = Servo(pin_id=w)
delay_ms = 20
async def find_remote():
# Scan for 5 seconds, in active mode, with very low interval/window (to
# maximise detection rate).
async with aioble.scan(5000, interval_us=30000, window_us=30000, active=True) as scanner:
async for result in scanner:
# See if it matches our name
if result.name() == "TheThing":
print("Found The_Thing")
for item in result.services():
print (item)
if _ENV_SENSE_UUID in result.services():
print("Found Robot Remote Service")
return result.device
return None
async def blink_task():
""" Blink the LED on and off every second """
toggle = True
while True and alive:
led.value(toggle)
toggle = not toggle
# print(f'blink {toggle}, connected: {connected}')
if connected:
blink = 1000
else:
blink = 250
await asyncio.sleep_ms(blink)
async def peripheral_task():
print('starting peripheral task')
global connected
connected = False
device = await find_remote()
if not device:
print("Robot Remote not found")
return
try:
print("Connecting to", device)
connection = await device.connect()
except asyncio.TimeoutError:
print("Timeout during connection")
return
async with connection:
print("Connected")
connected = True
alive = True
while True and alive:
try:
robot_service = await connection.service(_REMOTE_UUID)
print(robot_service)
control_characteristic = await robot_service.characteristic(_REMOTE_CHARACTERISTICS_UUID)
print(control_characteristic)
except asyncio.TimeoutError:
print("Timeout discovering services/characteristics")
return
while True:
if control_characteristic != None:
try:
command = await control_characteristic.read()
if command == b'r':
print("a button pressed")
crouch_pos()
if command == b'b':
print("b button pressed")
get_up()
if command == b'g':
print("x button pressed")
finger_stamp()
if command == b'y':
print("y button pressed")
point_finger()
if command == b'w':
print("y button pressed")
bow_down()
if command == b'k':
print("y button pressed")
rise_up()
if command == b'x':
print("y button pressed")
crouch_walk_forward()
if command == b'z':
print("y button pressed")
crouch_walk_backward()
if command == b'v':
print("y button pressed")
stand_walk()
except TypeError:
print(f'something went wrong; remote disconnected?')
connected = False
alive = False
return
except asyncio.TimeoutError:
print(f'something went wrong; timeout error?')
connected = False
alive = False
return
except asyncio.GattError:
print(f'something went wrong; Gatt error - did the remote die?')
connected = False
alive = False
return
else:
print('no characteristic')
await asyncio.sleep_ms(10)
async def main():
tasks = []
tasks = [
asyncio.create_task(blink_task()),
asyncio.create_task(peripheral_task()),
]
await asyncio.gather(*tasks)
def wrist_led_seq():
wrist_led.value(0)
time.sleep(2)
wrist_led.value(0)
for i in range (3):
wrist_led.value(1)
time.sleep(0.1)
wrist_led.value(0)
time.sleep(0.1)
wrist_led.value(1)
def straight_fingers():
i_b.write(76)
i_m.write(15)
i_t.write(55)
m_b.write(87)
m_m.write(44)
m_t.write(58)
r_b.write(85)
r_m.write(35)
r_t.write(60)
p_b.write(88)
p_m.write(45)
p_t.write(45)
t_b.write(88)
t_m.write(48)
t_t.write(78)
time.sleep(1)
def rise_up():
straight_fingers()
time.sleep(2)
t_b.write(84)
t_m.write(80)
t_t.write(170)
time.sleep(2)
t_b.write(84-50)
t_m.write(48+100)
time.sleep(1)
t_b.write(88-50)
t_m.write(48+70)
t_t.write(78+52)
time.sleep(1)
t_b.write(88-60)
t_m.write(48+90)
t_t.write(78+52)
time.sleep(0.5)
i_b.write(76-20)
i_m.write(15)
i_t.write(55)
time.sleep(1)
m_b.write(87-20)
m_m.write(44)
m_t.write(58)
r_b.write(85-20)
r_m.write(35)
r_t.write(60)
p_b.write(88-20)
p_m.write(45)
p_t.write(45)
time.sleep(1)
t_b.write(88-4)
t_m.write(48+125)
t_t.write(78-32)
time.sleep(1)
p_b.write(88-27)
p_m.write(45+30)
p_t.write(45+87)
time.sleep(1)
i_b.write(76-27)
i_m.write(15+30)
i_t.write(55+87)
m_b.write(87-27)
m_m.write(44+30)
m_t.write(58+87)
r_b.write(85-27)
r_m.write(35+30)
r_t.write(60+87)
time.sleep(1)
p_b.write(88-12)
p_m.write(45+104)
p_t.write(45-2)
t_b.write(88-4)
t_m.write(48+125)
t_t.write(78-32)
time.sleep(1)
r_b.write(85-12)
r_m.write(35+104)
r_t.write(60-2)
time.sleep(1)
m_b.write(87-12)
m_m.write(44+104)
m_t.write(58-2)
time.sleep(1)
i_b.write(76-12)
i_m.write(15+104)
i_t.write(55-2)
time.sleep(1)
crouch_pos()
time.sleep(1)
crouch_walk_backward(steps=1)
time.sleep(1)
t_b.write(88-6)
t_m.write(48+145)
t_t.write(78-10)
time.sleep(1)
get_up()
def finger_stamp():
for i in range(10):
i_b.write(76-4)
i_m.write(15+125)
i_t.write(55-32)
time.sleep(0.1)
i_b.write(76-63)
i_m.write(15+138)
i_t.write(55+15)
time.sleep(0.1)
i_b.write(76-12)
i_m.write(15+104)
i_t.write(55-2)
m_b.write(87-4)
m_m.write(44+125)
m_t.write(58-32)
time.sleep(0.1)
m_b.write(87-63)
m_m.write(44+138)
m_t.write(58+15)
time.sleep(0.1)
m_b.write(87-12)
m_m.write(44+104)
m_t.write(58-2)
r_b.write(85-4)
r_m.write(35+125)
r_t.write(60-32)
p_b.write(88-4)
p_m.write(45+125)
p_t.write(45-32)
t_b.write(88-4)
t_m.write(48+125)
t_t.write(78-32)
def get_up():
i_b.write(76-4)
i_m.write(15+125)
i_t.write(55-32)
m_b.write(87-4)
m_m.write(44+125)
m_t.write(58-32)
r_b.write(85-4)
r_m.write(35+125)
r_t.write(60-32)
p_b.write(88-4)
p_m.write(45+125)
p_t.write(45-32)
t_b.write(88-4)
t_m.write(48+125)
t_t.write(78-32)
time.sleep(2)
m_b.write(87+59)
m_m.write(44+60)
m_t.write(58-29)
r_b.write(85+59)
r_m.write(35+60)
r_t.write(60-29)
p_b.write(88+59)
p_m.write(45+60)
p_t.write(45-29)
t_b.write(88+59)
t_m.write(48+60)
t_t.write(78-29)
i_b.write(76+59)
i_m.write(15+60)
i_t.write(55-29)
def point_finger(point_count=4):
m_b.write(87+59)
m_m.write(44+60)
m_t.write(58-29)
r_b.write(85+59)
r_m.write(35+60)
r_t.write(60-29)
p_b.write(88+59)
p_m.write(45+60)
p_t.write(45-29)
t_b.write(88+59)
t_m.write(48+60)
t_t.write(78-29)
i_b.write(76+59)
i_m.write(15+60)
i_t.write(55-29)
# time.sleep(2)
for i in range(point_count):
i_b.write(76-59)
i_m.write(15+119)
i_t.write(55+20)
time.sleep(0.2)
i_b.write(76+10)
i_m.write(15)
i_t.write(55+20)
time.sleep(0.3)
time.sleep(1)
i_b.write(76+59)
i_m.write(15+60)
i_t.write(55-29)
def struggle():
i_b.write(76-28)
i_m.write(15+47)
i_t.write(55+37)
m_b.write(87-28)
m_m.write(44+47)
m_t.write(58+37)
r_b.write(85-28)
r_m.write(35+47)
r_t.write(60+37)
p_b.write(88-28)
p_m.write(45+47)
p_t.write(45+37)
t_b.write(88-28)
t_m.write(48+47)
t_t.write(78+37)
for i in range(48, 140):
i_b.write(i)
m_b.write(i)
r_b.write(i)
p_b.write(i)
t_b.write(i)
time.sleep(0.01)
for i in reversed(range(48, 140)):
i_b.write(i)
m_b.write(i)
r_b.write(i)
p_b.write(i)
t_b.write(i)
time.sleep(0.01)
def bow_down(bowTime=2):
get_up()
time.sleep(2)
wrt.write(90)
time.sleep(0.5)
for i in range(26,155):
i_t.write(i)
time.sleep(0.01)
for i in reversed(range(35,75)):
i_m.write(i)
time.sleep(0.02)
for i in range(29,158):
m_t.write(i)
time.sleep(0.01)
for i in reversed(range(64,104)):
m_m.write(i)
time.sleep(0.02)
time.sleep(1)
for i in range(144,175):
r_b.write(i)
time.sleep(0.02)
for i in range(95,105):
r_m.write(i)
time.sleep(0.02)
for i in range(31,90):
r_t.write(60+30)
time.sleep(0.02)
time.sleep(bowTime)
get_up()
def tap_fingers(tapCount=8,tapSpeed=0.05):
for i in range(tapCount):
t = tapSpeed
i_t.write(55+80)
i_m.write(15+37)
i_b.write(76-47)
time.sleep(t)
m_t.write(58+80)
m_m.write(44+37)
m_b.write(87-47)
time.sleep(t)
r_t.write(60+80)
r_m.write(35+37)
r_b.write(85-47)
time.sleep(t)
p_t.write(45+80)
p_m.write(45+37)
p_b.write(88-47)
time.sleep(t)
i_b.write(76-25)
i_m.write(15+25)
i_t.write(55+70)
time.sleep(t)
m_b.write(87-25)
m_m.write(44+25)
m_t.write(58+70)
time.sleep(t)
r_b.write(85-25)
r_m.write(35+25)
r_t.write(60+70)
time.sleep(t)
p_b.write(88-25)
p_m.write(45+25)
p_t.write(45+70)
def trick_or_treat():
for i in range(2):
i_b.write(76+10)
i_m.write(15+20)
i_t.write(55+20)
m_b.write(87+10)
m_m.write(44+40)
m_t.write(58+20)
r_b.write(85+10)
r_m.write(35+50)
r_t.write(60+20)
p_b.write(88+10)
p_m.write(45+70)
p_t.write(45+20)
t_b.write(88+10)
t_m.write(48+20)
t_t.write(78+20)
time.sleep(2)
i_b.write(76+80)
i_m.write(15+30)
i_t.write(55+65)
m_b.write(87+80)
m_m.write(44+30)
m_t.write(58+60)
r_b.write(85+80)
r_m.write(35+35)
r_t.write(60+60)
p_b.write(88+90)
p_m.write(45+35)
p_t.write(45+60)
t_b.write(88+60)
t_m.write(48+40)
t_t.write(78+80)
time.sleep(2)
i_b.write(76+10)
i_m.write(15+20)
i_t.write(55+20)
for i in range(3):
for i in reversed(range(35, 55)):
i_m.write(i)
time.sleep(0.003)
for i in reversed(range(75, 120)):
i_t.write(i)
time.sleep(0.003)
time.sleep(0.1)
for i in range(75, 120):
i_t.write(i)
time.sleep(0.003)
for i in range(35, 55):
i_m.write(i)
time.sleep(0.003)
def _initial(): # Private function used in the finger_count function
i_b.write(76+60)
i_m.write(15+70)
i_t.write(55+80)
m_b.write(87+60)
m_m.write(44+70)
m_t.write(58+80)
r_b.write(85+60)
r_m.write(35+70)
r_t.write(60+80)
p_b.write(88+60)
p_m.write(45+70)
p_t.write(45+75)
t_b.write(88+60)
t_m.write(48+70)
t_t.write(78+80)
def finger_count():
_initial()
time.sleep(1)
i_b.write(76+60)
i_m.write(15+30)
i_t.write(55+10)
time.sleep(1)
for i in range(3):
wrt.write(80)
time.sleep(0.1)
wrt.write(100)
time.sleep(0.1)
wrt.write(90)
time.sleep(1)
_initial()
time.sleep(1)
i_b.write(76+60)
i_m.write(15+30)
i_t.write(55+10)
m_b.write(87+60)
m_m.write(44+30)
m_t.write(58+10)
time.sleep(1)
for i in range(3):
wrt.write(80)
time.sleep(0.1)
wrt.write(100)
time.sleep(0.1)
wrt.write(90)
time.sleep(1)
_initial()
time.sleep(1)
i_b.write(76+60)
i_m.write(15+30)
i_t.write(55+10)
m_b.write(87+60)
m_m.write(44+30)
m_t.write(58+10)
r_b.write(85+60)
r_m.write(35+30)
r_t.write(60+10)
time.sleep(1)
for i in range(3):
wrt.write(80)
time.sleep(0.1)
wrt.write(100)
time.sleep(0.1)
wrt.write(90)
time.sleep(1)
_initial()
time.sleep(1)
i_b.write(76+60)
i_m.write(15+30)
i_t.write(55+10)
m_b.write(87+60)
m_m.write(44+30)
m_t.write(58+10)
r_b.write(85+60)
r_m.write(35+30)
r_t.write(60+10)
p_b.write(88+60)
p_m.write(45+30)
p_t.write(45+10)
time.sleep(1)
for i in range(3):
wrt.write(80)
time.sleep(0.1)
wrt.write(100)
time.sleep(0.1)
wrt.write(90)
time.sleep(1)
_initial()
t_b.write(88+60)
t_m.write(48+70)
t_t.write(78+80)
def crouch_walk_forward(steps=3, step_speed=0.1, speed=0.3):
for i in range(steps):
i_b.write(76-4)
i_m.write(15+125)
i_t.write(55-32)
m_b.write(87-4)
m_m.write(44+125)
m_t.write(58-32)
r_b.write(85-4)
r_m.write(35+125)
r_t.write(60-32)
p_b.write(88-4)
p_m.write(45+125)
p_t.write(45-32)
t_b.write(88-12)
t_m.write(48+104)
t_t.write(78-2)
time.sleep(speed)
i_b.write(76-63)
i_m.write(15+138)
i_t.write(55+15)
time.sleep(step_speed)
i_b.write(76-12)
i_m.write(15+104)
i_t.write(55-2)
time.sleep(speed)
m_b.write(87-63)
m_m.write(44+138)
m_t.write(58+15)
time.sleep(step_speed)
m_b.write(87-12)
m_m.write(44+104)
m_t.write(58-2)
time.sleep(speed)
r_b.write(85-63)
r_m.write(35+138)
r_t.write(60+15)
time.sleep(step_speed)
r_b.write(85-12)
r_m.write(35+104)
r_t.write(60-2)
time.sleep(speed)
p_b.write(88-63)
p_m.write(45+138)
p_t.write(45+15)
p_b.write(88-12)
p_m.write(45+104)
p_t.write(45-2)
time.sleep(speed)
t_b.write(88-63)
t_m.write(48+138)
t_t.write(78+15)
t_b.write(88-4)
t_m.write(48+125)
t_t.write(78-32)
time.sleep(speed)
i_b.write(76-4)
i_m.write(15+125)
i_t.write(55-32)
m_b.write(87-4)
m_m.write(44+125)
m_t.write(58-32)
r_b.write(85-4)
r_m.write(35+125)
r_t.write(60-32)
p_b.write(88-4)
p_m.write(45+125)
p_t.write(45-32)
t_b.write(88-12)
t_m.write(48+104)
t_t.write(78-2)
def crouch_walk_backward(steps=3, step_speed=0.1, speed=0.3):
for i in range(steps):
i_b.write(76-12)
i_m.write(15+104)
i_t.write(55-2)
m_b.write(87-12)
m_m.write(44+104)
m_t.write(58-2)
r_b.write(85-12)
r_m.write(35+104)
r_t.write(60-2)
p_b.write(88-12)
p_m.write(45+104)
p_t.write(45-2)
t_b.write(88-4)
t_m.write(48+125)
t_t.write(78-32)
time.sleep(speed)
i_b.write(76-63)
i_m.write(15+138)
i_t.write(55+15)
time.sleep(step_speed)
i_b.write(76-4)
i_m.write(15+125)
i_t.write(55-32)
time.sleep(speed)
m_b.write(87-63)
m_m.write(44+138)
m_t.write(58+15)
time.sleep(step_speed)
m_b.write(87-4)
m_m.write(44+125)
m_t.write(58-32)
time.sleep(speed)
r_b.write(85-63)
r_m.write(35+138)
r_t.write(60+15)
time.sleep(step_speed)
r_b.write(85-4)
r_m.write(35+125)
r_t.write(60-32)
time.sleep(speed)
p_b.write(88-63)
p_m.write(45+138)
p_t.write(45+15)
p_b.write(88-4)
p_m.write(45+125)
p_t.write(45-32)
time.sleep(speed)
t_b.write(88-63)
t_m.write(48+138)
t_t.write(78+15)
t_b.write(88-12)
t_m.write(48+104)
t_t.write(78-2)
time.sleep(speed)
i_b.write(76-12)
i_m.write(15+104)
i_t.write(55-2)
m_b.write(87-12)
m_m.write(44+104)
m_t.write(58-2)
r_b.write(85-12)
r_m.write(35+104)
r_t.write(60-2)
p_b.write(88-12)
p_m.write(45+104)
p_t.write(45-2)
t_b.write(88-4)
t_m.write(48+125)
t_t.write(78-32)
def crouch_pos():
i_b.write(76-12)
i_m.write(15+104)
i_t.write(55-2)
m_b.write(87-12)
m_m.write(44+104)
m_t.write(58-2)
r_b.write(85-12)
r_m.write(35+104)
r_t.write(60-2)
p_b.write(88-12)
p_m.write(45+104)
p_t.write(45-2)
t_b.write(88-4)
t_m.write(48+125)
t_t.write(78-32)
def stand_walk():
get_up()
for i in range(3):
wrt.write(90)
time.sleep(0.5)
m_b.write(87+59)
m_m.write(44+60)
m_t.write(58-29)
r_b.write(85+59)
r_m.write(35+60)
r_t.write(60-29)
p_b.write(88+59)
p_m.write(45+60)
p_t.write(45-29)
t_b.write(88+44)
t_m.write(48+83)
t_t.write(78-37)
t = 0.05
t2 = 0.05
i_b.write(76+38)
i_m.write(15+102)
i_t.write(55-50)
time.sleep(t)
i_b.write(76-2)
i_m.write(15+124)
i_t.write(55-22)
time.sleep(t)
i_t.write(55+1)
time.sleep(t)
i_m.write(15+76)
time.sleep(t)
i_b.write(76+15)
time.sleep(t2)
m_b.write(87+34)
m_m.write(44+111)
m_t.write(58-54)
time.sleep(t)
m_b.write(87-2)
m_m.write(44+124)
m_t.write(58-22)
time.sleep(t)
m_t.write(58-3)
time.sleep(t)
m_m.write(44+84)
time.sleep(t)
m_b.write(87+8)
time.sleep(t2)
r_b.write(85+38)
r_m.write(35+102)
r_t.write(60-50)
time.sleep(t)
r_b.write(85-2)
r_m.write(35+124)
r_t.write(60-22)
time.sleep(t)
r_t.write(60+1)
time.sleep(t)
r_m.write(35+76)
time.sleep(t)
r_b.write(85+15)
time.sleep(t2)
p_b.write(88+79)
p_m.write(45+20)
p_t.write(45-10)
p_b.write(88+9)
p_m.write(45+112)
p_t.write(45-31)
time.sleep(0.02)
p_t.write(35)
time.sleep(0.01)
p_m.write(45+42)
time.sleep(0.01)
p_b.write(88+39)
i_b.write(76+38)
i_m.write(15+102)
i_t.write(55-50)
m_b.write(87+34)
m_m.write(44+111)
m_t.write(58-54)
r_b.write(85+38)
r_m.write(35+102)
r_t.write(60-50)
p_b.write(88+17)
p_m.write(45+105)
p_t.write(45-50)
while True:
asyncio.run(main())
Step 11: Programming the Remote Control
In addition to the Bluetooth libraries we need to the ssd1306 library to use the OLED display. You can download the library direct from Thonny by searching for the it under tools->Manage Packages as micropython-ssd1306 and then download it onto the board. I have also used a pbm format image file to display on the OLED during startup of the remote. Here is a great tutorial on these small OLED displays and interfacing them with Raspberry Pi Pico W. If you would like to know more about using Bluetooth communication with the Pico W specifically as a remote control Kevin McAleer has made a great video on explaining it. You can check it out here.
The button presses on the remote are send over to the robot as bytes of characters and the thumb stick readings are checked for threshhold values and then sent over as bytes of characters. Make sure to wait for about a second or two after turning on the robot to let the capacitor charge up before sending commands over the remote. The onboard LED on the Raspberry Pi Pico W will go on blinking at 250ms until the robot has been connected to the remote. Once connected it will blink every second. The corresponding LEDs above the buttons light up as each button is pressed and remains lighted until another one is pressed. The OLED display also indicate which movements of the robot relate to the pressed button by displaying a simple text.
#Shoutout to Kevin McAleer for the great intro to using Bluetooth on Raspberry Pi Pico W to build remote control devices for robots.
import sys
import aioble
import bluetooth
import machine
from machine import Pin
from machine import I2C
from machine import ADC
import uasyncio as asyncio
from micropython import const
import time
import ssd1306
import framebuf
i2c = I2C(0, sda=Pin(0), scl=Pin(1))
display = ssd1306.SSD1306_I2C(128, 64, i2c)
def clear_display():
display.fill(0)
display.show()
# ---------- Display image of Thing on display -----------
display.text('The THING', 28, 0)
with open('hand.pbm', 'rb') as f:
f.readline()
f.readline()
f.readline()
data = bytearray(f.read())
fbuf = framebuf.FrameBuffer(data, 60, 61, framebuf.MONO_HLSB)
display.blit(fbuf, 35, 12, 0)
display.show()
time.sleep(2)
display.fill(0)
display.text("INITIALIZING", 15, 30)
display.show()
time.sleep(1)
display.text("CAPACITOR CHARGE",0, 40)
display.show()
time.sleep(1)
display.text("READY!", 40, 50)
display.show()
time.sleep(2)
clear_display()
# --------------------------------------------------------
# The Thing - Remote Buttons Pin Assignment
white_button = Pin(2, Pin.IN, Pin.PULL_DOWN)
black_button = Pin(3, Pin.IN, Pin.PULL_DOWN)
green_button = Pin(4, Pin.IN, Pin.PULL_DOWN)
red_button = Pin(5, Pin.IN, Pin.PULL_DOWN)
blue_button = Pin(6, Pin.IN, Pin.PULL_DOWN)
yellow_button = Pin(7, Pin.IN, Pin.PULL_DOWN)
white_led = Pin(8, machine.Pin.OUT)
black_led = Pin(10, machine.Pin.OUT)
green_led = Pin(11, machine.Pin.OUT)
red_led = Pin(12, machine.Pin.OUT)
blue_led = Pin(13, machine.Pin.OUT)
yellow_led = Pin(9, machine.Pin.OUT)
def turn_off_leds():
white_led.value(0)
black_led.value(0)
green_led.value(0)
red_led.value(0)
blue_led.value(0)
yellow_led.value(0)
# Thumb Stick Pin Definitions
adcpin1 = 26
adcpin2 = 27
adcpin3 = 28
# Thumb Stick Pin Assignment
left_thumb1 = ADC(adcpin1)
left_thumb2 = ADC(adcpin2)
right_thumb1 = ADC(adcpin3)
def uid():
""" Return the unique id of the device as a string """
return "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}".format(
*machine.unique_id())
MANUFACTURER_ID = const(0x02A29)
MODEL_NUMBER_ID = const(0x2A24)
SERIAL_NUMBER_ID = const(0x2A25)
HARDWARE_REVISION_ID = const(0x2A26)
BLE_VERSION_ID = const(0x2A28)
led = machine.Pin("LED", machine.Pin.OUT)
_ENV_SENSE_UUID = bluetooth.UUID(0x180A)
_GENERIC = bluetooth.UUID(0x1848)
_ENV_SENSE_TEMP_UUID = bluetooth.UUID(0x1800)
_BUTTON_UUID = bluetooth.UUID(0x2A6E)
_BLE_APPEARANCE_GENERIC_REMOTE_CONTROL = const(384)
# Advertising frequency
ADV_INTERVAL_MS = 250_000
device_info = aioble.Service(_ENV_SENSE_UUID)
connection = None
# Create characteristics for device info
aioble.Characteristic(device_info, bluetooth.UUID(MANUFACTURER_ID), read=True, initial="TheThingRemote")
aioble.Characteristic(device_info, bluetooth.UUID(MODEL_NUMBER_ID), read=True, initial="1.0")
aioble.Characteristic(device_info, bluetooth.UUID(SERIAL_NUMBER_ID), read=True, initial=uid())
aioble.Characteristic(device_info, bluetooth.UUID(HARDWARE_REVISION_ID), read=True, initial=sys.version)
aioble.Characteristic(device_info, bluetooth.UUID(BLE_VERSION_ID), read=True, initial="1.0")
remote_service = aioble.Service(_GENERIC)
button_characteristic = aioble.Characteristic(
remote_service, _BUTTON_UUID, read=True, notify=True
)
print('registering services')
aioble.register_services(remote_service, device_info)
connected = False
async def remote_task():
""" Send the event to the connected device """
while True:
left_thumb1_value = left_thumb1.read_u16()
right_thumb1_value = right_thumb1.read_u16()
if not connected:
print('not connected')
await asyncio.sleep_ms(1000)
continue
if green_button.value():
button_characteristic.write(b"g")
button_characteristic.notify(connection,b"g")
turn_off_leds()
green_led.value(1)
clear_display()
display.text("FINGER STAMP", 20, 40)
display.show()
if red_button.value():
button_characteristic.write(b"r")
button_characteristic.notify(connection,b"r")
turn_off_leds()
red_led.value(1)
clear_display()
display.text("CROUCH POS", 20, 40)
display.show()
if blue_button.value():
button_characteristic.write(b"b")
button_characteristic.notify(connection,b"b")
turn_off_leds()
blue_led.value(1)
clear_display()
display.text("STAND POS", 20, 40)
display.show()
if yellow_button.value():
button_characteristic.write(b"y")
button_characteristic.notify(connection,b"y")
turn_off_leds()
yellow_led.value(1)
clear_display()
display.text("POINT FINGER", 20, 40)
display.show()
if white_button.value():
button_characteristic.write(b"w")
button_characteristic.notify(connection,b"w")
turn_off_leds()
white_led.value(1)
clear_display()
display.text("LED ON", 20, 40)
display.show()
if black_button.value():
button_characteristic.write(b"k")
button_characteristic.notify(connection,b"k")
turn_off_leds()
black_led.value(1)
clear_display()
display.text("BOW DOWN", 20, 40)
display.show()
if left_thumb1_value > 40000:
button_characteristic.write(b"x")
button_characteristic.notify(connection,b"x")
clear_display()
display.text("CROUCH FWD", 20, 40)
display.show()
if left_thumb1_value < 20000:
button_characteristic.write(b"z")
button_characteristic.notify(connection,b"z")
clear_display()
display.text("CROUCH BWD", 20, 40)
display.show()
if right_thumb1_value > 40000:
button_characteristic.write(b"v")
button_characteristic.notify(connection,b"v")
clear_display()
display.text("STAND WALK", 20, 40)
display.show()
else:
button_characteristic.write(b"!")
await asyncio.sleep_ms(10)
async def peripheral_task():
print('peripheral task started')
global connected, connection
while True:
connected = False
async with await aioble.advertise(
ADV_INTERVAL_MS,
name="TheThing",
appearance=_BLE_APPEARANCE_GENERIC_REMOTE_CONTROL,
services=[_ENV_SENSE_TEMP_UUID]
) as connection:
print("Connection from", connection.device)
connected = True
print(f"connected: {connected}")
await connection.disconnected(timeout_ms=None)
print(f'disconnected')
async def blink_task():
print('blink task started')
toggle = True
while True:
led.value(toggle)
toggle = not toggle
blink = 1000
if connected:
blink = 1000
else:
blink = 250
await asyncio.sleep_ms(blink)
async def main():
tasks = [
asyncio.create_task(peripheral_task()),
asyncio.create_task(blink_task()),
asyncio.create_task(remote_task()),
]
await asyncio.gather(*tasks)
asyncio.run(main())
Step 12: Conclusion
This build was more an experimental project as opposed to a project that had been tried and tested. The main reason being that the fingers of a hand are not equally spaced apart to fully balance on its tip specially a disembodied one. However having built it its a proof of concept that if all the components of the build are placed in a balanced manner that does not create much of a moment to tip it over and the right amount of freedom in motion is at disposal, it could just work. Having said that there are many limitations in its motion such as moving backwards with bigger steps and maintaining a straight course when stepping forward.
The battery also added extra weight on the thumb alone which in turn put more stress on the thumb as compared to the other fingers. This restricted its agility and had to be accounted for when programming certain movements of the robot. Using the ball caster wheel at the tip of the thumb had sometimes resulted in favor during motion and at time was a disadvantage as it was easy to create slippage which required more holding torque from the servos on the thumb which already bear the load of the battery.
Thank you for reading till the end and hope you like this Halloween themed instructable.