rcbench
Servo and ESC test bench: panel, IOmcu and shared C
Loading...
Searching...
No Matches
hitec_session.h File Reference

Hitec servo programming protocols: the session with one servo, as a state machine that hands out steps and takes their results. More...

#include <stdbool.h>
#include <stdint.h>
#include "hitec_reg.h"
#include "hitec_wire.h"

Go to the source code of this file.

Data Structures

struct  hitec_ses_step_t
 One step. More...
struct  hitec_ses_result_t
 The result of a step. More...
struct  hitec_session_t
 The session with one servo. More...

Macros

#define HITEC_SES_TRIES   3u
 Tries of one transaction that has an answer: the first and 2 more.
#define HITEC_SES_C_GAP_MS   2u
 Gap after a classic transaction, ms.
#define HITEC_SES_D_GAP_MS   HITEC_D_WRITE_GAP_MS
 Gap after a D series transaction, ms: the HFP-30's write spacing.
#define HITEC_SES_D_ENTRY_WAIT_MS   HITEC_D_REBOOT_WAIT_MS
 Wait after the supply comes on before the first D series frame, ms: the wait after a reboot.
#define HITEC_SES_C_ENTRY_WINDOW_US   2000000u
 Classic entry: the window for the servo's first + from the ON, us.
#define HITEC_SES_C_NEXT_WINDOW_US   1000000u
 Classic entry: the window for the servo's next +, us.
#define HITEC_SES_POWER_WINDOW_US   1000u
 The window of the LISTEN of a POWER that waits for no byte, us: the D series entry and the classic leave.
#define HITEC_SES_OFF_MS   500u
 Supply off time of the classic entry and leave, ms.
#define HITEC_SES_OFF_MS_MAX   10000u
 Longest supply off time, ms.
#define HITEC_SES_SET_BYTES   32u
 Bytes of an address set: one bit for each of 256 addresses.

Enumerations

enum  hitec_ses_clock_t { HITEC_SES_CLOCK_US = 0 , HITEC_SES_CLOCK_MS = 1 }
 The clock the caller passes. More...
enum  hitec_ses_kind_t { HITEC_SES_STEP_XFER = 0 , HITEC_SES_STEP_POWER , HITEC_SES_STEP_WAIT , HITEC_SES_STEP_DONE }
 What a step asks of the caller. More...
enum  hitec_dec_t { HITEC_DEC_NONE = 0 , HITEC_DEC_C = 1 , HITEC_DEC_D = 2 , HITEC_DEC_LISTEN = 3 }
 The decoder of a step's capture. More...
enum  hitec_ses_run_t { HITEC_SES_RAN = 0 , HITEC_SES_CUT , HITEC_SES_FAILED }
 How a step ended. More...
enum  hitec_ses_t {
  HITEC_SES_OK = 0 , HITEC_SES_BUSY , HITEC_SES_ERR_BUSY , HITEC_SES_ERR_ARG ,
  HITEC_SES_ERR_NOT_IN_MODE , HITEC_SES_ERR_NO_SERVO , HITEC_SES_ERR_ENTRY , HITEC_SES_ERR_READ ,
  HITEC_SES_ERR_WRITE , HITEC_SES_ERR_VERIFY , HITEC_SES_ERR_PLAN , HITEC_SES_ERR_STEP ,
  HITEC_SES_ERR_CUT , HITEC_SES_ERR_LINK , HITEC_SES_ERR_ABORTED
}
 Result of an operation. More...

Functions

bool hitec_session_init (hitec_session_t *s, uint8_t series, uint8_t clock)
 Prepare a session for one series.
hitec_ses_t hitec_session_enter (hitec_session_t *s, bool power, uint16_t off_ms)
 Enter programming mode.
hitec_ses_t hitec_session_read_all (hitec_session_t *s)
 Read the series' full set: hitec_d_read_list() or hitec_c_read_ranges().
hitec_ses_t hitec_session_read_set (hitec_session_t *s, const uint8_t *addrs, uint16_t n)
 Read n addresses into the image, which keeps the rest.
hitec_ses_t hitec_session_write (hitec_session_t *s, const hitec_image_t *target)
 Write the servo from the image to target, then read it back.
hitec_ses_t hitec_session_run_plan (hitec_session_t *s, const hitec_plan_t *plan, const hitec_image_t *expect)
 Run a plan the caller built, such as hitec_d_factory_plan(), then read back as hitec_session_write() does.
hitec_ses_t hitec_session_leave (hitec_session_t *s)
 Leave programming mode.
bool hitec_session_abort (hitec_session_t *s)
 End the running operation with HITEC_SES_ERR_ABORTED.
hitec_ses_kind_t hitec_session_next (hitec_session_t *s, uint32_t now, hitec_ses_step_t *out)
 The step to run at now.
hitec_ses_t hitec_session_feed (hitec_session_t *s, uint32_t now, const hitec_ses_result_t *r)
 Take the result of the step that is out.
hitec_ses_t hitec_session_result (const hitec_session_t *s)
 The running operation's state.
bool hitec_ses_in_set (const uint8_t *set, uint8_t addr)
 True when address addr is in the set set.

Detailed Description

Hitec servo programming protocols: the session with one servo, as a state machine that hands out steps and takes their results.

Implements the session of the Hitec servo programming protocols. Hitec is a name or trademark of its owner; it is used here only to say which servos this module works with (README.md, "Legal notice and disclaimer").

The session runs one operation at a time: entry, a read, a write plan with its read-back, leave. It neither touches the wire nor reads a clock. The caller asks for the next step with hitec_session_next(), runs it and hands its result to hitec_session_feed():

  • XFER: one transaction (hitec_xfer_t), decoded by the named decoder;
  • POWER: the servo supply off for off_ms, on, and the transaction, a LISTEN, from the ON;
  • WAIT: nothing to run; ask again after wait_ms;
  • DONE: no operation runs; hitec_session_result() gives the last one's result.

The supply's voltage and current limit are the caller's. So are the port, the refusals before an entry (a classic servo without a supply the bench switches) and the journal; step_index and the plan are there for it.

Time: the caller passes the reading of one free-running 32-bit clock, in microseconds or in milliseconds (hitec_ses_clock_t), to every call that takes now. The session measures the gap before a transaction from the result of the one before, by unsigned difference, so the wrap of the clock at 2^32 does not matter. A gap has passed once the clock has moved by more than the gap in its ticks: one tick more than the gap, since a reading lies up to one tick behind the time.

The session object holds all state. Two sessions on two ports run side by side. No heap, no global or static mutable state.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ HITEC_SES_C_NEXT_WINDOW_US

#define HITEC_SES_C_NEXT_WINDOW_US   1000000u

Classic entry: the window for the servo's next +, us.

Measured 250 to 267ms after its first one.

Enumeration Type Documentation

◆ hitec_dec_t

The decoder of a step's capture.

The values are the HITEC link page's (link_hitec_dec_t).

Enumerator
HITEC_DEC_NONE 

no decode: the result's rx is not read

HITEC_DEC_C 

hitec_c_decode(), argument the command

HITEC_DEC_D 

hitec_d_decode(), argument the register

HITEC_DEC_LISTEN 

hitec_listen_decode(), argument the byte

◆ hitec_ses_clock_t

The clock the caller passes.

Enumerator
HITEC_SES_CLOCK_US 

microseconds, wraps after 71.6 minutes

HITEC_SES_CLOCK_MS 

milliseconds, wraps after 49.7 days

◆ hitec_ses_kind_t

What a step asks of the caller.

Enumerator
HITEC_SES_STEP_XFER 

run the transaction

HITEC_SES_STEP_POWER 

cycle the supply, then the LISTEN

HITEC_SES_STEP_WAIT 

run nothing; ask again later

HITEC_SES_STEP_DONE 

no operation runs

◆ hitec_ses_run_t

How a step ended.

Enumerator
HITEC_SES_RAN 

It ran to its end; rx and value are the decoder's.

HITEC_SES_CUT 

An abort, a close or a stop cut it.

HITEC_SES_FAILED 

It did not run: refused, not started, the supply refused, or the link was lost.

◆ hitec_ses_t

Result of an operation.

Enumerator
HITEC_SES_OK 

the operation ended without error

HITEC_SES_BUSY 

the operation runs

HITEC_SES_ERR_BUSY 

another operation runs

HITEC_SES_ERR_ARG 

NULL, an argument out of range, a result with no step out

HITEC_SES_ERR_NOT_IN_MODE 

no entry has succeeded

HITEC_SES_ERR_NO_SERVO 

no byte on the last try

HITEC_SES_ERR_ENTRY 

the entry got bytes, not the ones it waits for

HITEC_SES_ERR_READ 

a read failed on every try

HITEC_SES_ERR_WRITE 

a classic write got no + + on any try

HITEC_SES_ERR_VERIFY 

the read-back differs from the target; diff names the addresses

HITEC_SES_ERR_PLAN 

no plan: plan_result tells why, or a step of the other series

HITEC_SES_ERR_STEP 

the decoder refused the step (HITEC_RX_ERR_ARG)

HITEC_SES_ERR_CUT 

a step was cut

HITEC_SES_ERR_LINK 

a step did not run

HITEC_SES_ERR_ABORTED 

hitec_session_abort()

Function Documentation

◆ hitec_ses_in_set()

bool hitec_ses_in_set ( const uint8_t * set,
uint8_t addr )

True when address addr is in the set set.

Parameters
setHITEC_SES_SET_BYTES bytes, bit a % 8 of byte a / 8.
addrAddress.
Returns
true when the bit is set; false for NULL.

◆ hitec_session_abort()

bool hitec_session_abort ( hitec_session_t * s)

End the running operation with HITEC_SES_ERR_ABORTED.

With no step out it ends at once. With a step out it ends when that step's result comes: the caller cuts the step and feeds its result, which the session stores as far as it went. Until then hitec_session_next() gives the same step.

Parameters
sSession.
Returns
true when a step is out and its result is still owed.

◆ hitec_session_enter()

hitec_ses_t hitec_session_enter ( hitec_session_t * s,
bool power,
uint16_t off_ms )

Enter programming mode.

Classic: POWER (off off_ms, on, a LISTEN of HITEC_SES_C_ENTRY_WINDOW_US that answers the servo's + with +), a LISTEN of HITEC_SES_C_NEXT_WINDOW_US for the servo's next +, then the first command 67 00 00 99, whose reply's value is the version. A listen that gets no + ends the entry; the first command has HITEC_SES_TRIES tries. Classic entry always cycles the supply.

D series: with power, POWER (off off_ms, on, a LISTEN of HITEC_SES_POWER_WINDOW_US that waits for nothing) and HITEC_SES_D_ENTRY_WAIT_MS; without it, on the supply as it is. Then a read of register 0x00, the model, with HITEC_SES_TRIES tries.

The image starts empty and holds the version or the model. The off time is kept for the leave.

Parameters
sSession.
powerD series: cycle the supply first; classic: ignored.
off_msSupply off time, 0 to HITEC_SES_OFF_MS_MAX.
Returns
HITEC_SES_BUSY when the entry starts; HITEC_SES_ERR_BUSY, HITEC_SES_ERR_ARG.

◆ hitec_session_feed()

hitec_ses_t hitec_session_feed ( hitec_session_t * s,
uint32_t now,
const hitec_ses_result_t * r )

Take the result of the step that is out.

CUT ends the operation with HITEC_SES_ERR_CUT, FAILED with HITEC_SES_ERR_LINK. A step with a decoder: HITEC_RX_OK goes on; HITEC_RX_ERR_ARG ends it with HITEC_SES_ERR_STEP; HITEC_RX_ERR_ABORTED with HITEC_SES_ERR_CUT; every other code is a failed try. A failed try of a classic entry LISTEN ends the entry. A transaction with tries left goes again after its gap; one with none ends the operation with HITEC_SES_ERR_NO_SERVO when the last try was HITEC_RX_ERR_SILENT, else with the operation's error: ENTRY, READ or WRITE.

Every result starts the gap of the step that ended before anything else, a cut or lost step's included, since its frame may have reached the servo: the series gap, a plan step's longer wait, the D series entry's 1000ms, the 1000ms after a D series leave. An abort, an error or the end of the operation keeps it; the next operation waits it out.

Parameters
sSession.
nowThe clock when the step ended.
rThe result.
Returns
HITEC_SES_BUSY while the operation runs, then its result; HITEC_SES_ERR_ARG, and no change, for NULL or no step out.

◆ hitec_session_init()

bool hitec_session_init ( hitec_session_t * s,
uint8_t series,
uint8_t clock )

Prepare a session for one series.

No operation runs, no step is due, not in mode; off time HITEC_SES_OFF_MS.

Parameters
sSession.
serieshitec_family_t, chosen by the operator.
clockhitec_ses_clock_t of the now the caller passes.
Returns
false for NULL, an unknown series or an unknown clock.

◆ hitec_session_leave()

hitec_ses_t hitec_session_leave ( hitec_session_t * s)

Leave programming mode.

Classic: POWER (off off_ms, on, a LISTEN of HITEC_SES_POWER_WINDOW_US with no answer). D series: the reboot, 0x46 = 1, then HITEC_D_REBOOT_WAIT_MS before the next step of any operation. in_mode is 0 from the start of the leave. Taken in or out of mode.

Parameters
sSession.
Returns
HITEC_SES_BUSY when the leave starts; HITEC_SES_ERR_BUSY, HITEC_SES_ERR_ARG.

◆ hitec_session_next()

hitec_ses_kind_t hitec_session_next ( hitec_session_t * s,
uint32_t now,
hitec_ses_step_t * out )

The step to run at now.

A step stays out, and this call gives it again, until its result is fed. WAIT while the gap before the next transaction runs; DONE when no operation runs.

Parameters
sSession.
nowThe clock.
outReceives the step; NULL takes the kind only.
Returns
The step's hitec_ses_kind_t; DONE for a NULL session.

◆ hitec_session_read_all()

hitec_ses_t hitec_session_read_all ( hitec_session_t * s)

Read the series' full set: hitec_d_read_list() or hitec_c_read_ranges().

The image starts empty, the classic version kept. Addresses go in ascending order, each with HITEC_SES_TRIES tries; one that fails on every try ends the read, and the image keeps what was read before it.

Parameters
sSession.
Returns
HITEC_SES_BUSY when the read starts; HITEC_SES_ERR_BUSY, HITEC_SES_ERR_ARG, HITEC_SES_ERR_NOT_IN_MODE.

◆ hitec_session_read_set()

hitec_ses_t hitec_session_read_set ( hitec_session_t * s,
const uint8_t * addrs,
uint16_t n )

Read n addresses into the image, which keeps the rest.

As hitec_session_read_all(), in ascending order, each address once.

Parameters
sSession.
addrsCells, or even D series registers.
nCount, 1 to 256.
Returns
HITEC_SES_BUSY when the read starts; HITEC_SES_ERR_BUSY, HITEC_SES_ERR_ARG (NULL, n 0 or above 256, an odd D series register), HITEC_SES_ERR_NOT_IN_MODE.

◆ hitec_session_result()

hitec_ses_t hitec_session_result ( const hitec_session_t * s)

The running operation's state.

Parameters
sSession.
Returns
HITEC_SES_BUSY while an operation runs, else the last one's result; HITEC_SES_ERR_ARG for NULL.

◆ hitec_session_run_plan()

hitec_ses_t hitec_session_run_plan ( hitec_session_t * s,
const hitec_plan_t * plan,
const hitec_image_t * expect )

Run a plan the caller built, such as hitec_d_factory_plan(), then read back as hitec_session_write() does.

Parameters
sSession.
planThe plan, copied; steps of the session's series only.
expectThe image the read-back must hold, copied; NULL reads back and compares nothing.
Returns
As hitec_session_write(); HITEC_SES_ERR_PLAN for more than HITEC_PLAN_MAX steps, a step of the other series, a classic value above 255 or an odd D series register.

◆ hitec_session_write()

hitec_ses_t hitec_session_write ( hitec_session_t * s,
const hitec_image_t * target )

Write the servo from the image to target, then read it back.

The plan is hitec_plan(image, target), kept in plan. An empty plan ends with HITEC_SES_OK and sends nothing.

Classic: each cell and each balance (0x1C) as 62 cell value sum, with HITEC_SES_TRIES tries for its + +, HITEC_SES_C_GAP_MS apart. Then a read of cells 0x00 to 0x23.

D series: each write, the save and the reboot as a write frame with no answer, each followed by its plan wait (20ms, 100ms, 1000ms), at least HITEC_SES_D_GAP_MS. Then a read of the full set.

The read-back must hold every writable address (hitec_writable()) and the classic balance as target holds it, where target knows it; diff names the ones that differ. step_index counts the plan's writes that went out.

The image holds only what the servo confirmed. A classic write answered + + goes into it. A D series write has no answer and does not change it; the read-back puts the register there.

A write that ends with anything but HITEC_SES_OK once a plan step was handed out and its result fed (RAN, CUT or FAILED: a cut or lost step may have reached the servo) marks as not known (hitec_image_forget()) every address the servo may have changed and this write's read-back has not read: D series, every register of the full read and every register of the plan, since a save, a reboot or a factory reset may change registers the plan does not name; classic, the cell of a write whose last try got no + + (a cut or lost try included) and the balance 0x1C. hitec_plan() then refuses with HITEC_PLAN_ERR_UNKNOWN (HITEC_SES_ERR_PLAN): the caller reads the servo before it writes again. A write refused or aborted before a step's result was fed, and one whose read-back ended with HITEC_SES_ERR_VERIFY, forget nothing.

Parameters
sSession.
targetThe draft, copied.
Returns
HITEC_SES_BUSY when the write starts, HITEC_SES_OK for an empty plan; HITEC_SES_ERR_BUSY, HITEC_SES_ERR_ARG (NULL, a target of the other series), HITEC_SES_ERR_NOT_IN_MODE, HITEC_SES_ERR_PLAN.