Designing a PLC to automate the Big Arm walking
We have the scale model built by Arik
Done
- Labeled joints to correspond with SRL's Big Arm
- Identified which Arduino pin controls which linear actuator
- Tested that the linear actuator is strong enough to lift up the base (it is!)
Arduino pins to joint mapping (with the white tape on the 4 position header socket that connects to the 4 pins on the H-bridge facing away from the H-bridge)
- Wrist: Pins 4 and 5
- Elbow: Pins 2 and 3
- Shoulder: Pins 8 and 9
- Pins 6 and 7 are wired to a channel of the H-Bridge but are unused at this time; could be used for rotating or the stabilizing side legs
Sample code to make the shoulder move (either retract or extend)
void setup() {
pinMode (8, OUTPUT);
pinMode (9, OUTPUT);
// To reverse the direction, change the HIGH to LOW and LOW to HIGH
digitalWrite (8, LOW);
digitalWrite (9, HIGH);
}
- Note that this will not stop since there is no command to stop the motors
- Note that if the actuator is already fully extended or retracted, an internal switch prevents it from further movement and damage
Next steps:
- Schedule with Tony to get trained on the MIG welder, the horizontal band saw, and the metal drill press
- Unscrew the wooden base with all the electronics
- Undo the large bolt under the arm
- Coordinate with me to remove the arm. This is a two person job to be done safely.
- Take the base down to the Scene Shop and cut off the unused portion (horizontal band saw, I think)
- Drill holes for the casters
- Drill holes on the sides in case we decided to add the stabilizing side legs
- Should we add the rotating part at this time?
Created code that activates each limb of the arm indivudually
Uses three helper functions:
- extendActuator(): extends an actuator by making IN1 LOW and IN2 HIGH
- retractActuator(): retracts by making IN1 HIGH and IN2 LOW
- stopActuator(): stops motion by setting both lines LOW
In the setup() section:
- All actuator pins are configured as OUTPUT
- All joints that are moving now stops
- Initialise serial communication at 9600 baud and print usage instructions for the user
Controls:
- "sc" contracts the shoulder for 1 second
- "ec" contracts the elbow for 1 second
- "wc" contracts the wrist for 1 second
- "se" extends the shoulder for 1 second
- "ee" extends the elbow for 1 second
- "we" extends the wrist for 1 second
Final Points:
- Each command prints feedback, activates the joint for exactly one second, then stops it
- Invalid or unknown commands prompt a help message with valid options
- This arrangement lets me test each joint’s extend/contract movement and provides immediate feedback, ensuring manual control over each actuator from the serial monitor
Code
const int SHOULDER_IN1 = 8;
const int SHOULDER_IN2 = 9;
const int ELBOW_IN1 = 2;
const int ELBOW_IN2 = 3;
const int WRIST_IN1 = 4;
const int WRIST_IN2 = 5;
void stopActuator(int in1, int in2) {
digitalWrite(in1, LOW);
digitalWrite(in2, LOW);
}
// Using your polarity swap: extend = IN1 LOW, IN2 HIGH
void extendActuator(int in1, int in2) {
digitalWrite(in1, LOW);
digitalWrite(in2, HIGH);
}
// retract = IN1 HIGH, IN2 LOW
void retractActuator(int in1, int in2) {
digitalWrite(in1, HIGH);
digitalWrite(in2, LOW);
}
void setup() {
pinMode(SHOULDER_IN1, OUTPUT);
pinMode(SHOULDER_IN2, OUTPUT);
pinMode(ELBOW_IN1, OUTPUT);
pinMode(ELBOW_IN2, OUTPUT);
pinMode(WRIST_IN1, OUTPUT);
pinMode(WRIST_IN2, OUTPUT);
stopActuator(SHOULDER_IN1, SHOULDER_IN2);
stopActuator(ELBOW_IN1, ELBOW_IN2);
stopActuator(WRIST_IN1, WRIST_IN2);
Serial.begin(9600);
Serial.println("Type commands to move actuators:");
Serial.println("'sc', 'ec', 'wc' to contract shoulder, elbow, wrist");
Serial.println("'se', 'ee', 'we' to extend shoulder, elbow, wrist");
}
void loop() {
if (Serial.available() > 0) {
String command = Serial.readStringUntil('\n');
command.trim();
if (command == "sc") {
Serial.println("Contracting shoulder for 1 second...");
retractActuator(SHOULDER_IN1, SHOULDER_IN2);
delay(1000);
stopActuator(SHOULDER_IN1, SHOULDER_IN2);
}
else if (command == "ec") {
Serial.println("Contracting elbow for 1 second...");
retractActuator(ELBOW_IN1, ELBOW_IN2);
delay(1000);
stopActuator(ELBOW_IN1, ELBOW_IN2);
}
else if (command == "wc") {
Serial.println("Contracting wrist for 1 second...");
retractActuator(WRIST_IN1, WRIST_IN2);
delay(1000);
stopActuator(WRIST_IN1, WRIST_IN2);
}
else if (command == "se") {
Serial.println("Extending shoulder for 1 second...");
extendActuator(SHOULDER_IN1, SHOULDER_IN2);
delay(1000);
stopActuator(SHOULDER_IN1, SHOULDER_IN2);
}
else if (command == "ee") {
Serial.println("Extending elbow for 1 second...");
extendActuator(ELBOW_IN1, ELBOW_IN2);
delay(1000);
stopActuator(ELBOW_IN1, ELBOW_IN2);
}
else if (command == "we") {
Serial.println("Extending wrist for 1 second...");
extendActuator(WRIST_IN1, WRIST_IN2);
delay(1000);
stopActuator(WRIST_IN1, WRIST_IN2);
}
else {
Serial.println("Unknown command. Valid commands: sc, ec, wc, se, ee, we");
}
}
}
Identified Possible Locations For Complete R.O.M. Sensors
- Used cardboard pieces to mark possible locations of sensors and the manners in which they should be marked
Scheduled with Tony to get trained on the MIG welder, the horizontal band saw, and the metal drill press
- Should be completed by Monday 12th
To Do's
-
Unscrew the wooden base with all the electronics
-
Drill holes for the casters
-
Drill holes on the sides in case we decided to add the stabilizing side legs
-
Undo the large bolt under the arm
-
Coordinate with Prof to remove the arm. This is a two person job to be done safely.
-
Should we add the rotating part at this time?
Improved Previous Code iteration
Input Flexibility:
- Original: Accepts one command at a time with fixed 1-second duration.
- Improved: Accepts multiple commands with optional custom durations in a single input line.
Simultaneous Actuator Control:
- Original: Controls only one actuator per command.
- Improved: Allows several actuators to move simultaneously with independently timed durations.
Command Parsing:
- Original: Simple if-else matching on single command strings.
- Improved: Tokenizes input, supports dynamic command-duration pairs, and uses default duration when individual durations aren’t provided.
Timing Management:
- Original: Uses blocking delay() calls for fixed durations.
- Improved: Uses non-blocking timing logic with millis() allowing concurrent actuator timing and more fluid control.
Data Structure Usage:
- Original: No structured storage for actuator states or durations.
- Improved: Uses ActuatorControl structs to maintain clear, scalable actuator states.
User Feedback:
- Original: Prints simple, fixed messages per command.
- Improved: Prints detailed messages including actuator, direction, and duration.
Scalability and Readability:
- Original: Longer, repetitive code paths per command.
- Improved: Cleaner, modular code easier to extend to new commands or actuators.
Sequence Capability:
- Original: No built-in support for executing command sequences.
- Improved: Supports special commands like "cycle 1" to perform complex multi-step sequences with precise timing.
Code
const int SHOULDER_IN1 = 8;
const int SHOULDER_IN2 = 9;
const int ELBOW_IN1 = 2;
const int ELBOW_IN2 = 3;
const int WRIST_IN1 = 4;
const int WRIST_IN2 = 5;
struct ActuatorControl {
bool extend = false;
bool contract = false;
unsigned long duration = 0; // duration in milliseconds
unsigned long stopTime = 0;
};
void stopActuator(int in1, int in2) {
digitalWrite(in1, LOW);
digitalWrite(in2, LOW);
}
void extendActuator(int in1, int in2) {
digitalWrite(in1, LOW);
digitalWrite(in2, HIGH);
}
void retractActuator(int in1, int in2) {
digitalWrite(in1, HIGH);
digitalWrite(in2, LOW);
}
ActuatorControl shoulder, elbow, wrist;
void setup() {
pinMode(SHOULDER_IN1, OUTPUT);
pinMode(SHOULDER_IN2, OUTPUT);
pinMode(ELBOW_IN1, OUTPUT);
pinMode(ELBOW_IN2, OUTPUT);
pinMode(WRIST_IN1, OUTPUT);
pinMode(WRIST_IN2, OUTPUT);
stopActuator(SHOULDER_IN1, SHOULDER_IN2);
stopActuator(ELBOW_IN1, ELBOW_IN2);
stopActuator(WRIST_IN1, WRIST_IN2);
Serial.begin(9600);
Serial.println("Enter commands with optional individual durations:");
Serial.println("Examples:");
Serial.println(" sc ec wc 10 (all contract for 10s)");
Serial.println(" sc 10 ec 5 wc 2 (each different duration)");
}
void loop() {
unsigned long currentTime = 0;
if (Serial.available() > 0) {
String input = Serial.readStringUntil('\n');
input.trim();
// Reset actuator states before parsing
shoulder = ActuatorControl();
elbow = ActuatorControl();
wrist = ActuatorControl();
// Tokenize input based on spaces
const int maxTokens = 30;
String tokens[maxTokens];
int tokenCount = 0;
int fromIdx = 0;
while (fromIdx < input.length() && tokenCount < maxTokens) {
int sp = input.indexOf(' ', fromIdx);
if (sp == -1) sp = input.length();
tokens[tokenCount++] = input.substring(fromIdx, sp);
fromIdx = sp + 1;
}
// Determine default duration if last token is a positive number
unsigned long defaultDuration = 0;
if (tokenCount > 0) {
int lastVal = tokens[tokenCount - 1].toInt();
if (lastVal > 0) {
defaultDuration = (unsigned long) lastVal * 1000UL;
tokenCount--; // Exclude default duration token from command parsing
}
}
// Parse commands and optional durations
for (int i = 0; i < tokenCount; i++) {
String cmd = tokens[i];
cmd.trim();
unsigned long dur = 0;
// Peek next token for optional individual duration
if (i + 1 < tokenCount) {
int val = tokens[i + 1].toInt();
if (val > 0) {
dur = (unsigned long)val * 1000UL;
i++; // Skip duration token
} else {
dur = defaultDuration;
}
} else {
dur = defaultDuration;
}
if (dur == 0) {
Serial.print("Duration missing or zero for command ");
Serial.println(cmd);
continue;
}
if (cmd == "se") {
shoulder.extend = true;
shoulder.duration = dur;
} else if (cmd == "sc") {
shoulder.contract = true;
shoulder.duration = dur;
} else if (cmd == "ee") {
elbow.extend = true;
elbow.duration = dur;
} else if (cmd == "ec") {
elbow.contract = true;
elbow.duration = dur;
} else if (cmd == "we") {
wrist.extend = true;
wrist.duration = dur;
} else if (cmd == "wc") {
wrist.contract = true;
wrist.duration = dur;
} else if (cmd.length() > 0) {
Serial.print("Unknown command: ");
Serial.println(cmd);
}
}
// Start all actuators simultaneously
unsigned long startTime = millis();
if (shoulder.extend || shoulder.contract) {
Serial.print("Shoulder ");
Serial.print(shoulder.extend ? "extend" : "contract");
Serial.print(" for ");
Serial.print(shoulder.duration / 1000);
Serial.println(" seconds");
shoulder.stopTime = startTime + shoulder.duration;
if (shoulder.extend) extendActuator(SHOULDER_IN1, SHOULDER_IN2);
else retractActuator(SHOULDER_IN1, SHOULDER_IN2);
}
if (elbow.extend || elbow.contract) {
Serial.print("Elbow ");
Serial.print(elbow.extend ? "extend" : "contract");
Serial.print(" for ");
Serial.print(elbow.duration / 1000);
Serial.println(" seconds");
elbow.stopTime = startTime + elbow.duration;
if (elbow.extend) extendActuator(ELBOW_IN1, ELBOW_IN2);
else retractActuator(ELBOW_IN1, ELBOW_IN2);
}
if (wrist.extend || wrist.contract) {
Serial.print("Wrist ");
Serial.print(wrist.extend ? "extend" : "contract");
Serial.print(" for ");
Serial.print(wrist.duration / 1000);
Serial.println(" seconds");
wrist.stopTime = startTime + wrist.duration;
if (wrist.extend) extendActuator(WRIST_IN1, WRIST_IN2);
else retractActuator(WRIST_IN1, WRIST_IN2);
}
// Actuator control loop: stop them individually when their time elapses
while (true) {
currentTime = millis();
bool allDone = true;
if ((shoulder.extend || shoulder.contract) && currentTime >= shoulder.stopTime) {
stopActuator(SHOULDER_IN1, SHOULDER_IN2);
shoulder = ActuatorControl();
} else if (shoulder.extend || shoulder.contract) {
allDone = false;
}
if ((elbow.extend || elbow.contract) && currentTime >= elbow.stopTime) {
stopActuator(ELBOW_IN1, ELBOW_IN2);
elbow = ActuatorControl();
} else if (elbow.extend || elbow.contract) {
allDone = false;
}
if ((wrist.extend || wrist.contract) && currentTime >= wrist.stopTime) {
stopActuator(WRIST_IN1, WRIST_IN2);
wrist = ActuatorControl();
} else if (wrist.extend || wrist.contract) {
allDone = false;
}
if (allDone) break;
delay(10); // 10 ms polling delay
}
Serial.println("All done.");
}
}
Bolted On Wheels
- Attached wheels to the back through drilling, nuts, and bolts
- The back plate is slightly inclined so the wheels are slightly of angled obtusely but they still roll well and the angle prevents the robot from free-rolling when inclined
Completed 1 Cycle Of Movements That Moves Robot Forward
- The robot is capable of moving forward 60cm
- This was discovered through iterating possible movemnts between the Shoulder, Elbow, and Wrist
- The moves to input to achieve this are: SE 15 WE 5 SE 25 EE 10 WE 10 SC 40
Unscrewed the wooden base with all the electronics
- Unscrewed but still keeping on top of base for ease of electronics access
Welding Training
- Completed all training and only need to practice more to ensure skills are maintained
Immediate To Do's
- Optimise arm movement
- Start designing Max/Min sensors based on Arm movement
Delayed To Do's
- Will drill holes on the sides in case we decided to add the stabilizing side legs but this can be done once the rotation mechanism is more established
- Undo the large bolt under the arm
- Coordinate with Prof to remove the arm. (This is a two person job to be done safely.)
- Should we add the rotating part at this time?
this is how you make a paragraph this is part of the same paragraph this is part of the same paragraph this is part of the same paragraph this is part of the same paragraph this is part of the same paragraph
To start a new paragraph, insert a the blank line after the previous paragraph
- this is how you make a list
- second item
- etc.
More info here and in many other sources online