An actuator that uses a Feetech controller to drive a serial bus servo.
Configuration is derived from the joint's motor_profile injected by
BB.Actuator.Server:
- Position limits from
motor_profile.motor_lower/motor_upper - Velocity limit from
motor_profile.motor_velocity_limit - Position range maps to the servo's goal_position register
When initialised, the actuator:
- Disables torque on the servo
- Asks the servo what model it is, and sets its operating mode if it isn't already in the configured one
- Registers with the controller, receiving the shared ETS table reference
- Subscribes to the commands its mode admits
When a position command is received, the actuator:
- Clamps the position to motor limits
- Converts to servo position units (0-4095 for 360 degrees)
- Writes goal_position and goal_speed to the controller's ETS table
- Publishes a
BB.Message.Actuator.BeginMotionviaBB.Actuator.publish_begin_motion/3(which handles the motor → joint-space conversion)
The controller picks up pending commands on its next loop tick and batches them
into efficient sync_write operations on the serial bus.
Operating modes
An STS servo does one thing at a time, and :mode picks which. It's set once
at startup and never changed while running: the register lives in EEPROM,
which has a finite write budget and can only be written with torque off and
the servo unlocked.
:mode | commands it accepts |
|---|---|
:position (default) | Position, Trajectory, Effort, Hold, Stop |
:velocity | Velocity, Effort, Hold, Stop |
Anything outside that list is refused by the framework with
BB.Error.State.UnsupportedCommand before it reaches the driver.
The servo's :pwm and :step modes aren't offered. PWM is an open-loop duty
cycle with no feedback, which no BB command means; step mode is multi-turn
positioning, which the joint limits this driver derives its range from don't
describe.
Commands
Command.Position— travel to a position. Clamped to the joint's limits, and travelling no faster than its velocity limit.Command.Trajectory— walk a list of waypoints, one timer per leg.Command.Velocity— turn at a rate, clamped to the joint's velocity limit.Command.Effort— a ceiling, not a goal. These servos have no torque goal register;torque_limitcaps what a move may draw, as a fraction of the model's rated stall torque. Setting one won't make the joint move, and the torque it eventually produces is approximate — seeBB.Servo.Feetech.Modelfor how rough. The servo's own overload protection can also wind the ceiling down under sustained load.Command.Stop— cut torque, leaving the joint passive and free to be backdriven. Both:immediateand:deceleratedo the same thing, because becoming passive is a single register write with no ramp available.Command.Hold— stay under power without driving. A servo already doing that needs nothing; after aStopit re-applies torque where the joint has come to rest.
A motion command sent to a joint left passive by Stop re-applies torque on
the way past, so callers don't have to pair the two.
Velocity and Effort carry a duration. When it runs out, :expiry_action
decides whether the joint goes passive (:stop, the default) or stays under
power without driving (:hold) — the same choice the controller's
:disarm_action makes for the whole bus.
Beware that a passive joint under load will move, and Stop does not wait for
it to settle. Hold and Position both re-apply torque from the servo's own
present position, so neither snaps back to a pre-Stop goal, but a joint that
has sagged will still be somewhere the caller may not expect.
None of this is the safety path: making the hardware safe is disarm/1, which
is robot-wide and leaves the robot unable to move until it is armed again.
Position feedback
These servos answer position queries, so a joint driven by one needs no
separate sensor and capabilities/1 says so. It is the controller that reads
and publishes, not the actuator: BB.Servo.Feetech.Controller sync_reads
present_position for every registered servo on each tick of its loop and
publishes a BB.Message.Sensor.JointState per joint, which is what writes the
joint's configuration in BB.Robot.State. Nothing turns that off —
:status_poll_interval_ms governs only the temperature/voltage/load poll, and
:position_deadband suppresses republishing an unchanged position rather than
reading it.
Velocity and effort feedback aren't declared. Speed is never read back at all,
and present_load is read only by the status poll, which reports it as
BB.Servo.Feetech.Message.ServoStatus — a percentage of the servo's own
limit, not a torque — rather than as joint state.
Example DSL Usage
controllers do
controller :feetech, {BB.Servo.Feetech.Controller,
port: "/dev/ttyUSB0",
baud_rate: 1_000_000
}
end
joint :shoulder, type: :revolute do
limit lower: ~u(-90 degree), upper: ~u(90 degree), velocity: ~u(60 degree_per_second)
actuator :servo, {BB.Servo.Feetech.Actuator, servo_id: 1, controller: :feetech}
end
Summary
Functions
Safety disarm callback.