How to Implement PWM in Arduino Projects
Pulse Width Modulation (PWM) is one of the most versatile tools in an Arduino maker’s arsenal. While it powers everything from LED dimmers to motor speed controllers, its most iconic application is arguably the micro servo motor. These tiny, high-torque actuators are the backbone of countless robotics, animatronics, and automation projects. In this guide, we’ll explore not just the how but the why behind PWM, using the ubiquitous SG90 micro servo as our test subject. By the end, you’ll be able to control a servo with precision, troubleshoot common issues, and even build a custom servo controller from scratch.
Why Micro Servos Demand Precise PWM
Micro servos (like the SG90, MG90S, or Tower Pro) are fundamentally different from DC motors. A DC motor spins continuously when powered; a servo rotates to a specific angle and holds that position. This positional feedback requires a specific PWM signal—one that the Arduino’s analogWrite() cannot directly provide.
The 50 Hz Myth and Reality
Most micro servos expect a 50 Hz PWM signal (20 ms period). Within that period, the on-time (pulse width) determines the angle: - 1 ms (5% duty cycle) → 0° (usually full counterclockwise) - 1.5 ms (7.5% duty cycle) → 90° (center) - 2 ms (10% duty cycle) → 180° (full clockwise)
But here’s the nuance: not all servos are created equal. Cheap micro servos often have a usable range of 0.5 ms to 2.5 ms, and the exact endpoints vary by brand. This is why you’ll often see servo libraries using writeMicroseconds() instead of write()—the latter abstracts away the pulse width, but the former gives you raw control.
Why Not Use analogWrite()?
Arduino’s analogWrite() generates a fixed-frequency PWM (usually 490 Hz or 980 Hz depending on the pin). At 490 Hz, the period is ~2 ms, which is far too short for a servo to interpret correctly. A servo expects a 20 ms period; feeding it a 2 ms signal will either cause jittering, overheating, or no movement at all. You need a software-based or timer-based PWM that runs at 50 Hz.
Setting Up the Hardware: Minimal Wiring
Before diving into code, let’s wire a micro servo to an Arduino Uno. The SG90 has three wires: - Brown → GND - Red → 5V (servos can draw up to 500 mA under load; use an external 5V supply if driving multiple) - Orange → Signal pin (e.g., pin 9)
Critical: Power considerations A single micro servo can draw 150–250 mA when stalled. The Arduino’s onboard 5V regulator can handle one servo, but for two or more, use a separate 5V supply (e.g., a USB power bank or a buck converter from a LiPo battery). Connect the servo’s ground to the Arduino’s ground, and the signal pin directly to the digital pin. Never power a servo from the Arduino’s 3.3V rail.
Method 1: The Servo Library (Easiest for Beginners)
The Servo.h library is Arduino’s built-in solution. It uses Timer1 (on Uno/Nano) to generate the 50 Hz signal without blocking the main loop.
Basic Sweep Example
cpp
include <Servo.h>
Servo myServo; // Create servo object
void setup() { myServo.attach(9); // Attaches servo on pin 9 myServo.write(0); // Move to 0° delay(1000); // Wait for it to reach position }
void loop() { // Sweep from 0 to 180 degrees for (int angle = 0; angle <= 180; angle += 1) { myServo.write(angle); delay(15); // 15 ms per step = smooth motion } // Sweep back for (int angle = 180; angle >= 0; angle -= 1) { myServo.write(angle); delay(15); } }
What’s happening under the hood? - attach() configures Timer1 to generate a 50 Hz interrupt. - write(angle) maps 0–180 to 544–2400 microseconds (the default pulse width range). - The library updates the PWM duty cycle on every timer tick.
Fine-Tuning with writeMicroseconds()
If your servo doesn’t reach 0° or 180° correctly, use writeMicroseconds():
cpp myServo.writeMicroseconds(1000); // Try 1000 µs for 0° myServo.writeMicroseconds(2000); // Try 2000 µs for 180°
Calibrate by trial: start at 544 µs and increase until the servo stops moving, then back off 10 µs. Repeat for the upper limit.
Method 2: Manual PWM with Timer1 (Advanced Control)
Why bother writing your own PWM? Because the Servo library has limitations: - It disables PWM on pins 9 and 10 (Timer1 output). - It can only control 12 servos (on Uno/Nano). - You lose control of timer interrupts for other purposes.
If you need to control servos while also using timer-based sensors (e.g., ultrasonic rangefinders), manual PWM is the way.
The 16-Bit Timer Approach
The ATmega328P’s Timer1 is a 16-bit timer. We can set it to overflow every 20 ms and toggle the output compare registers to generate precise pulses.
cpp // Manual servo control using Timer1 (Arduino Uno) // Pin 9 (OC1A) outputs the signal
void setup() { pinMode(9, OUTPUT);
// Configure Timer1 for 50 Hz TCCR1A = 0; // Reset control registers TCCR1B = 0; TCNT1 = 0;
// Set compare match value for 20 ms period // 16 MHz / 64 prescaler = 250,000 ticks per second // 250,000 * 0.02 = 5,000 ticks per 20 ms OCR1A = 5000; // Top value (20 ms)
// Configure output compare A (pin 9) to toggle on match TCCR1A |= (1 << COM1A1); // Clear on match, set at bottom TCCR1B |= (1 << WGM12); // CTC mode (Clear Timer on Compare) TCCR1B |= (1 << CS11) | (1 << CS10); // Prescaler = 64
// Set initial pulse width (1.5 ms = 0.0015 * 250000 / 64 = 187.5 ticks) OCR1B = 187; // Approx 1.5 ms TCCR1A |= (1 << COM1B1); // Enable output on pin 10 (OC1B) if needed }
void loop() { // Change pulse width on the fly // For 1 ms (0°): OCR1A = 5000, OCR1B = 125 // For 2 ms (180°): OCR1A = 5000, OCR1B = 250 OCR1B = 125; // Move to 0° delay(1000); OCR1B = 250; // Move to 180° delay(1000); }
Warning: This code only works on pin 9 (OC1A) and pin 10 (OC1B). It also conflicts with the Servo library—never use both simultaneously.
Method 3: Software PWM (When Hardware Timers Are Tied Up)
Sometimes you’ve already used Timer1 for something else (e.g., a frequency counter). In that case, you can bit-bang PWM using delayMicroseconds() and digitalWrite(). This is less precise and consumes CPU cycles, but it works for non-critical applications.
Blocking Software PWM (Not Recommended for Production)
cpp void setServoAngle(int pin, int angle) { // Convert angle to pulse width (544 to 2400 µs) int pulseWidth = map(angle, 0, 180, 544, 2400);
digitalWrite(pin, HIGH); delayMicroseconds(pulseWidth); digitalWrite(pin, LOW); delayMicroseconds(20000 - pulseWidth); // Remainder of 20 ms }
void loop() { setServoAngle(9, 90); // Hold at 90° }
Why this is bad: delayMicroseconds() blocks the entire Arduino. You can’t read sensors or update other outputs during the 20 ms cycle. Use this only for quick tests.
Non-blocking Software PWM with millis()
A better approach uses millis() to track time without blocking:
cpp unsigned long lastPulse = 0; int pulseWidth = 1500; // 1.5 ms
void loop() { unsigned long now = millis();
if (now - lastPulse >= 20) { // Every 20 ms digitalWrite(9, HIGH); delayMicroseconds(pulseWidth); digitalWrite(9, LOW); lastPulse = now; }
// Do other tasks here readSensor(); }
This still uses delayMicroseconds(), but only for the pulse width (1–2 ms). The remaining 18–19 ms are free for other code. It’s a compromise.
Advanced PWM Techniques for Micro Servos
1. Smooth Acceleration Profiles
Sudden jumps from 0° to 180° can cause mechanical stress and power spikes. Implement a ramping function:
cpp void smoothMove(Servo &servo, int targetAngle, int stepDelay = 20) { static int currentAngle = 0;
if (targetAngle > currentAngle) { for (int i = currentAngle; i <= targetAngle; i++) { servo.write(i); delay(stepDelay); } } else { for (int i = currentAngle; i >= targetAngle; i--) { servo.write(i); delay(stepDelay); } } currentAngle = targetAngle; }
2. Reading Servo Position (Without Feedback)
Standard micro servos don’t have position feedback pins. To know the current angle, you must track it in software. Create a class that stores the last commanded angle:
cpp class ServoWithMemory { Servo s; int _lastAngle; public: void attach(int pin) { s.attach(pin); _lastAngle = 90; } void write(int angle) { s.write(angle); _lastAngle = angle; } int read() { return _lastAngle; } };
3. Daisy-Chaining Multiple Servos
The Servo library supports up to 12 servos on an Uno. However, each servo requires a separate signal pin. If you need more, use a PWM driver like the PCA9685 (I2C). This chip generates 16 independent 50 Hz PWM channels, freeing up your Arduino’s timers.
Wiring PCA9685 to Arduino: - VCC → 5V - GND → GND - SDA → A4 (Uno) / SDA (Mega) - SCL → A5 (Uno) / SCL (Mega)
Code snippet using Adafruit_PWMServoDriver library:
cpp
include <Wire.h> include <Adafruit_PWMServoDriver.h>
AdafruitPWMServoDriver pwm = AdafruitPWMServoDriver();
define SERVOMIN 150 // 0° pulse (adjust per servo) define SERVOMAX 600 // 180° pulse (adjust per servo)
void setup() { pwm.begin(); pwm.setPWMFreq(50); // 50 Hz }
void setServoAngle(uint8_t channel, int angle) { int pulse = map(angle, 0, 180, SERVOMIN, SERVOMAX); pwm.setPWM(channel, 0, pulse); }
void loop() { setServoAngle(0, 0); delay(1000); setServoAngle(0, 180); delay(1000); }
Troubleshooting Common PWM-Servo Issues
Servo Jitters or Vibrates
- Cause: Power supply noise or insufficient current.
- Fix: Add a 100 µF electrolytic capacitor between 5V and GND near the servo. Use a separate 5V regulator for multiple servos.
Servo Moves to Wrong Angles
- Cause: Incorrect pulse width range.
- Fix: Use
writeMicroseconds()and calibrate. Some servos have a non-linear response—create a lookup table.
Servo Gets Hot
- Cause: Stall (mechanical obstruction) or incorrect PWM frequency.
- Fix: Ensure the servo isn’t blocked. Verify the PWM period is exactly 20 ms (50 Hz). A frequency of 60 Hz (16.67 ms) can cause overheating.
Arduino Resets When Servo Moves
- Cause: Voltage drop from servo inrush current.
- Fix: Add a 470 µF capacitor across the servo power rails. Use a separate power supply for the servo, sharing only ground.
Real-World Project: DIY Robotic Arm with Micro Servos
Let’s tie it all together. A simple 2-DOF robotic arm uses two micro servos: - Base servo (shoulder) → pin 9 - Elbow servo → pin 10
Hardware Setup
- Two SG90 servos
- External 5V 2A supply
- 1000 µF capacitor on the 5V rail
- Potentiometers for manual control (optional)
Code with Potentiometer Control
cpp
include <Servo.h>
Servo shoulder; Servo elbow;
int pot1Pin = A0; int pot2Pin = A1;
void setup() { shoulder.attach(9); elbow.attach(10); shoulder.write(90); // Start at center elbow.write(90); }
void loop() { int pot1Val = analogRead(pot1Pin); int pot2Val = analogRead(pot2Pin);
int shoulderAngle = map(pot1Val, 0, 1023, 0, 180); int elbowAngle = map(pot2Val, 0, 1023, 0, 180);
shoulder.write(shoulderAngle); elbow.write(elbowAngle);
delay(15); // Smooth out analog noise }
Adding Inverse Kinematics (Advanced)
For a more sophisticated arm, you can calculate the angles required to reach a specific (x, y) coordinate. This involves trigonometry:
cpp // Given arm lengths L1 (shoulder to elbow) and L2 (elbow to gripper) // Target point (x, y) float theta2 = acos((xx + yy - L1L1 - L2L2) / (2L1L2)); float theta1 = atan2(y, x) - atan2(L2sin(theta2), L1 + L2cos(theta2));
// Convert radians to degrees shoulder.write(theta1 * 180 / PI); elbow.write(theta2 * 180 / PI);
This is the foundation of many desktop robot arms, from pick-and-place machines to camera gimbals.
Final Thoughts on PWM and Micro Servos
Mastering PWM for micro servos opens the door to precise motion control. Whether you use the Servo library for quick prototypes, manual timer registers for performance, or an I2C driver for scalability, the key is understanding the 20 ms period and the 1–2 ms pulse window. Always calibrate your servos, manage power wisely, and consider the mechanical limits of these tiny but powerful motors.
Now go build something that moves.
Copyright Statement:
Author: Micro Servo Motor
Link: https://microservomotor.com/pulse-width-modulation-pwm-control/implement-pwm-arduino-projects.htm
Source: Micro Servo Motor
The copyright of this article belongs to the author. Reproduction is not allowed without permission.
Recommended Blog
- PWM Control in Power Distribution Systems
- PWM Control in Lighting Systems: Design Considerations
- PWM Control in Lighting Systems: Applications and Benefits
- PWM Control in Lighting Systems: Techniques and Tips
- PWM in Communication Systems: Encoding Information
- Understanding the PWM Duty Cycle Formula
- The Role of PWM in Signal Filtering
- The Use of PWM in Signal Filtering: Applications and Techniques
- PWM in Audio Amplifiers: Design Considerations
- How PWM Affects Motor Torque and Speed
About Us
- Lucas Bennett
- Welcome to my blog!
Hot Blog
- How to Build a Remote-Controlled Car with a Speedometer
- How to Replace and Upgrade Your RC Car's Tires
- The Future of Micro Servo Motors in Smart Grid and Energy Systems
- How Load Affects Motor Torque and Speed
- Micro Servo Motor Sizing for Drone Payload Manipulators
- What Happens Inside a Micro Servo Motor When It Moves?
- Micro Servo Motor Control Signals: How They Drive Motion
- PWM in Communication Systems: Encoding Information
- The Impact of 5G Technology on Micro Servo Motor Performance
- Micro Servos for Articulated Robot Arms vs Fixed Mounts
Latest Blog
- Designing a Micro Servo Robotic Arm for Laboratory Automation
- How to Implement PWM in Arduino Projects
- The Impact of Gear Materials on Servo Motor Heat Generation
- How to Repair and Maintain Your RC Car's ESC Capacitor
- DIY Servo-Powered Blinds: Step-by-Step Guide
- The Use of Micro Servo Motors in Drones: Applications and Advancements
- PWM Control in Power Distribution Systems
- How Gear Teeth Design Influences Servo Motor Operation
- Micro Servo Motors in Automated Material Handling Systems
- Vector's Micro Servo Motors: Compact and Lightweight for Pan-Tilt Systems
- Specification of “Creeping” or Non-Holding Torque when Power Removed
- The Application of Micro Servo Motors in Robotics
- The Role of Micro Servo Motors in the Development of Smart Technological Systems
- Advances in Lubrication Systems for Micro Servo Motors
- Advances in Acoustic Management for Micro Servo Motors
- Micro Servo Motors in Automated Welding Systems
- The Best Micro Servo Motors for Arduino Projects: Brand Recommendations
- How to Control Servo Motors Using Raspberry Pi and the pigpio Library for Precision Robotics
- High-Torque Micro Servo Motors: Are They Worth the Higher Price?
- Operating Voltage Ranges for Micro Servos Explained