Add an encoder
Add an encoder to your machine’s configuration so you can track motor position and direction from the Viam app and from code.
Concepts
Encoders come in two main varieties:
- Incremental (quadrature): two signal channels (A and B) let Viam determine both position and direction. This is the most common type.
- Single-channel: one signal channel tracks position but can’t determine direction on its own.
Both connect to a board’s GPIO pins as digital interrupts. The encoder counts signal transitions (“ticks”) that correspond to motor shaft rotation.
Once you configure an encoder and reference it from a motor component, the
motor gains accurate position control. GoFor and GoTo use actual encoder
feedback instead of time-based estimates. Browse all available encoder models in the Viam registry.
Steps
1. Prerequisites
- Your machine is online in the Viam app.
- A board component is configured.
- Your encoder is wired to the board’s GPIO pins.
2. Add an encoder component
- Click the + button.
- Select Configuration block.
- Search for the encoder model that matches your hardware:
- For a two-channel quadrature encoder, search for incremental.
- For a single-channel encoder, search for single encoder.
- Name your encoder (for example,
left-encoder) and click Create.
3. Configure encoder attributes
Incremental (quadrature) encoder:
{
"board": "my-board",
"pins": {
"a": "37",
"b": "35"
}
}
| Attribute | Type | Required | Description |
|---|---|---|---|
board | string | Yes | Name of the board component. |
pins.a | string | Yes | GPIO pin for channel A. |
pins.b | string | Yes | GPIO pin for channel B. |
Single-channel encoder:
{
"board": "my-board",
"pins": {
"i": "37"
}
}
| Attribute | Type | Required | Description |
|---|---|---|---|
board | string | Yes | Name of the board component. |
pins.i | string | Yes | GPIO pin for the signal channel. |
4. Link the encoder to a motor
After creating the encoder, update your motor’s configuration to reference it:
{
"board": "my-board",
"max_rpm": 200,
"pins": { "a": "11", "b": "13", "pwm": "15" },
"encoder": "left-encoder",
"ticks_per_rotation": 600
}
ticks_per_rotation is the number of encoder ticks in one full rotation of
the motor shaft. Check your encoder’s datasheet. Common values are 12, 48,
200, or 600 depending on the encoder type and any gearing.
5. Save and test
Click Save, then expand the Test section for the encoder.
- Manually rotate the motor shaft. The tick count should change.
- Rotating in one direction should increase the count; the other direction should decrease it (with an incremental encoder).
Then test the motor. GoFor should now stop accurately at the target
position.
Try it
Read the encoder position and reset it.
To get the credentials for the code below, go to your machine’s page in the Viam app, click the CONNECT tab, and select API keys. Copy the API key and API key ID. Copy the machine address from the same tab. When you run the code below, you’ll see the encoder’s current position, then reset it to zero. Manually rotate the motor shaft to verify the count changes.
pip install viam-sdk
Save this as encoder_test.py:
import asyncio
from viam.robot.client import RobotClient
from viam.components.encoder import Encoder
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)
encoder = Encoder.from_robot(robot, "left-encoder")
position, pos_type = await encoder.get_position()
print(f"Position: {position} ({pos_type})")
properties = await encoder.get_properties()
print(f"Ticks count supported: {properties.ticks_count_supported}")
print(f"Angle degrees supported: {properties.angle_degrees_supported}")
await encoder.reset_position()
print("Position reset to zero")
position, pos_type = await encoder.get_position()
print(f"Position after reset: {position}")
await robot.close()
if __name__ == "__main__":
asyncio.run(main())
Run it:
python encoder_test.py
mkdir encoder-test && cd encoder-test
go mod init encoder-test
go get go.viam.com/rdk
Save this as main.go:
package main
import (
"context"
"fmt"
"go.viam.com/rdk/components/encoder"
"go.viam.com/rdk/logging"
"go.viam.com/rdk/robot/client"
"go.viam.com/rdk/utils"
)
func main() {
ctx := context.Background()
logger := logging.NewLogger("encoder-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)
enc, err := encoder.FromProvider(robot, "left-encoder")
if err != nil {
logger.Fatal(err)
}
position, posType, err := enc.Position(ctx, encoder.PositionTypeUnspecified, nil)
if err != nil {
logger.Fatal(err)
}
fmt.Printf("Position: %.2f (%v)\n", position, posType)
properties, err := enc.Properties(ctx, nil)
if err != nil {
logger.Fatal(err)
}
fmt.Printf("Ticks count supported: %v\n", properties.TicksCountSupported)
fmt.Printf("Angle degrees supported: %v\n", properties.AngleDegreesSupported)
if err := enc.ResetPosition(ctx, nil); err != nil {
logger.Fatal(err)
}
fmt.Println("Position reset to zero")
}
Run it:
go run main.go
Troubleshooting
What’s next
- Encoder API reference: full method documentation.
- Add a Motor: configure the motor that this encoder monitors.
- Add a Base: use motors with encoders for accurate odometry on a wheeled base.
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!