Skip to content

PWM Servo Communication Protocol and Stall Protection

Scope

This guide applies to all digital servo devices that support a standard PWM control interface. See the datasheet for each model for its electrical parameters, mechanical specifications, and wiring definitions.


1. PWM Control Protocol

1.1 Signal Definition

A PWM servo is controlled by a single-wire pulse-width modulation signal. The host sends a PWM pulse to the servo, and the servo moves its output shaft to the angle represented by the duration of the pulse's high level (the pulse width).

Term Meaning Carries angle information
Period Repetition interval of the PWM pulse; determines the refresh rate No
Pulse Width Duration of the high level in one pulse Yes
Duty Cycle Pulse width / period Indirectly

MCU PWM peripherals are configured in terms of duty cycle. Convert the target pulse width to a duty cycle before writing the value.

1.2 PWM Period Range

The PWM signal period determines how often the servo receives a command. Although it does not directly encode angle information, it significantly affects control behavior. A datasheet normally specifies the acceptable period range for the servo, such as 3-20 ms (approximately 50-333 Hz). Choose a period within that range to suit the application.

Period and Refresh Rate

Period T and refresh rate f are reciprocals:

刷新率 f (Hz) = 1 / 周期 T (s)
  • T = 20 ms -> f = 50 Hz (50 target-position updates per second)
  • T = 10 ms -> f = 100 Hz
  • T = 3 ms -> f ≈ 333 Hz

A higher refresh rate updates commands to the servo more frequently, producing smoother and more responsive motion, but it also increases the workload on the host PWM peripheral.

Why 50 Hz Became the Standard

A 20 ms period (50 Hz) is the traditional standard for PWM servo devices and originated in the radio-control (R/C) industry. Most general-purpose PWM servo datasheets use 50 Hz as the recommended or default period because:

  • The period is long enough for the motor to complete a full speed-measurement and drive cycle.
  • Duty-cycle variations have less effect on pulse-width accuracy.
  • It is directly compatible with R/C receivers and has the broadest ecosystem support.

1.3 Angle-to-Pulse-Width Mapping

The target angle of a servo has a linear mapping to the input pulse width:

角度 = (脉宽 - 中位脉宽) / 满量程脉宽 × 转角范围

Where:

  • Center pulse width is the pulse width at the mechanical center of the output shaft.
  • Full-scale pulse width is the pulse-width difference from the center to either endpoint.
  • Rotation range is the total rotation of the servo in degrees.

Center pulse width, endpoint pulse widths, and rotation range vary by model. Refer to the applicable servo datasheet.

Direction Convention

The servo follows this convention: as pulse width increases, the angle increases monotonically in the defined direction. The physical direction varies by model; see its datasheet. In control algorithms, calculate from target pulse width to target angle consistently to avoid mixing direction conventions.

1.4 Differences from a Bus Servo

A PWM servo differs fundamentally at the protocol layer from UART- or CAN-based bus servo devices:

Dimension PWM Servo Bus Servo (UART / CAN)
Signal direction One-way (host -> servo) Two-way (command + response)
Parameter readback Not supported Supported (position, current, temperature, etc.)
servo identification No ID concept servo ID
Multi-device control Requires an independent PWM channel bus daisy chain
Stall-state detection Requires additional host-side detection (see Section 3) Available through command readback

2. Basic Control Methods

2.1 Compatibility Overview

The simple PWM servo protocol is widely supported by host platforms and controllers:

  • Arduino - Use the Servo.h library or software-generated PWM.
  • ESP32 - Use the LEDC peripheral to generate PWM.
  • Raspberry Pi - Use pigpio or RPi.GPIO for hardware-timed PWM.
  • STM32 - Use a TIM peripheral in PWM mode through the HAL.
  • General-purpose servo controller - Send target angles over UART or USB and let the controller generate PWM internally.

All these platforms ultimately set PWM pulse width through the duty cycle, and pulse width controls the servo angle:

Duty cycle (%) = target pulse width (μs) / period (μs) × 100

2.2 Arduino

Use Arduino's built-in Servo.h library to write pulse widths directly in microseconds, without being limited by the default 0-180° angle mapping.

#include <Servo.h>

Servo myservo;

void setup() {
    // attach(引脚, 最小脉宽μs, 最大脉宽μs)
    myservo.attach(9, 500, 2500);
    myservo.writeMicroseconds(1500);  // 中位
}

void loop() {
    // 写目标脉宽 (μs),范围参见舵机规格书
    int pulse_us = 1500;
    myservo.writeMicroseconds(pulse_us);
    delay(15);
}

Key points:

  • On 8-bit boards such as the Uno and Nano, Servo.h uses Timer1.
  • Boards with multiple timers, such as the Mega and Due, can distribute multiple servo devices across timers.
  • writeMicroseconds() writes microseconds directly and is suitable for precise pulse-width control.

Complete Example: Servo Sweep with Error Handling

This example implements a back-and-forth servo sweep on Arduino with boundary checks and error handling:

#include <Servo.h>

// 舵机参数配置(根据实际舵机规格书调整)
const int SERVO_PIN      = 9;      // 信号线接 D9
const int PULSE_MIN      = 500;    // 最小脉宽 (μs)
const int PULSE_MAX      = 2500;   // 最大脉宽 (μs)
const int PULSE_CENTER   = 1500;   // 中位脉宽 (μs)
const int STEP_DELAY     = 15;     // 每步延时 (ms)
const int SWEEP_STEP     = 10;     // 每次增量 (μs)

Servo myServo;
int currentPulse = PULSE_CENTER;
int direction = 1;  // 1:正向, -1:反向

void setup() {
    Serial.begin(115200);

    // 初始化舵机
    if (!myServo.attach(SERVO_PIN, PULSE_MIN, PULSE_MAX)) {
        Serial.println("[ERROR] 舵机初始化失败,检查引脚连接!");
        while(1);  // 停止运行
    }

    Serial.println("[INFO] 舵机已初始化");
    Serial.print("[INFO] 脉宽范围: ");
    Serial.print(PULSE_MIN);
    Serial.print(" - ");
    Serial.print(PULSE_MAX);
    Serial.println(" μs");

    // 归中位
    myServo.writeMicroseconds(PULSE_CENTER);
    delay(500);
}

void loop() {
    // 更新目标脉宽
    currentPulse += direction * SWEEP_STEP;

    // 边界检查与反向
    if (currentPulse >= PULSE_MAX) {
        currentPulse = PULSE_MAX;
        direction = -1;
        Serial.println("[INFO] 到达最大脉宽,开始回程");
    } else if (currentPulse <= PULSE_MIN) {
        currentPulse = PULSE_MIN;
        direction = 1;
        Serial.println("[INFO] 到达最小脉宽,开始正向");
    }

    // 写入舵机
    myServo.writeMicroseconds(currentPulse);

    // 周期性输出当前状态(每500ms)
    static unsigned long lastPrint = 0;
    if (millis() - lastPrint > 500) {
        Serial.print("[STATUS] 当前脉宽: ");
        Serial.print(currentPulse);
        Serial.println(" μs");
        lastPrint = millis();
    }

    delay(STEP_DELAY);
}

Expected behavior:

  • The servo starts at its center position, moves slowly to one endpoint, reverses to the other endpoint, and repeats.
  • The serial port Monitor prints the current pulse width every 500 ms for easier configuration.
  • If the servo is disconnected or the pin is incorrect, the program reports an error in setup() and stops.

2.3 ESP32

The ESP32 Arduino core provides the LEDC (LED Control) peripheral for generating PWM and controlling duty cycle precisely. A 16-bit resolution is sufficient for pulse-width resolution on the order of 0.1 μs.

const int servoPin  = 18;     // 信号线接 GPIO 18
const int channel   = 0;      // LEDC 通道号
const int freq      = 50;     // 50 Hz PWM 周期
const int resBits   = 16;     // 16 位分辨率
const int periodUs  = 20000;  // 50 Hz 对应 20000 μs

void setup() {
    ledcSetup(channel, freq, resBits);
    ledcAttachPin(servoPin, channel);
}

void loop() {
    // 目标脉宽 (μs),例:中位 1500
    int pulse_us  = 1500;
    int max_duty  = (1 << resBits) - 1;  // 65535
    int duty      = (long)max_duty * pulse_us / periodUs;
    ledcWrite(channel, duty);
    delay(15);
}

Key points:

  • ESP32 LEDC supports 16 independent channels and can drive multiple servo devices.
  • Frequency and resolution trade off against each other; this example uses the common 50 Hz / 16-bit combination.
  • Recalculate duty cycle with 32-bit integer arithmetic to avoid accumulated floating-point error.

2.4 Raspberry Pi + pigpio

On Raspberry Pi, the pigpio daemon uses hardware timing to generate stable PWM with resolution down to 1 μs without consuming CPU time.

import pigpio
import time

SERVO_PIN = 18  # 信号线接 GPIO 18 (Pin 12)

pi = pigpio.pi()
if not pi.connected:
    raise RuntimeError("pigpio 守护进程未启动 (sudo pigpiod)")

def set_pulse(pulse_us: int) -> None:
    """写入目标脉宽 (μs),0 表示停止 PWM 输出"""
    pi.set_servo_pulsewidth(SERVO_PIN, pulse_us)

try:
    set_pulse(1500)  # 中位
    while True:
        # 在此更新 set_pulse(<新脉宽>) 即可控制角度
        time.sleep(0.02)
except KeyboardInterrupt:
    set_pulse(0)      # 停止输出
    pi.stop()

Key points:

  • Start the daemon first with sudo pigpiod.
  • Any GPIO pin can be used, but the number of simultaneous hardware PWM channels is limited to two on Raspberry Pi.
  • set_servo_pulsewidth() limits the pulse width internally to 500-2500 μs; the Servo will not respond to values outside that range.

2.5 STM32

STM32 generates a 50 Hz pulse by configuring a TIM timer in PWM mode. Accuracy depends on the timer clock. This example uses the HAL, a 72 MHz system clock, and TIM2_CH1 output.

CubeMX Configuration

Parameter Value Description
Prescaler 71 72 MHz / 72 = 1 MHz counter frequency
Counter Mode Up Counts upward
Counter Period 19999 1 MHz / 20000 = 50 Hz
Pulse (initial) 1500 1500 μs center pulse width
CH Polarity High Active high

Example Code

/* main.c */
#include "main.h"
#include "tim.h"

static void set_servo_pulse(uint16_t pulse_us) {
    /* 改变 CCR1 寄存器即可更新脉宽 */
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse_us);
}

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init();
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);  // 启动 PWM

    while (1) {
        set_servo_pulse(1500);                  // 中位
        HAL_Delay(15);
    }
}

Key points:

  • The approach applies to general-purpose, F1, and F4 series MCUs. Adjust the prescaler and reload value for the system clock.
  • Multiple servo devices can use separate TIM channels for independent control.
  • Change __HAL_TIM_SET_COMPARE to update the pulse width immediately.

2.6 General-Purpose Servo Controller

A general-purpose servo controller, commonly called a "servo driver board," usually includes PWM generation circuitry. The host sends a target angle over UART / USB / I2C, and the controller converts it to a PWM pulse width.

┌────────┐  UART/USB  ┌──────────────┐  PWM  ┌──────┐
│  主机  │ ──────────> │ 通用舵机控制器 │ ─────>│ 舵机 │
└────────┘             └──────────────┘       └──────┘
                              │
                              ├── 电源输入(单独供电)
                              └── GND

Usage notes:

  • Provide an independent power supply for the servo. The controller should supply only the signal to avoid overcurrent.
  • Host-side protocols are often vendor-specific. Configure baud rate and data format according to the controller manual.
  • This approach is useful when host resources are limited or multiple servo devices must be connected in parallel.

3. Stall Protection

Note: Stall protection is a special feature available on some digital PWM servo devices. This section describes the common behavior. Check the applicable servo datasheet to confirm support.

3.1 Purpose

When an external force prevents the output shaft of a servo from moving, the motor continues to apply full power to hold position and current rises sharply. A prolonged stall can:

  • Overheat the motor windings, degrade insulation, or burn out the motor.
  • Subject the gear train to excessive torque and accelerate wear.
  • Cause overcurrent in the driver circuit and shorten its service life.

Stall Protection detects a sustained stall and actively reduces output power. After the obstruction is removed, the actuator returns to its target position and resumes normal operation.

3.2 Trigger Condition

Stall duration ≥ 1 second.

Brief vibration or a transient obstruction lasting less than one second does not trigger protection, allowing the servo to maintain full output under normal disturbances.

3.3 Protected-State Parameters

After protection activates, the servo enters a limited-output mode:

Parameter Before protection (normal) After activation
Output power 100% 30%
Operating current Load-dependent Reduced by approximately 70%
Holding torque Full torque Approximately 30% torque
Control-signal reception Normal Normal

3.4 State-Machine Sequence

stateDiagram-v2
    [*] --> 正常
    正常 --> 保护激活 : 堵转持续 ≥ 1s
    保护激活 --> 回位中 : 堵转解除 / 以 30% 功率到达目标位置
    回位中 --> 正常 : 抵达目标位置 / 恢复全功率

The three states behave as follows:

  1. Normal - Runs at full power, responds to host control signals, and draws current according to load.
  2. Protection active - Enters after a stall lasts at least 1 second and reduces power to 30%.
  3. Returning - After the stall clears, drives the servo to the target position at 30% power, then restores normal full-power operation.

4. Troubleshooting

4.1 Servo Does Not Respond

Possible cause Check Solution
Incorrect wiring Check signal, VCC, and GND order Reconnect according to the datasheet
No power Measure voltage at the servo VCC pin Supply power within the datasheet voltage range
Pulse width out of range Check the transmitted pulse width Adjust it to the specified range, typically 500-2500 μs
PWM period too short or too long Check the PWM frequency Use the recommended 50 Hz (20 ms period)

4.2 Servo Jitters or Makes Unusual Noise

Possible cause Check Solution
Excessive supply ripple Measure the supply waveform with an oscilloscope Add a 100-470 μF electrolytic capacitor across the supply
Signal interference Check whether signal and power wires run in parallel Separate them or use shielded cable
Pulse-width jitter Check whether MCU timer interrupts are being delayed Increase PWM-generation priority or use hardware PWM
Excessive load Turn the output shaft manually and check resistance Reduce the load or use a higher-torque servo

4.3 Inaccurate Rotation Angle

Possible cause Check Solution
Incorrect pulse-width mapping Measure the actual PWM waveform Recalibrate the pulse-width-to-angle mapping
Mechanical wear in the servo Turn the shaft manually and check for binding Replace the servo or service the gear train
Insufficient duty-cycle precision Check for arithmetic overflow Use integer arithmetic or increase timer resolution
Incorrect period Check the configured PWM period Keep it within the datasheet range, typically 3-20 ms

4.4 Servo Overheats

Possible cause Check Solution
Prolonged stall Check the mechanism for interference Remove the interference and enable stall protection
Excessive supply voltage Measure the actual supply voltage Adjust it to the recommended datasheet value
Frequent large movements Observe the operating pattern Reduce movement frequency or allow more cooling time
servo overload Check load torque Reduce the load or select a model with higher torque

Appendix

A. Glossary

Term Meaning
Pulse Width Duration of the PWM signal's high level; determines the target angle of the servo
Period Repetition interval of the PWM signal; determines refresh rate
Duty Cycle Ratio of pulse width to period, expressed as a percentage

B. References