Holding a position in our arm

Heres the deal:

We have a revolute double jointed arm; say we wnat to hold the lower portion of the arm in one place; how would we do so?

We have a potentiometer mesaureing the position values of the arm. Lets say we want the arm at a target val of 180. The arm is plugged into pwm02. We made some simple if/then statements…

Please ignore syntax.

sensor = Get_Analog_Value(rc_ana_in02);
targetval = 180;
if (sensor < targetval)
{
pwm02 = (127 + 53);
else
{
pwm02 = (127 - 53);
}

This makes the arm oscillate at the target value, and it oscillates way too much. How can we essentially LOCK the arm through programming?

You can’t move the arm at a single hardcoded speed and then suddenly reverse it when you pass your target - you will overshoot, as I’m sure you’ve noticed. While full-fledged PID (PD, anyway) control would be best here, I understand you’re short on time so you can implement a rudimentary version of P control like this:

You will need a PWM-limiting function that takes an integer and bounds it between 0 and 254, because otherwise the unsigned char that representes a PWM setting will roll over and you will find your motors moving backwards.

targetVal = 180;
float pGain = 0.5;
int sensor = Get_Analog_Value(foo);
pwm02 = pwm_Limit((pGain*(targetVal-sensor))+127);

And then, just tweak the pGain up or down in small increments until your arm movement is acceptable.

Again, PID feedback is great, so if you have the time to do it properly I definitely would, but you can get away with this in a pinch.

…this is an 8 bit microcontroller with very limited math capabilities, and floating point math is quite time consuming.

Integer math is always going to be more computationally efficient and in this case where the data source and destination have very limited precision, can be handled by simply moving the binary point up a few bits for the intermediate math, and then scale the result back down.

Here’s a different way to approach the problem with a proportional only controller…


int targetVal = 180;
int sensor; 
int err;
int itmp;

#define PGAIN 5

  sensor = Get_Analog_Value(foo);
  err = targetVal - sensor;
  itmp = (PGAIN * err)/10;
  itmp = (itmp < -127)?-127:itmp;
  itmp = (itmp > 127)?127:itmp;
  pwm02 = itmp + 127;


So if PGAIN = 5 and then multiplied against the error, then divided by 10, it is the same as multiplying by 0.5.

If more resolution is needed, simply scale up the multiplers and divisors to whatever precision is desired, as long as the intermediate values will still remain within a signed 16 bit value, the math will work out fine.

If an intergal and differential term were needed then the code could be expanded like this…




int targetVal = 180;
int sensor; 
int err;
int p_out;
int i_out;
int d_out;

static int last_err = 0;
static int integrator = 0;
int delta_err;
int i_err;

int itmp;

#define PGAIN 5
#define IGAIN 3
#define DGAIN 2

  sensor = Get_Analog_Value(foo);

  err = targetVal - sensor;
  delta_err = last_err - err;
  last_err = err;
  i_err = err + integrator;

  p_out = PGAIN * err;
  i_out = IGAIN * i_err;
  d_out = DGAIN * delta_err;

  itmp = (p_out + i_out + d_out)/10;  // sum the totals and then move the binary point back into position.

  if (itmp > 127) {
      itmp = 127;
  } else if (itmp < -127) {
     itmp = -127;
  } else {
    integrator = i_err;    // limit integrator windup by only accumulating when the output values are not saturated
  }
 
  pwm02 = itmp + 127;


For the sake of example, I intentionally used different ways of accompishing the same thing in the examples and for clarity used more intermediate values than would be necessary.