Specifically the popular SG90 type. Here is a collection of principles that apply to their use in model railways. Some Arduino-based examples (more to follow). As yet I seem not to have come across the reported digital version of the SG90.
Dave (acE)Mason, Sept 2020.
SG90 servos are intended for use in model aircraft. They are also used in robotics. A lot of descriptions, and solutions to buy or build, therefore assume say six servos, with quite short wiring, which must respond as rapidly as possible and simultaneously - quite different from on a model railway.
They have an arm that is rotated by an electric motor via a gearbox. It typically can rotate through about 180 degrees. They have 3 wires. However the motor is not simply powered to run in one direction or the other. Two of the wires supply power - nominally 5V and 0v. The third wire carries a digital control signal, either "low" (near 0V) or "high" (near 5V). This control signal most of the time is "low" but calibrated pulses, short periods "high", are sent to move the servo. A single pulse 1.5ms (milliseconds) long will move the arm to the centre of its range of travel.
Inside the servo a potentiometer on the output shaft provides to the servo electronics a feedback Voltage proportional to the arm position. The servo electronics compare this (position/Voltage) to the length of the incoming control pulse and send a short burst of power to the motor which is calibrated to remove the error/difference and put the arm in a position corresponding to the pulse length. In practice a single pulse does not do the job exactly - depending on how hard the motor has to work overcoming friction, springs etc, and other factors such as supply Voltage and temperature.
For that reason pulse are typically sent in a continous stream, one pulse every 20ms. If the pulse length is constant the servo arm may reach the desired position by the third or fourth pulse. In model arcraft and robotics the length of the pulses is more likely being continuously changed providing a moving target for the servo to try to follow. The 20ms interval dates from the early days of Radio Control and is now merely a convention. You can send pulses whenever you like - though results may become unpredictable if the gap between pulses is less than a couple of ms.
Having said that 1.5ms pulses put the servo arm at the central position - say 90degrees - this is how different pulse lengths typically affect the position: 0.5ms = 0degrees, 1.0ms = 45degrees, 1.5ms = 90degrees, 2.0ms = 135degrees, 2.5ms = 180degrees - and all points in between.
There are several options:
const int servoPin = 19;
byte pin_state;
long pulse_us = 1500; // range 500 - 2500
void setup() {
pinMode(servoPin, OUTPUT);
pin_state = 1;
digitalWrite(servoPin, pin_state);
delayMicroseconds(pulse_us);
pin_state = 0;
digitalWrite(servoPin, pin_state);
}
void loop() {
}
const int servoPin = 19;
const long min_us = 1000;
const long max_us = 2000;
byte pin_state;
long pulse_us = 1500; // range 500 - 2500
long step_us = 4;
long interval_us = 20000; // range around 5000 upwards
void setup() {
pinMode(servoPin, OUTPUT);
}
void loop() {
if (pulse_us <= min_us or max_us <= pulse_us) {
step_us = -step_us; // changes direction
}
pulse_us += step_us;
pin_state = 1;
digitalWrite(servoPin, pin_state);
delayMicroseconds(pulse_us);
pin_state = 0;
digitalWrite(servoPin, pin_state);
delayMicroseconds(interval_us);
}
const int servoPin = 19;
const long min_us = 1000;
const long max_us = 2000;
byte pin_state;
long pulse_us = 1500; // range 500 - 2500
long step_us = 4;
long interval_us = 20000; // range around 5000 upwards
long pulse_begin_us;
long now_us;
void setup() {
pinMode(servoPin, OUTPUT);
}
void loop() {
now_us = micros(); // the time since Reset
if (pin_state==0) {
if ((now_us - pulse_begin_us) > interval_us) {
if (pulse_us <= min_us or max_us <= pulse_us) {
step_us = -step_us; // changes direction
}
pulse_us += step_us;
pin_state = 1;
digitalWrite(servoPin, pin_state);
pulse_begin_us = now_us;
}
} else { // pin_state==1
if ((now_us - pulse_begin_us) > pulse_us) {
pin_state = 0;
digitalWrite(servoPin, pin_state);
}
}
}
const int servoPin = 19;
const long min_us = 500;
const long max_us = 2500;
const long serialBaud = 115200; // set the Serial Monitor to the same rate
const byte pulses = 16;
byte pin_state;
long pulse_us = 1500; // range 500 - 2500
long step_us = 4;
long interval_us = 20000; // range around 5000 upwards
long pulse_begin_us;
long now_us;
long target_us = 1500;
byte pulse_counter;
void setup() {
pinMode(servoPin, OUTPUT);
Serial.begin(serialBaud); // Serial.begin causes MCU reset within 10ms.
delay(100); // allows Serial to settle
}
void loop() {
now_us = micros(); // the time since Reset
if (pin_state==0) {
if (Serial.available() > 0) {
String inStr = Serial.readString();
long new_us = long(inStr.toInt());
if (new_us < min_us or max_us < new_us) {
Serial.println(inStr+" is out of range");
} else {
target_us = new_us; // change position
pulse_counter = 0;
Serial.println(target_us);
}
}
if (pulse_us != target_us and pulse_counter < pulses) {
if ((now_us - pulse_begin_us) > interval_us) {
pin_state = 1;
digitalWrite(servoPin, pin_state);
pulse_begin_us = now_us;
if (pulse_us == target_us) {
pulse_counter++ ;
} else if (pulse_us < target_us) {
pulse_us = min(target_us, pulse_us + step_us);
} else { // if (pulse_us> target_us) {
pulse_us = max(target_us, pulse_us - step_us);
}
}
}
} else { // pin_state==1
if ((now_us - pulse_begin_us) > pulse_us) {
pin_state = 0;
digitalWrite(servoPin, pin_state);
}
}
}
The example above uses the Arduino Serial Monitor to allow the user to change values (and to provide feedabck to the user), This is useful for learning and for developing a sketch/program to work the way you want. In operation the required actions could be triggered by the state of another of the Arduino digital input pins.
Operating several servos can mean simultaneously, as in a model aircraft, or it can mean in groups, as in a 4-gate crossing, or it can just mean the Arduino is connected to 15 points/turnouts but only needs to operate one at a time - others can wait in a short queue. The challenge is to accurately time the end of each pulse.
Suppose you want to control 4 servos running at the same time - for crossing gates. You will have fine-tuned the start and end angles. At any time each of the 4 servos probably require a different length pulse. One trick is to sort the servos into order, the one with the shortest pulse first, longest last.
You then start the pulses at, say, 100us intervals (takes 300us if there are 4 servos) and then you have 200us to wait for the time to end the first pulse - if it has the very minimum pulse length of 500us. After that you have at least 100us to wait for the time to end the next servo's pulse - since it was started 100us after the previous servo's pulse and is longer. And so on.
With 4 servos and a maximum pulse length of 2500us it's all over in 2800us. The microcontroller can be tied up during that period watching for when to end pulses but after that it can be assigned other tasks without compromising the pulse timing accuracy, such as looking for input and sorting the servos for the next round of pulses. In principle this can be extended to hundreds of servos and the limitation is storing all the data in the Arduino.