Components · 9 min read

360 Degree Servo (FS90R): Speed and Direction with Arduino

How does the FS90R 360 degree servo work? Stopping with write(90), speed and direction control, the stop trimmer, use as a robot wheel, ESP32 and Pico PWM code.

A 360 degree servo may be called a servo, but it's really a small geared motor that keeps spinning rather than moving to an angle and stopping there. The best-known one is the FEETECH FS90R: it has the same size and the same three wires as an SG90, but the signal you send sets its speed and direction of rotation instead of an angle. Because it connects straight to an Arduino pin with no motor driver, it's very handy for small robot wheels, turntables and things like pet feeders.

In this guide you'll learn what's different inside the FS90R compared with a normal servo, why the write(90) command stops the motor, how to set speed and direction with pulse width, and how to use the stop trimmer to fix a servo that keeps creeping. Then you'll drive a small robot with two FS90Rs, control speed with a potentiometer and try the same thing on an ESP32 and a Raspberry Pi Pico. Finally you'll see why this servo can't do position control and what to choose instead.

FS90R 360 degree continuous rotation servo
FS90R continuous rotation servo: SG90-sized, 9 grams. The signal sets the speed and direction of rotation, not the angle.

What is a 360 degree servo and how is it different from a normal servo?

A normal servo contains a small DC motor, a gearbox, a control circuit and a potentiometer linked to the output shaft. The control circuit compares the angle requested by the incoming pulse with the actual angle read from the potentiometer and turns the motor until the difference is zero; once the shaft reaches the target, it stops. In a continuous rotation servo this feedback link is broken: the potentiometer is disconnected from the shaft and fixed in its middle position, and the mechanical stop in the gears is removed. The circuit still calculates the "difference between target and actual position", but since the actual position never changes, the difference never closes. As a result, the bigger the difference, the faster the motor turns, and the sign of the difference sets the direction.

FeatureSG90 (180° servo)FS90R (360° continuous rotation)
What does the signal set?The shaft angle (0–180°)Speed and direction of rotation
1500 µs pulseMoves to the middle (90°) and stays thereStops
1000 µs / 2000 µsMoves to one end or the otherFull speed one way or the other
Internal feedbackPotentiometer linked to the shaftNone; the potentiometer only sets the stop point
Mechanical stopYesNo, it turns endlessly
Holding positionKeeps its angle under loadCan't; it doesn't know where it stopped
Typical useArms, lids, steeringWheels, turntables, screw feeders

FS90R specifications

FeatureValue
Operating voltage4.8–6 V
No-load speedAbout 100–110 rpm at 4.8 V, about 130 rpm at 6 V
Stall torqueAbout 1.3 kg·cm at 4.8 V, about 1.5 kg·cm at 6 V
Current100–130 mA with no load, up to 800 mA at 6 V when stalled
Control signal50 Hz PWM; stop at 1500 µs, usable range about 900–2100 µs
Signal levelWorks with 5 V and 3.3 V signals
DirectionAbove 1500 µs one way, below it the other (seen from the output shaft, above 1500 µs is counterclockwise)
Weight and size9 g, about 23 × 12 × 22 mm (same body as the SG90)
WiresBrown GND, red VCC, orange signal
Not every product sellers call a "360 degree servo" is a continuous rotation servo. Some servos hold a position between 0 and 360° (or 0–270°); they don't spin endlessly like the FS90R but move to the requested angle and stop. Look for continuous rotation in the product description. Also, forcing an FS90R while it's stalled for a long time, by hand or against an obstacle, ruins the motor and the plastic gears.

FS90R wiring: Arduino Uno, ESP32 and Pico

Servo wireFunctionArduino UnoESP32Raspberry Pi Pico
BrownGNDGNDGNDGND
RedPower 4.8–6 V5V (single servo) or external 5 VVIN / 5V or external 5 VVBUS (5 V) or external 5 V
OrangePWM signalD9GPIO 18GP15

The Uno's 5V pin is enough to run a single FS90R with no load. When the servo's wheel touches the ground or two servos start up together, the current jumps to a few hundred milliamps; the USB voltage sags, the Arduino may reset or the servos jitter. So on a robot, power the servos from a separate 5 V source (4 NiMH cells, a 5 V regulator or a suitable power supply), always connect the servo supply's GND to the Arduino's GND and add a 470 µF capacitor near the servo wires. The 3.3 V signal of the ESP32 and Pico is enough for the FS90R; the servo itself must still be powered from 5 V, not from the 3.3 V pin.

write(90) = stop: how does pulse width turn into speed?

The servo control signal is a pulse sent 50 times a second (every 20 ms), and what matters is the pulse's length. For the FS90R, 1500 µs means "stop". As the pulse gets longer than 1500 µs, the servo speeds up in one direction; as it gets shorter, it speeds up in the other; it reaches full speed around 1000 µs and 2000 µs, and beyond that the speed barely increases. That's why write(90) in Arduino's Servo library means stop, and write(0) and write(180) mean full speed in each direction. There's a small trap here: by default the library maps 0–180 to the 544–2400 µs range, so write(90) actually outputs 1472 µs. That difference can be enough to make the servo creep even at write(90). The fix is to narrow the range with attach(9, 1000, 2000) or to use writeMicroseconds(1500) directly.

PulseServo.write equivalent (with attach(pin, 1000, 2000))FS90R behaviour
1000 µswrite(0)Full speed, clockwise
1300 µswrite(54)Medium speed, clockwise
1461 µswrite(83)Very slow, clockwise
1500 µswrite(90)Stop (if the trimmer is set correctly)
1538 µswrite(97)Very slow, counterclockwise
1700 µswrite(126)Medium speed, counterclockwise
2000 µswrite(180)Full speed, counterclockwise

First sketch: forward, stop, backward

C++ (Arduino IDE)
1// FS90R ilk test: ileri, dur, geri / FS90R first test: forward, stop, backward
2#include <Servo.h>
3
4Servo teker;                         // sürekli dönüş servosu / continuous rotation servo
5
6void setup() {
7  teker.attach(9, 1000, 2000);       // write(0..180) artık 1000..2000 µs üretir / write(0..180) now outputs 1000..2000 µs
8}
9
10void loop() {
11  teker.write(180);                  // tam hız bir yöne / full speed one way
12  delay(2000);
13  teker.write(90);                   // 1500 µs: dur / 1500 µs: stop
14  delay(1000);
15  teker.write(0);                    // tam hız ters yöne / full speed the other way
16  delay(2000);
17  teker.write(110);                  // yavaş / slow
18  delay(2000);
19  teker.writeMicroseconds(1500);     // durmanın en kesin yolu / the most precise way to stop
20  delay(1000);
21}

The sketch attaches the servo to D9 and, with attach(9, 1000, 2000), maps the write() scale onto the range the FS90R actually uses, so write(90) is exactly 1500 µs. Speed doesn't follow the pulse linearly: right around 1500 µs there's a dead band of a few tens of microseconds where the servo doesn't move, within 1500 ± 300 µs the speed rises quickly, and towards the ends it stays almost constant. If you want slow, precise movement, keep the values close to 1500 µs. The general idea of pulse width is in What is PWM, and angle control with normal servos in What is a servo motor.

Adjusting the stop (centre) trimmer

  1. Upload the calibration sketch below; the servo receives a 1500 µs signal and should normally stop.
  2. If the servo creeps, find the small hole on the side of the case near the cable exit; inside is the trimmer that sets the stop point.
  3. Turn the trimmer a tiny amount with a small flat or Phillips screwdriver; if the servo slows down, you're going the right way.
  4. Keep going in small steps until the servo stops completely and even the faint hum disappears. The trimmer is very sensitive; even a quarter turn is too much.
  5. If you'd rather not touch the trimmer, send + and - from the Serial Monitor to find the pulse value at which the servo stops, and use that value instead of 1500 in your code.
C++ (Arduino IDE)
1// Durma noktası ayarı: Serial Monitor'den µs değeri gönder
2// Stop point calibration: send a µs value from the Serial Monitor
3#include <Servo.h>
4
5Servo teker;
6int darbe = 1500;                    // başlangıç: teorik durma noktası / start: theoretical stop point
7
8void setup() {
9  Serial.begin(9600);
10  teker.attach(9);
11  teker.writeMicroseconds(darbe);
12  Serial.println(F("Sayi yaz (900-2100) ya da + / - gonder"));
13  Serial.println(F("Type a number (900-2100) or send + / -"));
14}
15
16void loop() {
17  if (Serial.available()) {
18    char c = Serial.peek();
19    if (c == '+') { Serial.read(); darbe += 5; }          // 5 µs artır / add 5 µs
20    else if (c == '-') { Serial.read(); darbe -= 5; }     // 5 µs azalt / subtract 5 µs
21    else if (isDigit(c)) darbe = Serial.parseInt();       // doğrudan değer / direct value
22    else { Serial.read(); return; }                       // satır sonu vb. atla / skip line endings etc.
23
24    darbe = constrain(darbe, 900, 2100);
25    teker.writeMicroseconds(darbe);
26    Serial.print(F("Darbe / pulse: "));
27    Serial.print(darbe);
28    Serial.println(F(" us"));
29  }
30}
The stop point can drift by a few microseconds with supply voltage and temperature; don't be surprised if the servo starts creeping slightly again as the batteries run down. For long pauses where the servo must stay completely still, you can cut the signal entirely with detach(): an FS90R with no signal stops and draws almost no current. Just call attach() again when it needs to move.

Speed and direction control with a potentiometer

C++ (Arduino IDE)
1// Potansiyometreyle hız ve yön: ortada dur, sağa ileri, sola geri
2// Speed and direction with a pot: centre = stop, right = forward, left = backward
3#include <Servo.h>
4
5const int POT_PIN = A0;
6const int OLU_BOLGE = 40;            // ortadaki ±40 adım: kesin dur / ±40 steps in the middle: solid stop
7Servo teker;
8
9void setup() {
10  teker.attach(9);
11}
12
13void loop() {
14  int okuma = analogRead(POT_PIN) - 512;                   // -512 ... +511
15  int darbe = 1500;                                        // varsayılan: dur / default: stop
16  if (okuma > OLU_BOLGE) {
17    darbe = map(okuma, OLU_BOLGE, 511, 1540, 2000);        // ileri, yavaştan hızlıya / forward, slow to fast
18  } else if (okuma < -OLU_BOLGE) {
19    darbe = map(okuma, -OLU_BOLGE, -512, 1460, 1000);      // geri / backward
20  }
21  teker.writeMicroseconds(darbe);
22  delay(20);                                               // servo periyodu kadar / one servo period
23}

With the potentiometer connected between 5V, A0 and GND, the middle position means stop, turning right means forward and turning left means backward. The code defines a dead zone of ±40 steps in the middle, so the servo stops reliably even if the potentiometer isn't exactly centred or the readings wobble a little. Outside the dead zone the value is mapped to 1540–2000 µs or 1460–1000 µs, so turning the knob a little makes the servo turn slowly and turning it all the way gives full speed. You can apply the same idea with a joystick; the potentiometer wiring is in What is a potentiometer.

The FS90R as a small robot wheel

The FS90R is so popular in robots because it's simple: with the gearbox and driver circuit built in, you need no separate motor driver, H-bridge or encoder; one signal pin per wheel is enough. The two servos are mounted on either side of the chassis as mirror images, so to drive forward one must turn clockwise and the other counterclockwise. The code below handles this inside the surus() function; you just give a speed between -100 and +100. A 60 mm wheel turning at 130 rpm on 6 V covers about 0.4 metres per second; at 4.8 V that drops to around 0.3 m/s.

C++ (Arduino IDE)
1// İki FS90R ile küçük robot: tekerlekler ayna gibi takılır
2// A small robot with two FS90Rs: the wheels are mounted as mirror images
3#include <Servo.h>
4
5Servo sol, sag;
6const int DUR_SOL = 1500;            // kalibrasyonla bulduğun değerler / values you found during calibration
7const int DUR_SAG = 1500;
8
9// hiz: -100 (tam geri) ... +100 (tam ileri) / speed: -100 (full back) ... +100 (full forward)
10void surus(int hizSol, int hizSag) {
11  sol.writeMicroseconds(DUR_SOL + hizSol * 5);             // sol teker normal yönde / left wheel normal direction
12  sag.writeMicroseconds(DUR_SAG - hizSag * 5);             // sağ teker ters takılı / right wheel is mounted reversed
13}
14
15void setup() {
16  sol.attach(9);
17  sag.attach(10);
18  surus(0, 0);
19  delay(1000);
20}
21
22void loop() {
23  surus(80, 80);    delay(1500);     // ileri / forward
24  surus(0, 0);      delay(500);      // dur / stop
25  surus(-60, 60);   delay(600);      // yerinde sola dön / spin left in place
26  surus(60, 60);    delay(1000);
27  surus(30, 80);    delay(1500);     // sola doğru yay çiz / curve to the left
28  surus(-80, -80);  delay(1000);     // geri / backward
29  surus(0, 0);      delay(2000);
30}

Two servos never turn at exactly the same speed; if your robot drifts to one side instead of going straight, calibrate each servo's stop value (DUR_SOL, DUR_SAG) separately and reduce the factor of the faster one slightly. The FS90R's 1.5 kg·cm of torque suits small robots weighing no more than a few hundred grams; for a heavier chassis or big wheels, a DC motor with an L298N or geared motors are the better choice. On batteries, 4 NiMH cells (4.8 V) give the ideal voltage; 4 fresh alkaline cells can reach 6.4 V and exceed the servo's limit. For a full chassis and sensor build, see Building an Arduino robot car.

You can't do position control with an FS90R. With nothing left inside the servo to measure where the shaft is, you can't say "turn 90 degrees and stop"; time-based moves such as "turn for 1 second" also give a different result each time depending on battery voltage, load and temperature. If you need to reach a specific angle, use a normal servo, a stepper motor for precise step-by-step rotation, or add an encoder to the wheel to count its turns. Which motor to pick and when is covered in DC vs servo vs stepper motors.

FS90R with the ESP32

Arduino's Servo library doesn't work on the ESP32; install the ESP32Servo library from the Library Manager instead. It's used almost the same way, except that you set 50 Hz explicitly with setPeriodHertz(50). Almost any GPIO that can output PWM works for the signal; only GPIO 34–39, which are input-only, can't be used for this. Connect the servo's red wire to the ESP32's VIN (USB 5 V) pin or an external 5 V supply, not to the 3V3 pin. The code below ramps the servo gently from standstill to full speed, then slows it down and reverses it; the Serial Monitor shows the pulse length at each step.

C++ (Arduino IDE)
1// ESP32 + FS90R: ESP32Servo kütüphanesiyle hız rampası
2// ESP32 + FS90R: a speed ramp with the ESP32Servo library
3#include <ESP32Servo.h>
4
5const int SERVO_PIN = 18;            // PWM verebilen herhangi bir GPIO / any PWM-capable GPIO
6Servo teker;
7
8void setup() {
9  Serial.begin(115200);
10  teker.setPeriodHertz(50);          // standart servo frekansı / standard servo frequency
11  teker.attach(SERVO_PIN, 1000, 2000);
12  teker.writeMicroseconds(1500);     // dur / stop
13  delay(1000);
14}
15
16void loop() {
17  for (int us = 1500; us <= 2000; us += 25) {   // yavaşça hızlan / speed up gently
18    teker.writeMicroseconds(us);
19    Serial.println(us);
20    delay(200);
21  }
22  for (int us = 2000; us >= 1000; us -= 25) {   // yavaşla, dur, ters yöne hızlan / slow down, stop, reverse
23    teker.writeMicroseconds(us);
24    Serial.println(us);
25    delay(200);
26  }
27  teker.writeMicroseconds(1500);
28  delay(2000);
29}

FS90R with the Raspberry Pi Pico and MicroPython

On the Pico you need no extra library: set the machine.PWM object to 50 Hz and give the pulse length in nanoseconds with duty_ns(). The hiz() function in the code converts a value between -100 and +100 into 1000–2000 µs; just put the stop value you found during your own calibration on the DUR_US line. When the program is stopped with Ctrl+C, the servo is stopped too; otherwise the PWM keeps running with the last value and the wheel keeps turning. Power the servo from the Pico's VBUS pin (the USB's 5 V). If you're new to MicroPython, read MicroPython basics first.

MicroPython
1# Raspberry Pi Pico (MicroPython) + FS90R: hız -100 ... +100
2# Raspberry Pi Pico (MicroPython) + FS90R: speed -100 ... +100
3from machine import Pin, PWM
4import time
5
6servo = PWM(Pin(15))
7servo.freq(50)                        # 20 ms periyot / 20 ms period
8DUR_US = 1500                         # kalibrasyonla bulduğun durma noktası / your calibrated stop point
9
10def hiz(yuzde):
11    yuzde = max(-100, min(100, yuzde))
12    us = DUR_US + yuzde * 5           # -100 -> 1000 µs, +100 -> 2000 µs
13    servo.duty_ns(us * 1000)
14
15try:
16    while True:
17        for h in (50, 100, 0, -50, -100, 0):
18            hiz(h)
19            print("Hiz / speed:", h)
20            time.sleep(2)
21except KeyboardInterrupt:
22    hiz(0)                            # durdurunca teker dönmeye devam etmesin / stop the wheel on exit
23    servo.deinit()

360 degree servo project ideas

  • Mini line follower: A light chassis with two FS90Rs and two infrared sensors; the logic is in line follower robot.
  • Obstacle-avoiding robot: Add an HC-SR04 at the front so it spins in place and changes direction when it sees an obstacle.
  • Phone-controlled car: Send commands from your phone with an HC-05 Bluetooth module.
  • Pet feeder: Let the FS90R turn an auger screw so food drops at set times on a timer.
  • Display turntable: A very slowly turning platform for product photos or time-lapse shots; set the speed with values close to 1500 µs.
  • Small winch or curtain puller: Wind up a light cord, with two limit switches at the ends to stop the motor in the right place.

Common problems and fixes

SymptomLikely causeFix
It creeps slowly even with write(90)The default scale outputs 1472 µs or the stop trimmer is offUse attach(pin, 1000, 2000) or writeMicroseconds(1500), adjust the trimmer
The servo doesn't turn at allWrong signal pin, GND not shared, or the product is actually a positional servoCheck the pin number and GND connection, make sure it's a continuous rotation servo
The servo jitters and the Arduino resetsUSB power can't handle the current peaksPower the servos from an external 5 V supply, join the GNDs, add a 470 µF capacitor
Changing the pulse from 1000 µs to 1200 µs barely changes the speedNear the ends the servo is already at full speed; speed saturatesFor fine speed control use the 1500 ± 300 µs range
The robot doesn't go straightThe two servos differ in speed and stop pointCalibrate each servo separately, reduce the factor of the faster one
The servo gets hot and hums but doesn't turnThe shaft is blocked or the load is too heavyRemove the obstruction, lighten the load; stalling ruins the gears and motor
Compile error or no signal on the ESP32The AVR-only Servo library was used or GPIO 34–39 was chosenInstall the ESP32Servo library, pick a GPIO that can output

The FS90R is the easiest way to control a continuously turning mechanism with a single pin and no motor driver: it stops at 1500 µs, turns both ways at whatever speed you like and is a ready-made wheel motor for small robots. Set the stop point once, remember to switch to a normal servo or a stepper for jobs that need position control, and it will run trouble-free for a long time. In the BlueGrays app you'll find servo basics, servo control with a potentiometer and a joystick, building a 360 degree servo from a stepper motor and robot car projects, with step-by-step wiring diagrams and ready-to-use code.

Frequently asked questions

How do you stop a 360 degree servo?

Continuous rotation servos like the FS90R stop when they receive a 1500 µs pulse; on Arduino you use writeMicroseconds(1500), or write(90) after attaching with attach(pin, 1000, 2000). If it still creeps at that value, adjust the small trimmer on the case with a screwdriver or use the value at which your servo really stops in your code.

What is the difference between a 360 degree servo and a normal servo?

A normal servo moves to a specific angle according to the signal and stays there, because the potentiometer inside measures the shaft position. A 360 degree continuous rotation servo has no such feedback; the signal sets speed and direction instead of an angle, and the servo turns endlessly.

Can a 360 degree servo do angle control?

No. The FS90R doesn't know where its shaft is, so it can't move to a specific angle and stop. Turning it for a set time gives an approximate result, but it varies with voltage and load. If you need an angle, use a normal servo, a stepper motor or a motor with an encoder.

What voltage does the FS90R run on?

The FS90R runs on 4.8–6 V; in Arduino projects it's usually powered from 5 V. Its signal line accepts both 5 V and 3.3 V levels, so it can be used directly with the ESP32 and Raspberry Pi Pico, but the power supply must still be 5 V.

How many FS90Rs can I connect to an Arduino?

The Servo library supports up to 12 servos on an Arduino Uno, but the real limit is current. Each FS90R can draw a few hundred milliamps under load and up to 800 mA when stalled, so always power more than two servos from an external 5 V supply and join the GNDs.

Can an SG90 be converted into a 360 degree servo?

Yes, but it's a permanent change. You open the servo, cut off the mechanical stop on the gear and disconnect the potentiometer from the shaft, either replacing it with two equal resistors or fixing it in its middle position. The result works like an FS90R, but the warranty is gone and the servo can never hold an angle again; buying a ready-made FS90R is usually less hassle.

See all projects
Using a Servo Motor
#35Beginner
Premium

Using a Servo Motor

The arm of a small servo motor turns to three positions in turn: all the way to the start, right to the middle and all the way to the end, waiting two seconds at each stop. The Arduino tells the motor

4.7(48)
67
Servo Motor Operation with Potentiometer
#36Beginner
Premium

Servo Motor Operation with Potentiometer

As you turn the potentiometer knob, the servo motor's arm turns right along with it: one end of the knob means zero degrees, the other end means half a turn. The Arduino keeps reading the knob's posit

4.6(37)
52
ESP32 Servo Motor (Potentiometer Control)
#118Intermediate
Premium

ESP32 Servo Motor (Potentiometer Control)

In this project, we control an SG90 servo motor using a potentiometer. We adjust the servo angle by mapping the analog value read from the potentiometer to the 0–180-degree range. The ESP32Servo library generates servo signals on suitable output-capable GPIO pins of the ESP32; this project uses GPIO 18. This is a fundamental control method for robotic arms, camera gimbals, and automatic door mechanisms.

4.3(29)
42
Servo Control with Joystick
#60Intermediate
Premium

Servo Control with Joystick

Steer two motors with a joystick, just like turning a security camera. Push the stick left or right and the bottom servo motor slowly turns, push it up or down and the top servo tilts up and down; let

4.7(42)
58
360-Degree Servo from a Stepper Motor
#68Intermediate
Premium

360-Degree Servo from a Stepper Motor

Teach an ordinary stepper motor to go to any angle you want, all the way around the circle. The Arduino works out how many steps each angle takes; the motor moves to 0, 90, 45, 180, 135 and 360 degree

4.5(22)
32
Bluetooth Controlled Car with L298N
#80Advanced
Premium

Bluetooth Controlled Car with L298N

Turn your phone into a remote and drive your own robot car. The Arduino receives commands from a remote-control app on your phone through a Bluetooth module and spins the wheels with a motor driver; t

5.0(38)
65
Obstacle Avoidance Robot
#81Advanced
Premium

Obstacle Avoidance Robot

How about building a smart vehicle that finds its own way? The Obstacle Avoidance Robot constantly scans its surroundings thanks to the ultrasonic sensor in front of it. When it encounters an obstacle

5.0(38)
49