- LEGACY/Hexapod_Teensy/: old robot sketch (reference only). - platformio.ini: robot env targets teensy36 (owner-confirmed board). - Config.h: per-servo SERVOS table (pin, pulse at 0 deg, signed us/deg, pulse limits) built from the old sketch's pins, ranges, trims and the old Unity servo directions. Rear-left/front-left coxa offsets set to 0: the owner re-horns those two servos (the old +-90 left them at the end of travel; the sim showed them clamping). - ServoDriver: 18 servos on hardware PWM (50 Hz, 16-bit); the Teensy 3.6 Servo library stops at 12. - Radio on CE 39 / CSN 15 with SPI SCK on pin 14. - sim: analogWrite/PWM shims, pulses decoded through SERVOS (API v6). - Docs: CLAUDE.md, READMEs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
162 lines
6.1 KiB
C++
162 lines
6.1 KiB
C++
#include "HexapodSimApi.h"
|
|
#include "SimHardware.h"
|
|
#include "SimRobotAccess.h"
|
|
#include "SimBody.h"
|
|
#include "../robot/Config.h" // geometry and pins as the firmware sees them
|
|
#include <HexapodProtocol.h>
|
|
|
|
static_assert(NUM_LEGS == HXSIM_NUM_LEGS, "HxGeometry arrays are sized for 6 legs");
|
|
|
|
// The real Teensy spins loop() far faster than this; 250 us keeps the
|
|
// control-rate jitter under 2% at 60 Hz while staying cheap.
|
|
static constexpr uint32_t HXSIM_LOOP_PERIOD_US = 250;
|
|
|
|
static bool g_started = false;
|
|
static uint32_t g_control_ticks = 0;
|
|
static bool g_manual_imu = false; // hxsim_set_imu() called: stop feeding the IMU from the body
|
|
static uint64_t g_last_body_us = 0;
|
|
|
|
// After each control tick: move the kinematic body to the new feet, and feed
|
|
// the simulated MPU6050 what it would measure on that body.
|
|
static void bodyStep() {
|
|
float feet[18];
|
|
hxsim_get_feet_mm(feet);
|
|
uint64_t now = simhw::nowMicros();
|
|
simbody::update(feet, (now - g_last_body_us) / 1e6f);
|
|
g_last_body_us = now;
|
|
if (!g_manual_imu) {
|
|
float a[3], g[3];
|
|
simbody::imu(a, g);
|
|
simhw::imuSet(a[0], a[1], a[2], g[0], g[1], g[2]);
|
|
}
|
|
}
|
|
|
|
HXSIM_API int32_t hxsim_api_version(void) { return HXSIM_API_VERSION; }
|
|
HXSIM_API int32_t hxsim_packet_size(void) { return (int32_t)sizeof(CmdPacket); }
|
|
|
|
HXSIM_API void hxsim_get_geometry(HxGeometry* g) {
|
|
g->num_legs = NUM_LEGS;
|
|
for (int i = 0; i < NUM_LEGS; i++) {
|
|
g->mount_x_mm[i] = LEG_MOUNT_X_MM[i];
|
|
g->mount_y_mm[i] = LEG_MOUNT_Y_MM[i];
|
|
g->mount_angle_deg[i] = LEG_MOUNT_ANGLE_DEG[i];
|
|
}
|
|
g->coxa_mm = COXA_LEN_MM;
|
|
g->femur_mm = FEMUR_LEN_MM;
|
|
g->tibia_mm = TIBIA_LEN_MM;
|
|
g->stance_radius_mm = STANCE_RADIUS_MM;
|
|
g->stance_height_mm = STANCE_HEIGHT_MM;
|
|
g->coxa_min_deg = COXA_MIN_DEG; g->coxa_max_deg = COXA_MAX_DEG;
|
|
g->femur_min_deg = FEMUR_MIN_DEG; g->femur_max_deg = FEMUR_MAX_DEG;
|
|
g->tibia_min_deg = TIBIA_MIN_DEG; g->tibia_max_deg = TIBIA_MAX_DEG;
|
|
g->control_hz = CONTROL_HZ;
|
|
g->leg_clearance_mm = LEG_CLEARANCE_MM;
|
|
g->stance_height_range_mm = STANCE_HEIGHT_RANGE_MM;
|
|
}
|
|
|
|
HXSIM_API void hxsim_set_radio_present(int32_t present) { simhw::radioSetPresent(present != 0); }
|
|
HXSIM_API void hxsim_set_imu_present(int32_t present) { simhw::imuSetPresent(present != 0); }
|
|
|
|
HXSIM_API void hxsim_start(void) {
|
|
if (g_started) return;
|
|
g_started = true;
|
|
simhw::imuSet(0, 0, 1, 0, 0, 0); // level and still until the body model says otherwise
|
|
simbody::reset();
|
|
simrobot::setupOnce();
|
|
bodyStep();
|
|
}
|
|
|
|
HXSIM_API void hxsim_step(uint32_t dt_us) {
|
|
if (!g_started) hxsim_start();
|
|
while (dt_us > 0) {
|
|
uint32_t chunk = dt_us < HXSIM_LOOP_PERIOD_US ? dt_us : HXSIM_LOOP_PERIOD_US;
|
|
simhw::advanceMicros(chunk);
|
|
dt_us -= chunk;
|
|
uint32_t before = simrobot::lastLoopMicros();
|
|
simrobot::loopOnce();
|
|
if (simrobot::lastLoopMicros() != before) {
|
|
g_control_ticks++;
|
|
bodyStep();
|
|
}
|
|
}
|
|
}
|
|
|
|
HXSIM_API void hxsim_radio_send(const uint8_t* bytes, int32_t len) { simhw::radioSend(bytes, len); }
|
|
|
|
HXSIM_API void hxsim_set_imu(float ax, float ay, float az, float gx, float gy, float gz) {
|
|
g_manual_imu = true;
|
|
simhw::imuSet(ax, ay, az, gx, gy, gz);
|
|
}
|
|
|
|
HXSIM_API void hxsim_set_ground_tilt(float roll_deg, float pitch_deg) { simbody::setGroundTilt(roll_deg, pitch_deg); }
|
|
|
|
HXSIM_API void hxsim_get_body(float* pos_mm, float* quat_wxyz, float* yaw_deg, int32_t* contact_mask) {
|
|
if (pos_mm) simbody::position(pos_mm);
|
|
if (quat_wxyz) simbody::orientation(quat_wxyz);
|
|
if (yaw_deg) *yaw_deg = simbody::yawDeg();
|
|
if (contact_mask) *contact_mask = simbody::contactMask();
|
|
}
|
|
|
|
HXSIM_API void hxsim_serial_input(const char* bytes, int32_t len) { simhw::serialFeed(bytes, len); }
|
|
HXSIM_API int32_t hxsim_serial_output(char* out, int32_t cap) { return simhw::serialDrain(out, cap); }
|
|
|
|
HXSIM_API void hxsim_get_servos(int32_t* us, int32_t* attached, uint32_t* writes) {
|
|
for (int i = 0; i < NUM_LEGS; i++)
|
|
for (int j = 0; j < 3; j++) {
|
|
const simhw::ServoPin& p = simhw::servoPin(SERVOS[i][j].pin);
|
|
if (us) us[i * 3 + j] = p.us;
|
|
if (attached) attached[i * 3 + j] = p.attached ? 1 : 0;
|
|
if (writes) writes[i * 3 + j] = p.writes;
|
|
}
|
|
}
|
|
|
|
// Inverse of ServoDriver::degToMicros through the same per-servo table:
|
|
// what angle the firmware meant by this pulse.
|
|
static float pulseToDeg(int leg, int joint, int us) {
|
|
const ServoCal& c = SERVOS[leg][joint];
|
|
return (us - c.us_at_0deg) / c.us_per_deg;
|
|
}
|
|
|
|
HXSIM_API void hxsim_get_joints_deg(float* deg) {
|
|
for (int i = 0; i < NUM_LEGS; i++)
|
|
for (int j = 0; j < 3; j++)
|
|
deg[i * 3 + j] = pulseToDeg(i, j, simhw::servoPin(SERVOS[i][j].pin).us);
|
|
}
|
|
|
|
// Forward kinematics, written independently of robot/Kinematics.cpp so it
|
|
// can catch IK mistakes. Joint convention (the one Config.h's limits imply):
|
|
// coxa - CCW from the leg's mount angle, viewed from above
|
|
// femur - elevation above horizontal, + = up
|
|
// tibia - relative to the femur line, + = up, so a knee-down leg is negative
|
|
HXSIM_API void hxsim_get_feet_mm(float* xyz) {
|
|
float deg[NUM_LEGS * 3];
|
|
hxsim_get_joints_deg(deg);
|
|
for (int i = 0; i < NUM_LEGS; i++) {
|
|
float a = radians(LEG_MOUNT_ANGLE_DEG[i] + deg[i * 3 + 0]);
|
|
float f = radians(deg[i * 3 + 1]);
|
|
float t = radians(deg[i * 3 + 1] + deg[i * 3 + 2]);
|
|
float horiz = COXA_LEN_MM + FEMUR_LEN_MM * cosf(f) + TIBIA_LEN_MM * cosf(t);
|
|
xyz[i * 3 + 0] = LEG_MOUNT_X_MM[i] + horiz * cosf(a);
|
|
xyz[i * 3 + 1] = LEG_MOUNT_Y_MM[i] + horiz * sinf(a);
|
|
xyz[i * 3 + 2] = FEMUR_LEN_MM * sinf(f) + TIBIA_LEN_MM * sinf(t);
|
|
}
|
|
}
|
|
|
|
HXSIM_API void hxsim_get_status(HxStatus* s) {
|
|
s->robot_state = simrobot::state();
|
|
s->sit_stand_blend = simrobot::sitStandBlend();
|
|
s->link_ok = simrobot::linkOk() ? 1 : 0;
|
|
s->heading_deg = simrobot::headingDeg();
|
|
s->imu_roll_deg = simrobot::imuRollDeg();
|
|
s->imu_pitch_deg = simrobot::imuPitchDeg();
|
|
s->imu_enabled = simrobot::imuEnabled() ? 1 : 0;
|
|
s->sim_time_ms = (uint32_t)(simhw::nowMicros() / 1000);
|
|
s->control_ticks = g_control_ticks;
|
|
s->pose_reach_scale = simrobot::poseReachScale();
|
|
s->twist_scale = simrobot::twistScale();
|
|
s->stance_height_mm = simrobot::stanceHeightMm();
|
|
s->stance_mask = 0;
|
|
for (int i = 0; i < NUM_LEGS; i++)
|
|
if (simrobot::legInStance(i)) s->stance_mask |= 1 << i;
|
|
}
|