Sketch to find the pulse range of a servo (help, simplification, explanation)

Hello,
I work with the sketch below and try to understand:

/*
 * Simple Arduino sketch to find the full pulsewidth range of a servo
 *
 * INSTRUCTIONS
 *
 * Watch the serial port, and press a key each time the servo starts/stops moving regularly.
 *
 * If you're using the Arduino IDE "Serial Monitor", you'll need to type something into the
 * text field and then press "Send"
 *
 * When you're done, the sketch will output the low and high pulse widths, a sample attach() line, and then
 * start sweeping from top to bottom so you can check it works and/or measure the total angle.
 *
 * Ignore any massive jumping movements when searching, servos often seem to move to a random angle when
 * you send a really out-of-range signal to them.
 *
 *
 * This sketch is released under the GNU General Public License v2, (C) 2009 Angus Gratton
 */

// Assuming the angle position servo motor is already powered, and digital pin 10 arduino is connected to the servo's PWM control signal INPUT line.
// Use the serial communications monitor. The software starts with pulse width modulation using short pulse-width duration, eg 100 microsecond width to start with.
// No motor response expected due to widths being outside the active range. Iteratively increase the pulse width (increments of +64 microseconds)...eg 164, 228 microseconds etc.
// Eventually the servo will move. This is when we need to press a key (or type a letter in the serial monitor, and press ENTER). This stores the minimum duration required to make the servo respond.
// The ramping continues until we get to a width level where the motor stops responding. At this point, press a key again. This stores the maximum pulse width.

// MG966R angle servos typically have pulse width 1.5 millisecond (1500 MICROsecond) for a mid-angle position

#include <Servo.h>
#include <stdio.h>

char buffer[128]; // This wastes a bunch of RAM, but we don't need it for this sketch
#define printfLn(format, args... ) \
  snprintf(buffer, sizeof(buffer), format, ## args); \
  Serial.println(buffer);


/*
 * Set this to the pin your servo is hooked to (9 or 10.)
 */
const int servopin = 10;

Servo myservo;

void setup()
{
  int del = 64;
  Serial.begin(9600);
  
  Serial.println("Searching for low pulse width");
  int low = search_low(100, del);  //starting pulse duration, incremental additive value, boolean value to trigger a print statement word
  
  Serial.println("Got low pulse width...");
  delay(2000);
  
  Serial.println("Searching for high pulse width");
  int high = search_high(low, del) - del;  //the minus is for removing the latest recorded value, as the latest one is invalid.

  Serial.println();
  Serial.println();
  printfLn("Got low pulse width of %d", low);
  printfLn("Got high pulse width of %d", high);
  Serial.println();
  printfLn("Example code: myservo.attach(%d, %d, %d);", servopin, low, high);
  
  Serial.println();
  Serial.println("Done (sweeping.)");
  
  myservo.attach(servopin, low, high);
}

int search_low(int value, int delta)  //
{
   Serial.println("Press any key when the servo %s moving starts");
   delay(5000);
   value = sweep(value, delta);  // this is a looping function
   return value;
   delay(5000);  
}

int search_high(int value, int delta)  //
{
   Serial.println("Press any key when the servo %s moving stops - ie when it doesn't respond to the most recent pulse width command");
   delay(5000);
   value = sweep(value, delta);  // this is a looping function
   return  value;
   delay(5000);
}

int sweep(int value, int delta)
{
   int value_store = value;
   value = value - delta;
   while(Serial.available()<=0)
   {
     value = value + delta;
     myservo.attach(servopin);
     myservo.writeMicroseconds(value);
     Serial.println(value);
     delay(5000);   // put the delay here.... not after the detach line, since it is important to attach servos before they move, and to detach after they have finished moving.... ie.. not during transitions or activity...otherwise leads to operating problems.
     myservo.detach();


     if (value >= 3000) {  // start the pulse width ramping cycle again once we've gone past a certain level.
      value = value_store;
     }
   }
   while(Serial.available()>0)
     Serial.read();
  return value;
}

void loop()
{
   // Just loop through a few working values
   while(true)
   {
     myservo.writeMicroseconds(700);
     delay(4000);
     myservo.writeMicroseconds(1500);
     delay(4000);
     myservo.writeMicroseconds(2400);
     delay(4000);
     myservo.writeMicroseconds(1500);
     delay(4000);
   }
}

I could read that:

printf (String format, Object ... args)

What is the object of the argument of the following line:

#define printfLn (format, args ...) \

What does (##) mean from the following line:

snprintf(buffer, sizeof(buffer), format, ## args); \

After in the code:

printfLn ("Got low pulse width of% d", low);

Few also write:

print ("Got low pulse width of");
printLn (low);

(% d) seems related to (args ...)

Is it possible to write all this more simply for the uninitiated like me?
Or can you explain to me?
Best regards

Someone did a whole lot of preprocessor magic. "args ..." is magic for accept unlimited amount of arguments. And they are copied to "## args".

I would say, cool to learn how it works, use this code as is, don't try to do in your own code :wink: He could have made a propper function for it :slight_smile:

Second is just a printf formatter.

An appropriate function would make the code easier to understand for beginners?
If so, would anyone be interested to do it?

No, not going to rewrite mediocre code. Why do you want it changed? What is your goal?

I would like to use as much code as possible in my project that can be understood and explained to teenagers whom I supervise outside the school periods.

This isn't code I would use to teach. There are just to many bad practices in the code. For teaching I would really start over or Google a bit more to find better code.

But I think the first is the quickest :slight_smile: The idea is pretty simple, just start low and increase the pulse with step by step until you see movement. Same (but inverse) for the higher end.

The teenagers that I frame, begin learning in C ++, which is not my case!
I am more specialized in mechanical design and 3D.
Personally I use arduino in my projects without necessarily mastering the entire code, as long as the desired result is present.
I do not have enough language to create my own program. I can adapt when it is simple and above all well documented.
This code is already complex in the way it was coded.
I hope I can find help or an alternative with you.

But if you don't understand what the code does and how it works or how to rewrite it, how can you learn your teenagers that?

Aka, do you want them to understand the code? Or just use the code? If it's the first, I think you need to understand the code and why you do it in a certain way before you can even try to teach it. Like I said, the code is written pretty terrible and complex but the whole idea is pretty simple. So writing your own code should not be to hard. But if you do, try not to look at this mess because it will confuse you :wink:

The code seems to be doing its job properly and I would like them to use it, but I think they will have questions about the code I can not answer.
They asked me if it was possible to take over a project on which the servos were undersized.
For these new servos it seems logical to define precisely their field of action.

Try this:

/*************************************
Simple Arduino sketch to find the full pulse width range of a servo.

It does so by starting with a low pulse width and to increase it in small steps. Once
you see movement the minimal pulse width is reached. Press "Send" in the serial monitor to 
indicate that to the program.

Note: The code is written in a simple blocking way and not optimized. It's a terrible
startingpoint to use in your own code!

INSTRUCTIONS

- Set Serial Monitor to 115200 baud
- Set Serial Monitor to "Both NL & CR"
- Connect a servo to 'ServoPin' (default: pin 10)
- Press "Send" when the Servo starts moving (for both lower and upper limit)
- When the limits are reached, the servo will alternate between them
- Press reset to start over
*************************************/

#include <Servo.h>

const byte ServoPin = 10;
//Minimal pulse width to try for lower limit (in ms)
const unsigned int StartLower = 200;
//maximal pulse width to try for upper limit (in ms)
const unsigned int StartUpper = 2500;
//Number to increment the pulse width during search (in ms)
const unsigned int StepSize = 50;
//time between increments (in ms)
const unsigned int StepInterval = 1000;

unsigned int lowerLimit;
unsigned int upperLimit;
Servo testServo;

void setup(){
  Serial.begin(115200);
  
  Serial.println("Servo pulse width finder sketch");
  Serial.println();
  Serial.println("Be sure to set Serial monitor to \"Both NL & CR\".");
  Serial.println("Press \"Send\" when the servo starts moving during the search.");
  Serial.println();
  
  //move servo already to lower end and attach it
  testServo.write(0);
  testServo.attach(ServoPin, StartLower, StartUpper);
  
  Serial.print("Searching for lower limit");
  lowerLimit = sweep(StartLower, StepSize, StepInterval);
  printResult("Lower", lowerLimit);
  
  Serial.print("Searching for upper limit");
  //minus the 'StepSize' to go the other way
  upperLimit = sweep(StartUpper, -StepSize, StepInterval);
  printResult("Upper", upperLimit);

  Serial.println("Press reset to test again.");
}

void loop(){
  testServo.writeMicroseconds(lowerLimit);
  delay(2000);
  testServo.writeMicroseconds(upperLimit);
  delay(2000);
}

unsigned int sweep(unsigned int start, int delta, unsigned int interval){
  testServo.writeMicroseconds(start);
  delay(interval);
  unsigned int value = start;
  
  //stop the search if we receive something on Serial
  while(!Serial.available()){
    //increase the value to the new position
    value += delta;
    
    //stop searching if we go beyond the start positions of the search (= Failed)
    if((value < StartLower) || (value > StartUpper)){
      Serial.println();
      return 0;
    }
    
    //otherwise move to new position
    testServo.writeMicroseconds(value);
    Serial.print(".");
    delay(interval);
  }
  
  clearSerialBuffer();
  Serial.println();
  
  //
  return value;
}

void printResult(char *text, unsigned int value){
  //value found
  if(value != 0){
    Serial.print(text);
    Serial.print(" limit: ");
    Serial.print(value);
    Serial.println("ms");
  }
  //value not found
  else{
    Serial.print(text);
    Serial.println(" limit not found!");
    Serial.println("Press reset to try again.");
    
    //and stop here
    while(true);
  }
}

void clearSerialBuffer(){
  //clear serial buffer (but do nothing with it)
  while(Serial.available()){
    Serial.read();
  }
}

Think this is way more understandable.

Thank you for this sketch, I am already much more comfortable!
I just tested.
When the servo starts to move by pressing send the monitor gives me the lowerLimit and the servo goes to that position.

When he starts to move again pressing send the monitor gives me the upperLimit and servo goes to that position.

Then a sweep between these values begins.

I did not understand how to restart the search (Press reset to test again.) Does not seem to be declared.

I did not understand name anymore the use of text commands

Thanks for your help

Reset, that little button on your Arduino (labeled reset) :wink:

Oops, I was looking for that in the code. What is not possible, it seems to me.
Thank you