IoTSimulator

Control a Servo with an ATtiny85 and Potentiometer

Turn an analog knob to steer an SG90 servo motor smoothly from 0 to 180 degrees. Learn how microcontrollers read knob angles and generate precision timing pulses without external libraries.
Muhammad Ichsanul Fadhil
IoTSim Editor
September 24, 2026
Control a Servo with an ATtiny85 and Potentiometer

Live project track

Interactive hardware & logic preview

When you turn a volume dial on a stereo or steer a remote-control car, you expect the output to follow your hand smoothly and immediately. Servo motors make this kind of control possible. Unlike standard DC motors that spin continuously without stopping, a servo motor rotates to a specific commanded angle and holds its position firmly.

In this project, you will build a knob-controlled servo system using an ATtiny85 microcontroller, a 10k rotary potentiometer, and an SG90 micro servo. The main idea is straightforward: turning the potentiometer changes the analog voltage entering the microcontroller, and the ATtiny85 converts that voltage into microsecond timing pulses that instruct the servo arm where to point in real time.

How a Servo Understands Pulse Timing

A servo motor does not read raw analog voltage like a sensor does. Inside the servo casing sits a small control circuit that listens for repeated electrical pulses delivered 50 times every second (once every 20 milliseconds). The width of each high pulse tells the internal motor exactly which angle to turn to.

50Hz Servo PWM Pulse Width Timing Waveforms
50Hz Servo PWM Pulse Width Timing Waveforms

The waveform diagram above illustrates the three key reference positions. An active pulse lasting 1.0 millisecond (1000 microseconds) instructs the servo horn to rotate to 0 degrees (full left). A 1.5 millisecond pulse (1500 microseconds) commands it to hold the center position at 90 degrees. A 2.0 millisecond pulse (2000 microseconds) commands it to full right at 180 degrees. Any pulse duration in between positions the motor arm proportionally across its full movement range.

High pulse durationDuration in microsecondsTarget horn anglePosition description
1.0 ms1000 µs0°Full left
1.5 ms1500 µs90°Neutral center
2.0 ms2000 µs180°Full right

Because the total time between pulses must stay consistent at 20 milliseconds (20,000 microseconds), the quiet rest time between pulses simply equals 20,000 minus your active pulse duration. This steady rhythm gives the servo motor continuous torque to hold its commanded position.

Turning Knob Values into Servo Pulses

A rotary potentiometer acts as an adjustable voltage divider. As you turn the knob from left to right, the voltage on its middle wiper pin slides smoothly between 0 volts and 5 volts. The ATtiny85 reads this voltage through its internal analog-to-digital converter (ADC), converting the measurement into a number from 0 to 1023.

Analog Voltage to Servo Angle Mapping Flow Diagram
Analog Voltage to Servo Angle Mapping Flow Diagram

The flow diagram above traces the signal through the entire system. The microcontroller reads the 0–1023 number, uses the built-in map() function to scale it into the 1000–2000 microsecond range, and sends the resulting pulse straight to the servo signal pin. As your fingers turn the knob, the servo arm mirrors your movement without lag.

Knob positionWiper voltageanalogRead(A1) valueGenerated pulse widthServo angle
Full counter-clockwise0.0 V01000 µs0° (Left)
Halfway turned2.5 V~5121500 µs90° (Center)
Full clockwise5.0 V10232000 µs180° (Right)

By creating pulses directly using delayMicroseconds(), this approach operates without heavy external servo libraries. This keeps the program tiny and ensures it fits easily into the ATtiny85's compact memory footprint.

Connecting the Circuit

The circuit links two simple physical actions: rotating the potentiometer knob and watching the servo motor arm mirror that exact angle. Because the ATtiny85 has only eight pins, every wire has a single direct job.

Illustration showing turning a knob clockwise rotates the servo arm from 0 to 180 degrees
Illustration showing turning a knob clockwise rotates the servo arm from 0 to 180 degrees

As shown in the diagram, turning the knob smoothly turns the motor horn like a car steering wheel or a miniature barrier gate. Connect the potentiometer signal and servo control wires to the ATtiny85 pins as detailed below:

Pin Connection Map
10k Potentiometer
VCC (+)
→
ATtiny85
VCC (Pin 8)
Explanation
Supplies 5V reference voltage across the potentiometer track.
10k Potentiometer
GND (−)
→
ATtiny85
GND (Pin 4)
Explanation
Connects to system common ground.
10k Potentiometer
SIG (Center Wiper)
→
ATtiny85
PB2 / A1 (Pin 7)
Explanation
Analog voltage input carrying the dial position (0V to 5V).

Next, connect the SG90 micro servo control wire to physical pin 5 (PB0) along with its 5V power and ground supply leads:

Pin Connection Map
SG90 Micro Servo
V+ (Red Wire)
→
ATtiny85
VCC (Pin 8)
Explanation
Provides 5V operating power for the servo's internal motor and driver.
SG90 Micro Servo
GND (Brown / Black)
→
ATtiny85
GND (Pin 4)
Explanation
Shared ground return for motor current and control pulses.
SG90 Micro Servo
PWM (Orange / Yellow)
→
ATtiny85
PB1 / Pin 1 (Pin 6)
Explanation
Receives the 50Hz position pulses generated by the microcontroller.

When working with physical hardware on a breadboard, servo motors can briefly draw up to 500 milliamps when starting or reversing direction quickly. Placing a 100µF or 470µF capacitor between 5V and GND close to the servo will smooth out power surges and prevent the microcontroller from resetting.

Complete Code

Here is the complete Arduino sketch. It reads the potentiometer, maps the measurement to a microsecond pulse duration, and transmits the standard 50Hz servo frame continuously.

C++ Source
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// ============================================================================
// ATtiny85 Knob-Controlled Servo Motor
// Reads Potentiometer on Physical Pin 7 (PB2 / ADC1 / Analog Pin A1)
// Drives SG90 Servo on Physical Pin 6 (PB1 / Digital Pin 1)
// ============================================================================

const int POT_PIN   = A1; // PB2 (Physical Pin 7 on DIP-8) - Analog Input
const int SERVO_PIN = 1;  // PB1 (Physical Pin 6 on DIP-8) - Servo Control Output

void setup() {
  // Configure the servo pin as an output
  pinMode(SERVO_PIN, OUTPUT);
}

void loop() {
  // 1. Read the knob position (0 = full left, 1023 = full right)
  int potVal = analogRead(POT_PIN);
  
  // 2. Convert knob reading to microsecond pulse duration (1000µs to 2000µs)
  int pulseWidth = map(potVal, 0, 1023, 1000, 2000);

  // 3. Transmit standard 50Hz servo frame (20ms total duration)
  digitalWrite(SERVO_PIN, HIGH);
  delayMicroseconds(pulseWidth);        // Active high pulse (1ms - 2ms)
  digitalWrite(SERVO_PIN, LOW);
  delayMicroseconds(20000 - pulseWidth); // Low rest period to complete 20ms
}

How the Code Works, Part by Part

The entire program runs inside a continuous loop with no complex background libraries. Each cycle completes three simple tasks: sample the knob, calculate the pulse duration, and transmit the pulse.

▸ Pin Declarations and Setup

The program begins by defining descriptive names for both pins. On the ATtiny85, PB2 is associated with analog channel 1, so we reference it with the alias A1. Physical pin 6 is Port B Pin 1, configured as digital output 1.

C++ Source
1
2
3
4
5
6
const int POT_PIN   = A1; // PB2 (Physical Pin 7 on DIP-8)
const int SERVO_PIN = 1;  // PB1 (Physical Pin 6 on DIP-8)

void setup() {
  pinMode(SERVO_PIN, OUTPUT);
}

We only configure SERVO_PIN with pinMode() because analog input pins are initialized automatically when analogRead() is called.

▸ Reading the Dial and Mapping to Microseconds

Inside loop(), the program reads the instantaneous wiper voltage from POT_PIN. It then passes that value into map() to rescale the integer range from 0–1023 into 1000–2000 microseconds.

C++ Source
1
2
int potVal = analogRead(POT_PIN);
int pulseWidth = map(potVal, 0, 1023, 1000, 2000);

The map() function uses fast integer math to translate the numbers proportionally. If potVal is 0, pulseWidth becomes 1000. If potVal is 1023, pulseWidth becomes 2000.

▸ Transmitting the Precision 50Hz Frame

To deliver the pulse to the servo motor, the code drives SERVO_PIN HIGH and holds it for pulseWidth microseconds. It then pulls the pin LOW and holds it for 20000 - pulseWidth microseconds.

C++ Source
1
2
3
4
digitalWrite(SERVO_PIN, HIGH);
delayMicroseconds(pulseWidth);
digitalWrite(SERVO_PIN, LOW);
delayMicroseconds(20000 - pulseWidth);

Notice how the two delays always add up to exactly 20,000 microseconds (20 ms). Whether the high pulse is short (1000 µs) or long (2000 µs), the total frame repetition rate remains locked at 50Hz, keeping the motor steady and responsive.

Fixing Common Problems

If the servo does not track the knob or behaves unexpectedly, use the troubleshooting table below to identify the issue.

What you seeLikely causeWhat to try
Servo does not turn at allPin assignment mismatch or reversed power wiringVerify servo signal connects to PB1 (Pin 6) and power leads are 5V (Red) and GND (Brown)
Servo jerks or resets constantly when moving knobPower supply voltage dips from motor current surgePower the servo from an external 5V supply or add a 100µF capacitor across 5V and GND
Servo moves through only half its full travelServo model expects wider 500µs–2500µs pulse rangeUpdate code mapping to map(potVal, 0, 1023, 500, 2500)
Servo buzzes or chatters when knob is stillSmall electrical noise on potentiometer wiperTwist your jumper wires together or introduce a 5-unit deadband filter in code

Some hobby servos support an extended pulse range beyond the standard 1000µs–2000µs window. If your servo only covers about 90 degrees of travel, broadening the target range in the map() function will allow it to access its full 180-degree physical range.

Try It in the Simulator

Click the Start Simulation (▶) button in the top toolbar to begin. Click and drag the rotary dial on the Position Knob component from minimum to maximum. Watch the SG90 Servo arm track the knob's position in real time across the full 180-degree sweep!

Keywords
#ATtiny85 #Servo #Potentiometer #ADC #PWM #DIP-8 #Actuators #Intermediate
Total word count: 1191 words

Project discussion

Questions, feedback, and community insights
No discussions yet. Be the first to start!