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
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack",
|
||||
"pioarduino.pioarduino-ide"
|
||||
]
|
||||
}
|
||||
+38
-8
@@ -14,7 +14,8 @@ remote/ Teensy 4.1 firmware (PlatformIO env remote)
|
||||
shared/HexapodProtocol/ wire protocol, used by robot, remote and sim
|
||||
sim/ robot firmware -> hexapod_sim.dll (CMake + g++), headless smoke test
|
||||
Hexapod_Simulation_Unity/ Unity 6000.6 project that loads the DLL (Assets/HexapodSim/)
|
||||
LEGACY/ owner's older material for reference (remote/remote.HMI = old Nextion UI, 6 pages)
|
||||
LEGACY/ owner's older material for reference (remote/remote.HMI = old Nextion UI, 6 pages;
|
||||
remote/teensy/ = old remote sketch from https://mangavalk.com/Hexapod/remote.git @ 492dece)
|
||||
scripts/ PlatformIO helper
|
||||
```
|
||||
|
||||
@@ -113,7 +114,7 @@ link-loss disarm) → PacketBuilder → `radio.write` → Nextion render.
|
||||
| `RemoteState` | authoritative operator state and interlocks: arm needs centered sticks, disarm clears stand, stand needs armed, calibrate needs disarmed, link-loss auto-disarm. Smooths speed scale and fades remote tilt in/out |
|
||||
| `PacketBuilder` | the inverse of InputMapper: stick frame → body frame per the contract, per-submode right-stick mapping, `FLAG_WALK` with 250 ms linger (twist ignored in mode B), tilt deadzone/scale × fade, zeros motion when disarmed |
|
||||
| `RadioLink` | nRF24 TX, short retries (1, 3), ack-based link status and 32-packet quality % |
|
||||
| `NextionUI` | library-free Nextion driver. HMI sends `'#' len cmd [val]` frames via `printh`. Remote pushes state by component name with diffing, a 2 s full refresh, and a redraw on the Nextion 0x88 "ready" frame |
|
||||
| `NextionUI` | library-free Nextion driver on `Serial1`. HMI sends `'#' len cmd [val]` frames via `printh`. Remote pushes state by component name, as `control.<name>` (the owner adds a `control` page with vscope-global components to the old HMI), with diffing, a 2 s full refresh, and a redraw on the Nextion 0x88 "ready" frame. Also writes the old HMI's `t1` status texts (`NEXTION_STATUS_TEXTS`). The old page-1 `b2` sends a standard 0x65 touch frame, which triggers `runUploadPassthrough()`: USB ↔ display bridge for Nextion Editor, following `Serial.baud()`, never returns. Serial key `U` does the same |
|
||||
| `SerialConsole` | USB keystrokes that mirror every UI command plus diagnostics |
|
||||
| `SmoothedValue.h` | copy of the robot's (kept separate so robot/ stays untouched) |
|
||||
| `NEXTION.md` | HMI build guide: component names, txt_maxl, touch event code |
|
||||
@@ -284,17 +285,46 @@ Robot (`robot/README.md` has detail):
|
||||
- `Limits::stancePolygonOk()` is computed but not acted on.
|
||||
|
||||
Remote (`remote/README.md` has detail):
|
||||
- All pins in `remote/Config.h` (radio CE 9 / CSN 10, sticks A0–A3/A6/A7,
|
||||
MPU6050 on 18/19, Nextion on Serial2 7/8).
|
||||
- Pins in `remote/Config.h`, taken from the old remote sketch: radio CE 9 /
|
||||
CSN 10, sticks A0–A5, Nextion on Serial1 (0/1). The MPU6050 is on
|
||||
**Wire2 (25/24)**, since A4/A5 = Wire's SDA/SCL are stick pins; that's
|
||||
the owner's choice and needs rewiring the MPU. Which stick axis is on
|
||||
which pin is still unknown.
|
||||
- `STICK_INVERT[]`, calibration defaults, deadzones, expo.
|
||||
- `IMU_AXIS_FROM_CHIP` / `IMU_AXIS_SIGN` depend on how the MPU6050 is
|
||||
mounted in the handset.
|
||||
- The Nextion must have `bauds=115200` in Program.s, and the HMI must match
|
||||
`NEXTION.md`. The owner's old 6-page HMI is in `LEGACY/remote/remote.HMI`;
|
||||
its component names aren't readable outside Nextion Editor. Adapting
|
||||
`NextionUI` to it needs the owner's component list, or the old remote
|
||||
Teensy code, which the owner will add to `LEGACY/` when found. The
|
||||
compiled `.tft` is gitignored.
|
||||
its component names aren't readable outside Nextion Editor. The compiled
|
||||
`.tft` is gitignored.
|
||||
- What the old remote sketch (`LEGACY/remote/teensy/`, last ran on a
|
||||
Teensy) tells us about the real hardware:
|
||||
- **Nextion on `Serial1` (pins 0/1)**, driven by the ITEAD Nextion
|
||||
library, whose `nexInit()` runs at 9600. `remote/Config.h` now uses
|
||||
Serial1 and expects 115200 via `bauds=` in the HMI.
|
||||
- The old HMI is a *servo monitor*, not the control UI in `NEXTION.md`:
|
||||
- page 1: `t1` "connected"/"not connected", and `b2` = enter
|
||||
upload mode;
|
||||
- page 3: sliders `h0`–`h17` showing the 18 servo positions, plus
|
||||
`t1` status;
|
||||
- page 4: `uploadscreen`.
|
||||
|
||||
None of the arm/stand/gait/mode/gyro/speed/height controls exist yet.
|
||||
The owner chose to add a `control` page per `NEXTION.md`.
|
||||
- `b2` → **Nextion upload passthrough**: shows `uploadscreen`, then
|
||||
bridges USB ↔ Serial1 forever, so Nextion Editor can upload a `.tft`
|
||||
through the remote's USB port. The new firmware keeps this.
|
||||
- Sticks on **A0–A5**, contiguous (`analogInPin + i`). On the Teensy 4.1,
|
||||
A4/A5 are pins 18/19 = Wire SDA/SCL, which the MPU6050 needs. The old
|
||||
code had the MPU code disabled (`if (false)`). Owner's decision: keep
|
||||
the sticks, move the MPU to Wire2.
|
||||
- nRF24 on CE 9 / CSN 10, the same as `remote/Config.h`. It used
|
||||
RF24Network on channel 90, which the new protocol doesn't.
|
||||
- Stick calibration was a 10 s min/max sweep saved to EEPROM, output
|
||||
0–255, with no center or deadzone.
|
||||
- USB serial at 250000. It received 18 servo bytes + checksum + `0xFE`
|
||||
from the old Unity `Serial.cs` (servo passthrough, forwarded over RF
|
||||
to the robot) and printed `i_value,...` stick lines.
|
||||
- The Unity remote stand-in's joystick axis assignment (which of axes 4/6
|
||||
is forward) couldn't be recovered from the old code. Check it with the
|
||||
panel's raw-axis readout.
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
#include <EEPROM.h>
|
||||
|
||||
// start reading from the first byte (address 0) of the EEPROM
|
||||
unsigned int address = 0;
|
||||
byte value;
|
||||
|
||||
// Joystick vars
|
||||
const int analogInPin = A0;
|
||||
|
||||
int sensorValue = 0;
|
||||
int outputValue = 0;
|
||||
|
||||
//volatile int center[6] = {0, 0, 0, 0, 0, 0};
|
||||
//volatile int minvalue[6] = {1000, 1000, 1000, 1000, 1000, 1000}; //ARDUINO
|
||||
//volatile int maxvalue[6] = {0, 0, 0, 0, 0, 0}; //ARDUINO
|
||||
int minvalue[6] = {1000, 1000, 1000, 1000, 1000, 1000}; //TEENSY
|
||||
int maxvalue[6] = {0, 0, 0, 0, 0, 0}; //TEENSY
|
||||
|
||||
void JoystickInit()
|
||||
{
|
||||
// Initialize joysticks first
|
||||
value = EEPROM.read(address);
|
||||
|
||||
if (value != 0)
|
||||
{
|
||||
address++;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
address += sizeof(int);
|
||||
EEPROM.get(address, minvalue[i]);
|
||||
address += sizeof(int);
|
||||
EEPROM.get(address, maxvalue[i]);
|
||||
// Serial.println(minvalue[i]);
|
||||
// Serial.println(maxvalue[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// if eeprom empty
|
||||
delay(500);
|
||||
|
||||
// for (int i = 0; i < 6; i++)
|
||||
// {
|
||||
// center[i] = analogRead(analogInPin + i);
|
||||
// }
|
||||
|
||||
int time = 10000;
|
||||
while (millis() < time)
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
sensorValue = analogRead(analogInPin + i);
|
||||
if (sensorValue > maxvalue[i]) {
|
||||
maxvalue[i] = sensorValue;
|
||||
}
|
||||
if (sensorValue < minvalue[i]) {
|
||||
minvalue[i] = sensorValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for (int i = 0; i < 6; i++)
|
||||
// {
|
||||
// minvalue[i] = center[i] - minvalue[i];
|
||||
// maxvalue[i] = (maxvalue[i] - center[i]);
|
||||
// }
|
||||
|
||||
// write callibration to eeprom
|
||||
address = 0;
|
||||
EEPROM.write(address, 1);
|
||||
|
||||
address++;
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
address += sizeof(int);
|
||||
EEPROM.put(address, minvalue[i]);
|
||||
address += sizeof(int);
|
||||
EEPROM.put(address, maxvalue[i]);
|
||||
// Serial.println(minvalue[i]);
|
||||
// Serial.println(maxvalue[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct joyStickdataStruct {
|
||||
float value[6];
|
||||
} myJoyStickdataStruct;
|
||||
|
||||
joyStickdataStruct JoystickGet(bool debug = false)
|
||||
{
|
||||
for (int i = 0; i < 6; i++)
|
||||
{
|
||||
sensorValue = analogRead(analogInPin + i);
|
||||
|
||||
if (sensorValue > maxvalue[i])
|
||||
maxvalue[i] = sensorValue;
|
||||
if (sensorValue < minvalue[i])
|
||||
minvalue[i] = sensorValue;
|
||||
|
||||
outputValue = map(sensorValue, minvalue[i], maxvalue[i], 0, 255);
|
||||
outputValue = sensorValue > maxvalue[i] ? 255 : sensorValue < minvalue[i] ? 0 : outputValue;
|
||||
// if (sensorValue <= center[i])
|
||||
// {
|
||||
// outputValue = map(sensorValue, minvalue[i], center[i], 0, 127);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// outputValue = map(sensorValue, center[i], maxvalue[i], 128, 255);
|
||||
// }
|
||||
|
||||
myJoyStickdataStruct.value[i] = outputValue;
|
||||
|
||||
if (debug)
|
||||
{
|
||||
Serial.print(i);
|
||||
Serial.print("_");
|
||||
Serial.print(myJoyStickdataStruct.value[i]);
|
||||
if (i != 5)
|
||||
Serial.print(",");
|
||||
// Serial.print(" ");
|
||||
}
|
||||
}
|
||||
if (debug)
|
||||
Serial.println();
|
||||
|
||||
return myJoyStickdataStruct;
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include <SPI.h>
|
||||
#include "RF24.h"
|
||||
#include <RF24Network.h>
|
||||
#include "TransferDataStructure.h"
|
||||
|
||||
// Nrf24 settings
|
||||
RF24 radio(9, 10); // CE, CSN
|
||||
RF24Network network(radio);
|
||||
|
||||
const uint16_t this_node = 00; // Address of our node in Octal format
|
||||
const uint16_t other_node = 01; // Address of the other node in Octal format
|
||||
|
||||
void NrfInit()
|
||||
{
|
||||
SPI.begin();
|
||||
radio.begin();
|
||||
network.begin(/*channel*/ 90, /*node address*/ this_node);
|
||||
|
||||
// Set the PA Level low to prevent power supply related issues since this is a
|
||||
// getting_started sketch, and the likelihood of close proximity of the devices. RF24_PA_MAX is default.
|
||||
radio.setPALevel(RF24_PA_LOW);
|
||||
}
|
||||
|
||||
bool NrfSendData(dataStruct2 data, bool debug = false)
|
||||
//dataStruct2 NrfSendData(dataStruct2 data, bool debug = false)
|
||||
{
|
||||
// network.update();
|
||||
|
||||
if (debug)
|
||||
Serial.print("Sending...");
|
||||
|
||||
RF24NetworkHeader header(/*to node*/ other_node);
|
||||
bool ok = network.write(header, &data, sizeof(data));
|
||||
|
||||
if (debug)
|
||||
{
|
||||
if (ok)
|
||||
Serial.println("ok.");
|
||||
else
|
||||
Serial.println("failed.");
|
||||
}
|
||||
|
||||
return ok;
|
||||
// return data;
|
||||
}
|
||||
|
||||
dataStruct2 NrfGetData(dataStruct2 data, bool debug = false)
|
||||
{
|
||||
// network.update();
|
||||
int timeout = millis() + 100;
|
||||
while ( network.available() && timeout > millis() )
|
||||
{
|
||||
RF24NetworkHeader header;
|
||||
network.read(header, &data, sizeof(data));
|
||||
|
||||
if (debug)
|
||||
{
|
||||
Serial.print("Received packet ");
|
||||
Serial.println(data.servoPositions[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# remote
|
||||
|
||||
arduino code for remote control
|
||||
@@ -0,0 +1,122 @@
|
||||
#include "TransferDataStructure.h"
|
||||
|
||||
String getValue(String data, char separator, int index)
|
||||
{
|
||||
int found = 0;
|
||||
int strIndex[] = { 0, -1 };
|
||||
int maxIndex = data.length() - 1;
|
||||
|
||||
for (int i = 0; i <= maxIndex && found <= index; i++) {
|
||||
if (data.charAt(i) == separator || i == maxIndex) {
|
||||
found++;
|
||||
strIndex[0] = strIndex[1] + 1;
|
||||
strIndex[1] = (i == maxIndex) ? i + 1 : i;
|
||||
}
|
||||
}
|
||||
return found > index ? data.substring(strIndex[0], strIndex[1]) : "";
|
||||
}
|
||||
|
||||
dataStruct2 GetSerialData(dataStruct2 &dataIn, bool debug = false)
|
||||
{
|
||||
if (Serial.available() && Serial.peek() != -1)
|
||||
{
|
||||
// String data = Serial.readStringUntil('\r');
|
||||
// while (Serial.read() >= 0);
|
||||
// int maxIndex = data.length() - 1;
|
||||
// int index = 0;
|
||||
// int next_index;
|
||||
// String data_word;
|
||||
// String channel;
|
||||
// String angle;
|
||||
// do {
|
||||
// next_index = data.indexOf(',', index);
|
||||
//
|
||||
// data_word = data.substring(index, next_index);
|
||||
// channel = data_word.substring(0, data_word.indexOf('_'));
|
||||
// angle = data_word.substring(data_word.indexOf('_') + 1);
|
||||
//
|
||||
// if (isDigit(angle[0])) // prevent error overwriting first entry.
|
||||
// dataIn.servoPositions[channel.toInt()] = angle.toFloat();
|
||||
//
|
||||
// index = next_index + 1;
|
||||
// } while ((next_index != -1) && (next_index < maxIndex));
|
||||
|
||||
|
||||
/*String s = Serial.readStringUntil('s');
|
||||
for (int i = 0; i < 18; i++)
|
||||
{
|
||||
Serial.setTimeout(10);
|
||||
String c = Serial.readStringUntil('_');
|
||||
String v = Serial.readStringUntil(',');
|
||||
String cc = Serial.readStringUntil('=');
|
||||
int channel = c.toInt();
|
||||
int value = v.toInt();
|
||||
int crc = cc.toInt();
|
||||
|
||||
if ((crc == (channel) * value) && value >= 0 && value <= 180)
|
||||
dataIn.servoPositions[channel] = value;*/
|
||||
//byte recv[18 + 1];
|
||||
if (Serial.available() >= 18 + 1 + 2 + 30)
|
||||
{
|
||||
bool recc = false;
|
||||
byte recv[256] = {0};
|
||||
//Serial.readBytesUntil(0xffffff, recv, 255);
|
||||
Serial.readBytesUntil(0xff, recv, 255);
|
||||
for (int i = 0; i < 255 - 19; i++)
|
||||
{
|
||||
// if (recv[i + 1] == 0xff && recv[i + 2] == 0xff && recv[i + 3] == 0xff)
|
||||
if (recv[i + 19] == 0xfe)
|
||||
{
|
||||
for (int j = 0; j < 18 + 1; j++)
|
||||
{
|
||||
recv[j] = recv[(i) + j];
|
||||
}
|
||||
recc = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//s.getBytes(recv, s.length());
|
||||
byte crc = 0x00;
|
||||
for (int i = 0; i < 18; i++)
|
||||
{
|
||||
crc += recv[i];
|
||||
}
|
||||
|
||||
|
||||
if (crc == recv[18])
|
||||
{
|
||||
|
||||
if (recc)
|
||||
{
|
||||
for (int i = 0; i < 18; i++)
|
||||
{
|
||||
dataIn.servoPositions[i] = recv[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
//}
|
||||
|
||||
dataIn.remotePasstroughControl = true;
|
||||
|
||||
if (debug)
|
||||
{
|
||||
Serial.println("Enabled Servo Passtrough mode!");
|
||||
|
||||
Serial.println("Output line: ");
|
||||
for (int i = 0; i < 18; i++)
|
||||
{
|
||||
Serial.print(i);
|
||||
Serial.print(", ");
|
||||
Serial.print(dataIn.servoPositions[i]);
|
||||
Serial.println("");
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dataIn;
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef dataStruct
|
||||
#define dataStruct
|
||||
|
||||
struct __attribute__((__packed__)) dataStruct2
|
||||
{
|
||||
bool STOP = false;
|
||||
|
||||
float walkSpeed = 10; //step size
|
||||
float walkSpeed2 = 0; //gate speed
|
||||
float walkRotation = 0 ;
|
||||
float walkDirection = 0;
|
||||
|
||||
float tranform[3] = {0, 0, 0};
|
||||
float tranform2[3] = {0, 0, 0};
|
||||
float rotation[3] = {0, 0, 0};
|
||||
|
||||
bool remotePasstroughControl = 0;
|
||||
float servoPositions[18] = {
|
||||
90, 90, 90,
|
||||
90, 90, 90,
|
||||
90, 90, 90,
|
||||
|
||||
90, 90, 90,
|
||||
90, 90, 90,
|
||||
90, 90, 90
|
||||
};
|
||||
} dataStruct;
|
||||
|
||||
|
||||
//template<size_t ... T> struct print_ct {
|
||||
// print_ct() {
|
||||
// static_assert(sizeof...(T) == -1, "");
|
||||
// }
|
||||
//};
|
||||
//
|
||||
//print_ct< sizeof(dataStruct2), alignof(dataStruct2) > v;
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,176 @@
|
||||
#include "Joysticks.h"
|
||||
#include "Nrf.h"
|
||||
#include "SerialPasstrough.h"
|
||||
#include <Wire.h>
|
||||
#include <MPU6050_tockn.h>
|
||||
#include "Nextion.h"
|
||||
|
||||
NexPage page0 = NexPage(4, 0, "uploadscreen");
|
||||
|
||||
NexText t0 = NexText(1, 6, "t1");
|
||||
NexText t1 = NexText(3, 22, "t1");
|
||||
|
||||
NexButton b2 = NexButton(1, 5, "b2");
|
||||
NexSlider sliders[18] = {NexSlider(3, 1, "h0"),
|
||||
NexSlider(3, 2, "h1"),
|
||||
NexSlider(3, 3, "h2"),
|
||||
NexSlider(3, 4, "h3"),
|
||||
NexSlider(3, 5, "h4"),
|
||||
NexSlider(3, 6, "h5"),
|
||||
NexSlider(3, 7, "h6"),
|
||||
NexSlider(3, 8, "h7"),
|
||||
NexSlider(3, 9, "h8"),
|
||||
NexSlider(3, 10, "h9"),
|
||||
NexSlider(3, 11, "h10"),
|
||||
NexSlider(3, 12, "h11"),
|
||||
NexSlider(3, 13, "h12"),
|
||||
NexSlider(3, 14, "h13"),
|
||||
NexSlider(3, 15, "h14"),
|
||||
NexSlider(3, 16, "h15"),
|
||||
NexSlider(3, 17, "h16"),
|
||||
NexSlider(3, 18, "h17")
|
||||
};
|
||||
|
||||
char buffer[100] = {0};
|
||||
|
||||
NexTouch *nex_listen_list[] =
|
||||
{
|
||||
&b2,
|
||||
&page0,
|
||||
&t0,
|
||||
&t1,
|
||||
NULL
|
||||
};
|
||||
|
||||
void b2PopCallback(void *ptr)
|
||||
{
|
||||
passtrough();
|
||||
}
|
||||
|
||||
MPU6050 mpu (Wire);
|
||||
|
||||
dataStruct2 data;
|
||||
joyStickdataStruct joystickData;
|
||||
|
||||
void setup() {
|
||||
// Serial.begin(250000, SERIAL_8E1); // ARDUINO
|
||||
Serial.begin(250000); // TEENSY
|
||||
// Serial.begin(115200);
|
||||
Serial1.begin(115200);
|
||||
// passtrough();
|
||||
|
||||
nexInit();
|
||||
b2.attachPush(b2PopCallback, &b2);
|
||||
b2.attachPop(b2PopCallback, &b2);
|
||||
// for (int i = 0; i < 18; i++)
|
||||
// sliders[i] = NexSlider(3, i++, "h" + i);
|
||||
// while (true)
|
||||
// {
|
||||
// nexLoop(nex_listen_list);
|
||||
// sliders[0].setValue(30);
|
||||
// }
|
||||
|
||||
// Initialization
|
||||
// Wire.begin();
|
||||
// mpu.begin();
|
||||
//
|
||||
// mpu.calcGyroOffsets(true, 300, 1000);
|
||||
//
|
||||
// // Calibration
|
||||
// Serial.println("=====================================");
|
||||
// Serial.println("Starting calibration...");
|
||||
// mpu.calcGyroOffsets();
|
||||
// Serial.println("Calibration complete!");
|
||||
|
||||
JoystickInit();
|
||||
|
||||
NrfInit();
|
||||
}
|
||||
|
||||
void passtrough()
|
||||
{
|
||||
delay(100);
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
// nexLoop(nex_listen_list);
|
||||
// delay(10);
|
||||
page0.show();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
Serial.begin(115200);
|
||||
Serial1.begin(115200);
|
||||
do {
|
||||
if (Serial.available()) { // If anything comes in Serial (USB),
|
||||
Serial1.write(Serial.read()); // read it and send it out Serial1 (pins 0 & 1)
|
||||
}
|
||||
|
||||
if (Serial1.available()) { // If anything comes in Serial1 (pins 0 & 1)
|
||||
Serial.write(Serial1.read()); // read it and send it out Serial (USB)
|
||||
}
|
||||
} while (true);
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
nexLoop(nex_listen_list);
|
||||
network.update();
|
||||
|
||||
if (false)
|
||||
{
|
||||
mpu.update();
|
||||
Serial.print("AngX = ");
|
||||
Serial.print(mpu.getAngleX());
|
||||
Serial.print(" / AngY = ");
|
||||
Serial.print(mpu.getAngleY());
|
||||
Serial.print(" / AngZ = ");
|
||||
Serial.println(mpu.getAngleZ());
|
||||
}
|
||||
|
||||
// Serial.print(" / AngX = ");
|
||||
// Serial.print(mpu.getGyroAngleX());
|
||||
// Serial.print(" / AngY = ");
|
||||
// Serial.print(mpu.getGyroAngleY());
|
||||
// Serial.print(" / AngZ = ");
|
||||
// Serial.println(mpu.getGyroAngleZ());
|
||||
|
||||
joystickData = JoystickGet(true);
|
||||
|
||||
// test
|
||||
if (false)
|
||||
{
|
||||
data.remotePasstroughControl = true;
|
||||
data.servoPositions[1] = (joystickData.value[2] * 0.78f);
|
||||
data.servoPositions[4] = (joystickData.value[2] * 0.78f);
|
||||
data.servoPositions[7] = (joystickData.value[2] * 0.78f);
|
||||
|
||||
data.servoPositions[10] = 180 - (joystickData.value[2] * 0.78f);
|
||||
data.servoPositions[13] = 180 - (joystickData.value[2] * 0.78f);
|
||||
data.servoPositions[16] = 180 - (joystickData.value[2] * 0.78f);
|
||||
}
|
||||
|
||||
GetSerialData(data, false);
|
||||
// data = GetSerialData(data, false);
|
||||
|
||||
// data.STOP = true;
|
||||
|
||||
for (int i = 0; i < 18; i++)
|
||||
sliders[i].setValue(data.servoPositions[i]);
|
||||
|
||||
bool ok = NrfSendData(data, false);
|
||||
// data = NrfSendData(data, false);
|
||||
|
||||
if (ok)
|
||||
t0.setText("connected");
|
||||
else
|
||||
t0.setText("not connected");
|
||||
if (ok)
|
||||
t1.setText("connected");
|
||||
else
|
||||
t1.setText("not connected");
|
||||
|
||||
//test
|
||||
// data = NrfGetData(data, true);
|
||||
|
||||
delay(20);
|
||||
}
|
||||
@@ -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