The long-awaited follow-up to my first post, here is the second chapter of the arm blog. Yes, you did read that title correctly, there will be four parts to this blog not three. The reason for that is that I realized part of the challenge of writing this was trying to combine hardware and software sections. So instead of cramming them into a long, convoluted post I’ve broken them out into two pieces. This post will cover the software section, and the next part will cover the hardware build. The final post will have the actual video taken from the GoPro in the arm and talk about some of the overall issues and what I can do to improve the camera arm system.
Who Should Read This?
If you’ve done some basic programming and read the first part of this blog (found here) then you’re set to understand the topics covered in this post. If you don’t have programming experience, I will try to keep this simple enough to follow along with but read at your own risk.
The Pin Assignments
const int controlPin1 = 2; //Wrist
const int controlPin2 = 3;
const int control2Pin1 = 12; //Elbow
const int control2Pin2 = 13;
const int control3Pin1 = 8; //Spin
const int control3Pin2 = 7;
const int enablePin = 9; // Wrist motor
const int enablePin2 = 11; // Elbow motor
const int enablePin3 = 10; // Spin motor
const int onOffPin = 5;
This section of code should look somewhat familiar to anyone who has programmed before and if you’ve programmed an Arduino before you know exactly what this is. The lines above assign constant values representing the different pins on the Arduino to names which will make them easier to understand throughout the code. Each motor requires two control pins, and one enable pin. The control pins for each motor are noted by the comment next to the first line representing them, and the enable pins are also commented to show which motor they represent. The “onOffPin” assignment will be used as an input for a button which can be used to start and stop the arm’s programmed motion. The mode setting will be used to define the state of the control system. There are three states for the system: off, forward, and reverse.
void setup(){
pinMode(onOffPin, INPUT);
pinMode(controlPin1, OUTPUT);
pinMode(controlPin2, OUTPUT);
pinMode(control2Pin1, OUTPUT);
pinMode(control2Pin2, OUTPUT);
pinMode(control3Pin1, OUTPUT);
pinMode(control3Pin2, OUTPUT);
pinMode(enablePin, OUTPUT);
digitalWrite(enablePin, LOW);
pinMode(enablePin2, OUTPUT);
digitalWrite(enablePin2, LOW);
pinMode(enablePin3, OUTPUT);
digitalWrite(enablePin3, LOW);
}
The above code simply defines the function of the different pins as well as the initial state of the enable pins. The control pins are all set as outputs, because the Arduino will be writing to them to control the arm’s direction and speed. The enable pins are all set to start with the value low, which will have the Arduino start with all the motors off (If you try this at home, do note that even though the enable pins are set to low at the start this happens after the Arduino boots and the motors may still move before these settings take effect).
The Functions
boolean debounce(boolean last)
{
boolean current = digitalRead(onOffPin);
if (last != current)
{
delay(5); //wait 5ms
current = digitalRead(onOffPin);
}
return current;
}
The above code is what is known as a debounce function. When a normal push button is pressed, it will generally not go directly between the open and closed state of the switch (it won’t just go from off to on). The physical properties of the button lead it to “bounce” and switch rapidly between off and on multiple times before settling into the proper state. To account for this you can either build a physical debounce circuit (which takes space and parts) or you can use a software debounce like I’ve done here to account for this.
void loop(){
onOffState = debounce(previousOnOffState);
delay(1);
if(previousOnOffState == LOW && onOffState == HIGH){
elbowMotorEnabled = !elbowMotorEnabled;
spinMotorEnabled = !spinMotorEnabled;
}
//Check if it's time to start raising the arm
if (spinCount >= 2900){
spinFlag = 1;
}
// checking if the motor movement is complete
if (elbowMotorEnabled == HIGH && spinFlag == 1) {
if(count < upDist && elbowMotorDirection == 1){
count = count + 1;
}
else if (count < downDist && elbowMotorDirection == 0) {
count = count + 1;
}
else{
count = 0;
spinFlag = 0;
elbowMotorEnabled = 0;
elbowMotorDirection = !elbowMotorDirection;
wristMotorDirection = !wristMotorDirection;
}
}
//checking spin motor movement against distance
if (spinMotorEnabled == HIGH){
if (spinCount < spinDist){
spinCount = spinCount + 1;
}
else{
spinCount = 0;
spinMotorEnabled = 0;
spinMotorDirection = !spinMotorDirection;
}
}
// Telling the H-bridge the direction of the elbow motor
if (elbowMotorDirection == 1){
digitalWrite(control2Pin1, HIGH); // elbow
digitalWrite(control2Pin2, LOW);
//elbowMotorSpeed = 200; // for 6v batt
elbowMotorSpeed = 70; // for 12v batt
}
else{
digitalWrite(control2Pin1, LOW); // elbow
digitalWrite(control2Pin2, HIGH);
//elbowMotorSpeed = 200; // for 6v batt
elbowMotorSpeed = 50; // for 12v batt
}
// Telling the H-bridge the direction of the wrist motor
if (wristMotorDirection == 1){
digitalWrite(controlPin1, LOW); // wrist
digitalWrite(controlPin2, HIGH);
//wristMotorSpeed = 200; // for 6v batt
wristMotorSpeed = 53; // for 12v batt
}
else{
digitalWrite(controlPin1, HIGH); // wrist
digitalWrite(controlPin2, LOW);
//wristMotorSpeed = 130; // for 6v batt
wristMotorSpeed = 40; // for 12v batt, minimum value is ~35
}
// Telling the H-bridge the direction of the spin motor
if (spinMotorDirection == 1){
digitalWrite(control3Pin1, HIGH); // elbow
digitalWrite(control3Pin2, LOW);
}
else{
digitalWrite(control3Pin1, LOW); // elbow
digitalWrite(control3Pin2, HIGH);
}
//Controlling vertical speed
if(elbowMotorEnabled == 1 && spinFlag == 1){
if (elbowMotorDirection == 1 && count % 2 == 0){
analogWrite(enablePin, wristMotorSpeed);
//Serial.println(count);
}
else if (elbowMotorDirection == 1 && count % 2 != 0){
analogWrite(enablePin, 0);
}
else if (elbowMotorDirection == 0){
analogWrite(enablePin, wristMotorSpeed);
}
analogWrite(enablePin2, elbowMotorSpeed);
}
else{
analogWrite(enablePin, 0);
analogWrite(enablePin2, 0);
}
// Controlling the spin motor speed
if(spinMotorEnabled == 1){
analogWrite(enablePin3, spinMotorSpeed);
}
else{
analogWrite(enablePin3, 0);
}
previousOnOffState = onOffState;
}
Finally, we get to the main function. It starts by checking to see whether the pushbutton has been pressed and debounced. If the button has changed position, then the value of onOffState is reversed to represent that change. The program then checks the direction and distance moved to see whether the motion has been completed, this is done at the beginning of the loop so that after a previous loop completes the motion it stops immediately while only having to run the check once.
From there the arm goes through a series of pre-programmed motions based on the value of the different counters. These counters act as timers, allowing for different motions to begin at different times after the button press which lets the arm make more complex motions and keep the camera steady as the arm moves. Changing the maximum values of these counters will change the motion of the arm. Once the forward motion is complete, the motor’s direction flag is changed so that the motion can be reversed and the arm can be returned to its initial position.
The other main thing of note in the above code is that the values are different depending on if a 6V or 12V battery setup is used. I chose to use the 12V battery setup because the power draw from the larger battery was more consistent at low voltages (which allow for low speeds). If I wanted to use the arm’s built-in batteries, then I would have to switch to the 6V values and comment out the 12V ones instead.
Conclusion
And that’s my post about the software component of the GoPro camera arm, look out for the next post where I will go over the hardware setup. The full code for this can be found in one piece here (with my debug comments in if you run into any problems)