Raspberry Pi Robot Arm with Analog Control

Raspberry Pi robot arm projectMy Raspberry Pi robot arm project, controlled by analog sliding potentiometers!

I’m one of those people who never really finished growing up. I can’t drink water without squash, I regularly forget family birthdays and I still giggle when someone passes wind.

The positive side to this lack of maturity is having no inhibitions to play with what some other 30+ aged humans might consider ‘silly things’. I don’t have to get far into a robotics project for a close friend to tell me to “grow up”, but they really don’t know what they’re missing.

Over the last month or so I’ve been playing with exactly this kind of project – a robot arm! This isn’t just any old kit arm project though, I’ve combined it with a couple of add-on boards and some other spare kit to make a fully analog controlled ‘robot arm panel slider thing’!

The Arm – HEW-DV4

This project started when Kai Longshaw from Blueprint Robotics offered to send me one of his new robot arm kits – the HEW-DV4. I’ve always loved the thought of controlling a robot arm with the Pi, but my usual lack of funds and ability have blocked that dream so far.

There are affordable robot kits out there for the Pi (the MeArm comes to mind), but the HEW is a different beast aimed at competing with more powerful arms on the market such as the uArm and Lynxmotion.

So, of course, I accepted this very generous offer and awaited delivery.

HEW DV4 box

Looks like my postman enjoyed kicking this to my door. Contents were unharmed though.

The Kit

Kai sent me the pre-assembled HEW kit which comes ready to go out of the box. The arm is a combination of 6 servos linked together with laser cut acrylic.

HEW robot pincer

The HEW’s pincer, Jurassic park style

It’s rocking 2x MG946R, 2X MG995 and 2x MG90D servos all made by TowerPro, however comes with no controller board due to its generic nature.

They’re pretty standard servo sizes which means it’ll be dead easy to find a replacement or even upgrade should you feel the need. In terms of the body, there’s a wide choice of colours available.

Me being me, I went for black. Everything looks cool in black right?

HEW servo

The HEW’s servos are cuddled in black acrylic. Sexy doughnuts.

The Controller – ABelectronics Servo PWM Pi Zero

The HEW isn’t just designed for the Raspberry Pi, it’s marketed to work with other micro-computers such as the Arduino. If your device can control 6 servos, it can work with a HEW.

However, you can’t just plug servos into a Raspberry Pi. Well – you can – but software PWM control via GPIO can be a pain. I used it on my Pi Wars AverageBot robot and it was pretty twitchy to say the least.

Rather than mess around with software control again, I got myself a Servo PWM Pi Zero board from ABelectronics.

ABelectronics PWM Pi Zero

The PWM Pi Zero add-on board gives you 16 PWM outputs to play with on your Raspberry Pi.

These guys make add-on boards for the Raspberry Pi (and other devices) and are based in Dorset, UK.

Two brothers with a love of electronics who set up a factory in their own home, they even made their own pick and place machine as they couldn’t find one small enough for their loft!

Check out the video of their machine in action – incredible stuff:

Soldering and Connections

The board from ABelectronics comes in kit form and requires some soldering. All of the main SMD parts are pre-populated on the PCB.

I soldered the board together with an angled row of pins for the servos, and pushed it on to the GPIO of my Pi Zero.

Connecting the board is very simple – servos attach to the angled header (orange signal wire to the top), and if you want to power the servos from an external source you just add the + and – to the terminal block.

PWM Pi Zero Connected

My camera decided to change the colour of the board for this shot. It’s definitely not my lack of photography skills…

Power

Whilst my Pi is powered via the usual micro USB connection for this project, the 6 servos of the robot arm were too meaty to run off the same source. They need around 6V ideally, and with some strong amperage at the same time.

I had a decent 7.2V NiMH RC battery pack knocking about from my youth, so that was selected as my power source for the servos:

Orion RC battery pack

This old Orion racing pack is some meaty kind of guy

At 7.2V this battery pack was a bit aggressive for the servos, so I ordered a step-down buck converter from eBay to drop the voltage.

I chose this specific XL4015 model because it had an output rating of up to 5 amps, more than the cheaper versions you see online. It also included an on-board voltmeter with a button to show either input or output voltages.

XL4015 buck converter

This XL4015 buck converter can pump out 5 amps of juice. That’s one powerful little board right there.

Wiring was simple – hook the RC battery pack into the buck converter inputs, wire the outputs to the terminal block on the ABelectronics board, then adjust the output voltage once connected to a battery.

Basic Control

ABelectronics provide decent code libraries with all of their boards. The servo board libraries have example scripts for setting different PWM values, so I just had to work out the limits for each of the servos.

After a bit of playing and some copying and pasting of lines, I ended up getting to the point where I could pick up random crap with the arm:

Adding Analog Control

Automation is all well and good when you have a purpose, but on my desk there isn’t really anything for a robot arm to do for me (stop sniggering at the back). Instead, I decided it would be much more fun to have some sort of manual control of the arm.

RasPiO Analog Zero

Around this time a fellow blogger and good buddy of mine Alex Eames released his Analog Zero board on Kickstarter. He sent me an early preview version to play with which was damn good timing for this project.

It’s a tidy little Pi Zero sized board that breaks out an MCP3008 Analog to Digital Converter (ADC), and fits neatly on top of a Pi Zero. It was the perfect addition to this project, allowing me to use analog ‘things’ to move each servo.

Analog Zero board

The latest Kickstarter from Alex Eames – the Analog Zero

Sliding Potentiometers

I needed something to use with Alex’s board, so I quickly ordered 6 cheap sliding potentiometer modules from AliExpress, with the aim of having a slider controlling each servo’s range of motion.

Analog sliders

Analog sliders from AliExpress, secured with some old screws in the nastiest way possible.

Once the sliders had arrived I fitted them to my MDF panel and wired them up. Each potentiometer had 3 wires: power, ground and signal. Super easy – power to 3.3V, ground to ground and signal to an input on the Analog Zero board.

Analog sliders

6 analog sliders, one for each servo. Potentiometer-tastic!

Coding

Now the boring hard part – coding! I had to combine the PWM control of the ABelectronics board with the analog control of the RasPiO board.

This was actually quite easy, however I pulled in some help from my USA pal Stuart Weenig to get the servos moving smoothly rather than jerking between positions. He taught me some great tricks with the ‘range()’ function in Python.

The Analog Zero uses the super simple GPIO Zero library, whilst the ABelectronics board has its own library which I’m using for the PWM control. Below is the code I’m using (also on PasteBin) with some comments to explain what’s happening:

#!/usr/bin/python

# Imports
from ABE_ServoPi import PWM
import time
from ABE_helpers import ABEHelpers
from gpiozero import MCP3008

# Set up Analog Zero channel names
adc0 = MCP3008(channel=0)
adc1 = MCP3008(channel=1)
adc2 = MCP3008(channel=2)
adc3 = MCP3008(channel=3)
adc4 = MCP3008(channel=4)
adc5 = MCP3008(channel=5)

# Set up some bits for the servo board
i2c_helper = ABEHelpers()
bus = i2c_helper.get_smbus()
pwm = PWM(bus, 0x40)

# Set PWM frequency to 50 Hz
pwm.set_pwm_freq(50)
pwm.output_enable()

# SERVO NUMBERING GUIDE
# baserotation = 0
# basearm = 1
# elbow = 2
# headangle = 3
# headrotate = 4
# pincer = 5

# Set an initial start positon range
currentpos = [400, 450, 200, 325, 340, 300]

# Move the arm to the start position
pwm.set_pwm(0, 0, 400)
pwm.set_pwm(1, 0, 450)
pwm.set_pwm(2, 0, 200)
pwm.set_pwm(3, 0, 325)
pwm.set_pwm(4, 0, 340)
pwm.set_pwm(5, 0, 300)

# Module for arm movement (Thanks to Stuart Weenig for the help)
def MoveArm(servo, stoppos, speed): # servo is the number of servo to move, stoppos is the position to move to, and speed is the speed with which to move the arm
   global currentpos # grab the current position variable in a RW capability so that the final position can be stored back
   startpos = currentpos[servo] # get the current position of the selected servo
   
   if stoppos - startpos == 0: # if the stop and start position are both 0, do nothing
      pass # do nothing
      
   else: # otherwise...
      direction = (stoppos - startpos)/abs(stoppos - startpos) # determine the direction of movement based on current position and requested position; will be -1 for going down and 1 for going up
      for i in range(startpos, stoppos, direction * speed): # loop through all the positions, moving to each position in small increments
         pwm.set_pwm(servo, 0, i) # move the arm
         currentpos[servo] = stoppos # store the final position
         time.sleep(0.01) # wait

while (True): # Run forever in a loop
    
    time.sleep(0.01)
    speed = 5 # set the speed
    
    adc_0 = round(adc0.value, 2) # round the ADC output to 2 decimals
    
    # Following if/elif statements detemin what happens when each slider is witin a set range
    # Ineficient way of doing things but it works!
    # Each block has a header with (a) and (b) - this is the PWM perating range of each servo
    
    ###############################################################
    # BASE ROTATION # a = 275 b = 525 (right - left)
    ###############################################################

    ### CENTRAL ###
    if 0.48 <= adc_0 <= 0.50:
        MoveArm(0, 435, speed)
    
    ### UPWARDS ###
    elif 0.51 <= adc_0 <= 0.59:
        MoveArm(0, 425, speed)
        
    elif 0.60 <= adc_0 <= 0.69:
        MoveArm(0, 400, speed)
        
    elif 0.70 <= adc_0 <= 0.79:
        MoveArm(0, 375, speed)
        
    elif 0.80 <= adc_0 <= 0.89:
        MoveArm(0, 350, speed)
        
    elif 0.90 <= adc_0 <= 1.00:
        MoveArm(0, 325, speed)
        
    ### DOWNWARDS ###
    
    elif 0.40 <= adc_0 <= 0.47:
        MoveArm(0, 450, speed)
    
    elif 0.30 <= adc_0 <= 0.39:
        MoveArm(0, 475, speed)
        
    elif 0.20 <= adc_0 <= 0.29:
        MoveArm(0, 500, speed)
        
    elif 0.10<= adc_0 <= 0.19:
        MoveArm(0, 525, speed)
        
    elif 0.00 <= adc_0 <= 0.09:
        MoveArm(0, 550, speed)
    
    ###############################################################
    # BASE ARM     a = 325 b = 550 (away - back)
    ###############################################################

    ### CENTRAL ###
    if 0.485000000000 <= adc1.value <= 0.500000000000:
        MoveArm(1, 435, speed)
    
    ### UPWARDS ###
    elif 0.500000000001 <= adc1.value <= 0.599999999999:
        MoveArm(1, 425, speed)
        
    elif 0.600000000000 <= adc1.value <= 0.699999999999:
        MoveArm(1, 400, speed)
        
    elif 0.700000000000 <= adc1.value <= 0.799999999999:
        MoveArm(1, 375, speed)
        
    elif 0.800000000000 <= adc1.value <= 0.899999999999:
        MoveArm(1, 350, speed)
        
    elif 0.900000000000 <= adc1.value <= 1.000000000000:
        MoveArm(1, 325, speed)
        
    ### DOWNWARDS ###
    
    elif 0.400000000000 <= adc1.value <= 0.484999999999:
        MoveArm(1, 450, speed)
    
    elif 0.300000000000 <= adc1.value <= 0.399999999999:
        MoveArm(1, 475, speed)
        
    elif 0.200000000000 <= adc1.value <= 0.299999999999:
        MoveArm(1, 500, speed)
        
    elif 0.100000000000 <= adc1.value <= 0.199999999999:
        MoveArm(1, 525, speed)
        
    elif 0.000000000000 <= adc1.value <= 0.099999999999:
        MoveArm(1, 550, speed)
        
    ###############################################################
    # ELBOW     a = 200 b = 525 (up - down)
    ###############################################################

    ### CENTRAL ###
    if 0.485000000000 <= adc2.value <= 0.500000000000:
        MoveArm(2, 362, speed)
    
    ### UPWARDS ###
    elif 0.500000000001 <= adc2.value <= 0.599999999999:
        MoveArm(2, 330, speed)
        
    elif 0.600000000000 <= adc2.value <= 0.699999999999:
        MoveArm(2, 300, speed)
        
    elif 0.700000000000 <= adc2.value <= 0.799999999999:
        MoveArm(2, 270, speed)
        
    elif 0.800000000000 <= adc2.value <= 0.899999999999:
        MoveArm(2, 230, speed)
        
    elif 0.900000000000 <= adc2.value <= 1.000000000000:
        MoveArm(2, 200, speed)
        
    ### DOWNWARDS ###
    
    elif 0.400000000000 <= adc2.value <= 0.484999999999:
        MoveArm(2, 390, speed)
    
    elif 0.300000000000 <= adc2.value <= 0.399999999999:
        MoveArm(2, 420, speed)
        
    elif 0.200000000000 <= adc2.value <= 0.299999999999:
        MoveArm(2, 450, speed)
        
    elif 0.100000000000 <= adc2.value <= 0.199999999999:
        MoveArm(2, 490, speed)
        
    elif 0.000000000000 <= adc2.value <= 0.099999999999:
        MoveArm(2, 525, speed)
        
    ###############################################################
    # HEAD ANGLE    a = 200 b = 450 (lower - higher)
    ###############################################################

    ### CENTRAL ###
    if 0.485000000000 <= adc3.value <= 0.500000000000:
        MoveArm(3, 325, speed)
    
    ### UPWARDS ###
    elif 0.500000000001 <= adc3.value <= 0.599999999999:
        MoveArm(3, 350, speed)
        
    elif 0.600000000000 <= adc3.value <= 0.699999999999:
        MoveArm(3, 375, speed)
        
    elif 0.700000000000 <= adc3.value <= 0.799999999999:
        MoveArm(3, 400, speed)
        
    elif 0.800000000000 <= adc3.value <= 0.899999999999:
        MoveArm(3, 425, speed)
        
    elif 0.900000000000 <= adc3.value <= 1.000000000000:
        MoveArm(3, 450, speed)
        
    ### DOWNWARDS ###
    
    elif 0.400000000000 <= adc3.value <= 0.484999999999:
        MoveArm(3, 300, speed)
    
    elif 0.300000000000 <= adc3.value <= 0.399999999999:
        MoveArm(3, 275, speed)
        
    elif 0.200000000000 <= adc3.value <= 0.299999999999:
        MoveArm(3, 250, speed)
        
    elif 0.100000000000 <= adc3.value <= 0.199999999999:
        MoveArm(3, 225, speed)
        
    elif 0.000000000000 <= adc3.value <= 0.099999999999:
        MoveArm(3, 200, speed)

    ###############################################################
    # HEAD ROTATE    a = 175 b = 475 (anticlockwise - clockwise)
    ###############################################################

    ### CENTRAL ###
    if 0.485000000000 <= adc4.value <= 0.500000000000:
        MoveArm(4, 325, speed)
    
    ### UPWARDS ###
    elif 0.500000000001 <= adc4.value <= 0.599999999999:
        MoveArm(4, 355, speed)
        
    elif 0.600000000000 <= adc4.value <= 0.699999999999:
        MoveArm(4, 385, speed)
        
    elif 0.700000000000 <= adc4.value <= 0.799999999999:
        MoveArm(4, 415, speed)
        
    elif 0.800000000000 <= adc4.value <= 0.899999999999:
        MoveArm(4, 445, speed)
        
    elif 0.900000000000 <= adc4.value <= 1.000000000000:
        MoveArm(4, 475, speed)
        
    ### DOWNWARDS ###
    
    elif 0.400000000000 <= adc4.value <= 0.484999999999:
        MoveArm(4, 295, speed)
    
    elif 0.300000000000 <= adc4.value <= 0.399999999999:
        MoveArm(4, 265, speed)
        
    elif 0.200000000000 <= adc4.value <= 0.299999999999:
        MoveArm(4, 235, speed)
        
    elif 0.100000000000 <= adc4.value <= 0.199999999999:
        MoveArm(4, 205, speed)
        
    elif 0.000000000000 <= adc4.value <= 0.099999999999:
        MoveArm(4, 175, speed)
        
    ###############################################################
    # PINCER    a = 300 b = 475 (open - nearly close)
    ###############################################################

    ### CENTRAL ###
    if 0.485000000000 <= adc5.value <= 0.500000000000:
        MoveArm(5, 385, speed)
    
    ### UPWARDS ###
    elif 0.500000000001 <= adc5.value <= 0.599999999999:
        MoveArm(5, 403, speed)
        
    elif 0.600000000000 <= adc5.value <= 0.699999999999:
        MoveArm(5, 430, speed)
        
    elif 0.700000000000 <= adc5.value <= 0.799999999999:
        MoveArm(5, 465, speed)
        
    elif 0.800000000000 <= adc5.value <= 0.899999999999:
        MoveArm(5, 490, speed)
        
    elif 0.900000000000 <= adc5.value <= 1.000000000000:
        MoveArm(5, 518, speed)
        
    ### DOWNWARDS ###
    
    elif 0.400000000000 <= adc5.value <= 0.484999999999:
        MoveArm(5, 367, speed)
    
    elif 0.300000000000 <= adc5.value <= 0.399999999999:
        MoveArm(5, 349, speed)
        
    elif 0.200000000000 <= adc5.value <= 0.299999999999:
        MoveArm(5, 331, speed)
        
    elif 0.100000000000 <= adc5.value <= 0.199999999999:
        MoveArm(5, 313, speed)
        
    elif 0.000000000000 <= adc5.value <= 0.099999999999:
        MoveArm(5, 300, speed)

(Probably best to ask any questions in the comments section at the end of this post)

The Result

…one badass robot arm slidey controly panely thing!

Anyone else think it looks like a DJ mixing desk?

Summary

Raspberry Pi robot arms RULE! Sliders RULE! Big poorly cut panels of MDF RULE!

This project was more fun than a clown on anti-depressants, and probably because it wasn’t just a case of buying a kit and following a guide. The HEW arm is a very cool piece of kit, but it didn’t come with a controller so I had to work that out myself. That was actually really rewarding once it all started working.

Adding the RasPiO Analog Zero made the whole thing more physical with the sliders, and even prompted me to learn about the ‘range()‘ function (or at least ask clever people from America about it!).

Best of all – this is the kind of big fun project that I can take to events to get kids excited about the Pi, electronics and coding. LEDs are so 2014!

Expect to see this (probably an improved version with better wiring) at the next CamJam, PiWars or similar, with me calling out for 6 volunteers to move random stuff in some kind of timed challenge.

Until next time…

3 Comments on "Raspberry Pi Robot Arm with Analog Control"

  1. Hi AverageMan, Cool project!

    Some comments / tips on your Python code…

    Lines 38-45 could be replaced with:
    for i, pos in enumerate(currentpos):
    pwm.set_pwm(i, 0, pos)

    Line 57 could be moved to after the ‘for’ loop.

    There’s no need for such a crazy number of decimal places in your ‘if’ tests. Instead of e.g.
    elif 0.500000000001 <= adc1.value <= 0.599999999999:
    MoveArm(1, 425, speed)
    elif 0.600000000000 <= adc1.value <= 0.699999999999:
    MoveArm(1, 400, speed)

    you could instead do this (note that it's now using a '<=' and '<' for each test, instead of two '<=')
    elif 0.5 <= adc1.value < 0.6:
    MoveArm(1, 425, speed)
    elif 0.6 <= adc1.value < 0.7:
    MoveArm(1, 400, speed)

    However, instead of 'chopping up' the slider values into a fixed number of steps (and then manually interpolating in your MoveArm function), you could instead do something like http://stackoverflow.com/questions/1969240/mapping-a-range-of-values-to-another to smoothly map the sensor values into servo positions. (which would also make your code much shorter)

    If you're feeling adventurous you could even try using https://gpiozero.readthedocs.io/en/v1.2.0/api_tools.html#gpiozero.tools.scaled to 'automatically' get scaled values from gpiozero and pass them directly to pwm.set_pwm – something like the following *might* work (but I can't test as I don't have sliders or a servo controller or a robot arm!)

    from gpiozero.tools import scaled # extra import

    scaled_adcs = [] # use a list to make the while-loop simpler
    scaled_adcs.append(scaled(adc0.values, 525, 275)) # these return generators
    scaled_adcs.append(scaled(adc1.values, 550, 325))
    scaled_adcs.append(scaled(adc2.values, 525, 200))
    # etc.

    while True:
    time.sleep(0.01)
    for i in range(6):
    pwm.set_pwm(i, 0, next(scaled_adcs[i]))

  2. And in terms of wiring, it looks like you might be able to jumper the power and ground signals from one slider board to the next, which might make things a little bit tidier?

  3. Great photos and write-up, well done.

Leave a comment

Your email address will not be published.


*