Parallax feedback 360 Servo control

Hello, I'm new here. I have a short history working with Arduino in a case by case basis with good results, but I have a question on this application.

I'm building a controller and have a Nano Every running a Parallax Feedback 360 Servo. I have a inbound 12v square wave signal (that I have running through a 10k/22k voltage divider, so actual input to the Arduino is a fixed 3.8V with a varying frequency.)

The code for that is operating well and tested out great with a regular normal servo, the problem is that, as this is for a gauge cluster, I need about a 350 degree sweep, so that's why I went with the feedback 360.

I'm powering the servo with my 12v in rail cut down to 6v with a 7806, and the servo functions great in continuous rotation with the current setup.

My current problem stems from the code for this particular servo. There seems to be a few attempts at making this work, but none that worked well for me. I actually found a library dedicated for it, but It's built on the wrong chipset.

Here's the code that is copied from an older post here (thanks @JavelinPoint!), although I can't seem to get it to work with some test movements. Ideally I'd like to see this test code turn the servo to a 30, 60, 180 and like a 270 degree angle before I go any further with it. I have my signal out hooked to pin 9, and my "feedback" connected to A0 (pin 14). At this point I'm having compiling errors, so I'm not sure.

Thanks in advance!

#include <Servo.h>            //Servo library

//PID angle control for Parallax 360 Servo
Servo servo_test;        //initialize a servo object for the connected servo 
int pinFeedback = A0;
               
int target_angle = 180;
float tHigh = 0;
float tLow = 0;
int tCycle = 0;
float dc = 0;
float angle = 0; //Measured angle from feedback
float dcMin = 2.9; //From Parallax spec sheet
float dcMax = 97.1; //From Parallax spec sheet
float Kp = .7; //Proportional Gain, higher values for faster response, higher values contribute to overshoot.
float Ki = .2; //Integral Gain, higher values to converge faster to zero error, higher values produce oscillations. Higher values are more unstable near a target_angle = 0.
float iLimit = 5; //Arbitrary Anti-wind-up
float Kd = 1; //Derivative Gain, higher values dampen oscillations around target_angle. Higher values produce more holding state jitter. May need filter for error noise.
float prev_error = 0;
float prev_pError = 0;
float error = 0;
float pError = 0;
float iError = 0;

 
void setup()
{
  servo_test.attach(9);      // attach the signal pin of servo to pin9 of arduino
  pinMode(A0, INPUT);
}
 
void loop()
{
  //use line below to determine "center" of servo. When near values of 90, manually force servo by hand in both directions to see if it continues to turn.
  //servo_test.write(85); //clockwise: max 0 - 90 min, counter-clockwise: min 96-180 max, may be different for your servo, 93 stopped.
 
  while(1) //From Parallax spec sheet
  {
    tHigh = pulseIn(pinFeedback, HIGH);
    tLow = pulseIn(pinFeedback, LOW);
    tCycle = tHigh + tLow;
    if ( tCycle > 1000 && tCycle < 1200)
    {
      break; //valid tCycle;
    }
  }
 
  dc = (100 * tHigh) / tCycle; //From Parallax spec sheet, you are trying to determine the percentage of the HIGH in the pulse
 
  angle = ((dc - dcMin) * 360) / (dcMax - dcMin + 1); //From Parallax spec sheet

  //Keep measured angles within bounds
  if (angle < 0)
  {
    angle = 0;
  }
  else if(angle > 359)
  {
    angle = 359;
  }

  if (target_angle < 0)
  {
    target_angle = 360 + target_angle; //handles negative target_angles;
  }
 
  error = target_angle - angle;
 
  if(error > 180)
  {
    error = error - 360; //tells it to rotate in the other direction because it is a smaller angle that way.
  }
  if (error < -180)
  {
    error = 360 - error - 360; //tells it to rotate in the other direction because it is a smaller angle that way.
  }

  // PID controller stuff, Adjust values of Kp, Ki, and Kd above to tune your system
  float pError = Kp * error;
  float iError = Ki * (error + prev_error);

  if  (iError > iLimit)
  {
    iError = iLimit;
  }
  if (iError <  -iLimit)
  {
    iError = -iLimit;
  }
 
  prev_error = error;
  float dError = Kd * (pError - prev_pError);
  prev_pError = pError;

  error = error / 2; //max 180 error will have max 90 offset value

  int val = 93 - (Kp * error) - iError - dError; // 93 is the middle of my servo's "no motion" dead-band
 
  servo_test.write(val); //Move the servo   

  void loop() {
    for (val = 0; val <= 30; val += 1) { 
     servo_test.write(val);              
     delay(100);                       
    }
    for (val = 30; val >= 60; val -= 1) { 
     servo_test.write(val);              
     delay(100);                       
    }
 }
}

Hello my friend,

Here you go. I have not tested the corrections.

#include <Servo.h>            //Servo library

//PID angle control for Parallax 360 Servo
Servo servo_test;        //initialize a servo object for the connected servo
int pinFeedback = A0;
               
int target_angle = 180;
float tHigh = 0;
float tLow = 0;
int tCycle = 0;
float dc = 0;
float angle = 0; //Measured angle from feedback
float dcMin = 2.9; //From Parallax spec sheet
float dcMax = 97.1; //From Parallax spec sheet
float Kp = .7; //Proportional Gain, higher values for faster response, higher values contribute to overshoot.
float Ki = .2; //Integral Gain, higher values to converge faster to zero error, higher values produce oscillations. Higher values are more unstable near a target_angle = 0.
float iLimit = 5; //Arbitrary Anti-wind-up
float Kd = 1; //Derivative Gain, higher values dampen oscillations around target_angle. Higher values produce more holding state jitter. May need filter for error noise.
float prev_error = 0;
float prev_pError = 0;
float error = 0;
float pError = 0;
float iError = 0;

 
void setup()
{
  servo_test.attach(9);      // attach the signal pin of servo to pin9 of arduino
  pinMode(A0, INPUT);
}
 
void loop()
{
  //use line below to determine "center" of servo. When near values of 90, manually force servo by hand in both directions to see if it continues to turn.
  //servo_test.write(85); //clockwise: max 0 - 90 min, counter-clockwise: min 96-180 max, may be different for your servo, 93 stopped.
 
  while(1) //From Parallax spec sheet
  {
    tHigh = pulseIn(pinFeedback, HIGH);
    tLow = pulseIn(pinFeedback, LOW);
    tCycle = tHigh + tLow;
    if ( tCycle > 1000 && tCycle < 1200)
    {
      break; //valid tCycle;
    }
  }
 
  dc = (100 * tHigh) / tCycle; //From Parallax spec sheet, you are trying to determine the percentage of the HIGH in the pulse
 
  angle = ((dc - dcMin) * 360) / (dcMax - dcMin + 1); //From Parallax spec sheet

  //Keep measured angles within bounds
  if (angle < 0)
  {
    angle = 0;
  }
  else if(angle > 359)
  {
    angle = 359;
  }

  if (target_angle < 0)
  {
    target_angle = 360 + target_angle; //handles negative target_angles;
  }
 
  error = target_angle - angle;
 
  if(error > 180)
  {
    error = error - 360; //tells it to rotate in the other direction because it is a smaller angle that way.
  }
  if (error < -180)
  {
    error = 360 - error - 360; //tells it to rotate in the other direction because it is a smaller angle that way.
  }

  // PID controller stuff, Adjust values of Kp, Ki, and Kd above to tune your system
  float pError = Kp * error;
  float iError = Ki * (error + prev_error);

  if  (iError > iLimit)
  {
    iError = iLimit;
  }
  if (iError <  -iLimit)
  {
    iError = -iLimit;
  }

{  prev_error = error;
  float dError = Kd * (pError - prev_pError);
  prev_pError = pError;

  error = error / 2; //max 180 error will have max 90 offset value

  int val = 93 - (Kp * error) - iError - dError; // 93 is the middle of my servo's "no motion" dead-band
 
  servo_test.write(val); //Move the servo   

    for (int val = 0; val <= 30; val += 1) 
    {
     servo_test.write(val);             
     delay(100);                       
    }
    for (int val = 30; val >= 60; val -= 1)
    {
     servo_test.write(val);             
     delay(100);                       
    }
 }
}

I appreciate you taking a look at it @Gates, but unfortunately I still have no movement. I tried changing up some values, and to double check my power source I ran a "normal "servo code and it does spin consistently, but nothing with this code.

So a deeper analyse of what is going on with debug-output to the serial monitor starts here.

There is a while-loop that keeps repeating measuring the feed-back-pulse until a valid pulse is detected

You should check if this happends by adding serial output below the while-loop
If you have questions how to do this just ask.
Best regards Stefan

the function pulsein() delivers the pulse-length in microseconds

the if-condition

if ( tCycle > 1000 && tCycle < 1200)

used in the while-loop checks for a measured time between 1 millisecond and 1,2 miliseconds.
this seems strange to me.

The pulses of a servo vary between 1 millisecond and 2 milliseconds.

Some servos go even beyond these "official" boarders like pulse-lengthes bteween 0,8 milliseconds up to 2,4 milliseconds.

I haven't looked up how continous-rotating servos are controlled exactly.

I guess the pulse-length has to be the "middle-value" between the boarders for maximum forward and maximum backward which would be 1,5 milliseconds to make the servo stand still.

From analysing a couple of different normal servos I know that this middlepoint isn't always at exact 1,500 milliseconds.

~~it can vary. So this has to be found by experimentation. ~~

if ( tCycle > 1000 && tCycle < 1200)

is pretty close to max rotation-speed of one direction.

wrong explanation: the measuring is right.
it measures the feedback-PWM. Which has a cycletime of 1,1 milliseconds.

For all futures posting: please provide download-links to the documentation of the hardware that you are using.

best regards Stefan

My friend,
While you browse over the code, you might be able to adjust some lines of code. I am not going to give you a terminate answer. You have to do the work yourself.... no one here is paid to give you a faultless code. Address the issues and post your conclusions.

Gates:
My friend,
While you browse over the code, you might be able to adjust some lines of code. I am not going to give you a terminate answer. You have to do the work yourself.... no one here is paid to give you a faultless code. Address the issues and post your conclusions.

I understand this- thank you for looking it over. Not looking for faultless code, just an idea where I'm going wrong.

At any rate, I decided to switch to a Arduino Uno, which is compatible with the library found here: GitHub - HyodaKazuaki/Parallax-FeedBack-360-Servo-Control-Library-4-Arduino: Arduino library which control Parallax FeedBack 360° High Speed Servo easy.

After a small issue with voltage source, I found myself with a working servo, and the ability to sweep from -180 to 180.

However- I'm running into some further issues which may be hardware related, but I want another set of eyes to make sure I'm not missing something.

Problem 1: Code like this, which (almost) is the example code from the above library, does move the servo as intended, but not consistently. It'll do a cycle with the correct delays, but then pause for 10-20 seconds before repeating. Or it'll do half, and then stop for 5 seconds or so. Very inconsistent.

#include "FeedBackServo.h"
// define feedback signal pin and servo control pin
#define FEEDBACK_PIN 2
#define SERVO_PIN 3

// set feedback signal pin number
FeedBackServo servo = FeedBackServo(FEEDBACK_PIN);

void setup() {
    // set servo control pin number
    servo.setServoControl(SERVO_PIN);
    // set Kp to proportional controller
    servo.setKp(1.0);
}

void loop() {
    // rotate servo to 270 and -180 degrees(with contains +-4 degrees error) each 1 second.
    servo.rotate(270, 4);
    delay(2000);
    servo.rotate(-180, 4);
    delay(2000);
    servo.rotate(270, 4);
    delay(2000);
}

Problem 2: Code like below, which is essentially a standard potentiometer/servo control, works well for 10-15 seconds, just as long as I don't go to the extremes of the potentiometer. After that, it gets stuck in a loop where it just bounces back and forth 5 degrees.

You can see where I wrote in a serial out to monitor the angle, as well as a serial to test the potentiometer code was outputting the correct values. See what you think.

#include "FeedBackServo.h"
#include <Servo.h>
// define feedback signal pin and servo control pin
#define FEEDBACK_PIN 2
#define SERVO_PIN 3

int potpin = 0;  // analog pin used to connect the potentiometer
int val;    // variable to read the value from the analog pin


// set feedback signal pin number
FeedBackServo servo = FeedBackServo(FEEDBACK_PIN);

void setup() {
    // set servo control pin number
    servo.setServoControl(SERVO_PIN);
    // set Kp to proportional controller
    servo.setKp(1.0);
    //serial communication 115200 bps
    Serial.begin(115200);
}

void loop() {
    
  val = analogRead(potpin);            // reads the value of the potentiometer (value between 0 and 1023)
  val = map(val, 0, 1023, -180, 180);  // Adjusts potentiometer to scale with Servo
  Serial.println(val); 
  //servo.rotate(val);
  //Serial.println(servo.Angle());
  delay(15);                       // waits 15ms for the servo to reach the position
  
   
}

You are using a PID-control-algorythm. If the parameters kp, ki, di are set to unfavorable values oscillating occurs.
Which values do perform best depends on a lot of factors including the mechanical properties of the controlled system.

So you have to find out th evalues that perfrom best by experimentation

You shouldn't use pin 0 and pin as they are used for the serial connection. This causes interfering.
Leave pin 0 and pin 1 alone always start with pin 2.
That's why all examples that use IO-pins start with pin 2

best regards Stefan

StefanL38:
You are using a PID-control-algorythm. If the parameters kp, ki, di are set to unfavorable values oscillating occurs.
Which values do perform best depends on a lot of factors including the mechanical properties of the controlled system.

So you have to find out th evalues that perfrom best by experimentation

You shouldn't use pin 0 and pin as they are used for the serial connection. This causes interfering.
Leave pin 0 and pin 1 alone always start with pin 2.
That's why all examples that use IO-pins start with pin 2

best regards Stefan

I see. The example code line servo.setKp (1.0); set Kp to proportional controller Makes a lot more sense.

I assume I'd adjust the number in that line, are we talking in steps of .1 or 1? I'd like to learn more, could you point me in the direction of a good in depth overview?

I also see a file included in the library titled FeedBackServo.cpp (Fix property of setKp). Is this something I can use?

I'm using pin 0 on the analog side, and PWM pins 2 and 3 for feedback and servo, respectively. Is this what you were referencing?

Interesting development- I did some testing and found that it's a specific set of numbers the servo doesn't like. Here is my data from 2 different servos, using the code below (same as above but I added a delay on the analogRead as a smoothing device.)

If approaching from -180 going toward +180, it starts oscillating as soon as it reaches a value of 4. I can move it anywhere between -180 and +4 with no problem, as soon as it crosses +4, it starts oscillating +-5 degrees and I have to re-upload the program.

If approaching from +180 to -180, it will stop around 20. This number was a lot less consistent, across 10 tests, I had results between +11 and +24. as soon as it crosses that number, it starts oscillating and I have to re-upload the program, same as above.

This was duplicated with an identical 10k potentiometer.

Is that helpful or a byproduct of something else?

#include "FeedBackServo.h"
#include <Servo.h>
// define feedback signal pin and servo control pin
#define FEEDBACK_PIN 2
#define SERVO_PIN 3

int potpin = 4;  // analog pin used to connect the potentiometer
int val;    // variable to read the value from the analog pin


// set feedback signal pin number
FeedBackServo servo = FeedBackServo(FEEDBACK_PIN);

void setup() {
    
    servo.setServoControl(SERVO_PIN); // set servo control pin number.
    
    servo.setKp(1.0);  // set Kp to proportional controller
    
    Serial.begin(115200); //serial communication 115200 bps
}



void loop() {
   val = analogRead(potpin);            // reads the value of the potentiometer (value between 0 and 1023)
   //Serial.println(val);
   delay(10);

   if (val < 20000); {
     val = map(val, 0, 1023, -180, 180);  // Adjusts potentiometer to scale with Servo
     Serial.println(val); 
     servo.rotate(val);
     Serial.println(servo.Angle());
     delay(15);                       // waits 15ms for the servo to reach the position
   
     

   }
   
}

I think I found the issue. Using the code below (reads and prints the position to a serial connector) I've found that as I have no reading between +1 and +30 or so. As I turn the servo by hand and it approaches that range, it will report 30 until I get to +1 and then it will go back to reporting the correct position.

It's identical on both servos, so it's either a defect or a problem in the code. If it's a defect, I'm trying to think of a way to ignore that dead zone in the code.

Here's a short video, as well as the test code.

#include "FeedBackServo.h"
// define feedback signal pin
#define FEEDBACK_PIN 2

// set feedback signal pin number
FeedBackServo servo = FeedBackServo(FEEDBACK_PIN);

void setup() {
    // serial communication start with 115200 bps
    Serial.begin(115200);
}

void loop() {
    Serial.print("Now angle: ");
    Serial.println(servo.Angle());
}

I can' t see a video-link.