Odrive源码分析(四) 位置爬坡算法

Odrive中自带一个简单的梯形速度爬坡算法,本文分析下这部分代码。

代码如下:

#include <cmath>
#include "odrive_main.h"
#include "utils.hpp"

// A sign function where input 0 has positive sign (not 0)
float sign_hard(float val) {
    return (std::signbit(val)) ? -1.0f : 1.0f;
}

// Symbol                     Description
// Ta, Tv and Td              Duration of the stages of the AL profile
// Xi and Vi                  Adapted initial conditions for the AL profile
// Xf                         Position set-point
// s                          Direction (sign) of the trajectory
// Vmax, Amax, Dmax and jmax  Kinematic bounds
// Ar, Dr and Vr              Reached values of acceleration and velocity

bool TrapezoidalTrajectory::planTrapezoidal(float Xf, float Xi, float Vi,
                                            float Vmax, float Amax, float Dmax) {
    float dX = Xf - Xi;  // Distance to travel
    float stop_dist = (Vi * Vi) / (2.0f * Dmax); // Minimum stopping distance
    float dXstop = std::copysign(stop_dist, Vi); // Minimum stopping displacement
    float s = sign_hard(dX - dXstop); // Sign of coast velocity (if any)
    Ar_ = s * Amax;  // Maximum Acceleration (signed)
    Dr_ = -s * Dmax; // Maximum Deceleration (signed)
    Vr_ = s * Vmax;  // Maximum Velocity (signed)

    // If we start with a speed faster than cruising, then we need to decel instead of accel
    // aka "double deceleration move" in the paper
    if ((s * Vi) > (s * Vr_)) {
        Ar_ = -s * Amax;
    }

    // Time to accel/decel to/from Vr (cruise speed)
    Ta_ = (Vr_ - Vi) / Ar_;
    Td_ = -Vr_ / Dr_;

    // Integral of velocity ramps over the full accel and decel times to get
    // minimum displacement required to reach cuising speed
    float dXmin = 0.5f*Ta_*(Vr_ + Vi) + 0.5f*Td_*Vr_;

    // Are we displacing enough to reach cruising speed?
    if (s*dX < s*dXmin) {
        // Short move (triangle profile)
        Vr_ = s * std::sqrt(std::max((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_), 0.0f));
        Ta_ = std::max(0.0f, (Vr_ - Vi) / Ar_);
        Td_ = std::max(0.0f, -Vr_ / Dr_);
        Tv_ = 0.0f;
    } else {
        // Long move (trapezoidal profile)
        Tv_ = (dX - dXmin) / Vr_;
    }

    // Fill in the rest of the values used at evaluation-time
    Tf_ = Ta_ + Tv_ + Td_;
    Xi_ = Xi;
    Xf_ = Xf;
    Vi_ = Vi;
    yAccel_ = Xi + Vi*Ta_ + 0.5f*Ar_*SQ(Ta_); // pos at end of accel phase

    return true;
}

TrapezoidalTrajectory::Step_t TrapezoidalTrajectory::eval(float t) {
    Step_t trajStep;
    if (t < 0.0f) {  // Initial Condition
        trajStep.Y   = Xi_;
        trajStep.Yd  = Vi_;
        trajStep.Ydd = 0.0f;
    } else if (t < Ta_) {  // Accelerating
        trajStep.Y   = Xi_ + Vi_*t + 0.5f*Ar_*SQ(t);
        trajStep.Yd  = Vi_ + Ar_*t;
        trajStep.Ydd = Ar_;
    } else if (t < Ta_ + Tv_) {  // Coasting
        trajStep.Y   = yAccel_ + Vr_*(t - Ta_);
        trajStep.Yd  = Vr_;
        trajStep.Ydd = 0.0f;
    } else if (t < Tf_) {  // Deceleration
        float td     = t - Tf_;
        trajStep.Y   = Xf_ + 0.5f*Dr_*SQ(td);
        trajStep.Yd  = Dr_*td;
        trajStep.Ydd = Dr_;
    } else if (t >= Tf_) {  // Final Condition
        trajStep.Y   = Xf_;
        trajStep.Yd  = 0.0f;
        trajStep.Ydd = 0.0f;
    } else {
        // TODO: report error here
    }

    return trajStep;
}

首先当需要控制电机运动到某个位置时,会调用函数,该函数会调用上面的函数planTrapezoidal。

void Controller::move_to_pos(float goal_point) {
    axis_->trap_traj_.planTrapezoidal(goal_point, pos_setpoint_, vel_setpoint_,
                                 axis_->trap_traj_.config_.vel_limit,
                                 axis_->trap_traj_.config_.accel_limit,
                                 axis_->trap_traj_.config_.decel_limit);
    axis_->trap_traj_.t_ = 0.0f;
    trajectory_done_ = false;
}

然后会在control对象中调用eval函数不断的计算出下一时刻的目标位置和速度。

        case INPUT_MODE_TRAP_TRAJ: {
            if(input_pos_updated_){
                move_to_pos(input_pos_);
                input_pos_updated_ = false;
            }
            // Avoid updating uninitialized trajectory
            if (trajectory_done_)
                break;
            
            if (axis_->trap_traj_.t_ > axis_->trap_traj_.Tf_) {
                // Drop into position control mode when done to avoid problems on loop counter delta overflow
                config_.control_mode = CONTROL_MODE_POSITION_CONTROL;
                pos_setpoint_ = axis_->trap_traj_.Xf_;
                vel_setpoint_ = 0.0f;
                torque_setpoint_ = 0.0f;
                trajectory_done_ = true;
            } else {
                TrapezoidalTrajectory::Step_t traj_step = axis_->trap_traj_.eval(axis_->trap_traj_.t_);
                pos_setpoint_ = traj_step.Y;
                vel_setpoint_ = traj_step.Yd;
                torque_setpoint_ = traj_step.Ydd * config_.inertia;
                axis_->trap_traj_.t_ += current_meas_period;
            }

那么关键就是两个函数planTrapezoidal和函数eval,当位置更新时调用前者,周期性调用后者,后者的输出更新到位置闭环和速度闭环中实现轨迹跟随。

planTrapezoidal代码分析如下:

    //计算出加速阶段和减速阶段需要的事件
    Ta_ = (Vr_ - Vi) / Ar_;
    Td_ = -Vr_ / Dr_;

    //如果能跑到最大速度,那么计算加速阶段和减速阶段运行的位移
    float dXmin = 0.5f*Ta_*(Vr_ + Vi) + 0.5f*Td_*Vr_;

    if (s*dX < s*dXmin) {
        //如果是短位移,这里算出三角规划的速度,这里看下面的公式推导
        Vr_ = s * std::sqrt(std::max((Dr_*SQ(Vi) + 2*Ar_*Dr_*dX) / (Dr_ - Ar_), 0.0f));

        //重新计算加速时间和减速时间,匀速阶段为0
        Ta_ = std::max(0.0f, (Vr_ - Vi) / Ar_);
        Td_ = std::max(0.0f, -Vr_ / Dr_);
        Tv_ = 0.0f;
    } else {
        //如果是长位移,那么走梯形速度,这里得出匀速阶段的时间
        Tv_ = (dX - dXmin) / Vr_;
    }

    //计算出本次规划需要的总时间
    Tf_ = Ta_ + Tv_ + Td_;
    Xi_ = Xi;
    Xf_ = Xf;
    Vi_ = Vi;

    //计算加速阶段结束时的位置,即加速阶段的位移。
    yAccel_ = Xi + Vi*Ta_ + 0.5f*Ar_*SQ(Ta_); 
  • 三角规划公式推导如下:

eval代码分析如下:

    if (t < 0.0f) {  //初始条件,不会进入
        trajStep.Y   = Xi_;
        trajStep.Yd  = Vi_;
        trajStep.Ydd = 0.0f;
    } else if (t < Ta_) {  //加速阶段
        trajStep.Y   = Xi_ + Vi_*t + 0.5f*Ar_*SQ(t);  //按照加速阶段计算当前时刻的位置
        trajStep.Yd  = Vi_ + Ar_*t;   //一阶导数,即当前时刻的速度
        trajStep.Ydd = Ar_;           //二阶导数即当前时刻的加速度
    } else if (t < Ta_ + Tv_) {  //匀速阶段
        trajStep.Y   = yAccel_ + Vr_*(t - Ta_);  //按照匀速阶段计算当前时刻的位置
        trajStep.Yd  = Vr_;     //一阶导数,即当前时刻的速度
        trajStep.Ydd = 0.0f;    //二阶导数即当前时刻的加速度为0
    } else if (t < Tf_) {  //减速阶段
        float td     = t - Tf_;
        trajStep.Y   = Xf_ + 0.5f*Dr_*SQ(td);  //按照减速阶段计算当前时刻的位置
        trajStep.Yd  = Dr_*td;   //一阶导数,即当前时刻的速度
        trajStep.Ydd = Dr_;      //二阶导数即当前时刻的减速度
    } else if (t >= Tf_) {  //规划完成
        trajStep.Y   = Xf_;    
        trajStep.Yd  = 0.0f; 
        trajStep.Ydd = 0.0f;
    } else {
        // TODO: report error here
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/941513.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电视大全 1.3.8|汇聚多频道资源,秒切换流畅播放

电视大全TV版是一款功能丰富的TV端直播软件&#xff0c;由悠兔电视的同一开发者打造。最新版本更新了更多频道&#xff0c;包括央视、卫视和地方频道等&#xff0c;提供了多线路流畅播放体验&#xff0c;并支持节目回看、线路选择、开机自启等功能。该应用免登录且无购物频道&a…

JAVAweb学习日记(二)JavaScript

一、概念 二、JavaScript引入方式 三、JavaScript书写语法 输出语句&#xff1a; 变量&#xff1a; 数据类型、运算符、流程控制语句&#xff1a; 数据类型&#xff1a; 运算符&#xff1a; 字符串如果是 数字字符构成&#xff0c;先把读到的数字转为数字类型&#xff0c;后续…

深圳龙岗戴尔dell r730xd服务器故障维修

深圳龙岗一台DELL POWEREDGE R730XD服务器系统故障问题处理&#xff1a; 1&#xff1a;客户工厂年底产线整改&#xff0c;时不时的会意外断电&#xff0c;导致服务器也频繁停机&#xff0c; 2&#xff1a;多次异常停机后导致服务器开机后windows server系统无法正常启动了&…

Ansible 批量管理华为 CE 交换机

注&#xff1a;本文为 “Ansible 管理华为 CE 交换机” 相关文章合辑。 使用 CloudEngine - Ansible 批量管理华为 CE 交换机 wsf535 IP 属地&#xff1a;贵州 2018.02.05 15:26:05 总体介绍 Ansible 是一个开源的自动化运维工具&#xff0c;AnsibleWorks 成立于 2012 年&a…

2024年Python最新下载安装教程,附详细图文,持续更新

大家好&#xff0c;我是Python老安&#xff0c;今天为大家带来的是Windows Python3下载、安装教程&#xff0c;适用于 Python3 所有版本&#xff0c;包括 Python3.7,Python33.8,Python33.10 等版本。希望对大家有所帮助 Python目前已支持所有主流操作系统&#xff0c;在Linux,…

《点点之歌》“意外”诞生记

世界是“点点”的&#xff0c;“点点”是世界的。 (笔记模板由python脚本于2024年12月23日 19:28:25创建&#xff0c;本篇笔记适合喜欢诗文的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”教程《 …

网络安全检测

实验目的与要求 (1) 帮助学生掌握木马和入侵的防护和检测方法、提高学习能力、应用能力和解决实际问题的能力。 (2) 要求学生掌握方法, 学会应用软件的安装和使用方法, 并能将应用结果展示出来。 实验原理与内容 入侵检测是通过对计算机网络或计算机系统中若干关键点收集信…

c++------------------函数

函数定义 语法格式 函数定义包括函数头和函数体。函数头包含返回类型、函数名和参数列表。函数体是用花括号{}括起来的代码块&#xff0c;用于实现函数的功能。例如&#xff0c;定义一个计算两个整数之和的函数&#xff1a; int add(int a, int b) {return a b; }这里int是返回…

Java WEB:从起源到现代的传奇之旅

Java Web 起源于上世纪 90 年代&#xff0c;随着网络和浏览器的飞速发展&#xff0c;Java 为应对动态处理网页的需求&#xff0c;推出了 Servlet 技术。 1. Servlet 出现之前 在 Servlet 出现之前&#xff0c;用户请求主要是静态资源&#xff0c;如 html、css 等。此时的网络…

社区管理系统:实现社区信息数字化管理的实践

3.1可行性分析 开发者在进行开发系统之前&#xff0c;都需要进行可行性分析&#xff0c;保证该系统能够被成功开发出来。 3.1.1技术可行性 开发该社区管理系统所采用的技术是vue和MYSQL数据库。计算机专业的学生在学校期间已经比较系统的学习了很多编程方面的知识&#xff0c;同…

DataV的安装与使用(Vue3版本)

1、DataV(vue3)地址&#xff1a;DataV Vue3TSVite版 | DataV - Vue3 2、使用 npm install kjgl77/datav-vue3 安装 3、全局引入。 4、此时就可以按需使用了~

隐藏指定文件/文件夹和自动提示功能消失解决方案

一. 隐藏指定文件/文件夹 Idea中隐藏指定文件或指定类型文件 Setting → File Types → Ignored Files and Folders输入要隐藏的文件名&#xff0c;支持*号通配符回车确认添加 二. 自动提示功能消失解决方案 指定SpringBoot配置文件 File → Project Structure → Facets选…

springboot474基于web的垃圾分类回收系统(论文+源码)_kaic

摘 要 如今社会上各行各业&#xff0c;都喜欢用自己行业的专属软件工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。新技术的产生&#xff0c;往往能解决一些老技术的弊端问题。因为传统垃圾分类回收系统信息管理难度大&#xff0c;容错率低&am…

最优二叉搜索树【东北大学oj数据结构10-4】C++

题面 最优二叉搜索树是由 n 个键和 n1 个虚拟键构造的二叉搜索树&#xff0c;以最小化搜索操作的成本期望值。 给定一个序列 Kk1​,k2​,...,kn​&#xff0c;其中 n 个不同的键按排序顺序 &#xff0c;我们希望构造一个二叉搜索树。 对于每个关键 ki​&#xff0c;我们有一个…

jsp-servlet开发

STS中开发步骤 建普通jsp项目过程 1.建项目&#xff08;非Maven项目&#xff09; new----project----other----Web----Dynamic Web Project 2.下载包放到LIB目录中,如果是Maven项目可以自动导包&#xff08;pom.xml中设置好&#xff09; 3.设置工作空间&#xff0c;网页…

easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层

需求&#xff1a;页面点击导出&#xff0c;先按照页面条件去数据库查询&#xff0c;然后将查询到的数据导出。 问题&#xff1a;由于查询特别耗时&#xff0c;所以点击之后页面会看上去没有反应 方案1&#xff1a;就在点击之后在页面增加了一个进度条&#xff0c;等待后端查询…

新版Android Studio 2024.1.2版本,如何通过无线wifi连接手机实现交互

1、首先&#xff0c;先确定手机是否启动了开发者选项 在我的设备 -> 全部参数 -> MIUI版本点击6下 &#xff08;有的手机是 关于手机 -> 查看手机版本 &#xff09; 2、在设置中搜索 开启开发者选项 3、进入开发者选项后&#xff0c;在 调试 中选择 无线调试并选择…

CEF127 编译指南 MacOS 篇 - 编译 CEF(六)

1. 引言 经过前面的准备工作&#xff0c;我们已经完成了所有必要的环境配置。本文将详细介绍如何在 macOS 系统上编译 CEF127。通过正确的编译命令和参数配置&#xff0c;我们将完成 CEF 的构建工作&#xff0c;最终生成可用的二进制文件。 2. 编译前准备 2.1 确认环境变量 …

扩散模型经典问题:在Image-to-Image或Image-to-Video任务中,如何尽可能地保持住原始输入Image的特征?

AIGC算法工程师 面试八股文 2025年版本 在Image-to-Image或Image-to-Video任务中,如何尽可能地保持住原始输入Image的特征?你知道有哪些经典方法?这些方法各有什么优缺点? 目录 经典条件扩散模型 垫图法 Adapter方法 ControlNet方法 UNet中的ReferenceNet DiT中的Re…

0.96寸OLED显示屏详解

我们之前讲了 LCD1602&#xff0c;今天我们将它的进阶模块——OLED。它接线更少&#xff0c;性能更强&#xff0c;也能显示中文和图像了。 大家在学习单片机的时候是否会遇到调试的问题呢&#xff1f;例如 “这串代码我到底运行成功了没有” &#xff0c;我相信很多刚开始学习…