This repository contains all the necessary documentation and code base for the Armbot-Junior Demobots project.
Armbot-Junior is a miniature 6DOF (Degrees of Freedom) robotic arm project. It is primarily designed to serve as a test bed for the main Armbot project and as an educational tool. This project uses DFRobot's Servos with an internal analog feedback potentiometer, enabling the user to get real-time position data of the robot arm.
Download the Arduino IDE either standalone or as a VSCode extension (or use platformIO if you're crazy). Clone the repository and open the project in the IDE. Be sure to install the Adafruit PWM, (Adafruit_PWMServoDriver.h), library from the Arduino Library Manager or elsewhere
-
ArmbotJr_DFR_servos_PWM_Shield.ino: The current working file. It's similar toArmbotJr_DFR_servosbut is designed for use with an Adafruit 16-Channel 12-bit PWM/Servo Driver Shield or a similar I2C device. -
ArmbotJr_DFR_Functions.h: Header file, includes the needed libraries, contains theServoConfigstruct, thePosestruct, global variables and function prototypes for theArmbotJr_DFR_servos_PWM_Shield.inoprogram. -
ArmbotJr_DFR_Functions.cpp: Source file, contains the function definitions for theArmbotJr_DFR_servos_PWM_Shield.inoprogram.
This Arduino Sketch was originally in a single file, but was split into a "main" .ino file, a header file, and a source file to make it easier to read and understand.
ArmbotJr_DFR_servos_PWM_Shield.ino Explained
The ArmbotJr_DFR_servos_PWM_Shield program works as follows:
A struct named ServoConfig is defined to hold the configuration details of each servo such as name, PWM pin number, feedback pin number, a minimum and maximum degree value, and a minimum and maximum feedback value (for use in calibration). A constructor is also defined which accepts these parameters to create an object of the struct.
(A constructor is a special kind of function that gets called automatically when an object of the struct is created. Its main purpose is to assign initial values to the data members of the new object. It helps ensure that objects are valid as soon as they're created and simplifies code since initialization details are kept within the struct. Honestly this shouldnt really be done with a struct, I just kind of suck using Classes properly, add that to the TODO list lol)
const int mode: A constant that determines the operating mode of the program. A value of0runs custom test code, while a value of1enables a "pseudo-multitasking" mode for running a sequence of poses.ServoConfig Base: AServoConfigobject that holds the configuration details of the base servo.ServoConfig J1: AServoConfigobject that holds the configuration details of the J1 servo.ServoConfig J2: AServoConfigobject that holds the configuration details of the J2 servo.ServoConfig J3: AServoConfigobject that holds the configuration details of the J3 servo.ServoConfig J4: AServoConfigobject that holds the configuration details of the J4 servo.ServoConfig allJoints[]: An array ofServoConfigobjects, each representing a joint in the robotic arm.const int numJoints: Specifies the total number of joints, which is 5 in this case.Pose poseSequence[]: An array ofPoseobjects, defining a sequence of poses to be executed.const int numPoses: The total number of poses inposeSequence.
The setup function performs the following tasks:
- Initializes serial communication with a baud rate of 9600.
- Starts the Adafruit PWM servo driver and sets its frequency to 60Hz.
- Locks all the motors in their current positions using
moveTo. - Calibrates all servos using the
calibratefunction.
The loop function is divided into two primary sections, controlled by the mode variable:
- Task 1 - Pose Execution :
- Uses the
millisfunction to keep track of elapsed time. - Executes a pose from
poseSequenceeveryintervalPosemilliseconds. - Moves the servos to the positions specified in the next pose.
- Cycles through the
poseSequencearray.
- Task 2 - Print Joint Positions :
- Prints the current positions of all joints at intervals specified by
intervalPrint.
- This is where you can add your own custom test code.
- The example code given moves joint J4 to -90 degrees and closes the claw, waits for 3 seconds, then moves J4 to 90 degrees and opens the claw, followed by another 3-second wait.
- Mode 1 : Intended for "pseudo-multitasking" by executing a sequence of poses while continuously printing the joint positions.
- Mode 0 : Intended for running custom test code.
The ServoConfig struct serves as a configuration container for each servo in the robotic arm. It holds various parameters and states for a given servo:
name: A string to hold the name of the servo.PWM_Channel: An integer representing the PWM pin number to which the servo is connected.Feedback_Pin: An integer representing the pin number that receives feedback from the servo.minDegreeandmaxDegree: Integers that specify the minimum and maximum limits of the servo in degrees.jointMinDegreeandjointMaxDegree: Integers that specify the minimum and maximum limits of the joint associated with the servo, in "joint space" (angles centered at zero, instead of 0 to 270 degrees, its -135 to +135 degrees, etc).minFeedbackandmaxFeedback: Integers that specify the minimum and maximum feedback values that can be received from the servo. These get added in thecalibrate()function.defaultPos: An integer that holds the default or "safe" position of the joint.
The struct also includes a constructor that initializes these fields.
At the top of the main code (.ino file), a ServoConfig object may be configured with:
ServoConfig J2("J2", 2, A2, 5, 270, -135, 135, 0);
Where "J2" is the joint's name, 2 is the PWM Channel, corresponding to where the servo cable is plugged into on the PWM Shield, A2 is the analog input pin connected to servo's white feedback signal wire, 5, 270 are the minimum and maximum rotation/angle the J2 joint can physically move, and -135, 135 are the minimum and maximum rotation/angle the J2 joint can move in "joint space" (centered at zero, instead of 0 to 270 degrees, its -135 to +135 degrees, etc). 0 is the default position of the joint (straight upwards) in this example.
You can configure the servo like this at the start of the code, or within the setup() function.
After configuring, you'll now be able to "pass in" J2 to functions like calibrate(), moveTo(), and getPos(), etc.
The Pose struct is used for holding the states of all joints in a particular pose. It contains:
jointStates: An array of integers, where each index corresponds to a joint state in joint space. The last index (jointStates[5]) is used to indicate the state of the claw (open or closed).
The struct provides two constructors:
- A default constructor that initializes all joint states to zero.
- A parameterized constructor that allows setting of each joint state and the claw state (open or closed).
At the top of the main code (.ino file), or in the setup(), a Pose object may be configured with:
Pose test_pose = Pose(0, -45, 90, -90, 90, open);
Where 0, -45, 90, -90, 90 are the joint states in joint space, and closed is the claw state (open or closed).
later on, in the loop() function or elsewhere, you can then do:
moveTo(Base, test_pose.jointStates[0]);
moveTo(J1, test_pose.jointStates[1]);
moveTo(J2, test_pose.jointStates[2]);
moveTo(J3, test_pose.jointStates[3]);
moveTo(J4, test_pose.jointStates[4]);
BinaryClaw(test_pose.jointStates[5]);
This will move all the joints to the positions specified in the test_pose object.
The calibrate function is responsible for calibrating a servo motor based on its configuration. Calibration involves finding the minimum and maximum feedback values and storing them in the ServoConfig object. It also moves the servo to its default position at the end of the process.
config: A reference to aServoConfigstruct that contains the servo's configuration.
- Initialization : It starts by printing the name of the servo being calibrated.
- Pulse Length Calculation : It calculates the pulse lengths (
pulseLen_minandpulseLen_max) corresponding to the servo's minimum and maximum angles. These calculations use the global constantsDFR_minandDFR_max. - Minimum Feedback Reading :
- The servo is first moved to its minimum angle using
pwm.setPWM(). - After a delay, the minimum feedback value is read using
analogRead()and stored in theServoConfigobject.
- Maximum Feedback Reading :
- The servo is then moved to its maximum angle.
- For joint "J1", it moves slowly to its max position for safety reasons.
- Again, after a delay, the maximum feedback value is read and stored in the
ServoConfigobject.
- Default Position : Finally, the servo is moved to its default position using the
moveTofunction. If the movement fails, an error message is printed. - Return : The function then returns, signaling the end of the calibration process.
Its best to do this in the setup() function:
calibrate(Base);
calibrate(J1);
calibrate(J2);
//etc...This function translates a given servo angle to its corresponding angle in joint space.
servo_angle: The angle of the servo in servo space.config: A constant reference to aServoConfigstruct containing the servo's configuration. Since each joint has its own limits, this parameter is used to determine the joint space limits of the servo.
The function uses the Arduino map function to translate the servo angle to a joint space angle based on the minimum and maximum degree limits defined in ServoConfig.
int joint_angle = servoToJoint(90, J1);This function performs the inverse operation of servoToJoint; it translates a given joint angle to its corresponding angle in servo space.
joint_angle: The angle of the joint in joint space.config: A constant reference to aServoConfigstruct containing the servo's configuration.
Similar to servoToJoint, it also uses the map function but reverses the mapping to translate from joint space to servo space.
int servo_angle = jointToServo(-45, J1);This function retrieves the current position of a servo in terms of joint space.
config: A constant reference to aServoConfigstruct containing the servo's configuration.
- It first reads the current feedback from the servo using
analogRead. - Then it maps this feedback to a servo angle based on the minimum and maximum feedback values defined in
ServoConfig. - Finally, it uses
servoToJointto convert this servo angle to joint space.
int current_joint_pos = getJointPos(J1);The moveTo function is designed to move a servo to a specific position in joint space. It checks for boundary conditions, calculates the required pulse length, and attempts to move the servo.
config: A reference to aServoConfigstruct containing the servo's configuration.joint_goal: The desired joint position in joint space.
- Servo Goal Calculation : It first translates the joint goal to servo space using
jointToServo. - Boundary Check : Checks if the calculated servo goal is within the min and max limits defined in
ServoConfig. If not, it prints an "Out of bounds!!!" error and returnsfalse. - Pulse Length Calculation : It calculates the pulse length (
pulseLen) required to move the servo to the desired position. - Servo Movement : Uses
pwm.setPWM()to move the servo to the desired position. - Position Verification (Commented Out) : The function contains commented-out code for verifying if the servo has reached the desired position. This part is currently not implemented, which is mentioned in the comment.
- Return : Finally, the function returns
true, indicating that the command to move the servo was issued.
// Using the returned boolean value to check for success
bool success = moveTo(myServoConfig, -45);
if (!success) {
Serial.println("Failed to move servo.");
}
// Without using the returned boolean value, simply issuing the command
moveTo(myServoConfig, -45);The BinaryClaw function controls the state of the claw, setting it to either open or closed based on the desired_claw_state parameter. This function assumes that the claw's servo is connected to channel 5.
desired_claw_state: An integer that indicates the desired state of the claw. The constantclosedrepresents a closed state, whileopenrepresents an open state.
- Pulse Lengths : The function has hardcoded pulse lengths (
close_PLandopen_PL), which were derived from testing, to open and close the claw. - State Check : Although commented out, there's a provision to check whether the claw is already in the desired state.
- Claw Movement :
- If
desired_claw_stateisclosed, the function usespwm.setPWM()to move the claw to the closed position. - If
desired_claw_stateisopen, it moves the claw to the open position.
- Logging : The function prints the action it is taking ("Moving Claw to Closed/Open").
- Return : Finally, the function returns
trueif it successfully issues the command to move the claw. Otherwise, it returnsfalse.
// Using the returned boolean value to check for success
bool success = BinaryClaw(closed);
if (!success) {
Serial.println("Failed to move claw.");
}
// Simply issuing the command without checking for success
BinaryClaw(open);The printPos function prints the current position of a specified servo in joint space.
config: A constant reference to aServoConfigstruct containing the servo's configuration.
- It prints the name of the servo followed by its current position in degrees, fetched using the
getJointPosfunction.
printPos(J1); // Outputs something like "J1: -45 deg"The printAllJointPos function prints the current positions of all servos in an array in joint space.
configs: An array ofServoConfigstructs, each containing the configuration for one servo.numJoints: The number of servos (or joints) in the array.
- It iterates through the
configsarray and prints the name of each servo along with its current position in joint space. - The function also includes formatting to align the output neatly.
ServoConfig allJoints[] = {Base, J1, J2, J3, J4};
printAllJointPos(allJoints, 5);
// Outputs the current positions of all servos in the allJoints[] array