Behaviour for servo-specific control table definitions.
Each servo model has a different memory layout (control table) that defines the addresses, sizes, and conversions for readable/writable parameters.
Implementing a Control Table
defmodule MyServo do
@behaviour Feetech.ControlTable
@impl true
def model_name, do: "MyServo"
@impl true
def registers do
%{
id: {5, 1, nil},
goal_position: {42, 2, :position},
present_position: {56, 2, :position}
}
end
endConversion Types
The third element of each register tuple specifies how to convert between raw register values and user-friendly values:
nil- No conversion, raw integer value:bool- 0/1 to false/truefloat- Scale factor (e.g.,0.1for voltage in 0.1V units):position- Steps to radians (unsigned, servo-specific):position_signed- Steps to radians with sign-magnitude encoding (bit 15 = sign):speed- Speed units to rad/s:speed_signed- Signed speed to rad/s (sign-magnitude, bit 15 = sign):load_signed- Signed load percentage (sign-magnitude, bit 10 = sign):mode- Operating mode enum:baud_rate- Baud rate enum{:sign_magnitude, sign_bit}- Raw sign-magnitude with specified sign bit{module, decode_fun, encode_fun}- Custom conversion functions
Summary
Types
Memory address in control table
Number of bytes for register
Conversion specification for translating between raw and user values.
Register definition tuple
Register name atom
Map of register names to their definitions
Functions
Resolve a run of registers to the single address and length that covers them.
Decodes raw bytes from a register to a user value.
Decodes raw bytes to an integer (no conversion).
Encodes a user value to raw bytes for writing to a register.
Encodes a raw integer value to bytes (no conversion).
Looks up a register definition by name.
Types
@type address() :: non_neg_integer()
Memory address in control table
@type byte_length() :: 1 | 2 | 4
Number of bytes for register
@type conversion() :: nil | :bool | :position | :position_signed | :speed | :speed_signed | :load_signed | :mode | :baud_rate | float() | {:sign_magnitude, non_neg_integer()} | {module(), atom(), atom()}
Conversion specification for translating between raw and user values.
@type register_def() :: {address(), byte_length(), conversion()}
Register definition tuple
@type register_name() :: atom()
Register name atom
@type registers() :: %{required(register_name()) => register_def()}
Map of register names to their definitions
Callbacks
Functions
@spec contiguous_span(module(), [register_name()]) :: {:ok, {address(), pos_integer()}} | {:error, term()}
Resolve a run of registers to the single address and length that covers them.
A SYNC_WRITE addresses one contiguous span of the control table, so writing
several registers in one instruction is only possible when they sit next to
each other, in order. Returns the address to start at and the number of bytes
each servo contributes.
Writing a position, its travel time and its speed together is the reason this
exists: the servo begins moving the moment goal_position lands, so a speed
arriving in a later packet can miss the start of the move entirely.
Examples
iex> Feetech.ControlTable.contiguous_span(Feetech.ControlTable.STS3215,
...> [:goal_position, :goal_time, :goal_speed])
{:ok, {42, 6}}Order matters, because the span is laid out in address order:
iex> Feetech.ControlTable.contiguous_span(Feetech.ControlTable.STS3215,
...> [:goal_speed, :goal_position])
{:error, {:not_contiguous, :goal_speed, :goal_position}}So does adjacency:
iex> Feetech.ControlTable.contiguous_span(Feetech.ControlTable.STS3215,
...> [:goal_position, :goal_speed])
{:error, {:not_contiguous, :goal_position, :goal_speed}}A single register is a span of one:
iex> Feetech.ControlTable.contiguous_span(Feetech.ControlTable.STS3215, [:goal_position])
{:ok, {42, 2}}
@spec decode(module(), register_name(), binary()) :: {:ok, term()} | {:error, atom()}
Decodes raw bytes from a register to a user value.
Decodes raw bytes to an integer (no conversion).
@spec encode(module(), register_name(), term()) :: {:ok, binary()} | {:error, atom()}
Encodes a user value to raw bytes for writing to a register.
@spec encode_raw(module(), register_name(), integer()) :: {:ok, binary()} | {:error, atom()}
Encodes a raw integer value to bytes (no conversion).
@spec get_register(module(), register_name()) :: {:ok, register_def()} | {:error, :unknown_register}
Looks up a register definition by name.