Parts

Servo (hobby, SG90 style)

The nine-gram hobby servo, SG90 style: three wires, and a horn that walks to the angle a pulse width tells it.

This part has a page of its own, with a note on using it and a circuit to try: the part page.

The signal on PWM is not PWM in the dimming sense; the duty cycle means nothing. It is a pulse width, repeated fifty times a second. By default the part reads it the way the Arduino Servo library writes it: 544 microseconds puts the horn at 0 degrees, 2400 microseconds at 180, and 1472 microseconds, which is what write(90) sends, in the middle.

Pins

Pin What it does
GND Ground, brown wire.
VCC Supply, red wire.
PWM Signal, orange wire: a pulse width, not a duty cycle.

Properties

min_pulse / max_pulse: the pulse widths for the two ends of travel, 544 and 2400 microseconds by default, the Arduino Servo library's MIN_PULSE_WIDTH and MAX_PULSE_WIDTH. range: degrees of travel, 180 by default. speed: how fast the horn moves towards a new angle, 600 degrees per second.

Sketch Pulse Horn, with the defaults
write(0) 544 us 0 degrees
write(90) 1472 us 90 degrees
write(180) 2400 us 180 degrees

A servo that wants another window, 1000 to 2000 microseconds say, is attach(pin, 1000, 2000) in the sketch and the same two numbers in min_pulse and max_pulse, so the two agree on where each angle is.

idle_ma / moving_ma: the supply current, 10 and 150 mA at 5 V.

mode: positional or continuous (below). rpm, neutral and dead_band are for the continuous kind only.

Models

model picks a servo by name, or custom (the default) for the settings below it. A named model sets the speed, the mode, the rpm and the dead band from its maker's page; the pulse range and range stay yours, because they are what attach() says.

Model What it sets
SG90, SG92R 0.1 s per 60 degrees at 4.8 V, a 10 µs dead band
MG90S 0.10 s at 4.8 V and 0.08 s at 6 V, following the supply between, a 10 µs dead band
FS90R, SG90-360 continuous rotation, 110 rpm (the 4.8 V figure), 1500 µs stop

The MG996R is its own part. The MG995, the DS3218 and the MG996R's 360 degree version are not here: no maker's figures for them could be read.

What the model gets right

The part is a stopwatch on one pin and a horn that walks towards the angle the stopwatch names: a rising edge starts the clock, a falling edge stops it, a width past either end of the min_pulse to max_pulse window is that end (the horn stops against its end stop), a pulse longer than 3 ms is treated as noise and ignored, and with no pulses at all the horn stays exactly where it is, the way a real servo does when its signal wire falls off. speed matches the datasheet's 0.1 seconds per sixty degrees. A servo that only sweeps 120 degrees, or one set up for 1000 to 2000 us, is those three numbers changed and nothing else.

Supply current

Between VCC and GND the servo is a load, so a bench supply with a multimeter in its lead (dca) reads what the servo draws: about 10 mA with the horn still and about 150 mA while it moves, both quoted at 5 V and following the supply from there. idle_ma and moving_ma set the two figures (SG90 sheets give 5 to 10 mA idle and 100 to 250 mA running). The load changes only when the horn starts or stops moving. It applies when a bench supply (psu, a battery) feeds the servo; on a board's own 5V pin the servo behaves as before.

What it does not model

The stall current (about 650 mA for an SG90 with the horn held against a stop): nothing here can hold the horn, so a servo pinned at the end of its window just arrives and goes idle. Nor the start-up surge, nor a brown-out of the board that powers it: Mokxi's rails have no impedance to sag. Below 4 V across VCC/GND the servo does not move at all.

No torque, no load and no overshoot. The horn walks to its target at a constant 600 degrees a second and stops dead. There is no acceleration, no settling, no hunting around the target and no gear backlash. A real SG90 ignores changes under about 10 microseconds of pulse width, the datasheet's dead band width. On custom there is no positional dead band, so a commanded angle is always reached. The named models ignore a change of 10 microseconds or less, about a degree on the default window, so a very small step can leave the horn where it was. The 0.1 seconds per sixty degrees is the datasheet's unloaded figure and applies here whatever is on the horn, because there is nothing on the horn.

The stopwatch is also more forgiving than a real servo's: the horn is updated every 20 ms whatever the pulse rate is, a pulse longer than 3 ms is discarded rather than treated as a fault, and a gap in the pulse train is simply held rather than released.

Code

The Arduino library's own calls work as they are on every board that compiles in the browser:

#include <Servo.h>

Servo myservo;

void setup() {
  myservo.attach(9);
}

void loop() {
  for (int pos = 0; pos <= 180; pos++) {
    myservo.write(pos);
    delay(15);
  }
}

Servo.h is Mokxi's own, on the same driver as mokxi_servo.h, with the upstream library's widths. attach(pin) uses 544 to 2400 microseconds and attach(pin, min, max) sets another window; write(angle) spreads 0 to 180 across the window, so write(0), write(90) and write(180) send 544, 1472 and 2400 microseconds; writeMicroseconds() passes a width straight through, held to the attached window; and read() gives the angle back. One driver serves every board, so the numbers are the same on the AVR boards, the Pico, the STM32 boards, the SAMD21 XIAO and the Uno R4 alike.

One thing differs from the upstream library: each write() sends one pulse rather than starting a timer that pulses for ever. The part holds the last width it was sent, as a real servo does with nothing pushing on the horn. On the bench, under load, call myservo.refresh() from loop() to keep the frame coming.

ESP32Servo.h is the same class, for ESP32 tutorials, with ESP32Servo's numbers: attach(pin) uses its DEFAULT_uS_LOW and DEFAULT_uS_HIGH, 544 and 2400 microseconds, and attach(pin, min, max) holds the window to 500 to 2500.

Projects saved before the window changed to 544 to 2400 open with it: a servo or robot arm that still had the old 1000 and 2000 microsecond defaults is moved onto the new ones, so write() lands where it says. A sketch that times its own pulses for a 1000 to 2000 microsecond servo now turns the horn a shorter way; set the servo's min_pulse and max_pulse back to 1000 and 2000 for it, and that choice is kept.

Continuous rotation

Set mode to continuous and the part is the other servo in the parts bin, the FS90R kind: the same case and wires, with the end stops removed and the position sensor replaced by a fixed middle. A pulse now names a speed. The servo's neutral, 1500 microseconds by default, is stopped; 500 microseconds either side of it (1000 and 2000) is full speed one way and the other, rpm (110 by default, the FS90R's figure at 4.8 V); in between is in proportion, and further out, as far as 544 and 2400, stays at full speed. A wider pulse turns it counterclockwise. The probe is then the speed in rpm, positive counterclockwise, and the horn spins on the canvas.

Why write(90) stops it. With the Arduino library's widths, write(90) sends 1472 microseconds, not 1500. A real continuous servo stops anyway, because it has a stop zone around its neutral, set by the factory trim (the small trimmer some have is for moving it). The part models that as dead_band: any pulse within 35 microseconds of neutral, 1465 to 1535 by default, reads as stopped. 1472 is 28 microseconds short of neutral, inside the band with room for the Uno's 4 microsecond timing step. write(85) (1420 microseconds) creeps one way and write(95) (1523) stays stopped; set dead_band to 0 to see how a servo with no stop zone at all creeps at write(90), or set neutral to match a servo trimmed another way.

A real one stops within a frame or two when the pulses stop. This one keeps the last speed it was sent, because Mokxi's Servo library sends one pulse per write(), and write(180); delay(2000); means two seconds of turning. The neutral, the dead band and the full-speed pulse width vary from servo to servo on the bench, which is why some have a trimmer; here they are exact.

Common mistakes

Powering several servos from a board's 5V pin and expecting it to sag. The supply current shows on a bench supply with a meter, but a board rail does not brown out here, which is worth knowing before trusting the simulation for a power budget.

See it in action

Servo fader drives the horn from a slide potentiometer. Open it at /templates.

The RC plane elevon and V-tail mixer sets two servos to the RC window, 1000 to 2000 microseconds, with min_pulse and max_pulse.