Parallax 360 degree feedback servo motor

Hello,

Has anyone played around with Parallax 360 degree feedback servo motors and if so would they be willing to post working code here so I and others like myself can get an idea of how to program them, info on this motor + arduino is pretty scarce so any advice or shared work would be greatly appreciated.

Thanks all :slight_smile:

Did you search? There are many examples, both on the forum search and on google.

It would be a good idea for you to read through the sticky post at the top of this and every forum named How to use this forum - please read.

Item 4 looks like it applies here.

Hi,

Yeap I did search, I just made the post open ended to hopefully encourage more input.

The most useful information that I have come across for this particular motor + arduino was some code posted by @JavelinPoint on this forum post:

https://forum.arduino.cc/index.php?topic=516986.0

I have tried to play around and adjust this code to attempt to make it behave the way I need it to which is, using a variable determined by a compass module to control the rotational position of the servo.

Ill post my code here for others to inspect:
The dependent variable is target_angle which in the original code from the other forum controlled the position it rotated to.

Thanks in advance for any help

#include <Servo.h>            //Servo library
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h>


#include <Adafruit_Sensor.h>
#include <Adafruit_HMC5883_U.h>

//PID angle control for Parallax 360 Servo
Servo servo_test;        //initialize a servo object for the connected servo 
TinyGPSPlus gps;
SoftwareSerial ss(11,10);

static const uint32_t GPSBaud = 9600;
Adafruit_HMC5883_Unified mag = Adafruit_HMC5883_Unified(12345);

int pinFeedback = 5;
               
int target_angle;
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;
float headingDegrees;

unsigned long previousMillis = 0;
const long interval = 3000;



void displaySensorDetails(void)
{
 sensor_t sensor;
 mag.getSensor(&sensor);
 Serial.println("------------------------------------");
 Serial.print  ("Sensor:       "); Serial.println(sensor.name);
 Serial.print  ("Driver Ver:   "); Serial.println(sensor.version);
 Serial.print  ("Unique ID:    "); Serial.println(sensor.sensor_id);
 Serial.print  ("Max Value:    "); Serial.print(sensor.max_value); Serial.println(" uT");
 Serial.print  ("Min Value:    "); Serial.print(sensor.min_value); Serial.println(" uT");
 Serial.print  ("Resolution:   "); Serial.print(sensor.resolution); Serial.println(" uT");  
 Serial.println("------------------------------------");
 Serial.println("");
 delay(500);
}

void setup() 
{ 
 Serial.begin(9600);
 servo_test.attach(6);      // attach the signal pin of servo to pin9 of arduino
 pinMode(5, INPUT);   
 ss.begin(GPSBaud);


 Serial.println(F("Simple Test with TinyGPS++ and attached NEO-6M GPS module"));
 Serial.print(F("Testing TinyGPS++ library v. ")); Serial.println(TinyGPSPlus::libraryVersion());
 Serial.println();

 displaySensorDetails();
} 
 
void loop() 
{ 
 
 while (ss.available() > 0)
   if (gps.encode(ss.read()))
     displayGpsInfo();

 
 //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.

 

 unsigned long currentMillis = millis();
 if (currentMillis - previousMillis >= interval){

   previousMillis = currentMillis;

 int target_angle = headingDegrees;
 
 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

 delay(5000);
 }

 
}




void displayGpsInfo()
{
 Serial.print(F("Location: ")); 
 if (gps.location.isValid())
 {
   Serial.print(gps.location.lat(), 6);
   Serial.print(F(","));
   Serial.print(gps.location.lng(), 6);
 }

 else
 {
   Serial.print(F("INVALID"));
 }

 Serial.print(F("  Date/Time: "));
 
 if (gps.date.isValid())
 {
   Serial.print(gps.date.month());
   Serial.print(F("/"));
   Serial.print(gps.date.day());
   Serial.print(F("/"));
   Serial.print(gps.date.year());
 }
 else
 {
   
   Serial.print(F("INVALID"));
 }

 Serial.print(F(" "));
 if (gps.time.isValid())
 {
   if (gps.time.hour() < 10) Serial.print(F("0"));
   Serial.print(gps.time.hour());
   Serial.print(F(":"));
   if (gps.time.minute() < 10) Serial.print(F("0"));
   Serial.print(gps.time.minute());
   Serial.print(F(":"));
   if (gps.time.second() < 10) Serial.print(F("0"));
   Serial.print(gps.time.second());
   Serial.print(F("."));
   if (gps.time.centisecond() < 10) Serial.print(F("0"));
   Serial.print(gps.time.centisecond());
 }
 else
 {
   // Print invalid otherwise.
   Serial.print(F("INVALID"));
 }
 Serial.println();
 if(mag.begin())
 {
   Serial.println("");
   displayCompassInfo();
 }
 Serial.println("");
 Serial.println("");
 Serial.println("");
}

void displayCompassInfo()
{
 sensors_event_t event; 
 mag.getEvent(&event);

 Serial.print("X: "); Serial.print(event.magnetic.x); Serial.print("  ");
 Serial.print("Y: "); Serial.print(event.magnetic.y); Serial.print("  ");
 Serial.print("Z: "); Serial.print(event.magnetic.z); Serial.print("  ");Serial.println("uT");

 float heading = atan2(event.magnetic.y, event.magnetic.x);
 

 float declinationAngle = 0.05;
 heading += declinationAngle;
 
 // Correct for when signs are reversed.
 if(heading < 0)
   heading += 2*PI;
   
 // Check for wrap due to addition of declination.
 if(heading > 2*PI)
   heading -= 2*PI;
  
 // Convert radians to degrees for readability.
int headingDegrees = heading * 180/M_PI; 
 
 Serial.print("Heading (degrees): "); Serial.println(headingDegrees);

 
 delay(500);
}

Can you edit your post and put your code inside of code tags? It makes it much easier for others to load into an editor and examine.

#include <Servo.h>            //Servo library
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h>

#include <Adafruit_Sensor.h>
#include <Adafruit_HMC5883_U.h>

You are a brave coder! I am paranoid about conflicts with multiple libraries. Many times they try to use the same resources and give unpredictable results. SoftwareSerial and Servo likely try to use the same timer.

I am not sure I have a clear understanding of the problems you are having with the servo.
Can you restate the issue? Often it is helpful to explain what your expected behavior is, and how the actual results differ from that expectation.

Another tip. Break down the problem. Can you modify the know tutorial to work with your servo? That reads a potentiometer and moves the servo. This would confirm that you have the servo code working properly before you tackle trying ti integrate it with all your other hardware and libraries.

Hi,

Sorry about the code, didn't realise to use the code tags.

I did attempt to connect and map a potentiometer to move the servo as a test, but admittedly had no success, I noticed that if I altered the target angle, the rotation would correspond to that position so I attempted to use the compass readings to update the target variable.

The desired behave would be the servo to rotate to the corresponding compass reading and maintain that position until it receives the next compass reading, however the behavior with provided code results in the servo doing a few incremental movements then begin to rotate continuously without stopping

I did attempt to connect and map a potentiometer to move the servo as a test, but admittedly had no success,

I think that this is the place to start. (without any compass involved)
Can you post that code?
I am not sure I understand the expected behavior and the actual behavior. Can you describe that again?

This is the code which tries to use a potentiometer to control the desired angle, without the compass.

#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 = 5;
                
int target_angle;
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;
int potpin = 3;



 
void setup() 
{ 
  Serial.begin (9600);
  servo_test.attach(6);      // attach the signal pin of servo to pin9 of arduino
  pinMode(5, INPUT);
} 
  
void loop() 
{ 

 target_angle = analogRead(potpin);
 target_angle = map(target_angle,0,359,0,359);
 
  
  
  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

  Serial.println(val);
  

  }

This is the source code which I'm trying to work off, credit to @JavelinPoint, in this code if you adjust the target angle between 0 - 359 the servo will accurately move / point to that position.

#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 = 5;
                
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() 
{ 
  Serial.begin (9600);
  servo_test.attach(6);      // attach the signal pin of servo to pin9 of arduino
  pinMode(5, 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

  Serial.println(val);


  
}

The desired behavior I'm trying to achieve is to have the target angle determined by the compass readings and have it point in that direction, whats currently happening is the motor will make a few slight turns (no specific position) and then begin to randomly spin continuously without stopping.

1 Like

Did you find out more about controlling the servo with Arduino-Code?

Looks to me so far that it does not behave like a normal continuous servo, 0 beeing full speed CCW, 90 stop and 180 full speed CW.

The behaviour of my servo with servo.write (using arduino servo library):

93 stopped
97.. 110 slower to faster CCW, using higher values does visually not change movement speed
89..75 slower to faster CW, using lower values does visually not change movement speed

I check for progress every 20 ms. I see deltas of 2..13 degrees per 20ms, resulting in a very jerky arm movement. Can't see at the moment what is causing this. The demo videos with the parallax controller board do not show such jerky movements.

I am running this on a Arduino Due, timing should not be a problem