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>
This commit is contained in:
1 parent
ad77b06c36
commit
91dac16fca
30 files changed
+1706
-57
No files matched your search
@@ -3,15 +3,19 @@
|
||||
#include <RF24.h>
|
||||
#include <SPI.h>
|
||||
|
||||
static_assert(HXP_RADIO_DATA_RATE == RF24_250KBPS && HXP_RADIO_PA_LEVEL == RF24_PA_LOW,
|
||||
"HexapodProtocol.h radio enum values out of sync with RF24.h");
|
||||
|
||||
static RF24 radio(RADIO_CE_PIN, RADIO_CSN_PIN);
|
||||
|
||||
bool Comms::begin() {
|
||||
radio_present_ = radio.begin();
|
||||
if (radio_present_) {
|
||||
radio.openReadingPipe(1, RADIO_ADDRESS);
|
||||
radio.setPALevel(RF24_PA_LOW);
|
||||
radio.setDataRate(RF24_250KBPS);
|
||||
radio.setChannel(96); // pick one clear of your local Wi-Fi
|
||||
radio.setPayloadSize(HXP_RADIO_PAYLOAD); // mismatched protocol versions won't be received at all
|
||||
radio.openReadingPipe(1, HXP_RADIO_ADDRESS);
|
||||
radio.setPALevel((rf24_pa_dbm_e)HXP_RADIO_PA_LEVEL);
|
||||
radio.setDataRate((rf24_datarate_e)HXP_RADIO_DATA_RATE);
|
||||
radio.setChannel(HXP_RADIO_CHANNEL);
|
||||
radio.startListening();
|
||||
}
|
||||
return radio_present_;
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
|
||||
// Remote -> robot, sent at a fixed rate. Kept as the single source of truth
|
||||
// here so a future remote sketch just has to fill this same struct and
|
||||
// radio.write() it - nothing on the robot side needs to change.
|
||||
struct CmdPacket {
|
||||
int16_t vx = 0, vy = 0; // left stick: strafe / forward-back, -1000..1000
|
||||
int16_t yaw_rate = 0; // left stick twist, -1000..1000
|
||||
int16_t rx = 0, ry = 0, rz = 0; // right stick: roll/pitch/yaw OR X/Y/height, per flags
|
||||
uint8_t speed_scale = 128; // 0-255
|
||||
uint8_t flags = 0; // bit-packed, see FLAG_* below
|
||||
uint8_t seq = 0;
|
||||
};
|
||||
|
||||
constexpr uint8_t FLAG_ARMED = 1 << 0;
|
||||
constexpr uint8_t FLAG_STAND = 1 << 1; // 0 = sit requested, 1 = stand requested
|
||||
constexpr uint8_t FLAG_WALK = 1 << 2; // stick past deadzone -> walking
|
||||
constexpr uint8_t FLAG_MODE_B = 1 << 3; // 0 = mode A direct, 1 = mode B heading-hold
|
||||
constexpr uint8_t FLAG_RSTICK_XLATE = 1 << 4; // 0 = right stick = rotation, 1 = translation
|
||||
constexpr uint8_t FLAG_GAIT_WAVE = 1 << 5; // 0 = tripod requested, 1 = wave requested
|
||||
constexpr uint8_t FLAG_REMOTE_GYRO = 1 << 6; // remote tilt feedforward enabled
|
||||
constexpr uint8_t FLAG_ROBOT_GYRO = 1 << 7; // robot stabilization feedback enabled
|
||||
#include <HexapodProtocol.h> // CmdPacket + FLAG_* + radio settings, shared with the remote (shared/)
|
||||
|
||||
class Comms {
|
||||
public:
|
||||
|
||||
@@ -49,9 +49,10 @@ constexpr float SWING_LIFT_MM = 35.0f;
|
||||
constexpr float CONTROL_HZ = 60.0f;
|
||||
|
||||
// ---- Radio (nRF24L01) ----
|
||||
// Address/channel/data rate/PA/payload size live in shared/HexapodProtocol
|
||||
// so the robot and remote can't drift apart. Only this board's wiring is here.
|
||||
constexpr uint8_t RADIO_CE_PIN = 33;
|
||||
constexpr uint8_t RADIO_CSN_PIN = 34;
|
||||
constexpr uint8_t RADIO_ADDRESS[6] = "HXPD1"; // must match the remote once it exists
|
||||
constexpr float LINK_TIMEOUT_SEC = 0.3f;
|
||||
|
||||
// ---- Setpoint smoothing rates ----
|
||||
|
||||
@@ -56,5 +56,15 @@ MapperOutput InputMapper::map(const CmdPacket& p, float heading_deg, float dt) {
|
||||
out.pose.yaw_deg = rz * MAX_TILT_DEG;
|
||||
}
|
||||
|
||||
// Remote handset tilt feedforward: its own packet fields, gated only by
|
||||
// FLAG_REMOTE_GYRO, added on top of whatever the right stick commands (so
|
||||
// it works in both right-stick sub-modes). Total tilt stays within MAX_TILT_DEG.
|
||||
if (out.remote_gyro_enabled) {
|
||||
out.pose.roll_deg = constrain(out.pose.roll_deg + normalize(p.remote_roll) * MAX_TILT_DEG,
|
||||
-MAX_TILT_DEG, MAX_TILT_DEG);
|
||||
out.pose.pitch_deg = constrain(out.pose.pitch_deg + normalize(p.remote_pitch) * MAX_TILT_DEG,
|
||||
-MAX_TILT_DEG, MAX_TILT_DEG);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
@@ -1,16 +1,20 @@
|
||||
# Hexapod robot firmware (Teensy 4.0)
|
||||
|
||||
This is the **robot-side** firmware only. It is fully functional on its own
|
||||
(driven by the serial test harness below) and is already wired to receive
|
||||
commands from a future remote over nRF24L01 — the packet format in
|
||||
`Comms.h` is the contract the remote sketch must match.
|
||||
This is the **robot-side** firmware. It is fully functional on its own
|
||||
(driven by the serial test harness below) and receives commands from the
|
||||
remote (`../remote/`) over nRF24L01. The packet format lives in
|
||||
`../shared/HexapodProtocol/HexapodProtocol.h` — the single definition both
|
||||
sketches compile against.
|
||||
|
||||
## Required libraries
|
||||
## Building
|
||||
|
||||
Install via Arduino Library Manager (or Teensyduino's manager):
|
||||
From the repo root (PlatformIO): `pio run -e robot`, flash with
|
||||
`pio run -e robot -t upload`, monitor with `pio device monitor -e robot`.
|
||||
RF24 is pulled in automatically via `lib_deps`; Servo ships with the Teensy
|
||||
framework.
|
||||
|
||||
- **RF24** by TMRh20 — nRF24L01 radio driver
|
||||
- **Servo** — bundled with Teensyduino, nothing to install
|
||||
Arduino IDE also works if you copy `shared/HexapodProtocol/` into your
|
||||
sketchbook `libraries/` folder and install **RF24** by TMRh20.
|
||||
|
||||
## Before you trust this on the floor
|
||||
|
||||
@@ -26,8 +30,12 @@ your own robot and edit:
|
||||
- `SERVO_PINS` — must be PWM-capable pins on your Teensy 4.0 wiring
|
||||
- `SERVO_US_AT_0DEG` / `SERVO_US_PER_DEG` — calibrate per servo, they vary
|
||||
batch to batch
|
||||
- `RADIO_ADDRESS` / channel (set in `Comms.cpp`) — must match the remote
|
||||
once it exists
|
||||
- `RADIO_CE_PIN` / `RADIO_CSN_PIN` — your nRF24 wiring. Address, channel,
|
||||
data rate, PA level and payload size are in `shared/HexapodProtocol` so
|
||||
they can't drift from the remote.
|
||||
- **Pin conflict:** `SERVO_PINS` leg 3 uses pins 11 and 12, which are the
|
||||
Teensy 4.0's SPI MOSI/MISO that the nRF24 needs. Move those two servos
|
||||
before wiring the radio.
|
||||
|
||||
## Bench-check checklist (do this before the first stand-up)
|
||||
|
||||
@@ -50,6 +58,14 @@ for your build:
|
||||
If your chassis is meant to rest its belly on the ground instead, this
|
||||
needs a different, sequenced approach (fold legs up rather than just
|
||||
lowering).
|
||||
5. **Gait travel direction** (`GaitEngine.cpp`) — the protocol defines
|
||||
`+vx` = forward, `+vy` = left, `+yaw_rate` = turn left (CCW). As written,
|
||||
the stance phase sweeps each foot from `neutral - half_stride` to
|
||||
`neutral + half_stride`, i.e. the foot moves *with* the commanded
|
||||
direction, which pushes the body the opposite way. Verify with `w` on the
|
||||
bench; if the robot backs up, swap `start`/`end` in `GaitEngine::update`
|
||||
(fixes translation and yaw together). The `z`/`c` harness keys were
|
||||
written against the current behavior — recheck them after that fix.
|
||||
|
||||
## Testing without a remote yet
|
||||
|
||||
@@ -61,8 +77,8 @@ keystrokes) and use:
|
||||
|-----|--------|
|
||||
| `a` | toggle armed |
|
||||
| `s` | toggle sit / stand |
|
||||
| `w` / `x` | walk forward / backward |
|
||||
| `q` / `e` | strafe left / right |
|
||||
| `w` / `x` | walk forward / backward (`vx` ±) |
|
||||
| `q` / `e` | strafe left / right (`vy` ±) |
|
||||
| `z` / `c` | yaw left / right |
|
||||
| `g` | toggle gait: tripod ↔ wave |
|
||||
| `m` | toggle walk mode: direct (A) ↔ heading-hold (B) |
|
||||
@@ -79,17 +95,20 @@ Arm (`a`), then stand (`s`), before trying to walk — the state machine
|
||||
enforces `DISARMED → SIT → STAND → WALKING` in order and will otherwise
|
||||
ignore walk commands.
|
||||
|
||||
## What's ready for the remote
|
||||
## Link with the remote
|
||||
|
||||
- `Comms.h`'s `CmdPacket` struct and `FLAG_*` bit constants are the exact
|
||||
wire format the remote must send (as an RF24 payload to `RADIO_ADDRESS`
|
||||
on the same channel/data-rate/PA-level set in `Comms::begin()`).
|
||||
- `CmdPacket` and the `FLAG_*` bits in `shared/HexapodProtocol` are the
|
||||
exact wire format. Both radios set a static payload size of
|
||||
`sizeof(CmdPacket)`, so a robot and remote built from different protocol
|
||||
versions won't receive each other at all (and the robot fails safe).
|
||||
- Failsafe: if no packet arrives for `LINK_TIMEOUT_SEC`, `Comms::linkOk()`
|
||||
goes false and the main loop falls back to a zeroed `CmdPacket`, which
|
||||
disarms the robot.
|
||||
- `FLAG_REMOTE_GYRO` / `FLAG_ROBOT_GYRO` independently gate whether the
|
||||
remote's tilt feeds into body pose and whether the robot's own IMU
|
||||
stabilizes it — matching the touchscreen toggles you described.
|
||||
- The right stick's roll/pitch/yaw (or X/Y/Z) always apply. The remote's
|
||||
handset tilt arrives in its own `remote_roll` / `remote_pitch` fields and
|
||||
is added on top only while `FLAG_REMOTE_GYRO` is set, capped at
|
||||
`MAX_TILT_DEG` total (`InputMapper`). `FLAG_ROBOT_GYRO` independently
|
||||
enables the robot's own IMU stabilization.
|
||||
- `FLAG_RSTICK_XLATE` switches the right stick between rotation-axes and
|
||||
translation-axes interpretation, and `FLAG_MODE_B` switches between the
|
||||
two walk modes, all inside the existing `InputMapper`.
|
||||
@@ -108,6 +127,6 @@ robot/
|
||||
├── StateMachine.h/.cpp - DISARMED/SIT/STAND/WALKING with smoothed sit-stand blend
|
||||
├── InputMapper.h/.cpp - CmdPacket -> twist/pose/mode decoding
|
||||
├── Limits.h/.cpp - workspace clamp, joint rate limit, stance polygon check
|
||||
├── Comms.h/.cpp - nRF24L01 receive + packet/flags definition
|
||||
├── Comms.h/.cpp - nRF24L01 receive (packet definition is in shared/HexapodProtocol)
|
||||
└── ServoDriver.h/.cpp - servo attach/relax/write
|
||||
```
|
||||
@@ -36,10 +36,11 @@ void pollSerialTestInput() {
|
||||
switch (c) {
|
||||
case 'a': test_packet.flags ^= FLAG_ARMED; break;
|
||||
case 's': test_packet.flags ^= FLAG_STAND; break;
|
||||
case 'w': test_packet.vy = min(test_packet.vy + 200, 1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'x': test_packet.vy = max(test_packet.vy - 200, -1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'q': test_packet.vx = max(test_packet.vx - 200, -1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'e': test_packet.vx = min(test_packet.vx + 200, 1000); test_packet.flags |= FLAG_WALK; break;
|
||||
// vx = forward (+X), vy = strafe left (+Y) - see HexapodProtocol.h axis convention.
|
||||
case 'w': test_packet.vx = min(test_packet.vx + 200, 1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'x': test_packet.vx = max(test_packet.vx - 200, -1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'q': test_packet.vy = min(test_packet.vy + 200, 1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'e': test_packet.vy = max(test_packet.vy - 200, -1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'z': test_packet.yaw_rate = max(test_packet.yaw_rate - 200, -1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'c': test_packet.yaw_rate = min(test_packet.yaw_rate + 200, 1000); test_packet.flags |= FLAG_WALK; break;
|
||||
case 'g': test_packet.flags ^= FLAG_GAIT_WAVE; break;
|
||||
@@ -108,13 +109,9 @@ void loop() {
|
||||
|
||||
gait.setRequestedPattern(mapped.gait_wave ? &GAIT_WAVE : &GAIT_TRIPOD);
|
||||
|
||||
// Remote-gyro toggle off -> drop commanded tilt but keep XYZ offset/height.
|
||||
PoseCommand pose_cmd = mapped.pose;
|
||||
if (!mapped.remote_gyro_enabled) {
|
||||
pose_cmd.roll_deg = 0;
|
||||
pose_cmd.pitch_deg = 0;
|
||||
}
|
||||
pose.setCommand(pose_cmd);
|
||||
// Stick pose + remote-tilt feedforward (gated on FLAG_REMOTE_GYRO) are
|
||||
// already combined in InputMapper.
|
||||
pose.setCommand(mapped.pose);
|
||||
pose.setCorrection(mapped.robot_gyro_enabled ? -imu.rollDeg() : 0,
|
||||
mapped.robot_gyro_enabled ? -imu.pitchDeg() : 0);
|
||||
pose.update(dt);
|
||||
|
||||
Reference in new issue
Block a user