// Tuning and wiring for the TranspOtter drive controller. // // Everything a new chassis needs changing is in this file. The values marked // MEASURE are geometry — the controller's distance and rotation commands are // only as accurate as these. #pragma once #include // ---------------------------------------------------------------- wiring --- // UART1 to the hoverboard's RIGHT sideboard cable (USART3). // // GPIO2/8/9 are strapping pins on the C3 and GPIO8 also drives the Supermini's // LED, so all three are out. GPIO20 is UART0's default RX, but the GPIO matrix // routes UART1 here happily — just never call Serial0.begin(), which would // claim GPIO20/21 and fight for the pin. GPIO21 is deliberately left alone: // the ROM bootloader logs there at every reset, which would otherwise land in // the mainboard's receiver. constexpr int PIN_HOVER_RX = 20; // ESP32 RX <-- mainboard TX (blue, PB10) constexpr int PIN_HOVER_TX = 10; // ESP32 TX --> mainboard RX (green, PB11) constexpr uint32_t HOVER_BAUD = 115200; // must match USART3_BAUD in the firmware // ------------------------------------------------------------- geometry --- constexpr float WHEEL_DIAMETER_M = 0.1651f; // 6.5" hub motor. MEASURE for other wheels. constexpr float TRACK_WIDTH_M = 0.570f; // measured, wheel centre to wheel centre constexpr int TICKS_PER_WHEEL_REV = 90; // 15 pole pairs x 6 hall states (firmware n_polePairs) constexpr int ODOM_MODULO = 9000; // firmware wraps wheel_cnt at 9000 (= 100 revolutions) // Set true if a wheel counts or drives backwards. Calibrate once, on blocks, // with `fwd 1` and `status` — see the README. constexpr bool INVERT_LEFT = false; constexpr bool INVERT_RIGHT = false; constexpr bool INVERT_ODOM_LEFT = false; constexpr bool INVERT_ODOM_RIGHT = false; // ------------------------------------------- agreement with the firmware --- // These mirror Inc/config.h in hoverboard-firmware-hack-FOC. In SPD_MODE a // command of +/-1000 is a closed-loop request for +/-N_MOT_MAX rpm, so the // mapping from m/s to command units is exact. Change one side, change both. constexpr float N_MOT_MAX_RPM = 200.0f; // firmware N_MOT_MAX constexpr int CMD_MAX = 1000; // firmware INPUT_MAX // -------------------------------------------------------- motion limits --- // Body-frame limits. The linear and angular axes are each jerk-limited, so a // wheel never sees a step in acceleration — this is what keeps a heavy load // from lurching at the start and nodding at the stop. constexpr float A_MAX_MPS2 = 0.50f; // linear acceleration constexpr float J_MAX_MPS3 = 3.00f; // linear jerk: how fast acceleration itself may change. // 1.0 was too low to stop on a mark: reversing acceleration // took a full second, and a closed-loop move sailed 160 mm // past its target. 3.0 still ramps in over ~170 ms, so there // is no step in acceleration and no lurch. // Braking may be firmer than acceleration — a gentle start is what stops a // load lurching, but a lazy stop just feels like slack in the controls. // // The multiplier scales the deceleration limit by x and the deceleration JERK // by x*x. That second part is the one you feel: the delay before braking bites // is amax/jmax, so squaring the jerk shortens that dead time by a factor of x // rather than leaving it unchanged. constexpr float DECEL_BOOST_DEFAULT = 2.0f; constexpr float W_MAX_RADS = 1.40f; // angular rate at full steer, standstill (~80 deg/s) constexpr float ALPHA_MAX = 1.60f; // angular acceleration constexpr float J_MAX_ALPHA = 9.60f; // angular jerk (same reasoning as J_MAX_MPS3) // The four limits above are the SOFT end of the response range — the tuning // validated on the bench, and the right feel for hauling a load a long way. // The values below are the SHARP end, for indoor work where reacting matters // more than smoothness. The `response` setting (0..1, live-adjustable and // saved to flash) interpolates between the two, so nothing here is a rebuild. constexpr float A_MAX_SHARP = 1.50f; constexpr float J_MAX_SHARP = 9.00f; constexpr float ALPHA_MAX_SHARP = 3.50f; constexpr float J_ALPHA_SHARP = 21.0f; // Startup defaults, overridden by whatever was last saved. constexpr float RESPONSE_DEFAULT = 0.0f; // soft constexpr float SPEED_LIMIT_DEFAULT = 0.4f; // 40% of V_MAX on the joystick // --------------------------------------------------------- steering feel --- // Angular authority as a function of how fast the robot is already going: // // gain(s) = HIGH + (LOW - HIGH) * exp(-s / KNEE), s = |v| / V_MAX // // LOW applies at standstill, decaying towards HIGH as speed builds. KNEE is the // fraction of top speed over which most of the extra authority disappears, so a // small KNEE means "sharp only when crawling". constexpr float STEER_GAIN_LOW = 1.00f; // full W_MAX when stationary — spins on the spot constexpr float STEER_GAIN_HIGH = 0.22f; // at top speed, a gentle curve instead of a swerve constexpr float STEER_KNEE = 0.15f; // authority is ~halved by 10% of top speed // ---------------------------------------------------- closed-loop moves --- constexpr float MOVE_V_MAX = 0.35f; // m/s ceiling during `fwd` (leaves braking margin) constexpr float MOVE_W_MAX = 0.70f; // rad/s ceiling during `rot` constexpr float MOVE_TOL_M = 0.010f; // arrival tolerance, metres (~2 hall ticks) constexpr float MOVE_TOL_RAD = 0.020f; // arrival tolerance, radians (~1.1 deg) constexpr float MOVE_SETTLE_MPS = 0.02f; // below this a linear move counts as stopped constexpr float MOVE_SETTLE_RADS = 0.05f; // below this a rotation counts as stopped // Floor on the approach speed. Without it the braking profile decays towards // zero as the target nears and the last millimetres take forever. Both values // are slow enough to stop inside the tolerance above. constexpr float MOVE_CREEP_MPS = 0.030f; constexpr float MOVE_CREEP_RADS = 0.050f; // ---------------------------------------------------------------- timing --- constexpr uint32_t CONTROL_PERIOD_MS = 10; // 100 Hz command rate (firmware times out at 800 ms) constexpr uint32_t FEEDBACK_TIMEOUT_MS = 500; // no valid frame for this long -> ramp down constexpr uint32_t ARM_HOLD_MS = 1500; // hold zero at boot so the firmware arms the motors // Derived: top wheel speed the firmware will honour, in m/s. constexpr float PI_F = 3.14159265f; constexpr float V_MAX = (N_MOT_MAX_RPM / 60.0f) * PI_F * WHEEL_DIAMETER_M; // Trim gains for closed-loop moves: a straight `fwd` holds heading, a `rot` // holds position. Both correct the small mismatch between the two wheels. constexpr float MOVE_HEADING_KP = 1.5f; // rad/s per radian of heading error constexpr float MOVE_POSITION_KP = 1.0f; // m/s per metre of drift constexpr float MOVE_TRIM_W_MAX = 0.35f; // clamp on the heading correction constexpr float MOVE_TRIM_V_MAX = 0.15f; // clamp on the position correction // Metres of travel per hall tick — the resolution of every closed-loop move. constexpr float METRES_PER_TICK = (PI_F * WHEEL_DIAMETER_M) / TICKS_PER_WHEEL_REV; // ------------------------------------------------------------- gamepad --- // Turbo is a toggle on the pad. While it is on these replace the saved speed // and response, so one button swaps a loaded walking pace for something // reactive without touching the terminal. constexpr float TURBO_SPEED = 0.90f; constexpr float TURBO_RESPONSE = 0.85f; // Buzzer signatures, as (pitch, milliseconds). Lower pitch value = higher // tone: the mainboard toggles the buzzer every `pitch` ticks of a 16 kHz // counter, so 4 is about 2 kHz and 24 about 330 Hz. constexpr uint8_t BEEP_CONNECT = 5; constexpr uint16_t BEEP_CONNECT_MS = 400; constexpr uint8_t BEEP_DISCONNECT = 24; constexpr uint16_t BEEP_DISCONNECT_MS = 800; constexpr uint8_t BEEP_TURBO_ON = 4; constexpr uint16_t BEEP_TURBO_ON_MS = 220; constexpr uint8_t BEEP_TURBO_OFF = 14; constexpr uint16_t BEEP_TURBO_OFF_MS = 220; constexpr uint8_t BEEP_BRAKE_OFF = 8; constexpr uint16_t BEEP_BRAKE_OFF_MS = 90; constexpr uint8_t BEEP_BRAKE_ON = 16; constexpr uint16_t BEEP_BRAKE_ON_MS = 90; // Some remotes transmit on press only and never on release, so a button can // never be observed going back up. For those the arrival of a report is the // click, and each function latches or pulses rather than tracking a level. // hold = level; needs a controller that reports release // latch = click on, click off // pulse = each click drives for PULSE_MS, repeated clicks extend it constexpr uint16_t PULSE_MS_DEFAULT = 1500; // Controllers that repeat a report while a button is held (10-20 Hz is // typical) need edge detection, or every repeat reads as another press. And // because a release report can be dropped by the radio, button state is also // treated as stale if no report arrives for this long — which doubles as a // guard against a held direction sticking on after a lost release. constexpr uint32_t INPUT_STALE_MS_DEFAULT = 300; // Latching with no release event means a lost or pocketed remote leaves the // machine driving. In latch mode, this long without any button event clears // the latches. 0 disables it, which is not advisable on anything heavy. constexpr uint32_t IDLE_STOP_MS_DEFAULT = 10000; // A HID pad only reports when something changes, so there is no heartbeat to // time out against. Liveness comes from the BLE link itself instead: a short // supervision timeout means walking out of range drops the connection in about // a second and a half rather than the default six. constexpr uint16_t BLE_CONN_MIN_INTERVAL = 12; // x1.25 ms = 15 ms constexpr uint16_t BLE_CONN_MAX_INTERVAL = 24; // x1.25 ms = 30 ms constexpr uint16_t BLE_CONN_LATENCY = 0; constexpr uint16_t BLE_CONN_TIMEOUT = 150; // x10 ms = 1.5 s // A closed-loop move ends by arriving, so it is exempt from the deadman below. // That leaves it with no time limit at all, which is dangerous if the odometry // stops advancing — a dropped link, or a wheel jammed against an obstacle. If // the remaining distance fails to change by PROGRESS_EPS within STALL_S, the // move is abandoned. constexpr float MOVE_PROGRESS_EPS = 0.005f; // metres or radians constexpr float MOVE_STALL_S = 1.5f; // A `drive` or `vel` command is a standing order; if nothing refreshes it // within this long the controller ramps down. Closed-loop moves are exempt — // they end by arriving. constexpr uint32_t COMMAND_DEADMAN_MS = 2000;