Add a servo

Add a servo to your machine’s configuration so you can control its angular position from the Viam app and from code.

Concepts

A servo moves to a specific angle (typically 0-180 degrees) and holds that position. Unlike a motor, which spins continuously, a servo provides precise angular positioning through PWM control.

The servo component uses a single PWM-capable pin on a board component. Browse all available servo models in the Viam registry.

Steps

1. Prerequisites

  • Your machine is online in the Viam app.
  • A board component is configured.
  • Your servo’s signal wire is connected to a PWM-capable GPIO pin, with power and ground wired appropriately.

2. Add a servo component

  1. Click the + button.
  2. Select Configuration block.
  3. For a standard hobby servo controlled by a PWM pin on your board, search for gpio servo.
  4. Name your servo (for example, pan-servo) and click Create.

3. Configure servo attributes

{
  "board": "my-board",
  "pin": "12"
}
AttributeTypeRequiredDescription
boardstringYesName of the board component.
pinstringYesGPIO pin for the servo signal wire.
min_angle_degfloatNoMinimum angle. Default: 0.
max_angle_degfloatNoMaximum angle. Default: 180.
starting_position_degfloatNoPosition on startup. Default: 0.
frequency_hzintNoPWM frequency. Default: 300. Most servos expect 50-330 Hz.

If your servo doesn’t reach its full range or jitters at the extremes, adjust the pulse width:

AttributeTypeDescription
min_width_usintMinimum pulse width in microseconds (>450).
max_width_usintMaximum pulse width in microseconds (<2500).

4. Save and test

Click Save, then expand the TEST section.

  • Use the angle slider to move the servo to different positions.
  • The servo should move smoothly and hold its position.

Try it

Sweep the servo through a range of positions.

To get the credentials for the code below, go to your machine’s page in the Viam app, click the CONNECT tab, and select SDK code sample. Toggle Include API key on. Copy the machine address, API key, and API key ID from the code sample. If you’re using real hardware, you’ll see the servo sweep through positions when you run the code below.

pip install viam-sdk

Save this as servo_test.py:

import asyncio
from viam.robot.client import RobotClient
from viam.components.servo import Servo


async def main():
    opts = RobotClient.Options.with_api_key(
        api_key="YOUR-API-KEY",
        api_key_id="YOUR-API-KEY-ID"
    )
    robot = await RobotClient.at_address("YOUR-MACHINE-ADDRESS", opts)

    servo = Servo.from_robot(robot, "pan-servo")

    # Sweep through positions
    for angle in [0, 45, 90, 135, 180]:
        await servo.move(angle)
        current = await servo.get_position()
        print(f"Moved to {angle}°, position reads {current}°")
        await asyncio.sleep(0.5)

    # Return to center
    await servo.move(90)
    print("Returned to 90°")

    await robot.close()

if __name__ == "__main__":
    asyncio.run(main())

Run it:

python servo_test.py
mkdir servo-test && cd servo-test
go mod init servo-test
go get go.viam.com/rdk

Save this as main.go:

package main

import (
    "context"
    "fmt"
    "time"

    "go.viam.com/rdk/components/servo"
    "go.viam.com/rdk/logging"
    "go.viam.com/rdk/robot/client"
    "go.viam.com/rdk/utils"
)

func main() {
    ctx := context.Background()
    logger := logging.NewLogger("servo-test")

    robot, err := client.New(ctx, "YOUR-MACHINE-ADDRESS", logger,
        client.WithCredentials(utils.Credentials{
            Type:    utils.CredentialsTypeAPIKey,
            Payload: "YOUR-API-KEY",
        }),
        client.WithAPIKeyID("YOUR-API-KEY-ID"),
    )
    if err != nil {
        logger.Fatal(err)
    }
    defer robot.Close(ctx)

    s, err := servo.FromProvider(robot, "pan-servo")
    if err != nil {
        logger.Fatal(err)
    }

    // Sweep through positions
    for _, angle := range []uint32{0, 45, 90, 135, 180} {
        if err := s.Move(ctx, angle, nil); err != nil {
            logger.Fatal(err)
        }
        position, err := s.Position(ctx, nil)
        if err != nil {
            logger.Fatal(err)
        }
        fmt.Printf("Moved to %d°, position reads %d°\n", angle, position)
        time.Sleep(500 * time.Millisecond)
    }

    // Return to center
    if err := s.Move(ctx, 90, nil); err != nil {
        logger.Fatal(err)
    }
    fmt.Println("Returned to 90°")
}

Run it:

go run main.go

Troubleshooting

Servo jitters or buzzes
  • The servo may not be getting enough power. Servos draw significant current when loaded. Use an external power supply rather than powering from the SBC’s GPIO header.
  • Adjust frequency_hz. Some servos work better at 50 Hz, others at 300 Hz.
Servo doesn't reach full range
  • Adjust min_width_us and max_width_us to match your servo’s actual pulse width range. Check the servo’s datasheet for the correct values.
Servo doesn't respond
  • Verify the pin supports PWM output. Not all GPIO pins can generate PWM.
  • Check power and ground connections.
  • Confirm the pin number in your config matches the physical wiring.

What’s next