2026-09-28 18:14:00 +02:00
|
|
|
#pragma once
|
|
|
|
|
#include <Arduino.h>
|
|
|
|
|
|
|
|
|
|
// =====================================================================
|
|
|
|
|
// Remote handset config (Teensy 4.1). Everything here is a placeholder
|
|
|
|
|
// until checked against your real wiring and parts - see the BENCH notes.
|
|
|
|
|
// Radio address/channel/rate/PA/payload size are NOT here: they live in
|
|
|
|
|
// shared/HexapodProtocol so they can't drift from the robot.
|
|
|
|
|
// =====================================================================
|
|
|
|
|
|
|
|
|
|
// ---- Loop rate ----
|
|
|
|
|
// Must be >= the robot's CONTROL_HZ (60) so every robot control tick sees a
|
|
|
|
|
// fresh packet. 100 Hz gives margin for the odd dropped packet.
|
|
|
|
|
constexpr float TX_HZ = 100.0f;
|
|
|
|
|
|
|
|
|
|
// ---- Radio (nRF24L01) on SPI0: MOSI 11, MISO 12, SCK 13 ----
|
2026-10-25 17:30:35 +01:00
|
|
|
// CE/CSN as in the old remote sketch (2019-teensy-nrf24/remote/Nrf.h).
|
2026-09-28 18:14:00 +02:00
|
|
|
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
|
|
|
|
|
// newer packet replaces a lost one 10 ms later anyway, and write() blocks for
|
|
|
|
|
// the whole retry sequence. Worst case here is ~4 attempts x ~1.7 ms.
|
|
|
|
|
constexpr uint8_t RADIO_RETRY_DELAY = 1;
|
|
|
|
|
constexpr uint8_t RADIO_RETRY_COUNT = 3;
|
|
|
|
|
// No acks for this long -> remote considers the link lost and disarms itself,
|
|
|
|
|
// mirroring the robot's LINK_TIMEOUT_SEC (0.3 s) failsafe, so the robot can't
|
|
|
|
|
// spring back up on its own when the link returns.
|
|
|
|
|
constexpr uint32_t LINK_LOST_MS = 300;
|
|
|
|
|
|
|
|
|
|
// ---- Joysticks: 2 x 3-axis analog potentiometer sticks ----
|
|
|
|
|
// 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 };
|
|
|
|
|
|
2026-10-25 17:30:35 +01:00
|
|
|
// A0-A5 in a row, as wired for the old remote (2019-teensy-nrf24/remote/Joysticks.h).
|
2026-10-01 15:29:21 +02:00
|
|
|
// 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 };
|
2026-09-28 18:14:00 +02:00
|
|
|
|
|
|
|
|
// Each logical axis must end up: X + = pushed RIGHT, Y + = pushed FORWARD
|
|
|
|
|
// (away from you), TWIST + = rotated COUNTERCLOCKWISE seen from above.
|
|
|
|
|
// BENCH: flip any axis that reads the wrong way on the serial 'p' status dump.
|
|
|
|
|
constexpr bool STICK_INVERT[NUM_AXES] = { false, false, false, false, false, false };
|
|
|
|
|
|
|
|
|
|
constexpr int ADC_BITS = 12; // 0..4095
|
|
|
|
|
constexpr int ADC_AVERAGING = 8; // hardware averaging per analogRead()
|
|
|
|
|
|
|
|
|
|
// Fallback calibration used until you run the on-device sweep calibration
|
|
|
|
|
// (which is saved to EEPROM and overrides these).
|
|
|
|
|
// BENCH: typical pot sticks don't use the full electrical range - run the
|
|
|
|
|
// sweep calibration once, then optionally paste the printed values here.
|
|
|
|
|
constexpr int16_t STICK_DEFAULT_MIN = 200;
|
|
|
|
|
constexpr int16_t STICK_DEFAULT_CENTER = 2048;
|
|
|
|
|
constexpr int16_t STICK_DEFAULT_MAX = 3900;
|
|
|
|
|
|
|
|
|
|
// At power-on the resting position of each axis is re-captured as its center
|
|
|
|
|
// (pots drift a little with temperature). Rejected if a stick is visibly
|
|
|
|
|
// deflected, i.e. further than this from the stored center (ADC counts).
|
|
|
|
|
constexpr int16_t CENTER_CAPTURE_TOLERANCE = 250;
|
|
|
|
|
// A sweep calibration is rejected unless every axis travels at least this
|
|
|
|
|
// far on both sides of center (ADC counts) - catches "forgot to move a stick".
|
|
|
|
|
constexpr int16_t CAL_MIN_HALF_SPAN = 600;
|
|
|
|
|
|
|
|
|
|
// Deadzone as a fraction of full deflection; output is rescaled so motion
|
|
|
|
|
// starts smoothly from 0 at the deadzone edge instead of jumping.
|
|
|
|
|
constexpr float STICK_DEADZONE[NUM_AXES] = { 0.06f, 0.06f, 0.10f, 0.06f, 0.06f, 0.10f };
|
|
|
|
|
// 0 = linear, 1 = fully cubic. A bit of expo gives fine control near center.
|
|
|
|
|
constexpr float STICK_EXPO[NUM_AXES] = { 0.25f, 0.25f, 0.30f, 0.20f, 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;
|
|
|
|
|
|
2026-10-01 15:29:21 +02:00
|
|
|
// ---- 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
|
2026-09-28 18:14:00 +02:00
|
|
|
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.
|
|
|
|
|
// BENCH: with the defaults the chip is assumed flat, component side up, its
|
|
|
|
|
// X arrow pointing away from you. Verify with the serial 'p' dump: tipping
|
|
|
|
|
// the far edge down must read +pitch, dipping the right side must read +roll.
|
|
|
|
|
constexpr uint8_t IMU_AXIS_FROM_CHIP[3] = { 0, 1, 2 };
|
|
|
|
|
constexpr int8_t IMU_AXIS_SIGN[3] = { +1, +1, +1 };
|
|
|
|
|
constexpr float IMU_ALPHA = 0.98f; // complementary filter, same as the robot
|
|
|
|
|
constexpr int IMU_GYRO_BIAS_SAMPLES = 200; // at boot; hold the remote still
|
|
|
|
|
|
|
|
|
|
// Handset tilt -> CmdPacket.remote_roll/pitch. This many degrees of tilt away
|
|
|
|
|
// from the zeroed "level" pose = full scale (1000); the robot then scales
|
|
|
|
|
// full scale to its own MAX_TILT_DEG.
|
|
|
|
|
constexpr float REMOTE_TILT_FULL_SCALE_DEG = 30.0f;
|
|
|
|
|
constexpr float REMOTE_TILT_DEADZONE_DEG = 2.0f; // ignore hand tremor around level
|
|
|
|
|
// Toggling "remote gyro" fades the tilt in/out at this rate (fraction/s)
|
|
|
|
|
// instead of stepping it. The robot rate-limits pose as well; this just
|
|
|
|
|
// keeps the flag change itself from being a hard edge.
|
|
|
|
|
constexpr float REMOTE_TILT_FADE_PER_SEC = 2.0f;
|
|
|
|
|
|
|
|
|
|
// ---- Operator setpoints ----
|
|
|
|
|
constexpr uint8_t SPEED_SCALE_DEFAULT = 128;
|
|
|
|
|
constexpr float SPEED_SCALE_RATE_PER_SEC = 255.0f; // slider jumps ramp over <= 1 s (robot doesn't smooth twist)
|
|
|
|
|
constexpr uint8_t SPEED_SCALE_STEP = 25; // serial console +/- step
|
2026-09-29 00:37:12 +02:00
|
|
|
// Standing height slider -> CmdPacket.body_height (-1000..1000, 0 = the
|
|
|
|
|
// robot's default). No smoothing here: the robot rate-limits height itself.
|
|
|
|
|
constexpr int16_t HEIGHT_STEP = 100; // serial console [ / ] step
|
2026-09-28 18:14:00 +02:00
|
|
|
// FLAG_WALK stays set this long after the left stick returns to center, so
|
|
|
|
|
// hovering at the deadzone edge doesn't chatter STAND <-> WALKING.
|
|
|
|
|
constexpr uint32_t WALK_LINGER_MS = 250;
|
|
|
|
|
// Arming is refused unless every stick axis is within this of center
|
|
|
|
|
// (post-deadzone, -1000..1000 scale), so the robot never arms mid-command.
|
|
|
|
|
constexpr int16_t ARM_CENTER_TOLERANCE = 50;
|
|
|
|
|
|
2026-10-01 15:29:21 +02:00
|
|
|
// ---- 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.
|
2026-09-28 18:14:00 +02:00
|
|
|
constexpr uint32_t NEXTION_BAUD = 115200;
|
2026-10-01 15:29:21 +02:00
|
|
|
// 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"
|
2026-09-28 18:14:00 +02:00
|
|
|
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
|