4

This is my first post to this forum. I am fairly experienced in electronics, but am in the process of learning C, Arduino coding, and how to use microcontrollers in general for various applications.

I have a toy-like 5 degree of freedom (DOF) robot arm that is currently controlled using the OEM hand-held switch box. It is a basic electromechanical robot arm to illustrate the fundamentals and it uses DC motors to drive the joint motions. The direction of motion is controlled by changing the polarity of the applied voltage. The manufacturer did offer an "interface board" and SW to be able to hook up the arm and control it using a computer program. However, this approach was fairly dated and their SW was not compatible with windows 8.1. I wanted to experiment and use an Arduino Uno Rev 3 and 5V DC relay PCBs to develop my own interface system.

Basically, the HW aspect was straightforward and is working fine. The Arduino SW also is working fine. I am playing around with 2 variations of the program: 1) One program that simultaneously moves up to 3 joints and 2) A sequential joint movement program where each joint only moves after the other has finished moving. One note about the simultaneous program is that I am using the "internal" batteries in the robot's base (only 3V each direction) and thus can not realistically move more than 3 joints at the same time without putting too much load on the source voltage.

Ok, now to my question! As it stands now, the program works well, but is not intuitive. Each joint has 2 relays associated with it: one for each direction of travel. There are 5 joints total in the robot arm. I am able to use the program easily since I know directly (from setting up the HW and then the SW) which relays correspond to which Arduino pins and of course which relays correspond to each joint. I'd like to make the program more intuitive where instead of "2,2,0,0,3,4" input string for example, the user would do something like "B,CCW,S,CW" where B is for B = base, CCW = is counter clockwise etc.

Please note that in the numerical only input string "2,2,3,4" for example, the 2nd value corresponds to the duration in seconds that the relay will be ON (and thus the joint moving). The robot arm is not very sophisticated in that it doesn't have encoders for position feedback. The simplest way (and the method which the OEM interface board uses) to interface this arm and make a program where the arm will move in a "hands-off fashion" was to use relays and program delays (to create time durations of joint movement". The numerical only input works really well currently and it was very easy to use examples to figure out how to parse a comma separated string.

I understand (I think) from looking at the examples how to parse a comma separated text string and pull from it the input values.

BUT, I am uncertain how to use those input values and then associate them in the SW with the correct relays (Arduino Pins)!

I have already tried seeking help with this but am unable to understand this thread: http://forum.arduino.cc/index.php/topic,94701.0.html

Basically, I need a 2D table where the joint and direction are input and then the SW associates one particular relay with those inputs. Please see the table below for a graphical example of what I tried to say above.

One possible solution that I dread and saw mentioned was to use a ton of if else-if statements.... I dread this since I want to be able to input any joint in any order and thus it seems like when the code parses the input string for the ith relay, it would have to check ALL of the conditions to see which joint it is, which direction etc. For example, I think I could minimize the # of if statements needed if I only entered in the input commands in a certain order: base first, shoulder next, etc. Doing so however, limits the intuitiveness of the program.

enter image description here

Thank you all for reading this! I hope this isn't too challenging!

1
  • @Edgar Bonet, JRobert and Majenko Thanks for the prompt replies! I will sandbox the example code you provided. I have a feeling that one method may be easier to use for my program that takes command inputs through the Serial Monitor and another method for the hard-coded sequential moment program. In the hard coded program, by having the code contain abbreviated references to the joint and direction, it will make it easier to understand since it now simply contains numerical arrays of relay order, ON duration, delay after each movement, etc. May 21, 2015 at 14:00

4 Answers 4

2

I suggest you turn this into a simple 1D table by using two different separators, for example ‘:’ between joint and direction, and ‘,’ between consecutive movements. Then, instead of “B,CCW,S,CW” you would type “B:CCW,S:CW”. For parsing the string, you split at the commas, and consider the pieces between the commas as single items. In other words, “B:CCW” would be the name for relay 2, and you do not try to split it further.

Here is a simple method to map the strings to the relays:

const char * const relay_names[] = {"", "B:CW", "B:CCW", "S:UP",
    "S:DN", "W:CW", "W:CCW", "E:UP", "E:DN", "G:OP", "G:CL"};

// Return the relay number.
int get_relay(const char * name)
{
    for (int i = 1; i <= 10; i++)
        if (strcmp(name, relay_names[i]) == 0)
            return i;
    return -1;  // meaning invalid
}
1
  • I selected this method because it seemed more intuitive imo to input commands in the serial interface that had direct correlation with the joint name and movement direction. I appreciate the other answers for showing alternative methods of achieving the same goal. May 27, 2015 at 14:11
3

Another thing you can consider is to define a struct. This is a compound variable that contains other variables of different types.

You could define a struct to describe the joints of an arm:

struct arm {
    int base;
    int shoulder;
    int elbow;
    int wrist;
    int grip;
};

Which you can then assign as variables:

const struct arm left_forward = { 1, 3, 7, 5, 9};
const struct arm left_reverse = { 2, 4, 8, 6, 10};

You can then get at different pins by using such things as:

digitalWrite(left_forward.shoulder, HIGH);

Another trick is to make your struct contain a union and an anonymous struct so you have multiple ways of accessing the pins:

struct arm {
    union {
        struct {
            int base;
            int shoulder;
            int elbow;
            int wrist;
            int grip;
        };
        int pins[5];
    };
};

You can now get at left_forward.shoulder and left_shoulder.pins[1] and they point to the same thing.

You can create an array of these structures now:

const struct arm left[2] = {
    { 1, 3, 7, 5, 9},
    { 2, 4, 8, 6, 10}
};

So now you have left[0].shoulder and left[0].pins[1] both for the left shoulder moving forwards.

Another option, which might be far more logical, is to turn the whole thing inside out. Define a "pin pair" structure that defines the two pins for a joint:

struct pinpair {
    int forward;
    int reverse;
};

Then an arm consists of 5 "pin pairs":

const struct pinpair leftarm[5] = {
    {1, 2},
    {3, 4},
    {7, 8},
    {5, 6},
    {9, 10}
};

So to move the left shoulder forward it would be leftarm[1].forward and to move the wrist backward would be leftarm[4].reverse.

Of course, you could combine the two and have a struct of named structs for each joint if you wanted (leftarm.shoulder.reverse. I'll leave you to work out the syntax for that from the examples above).

Now for your "control string". How to deal with that. Well, I would "pre-parse" it into an array of numbers. Compile it into "byte code" if you will.

Start with an empty array that would be big enough to store the data you want, then work your way through the string.

Tokenize it (hint: strtok() is good for this) and check each piece of the string against others. Unfortunately it's not something you can easily automate, and you'd either have to loop through an array of strings to compare each one to, or use a series of "if" statements.

For each item that you match you then assign a "command code" to - a number you can then easily associate with a specific action or joint. For instance, "B" may equate to command 0, "S" to 1, ""E" to 2, etc - which just happen to coincide with the array slices for the joints.

The directions could then be other codes (keep them outside the range of your array so you can identify them), maybe 16 and 17 (hint: think about the hexadecimal representation of those and how you could use that to your benefit). It can also be useful to mark the end of the array with a special token to say "this is the end" - say 255 (0xff).

Then once you have parsed the string and got your list of numbers, which may be something like { 0, 16, 1, 17, 255 }, you can then "execute" it. Just step through each entry, if it's < 16 then it's a command, if it's >= 16 then it's a direction, and if it's == 255 then it's the end of the command list. It's then up to you to drive the right pins for the right periods to execute those commands.

0

In C/C++, multi-dimensional arrays are represented as arrays of arrays of arrays ..., for the number of dimensions needed (the last subscript varying most rapidly as you step through the stored elements in RAM).

The array you described is a sparse array; not all cells hold valid data. To simplify (and that's a relative term!) the storage and the code, I would arbitrarily assign each joint a forward and a reverse direction, reducing your 10-row sparse array to a dense array of only 2 rows. You can then, if you wish associate joint-relative motionss (up/down, open/close, etc.) with forward/reverse. Then you can find the pin # for a given joint relay by the joint name and direction as RelayPin[<direction-name>][<joint-name>]. A downside is that writing RelayPin[DIR_OPEN][JNT_ELBO] looks like nonsense but actually executes, though maybe not in the direction you think you meant.

This code snippet defines a 2D array of joint-relay pin numbers, with an example of enabling one of the relays:

// Number of joints and directions
#define NDIRECTIONS 2
#define NJOINTS 5

// Basic joint-motion directions
#define _DIR_FWD 0
#define _DIR_REV 1

// Aliases for joint-motion directions
#define DIR_CW _DIR_FWD
#define DIR_CCW _DIR_REV

#define DIR_UP _DIR_FWD
#define DIR_DN _DIR_REV

#define DIR_OPEN _DIR_FWD
#define DIR_CLOSE _DIR_REV

// Joint table indices
#define JNT_BASE 0
#define JNT_SHOU 1
#define JNT_ELBO 2
#define JNT_WRIS 3
#define JNT_GRIP 4

// Joint table - each element is a digital pin number
uint8_t const RelayPin[DIRECTIONS][JOINTS] = {
//  B  S  E  W  Ga
   {1, 3, 7, 5, 9}, // forward
   {2, 4, 8, 6, 10},    // reverse
};

// Example of enabling motion to raise the shoulder:
digitalWrite(RelayPin[DIR_UP][JNT_SHOU], HIGH)  // raise shoulder
0

The other answers have presented some good techniques that are flexible enough to be adapted to other, similar problems. By contrast, the following method is specialized to work with the particular chart of joint-letters and relay-numbers mentioned in the question. (It can be modified for other relay-number sequences, as noted in program's comments.)

Rather than requiring entry of verbose text like CCW, or obscure abbreviations like DN, OP, and CL, it accepts a + or a - to set motor direction, with “+” corresponding to choices CW, UP, and OP, and “-” corresponding to other choices. For example, the question's sequence “B,CCW,S,CW” could be written as any of “B-S+”, “b-s+”, “B- S+”, “B-,S+”, etc (ie, case is ignored, and spaces and commas are ignored before a joint-letter).

Note, the code as shown assumes each command-list line starts with a number that gives desired delay time in tenths of a second. For example, “23 e-s+” would run E down and S up for 2.3 seconds. The command-list “47” would sit idle (no motors on) for 4.7 seconds.

#include <ctype.h>
void operateRelays(char *cmdList) {
  char *joints = "BSEWG", *c=cmdList, *j;
  byte relay, delaytenths= 0;

  while (*c && isdigit(*c)) // Get # of tenths-of-seconds to delay
    delaytenths = 10*delaytenths + *c-'0';

  for (; *c; ++c) {
    while (*c && !isalpha(*c)) ++c; // Ignore non-alphas before joint
    if (!*c) break;     // At end of command list, break
    for (j=joints; *j; ++j)
      if (toupper(*c) == *j) break;
    if (!*j) break;     // Ab-exit if joint-letter not found
    if (!*(++c)) break;     // Advance to + or -
    relay = 2*(j-joints) + 1 + (*c != '+'); // The last term is 0 or 1
    digitalWrite(relay, HIGH);
    // If relay numbers aren't contiguous as in question, just use
    // an array (like: byte relays[] = {2,1,5,4,6,3,7,9,10,8};)
    // and in above statement say digitalWrite(relays[relay-1], HIGH);.
  }    
  delay (100*delaytenths); // Wait specified time
  for (relay=1; relay<11; ++relay) // Turn motors off
    digitalWrite(relay, LOW);
}

Your Answer

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

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