|
rcbench
Servo and ESC test bench: panel, IOmcu and shared C
|
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. | |
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:
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
| #define HITEC_C_ACK 0x2Bu |
+
| #define HITEC_C_FIRST 0x67u |
the first command; reply value, +
| #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.
| #define HITEC_C_MOVE 0x66u |
target high, low; reply + +
| #define HITEC_C_NAK 0x2Du |
-: described as the reply to a bad sum
| #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, +
| #define HITEC_C_READ_POSITION 0x65u |
unused; reply high, low
| #define HITEC_C_READ_RAM 0x63u |
address; reply value, +
| #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.
| #define HITEC_C_WRITE_EEPROM 0x62u |
address, value; reply + +
| #define HITEC_C_WRITE_RAM 0x64u |
address, value; reply + +
| #define HITEC_D_ANSWER_LEN 7u |
bytes of an answer
| #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.
| #define HITEC_D_HEADER_ANSWER 0x69u |
first byte of an answer
| #define HITEC_D_HEADER_REQUEST 0x96u |
D series frame layout: header bytes and frame lengths.
first byte of a request
| #define HITEC_D_READ_LEN 5u |
bytes of a read request
| #define HITEC_D_WRITE_LEN 7u |
bytes of a write request
| #define HITEC_GAP_MAX 6u |
idle bit times between two sent bytes
| #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).
| #define HITEC_RX_MAX 16u |
bytes one capture holds
| #define HITEC_TX_MAX 8u |
bytes one transaction sends
| enum hitec_c_expect_t |
| enum hitec_drive_t |
| enum hitec_family_t |
| enum hitec_kind_t |
| enum hitec_rx_t |
Result of a decoder.
One code per failed check, in the order the decoders check.
| uint32_t hitec_bit_rate | ( | uint8_t | family | ) |
Bit rate of family in bit/s; 0 for a family this module does not have.
| family | hitec_family_t value. |
| 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.
| family | hitec_family_t value. |
| bits | Number of bit times. |
| 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.
| cap | Capture to decode. |
| expect | Reply shape to check against. |
| out | Receives the decoded reply. |
| 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.
| cmd | Command byte. |
| out | Receives the expected reply shape; may be NULL. |
| 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.
| cmd | Command byte. |
| p1 | First parameter byte. |
| p2 | Second parameter byte. |
| out | Receives the four bytes of the frame. |
| 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.
| frame | The four bytes to check. |
| 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.
| cmd | Command byte. |
| p1 | First parameter byte. |
| p2 | Second parameter byte. |
| 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.
| cmd | Command byte. |
| p1 | First parameter byte. |
| p2 | Second parameter byte. |
| out | Receives the transaction. |
| 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.
| cap | Capture to decode. |
| addr | Register address the read asked for. |
| out | Receives the decoded reply. |
| void hitec_d_read_frame | ( | uint8_t | id, |
| uint8_t | addr, | ||
| uint8_t | out[HITEC_D_READ_LEN] ) |
96 id addr 00 sum.
| id | Servo ID. |
| addr | Register address. |
| out | Receives the five bytes of the request. |
| 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.
| id | Servo ID. |
| addr | Register address. |
| out | Receives the transaction. |
| uint8_t hitec_d_sum | ( | const uint8_t * | bytes, |
| size_t | n ) |
The low byte of the sum of the n bytes at bytes.
| bytes | Bytes to sum. |
| n | Number of bytes. |
| 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.
| id | Servo ID. |
| addr | Register address. |
| value | Data word, sent little endian. |
| out | Receives the seven bytes of the request. |
| 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.
| id | Servo ID. |
| addr | Register address. |
| value | Data word, sent little endian. |
| out | Receives the transaction. |
| 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.
| now | Current time, in us. |
| since | Earlier time, in us. |
| span_us | Span to test, in 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.
| now | Current time, in us. |
| since | Earlier time, in us. |
| 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.
| family | hitec_family_t value. |
| level | Line level to hold, 0 or 1. |
| time_us | Hold time, in us. |
| out | Receives the transaction; NULL returns false. |
| 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.
| cap | Capture to decode. |
| want | Byte expected as the first received byte. |
| out | Receives the decoded reply. |
| 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.
| family | hitec_family_t value. |
| window_us | Waiting time for a start bit, in us. |
| max_rx | Bytes to receive. |
| answer | true to answer the first received byte. |
| answer_byte | Byte sent as the answer. |
| out | Receives the transaction; NULL returns false. |
| 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.
| x | Transaction to check; NULL returns false. |