4-bar linkages are commonly found in a wide variety of mechanical engineering applications, with lifts being one of them. Compared to the more simplistic single-stage 4-bar and 6-bar lifts, the double reverse 4-bar (DR4B) offers some advantages: Unlike a 4- or 6-bar lift, which shifts the robot's centre of mass as it lifts an object up and forward, a DR4B lifts the object almost exclusively up and down, with little horizontal translation. The end result is that the robot can drive while lifting an object without wobbling. In addition, the compact double-stage design allows the lift to extend to greater heights than a single-stage lift without taking up any more space.
A DR4B is essentially two 4-bar lifts connected via a 1:1 ratio gearing at the centre link, where one of the stages is reversed in terms of the direction it's facing. The motors are connected to the gears at the centre link where the two stages meet and provide the power needed to actuate the lift. The motors are connected to the centre link at a gear ratio of 12:60, which produces a stall torque of 2.07 Nm each.
Rubber bands are used on both stages of the lift to relieve strain on the motors. When the lift is in the lowest position, the rubber bands are under maximum stress which creates a squeezing force on the lift to encourage it to lift upward. As the lift extends, the effect of the rubber bands decreases as they are under less stress, and the load is transferred to the motors. The rubber bands have a big contribution to the lift capacity because it has maximum effect during the most energy-intensive part of the lifting evolution: when the object is being initially lifted off the ground. The rubber bands also act as a method of redundancy in case the motors fail while the lift is extended by serving as a cushion to the mass that will inevitably come crashing down due to the sudden loss of power.
The drive base of the robot makes use of two omni wheels at the rear and two traction wheels at the front. This combination of wheels allows the robot to spin easily on the spot while allowing the robot to maintain a position without accidentally drifting laterally due to the uncontrolled caster wheels. The traction wheels are intentionally placed at the front such that when the robot rotates on the spot, the centre of rotation is at the front, near the end effector, which mitigates any centripetal forces on the object that may cause instability when rotating.
Since this project was an exercise in implementing a DR4B lift, a rather simple end effector was used. It is a claw-type mechanism that closes to grip the object and opens to release it.
The programming of this robot made use of VEXcode PID-enabled motor functions. This enables the lift to maintain any desired position as controlled by the user, regardless of how heavy the object is or if the weight changes while in operation. The speed at which the motors were operated was limited to 25% to maintain stability and allow finer adjustments to the lift position.
The robot screen displays the current position of the lift in degrees ranging from 0° (unextended) to 400° (fully extended). The reading on the screen should only be used for loose reference as the function call is set to a moderate refresh rate to save on processor resources.
As for control of the drive base, the robot uses a tank drive setup, where the left joystick on the VEX IQ Controller was used to control the left wheels, and the right joystick was used to control the right wheels. This choice was mostly due to personal preference. An arcade drive setup where one joystick is used for forward/backward motion, and the other for rotating on the spot, is also suitable.
Some issues I encountered while building this robot include:
Trouble aligning the 1:1 gears on both sides of the two stages, causing lopsided lifting evolutions. This was solved by carefully viewing the lifting evolution from multiple angles and incrementing stage alignment by one tooth at a time to observe the difference. You may observe this in one of the videos below, where the lift is visibly tilted to one side when lifting the jar of almonds.
Inability to achieve maximum lift height due to the wire servicing the end effector not being long enough. Since the longest wire in my VEX IQ toolboxes was 600 mm long, my only option was to flip the end effector assembly upside-down such that the motor was located at a lower height (as seen in all pictures and videos). Prior to this change, the motor was placed facing upward.
Both sides of the lift in early stages of assembly
#include "vex.h"
using namespace vex;
const float DEADBAND = 5.0;
const unsigned int MAX_LIFT_HEIGHT = 400;
void joystickControl();
void onButtonRUpPress();
void onButtonRDownPress();
void onButtonFUpPress();
void displayData();
////////////////////////////////////////////////////////////////
int main() {
// Register event handlers and pass callback functions
Controller.ButtonRUp.pressed(onButtonRUpPress);
Controller.ButtonRDown.pressed(onButtonRDownPress);
Controller.ButtonFUp.pressed(onButtonFUpPress);
// Set default motor stopping behavior and velocity
leftMotor.setVelocity(100, percent);
rightMotor.setVelocity(100, percent);
liftMotors.setStopping(hold);
liftMotors.setVelocity(25, percent);
liftMotors.setMaxTorque(100, percent);
clawMotor.setVelocity(100, percent);
clawMotor.setStopping(hold);
joystickControl();
}
////////////////////////////////////////////////////////////////
// Callback function when Controller ButtonRUp is pressed
void onButtonRUpPress() {
while (Controller.ButtonRUp.pressing() && liftMotors.position(degrees) < MAX_LIFT_HEIGHT) {
liftMotors.spin(forward);
displayData();
}
liftMotors.stop();
}
// Callback function when Controller ButtonRDown is pressed
void onButtonRDownPress() {
while (Controller.ButtonRDown.pressing() && liftMotors.position(degrees) > 0) {
liftMotors.spin(reverse);
displayData();
}
liftMotors.stop();
}
void onButtonFUpPress(){
while (Controller.ButtonFUp.pressing() && fabs(clawMotor.position(degrees)) > 20) {
clawMotor.spin(forward);
wait(250, msec);
}
while (Controller.ButtonFUp.pressing() && fabs(clawMotor.position(degrees)) < 20)
clawMotor.spin(reverse);
wait(250, msec);
clawMotor.stop();
}
void joystickControl(){
// Main controller loop to set motor velocities to controller axis positions
while (true) {
if (fabs(static_cast<float>(Controller.AxisA.position())) > DEADBAND) {
leftMotor.setVelocity(Controller.AxisA.position(), percent);
} else {
leftMotor.setVelocity(0, percent);
}
if (fabs(static_cast<float>(Controller.AxisD.position())) > DEADBAND) {
rightMotor.setVelocity(Controller.AxisD.position(), percent);
} else {
rightMotor.setVelocity(0, percent);
}
leftMotor.spin(forward);
rightMotor.spin(forward);
wait(20, msec);
}
}
void displayData(){
Brain.Screen.clearScreen();
Brain.Screen.setCursor(1, 1);
Brain.Screen.print("Lift pos: %.2f", liftMotors.position(degrees));
//printf("\nLift position: %.2f", liftMotors.position(degrees)); printf("°");
wait(20, msec);
}