Firmware simulation, Unity project, and sim-verified robot fixes
Simulation - sim/: the unchanged robot firmware compiled into hexapod_sim.dll against PC stand-ins (Arduino, Servo, Wire + register-level MPU6050, RF24 with matching-config radio), a kinematic body model (support plane + planted-foot odometry) that also drives the simulated IMU, and a headless smoke test (sim_smoke). build.ps1 builds and tests. - Hexapod_Simulation_Unity/ folded in (history kept in a bundle outside the repo). Assets/HexapodSim loads the DLL, drives the existing leg rig, and offers joystick / keyboard / robot-serial input plus a test panel. Robot (all found or confirmed in the simulation) - Config.h geometry taken from the Unity model. - IK femur elevation sign, gait stance direction, body pose rotation direction, yaw lever arm. - Stance feet integrated from the current command; gait restarts at mid-stance; heading integrates the applied turn rate, only while walking. - Leg clearance and reach checks (Limits::feetFeasible); walk command and body pose scaled as a whole, pose scale rate-limited, planted feet guarded so the body never snaps or legs collide. - Robot-gyro self-leveling as an integrator. - Heading mode: steering relative to the body; S walks backward. - Standing height from the remote (STANCE_HEIGHT_RANGE_MM). Protocol v3 (reflash both boards) - CmdPacket gains body_height (22 bytes). Remote - Height slider (Nextion hHeight/nHeight, serial [ ]). - Fixed unspecified I2C read order in RemoteIMU. LEGACY/: owner's old Nextion HMI and remote hardware assets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
91dac16fca
commit
935526eab5
145 files changed
+431258
-114
No files matched your search
@@ -93,6 +93,9 @@ constexpr float REMOTE_TILT_FADE_PER_SEC = 2.0f;
|
||||
constexpr uint8_t SPEED_SCALE_DEFAULT = 128;
|
||||
constexpr float SPEED_SCALE_RATE_PER_SEC = 255.0f; // slider jumps ramp over <= 1 s (robot doesn't smooth twist)
|
||||
constexpr uint8_t SPEED_SCALE_STEP = 25; // serial console +/- step
|
||||
// Standing height slider -> CmdPacket.body_height (-1000..1000, 0 = the
|
||||
// robot's default). No smoothing here: the robot rate-limits height itself.
|
||||
constexpr int16_t HEIGHT_STEP = 100; // serial console [ / ] step
|
||||
// FLAG_WALK stays set this long after the left stick returns to center, so
|
||||
// hovering at the deadzone edge doesn't chatter STAND <-> WALKING.
|
||||
constexpr uint32_t WALK_LINGER_MS = 250;
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Nextion HMI build guide
|
||||
|
||||
> The owner's earlier 6-page HMI is in `LEGACY/remote/remote.HMI`. This
|
||||
> guide describes the minimal single page the current firmware expects.
|
||||
> To reuse the old HMI instead, `NextionUI` has to be changed to match
|
||||
> its component names.
|
||||
|
||||
The firmware drives the display by **component name**, and the display
|
||||
talks back with small custom frames sent from each button's touch event.
|
||||
Build one page in Nextion Editor to match the tables below. Layout, fonts
|
||||
@@ -40,6 +45,8 @@ the firmware writes. Button bounds here allow some slack.
|
||||
| `bBGyro` | Button | – | bco green / gray (label it "ROBOT GYRO") |
|
||||
| `hSpeed` | Slider | – | `val` 0..255 (set **maxval = 255**) |
|
||||
| `nSpeed` | Number | – | `val` 0..100 (speed %) |
|
||||
| `hHeight`| Slider | – | `val` 0..200, 100 = default (set **maxval = 200**) |
|
||||
| `nHeight`| Number | – | `val` -100..100 (height offset %) |
|
||||
| `bCal` | Button | 9 | `CAL` / `SAVE CAL`, bco orange while active |
|
||||
| `bCalX` | Button | – | nothing (static "CANCEL CAL") |
|
||||
| `bZero` | Button | – | nothing (static "LEVEL") |
|
||||
@@ -63,6 +70,7 @@ uses **Touch Move** as well so the value tracks your finger). Frame format:
|
||||
| `bRGyro` | `printh 23 01 52` | `R` remote-gyro tilt on/off |
|
||||
| `bBGyro` | `printh 23 01 42` | `B` robot self-stabilize on/off |
|
||||
| `hSpeed` | `printh 23 02 56`<br>`prints hSpeed.val,1` | `V` + speed byte |
|
||||
| `hHeight` | `printh 23 02 48`<br>`prints hHeight.val,1` | `H` + height byte (Touch Move + Release) |
|
||||
| `bZero` | `printh 23 01 5A` | `Z` zero handset level |
|
||||
| `bCal` | `printh 23 01 43` | `C` start / save stick calibration |
|
||||
| `bCalX` | `printh 23 01 51` | `Q` cancel calibration |
|
||||
|
||||
@@ -46,6 +46,7 @@ bool NextionUI::decodeCustom(UiEvent& out) {
|
||||
case 'R': out.cmd = UiCmd::ToggleRemoteGyro; break;
|
||||
case 'B': out.cmd = UiCmd::ToggleRobotGyro; break;
|
||||
case 'V': out.cmd = UiCmd::SetSpeed; break;
|
||||
case 'H': out.cmd = UiCmd::SetHeight; break;
|
||||
case 'Z': out.cmd = UiCmd::ZeroGyro; break;
|
||||
case 'C': out.cmd = UiCmd::CalStartSave; break;
|
||||
case 'Q': out.cmd = UiCmd::CalCancel; break;
|
||||
@@ -124,6 +125,10 @@ void NextionUI::pushState(const UiView& v, bool full) {
|
||||
cmd("hSpeed.val=%u", v.speed_target);
|
||||
cmd("nSpeed.val=%u", (v.speed_target * 100 + 127) / 255);
|
||||
}
|
||||
if (full || v.height != s.height) {
|
||||
cmd("hHeight.val=%d", v.height / 10 + 100); // slider 0..200, 100 = default
|
||||
cmd("nHeight.val=%d", v.height / 10); // -100..100 %
|
||||
}
|
||||
if (full || v.calibrating != s.calibrating) {
|
||||
cmd("bCal.txt=\"%s\"", v.calibrating ? "SAVE CAL" : "CAL");
|
||||
cmd("bCal.bco=%u", v.calibrating ? COL_WARN : COL_OFF);
|
||||
|
||||
@@ -13,6 +13,7 @@ CmdPacket PacketBuilder::build(const StickFrame& s, const RemoteIMU& imu, const
|
||||
CmdPacket p;
|
||||
p.seq = seq_++;
|
||||
p.speed_scale = st.speedCurrent();
|
||||
p.body_height = st.height(); // a setting, not motion: sent even while disarmed
|
||||
|
||||
uint8_t f = 0;
|
||||
if (st.armed()) f |= FLAG_ARMED;
|
||||
|
||||
@@ -36,6 +36,13 @@ Stick pots are wired 3.3 V – wiper – GND. **Never feed 5 V into a Teensy
|
||||
| Right stick, **ROTATE** (default) | `rx/ry/rz` | body roll / pitch / yaw |
|
||||
| Right stick, **MOVE** (`FLAG_RSTICK_XLATE`) | `rx/ry/rz` | body X / Y / Z (height, via twist) offset |
|
||||
| Handset tilt | `remote_roll/pitch` | extra body roll/pitch while REMOTE GYRO is on |
|
||||
| Height slider | `body_height` | standing height, default ± `STANCE_HEIGHT_RANGE_MM` (robot rate-limits the change) |
|
||||
|
||||
In **HEADING** mode the left stick steers, relative to where the robot
|
||||
faces at that moment. Forward/back walks forward/back, and sideways
|
||||
steers: forward+left or back+left walk while turning left, and left alone
|
||||
turns on the spot for as long as you hold it. Let go and the robot holds
|
||||
its heading.
|
||||
|
||||
Touchscreen: ARM / DISARM, SIT/STAND, TRIPOD/WAVE, DIRECT/HEADING,
|
||||
R: ROTATE/MOVE, REMOTE GYRO, ROBOT GYRO, speed slider, LEVEL (re-zero the
|
||||
@@ -91,6 +98,7 @@ display attached.
|
||||
| `m` | direct ↔ heading-hold | `t` | right stick rotate ↔ translate |
|
||||
| `r` | remote gyro on/off | `b` | robot gyro on/off |
|
||||
| `+` / `-` | speed ±25 | `z` | zero handset level |
|
||||
| `]` / `[` | height up / down 10% | | |
|
||||
| `c` | start / save calibration | `x` | cancel calibration |
|
||||
| `p` | print status (raw + mapped sticks, IMU, link, state, cal) | `d` | toggle 10 Hz packet dump |
|
||||
| `R` | reset stick cal to Config defaults | | |
|
||||
|
||||
@@ -55,8 +55,12 @@ bool RemoteIMU::readRaw(float a[3], float g[3]) {
|
||||
Wire.requestFrom((int)IMU_ADDR, 14);
|
||||
if (Wire.available() < 14) return false;
|
||||
|
||||
int16_t raw[7];
|
||||
for (int k = 0; k < 7; k++) raw[k] = (Wire.read() << 8) | Wire.read(); // ax ay az temp gx gy gz
|
||||
int16_t raw[7]; // ax ay az temp gx gy gz, big-endian
|
||||
for (int k = 0; k < 7; k++) {
|
||||
uint8_t hi = Wire.read(); // two statements: the order of calls inside one expression is unspecified
|
||||
uint8_t lo = Wire.read();
|
||||
raw[k] = (int16_t)((hi << 8) | lo);
|
||||
}
|
||||
|
||||
float chip_a[3] = { raw[0] / 16384.0f, raw[1] / 16384.0f, raw[2] / 16384.0f };
|
||||
float chip_g[3] = { raw[4] / 131.0f, raw[5] / 131.0f, raw[6] / 131.0f };
|
||||
|
||||
@@ -56,6 +56,10 @@ bool RemoteState::handle(const UiEvent& e, Sticks& stick_hw, RemoteIMU& imu, uin
|
||||
case UiCmd::SpeedUp: speed_target_ = (uint8_t)min(255, speed_target_ + SPEED_SCALE_STEP); return true;
|
||||
case UiCmd::SpeedDown: speed_target_ = (uint8_t)max(0, speed_target_ - SPEED_SCALE_STEP); return true;
|
||||
|
||||
case UiCmd::SetHeight: height_ = (int16_t)constrain(((int)e.value - 100) * 10, -1000, 1000); return true;
|
||||
case UiCmd::HeightUp: height_ = (int16_t)min(1000, height_ + HEIGHT_STEP); return true;
|
||||
case UiCmd::HeightDown: height_ = (int16_t)max(-1000, height_ - HEIGHT_STEP); return true;
|
||||
|
||||
case UiCmd::ZeroGyro:
|
||||
imu.zeroLevel();
|
||||
setMessage("Remote level zeroed", now);
|
||||
@@ -110,5 +114,6 @@ void RemoteState::fillView(UiView& v, uint32_t now_ms) const {
|
||||
v.robot_gyro = robot_gyro_;
|
||||
v.calibrating = calibrating_;
|
||||
v.speed_target = speed_target_;
|
||||
v.height = height_;
|
||||
v.message = message(now_ms);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ public:
|
||||
bool robotGyro() const { return robot_gyro_; }
|
||||
|
||||
uint8_t speedTarget() const { return speed_target_; }
|
||||
int16_t height() const { return height_; } // -1000..1000, 0 = default
|
||||
uint8_t speedCurrent() const { return (uint8_t)lroundf(speed_.current); }
|
||||
float remoteTiltBlend() const { return tilt_blend_.current; } // 0..1, fades on the gyro toggle
|
||||
|
||||
@@ -48,6 +49,7 @@ private:
|
||||
bool calibrating_ = false;
|
||||
|
||||
uint8_t speed_target_ = SPEED_SCALE_DEFAULT;
|
||||
int16_t height_ = 0;
|
||||
SmoothedValue speed_{SPEED_SCALE_RATE_PER_SEC};
|
||||
SmoothedValue tilt_blend_{REMOTE_TILT_FADE_PER_SEC};
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ bool SerialConsole::poll(UiEvent& ui, Local& local) {
|
||||
case 'b': ui.cmd = UiCmd::ToggleRobotGyro; return true;
|
||||
case '+': case '=': ui.cmd = UiCmd::SpeedUp; return true;
|
||||
case '-': case '_': ui.cmd = UiCmd::SpeedDown; return true;
|
||||
case ']': ui.cmd = UiCmd::HeightUp; return true;
|
||||
case '[': ui.cmd = UiCmd::HeightDown; return true;
|
||||
case 'z': ui.cmd = UiCmd::ZeroGyro; return true;
|
||||
case 'c': ui.cmd = UiCmd::CalStartSave; return true;
|
||||
case 'x': ui.cmd = UiCmd::CalCancel; return true;
|
||||
|
||||
@@ -17,6 +17,9 @@ enum class UiCmd : uint8_t {
|
||||
SetSpeed, // value = 0..255
|
||||
SpeedUp, // serial console only
|
||||
SpeedDown, // serial console only
|
||||
SetHeight, // value = 0..200, 100 = default standing height
|
||||
HeightUp, // serial console only
|
||||
HeightDown, // serial console only
|
||||
ZeroGyro, // capture the handset's current attitude as "level"
|
||||
CalStartSave, // start sweep calibration, or save it if running
|
||||
CalCancel,
|
||||
@@ -39,6 +42,7 @@ struct UiView {
|
||||
bool robot_gyro = false;
|
||||
bool calibrating = false;
|
||||
uint8_t speed_target = 0;
|
||||
int16_t height = 0; // -1000..1000, 0 = default
|
||||
bool link_ok = false;
|
||||
uint8_t link_pct = 0;
|
||||
const char* message = ""; // always a string literal - NextionUI diffs it by pointer
|
||||
|
||||
@@ -34,17 +34,17 @@ static void printStatus() {
|
||||
imu.ok() ? "ok" : "MISSING", imu.rollDeg(), imu.pitchDeg());
|
||||
Serial.printf("radio %s link %s %u%%\n", radio_link.present() ? "ok" : "MISSING",
|
||||
radio_link.linkOk() ? "OK" : "DOWN", radio_link.qualityPct());
|
||||
Serial.printf("armed %d stand %d gait %s mode %s rstick %s remote_gyro %d robot_gyro %d speed %u->%u\n",
|
||||
Serial.printf("armed %d stand %d gait %s mode %s rstick %s remote_gyro %d robot_gyro %d speed %u->%u height %d\n",
|
||||
state.armed(), state.standRequested(), state.gaitWave() ? "wave" : "tripod",
|
||||
state.modeB() ? "B" : "A", state.rstickXlate() ? "xlate" : "rot",
|
||||
state.remoteGyro(), state.robotGyro(), state.speedTarget(), state.speedCurrent());
|
||||
state.remoteGyro(), state.robotGyro(), state.speedTarget(), state.speedCurrent(), state.height());
|
||||
sticks.printCalibration(Serial);
|
||||
}
|
||||
|
||||
static void dumpPacket(const CmdPacket& p) {
|
||||
Serial.printf("#%3u vx %5d vy %5d yaw %5d | r %5d %5d %5d | tilt %5d %5d | spd %3u flags %02X\n",
|
||||
Serial.printf("#%3u vx %5d vy %5d yaw %5d | r %5d %5d %5d | tilt %5d %5d | h %5d spd %3u flags %02X\n",
|
||||
p.seq, p.vx, p.vy, p.yaw_rate, p.rx, p.ry, p.rz,
|
||||
p.remote_roll, p.remote_pitch, p.speed_scale, p.flags);
|
||||
p.remote_roll, p.remote_pitch, p.body_height, p.speed_scale, p.flags);
|
||||
}
|
||||
|
||||
static void pollInputs(uint32_t now_ms) {
|
||||
|
||||
Reference in new issue
Block a user