Is there a simple way to convert servo sketches from angle to position/pulsewidth and viseversa?

I'm sure this is a very basic question but despite spending a good few hours searching I'm having no luck finding an answer.

I'm looking at controlling model railway turnouts using servos, a Mega and PCA9685s. The problem I have is that some scripts use the angle of the servo (i.e. 0 - 180) and some use position (1000 - 2000 i.e. pulse width).

The sketch below is my starting point. It's a turnout calibration sketch used by a YouTuber called Rob on his Little Wicket Railway videos (note: the calibration is running on a Nano linked to a PCA9685):

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

uint8_t servonum = 0; // address of servo to be calibrated
String readString;
int pos;

void setup() {
  Serial.begin(9600);
  pos = 1500; // Initial position
  pwm.begin();
  pwm.setOscillatorFrequency(25000000);
  pwm.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates

  Serial.println("Servo calibration");
  Serial.println("Use this to calibrate your servo to find the range of movement required");
  Serial.println("The servo should start close to the centre of the range");
  Serial.println("Type a value followed by a + to move in one direction or a valu followed by a - to move in the other direction");
  Serial.println("For example 100+ to 200-");
  Serial.println("To move to a specific location use strings like 900x or 1800x for new servo position");
  Serial.println("Move the servo to find the required range for whatever you're operating.");
  Serial.println("Servos min and max can vary, try the 1000 - 2000 range to start with.");
  Serial.println("WARNING: Exceeding the max range could damage the servo.");
  Serial.println();
  pwm.writeMicroseconds(servonum, pos);
  Serial.println("Centre point:");
  Serial.println(pos);
  delay(10);
}

void loop()
{
  while (Serial.available()) {
    char c = Serial.read();  //gets one byte from serial buffer
    readString += c; //makes the string readString
    delay(2);  //slow looping to allow buffer to fill with next character
  }
  if (readString.length() > 0) {

    if (readString.indexOf('x') > 0) {
      pos = readString.toInt();
    }

    if (readString.indexOf('+') > 0) {
      pos = pos + readString.toInt();
    }

    if (readString.indexOf('-') > 0) {
      pos = pos - readString.toInt();
    }

    pwm.writeMicroseconds(servonum, pos);
    Serial.println(pos);
    readString = ""; //empty for next input
  }
}

This works perfectly well but when I found a sketch for moving the turnouts at a more realistic speed it was written in terms of the servo angle rather than the position.

So I thought I'd have a go at editing the original. It can't be that hard......

I subbed in "angle" for "pos" and having read this:
https://www.circuitbasics.com/controlling-servo-motors-with-arduino/
I tried subbing "write" for "writeMicroseconds".

This gave me an error message;
'class Adafruit_PWMServoDriver' has no member named 'write'; did you mean 'write8'?

I tried subbing in 'write8' but this did not appear to be recognised.

This is my sketch as it stands. I'd be most grateful if anyone could point me in the "write" :roll_eyes: direction (or point out any stupid mistakes).

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
// #include <Servo.h>  // added to see if it would allow the use of "write" but it didn't work

Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();

#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

uint8_t servonum = 0; // address of servo to be calibrated
String readString;
int angle;  //  subbed in angle for pos wherever it was found in the sketch

void setup() {
  Serial.begin(9600);
  angle=170; // Initial position (changed from pos=1500)
  pwm.begin();
  pwm.setOscillatorFrequency(25000000);
  pwm.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates

  Serial.println("Servo calibration");
  Serial.println("Use this to calibrate your servo to find the range of movement required");
  Serial.println("The servo should start close to the centre of the range");
  Serial.println("Type a value followed by a + to move in one direction or a value followed by a - to move in the other direction");
  Serial.println("For example 100+ to 200-");  // This needs editing - and any line of text referencing position
  Serial.println("To move to a specific location use strings like 900x or 1800x for new servo position");
  Serial.println("Move the servo to find the required range for whatever you're operating.");
  Serial.println("Servos min and max can vary, try the 1000 - 2000 range to start with.");
  Serial.println("WARNING: Exceeding the max range could damage the servo.");
  Serial.println();
  pwm.write(servonum, angle);
  Serial.println("Centre point:");  
  Serial.println(angle);  
  delay(10);
}

void loop()
  {
  while (Serial.available()) {
    char c = Serial.read();  //gets one byte from serial buffer
    readString += c; //makes the string readString
    delay(2);  //slow looping to allow buffer to fill with next character
  }
  if (readString.length() >0) {

    if(readString.indexOf('x') >0) {
      angle = readString.toInt();
    }

    if(readString.indexOf('+') >0) {
      angle = angle + readString.toInt();
    }

    if(readString.indexOf('-') >0) {
      angle = angle - readString.toInt();
    }

    pwm.write(servonum, angle);  //write subbed in for writeMicroseconds
    Serial.println(angle);
    readString=""; //empty for next input
  }
}

If you've made it this far, thanks!

If you open up the Servo library, you'll see how the write function uses map, and then calls writeMicroseconds.

generally:

500us is = 0 degrees

2500us = 180 degrees

a total of 2000us/180= 11.11us per degree.

The code you present is intended to use an Adafruit PCA9685 board to connect the servos. Is that what you're using? If so you will need to convert angles to microseconds and use the writeMicroseconds() function. A simple formula like microseconds = (angle * 10) + 600 will get you very close but may need some calibration for the specific servos you're using.

But I can't see anything in that code which is controlling the speed that the servo moves at. Which part of your code do you think is doing that?

Steve

Thanks for the rapid responses. I'll take a better look as time allows.

TMFKAAWOL - just to clarify, after a very quick look I have seen where writeMicroseconds is used within the Adafruit Servo Driver Library Servo example. Is that where you mean or should I be looking at the servo library within the main Arduino directory?

Steve - the sketch is just for calibrating the throw/close on turnouts away from the main layout (i.e. on boards under construction. It runs on a portable Nano/PCA9685 setup (also handy if I need to replace any servos along the way).
For control on the layout I am intending to use the sketch you helped Waffenfritz develop some time ago. My intention yesterday was to see if I could make some use of arrays within that sketch (which I think you suggested in the original posts with Waffenfritz). I got slightly side tracked by the angle/position dilemma! Once I have got past arrays, I will be looking to see if I can add relay operation for frog switching.

I very carefully wrote

Why do you use the PCA9685 at all? It makes things more complicated. With the Servo lib you can control the servos directly at the Nano pins. And if you want them to move slowly you can use my MobaTools library. The MoToServo methods are widely compatible to the standard Servo lib, but you can control the speed of the servo by simply setting the speed you want. And you can ask the actual position of the servo while it is moving. I use this to switch a frog relay in the middle of the movement.

Yes, I read that the first time around. Clearly you know exactly what you mean. Unfortunately, I don't. I am in the directory shown below but don't see anything that I can actually read. Maybe it's protected. Maybe I need to look for a program to read the files. Maybe I should just go and have a beer.

Right there in the "src" directory, are some more directories.
If you look in "avr", there are two files.
Open "Servo.cpp" in an editor

Get your beer and then take a look in the src directory.

Hi MicroBahner, thanks for the suggestions. I had the Nano rigged up via a PCA9685 as that is similar to the way I was planning to rig the main layout (Mega plus multiple PCAs, although this could well change). Also I can just plug the servo 3-wire connectors straight in. However, it makes sense to take out the PCA and just use a custom jumper.
I hadn't come across your MobaTools library but I'll take a look. It sounds as if it does exactly what I'm looking for.

I'm in!
TMFKAAWOL - a quick scan though the code shows me writeMircoseconds used here, so that's where I will start playing next.

Wildbill - beer is next :beer:

void Servo::write(int value)
{
  if(value < MIN_PULSE_WIDTH)
  {  // treat values less than 544 as angles in degrees (valid values in microseconds are handled as microseconds)
    if(value < 0) value = 0;
    if(value > 180) value = 180;
    value = map(value, 0, 180, SERVO_MIN(),  SERVO_MAX());
  }
  this->writeMicroseconds(value);
}

void Servo::writeMicroseconds(int value)
{
  // calculate and store the values for the given channel

Maybe a board like this could be a solution for easy connection of th servos:

Edit:

I always prefer to do adjusting the limit values with the 'main layout' too. The set values can then be stored in EEPROM. This allows adjusting at any time, e.g. if you must change the turnout or the servo. And you don't need to transfer the limit values from your 'adjusting hardware' to the main hardware.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.