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:
1 parent
935526eab5
commit
a961fa721b
18 files changed
+760
-76
No files matched your search
@@ -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
|
||||
@@ -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()`
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
|
||||
Reference in new issue
Block a user