-
Notifications
You must be signed in to change notification settings - Fork 6
Fixing speed commands with motors #171
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
e77352d
6dc9cd0
6a9043a
4aa4e95
f5fb79e
f365ffa
4bc9c68
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,10 @@ | |
| #include <algorithm> | ||
| #include <cmath> | ||
| #include <map> | ||
| #include <stdexcept> | ||
|
|
||
|
|
||
| // Function made to parse one joint's YAML block into a JointConfig. | ||
| JointConfig JointCommandCore::loadJointConfig(const YAML::Node& joint_node) { | ||
| JointConfig joint; | ||
| joint.motor_id = static_cast<int8_t>(joint_node["can_id"].as<int>()); | ||
|
|
@@ -16,6 +18,7 @@ | |
| return joint; | ||
| } | ||
|
|
||
| // Loads all 6 joints for an arm side, resets safety/state to defaults. | ||
| bool JointCommandCore::loadFromYaml(const YAML::Node& config, const std::string& arm_side) { | ||
| joints_.clear(); | ||
|
|
||
|
|
@@ -49,10 +52,12 @@ | |
| // Previously this started false, letting the first command bypass all rate limiting | ||
| // and jump straight to its target -- visible as a sudden snap before smooth tracking. | ||
| prev_targets_.assign(joints_.size(), 0.0); | ||
| prev_velocities_.assign(joints_.size(), 0.0); | ||
| have_prev_targets_ = true; | ||
| return true; | ||
| } | ||
|
|
||
| // Overwrites the assumed-0 start pose with real measured motor angles. | ||
| size_t JointCommandCore::seedPrevTargetsFromFeedback(const std::map<int, double>& motor_positions) { | ||
| std::vector<double> seeded(joints_.size(), 0.0); | ||
| size_t matched = 0; | ||
|
|
@@ -69,21 +74,28 @@ | |
| ++matched; | ||
| } | ||
| prev_targets_ = std::move(seeded); | ||
| // Real feedback carries no velocity sample here, so the ramp starts from rest each time it's | ||
| // re-seeded (e.g. after a hand-move) -- safe, since starting from 0 can only under-accelerate, | ||
| // never overshoot. | ||
| prev_velocities_.assign(joints_.size(), 0.0); | ||
| have_prev_targets_ = true; | ||
| return matched; | ||
| } | ||
|
|
||
| // Hard-limit clip, and arm-frame<->motor-frame conversion. | ||
| double JointCommandCore::clampAngle(double angle, const JointConfig& joint) { | ||
| if (!joint.limit_range) { | ||
| return angle; | ||
| } | ||
| return std::clamp(angle, joint.lower_limit, joint.upper_limit); | ||
| } | ||
|
|
||
| // Applies calibration lol | ||
| double JointCommandCore::applyCalibration(double angle, const JointConfig& joint) { | ||
| return static_cast<double>(joint.direction) * (angle - joint.zero_offset); | ||
| } | ||
|
|
||
| // Applies a joint's YAML overrides on top of a base/default config. | ||
| JointSafetyConfig JointCommandCore::loadJointSafetyConfig(const YAML::Node& joint_node, | ||
| const JointSafetyConfig& base) { | ||
| JointSafetyConfig cfg = base; | ||
|
|
@@ -119,9 +131,16 @@ | |
| if (joint_node["mit_kd"]) { | ||
| cfg.mit_kd = joint_node["mit_kd"].as<double>(); | ||
| } | ||
| if (joint_node["enable_trapezoidal_limit"]) { | ||
| cfg.enable_trapezoidal_limit = joint_node["enable_trapezoidal_limit"].as<bool>(); | ||
| } | ||
| if (joint_node["accel_max"]) { | ||
| cfg.accel_max = joint_node["accel_max"].as<double>(); | ||
| } | ||
| return cfg; | ||
| } | ||
|
|
||
| // Resolves global defaults then per-joint overrides for all 6 joints. | ||
| bool JointCommandCore::loadSafetyFromYaml(const YAML::Node& safety_cfg, double control_rate_hz) { | ||
| if (joints_.empty()) { | ||
| return false; | ||
|
|
@@ -147,6 +166,7 @@ | |
| return true; | ||
| } | ||
|
|
||
| // Generic rate-limit-toward-target, and exponential smoothing. | ||
| double JointCommandCore::clampStep(double target, double previous, double delta_max) { | ||
| return previous + std::clamp(target - previous, -delta_max, delta_max); | ||
| } | ||
|
|
@@ -155,6 +175,42 @@ | |
| return alpha * previous + (1.0 - alpha) * target; | ||
| } | ||
|
|
||
| // Accelerate/cruise/decelerate ramp onto a (possibly moving) target, no overshoot. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Still think that comment like this may not be that necessary, feel free to disagree though Generally convention in comments is that as few comment as possible will be nice, if the code is self-readable, it's better to not put comment can double check all comments as a whole
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just removed it. i double checked comments, everything else seems essential. lmk if u disagree though |
||
| double JointCommandCore::stepTrapezoidal(double target, double prev_pos, double& prev_vel, | ||
| double velocity_max, double accel_max, double dt) { | ||
| const double error = target - prev_pos; | ||
| const double direction = (error > 0.0) ? 1.0 : (error < 0.0 ? -1.0 : 0.0); | ||
| const double accel_step = accel_max * dt; | ||
|
|
||
| double desired_vel; | ||
| if (direction == 0.0 || (prev_vel != 0.0 && (prev_vel > 0.0) != (direction > 0.0))) { | ||
| // Already there, or still moving the wrong way (target reversed under us) -- brake to a | ||
| // stop before committing to the new direction, so we never yank straight through zero. | ||
| const double braked = std::abs(prev_vel) - accel_step; | ||
| desired_vel = (braked > 0.0) ? std::copysign(braked, prev_vel) : 0.0; | ||
| } else { | ||
| const double stopping_distance = (prev_vel * prev_vel) / (2.0 * accel_max); | ||
| if (std::abs(error) <= stopping_distance) { | ||
| // Close enough that cruising another tick would overshoot -- decelerate. | ||
| desired_vel = direction * std::max(0.0, std::abs(prev_vel) - accel_step); | ||
| } else { | ||
| // Free to speed up toward cruise. | ||
| desired_vel = direction * std::min(velocity_max, std::abs(prev_vel) + accel_step); | ||
| } | ||
| } | ||
|
|
||
| double step = desired_vel * dt; | ||
| if (std::abs(step) >= std::abs(error)) { | ||
| // Never overshoot within a single tick; land exactly on target and stop. | ||
| step = error; | ||
| desired_vel = 0.0; | ||
| } | ||
|
|
||
| prev_vel = desired_vel; | ||
| return prev_pos + step; | ||
| } | ||
|
|
||
| // Setup: validates message shape, flattens angles, defensive state resizing. | ||
| std::vector<common_msgs::msg::MotorCmd> | ||
| JointCommandCore::armPoseToMotorCmds(const common_msgs::msg::ArmPose& pose, int8_t control_type) { | ||
| if (joints_.size() != 6) { | ||
|
|
@@ -182,7 +238,11 @@ | |
| prev_targets_.assign(joints_.size(), 0.0); | ||
| have_prev_targets_ = true; | ||
| } | ||
| if (prev_velocities_.size() != joints_.size()) { | ||
| prev_velocities_.assign(joints_.size(), 0.0); | ||
| } | ||
|
|
||
| // Per-joint moderation loop: clamp -> velocity-limit (trapezoidal or plain+low-pass) -> delta-limit -> clamp. | ||
| for (size_t i = 0; i < joints_.size(); ++i) { | ||
| const JointSafetyConfig& safety = safety_[i]; | ||
|
|
||
|
|
@@ -192,14 +252,21 @@ | |
| } | ||
|
|
||
| if (have_prev_targets_) { | ||
| if (safety.enable_velocity_limit && control_rate_hz_ > 0.0) { | ||
| if (safety.enable_trapezoidal_limit && control_rate_hz_ > 0.0) { | ||
| // Replaces the plain velocity clamp and low-pass below: both of those re-discount the | ||
| // ramp's own speed (a low-pass after a velocity clamp silently cuts steady-state speed | ||
| // by ~(1-alpha) -- see JointCommand.md), which is the bug this profile fixes. | ||
| target = stepTrapezoidal(target, prev_targets_[i], prev_velocities_[i], | ||
| std::abs(safety.velocity_max), std::abs(safety.accel_max), | ||
| 1.0 / control_rate_hz_); | ||
| } else if (safety.enable_velocity_limit && control_rate_hz_ > 0.0) { | ||
| const double velocity_step = std::abs(safety.velocity_max) / control_rate_hz_; | ||
| target = clampStep(target, prev_targets_[i], velocity_step); | ||
| } | ||
| if (safety.enable_delta_limit) { | ||
| target = clampStep(target, prev_targets_[i], std::abs(safety.delta_max)); | ||
| } | ||
| if (safety.enable_low_pass) { | ||
| if (!safety.enable_trapezoidal_limit && safety.enable_low_pass) { | ||
| target = applyLowPass(target, prev_targets_[i], safety.low_pass_alpha); | ||
| } | ||
| } | ||
|
|
@@ -209,6 +276,7 @@ | |
| } | ||
| next_targets[i] = target; | ||
|
|
||
| // Build MotorCmd: calibrate, branch MIT vs. position-loop encoding, save state for next tick. | ||
| const double calibrated_deg = applyCalibration(target, joints_[i]); | ||
|
|
||
| common_msgs::msg::MotorCmd cmd; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,16 @@ class CanNode : public rclcpp::Node { | |
| void loadMitProfiles(); | ||
| static uint32_t packMitValue(double phys, double min, double max, unsigned bits); | ||
|
|
||
| // POSITION_VELOCITY's PosVelSpeed/PosVelAccel are wire-encoded in ERPM (mechanical RPM * | ||
| // gear_ratio * pole_pairs), not deg/s -- see humanoid.dbc. Pole pairs (21) and gear ratio | ||
| // (9:1) below are from CubeMars's product pages, NOT the manual or a physical label -- | ||
| // verify before trusting for real motion. mit_profiles_ doubles as the "validated AK-series | ||
| // motor" gate, so GL40/unknown models are refused rather than silently mis-scaled. | ||
|
|
||
| static constexpr double kAkSeriesPolePairs = 21.0; | ||
| static constexpr double kAkSeriesGearRatio = 9.0; | ||
|
Comment on lines
+80
to
+81
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it always gonna this number for all our cubemars motors btw? If it could change in the future for other cubemars motors can note that down too
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just commented next to them. for the current motors were using though it should be constant unless someone changes them in the firmware |
||
| static double degPerSecToErpm(double deg_per_sec); | ||
|
|
||
| // Subscribers and publishers | ||
| std::unordered_map<std::string, rclcpp::SubscriptionBase::SharedPtr> _subscribers; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe need fix CI clang issue
can rebase too, maybe some clang issue is caused by other people's change that are fixed now
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just fixed