Remote: match the old remote's hardware; old remote code in LEGACY

- LEGACY/remote/teensy/: old remote sketch from
  https://mangavalk.com/Hexapod/remote.git @ 492dece (reference only).
- remote/Config.h: sticks on A0-A5, Nextion on Serial1 (0/1), as in the
  old sketch; MPU6050 moved to Wire2 (25/24) since A4/A5 are Wire's pins.
- NextionUI: controls on a vscope-global "control" page added to the
  old HMI; keeps the old t1 status texts; the old page-1 b2 button (and
  serial 'U') enter Nextion upload passthrough, following the PC's baud.
- Docs: NEXTION.md, remote README wiring, CLAUDE.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
mangavalkandClaude Opus 5.5 committed 2026-10-01 15:29:21 +02:00
1 parent 935526eab5
commit a961fa721b
18 files changed
+760 -76

No files matched your search

+26 -9
View File
@@ -14,7 +14,7 @@
constexpr float TX_HZ = 100.0f;
// ---- Radio (nRF24L01) on SPI0: MOSI 11, MISO 12, SCK 13 ----
// BENCH: placeholder CE/CSN pins - match your wiring.
// CE/CSN as in the old remote sketch (LEGACY/remote/teensy/Nrf.h).
constexpr uint8_t RADIO_CE_PIN = 9;
constexpr uint8_t RADIO_CSN_PIN = 10;
// Auto-retransmit: (delay units of 250us, count). Kept short on purpose - a
@@ -31,9 +31,11 @@ constexpr uint32_t LINK_LOST_MS = 300;
// Logical axis order used everywhere in the remote code.
enum StickAxis : uint8_t { L_X = 0, L_Y, L_TWIST, R_X, R_Y, R_TWIST, NUM_AXES };
// BENCH: placeholder pins. A4/A5 (18/19) are skipped because they're the I2C
// pins the MPU6050 uses.
constexpr uint8_t STICK_PINS[NUM_AXES] = { A0, A1, A2, A3, A6, A7 };
// A0-A5 in a row, as wired for the old remote (LEGACY/remote/teensy/Joysticks.h).
// A4/A5 are also Wire's SDA/SCL - that's why the MPU6050 is on Wire2 below.
// BENCH: which physical axis is on which pin wasn't recoverable from the old
// code; reorder these so each lands on the right logical axis ('p' dump).
constexpr uint8_t STICK_PINS[NUM_AXES] = { A0, A1, A2, A3, A4, A5 };
// Each logical axis must end up: X + = pushed RIGHT, Y + = pushed FORWARD
// (away from you), TWIST + = rotated COUNTERCLOCKWISE seen from above.
@@ -67,7 +69,9 @@ constexpr float STICK_EXPO[NUM_AXES] = { 0.25f, 0.25f, 0.30f, 0.20f, 0.20f,
// One-pole low-pass on raw ADC counts (1 = no filtering). Pots are noisy.
constexpr float STICK_FILTER_ALPHA = 0.5f;
// ---- Remote IMU: MPU6050 on I2C (Wire: SDA 18, SCL 19) ----
// ---- Remote IMU: MPU6050 on the second I2C bus (Wire2: SDA2 = 25, SCL2 = 24) ----
// Not Wire (18/19): those pins are stick axes A4/A5.
#define IMU_WIRE Wire2
constexpr uint8_t IMU_ADDR = 0x68; // 0x69 if AD0 is pulled high
// Mounting remap: which chip axis (0=X,1=Y,2=Z) and sign gives each handset
// axis. Handset frame: +X = away from you, +Y = your left, +Z = up.
@@ -103,11 +107,24 @@ constexpr uint32_t WALK_LINGER_MS = 250;
// (post-deadzone, -1000..1000 scale), so the robot never arms mid-command.
constexpr int16_t ARM_CENTER_TOLERANCE = 50;
// ---- Nextion touchscreen on Serial2 (RX2 = pin 7, TX2 = pin 8) ----
#define NEXTION_SERIAL Serial2
// BENCH: Nextion ships at 9600. The HMI's Program.s must set `bauds=115200`
// (see remote/NEXTION.md) or change this to match.
// ---- Nextion touchscreen on Serial1 (RX1 = pin 0, TX1 = pin 1), as in the old remote ----
#define NEXTION_SERIAL Serial1
// BENCH: the old sketch used the ITEAD library, whose nexInit() talks at 9600
// unless it was modified. This firmware expects 115200: add `bauds=115200`
// to the HMI's Program.s (see remote/NEXTION.md) or set 9600 here.
constexpr uint32_t NEXTION_BAUD = 115200;
// Controls live on this page (remote/NEXTION.md). Its components must have
// vscope = global, so the firmware can update them whichever page is showing.
#define NEXTION_CONTROL_PAGE "control"
// The old HMI's link-status texts (pages 1 and 3, both named t1), also
// vscope = global. BENCH: use your real page names (Nextion's default
// names are page0..page5; only page 4 "uploadscreen" is known).
constexpr const char* NEXTION_STATUS_TEXTS[] = { "page1.t1", "page3.t1" };
// The old HMI's "upload" button: page 1, component id 5 (b2), sends the
// standard touch event. It puts the remote into Nextion upload passthrough.
constexpr uint8_t NEXTION_UPLOAD_BUTTON_PAGE = 1;
constexpr uint8_t NEXTION_UPLOAD_BUTTON_ID = 5;
#define NEXTION_UPLOAD_PAGE "uploadscreen"
constexpr uint32_t NEXTION_FULL_REFRESH_MS = 2000; // re-push everything periodically (self-heals after a display reset)
constexpr uint32_t NEXTION_LINK_REFRESH_MS = 250; // link-quality text update rate
constexpr uint32_t STATUS_MESSAGE_MS = 2500; // how long transient status messages stay up
+26 -10
View File
@@ -1,14 +1,25 @@
# 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.
Start from your existing HMI (`LEGACY/remote/remote.HMI`, the servo
monitor) and **add one page named `control`** with the components below.
Your existing pages keep working:
| Existing | What the firmware does with it |
|----------|--------------------------------|
| page 1 `t1`, page 3 `t1` | writes `connected` / `not connected`, as the old firmware did. Set both to **vscope = global**, and put their real names (`page1.t1`, …) in `NEXTION_STATUS_TEXTS` in `Config.h` |
| page 1 `b2` (id 5) | **upload mode**: keep "Send Component ID" ticked on it. The remote shows `uploadscreen` and passes USB straight through to the display, so Nextion Editor can upload over the remote's USB port. Power-cycle to leave. Serial key `U` does the same |
| page 4 `uploadscreen` | shown on entering upload mode |
| page 3 sliders `h0`–`h17` | not driven yet: the robot doesn't send servo positions back (that needs the planned telemetry channel) |
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
and colors are up to you. The names and event code must match exactly.
Layout, fonts and colors are up to you. The names and event code must
match exactly.
**Set every component on `control` to vscope = global.** The firmware
writes them as `control.<name>` and keeps them current whichever page is
showing. A local component on another page can't be written from outside,
and with `bkcmd=0` the display ignores those writes silently.
The remote is authoritative. Use **plain Button** components (not
Dual-state buttons) and don't toggle anything locally in the HMI. The
@@ -24,9 +35,13 @@ bauds=115200
```
This sets the display to the `NEXTION_BAUD` in `remote/Config.h`. The
display ships at 9600, and `bauds=` persists across power cycles.
display ships at 9600, and `bauds=` persists across power cycles. The old
firmware used the ITEAD library at 9600, so your HMI probably runs at 9600
today.
Name the page `main` (page 0). The firmware never changes pages.
Name the new page `control`. The firmware never changes pages, except
into `uploadscreen` for upload mode. Add buttons on your other pages to
switch to it (`page control`).
## Components
@@ -75,8 +90,9 @@ uses **Touch Move** as well so the value tracks your finger). Frame format:
| `bCal` | `printh 23 01 43` | `C` start / save stick calibration |
| `bCalX` | `printh 23 01 51` | `Q` cancel calibration |
**Uncheck "Send Component ID"** on every component. The firmware ignores
standard touch frames, and they only add serial traffic.
**Uncheck "Send Component ID"** on every component on `control`: those
standard touch frames only add serial traffic. The one exception is the old
page 1 `b2` upload button (see the top).
Protocol details and the parser live in `NextionUI.h/.cpp`. To add a
control, pick an unused command byte, add it to `NextionUI::decodeCustom()`
+78 -26
View File
@@ -7,21 +7,26 @@ static constexpr uint16_t COL_OFF = 50712; // Nextion default light gray
static constexpr uint16_t COL_DANGER = 63488; // red
static constexpr uint16_t COL_WARN = 64512; // orange
// Serial2's default 64-byte TX buffer would make a full redraw (~300 bytes
// at 115200 baud) block the TX loop for ~25 ms. Give it room to queue.
// The default 64-byte TX buffer would make a full redraw (~400 bytes at
// 115200 baud) block the TX loop for ~35 ms. Give it room to queue.
static uint8_t tx_extra[1024];
static uint8_t rx_extra[128];
// Every control lives on NEXTION_CONTROL_PAGE with vscope = global, so it's
// addressed as page.component and can be updated whichever page is showing.
#define P NEXTION_CONTROL_PAGE "."
void NextionUI::begin() {
NEXTION_SERIAL.begin(NEXTION_BAUD);
NEXTION_SERIAL.addMemoryForWrite(tx_extra, sizeof(tx_extra));
NEXTION_SERIAL.addMemoryForRead(rx_extra, sizeof(rx_extra));
cmd("bkcmd=0"); // no success/fail replies - we never need them
cmd("bkcmd=0"); // no success/fail replies - we never need them (and a
// write to a page the HMI doesn't have is then just ignored)
force_full_ = true;
}
void NextionUI::cmd(const char* fmt, ...) {
char buf[64];
char buf[80];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
@@ -50,11 +55,31 @@ bool NextionUI::decodeCustom(UiEvent& out) {
case 'Z': out.cmd = UiCmd::ZeroGyro; break;
case 'C': out.cmd = UiCmd::CalStartSave; break;
case 'Q': out.cmd = UiCmd::CalCancel; break;
case 'U': out.cmd = UiCmd::NextionUpload; break;
default: return false;
}
return true;
}
// Standard Nextion frames (terminated FF FF FF). Two matter:
// 0x88 / 0x00 display (re)started -> redraw everything
// 0x65 page id event touch event from a component with "Send Component
// ID" ticked - how the old HMI's upload button (b2)
// reports, so it keeps working unchanged.
bool NextionUI::decodeNative(UiEvent& out) {
if (native_buf_[0] == 0x88 || native_buf_[0] == 0x00) {
force_full_ = true;
return false;
}
if (native_buf_[0] == 0x65 && native_len_ >= 4 &&
native_buf_[1] == NEXTION_UPLOAD_BUTTON_PAGE && native_buf_[2] == NEXTION_UPLOAD_BUTTON_ID) {
out.cmd = UiCmd::NextionUpload;
out.value = 0;
return true;
}
return false;
}
bool NextionUI::poll(UiEvent& out) {
while (NEXTION_SERIAL.available()) {
uint8_t b = NEXTION_SERIAL.read();
@@ -64,7 +89,7 @@ bool NextionUI::poll(UiEvent& out) {
rx_ = Rx::CustomLen;
} else {
rx_ = Rx::Native;
native_first_ = b;
native_buf_[0] = b;
native_len_ = 1;
ff_run_ = (b == 0xFF) ? 1 : 0;
}
@@ -89,12 +114,12 @@ bool NextionUI::poll(UiEvent& out) {
break;
case Rx::Native:
if (native_len_ < sizeof(native_buf_)) native_buf_[native_len_] = b;
native_len_++;
ff_run_ = (b == 0xFF) ? ff_run_ + 1 : 0;
if (ff_run_ == 3) {
// 0x88 = "Nextion ready" after power-on/reset, 0x00 = startup.
if (native_first_ == 0x88 || native_first_ == 0x00) force_full_ = true;
rx_ = Rx::Idle;
if (decodeNative(out)) return true;
} else if (native_len_ > 16) {
rx_ = Rx::Idle; // not a frame we understand
}
@@ -109,37 +134,40 @@ bool NextionUI::poll(UiEvent& out) {
void NextionUI::pushState(const UiView& v, bool full) {
const UiView& s = shown_;
if (full || v.armed != s.armed) {
cmd("bArm.txt=\"%s\"", v.armed ? "ARMED" : "DISARMED");
cmd("bArm.bco=%u", v.armed ? COL_DANGER : COL_OFF);
cmd(P "bArm.txt=\"%s\"", v.armed ? "ARMED" : "DISARMED");
cmd(P "bArm.bco=%u", v.armed ? COL_DANGER : COL_OFF);
}
if (full || v.stand != s.stand) {
cmd("bStand.txt=\"%s\"", v.stand ? "STAND" : "SIT");
cmd("bStand.bco=%u", v.stand ? COL_ON : COL_OFF);
cmd(P "bStand.txt=\"%s\"", v.stand ? "STAND" : "SIT");
cmd(P "bStand.bco=%u", v.stand ? COL_ON : COL_OFF);
}
if (full || v.gait_wave != s.gait_wave) cmd("bGait.txt=\"%s\"", v.gait_wave ? "WAVE" : "TRIPOD");
if (full || v.mode_b != s.mode_b) cmd("bMode.txt=\"%s\"", v.mode_b ? "HEADING" : "DIRECT");
if (full || v.rstick_xlate != s.rstick_xlate) cmd("bRs.txt=\"%s\"", v.rstick_xlate ? "R: MOVE" : "R: ROTATE");
if (full || v.remote_gyro != s.remote_gyro) cmd("bRGyro.bco=%u", v.remote_gyro ? COL_ON : COL_OFF);
if (full || v.robot_gyro != s.robot_gyro) cmd("bBGyro.bco=%u", v.robot_gyro ? COL_ON : COL_OFF);
if (full || v.gait_wave != s.gait_wave) cmd(P "bGait.txt=\"%s\"", v.gait_wave ? "WAVE" : "TRIPOD");
if (full || v.mode_b != s.mode_b) cmd(P "bMode.txt=\"%s\"", v.mode_b ? "HEADING" : "DIRECT");
if (full || v.rstick_xlate != s.rstick_xlate) cmd(P "bRs.txt=\"%s\"", v.rstick_xlate ? "R: MOVE" : "R: ROTATE");
if (full || v.remote_gyro != s.remote_gyro) cmd(P "bRGyro.bco=%u", v.remote_gyro ? COL_ON : COL_OFF);
if (full || v.robot_gyro != s.robot_gyro) cmd(P "bBGyro.bco=%u", v.robot_gyro ? COL_ON : COL_OFF);
if (full || v.speed_target != s.speed_target) {
cmd("hSpeed.val=%u", v.speed_target);
cmd("nSpeed.val=%u", (v.speed_target * 100 + 127) / 255);
cmd(P "hSpeed.val=%u", v.speed_target);
cmd(P "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 %
cmd(P "hHeight.val=%d", v.height / 10 + 100); // slider 0..200, 100 = default
cmd(P "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);
cmd(P "bCal.txt=\"%s\"", v.calibrating ? "SAVE CAL" : "CAL");
cmd(P "bCal.bco=%u", v.calibrating ? COL_WARN : COL_OFF);
}
if (full || v.message != s.message) cmd("tMsg.txt=\"%s\"", v.message);
if (full || v.message != s.message) cmd(P "tMsg.txt=\"%s\"", v.message);
}
void NextionUI::pushLink(const UiView& v) {
if (v.link_ok) cmd("tLink.txt=\"LINK %u%%\"", v.link_pct);
else cmd("tLink.txt=\"NO LINK\"");
cmd("tLink.pco=%u", v.link_ok ? COL_ON : COL_DANGER);
if (v.link_ok) cmd(P "tLink.txt=\"LINK %u%%\"", v.link_pct);
else cmd(P "tLink.txt=\"NO LINK\"");
cmd(P "tLink.pco=%u", v.link_ok ? COL_ON : COL_DANGER);
// The old HMI's own status texts, same wording as the old firmware.
for (const char* t : NEXTION_STATUS_TEXTS)
cmd("%s.txt=\"%s\"", t, v.link_ok ? "connected" : "not connected");
}
void NextionUI::render(const UiView& v, uint32_t now_ms) {
@@ -160,3 +188,27 @@ void NextionUI::render(const UiView& v, uint32_t now_ms) {
shown_ = v;
}
// ---- Upload passthrough ----
// Lets Nextion Editor upload a new .tft through the remote's USB port, with
// the display still in the case: show the upload page, then wire USB serial
// straight through to the display, forever (power-cycle to leave). Nextion
// Editor switches baud rate mid-upload; Teensy's USB serial reports the
// rate the PC asked for, so the display side follows it.
void NextionUI::runUploadPassthrough() {
cmd("page " NEXTION_UPLOAD_PAGE);
NEXTION_SERIAL.flush();
delay(100);
uint32_t baud = NEXTION_BAUD;
for (;;) {
uint32_t want = Serial.baud();
if (want >= 2400 && want != baud) {
NEXTION_SERIAL.flush();
NEXTION_SERIAL.begin(want);
baud = want;
}
while (Serial.available()) NEXTION_SERIAL.write(Serial.read());
while (NEXTION_SERIAL.available()) Serial.write(NEXTION_SERIAL.read());
}
}
+9 -3
View File
@@ -8,8 +8,9 @@
// '#' <len> <cmd> [value] e.g. printh 23 01 41 (ToggleArm)
// printh 23 02 56 + prints hSpeed.val,1
// Command bytes are listed in NEXTION.md and in NextionUI.cpp.
// Native Nextion return frames (terminated FF FF FF) are also parsed so a
// display reset (0x88 "ready") triggers a full redraw.
// Native Nextion return frames (terminated FF FF FF) are also parsed: a
// display reset (0x88 "ready") triggers a full redraw, and the old HMI's
// upload button sends a standard touch event (0x65) that's recognized.
//
// remote -> Nextion: plain instructions ("bArm.txt=\"ARMED\"" + FF FF FF).
//
@@ -23,12 +24,16 @@ public:
bool poll(UiEvent& out); // call every loop; returns one decoded event at a time
void render(const UiView& v, uint32_t now_ms);
// Nextion Editor upload through the remote's USB port. Never returns.
[[noreturn]] void runUploadPassthrough();
private:
enum class Rx : uint8_t { Idle, CustomLen, CustomBody, Native };
Rx rx_ = Rx::Idle;
uint8_t body_[4];
uint8_t body_len_ = 0, body_got_ = 0;
uint8_t native_first_ = 0, native_len_ = 0, ff_run_ = 0;
uint8_t native_buf_[8];
uint8_t native_len_ = 0, ff_run_ = 0;
UiView shown_;
bool force_full_ = true;
@@ -39,4 +44,5 @@ private:
void pushState(const UiView& v, bool full);
void pushLink(const UiView& v);
bool decodeCustom(UiEvent& out);
bool decodeNative(UiEvent& out);
};
+8 -6
View File
@@ -11,17 +11,19 @@ at 100 Hz.
From the repo root: `pio run -e remote`, flash with
`pio run -e remote -t upload`, monitor with `pio device monitor -e remote`.
## Wiring (placeholders; edit `Config.h` to match)
## Wiring (from the old remote sketch in `LEGACY/remote/teensy/`; edit `Config.h` if yours differs)
| Part | Signal | Teensy 4.1 pin |
|------|--------|----------------|
| nRF24L01 | MOSI / MISO / SCK | 11 / 12 / 13 (SPI0) |
| nRF24L01 | CE / CSN | 9 / 10 |
| nRF24L01 | VCC | 3.3 V, with a 10–100 µF cap right at the module |
| MPU6050 | SDA / SCL | 18 / 19 (Wire) |
| Nextion | TX → RX2, RX ← TX2 | 7 / 8 (Serial2), 5 V supply |
| Left stick | X / Y / twist | A0 / A1 / A2 |
| Right stick | X / Y / twist | A3 / A6 / A7 |
| MPU6050 | SDA / SCL | **25 / 24 (Wire2)**: not 18/19, those are stick pins A4/A5 |
| Nextion | TX → RX1, RX ← TX1 | 0 / 1 (Serial1), 5 V supply |
| Sticks | 6 axes | A0–A5. Which axis is on which pin isn't known yet: reorder `STICK_PINS` until the `p` dump reads right |
**MPU6050 rewiring:** the old remote had its MPU code switched off, probably
because A4/A5 double as Wire's pins. Move the MPU's SDA/SCL to pins 25/24.
Stick pots are wired 3.3 V – wiper – GND. **Never feed 5 V into a Teensy
4.x analog pin.**
@@ -101,7 +103,7 @@ display attached.
| `]` / `[` | 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 | | |
| `R` | reset stick cal to Config defaults | `U` | Nextion upload mode (USB ↔ display; power-cycle to leave) |
## Bench-check checklist
+13 -13
View File
@@ -9,15 +9,15 @@ static constexpr uint8_t REG_PWR_MGMT_1 = 0x6B;
static constexpr uint8_t REG_ACCEL_XOUT_H = 0x3B;
static bool writeReg(uint8_t reg, uint8_t val) {
Wire.beginTransmission(IMU_ADDR);
Wire.write(reg);
Wire.write(val);
return Wire.endTransmission() == 0;
IMU_WIRE.beginTransmission(IMU_ADDR);
IMU_WIRE.write(reg);
IMU_WIRE.write(val);
return IMU_WIRE.endTransmission() == 0;
}
bool RemoteIMU::begin() {
Wire.begin();
Wire.setClock(400000);
IMU_WIRE.begin();
IMU_WIRE.setClock(400000);
ok_ = writeReg(REG_PWR_MGMT_1, 0x00); // wake out of sleep
if (!ok_) return false;
writeReg(REG_CONFIG, 0x03); // 44 Hz DLPF: a handheld remote doesn't need more bandwidth
@@ -49,16 +49,16 @@ bool RemoteIMU::begin() {
}
bool RemoteIMU::readRaw(float a[3], float g[3]) {
Wire.beginTransmission(IMU_ADDR);
Wire.write(REG_ACCEL_XOUT_H);
if (Wire.endTransmission(false) != 0) return false;
Wire.requestFrom((int)IMU_ADDR, 14);
if (Wire.available() < 14) return false;
IMU_WIRE.beginTransmission(IMU_ADDR);
IMU_WIRE.write(REG_ACCEL_XOUT_H);
if (IMU_WIRE.endTransmission(false) != 0) return false;
IMU_WIRE.requestFrom((int)IMU_ADDR, 14);
if (IMU_WIRE.available() < 14) return false;
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();
uint8_t hi = IMU_WIRE.read(); // two statements: the order of calls inside one expression is unspecified
uint8_t lo = IMU_WIRE.read();
raw[k] = (int16_t)((hi << 8) | lo);
}
+3
View File
@@ -78,6 +78,9 @@ bool RemoteState::handle(const UiEvent& e, Sticks& stick_hw, RemoteIMU& imu, uin
setMessage("Calibration saved", now);
return true;
case UiCmd::NextionUpload:
return false; // not operator state - remote.ino handles it
case UiCmd::CalCancel:
if (!calibrating_) return false;
stick_hw.cancelCalibration();
+1
View File
@@ -22,6 +22,7 @@ bool SerialConsole::poll(UiEvent& ui, Local& local) {
case 'z': ui.cmd = UiCmd::ZeroGyro; return true;
case 'c': ui.cmd = UiCmd::CalStartSave; return true;
case 'x': ui.cmd = UiCmd::CalCancel; return true;
case 'U': ui.cmd = UiCmd::NextionUpload; return true;
case 'p': local = Local::PrintStatus; return true;
case 'd': local = Local::ToggleDump; return true;
case 'R': local = Local::ResetCal; return true;
+1
View File
@@ -23,6 +23,7 @@ enum class UiCmd : uint8_t {
ZeroGyro, // capture the handset's current attitude as "level"
CalStartSave, // start sweep calibration, or save it if running
CalCancel,
NextionUpload, // USB <-> display passthrough for Nextion Editor (handled in remote.ino)
};
struct UiEvent {
+16 -1
View File
@@ -47,12 +47,27 @@ static void dumpPacket(const CmdPacket& p) {
p.remote_roll, p.remote_pitch, p.body_height, p.speed_scale, p.flags);
}
// Nextion Editor upload through this USB port (the old remote's b2 button).
// Never returns: the TX loop stops, so the robot loses the link and
// disarms itself after LINK_TIMEOUT_SEC. Power-cycle the remote to leave.
static void enterNextionUpload() {
state.handle(UiEvent{ UiCmd::Disarm, 0 }, sticks, imu, millis());
Serial.println("Nextion upload mode: connect Nextion Editor to this USB port and upload.");
Serial.println("Power-cycle the remote to leave.");
Serial.flush();
nextion.runUploadPassthrough();
}
static void pollInputs(uint32_t now_ms) {
UiEvent ev;
while (nextion.poll(ev)) state.handle(ev, sticks, imu, now_ms);
while (nextion.poll(ev)) {
if (ev.cmd == UiCmd::NextionUpload) enterNextionUpload();
state.handle(ev, sticks, imu, now_ms);
}
SerialConsole::Local local;
while (console.poll(ev, local)) {
if (ev.cmd == UiCmd::NextionUpload) enterNextionUpload();
if (ev.cmd != UiCmd::None) {
bool ok = state.handle(ev, sticks, imu, now_ms);
if (!ok && state.message(now_ms)[0]) Serial.println(state.message(now_ms));