lfurze
Posts: 10
Joined: Sun Apr 21, 2019 9:21 am

Moving 16 servos in a wave

Sun Jun 09, 2019 2:50 am

I'm trying to write a program which uses the Adafruit 16 servo HAT to control 16 servos in a wave, moving 4 at a time.

I have found the code so that one servo moves following a sine wave, but am struggling with applying that to multiple servos.

The servos will be arranged as follows (imagine this is a top down view)
Row 1 * * * *
Row 2 * * * *
Row 3 * * * *
Row 4 * * * *

They will each have equal length nylon fishing line with a weight at the end. I'd like row one to move, then a split second later row 2, etc, in a loop, in order to create a wave across the four rows.

Is there a way to do this in python on the pi with this hat?

ghp
Posts: 1418
Joined: Wed Jun 12, 2013 12:41 pm
Location: Stuttgart Germany
Contact: Website

Re: Moving 16 servos in a wave

Sun Jun 09, 2019 9:07 am

Something like this ?
anicircle.gif
anicircle.gif (81.69 KiB) Viewed 724 times

lfurze
Posts: 10
Joined: Sun Apr 21, 2019 9:21 am

Re: Moving 16 servos in a wave

Sun Jun 09, 2019 10:13 am

8-) precisely, in 4 rows of 4

ghp
Posts: 1418
Joined: Wed Jun 12, 2013 12:41 pm
Location: Stuttgart Germany
Contact: Website

Re: Moving 16 servos in a wave

Sun Jun 09, 2019 10:25 am

test_image.gif
test_image.gif (69.37 KiB) Viewed 702 times

Code: Select all

import math
import time

# the time a sweep cycle shall take is  
total_time = 5.0

# there will not be too frequent updates, the update cycle is
wait_time = 0.2

# the number of updates in a cycle will be approximate
N = int( total_time / wait_time)
#
# Number of Servo
#
SERVO = 16

# build a data arry which contains the precalculated sine values.
data = []
for n in range(N):
    radian = 2 * math.pi / N * n
    data.append ( math.sin(radian) )
#
# always a good idea to check values.
# sine goes from 0 .. prox 1 then back to 0 then to prox -1 then to zero.
#    
print ( data)
#

def convert_sine_to_0_180( sin_value):
    """ you need a conversion from 
        sine values in range [-1.. +1] to the adafruit servo values [0..180]"""
    out = (sin_value + 1.0) * 90.
    return out
    
tick = 0
while True:
    #
    # for clarity, here separate values are calculated.
    # better approach would be a loop, of course
    #
    s0_sine = data[( tick + N // SERVO * 0 ) % N]
    s1_sine = data[( tick + N // SERVO * 1 ) % N]
    s2_sine = data[( tick + N // SERVO * 2 ) % N]
    s3_sine = data[( tick + N // SERVO * 3 ) % N]
    # do this for all SERVO values
           
    s0 = convert_sine_to_0_180(s0_sine)
    s1 = convert_sine_to_0_180(s1_sine)
    s2 = convert_sine_to_0_180(s2_sine)
    s3 = convert_sine_to_0_180(s3_sine)
    # do this for all SERVO values       
    
    #
    # put these values to adafruit api call for servo 0, 1, 2, 3
    #
    # here only a printout is made
    print("s0", tick, s0)
    print("s1", tick, s1)
    print("s2", tick, s2)
    print("s3", tick, s3)
    # do this for all SERVO values       
    
    tick += 1
    time.sleep(wait_time)    


lfurze
Posts: 10
Joined: Sun Apr 21, 2019 9:21 am

Re: Moving 16 servos in a wave

Sun Jun 09, 2019 6:01 pm

That looks fantastic! Long weekend here in Australia for the queen's birthday, if you can believe that. All the gear is at work so I'll be trying this out first thing Tuesday morning. Thanks.

User avatar
ben_nuttall
Raspberry Pi Foundation Employee & Forum Moderator
Raspberry Pi Foundation Employee & Forum Moderator
Posts: 231
Joined: Sun Aug 19, 2012 11:19 am
Location: Cambridge, UK
Contact: Website

Re: Moving 16 servos in a wave

Mon Jun 10, 2019 5:30 pm

Does the servo hat allow you to use GPIO Zero, or does it have another interface than just PWM on GPIO pins?

Would be really easy with GPIO Zero:

Code: Select all

from gpiozero import Servo
from gpiozero.tools import cos_values
from signal import pause

servos = [Servo(pin) for pin in (2, 3, 4, 5, 6, 7)]

for servo in servos:
    servo.source = cos_values()
    servo.source_delay = 0.1  # this sets the speed
    sleep(0.1)  # this sets the time delay between each servo

pause()
I'd recommend using pigpio as the pin factory. Read about this here: https://gpiozero.readthedocs.io/en/stab ... in-factory
Community Manager - Raspberry Pi Foundation
Author of GPIO Zero and creator of piwheels

Lfurze86
Posts: 3
Joined: Mon Jun 10, 2019 9:38 pm

Re: Moving 16 servos in a wave

Mon Jun 10, 2019 9:47 pm

If only everything were that simple! I'll definitely give it a try. I can't find anything on adafruit re. driving the servos directly from the GPIO but we'll see.

In the meantime I've had a crack at the code above to put it in a loop. I also put a visual representation of the sine wave in because long lists of numbers evade me. I'm yet to get back to work to test the actual servos but it all works in theory...

EDIT: Code works. Movement is a little jittery but I'll play around with the timings and hopefully resolve that. Changed the while loop to <= 15 from 16 as the servos go from 0-15 (still getting my head around 0 based indices ;) )

Code: Select all


tick = 0

try:
    while True:

        serv = 0

        while serv <= 15:
            angle = data[( tick + N // SERVO * serv ) % N]
            angle = convert_sine_to_0_180(angle)
            # uncomment the following line to print all servo angles
            #print (serv, tick, s) 
            # uncomment the following line to run servos
            #kit.servo[serv].angle = angle
            # the following if statement prints a sine wave from servo 0 to terminal
            if serv == 0:
                print ((int(angle)/5) * "*")
            serv += 1

        tick += 1
        time.sleep(wait_time)

except KeyboardInterrupt:
    print("Resetting servos")
    serv = 0
    for serv in range (0, 15):
        angle = 0
        serv += 1
        print(serv, angle)
        # uncomment the following to reset servos
        #kit.servo[serv].angle = 0

ghp
Posts: 1418
Joined: Wed Jun 12, 2013 12:41 pm
Location: Stuttgart Germany
Contact: Website

Re: Moving 16 servos in a wave

Tue Jun 11, 2019 4:26 am

I can't find anything on adafruit re. driving the servos directly from the GPIO but we'll see.
The servo hat is best driven with the adafruit library. There are possibilities (pigpiod) to drive servo with GPIO directly, but then the servo hat is obsolete. Servo hat provides convenient pin connections for servo and external power supply, which needs to be build separately when using GPIO.
Movement is a little jittery but I'll play around with the timings and hopefully resolve that.
The internal chip in servo hat PCA9685 has an own oscillator and provides 12 bit resolution full scale. For servo, which effectively use only 1ms in a 20ms cycle, this results in 204 available steps. There is no jitter expected. Jitter would show up as random small moves around a stable position. If the movement is 'in steps', meaning the servo makes small pause in between, then try to decrease the wait_time, 0.1 sec down to 0.06 sec would be fine. Faster than 40 ms makes no sense, as the servo data cycle is fixed 20ms.

Lfurze86
Posts: 3
Joined: Mon Jun 10, 2019 9:38 pm

Re: Moving 16 servos in a wave

Tue Jun 11, 2019 4:39 am

Thanks again - yes "jitter" was a bit ambiguous. I took the wait_time down to 0.06 and it smoothed out nicely.

danjperron
Posts: 3404
Joined: Thu Dec 27, 2012 4:05 am
Location: Québec, Canada

Re: Moving 16 servos in a wave

Tue Jun 11, 2019 6:14 pm

Just A question.

Why calculating sine value since servos are in facto in angle already! more or less from -90 to 90 degree.

Using sine value to calculate the difference between each servo won't provide a nice sinewave.
You should instead split the number between the -90 to 90 using linear value. Normally 500ms = -90 degree and 1500ms = 90 degree.

Lfurze86
Posts: 3
Joined: Mon Jun 10, 2019 9:38 pm

Re: Moving 16 servos in a wave

Wed Jun 12, 2019 12:55 am

The sine wave calculation (ghp's code) provides a sinusoidal movement to the servos, controlling the speed of the rotation. The code is working, and the effect is very pleasing. For anyone that's been paying attention the finished product is below. Can't make any guarantees for the elegance of my bits of code, and I'm fairly sure that a few things in there could be tightened up.

Essentially, the code reads total daily rainfall data captured at my nearest station (Bureau of Meteorology data) from an HTML table on their site and uses this data to set the speed of the cycle. ghp's code translates the movement into the pleasing sine wave across 16 servos, which lower and raise weights on a nylon thread to create a rippling sculpture which changes based on the amount of rainfall.

Code: Select all

import math
import time
import requests
import pandas as pd
from adafruit_servokit import ServoKit
print("""
----------------------------
Rainfall - Kinetic Sculpture
----------------------------
""")
print("Gathering rainfall data")
# Get the rainfall data from BOM
# state the URL
url = 'http://www.bom.gov.au/products/IDV60801/IDV60801.94829.shtml'
# pretend to be a browser to avoid error 403 Forbidden
header = {
  "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.75 Safari/537.36",
  "X-Requested-With": "XMLHttpRequest"
}
# open the url
r = requests.get(url, headers=header)
# convert the HTML tables to a Pandas DataFrame
df = pd.read_html(r.text)
# we only want today's data
# today's data is found in the 2nd table on the page
df = df[1]
# pull out the maximum rain measurment
# for some reason either Pandas or the BOM website is confused...
# the rainfall data is in the windspeed column
max = df['Spdkts'].max()
print("Rainfall: "+str(max)+"mm")

# setup adafruit servo HAT
snumber = 16
kit = ServoKit(channels=snumber)

# calculate the sweep cycle time based on rainfall
if max == 0:
    total_time = 10
elif max > 0 and max <= 1:
    total_time = 7
elif max > 1 and max <= 3:
    total_time = 4
elif max > 3 and max <= 6:
    total_time = 1
elif max > 3 and max <= 10:
    total_time = 0.5
        
print("Sweep cycle time is: "+str(total_time)+"s")

# there will not be too frequent updates, the update cycle is
wait_time = 0.05

# the number of updates in a cycle will be approximate
N = int( total_time / wait_time)

# build a data array which contains the precalculated sine values.
data = []
for n in range(N):
    radian = 2 * math.pi / N * n
    data.append ( math.sin(radian) )

def convert_sine_to_0_180(sin_value):
    """ you need a conversion from 
        sine values in range [-1.. +1] to the adafruit servo values [0..180]"""
    out = (sin_value + 1.0) * 90.
    return round(out, 2)

tick = 0

print("Running servos")
try:
    while True:

        serv = 0

        while serv <= 15:
            angle = data[( tick + N // snumber * serv ) % N]
            angle = convert_sine_to_0_180(angle)
            # uncomment the following line to print all servo angles
            #print (serv, tick, s) 
            # uncomment the following line to run servos
            kit.servo[serv].angle = angle
            # the following if statement prints a sine wave from servo 0 to terminal
            #if serv == 0:
             #   print ((int(angle)/5) * "*")
            serv += 1

        tick += 1
        time.sleep(wait_time)

except KeyboardInterrupt:
    print("Resetting servos")
    serv = 0
    for serv in range (0, 15):
        angle = 0
        serv += 1
        kit.servo[serv].angle = 0
    print("""
----------------------------""")

Return to “Python”