Jump to content

Servo Gauge Calibrator


No1sonuk

Recommended Posts

Not sure if here is the right place for this, but here goes:
I thought I'd share a device I've made for quickly working out the servo times for gauge positions.
I don't have photos of mine at the moment because I made it on breadboard and dismantled it when I was done.
 

Here's the schematic:

EC9RKw6.png

 

Here's the Arduino Code:

 

/* Servo calibration program
 *  Designed to be used to determine the times of servo pulses for setting up 
 *  flight simulator gauges.
 *  
 *  Pin assignments are for Arduino Nano.  Other arduinos my work without modifying
 *  the code, but you may need to adjust pin numbers dependent on function.
 *  
 *  The display used is a 20x4 character LCD with I2C backpack.
 *  The two ceramic 100nF capacitors kill noise from the rotary switch.
 *  
 *  Potentiometer code is included, but disabled as the rotary encoder 
 *  is more reliable.
 *  
 *  Written by Tim Jacobs
 *  Publicised May 2021
 */


#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 20 chars and 4 line display

#include <Servo.h> 

Servo myservo;

byte vertline0[8] = {16,16,16,16,16,16,16,};  // Custom character bitmaps for bars
byte vertline1[8] = {8,8,8,8,8,8,8,};
byte vertline2[8] = {4,4,4,4,4,4,4,};
byte vertline3[8] = {2,2,2,2,2,2,2,};
byte vertline4[8] = {1,1,1,1,1,1,1,};

unsigned int ServoTimeOutput;
unsigned int ServoTimeOutputLast;
unsigned int ServoTimeMin;
unsigned int ServoTimeMax;
unsigned int ServoTimeMid;
unsigned int manualpos;

int ServoTimeIncrement;

int bar_charNo;
int bar_number;
int bar_character;


#define ServoPin 6   // Servo output pin (PWM pin)
#define PotPin A3    // NOT USED
#define encoder0PinA 2 // Encoder input A (Interrupt pin)
#define encoder0PinB 3 // Encoder input B (Interrupt pin)
#define encoder0Btn 7  // Encoder button input (Any input pin)

// A4 and A5 are automatically defined for I2C use by the WIRE library

int encoder0PinALast = LOW;
int n = LOW;

int ButtonState;
int ButtonStateLast;
int incrementButtonClicks;

void setup() {

  pinMode(encoder0PinA, INPUT_PULLUP);
  pinMode(encoder0PinB, INPUT_PULLUP);
  pinMode(encoder0Btn, INPUT_PULLUP);

  attachInterrupt(0, encoderRead, CHANGE);
  attachInterrupt(1, encoderRead, CHANGE);

    lcd.init();                      // initialize the lcd 

    lcd.createChar(0, vertline0);  // Custom character definition
    lcd.createChar(1, vertline1);
    lcd.createChar(2, vertline2);
    lcd.createChar(3, vertline3);
    lcd.createChar(4, vertline4);
    
    lcd.backlight();

    lcd.setCursor(0,0);  //  column, row

    ServoTimeMin = 500;  // safe defaults
    ServoTimeMax = 2300;
    ServoTimeMid = ((ServoTimeMax-ServoTimeMin)/2)+ServoTimeMin;
    
   ServoTimeOutput = ServoTimeMid;  //  set servo to mid-point

   incrementButtonClicks = 1;  // Set default increment digit 0 = 1, 3 = 1000

  myservo.attach(ServoPin);
  myservo.writeMicroseconds(ServoTimeOutput);  // 

}

void loop() {

// PotInput();

incrementButtonRead();
TimeBarDisplay();

  
 delay(10);

}



void incrementButtonRead(){
  ButtonState = digitalRead(encoder0Btn);

  
  if (ButtonState != ButtonStateLast){

    if (ButtonState == LOW){

    incrementButtonClicks = incrementButtonClicks + 1;
    if (incrementButtonClicks >=4){incrementButtonClicks = 0;}
  }
   ButtonStateLast = ButtonState;

  switch (incrementButtonClicks) {
  case 0:
    ServoTimeIncrement = 1;
    break;
  case 1:
    ServoTimeIncrement = 10;
    break;
  case 2:
    ServoTimeIncrement = 100;
    break;
   case 3:
    ServoTimeIncrement = 1000;
    break;
}

manualCursorBlink();  // Sets manual mode cursor position to blink
}

}

//  Potentiometer input not used
void PotInput(){
  manualpos = analogRead(PotPin);
  ServoTimeOutput = map(manualpos, 0, 1023, ServoTimeMin, ServoTimeMax);
  
}

void TimeBarDisplay(){


if (ServoTimeOutput >= ServoTimeMax){
    ServoTimeOutput = ServoTimeMax;
  }
  
  if (ServoTimeOutput <= ServoTimeMin){
    ServoTimeOutput = ServoTimeMin;
  }

  if (ServoTimeOutput != ServoTimeOutputLast){


//  lcd.clear();  // May take too much time.

  lcd.setCursor(0,1);
  lcd.print("Servo output:  "); // Clear the line
  if (ServoTimeOutput >= 1000){
    lcd.setCursor(14,1);
    }
  else{
      lcd.setCursor(15,1);
    }

  lcd.print(ServoTimeOutput);
  lcd.print("us  "); 

  lcd.setCursor(0,2);
  lcd.print(ServoTimeMin);

  ServoTimeMid = ((ServoTimeMax-ServoTimeMin)/2)+ServoTimeMin;

  lcd.setCursor(8,2);
  lcd.print(ServoTimeMid);

  lcd.setCursor(16,2);
  lcd.print(ServoTimeMax);

  
  lcd.setCursor(0,3);
  lcd.print("                    "); // Clear the line

  bar_number = map(ServoTimeOutput, ServoTimeMin, ServoTimeMax, 0, 99);
  bar_charNo = bar_number/5;
 
  bar_character = (bar_number) - (bar_charNo * 5);

  lcd.setCursor(bar_charNo,3);
  lcd.write (bar_character);

  myservo.writeMicroseconds(ServoTimeOutput);  
  ServoTimeOutputLast = ServoTimeOutput;

  manualCursorBlink();  // Sets manual mode cursor position to blink

  }
}

void manualCursorBlink() {  // Sets manual mode cursor position to blink
 lcd.cursor();  // Straight underline cursor
//  lcd.blink();     // Full block flashing cursor

switch (ServoTimeIncrement) {
  case 1:
   lcd.setCursor(17,1);
    break;
  case 10:
   lcd.setCursor(16,1);
    break;
  case 100:
   lcd.setCursor(15,1);
    break;
   case 1000:
   lcd.setCursor(14,1);
    break;
}

  
}


void encoderRead() {
  n = digitalRead(encoder0PinA);
  if ((encoder0PinALast == LOW) && (n == HIGH)) {
    if (digitalRead(encoder0PinB) == HIGH) {
      ServoTimeOutput = ServoTimeOutput - ServoTimeIncrement;
      
    } else {
      ServoTimeOutput = ServoTimeOutput + ServoTimeIncrement;
    }
  }
  encoder0PinALast = n;
}

 

Usage:
Connect the device to the servo that drives a gauge.
When the device first starts, it has some "safe" min and max servo times set. These can be changed if you want, but I just went with the numbers given.
To adjust the time, turn the rotary encoder and the selected digit will change, and also the servo output.
Pressing the rotary encoder button changes the selected digit.
You can then adjust the time until the gauge needle reaches the required end positions and note the times to put into the DCS servo lines.

Note that if your servo draws too much current, it could overload the arduino's regulator and "brown-out" the arduino.  In this case, you might have to drive the circuit (or just the servo) off an external 5V supply.

I hope that helps someone.  I may one day get around to building a permanent version

  • Thanks 1
Link to comment
Share on other sites

On 5/28/2021 at 9:02 PM, crash test pilot said:

Wonderful! Thanks for a really handy tool! A note: even the small 9g servo draws up to 700 mA, which is far too much for a nano (max 200mA), so an external power source is a must.

Agreed.  Though I've not had much trouble driving one or two 9g servos from a Nano - they don't tend to be pulling hard when used in gauges.
I may have just been lucky.

Link to comment
Share on other sites

A few years ago I invested in about $2000 worth of Flight Illusion gauges and use them with P3D and X-Plane. Does anyone know how or where I might find someone who might be able to create a software interface to drive them?

Link to comment
Share on other sites

On 6/2/2021 at 2:43 PM, DH14300 said:

A few years ago I invested in about $2000 worth of Flight Illusion gauges and use them with P3D and X-Plane. Does anyone know how or where I might find someone who might be able to create a software interface to drive them?

You should probably create your own new topic so more people could see your question.

Link to comment
Share on other sites

On 6/4/2021 at 1:06 PM, DH14300 said:

This question was intentionally directed to gauge builders.

This part of the forum is pretty much ALL gauge builders, and you'll get more of a response in a separate thread because people skip threads they're not interested in.
It's entirely possible someone out there knows the answer to your question, but isn't reading this topic.

It would also make it easier for others to find the info later.  Unless you're one of those annoying people who delete their posts after getting the info they want...

Link to comment
Share on other sites

  • 3 months later...
On 5/28/2021 at 12:29 AM, No1sonuk said:

I may one day get around to building a permanent version

"One day" has arrived...
GJNvjZ1.jpg


This is behind the face:
YgGUYCO.jpg

 

A close-up of the Arduino and encoder:
4ItbT9w.jpg
The screw in the corner of the Arduino is securing it in the holder.

In order to make the wiring a bit easier, I switched around some signals.
This includes using D11 for the servo as that meant I could wire that into the ICSP header holes.

New schematic:
mYd41CM.jpg

 

And the new code:
 

/* Servo calibration program
 *  Designed to be used to determine the times of servo pulses for setting up 
 *  flight simulator gauges.
 *  
 *  Pin assignments are for Arduino Nano.  Other arduinos my work without modifying
 *  the code, but you may need to adjust pin numbers dependent on function.
 *  
 *  The display used is a 20x4 character LCD with I2C backpack.
 *  The two ceramic 100nF capacitors kill noise from the rotary switch.
 *  
 *  Potentiometer code is included, but disabled as the rotary encoder 
 *  is more reliable.
 *  
 *  Written by Tim Jacobs
 *  Publicised September 2021
 */


#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27,20,4);  // set the LCD address to 0x27 for a 20 chars and 4 line display

#include <Servo.h> 

Servo myservo;

byte vertline0[8] = {16,16,16,16,16,16,16,};  // Custom character bitmaps for bars
byte vertline1[8] = {8,8,8,8,8,8,8,};
byte vertline2[8] = {4,4,4,4,4,4,4,};
byte vertline3[8] = {2,2,2,2,2,2,2,};
byte vertline4[8] = {1,1,1,1,1,1,1,};

unsigned int ServoTimeOutput;
unsigned int ServoTimeOutputLast;
unsigned int ServoTimeMin;
unsigned int ServoTimeMax;
unsigned int ServoTimeMid;
unsigned int manualpos;

int ServoTimeIncrement;

int bar_charNo;
int bar_number;
int bar_character;


#define ServoPin 11   // Servo output pin (PWM pin)(Also available on the ICSP header) 
#define PotPin A3    // NOT USED
#define encoder0PinA 3 // Encoder input A (Interrupt pin)
#define encoder0PinB 2 // Encoder input B (Interrupt pin)
#define encoder0Btn 4  // Encoder button input (Any input pin)

// A4 and A5 are automatically defined for I2C use by the WIRE library

int encoder0PinALast = LOW;
int n = LOW;

int ButtonState;
int ButtonStateLast;
int incrementButtonClicks;

void setup() {

  pinMode(encoder0PinA, INPUT_PULLUP);
  pinMode(encoder0PinB, INPUT_PULLUP);
  pinMode(encoder0Btn, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(encoder0PinA), encoderRead, CHANGE);
  attachInterrupt(digitalPinToInterrupt(encoder0PinB), encoderRead, CHANGE);

    lcd.init();                      // initialize the lcd 

    lcd.createChar(0, vertline0);  // Custom character definition
    lcd.createChar(1, vertline1);
    lcd.createChar(2, vertline2);
    lcd.createChar(3, vertline3);
    lcd.createChar(4, vertline4);
    
    lcd.backlight();

    lcd.setCursor(0,0);  //  column, row

    ServoTimeMin = 400;  // safe defaults
    ServoTimeMax = 2500;
    ServoTimeMid = ((ServoTimeMax-ServoTimeMin)/2)+ServoTimeMin;
    
   ServoTimeOutput = ServoTimeMid;  //  set servo to mid-point

   incrementButtonClicks = 1;  // Set default increment digit 0 = 1, 3 = 1000

  myservo.attach(ServoPin);
  myservo.writeMicroseconds(ServoTimeOutput);  // 

}

void loop() {

// PotInput();

incrementButtonRead();
TimeBarDisplay();

  
 delay(10);

}



void incrementButtonRead(){
  ButtonState = digitalRead(encoder0Btn);

  
  if (ButtonState != ButtonStateLast){

    if (ButtonState == LOW){

    incrementButtonClicks = incrementButtonClicks + 1;
    if (incrementButtonClicks >=4){incrementButtonClicks = 0;}
  }
   ButtonStateLast = ButtonState;

  switch (incrementButtonClicks) {
  case 0:
    ServoTimeIncrement = 1;
    break;
  case 1:
    ServoTimeIncrement = 10;
    break;
  case 2:
    ServoTimeIncrement = 100;
    break;
   case 3:
    ServoTimeIncrement = 1000;
    break;
}

manualCursorBlink();  // Sets manual mode cursor position to blink
}

}

//  Potentiometer input not used
void PotInput(){
  manualpos = analogRead(PotPin);
  ServoTimeOutput = map(manualpos, 0, 1023, ServoTimeMin, ServoTimeMax);
  
}

void TimeBarDisplay(){


if (ServoTimeOutput >= ServoTimeMax){
    ServoTimeOutput = ServoTimeMax;
  }
  
  if (ServoTimeOutput <= ServoTimeMin){
    ServoTimeOutput = ServoTimeMin;
  }

  if (ServoTimeOutput != ServoTimeOutputLast){


//  lcd.clear();  // May take too much time.

  lcd.setCursor(0,1);
  lcd.print("Servo output:  "); // Clear the line
  if (ServoTimeOutput >= 1000){
    lcd.setCursor(14,1);
    }
  else{
      lcd.setCursor(15,1);
    }

  lcd.print(ServoTimeOutput);
  lcd.print("us  "); 

  lcd.setCursor(0,2);
  lcd.print(ServoTimeMin);

  ServoTimeMid = ((ServoTimeMax-ServoTimeMin)/2)+ServoTimeMin;

  lcd.setCursor(8,2);
  lcd.print(ServoTimeMid);

  lcd.setCursor(16,2);
  lcd.print(ServoTimeMax);

  
  lcd.setCursor(0,3);
  lcd.print("                    "); // Clear the line

  bar_number = map(ServoTimeOutput, ServoTimeMin, ServoTimeMax, 0, 99);
  bar_charNo = bar_number/5;
 
  bar_character = (bar_number) - (bar_charNo * 5);

  lcd.setCursor(bar_charNo,3);
  lcd.write (bar_character);

  myservo.writeMicroseconds(ServoTimeOutput);  
  ServoTimeOutputLast = ServoTimeOutput;

  manualCursorBlink();  // Sets manual mode cursor position to blink

  }
}

void manualCursorBlink() {  // Sets manual mode cursor position to blink
 lcd.cursor();  // Straight underline cursor
//  lcd.blink();     // Full block flashing cursor

switch (ServoTimeIncrement) {
  case 1:
   lcd.setCursor(17,1);
    break;
  case 10:
   lcd.setCursor(16,1);
    break;
  case 100:
   lcd.setCursor(15,1);
    break;
   case 1000:
   lcd.setCursor(14,1);
    break;
}

  
}


void encoderRead() {
  n = digitalRead(encoder0PinA);
  if ((encoder0PinALast == LOW) && (n == HIGH)) {
    if (digitalRead(encoder0PinB) == HIGH) {
      ServoTimeOutput = ServoTimeOutput - ServoTimeIncrement;
      
    } else {
      ServoTimeOutput = ServoTimeOutput + ServoTimeIncrement;
    }
  }
  encoder0PinALast = n;
}


The hardware is fixed now, but I may still add functions to the code.

Model and image files here:
https://www.thingiverse.com/thing:4965237
 

  • Thanks 1
Link to comment
Share on other sites

2 hours ago, Vinc_Vega said:

Thank you Tim, that seems to be a handy tool 👍

The other black box in the first picture is a power bank, am I right? (battery power for an USB port)

 

Regards, Vinc

Yes.
It's an old 4-LED Tecknet IEP1500 dual output power bank.
It should run OK on a 1A or more phone charger with miniUSB lead.

Link to comment
Share on other sites

  • 1 year later...

Hi, great job on the tool!

Just a quick one for you, I am really struggling to understand the wiring on the rotary.

Any chance of a close up pic so i can see where the wires and the resistors go.

Thanks very much

 

Screenshot 2022-09-29 173516.png

PC Specs:

Asus ROG Strix Z370-E Motherboard

4x 8Gb corsair vengance 3000Mhz RAM

ROG STRIX 1080Ti Graphics

Processor Intel® Core i7-8700K CPU @ 3.70GHz, 3701 Mhz, 6 Core(s), 12 Logical Processor(s)

DCS runs on its own 1Tb 970 EVO M.2 SSD

Oculas Rift S

Thrustmaster Hotas Warthog setup.

Link to comment
Share on other sites

Awesome, Thankyou very much 👍

PC Specs:

Asus ROG Strix Z370-E Motherboard

4x 8Gb corsair vengance 3000Mhz RAM

ROG STRIX 1080Ti Graphics

Processor Intel® Core i7-8700K CPU @ 3.70GHz, 3701 Mhz, 6 Core(s), 12 Logical Processor(s)

DCS runs on its own 1Tb 970 EVO M.2 SSD

Oculas Rift S

Thrustmaster Hotas Warthog setup.

Link to comment
Share on other sites

Hi Tim,

unit all built now and the screen comes on with nothing on the display! What will I expect to see when powering up? Or do I need to do something else to start the code?

5734CD8C-2ECB-48A6-B9C6-31EE6EBF4941.jpeg

PC Specs:

Asus ROG Strix Z370-E Motherboard

4x 8Gb corsair vengance 3000Mhz RAM

ROG STRIX 1080Ti Graphics

Processor Intel® Core i7-8700K CPU @ 3.70GHz, 3701 Mhz, 6 Core(s), 12 Logical Processor(s)

DCS runs on its own 1Tb 970 EVO M.2 SSD

Oculas Rift S

Thrustmaster Hotas Warthog setup.

Link to comment
Share on other sites

Thanks, I found the problem, Just a loose female connector! story of my life.

Working now thanks

  • Like 1

PC Specs:

Asus ROG Strix Z370-E Motherboard

4x 8Gb corsair vengance 3000Mhz RAM

ROG STRIX 1080Ti Graphics

Processor Intel® Core i7-8700K CPU @ 3.70GHz, 3701 Mhz, 6 Core(s), 12 Logical Processor(s)

DCS runs on its own 1Tb 970 EVO M.2 SSD

Oculas Rift S

Thrustmaster Hotas Warthog setup.

Link to comment
Share on other sites

So, I was having issues with the burning out of a servo in an instrument I made and now your device has given me the ability to accurately enter the extremes of the instrument in the code!

The instrument is now perfectly aligned and works flawlessly.

Thanks so much, Ill post a make on thingiverse 👏

  • Like 1
  • Thanks 1

PC Specs:

Asus ROG Strix Z370-E Motherboard

4x 8Gb corsair vengance 3000Mhz RAM

ROG STRIX 1080Ti Graphics

Processor Intel® Core i7-8700K CPU @ 3.70GHz, 3701 Mhz, 6 Core(s), 12 Logical Processor(s)

DCS runs on its own 1Tb 970 EVO M.2 SSD

Oculas Rift S

Thrustmaster Hotas Warthog setup.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...