-2
\$\begingroup\$

I've seen a very similar question to this but want some extra pair of eyes on my circuit here.

The issue I've encountered is on startup of this script every so often the servos will randomly move to their limit without being told to do so.

From my understanding, as it is only done that when a script is ran, the issue revolves around the I2C bus. The bus will always be initialized when starting. Could this be an issue with too much noise around the signal wires? I.e. the SCL, SDA.

I also know that power supplies are a common issue with sudden movement, but this only happens when the script is run so often so I don't believe it to be so.

Any advice to determine the issue and as well as solve would be greatly appreciated.

#ROS IMPORTS
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Joy
from geometry_msgs.msg import Twist

#HARDWARE IMPORTS
import board
from adafruit_pca9685 import PCA9685
from adafruit_motor import servo
import RPi.GPIO as GPIO

#IMPORT KINEMATIC LIBRARIES
import math as math

#GENERAL IMPORTS
from time import sleep as sleep

class ServoDriver(Node):
    
    #   TO DO
    #   1. Figure out Sudo Privileges for this script.
    #   2. Create Joystick Functionality (4 Buttons) 
    #   3. Create Seperate Kinematics Library
    #   4. Add Dependecies in Package.xml
    #   5. Create Launch File
    #   6. Create Helper Functions

    def __init__(self):
        super().__init__('ServoDriver')
        self.get_logger().info('Clifford Servo Driver Online...')

        #Declare our Publishers & Subscribers
        self.clifford_joy_sub = self.create_subscription( Joy, 'joy', self.clifford_joystick_callback,10)
        #self.cmd_vel_sub = self.create_subscription(Twist, 'cmd_vel',self.cmd_vel_callback,10)

        #INIT OUR HARDWARE
        i2c = board.I2C() # Init the I2C bus interface
        pca = PCA9685(i2c) # Create an instance of PCA9685
        pca.frequency = 60 # Set the PWM frequency to 60Hz
        
        #INIT SERVO DUTY
        for i in range (12):
            pca.channels[i].duty_cycle = 0x7FFF #50% Duty Cycle

        #INIT CORRESPONDING CHANNELS
        self.front_left_shoulder = servo.Servo(pca.channels[12])
        self.front_left_arm = servo.Servo(pca.channels[13])
        self.front_left_wrist = servo.Servo(pca.channels[14])

        self.front_right_shoulder = servo.Servo(pca.channels[8])
        self.front_right_arm = servo.Servo(pca.channels[9])
        self.front_right_wrist = servo.Servo(pca.channels[10])

        self.back_left_shoulder = servo.Servo(pca.channels[4])
        self.back_left_arm = servo.Servo(pca.channels[5])
        self.back_left_wrist = servo.Servo(pca.channels[6])

        self.back_right_shoulder = servo.Servo(pca.channels[0])
        self.back_right_arm = servo.Servo(pca.channels[1])
        self.back_right_wrist = servo.Servo(pca.channels[2])



        #INIT PULSE PARAMETERS
        self.front_left_shoulder.set_pulse_width_range( 600,2400 )
        self.front_left_arm.set_pulse_width_range( 600,2400 )
        self.front_left_wrist.set_pulse_width_range( 600,2400 )

        self.front_right_shoulder.set_pulse_width_range( 600,2400 )
        self.front_right_arm.set_pulse_width_range( 600,2400 )
        self.front_right_wrist.set_pulse_width_range( 600,2400 )

        self.back_left_shoulder.set_pulse_width_range( 600,2400 )
        self.back_left_arm.set_pulse_width_range( 600,2400 )
        self.back_left_wrist.set_pulse_width_range( 600,2400 )

        self.back_right_shoulder.set_pulse_width_range( 600,2400 )
        self.back_right_arm.set_pulse_width_range( 600,2400 )
        self.back_right_wrist.set_pulse_width_range( 600,2400 )
        
        #INIT SERVO RELATIVE COODS + MISC
        self.universal_shoulder_len = 58.17
        self.universal_arm_len = 107.00
        self.universal_wrist_len = 130.43


        #INIT OUR SERVOS TO CORRECT POSITIONS
        #self.init_servos()

        #INIT OUR SERVO COORDINATE SYSTEM (TO DO)
        self.coordinates_front_left = [130.43,58.17,107.0]
        self.coordinates_front_right = [
            [59.43,58.17,150.0],
            [59.53,58.17,100.0],
            [109.53,58.17,100.0],
            [109.43,58.17,150.0],
            ]
        self.coordinates_back_left = [130.43,58.17,107.0]
        self.coordinates_back_right = [50.2,58.17,154]

        #FLAGS FOR CLIFFORD DIFFERENT MODES DIFFERENT MODES
        self.idle_mode = 0
        self.walk_mode = 1

        #DEFINE INDEXS AND TRACKING VARIABLES FOR WALK GAIT
        self.set1_walk_index = 0
        self.set2_walk_index = 0
        self.set1_target_index = 1
        self.set2_target_index = 1
       
        #TESTING VARIABLES FOR SINGLE LEG MOTION (07/24/24) / FRONT RIGHT
        self.speed_param = 2.0
        self.gait_walk_index = 0
        self.target_index = 1
        self.current_coords = [59.43,58.17,150.0]
 

    def clifford_joystick_callback(self, data):
        #self.get_logger().info('Clifford Joystick Callback')

        # X button condition
        if data.buttons[0] == 1:
            self.get_logger().info('X Pressed...')
            #self.walk_mode = 1
            #self.idle_mode = 0
          #  sleep(2)
            # self.front_right_arm.angle, self.front_right_wrist.angle = self.solve_ik_right([59.43,58.17,150.0])
            # self.get_logger().info(f'arm angle: {self.front_right_arm.angle}')
            # self.get_logger().info(f'wrist angle: {self.front_right_wrist.angle}')
            self.init_servos()

        # Circle button condition
        elif data.buttons[1] == 1:
            self.get_logger().info("Circle Pressed...")
           # sleep(2)
            # self.front_right_arm.angle, self.front_right_wrist.angle = self.solve_ik_right([59.43,58.17,100.0])
            # self.get_logger().info(f'arm angle: {self.front_right_arm.angle}')
            # self.get_logger().info(f'wrist angle: {self.front_right_wrist.angle}')

        #Triangle button condition
        elif data.buttons[2] == 1:
            self.get_logger().info("Triangle Pressed...")
            #sleep(2)
            # self.front_right_arm.angle, self.front_right_wrist.angle = self.solve_ik_right([109.43,58.17,100.0])
            # self.get_logger().info(f'arm angle: {self.front_right_arm.angle}')
            # self.get_logger().info(f'wrist angle: {self.front_right_wrist.angle}')
          
        # Square button condition
        elif data.buttons[3] == 1:
            self.get_logger().info('Sqaure Pressed...')
            # self.front_right_arm.angle, self.front_right_wrist.angle = self.solve_ik_right([109.43,58.17,150.0])
            # self.get_logger().info(f'arm angle: {self.front_right_arm.angle}')
            # self.get_logger().info(f'wrist angle: {self.front_right_wrist.angle}')

        if self.walk_mode:
            
            walk_speed = abs(data.axes[1]) * self.speed_param # define our walking speed
            forward = data.axes[1] >= 0 # Determine direction of movement
            self.get_logger().info(f'walk_speed: {walk_speed}')
            if self.gait_walk_index == 0:
                #WALKING GAIT POS 1
                new_z = self.current_coords[2] - walk_speed if forward else self.current_coords[2] + walk_speed

                if (forward and self.current_coords[2] >= self.coordinates_front_right[self.target_index][2]) or \
                (not forward and self.current_coords[2] <= self.coordinates_front_right[3][2]):
                    self.current_coords[2] = new_z
                    self.front_right_arm.angle,self.front_right_wrist.angle = self.solve_ik_right(self.current_coords)
                else:
                    self.get_logger().info('Transitioning gait index')
                    #CONDITIONS TO SWITCH OUR INDEXS AND TARGETS
                    self.gait_walk_index = 1 if forward else 3
                    self.target_index = 2 if forward else 0

            elif self.gait_walk_index == 1:
                # WALKING GAIT POS 2
                new_x = self.current_coords[0] + walk_speed if forward else self.current_coords[0] - walk_speed

                if (forward and self.current_coords[0] < self.coordinates_front_right[self.target_index][0]) or \
                (not forward and self.current_coords[0] > self.coordinates_front_right[0][0]):
                    self.current_coords[0] = new_x
                    self.front_right_arm.angle,self.front_right_wrist.angle = self.solve_ik_right(self.current_coords)    
                else:
                    self.get_logger().info('Transitioning gait index')
                    self.gait_walk_index = 2 if forward else 0
                    self.target_index = 3 if forward else 1
                    
            elif self.gait_walk_index == 2:
                # WALKING GAIT POS 3
                new_z = self.current_coords[2] + walk_speed if forward else self.current_coords[2] - walk_speed

                if (forward and self.current_coords[2] < self.coordinates_front_right[self.target_index][2]) or \
                (not forward and self.current_coords[2] > self.coordinates_front_right[1][2]):
                    self.current_coords[2] = new_z
                    self.front_right_arm.angle,self.front_right_wrist.angle = self.solve_ik_right(self.current_coords)
                else:
                    self.get_logger().info('Transitioning gait index')
                    self.gait_walk_index = 3 if forward else 1
                    self.target_index = 0 if forward else 2

            elif self.gait_walk_index == 3:
                # WALKING GAIT POS 4
                new_x = self.current_coords[0] - walk_speed if forward else self.current_coords[0] + walk_speed

                if (forward and self.current_coords[0] > self.coordinates_front_right[0][0]) or \
                (not forward and self.current_coords[0] < self.coordinates_front_right[2][0]):
                    self.current_coords[0] = new_x
                    self.front_right_arm.angle,self.front_right_wrist.angle = self.solve_ik_right(self.current_coords)
                  

                    self.get_logger().info('updated x coordinate')
                else:
                    self.get_logger().info('Transitioning gait index')
                    self.gait_walk_index = 0 if forward else 2
                    self.target_index = 1 if forward else 3

            else:
                self.get_logger().info("unexpected condition hit.")

                

    

    def cmd_vel_callback1(self, msg):
        
        #QUESTIONABLE SETUP FOR CHOOSING WHETHER TO LISTEN TO CMD_VEL TOPIC
        if not self.walk_mode:
            return 

        self.get_logger().info('Clifford Command Vel Topic')
        speed_factor = 1.0
        walk_speed = abs(msg.linear.x) * 1.0
        forward = msg.linear.x >= 0


        if self.set1_walk_index == 0 or self.set1_walk_index == 1 or self.set1_walk_index == 2:
            self.get_logger().info('SET1_WALK_INDEX: 0-2')
            #set1_new_z = self.current_coords_set1[2] - walk_speed if forward else self.current_coords_set1[2] + walk_speed
                

            if self.set1_walk_index == 0:
                self.get_logger().info('SET1 INDEX = 0')
                set1_new_z = self.current_coords_set1[2] - walk_speed if forward else self.current_coords_set1[2] + walk_speed
                set2_new_x = self.current_coords_set2[0] - (walk_speed / speed_factor) if forward else self.current_coords_set2[0] + walk_speed

                self.get_logger().info(f'SET2 NEWX: {set2_new_x}')
                self.get_logger().info(f'SET1 coordinate z {set1_new_z}')
                if forward and self.current_coords_set1[2] >= self.coordinates_front_right_set1[self.target_index_set1][2]:
                    
                   
                    self.get_logger().info('UPDATING COORDINATES SET1 AS MAIN')
                    #GET ALL SET1 INFO
                    #self.current_coords_set1 = [self.current_coords_set1[0], self.current_coords_set1[1],set1_new_z]
                    self.current_coords_set1[2] = set1_new_z
                    self.current_position_right_arm, self.current_position_right_wrist = self.solve_ik(self.current_coords_set1)
                    self.current_position_rear_left_arm, self.current_position_rear_left_wrist = -self.current_position_right_arm, -self.current_position_right_wrist

                    #GET ALL SET2 INFO
                    self.get_logger().info(f'CURRENT COORDS FOR SET2: {self.current_coords_set2}')
                    self.current_coords_set2[0] = set2_new_x
        
                    self.current_position_rear_right_arm, self.current_position_rear_right_wrist = self.solve_ik(self.current_coords_set2)
                    self.current_position_front_left_arm, self.current_position_front_left_wrist = -self.current_position_rear_right_arm, -self.current_position_rear_right_wrist

                else:
                    #ENTERING THIS CONDITION
                    self.get_logger().info('ELSE HIT')
                    self.get_logger().info(f'PIECE OF SHIT FAILING CONDITION {self.coordinates_set1[self.target_index_set1][2]:}')
                    self.set1_walk_index = 1
                    self.target_index_set1 = 2
            
            
    def zero_servos(self):
        self.get_logger().info("Helper Function: 'zero_servos' called")
        sleep(1)
        #self.servo0.angle = 0
        sleep(2)
        self.servo0.angle = 90
        self.servo1.angle = 90

    # Helper function to test servo range of motion (FINISH IMPLEMENTATION ONCE REST ARE INSTALLED).
    def init_servos(self): 
        self.get_logger().info("Helper Function: 'init_servos' called.")
        sleep(2.5)
        self.front_left_shoulder.angle = 100
        self.front_left_arm.angle = 145
        self.front_left_wrist.angle = 105
        sleep(2.5)
        self.front_right_shoulder.angle = 110
        self.front_right_arm.angle = 58
        self.front_right_wrist.angle = 95
        sleep(2.5)
        self.back_left_shoulder.angle = 100
        self.back_left_arm.angle = 140
        self.back_left_wrist.angle = 110
        sleep(2.5)
        self.back_right_shoulder.angle = 105
        self.back_right_arm.angle = 55
        self.back_right_wrist.angle = 95

    #DEFINE NEW FUNC RESET SERVOS

    def solve_ik_right(self,cords):
            # These kinematics calculations will try to be as descripitional as possible but please refer
            # to sheet of calculations by Cameron Bauman. 
            #UNIT: RADIANS & mm
            x_cord = cords[0] #x cord value
            y_cord = cords[1] #y cord value not really relevant rn.
            z_cord = cords[2] #z cord value

            #Find length B using Pythagorean's Theorem
            b_len = math.sqrt( pow(x_cord,2) + pow(z_cord,2) ) 
            
            # Angle of B
            beta_1 = math.atan(z_cord/x_cord)
            
            #Calculations for 'right_arm' and 'right_wrist' applied through cosine law.

            #This is the angle of which right_arm is set. This is necessary for calculating how the long will be SET
            beta_2 = math.acos( ( pow(self.universal_arm_len,2) + pow(b_len,2) - pow(self.universal_wrist_len,2) ) 
                                    / (2 * self.universal_arm_len * b_len) )

            #This is the angle of which right_wrist is set.
            beta_3 = math.acos( ( pow(self.universal_arm_len,2) + pow(self.universal_wrist_len,2) - pow(b_len,2) ) 
                                    / (2 * self.universal_arm_len * self.universal_wrist_len) )

            #Shouldn't be too relevant to calculations besides for RVIZ, but this is to make the calculations relative to their axes.
            theta_2 = math.pi - (beta_2 + beta_1)
            theta_3 = math.pi - beta_3

            #FINAL VALUE FOR RVIZ
            #theta_3 = (math.pi/2) - theta_3 #Final value of right_wrist
            #theta_3 = (math.pi/2) + beta_3

            theta_2 = theta_2 * (180/math.pi)
            theta_3 = theta_3 * (180/math.pi)

            return [theta_2,theta_3]

    def solve_ik_left(self,cords):
            # These kinematics calculations will try to be as descripitional as possible but please refer
            # to sheet of calculations by Cameron Bauman. 
            #UNIT: RADIANS & mm
            x_cord = cords[0] #x cord value
            y_cord = cords[1] #y cord value not really relevant rn.
            z_cord = cords[2] #z cord value

            #Find length B using Pythagorean's Theorem
            b_len = math.sqrt( pow(x_cord,2) + pow(z_cord,2) ) 
            
            # Angle of B
            beta_1 = math.atan(z_cord/x_cord)
            
            #Calculations for 'right_arm' and 'right_wrist' applied through cosine law.

            #This is the angle of which right_arm is set. This is necessary for calculating how the long will be SET
            beta_2 = math.acos( ( pow(self.universal_arm_len,2) + pow(b_len,2) - pow(self.universal_wrist_len,2) ) 
                                    / (2 * self.universal_arm_len * b_len) )

            #This is the angle of which right_wrist is set.
            beta_3 = math.acos( ( pow(self.universal_arm_len,2) + pow(self.universal_wrist_len,2) - pow(b_len,2) ) 
                                    / (2 * self.universal_arm_len * self.universal_wrist_len) )
            
            #Shouldn't be too relevant to calculations besides for RVIZ, but this is to make the calculations relative to their axes.
            theta_2 = beta_2 + beta_1
            theta_3 = beta_3

            theta_2 = theta_2 * (180/math.pi)
            theta_3 = theta_3 * (180/math.pi)

            self.get_logger().info(f"THETA_3 before math corrects: {theta_3}")
            
            return [theta_2,theta_3]

    def solve_pitch(self, coords):
        self.get_logger().info(f"SOLVE FOR PITCH")

        C_value = math.sqrt( ( math.pow(coords[2],2) ) + math.pow(coords[1],2) )
        D_value = math.sqrt( math.pow(C_value,2) - math.pow(self.universal_shoulder_len,2) )
        self.get_logger().info(f"D VALUE: {D_value}")
            

        alpha = math.atan( coords[1] / coords[2] )
        beta = math.atan( D_value / self.universal_shoulder_len )
            
        self.get_logger().info(f"ALPHA VALUE: {alpha}")
        self.get_logger().info(f"BETA VALUE: {beta}")

        omega = alpha + beta
        theta_1 = math.pi - omega
        self.get_logger().info(f"OMEGA VALUE: {omega}")


        #define right shoulder = omega
        coords[2] = D_value #update only the z value

        theta_2,theta_3 = self.solve_ik_left(coords) #returned values of just z updated   
        theta_1 = theta_1 * (180/math.pi)

        return [theta_1,theta_2,theta_3]
 

def main(args=None):
    rclpy.init(args=args)
    driveServos = ServoDriver()
    rclpy.spin(driveServos)
    driveServos.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Circuit Preview

\$\endgroup\$
4
  • 2
    \$\begingroup\$ This seems like a simple software issue, but you haven't told us what libraries you are using. Perhasl the Servo() function initialises the position of the servo? Have you checked where in the code they suddenly move? \$\endgroup\$ Commented Jul 20 at 14:52
  • \$\begingroup\$ @TomCarpenter, I've added the libraries let me know what you think. \$\endgroup\$
    – msalaz03
    Commented Jul 21 at 15:46
  • \$\begingroup\$ But we only have your init function. That's not where your application runs - isn't there a main function as well? \$\endgroup\$
    – MrGerber
    Commented Jul 25 at 19:35
  • \$\begingroup\$ @MrGerber, I will add everything then, do you think I need to do deinit the chip? Cause in order to cancel the program I do ctrl + c in terminal. \$\endgroup\$
    – msalaz03
    Commented Jul 26 at 3:03

1 Answer 1

0
\$\begingroup\$

For future users of the PCA9685: The issue was not with the code. After repeated testing, I noticed a specific servo exhibited jittery behavior, with its position fluctuating several degrees. I don't fully understand the circuitry of the PCA9685 (you can look it up if you want), but the problem was resolved once the servo was replaced.

Hope this helps, if anyone can elaborate please do.

\$\endgroup\$
4
  • \$\begingroup\$ Servos take a lot of supply current when starting to run, and while jittering continuously. Make sure your power supply is able to deliver enough current and use enough decoupling capacitance. Failing to do so leads to the observed effect. \$\endgroup\$ Commented Aug 2 at 5:42
  • \$\begingroup\$ Appreciate the response @thebusybee. The power supply is definitely not an issue this battery packs of punch with a 7400mAh rating lol. \$\endgroup\$
    – msalaz03
    Commented Aug 5 at 16:42
  • \$\begingroup\$ The capacity of a battery says nothing about its ability to deliver current. Your supply design could be flawed and the "misbehaving" servo just triggered that flaw. \$\endgroup\$ Commented Aug 5 at 18:34
  • \$\begingroup\$ @thebusybee very true but regardless the issue stopped once the servo was replaced. \$\endgroup\$
    – msalaz03
    Commented Aug 7 at 22:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.