Files
StackChan/firmware/main/stackchan/motion/servo.cpp
T
2026-03-25 11:11:14 +08:00

157 lines
4.2 KiB
C++

/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#include "servo.h"
#include <hal/hal.h>
using namespace uitk;
namespace stackchan::motion {
static SpringOptions_t _default_spring_options = {
.stiffness = 170.0,
.damping = 26.0,
.mass = 1.0,
.velocity = 0.0,
.restSpeed = 0.1,
.restDelta = 0.1,
.duration = 0.0,
.bounce = 0.0,
.visualDuration = 0.0,
};
void Servo::init()
{
apply_default_spring_options();
_angle_anim.teleport(getCurrentAngle());
update();
setTorqueEnabled(false);
}
void Servo::update()
{
// Keep update in at most 50Hz
if (GetHAL().millis() - _last_tick < 20) {
return;
}
_last_tick = GetHAL().millis();
// Apply animation
if (!_angle_anim.done()) {
_angle_anim.updateWithDelta(0.02f); // Fixed delta time for consistency
set_angle_impl(static_cast<int>(_angle_anim.directValue()));
}
// Snap to target angle when animation ends
else if (_snap_to_target_on_rest) {
_snap_to_target_on_rest = false;
set_angle_impl(_angle_anim.end);
}
// Auto release torque on rest
else if (_auto_torque_release_enabled && !isMoving()) {
if (GetHAL().millis() - _last_torque_check_tick > 200) {
if (getTorqueEnabled()) {
setTorqueEnabled(false);
}
_last_torque_check_tick = GetHAL().millis();
}
}
}
void Servo::move(int angle)
{
apply_default_spring_options();
update_angle_anim_target(angle);
}
void Servo::moveWithSpringParams(int angle, float stiffness, float damping)
{
_angle_anim.springOptions().visualDuration = 0.0f; // Disable timing override
_angle_anim.springOptions().stiffness = stiffness;
_angle_anim.springOptions().damping = damping;
update_angle_anim_target(angle);
}
void Servo::moveWithSpeed(int angle, int speed)
{
auto spring_options = map_speed_to_spring_options(speed);
moveWithSpringParams(angle, spring_options.stiffness, spring_options.damping);
}
int Servo::getCurrentAngle()
{
return _angle_anim.directValue();
}
bool Servo::isMoving()
{
return _angle_anim.done() == false || is_moving_impl();
}
void Servo::apply_default_spring_options()
{
auto& options = _angle_anim.springOptions();
options.visualDuration = 0.0f; // Disable timing override
options.stiffness = _default_spring_options.stiffness;
options.damping = _default_spring_options.damping;
}
void Servo::update_angle_anim_target(int angle)
{
angle = uitk::clamp(angle, _angle_limit.x, _angle_limit.y);
if (_auto_angle_sync_enabled) {
_angle_anim.teleport(getCurrentAngle()); // Use current angle as start
}
_angle_anim = angle; // Apply new target
_snap_to_target_on_rest = true;
}
uitk::SpringOptions_t Servo::map_speed_to_spring_options(int speed)
{
speed = uitk::clamp(speed, 0, 1000);
// 1. 计算 Stiffness (刚度)
// 使用二次方映射: k = k_min + (speed/1000)^2 * k_range
// 当 speed=500 时,k 约为 10 + 0.25 * 640 = 170
float k_min = 10.0f;
float k_max = 650.0f;
float normalizedSpeed = speed / 1000.0f;
float stiffness = k_min + (normalizedSpeed * normalizedSpeed) * (k_max - k_min);
// 2. 计算 Damping (阻尼)
// 为了保持临界阻尼(无过冲,最快稳定),公式为 d = 2 * sqrt(m * k)
// 如果想要带一点点弹性感(bounce),可以将系数从 2.0 降到 1.5~1.8
float mass = 1.0f;
float damping = 2.0f * sqrtf(mass * stiffness);
// 3. 构造选项
uitk::SpringOptions_t options = _default_spring_options;
options.stiffness = stiffness;
options.damping = damping;
options.mass = mass;
// 4. 动态调整静止阈值
// 高速时阈值大一点可以防止由于离散计算导致的微小抖动
if (speed > 800) {
options.restDelta = 0.5f;
options.restSpeed = 0.5f;
} else {
options.restDelta = 0.1f;
options.restSpeed = 0.1f;
}
return options;
}
} // namespace stackchan::motion