Beam Bots Logo

Beam Bots Pigpio servo control

CILicense: Apache 2.0Hex version badgeHexdocs badgeREUSE statusAsk DeepWiki

BB.Servo.Pigpio

BB integration for driving RC servos via pigpio on Raspberry Pi.

This library provides an actuator module for controlling RC servos directly connected to Raspberry Pi GPIO pins using the pigpio daemon.

Installation

Add bb_servo_pigpio to your list of dependencies in mix.exs:

def deps do
[
{:bb_servo_pigpio, "~> 0.7.0"}
]
end

Requirements

Usage

Define a joint with a servo actuator in your robot DSL:

defmodule MyRobot do
use BB
# A robot won't move until armed, and arming is a command.
commands do
command :arm do
handler BB.Command.Arm
allowed_states [:disarmed]
end
command :disarm do
handler BB.Command.Disarm
allowed_states [:idle]
end
end
topology do
link :base do
joint :shoulder do
type :revolute
limit lower: ~u(-45 degree), upper: ~u(45 degree),
velocity: ~u(60 degree_per_second), effort: ~u(1 newton_meter)
actuator :servo, {BB.Servo.Pigpio.Actuator, pin: 17}
sensor :feedback, {BB.Sensor.OpenLoopPositionEstimator, actuator: :servo}
link :arm do
# ...
end
end
end
end
end

The actuator automatically derives its configuration from the joint limits - no need to specify servo rotation range or speed separately.

Sending Commands

Use the BB.Actuator module to send commands to servos. Arm the robot first — commands to a disarmed robot are refused before they reach the driver:

{:ok, cmd} = MyRobot.arm()
{:ok, :armed, _} = BB.Command.await(cmd)

There are three ways to deliver a command. They differ in transport, not in what the driver sees: all three arrive at the actuator's handle_command/2, and none can skip the framework's arm check or its joint-to-motor transmission.

Every function takes either the actuator's unique name or its full path through the topology.

Pubsub Delivery (for orchestration)

Commands are published via pubsub, enabling logging, replay, and multi-subscriber patterns:

# Send position command via pubsub
BB.Actuator.set_position(MyRobot, [:base, :shoulder, :servo], 0.5)
# With options
BB.Actuator.set_position(MyRobot, [:base, :shoulder, :servo], 0.5,
command_id: make_ref()
)

Direct Delivery (for time-critical control)

Commands bypass pubsub for lower latency. Use when responsiveness matters more than observability:

# Fire-and-forget
BB.Actuator.set_position!(MyRobot, :servo, 0.5)

Synchronous Delivery (with acknowledgement)

Wait for the actuator to acknowledge the command:

case BB.Actuator.set_position_sync(MyRobot, :servo, 0.5) do
{:ok, :accepted} -> :ok
{:error, reason} -> handle_error(reason)
end

Components

Actuator

BB.Servo.Pigpio.Actuator controls servo position via PWM.

Options:

OptionTypeDefaultDescription
pinintegerrequiredGPIO pin number
min_pulseinteger500Minimum PWM pulse width (microseconds)
max_pulseinteger2500Maximum PWM pulse width (microseconds)
update_speedunit50 HzPWM update frequency

To reverse the servo relative to the joint, configure the actuator's joint transmission rather than passing an actuator option:

actuator :servo, {BB.Servo.Pigpio.Actuator, pin: 17} do
transmission do
reversed? true
end
end

Behaviour:

Sensor

Use BB.Sensor.OpenLoopPositionEstimator from the BB core library for position feedback. It subscribes to actuator BeginMotion messages and interpolates position during movement.

sensor :feedback, {BB.Sensor.OpenLoopPositionEstimator, actuator: :servo}

How It Works

Position Mapping

The actuator maps the joint's position limits to the servo's PWM range:

Joint lower limit -> min_pulse (500 microseconds)
Joint upper limit -> max_pulse (2500 microseconds)
Joint centre -> mid_pulse (1500 microseconds)

For a joint with limits -45 degrees to +45 degrees:

Position Feedback

Since RC servos don't provide position feedback, the open-loop position estimator estimates position based on commanded targets and expected arrival times:

  1. Actuator sends command and publishes BeginMotion with expected arrival time
  2. Sensor receives BeginMotion and interpolates position during movement
  3. After arrival time, sensor reports the target position

This provides realistic position feedback for trajectory planning and monitoring.

Motion Lifecycle

When a position command is processed:

  1. Actuator clamps position to joint limits
  2. Converts angle to PWM pulse width
  3. Sends PWM command to pigpiod
  4. Publishes BB.Message.Actuator.BeginMotion with:
    • initial_position - where the servo was
    • target_position - where it's going
    • expected_arrival - when it should arrive (monotonic milliseconds)
    • command_id - correlation ID (if provided)
    • command_type - :position

Documentation

Full documentation is available at HexDocs.