|
Feetech STS Driver
|
A portable, dependency-free C11 core for Feetech STS series smart servos over half-duplex UART. The project includes an STM32F103 hardware port using DMA-driven half-duplex UART with IDLE-line reception. The core is covered by 198 host unit tests in CI. The current AF open-drain port has completed 304,227 live transactions with zero retries or hard communication failures on the tested single-servo bench, separately from 390,385 historical transactions.
Quick links: API documentation | Hardware validation | Design decisions | Porting guide | Engineering postmortem
An intermittent test failure was traced to state persisting between test runs rather than the initially suspected bus EMI. The investigation and supporting campaign logs are documented as an engineering postmortem in issue #8. The full hardware bring-up was merged in PR #11.
Three layers, each independently testable:
Protocol layer (sts_protocol): stateless packet framing, checksum, and response parsing. It holds no knowledge of hardware or servo state, and each parse call has no shared parser state. Synchronisation of shared bus access remains the responsibility of the service or application layer.
Service layer (sts_servo, sts_servo_cmd): HAL-agnostic servo management built on the protocol layer. Handles bus wiring, servo handles, and all transactions through a single command engine (sts_execute_command), which also carries the transaction, retry, and failure counters. Platform UART is injected as function pointers via sts_bus_t, so the core has no MCU dependency. The command set (sts_servo_cmd) provides motion control, telemetry reads, and configuration built on the register-access primitives.
Port layer (Ports/sts_ports_stm32.c): the STM32F103 implementation of the injected transmit, receive, and flush contract: DMA transfers with IDLE-line variable-length reception, direct-wired half-duplex turnaround, and bounded error recovery. Implementing the transport callbacks ports the core to another MCU, with this file as a worked reference; see the porting guide.
The STM32F103 port is exercised by an on-target integration suite (Hardware_Tests/) over SEGGER RTT, covering protocol validation, position, speed and acceleration control, the torque state machine, and moving-status semantics. An instrumented stress runner drives repeated campaigns and reports pass, skip and fail counts, a per-test failure histogram, quiescence-gate activation, and bus-counter deltas.
| Campaign | Runs | Transactions | Retries | Hard failures |
|---|---|---|---|---|
| Baseline | 100 | 67,801 | 0 | 0 |
| Intermediate | 30 | 19,269 | 0 | 0 |
| Final | 200 | 152,980 | 0 | 0 |
| Flush removal | 200 | 150,335 | 0 | 0 |
| Total | 530 | 390,385 | 0 | 0 |
These historical figures cover the earlier GPIO-switching port, with the timed turnaround flush removed for the fourth campaign. The current AF_OD results are kept separate because they describe a different electrical configuration:
| AF_OD campaign | Runs passed | Individual tests passed | Transactions | Retries | Hard failures |
|---|---|---|---|---|---|
| Rise-time capture | 200/200 | 6,400/6,400 | 152,140 | 0 | 0 |
| Fall-time capture | 200/200 | 6,400/6,400 | 152,087 | 0 | 0 |
| Total | 400/400 | 12,800/12,800 | 304,227 | 0 | 0 |
The Phase 1 report records a passed functional non-regression gate. Electrical evidence closure and subsequent software phases remain pending.
These figures characterise one bench configuration: a single MCU, servo, cable, and environment. They are direct observations, not a general reliability claim for the design. Methodology, per-campaign conditions, and the oscilloscope work that retracted an earlier transient hypothesis are documented in docs/hardware-validation.md.
Why are hardware errors on ping treated as online? STS_ERR_HARDWARE means the servo responded, so communication succeeded. The fault lies in the servo's internal state, such as overtemperature or overload, rather than the bus. Marking the servo offline in this case would be incorrect. Hardware error semantics belong in a higher application layer that has the context to make recovery decisions.
Why a centralised command engine? All service layer transactions route through a single sts_execute_command function. This keeps TX framing, RX receive, and response parsing in one place, gives retry policy and the transaction counters a single home, and provides one point of change for future work such as mutex protection or asynchronous IO.
Why caller-provided buffers? Dynamic allocation can introduce variable latency, fragmentation, and runtime allocation failures. Caller-provided API buffers make memory ownership and capacity explicit while keeping the core usable on targets without a heap.
The full set, including the operating-mode and direction-encoding semantics, is in docs/design-decisions.md.
The example below shows the injection contract with a minimal blocking transport. The production STM32 port in Ports/sts_ports_stm32.c implements the same contract over DMA with IDLE-line half-duplex reception. Full API documentation is published at grish98.github.io/Feetech_Stm32.
GitHub tags are the source of project versions; the badge above follows the latest tag automatically. On a push to main, successful host tests and static analysis allow CI to create the next tag: v0.1 for the first merge with this workflow, then v0.2, v0.3, and so on. Feature branches and pull-request checks do not create tags. Re-running CI for an already tagged commit keeps its existing version; superseded main commits are skipped. Documentation deployment runs separately and does not gate tagging.
Tags remain attached to their original commits. There are no manually maintained per-file version numbers, and dependency/tool versions are independent of the project version.
The library uses CMake with a dual-target build system. Host tests run on the development machine with a native compiler, so no hardware is required. The ARM firmware target is selected automatically when an arm-none-eabi toolchain is configured.
All 198 tests should pass across two suites: 40 in the protocol layer and 158 in the service and command layers. The --config and -C flags are required by multi-config generators such as Visual Studio and are ignored by single-config generators such as Ninja and Unix Makefiles.
Requires Doxygen. CI publishes the same output to GitHub Pages on every push to main.
Generated HTML is written to build_native/html/index.html.
The Feetech STS protocol is a binary half-duplex UART protocol. Every packet follows this structure:
| Byte(s) | Field | Description |
|---|---|---|
| 0–1 | Header | Always 0xFF 0xFF |
| 2 | ID | Servo ID (0–253, 254 = broadcast) |
| 3 | Length | Number of remaining bytes (excl. header + ID) |
| 4 | Instruction | Command or status byte |
| 5..N | Parameters | Optional payload (0–253 bytes) |
| N+1 | Checksum | ~(ID + Length + Instruction + Params) & 0xFF |
Responses are variable length, which is why the STM32 port frames them with UART IDLE-line detection rather than a fixed byte count. The servo supports four operating modes (position, speed, PWM, and step) selected through STS_SetOperatingMode; their semantics are described in docs/design-decisions.md.
Hardware_Tests/ timing through the STS_Delay_ms and STS_GetTick_ms port hooks so the integration and stress suites can validate a new MCU port unmodifiedThe core (protocol and service layers) has no platform-specific dependencies and builds on any target with a C11 toolchain. Fixed-width integer types are used throughout for cross-architecture correctness. Porting to another MCU means supplying three transport callbacks, transmit, receive, and the optional flush_rx; no other platform code is required to drive servos. The porting guide documents the full contract, and the STM32F103 port in Lib/STS_Servo/Ports/ is a worked reference implementation.
Copyright (c) 2026 Grisham Balloo. All rights reserved.