Need help with PCA9865 and Servo Limits

Hello everyone. I am working on a robotic project with some code that I did not write. Essentially I have 6 x MG90S servos and 3 x MG92b Servos installed in a droid. The code makes him move, except that I need to adjust the servo limits so things dont go beyond physical limits.

The code defines a home position as follows:

#define neckHome 300
uint16_t neckPos = neckHome;
uint16_t neckPrevPos = neckHome;

Then it defines extents like this:

Servo Extents
#define neckMin 150
#define neckMax 460

This is all used in the following function:

/*
   servoName - Name of servo to move  eg. servoLegs
   bEnableServo - Boolean for the servo eg. bLegs
   prevPos - Servo's previous position eg. legsPrevPos
   currPos- Servo's current position eg. legsPos
   servoMin - Servo's minimum extent eg. legsMin
   servoMax - Servo's maximum extent eg. legsMax
   servoDelay - Servo's delay random number between delMin and delMax eg. legsDelay
   delayCount - Counter to keep track of where we are eg. legsDelayCount
   delMin - Minimum delay (fastest speed) for servoDelay eg. DelayMed or DelayMin
   delMax - Maximum delay (slowest speed) for servoDelay eg. DelayMin or DelayMed
   returnPos - If not zero, then return to this position after movement eg. legsHome or legsMin
*/
void MoveServo(uint16_t pwmAddress, uint16_t servoName, bool& bEnableServo, uint16_t& prevPos, uint16_t& currPos, uint16_t servoMin, uint16_t servoMax, uint16_t& servoDelay, uint16_t& delayCount, uint16_t delMin, uint16_t delMax, uint16_t chanceToMove, uint16_t returnPos, uint16_t destPos )
{
  if (bEnableServo) {
    if (delayCount >= servoDelay) {
      if (prevPos <= currPos) {
        prevPos = prevPos + DelayStep;
        if (prevPos > currPos) {
          prevPos = currPos;
        }
      }

      if (prevPos > currPos) {
        prevPos = prevPos - DelayStep;
        if (prevPos < currPos) {
          prevPos = currPos;
        }
      }
      //check to see which PWM board the device is on.
      if (pwmAddress == pwmMainAddr) {
        pwmMain.setPWM(servoName, 0, prevPos);
      }
      else {
        pwmBase.setPWM(servoName, 0, prevPos);
      }

      delayCount = 0;
      if (prevPos == currPos) {
        bEnableServo = false;
      }

      //some servos have an extra function, like LEDS etc.
      switch (servoName) {
        case servoAperature:
          pwmMain.setPWM(aperr, 0, map(aperPrevPos, aperMin, aperMax, 0, 4095)); // brighten the aperature LED as the aperature opens
          break;
        case servoHolo:
          if (bEnableServo) {
            // a little flicker before setting to a value relative to the holoprojector position
            pwmMain.setPWM(holob, 0, 4095);
            pwmMain.setPWM(holob, 0, holoPos * 17);
          }
          else {
            pwmMain.setPWM(holob, 0, 400);
          }
          break;
        case servoEar2:
          pwmMain.setPWM(servoEar1, 0, map(earPrevPos, earMin, earMax, earMax, earMin));
          break;
      }
    }
    delayCount++;
  }
  else {
    if (destPos != 0 && currPos != destPos) { //move to specific position
      servoDelay = 1;
      prevPos = currPos;
      currPos = destPos;
      bEnableServo = true;
    }

    if (random (chanceToMove) == 1 && destPos == 0) { //random chance of moving to a random position
      servoDelay = random(delMin, delMax);
      prevPos = currPos;
      currPos = (random (servoMin, servoMax));
      bEnableServo = true;
      if (servoName == servoHolo) {
        myDFPlayer.play(random(9, 76));
      }
    }

    if (returnPos != 0 && bEnableServo == false && currPos != returnPos) { //after moving, return
      servoDelay = DelayMax;
      currPos = returnPos;
      bEnableServo = true;
    }
  }
}

So how do I test the servo by moving it to the end point on both sides of center and then figure out what those endpoints are to insert into the extents?

I've hooked up a potentiometer and am able to control the servo while reading out the voltage, potentiometer value, and corresponding angle. How do I convert that into a pulse length for pwm?

Thank you for helping!

Convert your test program to use writeMicroseconds() instead of write(). Then conversion to PCA values is fairly straightforward but depends to some extent on what PWMFreq you're running the PCA at which you haven't show in your code snippets.

To get at least fairly close try PCA value = writeMicrosecond value / 4. E.g. if your servo test shows 600us min and 1600us max that will translate to 150, 400 in PCA speak.

Steve

1 Like

So instead of servo.write(angle) I would use servo.writeMicroseconds()?

What about continuous rotation servos? How do I define end points for them?

That is the problem you can’t. All that writing to them can do is to affect the speed and direction of rotation.

So can I use the speed and direction with a time to limit travel?

You would likely be blocking the program execution while waiting for the time limit.
Paul

You could try but i doubt if it would very accurate, have you every tried giving a motor a specific times pulse? This is because a lot depends on the load.

This is for a neck lift / rotation that I dont need to be SUPER accurate about, but also need to stay within some guardrails. Basically its like I dont need to be within 1 or even 10 degrees of rotation, but being off by 45 is too much.
Using the MG92b continuous rotation servos because I need the additional torque.

Give it a try and see how well it works for you. If you don't want any real accuracy it might be good enough for whatever your requirements turn out to be.

Steve

Can I use the servo.read() function to read the angle and then convert that to PCA speak?

How would I do that?

No.

Read the current angle of the servo (the value passed to the last call to write()).

It doesn’t actually read the servo angle, just what you last set it to.

Correct, I'm thinking that if I can use the potentiometer to control the servo then I know the Angle, Knob Value, and Voltage of the potentiometer.

This is the code I have so far:

#include<Servo.h>

Servo servo;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  servo.attach(9);
}

void loop() {
  // put your main code here, to run repeatedly:
  int knobValue = analogRead(A0);
  float voltage= knobValue*(5.0/1023.0);
  String voltageResult = "Voltage : ";
  String knobValueResult = "Knob Value : ";
  String angleResult = "Angle : "; 

  voltageResult += voltage; 
  Serial.println(voltageResult);
  Serial.println('\n');

  knobValueResult += knobValue;
  Serial.println(knobValueResult);
  Serial.println('\n');
  
  int angle = map(knobValue, 0, 1023, 0, 180);
  angleResult += angle;
  Serial.println(angleResult);
  Serial.println('\n');

  servo.write(angle);
  
  delay(100);
  
}

Since I can move the servo with the potentiometer, I can move it to the position limit and know the resultant angle / voltage / knob value used to get there. Then I would just need to convert that to the PCA extent value correct?

So I can manually set the servo to the limits I need, I just need to know how to figure out what value to enter into the Extent Min and Max.

Are we still talking about continuous rotation "servos" or are we back to normal servos? That's not going to work for the former but it should for normal servos though it would be a lot simpler to use writeMicroseconds() as I suggested previously.

If you insist on sticking with angles then you need to know that write(0) = 544us and write(180) = 2400us. From there you can calculate the PCA values as in #2.

Steve