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>
10 KiB
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'slib_dir.shared/HexapodProtocol/is a header-only library both sketches#include <HexapodProtocol.h>.- PlatformIO has a single global
src_dirand only converts.inofiles at its root, soscripts/select_src_dir.py(apre:extra script) swapsPROJECT_SRC_DIRto each env'scustom_src_dir. That keepsrobot/andremote/plain Arduino sketch folders: one.inoplus paired.h/.cppper concern. Keep that structure. No giant files, nosrc/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 atC:\Users\patri\.platformio\penv\Scripts\pio.exe. Both envs build clean. Build both after touchingshared/.
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
InputMapperowns the scaling:MAX_LINEAR_MM_S150,MAX_TURN_RATE_DEG_S90,MAX_TILT_DEG20,MAX_POSE_OFFSET_MM40. - 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 afterLINK_TIMEOUT_SEC(0.3 s) without packets and then uses a defaultCmdPacket(flags 0 = disarmed, servos detached). The remote disarms itself afterLINK_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
InputMapperlogic 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_INPUTinrobot.ino(on by default). Serial 115200, single keys:aarm,ssit/stand,w/xvx ±200,q/evy ±200 (left/right),z/cyaw_rate ∓/±200,ggait,mmode, space = zero velocity. Keys inject throughComms::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).pshows raw/mapped sticks, IMU, link and state.ddumps 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_PINSleg 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_radsign), BodyPose roll/pitch sign, IMU register map, and the sit pose assuming feet stay grounded. - Suspected gait direction inversion: in
GaitEngine::updatethe stance phase moves the foot fromneutral - halftoneutral + 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 harnessz/cyaw 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_SIGNdepend on how the MPU6050 is mounted in the handset.- The Nextion must have
bauds=115200in Program.s, and the HMI must matchNEXTION.md. No.HMIfile is in the repo yet. The compiled.tftis 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 sizestatic_assert, this file's contract table, and both READMEs. - Match the existing style: config constants at the top of
Config.hwith BENCH comments for anything hardware-dependent,SmoothedValuerate limiting on any setpoint that can jump, and paired.h/.cppper 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
.HMIfile checked intoremote/.