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

Hitec servo programming protocols: frames, transactions, the reply decoders and the driver interface. More...

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

Go to the source code of this file.

Data Structures

struct  hitec_xfer_t
 One transaction. More...
struct  hitec_rx_byte_t
 One received byte. More...
struct  hitec_capture_t
 What the receiver took, in the order the bytes came. More...
struct  hitec_reply_t
 A decoded reply. More...
struct  hitec_driver_t
 A pin driver. More...

Macros

#define HITEC_C_BIT_RATE   19200u
 Classic bit rate, in bit/s.
#define HITEC_D_BIT_RATE   115200u
 D series bit rate, in bit/s.
#define HITEC_C_FRAME_LEN   4u
 Bytes of a classic frame.
#define HITEC_C_READ_EEPROM   0x61u
 Command bytes.
#define HITEC_C_WRITE_EEPROM   0x62u
#define HITEC_C_READ_RAM   0x63u
#define HITEC_C_WRITE_RAM   0x64u
#define HITEC_C_READ_POSITION   0x65u
#define HITEC_C_MOVE   0x66u
#define HITEC_C_FIRST   0x67u
#define HITEC_C_ACK   0x2Bu
#define HITEC_C_NAK   0x2Du
#define HITEC_D_HEADER_REQUEST   0x96u
 D series frame layout: header bytes and frame lengths.
#define HITEC_D_HEADER_ANSWER   0x69u
#define HITEC_D_READ_LEN   5u
#define HITEC_D_WRITE_LEN   7u
#define HITEC_D_ANSWER_LEN   7u
#define HITEC_D_DATA_LEN   2u
 The length byte of a write and of an answer: 2 data bytes.
#define HITEC_TX_MAX   8u
#define HITEC_GAP_MAX   6u
#define HITEC_RX_MAX   16u
#define HITEC_TIME_MAX_US   4000000u
 Longest lead, window and level time, 4s.
#define HITEC_C_GAP_BITS   1u
 Idle bit times between two command bytes: 11 bit times start to start, 572.9us.
#define HITEC_C_ARM_BITS   9u
 The receiver starts at the start of the last byte's stop bit, 9 bit times after the start of that byte.
#define HITEC_C_REPLY_MIN_NS   494792u
 The first reply byte's start bit, from the start of the master's last byte.
#define HITEC_C_REPLY_MAX_NS   1000000u
#define HITEC_C_WINDOW_US   1500u
 Receive window after a command, waiting time.
#define HITEC_C_REPLY_LEN   2u
 Bytes of a reply.
#define HITEC_D_LEAD_US   88u
 The line driven low this long before the first start bit, as the HFP-30 does from a released line: 88us.
#define HITEC_D_SETTLE_BITS   1u
 Released bit times between the last stop bit and the receiver's start.
#define HITEC_D_ANSWER_MIN_NS   2000000u
 The answer's first start bit, from the end of the request's last stop bit.
#define HITEC_D_ANSWER_MAX_NS   20000000u
#define HITEC_D_WINDOW_US   25000u
 Receive window after a read, waiting time.
#define HITEC_D_READ_RX   8u
 Bytes a read receives: the 7 of the answer and the byte 0x00 the D485HW sends after it.
#define HITEC_NEXT_MIN_HALF_BITS   19u
 A byte of a reply starts 9.5 to 15 bit times after the start of the one before.
#define HITEC_NEXT_MAX_HALF_BITS   30u

Enumerations

enum  hitec_family_t { HITEC_CLASSIC = 0 , HITEC_D = 1 }
 The two servo families. More...
enum  hitec_drive_t { HITEC_DRIVE_OPEN_DRAIN = 0 , HITEC_DRIVE_PUSH_PULL = 1 }
 How the master drives the line. More...
enum  hitec_kind_t { HITEC_XFER_SEND = 0 , HITEC_XFER_LISTEN , HITEC_XFER_LEVEL }
 The kind of a transaction. More...
enum  hitec_rx_t {
  HITEC_RX_OK = 0 , HITEC_RX_ERR_ARG , HITEC_RX_ERR_ABORTED , HITEC_RX_ERR_OVERFLOW ,
  HITEC_RX_ERR_SILENT , HITEC_RX_ERR_EARLY , HITEC_RX_ERR_LATE , HITEC_RX_ERR_NAK ,
  HITEC_RX_ERR_LENGTH , HITEC_RX_ERR_FRAMING , HITEC_RX_ERR_GAP , HITEC_RX_ERR_ACK ,
  HITEC_RX_ERR_HEADER , HITEC_RX_ERR_ADDRESS , HITEC_RX_ERR_SUM
}
 Result of a decoder. More...
enum  hitec_c_expect_t { HITEC_C_EXPECT_VALUE = 0 , HITEC_C_EXPECT_ACK , HITEC_C_EXPECT_WORD }
 The reply shape a classic command expects. More...

Functions

uint32_t hitec_bit_rate (uint8_t family)
 Bit rate of family in bit/s; 0 for a family this module does not have.
uint32_t hitec_bits_ns (uint8_t family, uint32_t bits)
 bits bit times of family in ns, rounded to nearest; 0 for an unknown family.
uint8_t hitec_c_sum (uint8_t cmd, uint8_t p1, uint8_t p2)
 The byte that makes cmd p1 p2 sum sum to 0 modulo 256.
void hitec_c_frame (uint8_t cmd, uint8_t p1, uint8_t p2, uint8_t out[HITEC_C_FRAME_LEN])
 The 4 bytes of a classic command.
bool hitec_c_frame_ok (const uint8_t frame[HITEC_C_FRAME_LEN])
 True when the 4 bytes at frame sum to 0 modulo 256.
uint8_t hitec_d_sum (const uint8_t *bytes, size_t n)
 The low byte of the sum of the n bytes at bytes.
void hitec_d_read_frame (uint8_t id, uint8_t addr, uint8_t out[HITEC_D_READ_LEN])
 96 id addr 00 sum.
void hitec_d_write_frame (uint8_t id, uint8_t addr, uint16_t value, uint8_t out[HITEC_D_WRITE_LEN])
 96 id addr 02 lo hi sum, value little endian.
bool hitec_xfer_valid (const hitec_xfer_t *x)
 True when x is a transaction a driver runs: every field in its range, LISTEN with a window, LEVEL with a time.
void hitec_c_xfer (uint8_t cmd, uint8_t p1, uint8_t p2, hitec_xfer_t *out)
 A command and its 2-byte reply: 4 bytes, HITEC_C_GAP_BITS between them, HITEC_C_WINDOW_US, 2 bytes.
void hitec_d_read_xfer (uint8_t id, uint8_t addr, hitec_xfer_t *out)
 A read and its answer: lead, 5 bytes back to back, HITEC_D_WINDOW_US, HITEC_D_READ_RX bytes.
void hitec_d_write_xfer (uint8_t id, uint8_t addr, uint16_t value, hitec_xfer_t *out)
 A write: lead, 7 bytes back to back, nothing received.
bool hitec_listen_xfer (uint8_t family, uint32_t window_us, uint8_t max_rx, bool answer, uint8_t answer_byte, hitec_xfer_t *out)
 A LISTEN of window_us for up to max_rx bytes; with answer set the first byte is answered with answer_byte.
bool hitec_level_xfer (uint8_t family, uint8_t level, uint32_t time_us, hitec_xfer_t *out)
 A LEVEL of level for time_us.
bool hitec_c_expect_of (uint8_t cmd, hitec_c_expect_t *out)
 The reply cmd has.
hitec_rx_t hitec_c_decode (const hitec_capture_t *cap, hitec_c_expect_t expect, hitec_reply_t *out)
 Decode a classic reply.
hitec_rx_t hitec_d_decode (const hitec_capture_t *cap, uint8_t addr, hitec_reply_t *out)
 Decode a D series answer to a read of addr.
hitec_rx_t hitec_listen_decode (const hitec_capture_t *cap, uint8_t want, hitec_reply_t *out)
 Decode a listen for the byte want, as the classic entry's +: abort, overflow, a byte at all, its stop bit, its value, no byte after it. delay_ns is the first byte's time from the start of the listen.
uint32_t hitec_elapsed_us (uint32_t now, uint32_t since)
 Microseconds from since to now on a clock that wraps at 2^32.
bool hitec_due (uint32_t now, uint32_t since, uint32_t span_us)
 True when span_us or more have passed from since to now, also across the wrap of the clock.

Detailed Description

Hitec servo programming protocols: frames, transactions, the reply decoders and the driver interface.

Implements the signal-wire programming protocols of Hitec servos. 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").

Two wire families share one driver:

  • Classic, the HS-5xxx and HS-7xxx servos: 19 200bit/s, 8N1, least significant bit first, normal polarity (the line idles high, a start bit is low). The servo makes the high level; the master pulls low. A command is 4 bytes cmd p1 p2 sum whose sum is 0 modulo 256; the servo answers 2 bytes and starts its first one inside the stop bit of the master's last byte.
  • D series: 115 200bit/s, 8N1, least significant bit first, inverted (inside a frame the line rests low, a start bit is high). A write is 7 bytes 96 ID addr 02 lo hi sum, a read 5 bytes 96 ID addr 00 sum, the answer 7 bytes 69 ID addr 02 lo hi sum; the sum is the low byte of the sum of every byte after the header. A write is not answered.

This file has no hardware access. A transaction (hitec_xfer_t) says what to send and how long to listen; a driver puts it on the wire and hands back the received bytes with the time of each start bit (hitec_capture_t). The decoders check a capture against what the transaction asked for and give one result code per failed check.

The timings are measured on an HS-5645MG, an HS-7955TG and a D485HW, with the HPP-21 Plus and HFP-30 programmers, at a 1.4V threshold.

The module includes nothing outside the C standard headers and holds no global state.

SPDX-License-Identifier: MIT

Macro Definition Documentation

◆ HITEC_C_ACK

#define HITEC_C_ACK   0x2Bu

+

◆ HITEC_C_FIRST

#define HITEC_C_FIRST   0x67u

the first command; reply value, +

◆ HITEC_C_GAP_BITS

#define HITEC_C_GAP_BITS   1u

Idle bit times between two command bytes: 11 bit times start to start, 572.9us.

The HPP-21 Plus sends 572us start to start, the HFP-30 521us.

◆ HITEC_C_MOVE

#define HITEC_C_MOVE   0x66u

target high, low; reply + +

◆ HITEC_C_NAK

#define HITEC_C_NAK   0x2Du

-: described as the reply to a bad sum

◆ HITEC_C_READ_EEPROM

#define HITEC_C_READ_EEPROM   0x61u

Command bytes.

0x63 is described for the HS-5xxx and not seen on the wire; the others are measured. address; reply value, +

◆ HITEC_C_READ_POSITION

#define HITEC_C_READ_POSITION   0x65u

unused; reply high, low

◆ HITEC_C_READ_RAM

#define HITEC_C_READ_RAM   0x63u

address; reply value, +

◆ HITEC_C_REPLY_MIN_NS

#define HITEC_C_REPLY_MIN_NS   494792u

The first reply byte's start bit, from the start of the master's last byte.

Measured 508 to 517us after reads, up to 614us after writes and after the first read of a session; accepted 494.8us (the middle of the master's stop bit) to 1ms.

◆ HITEC_C_WRITE_EEPROM

#define HITEC_C_WRITE_EEPROM   0x62u

address, value; reply + +

◆ HITEC_C_WRITE_RAM

#define HITEC_C_WRITE_RAM   0x64u

address, value; reply + +

◆ HITEC_D_ANSWER_LEN

#define HITEC_D_ANSWER_LEN   7u

bytes of an answer

◆ HITEC_D_ANSWER_MIN_NS

#define HITEC_D_ANSWER_MIN_NS   2000000u

The answer's first start bit, from the end of the request's last stop bit.

Measured 5.9 to 6.5ms with the HFP-30, and 5.93 to 6.52ms over 153 reads by PRECHARGE (D485HW, 2026-10-20); 15.2ms is reported with another programmer. Accepted 2ms to 20ms.

◆ HITEC_D_HEADER_ANSWER

#define HITEC_D_HEADER_ANSWER   0x69u

first byte of an answer

◆ HITEC_D_HEADER_REQUEST

#define HITEC_D_HEADER_REQUEST   0x96u

D series frame layout: header bytes and frame lengths.

first byte of a request

◆ HITEC_D_READ_LEN

#define HITEC_D_READ_LEN   5u

bytes of a read request

◆ HITEC_D_WRITE_LEN

#define HITEC_D_WRITE_LEN   7u

bytes of a write request

◆ HITEC_GAP_MAX

#define HITEC_GAP_MAX   6u

idle bit times between two sent bytes

◆ HITEC_NEXT_MIN_HALF_BITS

#define HITEC_NEXT_MIN_HALF_BITS   19u

A byte of a reply starts 9.5 to 15 bit times after the start of the one before.

Measured: classic 516 to 528us (10 bit times are 520.8us), D series 86 to 87us (86.8us).

◆ HITEC_RX_MAX

#define HITEC_RX_MAX   16u

bytes one capture holds

◆ HITEC_TX_MAX

#define HITEC_TX_MAX   8u

bytes one transaction sends

Enumeration Type Documentation

◆ hitec_c_expect_t

The reply shape a classic command expects.

Enumerator
HITEC_C_EXPECT_VALUE 

value, +

HITEC_C_EXPECT_ACK 

+ +

HITEC_C_EXPECT_WORD 

high, low

◆ hitec_drive_t

How the master drives the line.

Classic is open drain only.

Enumerator
HITEC_DRIVE_OPEN_DRAIN 

The pad drives low and is released for high: classic toward the servo's high level, D series toward a pull-up.

HITEC_DRIVE_PUSH_PULL 

D series only: the pad drives both levels while it sends.

◆ hitec_family_t

The two servo families.

Enumerator
HITEC_CLASSIC 

HS-5xxx, HS-7xxx: 19 200bit/s, normal polarity

HITEC_D 

D series: 115 200bit/s, inverted

◆ hitec_kind_t

The kind of a transaction.

Enumerator
HITEC_XFER_SEND 

Send 1 to HITEC_TX_MAX bytes, then receive.

HITEC_XFER_LISTEN 

Receive only; the first byte may be answered with one byte.

HITEC_XFER_LEVEL 

Hold the line at one level for a time, then release it.

◆ hitec_rx_t

enum hitec_rx_t

Result of a decoder.

One code per failed check, in the order the decoders check.

Enumerator
HITEC_RX_OK 

every check passed

HITEC_RX_ERR_ARG 

NULL, another family, an unknown expectation

HITEC_RX_ERR_ABORTED 

the transaction was cut

HITEC_RX_ERR_OVERFLOW 

the capture is not whole

HITEC_RX_ERR_SILENT 

no byte

HITEC_RX_ERR_EARLY 

the first byte before the accept window

HITEC_RX_ERR_LATE 

the first byte after it

HITEC_RX_ERR_NAK 

classic: - and nothing after it

HITEC_RX_ERR_LENGTH 

fewer bytes than the reply has; classic: more

HITEC_RX_ERR_FRAMING 

a byte of the reply with a bad stop bit

HITEC_RX_ERR_GAP 

a byte of the reply too soon or too late after the one before

HITEC_RX_ERR_ACK 

classic: no + where the reply has one

HITEC_RX_ERR_HEADER 

D: not 0x69, or a length byte other than 2

HITEC_RX_ERR_ADDRESS 

D: the answer of another register

HITEC_RX_ERR_SUM 

D: the sum does not fit

Function Documentation

◆ hitec_bit_rate()

uint32_t hitec_bit_rate ( uint8_t family)

Bit rate of family in bit/s; 0 for a family this module does not have.

Parameters
familyhitec_family_t value.
Returns
Bit rate in bit/s, or 0 for an unknown family.

◆ hitec_bits_ns()

uint32_t hitec_bits_ns ( uint8_t family,
uint32_t bits )

bits bit times of family in ns, rounded to nearest; 0 for an unknown family.

Parameters
familyhitec_family_t value.
bitsNumber of bit times.
Returns
Duration in ns, rounded to nearest, or 0 for an unknown family.

◆ hitec_c_decode()

hitec_rx_t hitec_c_decode ( const hitec_capture_t * cap,
hitec_c_expect_t expect,
hitec_reply_t * out )

Decode a classic reply.

Checks in this order: the arguments, abort, overflow, a byte at all, the first byte's time (HITEC_C_REPLY_MIN_NS to HITEC_C_REPLY_MAX_NS), - alone, 2 bytes, both stop bits, the second byte's time, the + bytes. out is filled in every case as far as the checks went.

Parameters
capCapture to decode.
expectReply shape to check against.
outReceives the decoded reply.
Returns
HITEC_RX_OK, or the code of the first failed check.

◆ hitec_c_expect_of()

bool hitec_c_expect_of ( uint8_t cmd,
hitec_c_expect_t * out )

The reply cmd has.

False for a command byte this module does not know.

Parameters
cmdCommand byte.
outReceives the expected reply shape; may be NULL.
Returns
false for a command byte this module does not know.

◆ hitec_c_frame()

void hitec_c_frame ( uint8_t cmd,
uint8_t p1,
uint8_t p2,
uint8_t out[HITEC_C_FRAME_LEN] )

The 4 bytes of a classic command.

Parameters
cmdCommand byte.
p1First parameter byte.
p2Second parameter byte.
outReceives the four bytes of the frame.

◆ hitec_c_frame_ok()

bool hitec_c_frame_ok ( const uint8_t frame[HITEC_C_FRAME_LEN])

True when the 4 bytes at frame sum to 0 modulo 256.

Parameters
frameThe four bytes to check.
Returns
true when they sum to 0 modulo 256.

◆ hitec_c_sum()

uint8_t hitec_c_sum ( uint8_t cmd,
uint8_t p1,
uint8_t p2 )

The byte that makes cmd p1 p2 sum sum to 0 modulo 256.

Parameters
cmdCommand byte.
p1First parameter byte.
p2Second parameter byte.
Returns
The check byte that makes the four bytes sum to 0 modulo 256.

◆ hitec_c_xfer()

void hitec_c_xfer ( uint8_t cmd,
uint8_t p1,
uint8_t p2,
hitec_xfer_t * out )

A command and its 2-byte reply: 4 bytes, HITEC_C_GAP_BITS between them, HITEC_C_WINDOW_US, 2 bytes.

Parameters
cmdCommand byte.
p1First parameter byte.
p2Second parameter byte.
outReceives the transaction.

◆ hitec_d_decode()

hitec_rx_t hitec_d_decode ( const hitec_capture_t * cap,
uint8_t addr,
hitec_reply_t * out )

Decode a D series answer to a read of addr.

Checks in this order: the arguments, abort, overflow, a byte at all, the first byte's time (HITEC_D_ANSWER_MIN_NS to HITEC_D_ANSWER_MAX_NS), 7 bytes, their stop bits, their times, the header and length byte, the address, the sum. Bytes after the seventh are not checked: the D485HW sends 0x00 after every answer, and the release of the line reads as a byte with a bad stop bit. The ID byte is reported and not checked.

Parameters
capCapture to decode.
addrRegister address the read asked for.
outReceives the decoded reply.
Returns
HITEC_RX_OK, or the code of the first failed check.

◆ hitec_d_read_frame()

void hitec_d_read_frame ( uint8_t id,
uint8_t addr,
uint8_t out[HITEC_D_READ_LEN] )

96 id addr 00 sum.

Parameters
idServo ID.
addrRegister address.
outReceives the five bytes of the request.

◆ hitec_d_read_xfer()

void hitec_d_read_xfer ( uint8_t id,
uint8_t addr,
hitec_xfer_t * out )

A read and its answer: lead, 5 bytes back to back, HITEC_D_WINDOW_US, HITEC_D_READ_RX bytes.

Parameters
idServo ID.
addrRegister address.
outReceives the transaction.

◆ hitec_d_sum()

uint8_t hitec_d_sum ( const uint8_t * bytes,
size_t n )

The low byte of the sum of the n bytes at bytes.

Parameters
bytesBytes to sum.
nNumber of bytes.
Returns
The low byte of the sum.

◆ hitec_d_write_frame()

void hitec_d_write_frame ( uint8_t id,
uint8_t addr,
uint16_t value,
uint8_t out[HITEC_D_WRITE_LEN] )

96 id addr 02 lo hi sum, value little endian.

Parameters
idServo ID.
addrRegister address.
valueData word, sent little endian.
outReceives the seven bytes of the request.

◆ hitec_d_write_xfer()

void hitec_d_write_xfer ( uint8_t id,
uint8_t addr,
uint16_t value,
hitec_xfer_t * out )

A write: lead, 7 bytes back to back, nothing received.

Parameters
idServo ID.
addrRegister address.
valueData word, sent little endian.
outReceives the transaction.

◆ hitec_due()

bool hitec_due ( uint32_t now,
uint32_t since,
uint32_t span_us )

True when span_us or more have passed from since to now, also across the wrap of the clock.

A span is shorter than 2^31us.

Parameters
nowCurrent time, in us.
sinceEarlier time, in us.
span_usSpan to test, in us.
Returns
true when span_us or more have passed, also across the wrap of the clock.

◆ hitec_elapsed_us()

uint32_t hitec_elapsed_us ( uint32_t now,
uint32_t since )

Microseconds from since to now on a clock that wraps at 2^32.

Parameters
nowCurrent time, in us.
sinceEarlier time, in us.
Returns
Microseconds from since to now, on a clock that wraps at 2^32.

◆ hitec_level_xfer()

bool hitec_level_xfer ( uint8_t family,
uint8_t level,
uint32_t time_us,
hitec_xfer_t * out )

A LEVEL of level for time_us.

False, and out untouched, when the result would not be valid.

Parameters
familyhitec_family_t value.
levelLine level to hold, 0 or 1.
time_usHold time, in us.
outReceives the transaction; NULL returns false.
Returns
false when the result would not be valid.

◆ hitec_listen_decode()

hitec_rx_t hitec_listen_decode ( const hitec_capture_t * cap,
uint8_t want,
hitec_reply_t * out )

Decode a listen for the byte want, as the classic entry's +: abort, overflow, a byte at all, its stop bit, its value, no byte after it. delay_ns is the first byte's time from the start of the listen.

A listen for more bytes reads them from the capture.

Parameters
capCapture to decode.
wantByte expected as the first received byte.
outReceives the decoded reply.
Returns
HITEC_RX_OK, or the code of the first failed check.

◆ hitec_listen_xfer()

bool hitec_listen_xfer ( uint8_t family,
uint32_t window_us,
uint8_t max_rx,
bool answer,
uint8_t answer_byte,
hitec_xfer_t * out )

A LISTEN of window_us for up to max_rx bytes; with answer set the first byte is answered with answer_byte.

False, and out untouched, when the result would not be valid.

Parameters
familyhitec_family_t value.
window_usWaiting time for a start bit, in us.
max_rxBytes to receive.
answertrue to answer the first received byte.
answer_byteByte sent as the answer.
outReceives the transaction; NULL returns false.
Returns
false when the result would not be valid.

◆ hitec_xfer_valid()

bool hitec_xfer_valid ( const hitec_xfer_t * x)

True when x is a transaction a driver runs: every field in its range, LISTEN with a window, LEVEL with a time.

Whether the drive can make the level is the driver's check.

Parameters
xTransaction to check; NULL returns false.
Returns
true when x is a transaction a driver runs.