Files
hexapod-robot/2026-current/CLAUDE.md
T
mangavalkandClaude Opus 5.5 91dac16fca Draft: shared protocol, remote firmware, project docs
Not yet verified on hardware.

- shared/HexapodProtocol: CmdPacket (now 20 bytes, adds remote_roll /
  remote_pitch), FLAG_* bits and radio settings as the single protocol
  definition; static payload size = sizeof(CmdPacket) on both radios.
- robot: use the shared protocol; vx = forward (harness w/x <-> q/e);
  InputMapper adds remote tilt on top of the right stick when
  FLAG_REMOTE_GYRO is set, and robot.ino no longer zeroes stick
  roll/pitch when it's off.
- remote (Teensy 4.1): Sticks (calibration, EEPROM), RemoteIMU,
  RemoteState (interlocks, link-loss disarm, smoothing), PacketBuilder,
  RadioLink, NextionUI, SerialConsole.
- CLAUDE.md, README.md, remote/README.md, remote/NEXTION.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 18:14:00 +02:00

10 KiB
Raw Blame History

Hexapod: project memory

Hexapod walking robot plus a custom handheld remote, both Teensy-based and linked one-way over nRF24L01 (remote → robot, with hardware auto-ack only). This file is the persistent context for working on either side. Read it fully before changing anything.

Layout and toolchain

PlatformIO, one platformio.ini, two environments:

Env Board Sketch dir Purpose
robot teensy40 robot/ walking robot
remote teensy41 remote/ handheld transmitter
  • shared/ is PlatformIO's lib_dir. shared/HexapodProtocol/ is a header-only library both sketches #include <HexapodProtocol.h>.
  • PlatformIO has a single global src_dir and only converts .ino files at its root, so scripts/select_src_dir.py (a pre: extra script) swaps PROJECT_SRC_DIR to each env's custom_src_dir. That keeps robot/ and remote/ plain Arduino sketch folders: one .ino plus paired .h/.cpp per concern. Keep that structure. No giant files, no src/ folders.
  • RF24 (nrf24/RF24, resolved 1.6.x) comes from lib_deps. Servo, Wire, SPI and EEPROM ship with the Teensy framework.
  • Build: pio run (both), pio run -e robot, pio run -e remote. On this machine pio lives at C:\Users\patri\.platformio\penv\Scripts\pio.exe. Both envs build clean. Build both after touching shared/.

Communication contract (shared/HexapodProtocol/HexapodProtocol.h)

CmdPacket, 20 bytes (static_asserted), sent remote → robot at 100 Hz (robot CONTROL_HZ is 60):

Field Range Meaning
vx ±1000 + forward
vy ±1000 + strafe left
yaw_rate ±1000 + turn left (CCW). Ignored by the robot in mode B
rx, ry, rz ±1000 right stick: roll/pitch/yaw, or X/Y/Z offset when FLAG_RSTICK_XLATE
remote_roll, remote_pitch ±1000 handset tilt feedforward, used only when FLAG_REMOTE_GYRO
speed_scale 0–255 multiplies vx/vy on the robot
flags bits ARMED, STAND, WALK, MODE_B, RSTICK_XLATE, GAIT_WAVE, REMOTE_GYRO, ROBOT_GYRO (bits 0–7)
seq 0–255 increments per packet (robot doesn't check it yet)
  • Axis convention (ROS REP-103 style): body +X forward, +Y left, +Z up. +roll = right side down, +pitch = nose down, +yaw = CCW.
  • Normalized fields. The remote never knows robot geometry. The robot's InputMapper owns the scaling: MAX_LINEAR_MM_S 150, MAX_TURN_RATE_DEG_S 90, MAX_TILT_DEG 20, MAX_POSE_OFFSET_MM 40.
  • Radio settings live in the same header: address "HXPD1", channel 96, 250 kbps, PA LOW, static payload size = sizeof(CmdPacket) on both radios. That makes version mismatches fail closed: packets are not received, the robot times out, and it disarms.
  • Changing the protocol means reflashing both boards. Keep it ≤ 32 bytes and update the static_assert.
  • Failsafes: the robot sets linkOk() false after LINK_TIMEOUT_SEC (0.3 s) without packets and then uses a default CmdPacket (flags 0 = disarmed, servos detached). The remote disarms itself after LINK_LOST_MS (300 ms) without acks, so the robot can't re-arm itself when the link returns.
  • Division of labor: the remote only fills fields per this contract. Mode A/B interpretation, heading-hold, pose scaling and all smoothing of pose happen on the robot. Don't duplicate InputMapper logic in the remote.

Robot architecture (robot/, Teensy 4.0)

Loop at CONTROL_HZ (60): comms → InputMapper → IMU → heading integrate → StateMachine → gait/sit-stand targets → BodyPose → Limits clamp → IK → joint-rate clamp → servos.

Module Role
robot.ino setup/loop wiring, open-loop heading estimate, sit/stand blend, TEST_SERIAL_INPUT harness
Config.h all placeholder geometry/pins/rates
SmoothedValue.h rate-limited setpoint smoother used for every discrete command
Kinematics per-leg 3-DOF analytic IK, neutralStance()
GaitEngine tripod/wave patterns, per-leg phase, pattern swap only at touchdown
BodyPose 6-axis body pose (smoothed command + IMU roll/pitch correction) applied to foot targets
RobotIMU MPU6050 complementary filter (α 0.98), gated by FLAG_ROBOT_GYRO
StateMachine DISARMED → SIT → STAND → WALKING, sit/stand blend at 1/s. Disarm is instant
InputMapper CmdPacket → Twist + PoseCommand + mode flags. Mode B = heading-hold from stick angle. Adds remote tilt on top of stick pose when FLAG_REMOTE_GYRO, clamped to MAX_TILT_DEG
Limits workspace clamp (70 mm horiz / 60 mm vert), joint rate clamp, ≥3-legs-in-stance check (not acted on yet)
Comms nRF24 RX, link timeout, injectPacket() for the harness
ServoDriver attach/detach/write, skips invalid IK solutions

Remote architecture (remote/, Teensy 4.1)

Loop: drain Nextion + USB serial input every pass. On each TX_HZ (100) tick: sticks → IMU → radio link status → RemoteState (smoothers, link-loss disarm) → PacketBuilder → radio.write → Nextion render.

Module Role
remote.ino loop wiring, status print, packet dump
Config.h all placeholder pins, calibration defaults, deadzones, rates
UiTypes.h UiCmd/UiEvent (operator requests), UiView (what the display shows)
Sticks 6 analog axes: ADC averaging + IIR, piecewise calibration around center, invert, deadzone with rescale, expo → ±1000. Boot-time center capture, sweep calibration saved to EEPROM (magic/version/checksum)
RemoteIMU MPU6050, same complementary filter as the robot, axis remap for mounting, boot gyro-bias capture, "level" zero
RemoteState authoritative operator state and interlocks: arm needs centered sticks, disarm clears stand, stand needs armed, calibrate needs disarmed, link-loss auto-disarm. Smooths speed scale and fades remote tilt in/out
PacketBuilder the inverse of InputMapper: stick frame → body frame per the contract, per-submode right-stick mapping, FLAG_WALK with 250 ms linger (twist ignored in mode B), tilt deadzone/scale × fade, zeros motion when disarmed
RadioLink nRF24 TX, short retries (1, 3), ack-based link status and 32-packet quality %
NextionUI library-free Nextion driver. HMI sends '#' len cmd [val] frames via printh. Remote pushes state by component name with diffing, a 2 s full refresh, and a redraw on the Nextion 0x88 "ready" frame
SerialConsole USB keystrokes that mirror every UI command plus diagnostics
SmoothedValue.h copy of the robot's (kept separate so robot/ stays untouched)
NEXTION.md HMI build guide: component names, txt_maxl, touch event code

Remote stick-frame convention: logical X + = right, Y + = forward, twist + = CCW. The mapping into the packet is in PacketBuilder.cpp (e.g. vy = -L_X, translate mode ry = -R_X).

Testing without the other board

  • Robot without remote: #define TEST_SERIAL_INPUT in robot.ino (on by default). Serial 115200, single keys: a arm, s sit/stand, w/x vx ±200, q/e vy ±200 (left/right), z/c yaw_rate ∓/±200, g gait, m mode, space = zero velocity. Keys inject through Comms::injectPacket(), the same path radio packets take. Order: arm → stand → walk. The harness does not exercise the right stick or remote tilt. Add keys if needed.
  • Remote without robot or display: the USB serial console (see remote/README.md). p shows raw/mapped sticks, IMU, link and state. d dumps packets at 10 Hz. With no robot, the link shows DOWN and arming still works; the link-loss disarm only fires after the link was up once.

Placeholders and bench-check items (not yet verified on hardware)

Robot (robot/README.md has detail):

  • All of Config.h: leg mount angles/positions, link lengths, joint limits, stance radius/height, servo pins, µs/deg calibration, MAX_JOINT_DEG_S (in robot.ino), Limits envelope, InputMapper maxima.
  • Pin conflict: SERVO_PINS leg 3 uses pins 11 and 12, which are Teensy 4.0 SPI MOSI/MISO needed by the nRF24. Must be moved. Some servo pins (26, 27, 30…) may also not be PWM-capable on the 4.0. Verify.
  • IK knee direction (tibia_rad sign), BodyPose roll/pitch sign, IMU register map, and the sit pose assuming feet stay grounded.
  • Suspected gait direction inversion: in GaitEngine::update the stance phase moves the foot from neutral - half to neutral + half, i.e. with the commanded direction. That pushes the body backward for +vx, and the same happens for yaw. Fix = swap start/end. Flagged to the owner, not changed. The harness z/c yaw keys currently rely on this behavior and need rechecking after the fix.
  • Heading for mode B is open-loop (integrated commanded ω). Fusing IMU yaw is a planned upgrade.
  • Limits::stancePolygonOk() is computed but not acted on.

Remote (remote/README.md has detail):

  • All pins in remote/Config.h (radio CE 9 / CSN 10, sticks A0–A3/A6/A7, MPU6050 on 18/19, Nextion on Serial2 7/8).
  • STICK_INVERT[], calibration defaults, deadzones, expo.
  • IMU_AXIS_FROM_CHIP / IMU_AXIS_SIGN depend on how the MPU6050 is mounted in the handset.
  • The Nextion must have bauds=115200 in Program.s, and the HMI must match NEXTION.md. No .HMI file is in the repo yet. The compiled .tft is gitignored.
  • Radio retry timing (RADIO_RETRY_*) is untested under real link loss.

Working rules for this repo

  • The robot side is owner-verified code. Don't edit robot/ unless something is factually wrong relative to the protocol, and ask the owner first even then. Flag suspected robot bugs instead of fixing them.
  • Protocol changes go in shared/HexapodProtocol/ only. Update the size static_assert, this file's contract table, and both READMEs.
  • Match the existing style: config constants at the top of Config.h with BENCH comments for anything hardware-dependent, SmoothedValue rate limiting on any setpoint that can jump, and paired .h/.cpp per concern.
  • Planned or possible next steps: an ack-payload telemetry channel from robot → remote (state, battery, link) to show real robot state on the Nextion, battery monitoring on both sides, a .HMI file checked into remote/.