Previous
Sensor
Add a servo to your machine’s configuration so you can control its angular position from the Viam app and from code.
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.
pan-servo) and click Create.
{
"board": "my-board",
"pin": "12"
}
| Attribute | Type | Required | Description |
|---|---|---|---|
board | string | Yes | Name of the board component. |
pin | string | Yes | GPIO pin for the servo signal wire. |
min_angle_deg | float | No | Minimum angle. Default: 0. |
max_angle_deg | float | No | Maximum angle. Default: 180. |
starting_position_deg | float | No | Position on startup. Default: 0. |
frequency_hz | int | No | PWM 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:
| Attribute | Type | Description |
|---|---|---|
min_width_us | int | Minimum pulse width in microseconds (>450). |
max_width_us | int | Maximum pulse width in microseconds (<2500). |
Click Save, then expand the TEST section.
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
Was this page helpful?
Glad to hear it! If you have any other feedback please let us know:
We're sorry about that. To help us improve, please tell us what we can do better:
Thank you!