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
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user