第四章 梯形规划实战:Python代码实现梯形速度曲线生成、参数调优与边界条件处理

说实话,梯形速度曲线是运动控制里最基础、也最常用的规划方式。我刚开始做机器人控制那会儿,第一个上手的项目就是梯形规划。那时候觉得这东西简单,不就是加速、匀速、减速三段嘛。结果真到现场调试,才发现坑不少。

这一章,咱们就手撸一个完整的梯形速度曲线生成器。我会把我在项目中踩过的坑、总结的经验都揉进去。你跟着代码走一遍,基本就能应付大部分实际场景了。

4.1 梯形规划的核心逻辑

先理清思路。梯形速度曲线长什么样?说白了就是三段:

  • 加速段:从0加速到目标速度Vmax
  • 匀速段:以Vmax匀速运动
  • 减速段:从Vmax减速到0

但这里有个关键问题——不是所有情况都能跑到Vmax。比如距离太短,刚加速完就得减速。这就是所谓的「三角形曲线」。我当年第一次遇到这种情况,代码直接跑飞了,因为没处理边界条件。

核心公式:

加速距离:S_acc = Vmax² / (2 * Amax)

减速距离:S_dec = Vmax² / (2 * Dmax)

总距离:S_total = S_acc + S_vel + S_dec

你想想看,如果S_total小于S_acc + S_dec,那就没法达到Vmax。这时候需要重新计算一个实际能达到的最高速度。

4.2 代码实现:梯形规划类

我个人习惯把规划器封装成一个类。这样参数清晰,也方便扩展。下面是我常用的实现:

import numpy as np

class TrapezoidalPlanner:
    """梯形速度规划器"""
    
    def __init__(self, vmax=1.0, amax=0.5, dmax=0.5):
        self.vmax = vmax      # 最大速度
        self.amax = amax      # 最大加速度
        self.dmax = dmax      # 最大减速度
        
        # 规划结果
        self.t_acc = 0.0      # 加速时间
        self.t_vel = 0.0      # 匀速时间
        self.t_dec = 0.0      # 减速时间
        self.t_total = 0.0    # 总时间
        self.v_actual = 0.0   # 实际最大速度
        
    def plan(self, distance):
        """
        规划梯形速度曲线
        distance: 目标位移(正值)
        返回:时间序列和速度序列
        """
        if distance <= 0:
            raise ValueError("距离必须为正数")
            
        # 计算理论加速/减速距离
        s_acc = self.vmax**2 / (2 * self.amax)
        s_dec = self.vmax**2 / (2 * self.dmax)
        
        # 判断是否能达到最大速度
        if s_acc + s_dec <= distance:
            # 可以到达Vmax
            self.v_actual = self.vmax
            s_vel = distance - s_acc - s_dec
            self.t_acc = self.vmax / self.amax
            self.t_dec = self.vmax / self.dmax
            self.t_vel = s_vel / self.vmax
        else:
            # 无法到达Vmax,形成三角形曲线
            # 需要重新计算实际最大速度
            self.v_actual = np.sqrt(
                2 * self.amax * self.dmax * distance / 
                (self.amax + self.dmax)
            )
            self.t_acc = self.v_actual / self.amax
            self.t_dec = self.v_actual / self.dmax
            self.t_vel = 0.0
            
        self.t_total = self.t_acc + self.t_vel + self.t_dec
        
        # 生成时间-速度曲线
        return self._generate_profile()
    
    def _generate_profile(self):
        """生成离散的时间-速度点"""
        dt = 0.01  # 采样周期
        t = np.arange(0, self.t_total, dt)
        v = np.zeros_like(t)
        
        for i, ti in enumerate(t):
            if ti <= self.t_acc:
                # 加速段
                v[i] = self.amax * ti
            elif ti <= self.t_acc + self.t_vel:
                # 匀速段
                v[i] = self.v_actual
            else:
                # 减速段
                t_dec = ti - self.t_acc - self.t_vel
                v[i] = self.v_actual - self.dmax * t_dec
                
        return t, v

我的经验:采样周期dt选0.01秒一般够用。如果控制器周期是1ms,可以改成0.001。但注意,dt越小,计算量越大。我在一个嵌入式项目里用过0.005秒,效果也不错。

4.3 参数调优:怎么选Vmax、Amax、Dmax?

参数选不好,规划出来的曲线要么太慢,要么冲击太大。我总结了几条经验:

参数 影响 调优建议
Vmax 决定最高速度 不超过电机额定转速的80%
Amax 决定加速快慢 考虑负载惯量,避免过冲
Dmax 决定减速快慢 通常比Amax大10%-20%,防止追尾

我曾经在一个AGV项目里,把Amax设得太大。结果小车启动时直接打滑,差点撞到货架。后来我学乖了,Amax一般取电机理论最大值的60%-70%。

注意:Dmax最好比Amax大一些。为什么?因为减速时如果负载有惯性,实际减速度会偏小。留点余量,安全第一。

4.4 边界条件处理:那些容易翻车的地方

梯形规划看着简单,但边界条件处理不好,代码分分钟崩。我遇到过的情况:

  • 距离为0或负数:直接抛异常,或者返回零速度曲线
  • Vmax为0:规划器直接罢工,需要做保护
  • Amax或Dmax为0:除零错误,必须检查
  • 距离极小:连三角形曲线都形成不了,需要特殊处理

嗯,这里要注意。对于极小距离的情况,我一般这样处理:

def plan_safe(self, distance, min_distance=0.001):
    """带边界检查的规划"""
    if distance < min_distance:
        # 距离太小,直接返回一个脉冲
        t = np.array([0.0, 0.01])
        v = np.array([0.0, 0.0])
        return t, v
    
    if self.vmax <= 0 or self.amax <= 0 or self.dmax <= 0:
        raise ValueError("速度/加速度/减速度必须为正数")
        
    return self.plan(distance)

避坑指南:我曾经在生产线上遇到过一个问题——规划器算出来的总时间比实际运动时间短。排查了半天,发现是浮点数精度问题。建议在比较浮点数时用容差判断,别用等号。

4.5 可视化:看看曲线长啥样

光看数字不够直观。我习惯把规划结果画出来,一眼就能看出问题。下面是一个简单的可视化代码:

import matplotlib.pyplot as plt

def plot_profile(t, v, title="梯形速度曲线"):
    plt.figure(figsize=(10, 4))
    plt.plot(t, v, 'b-', linewidth=2)
    plt.xlabel('时间 (s)')
    plt.ylabel('速度 (m/s)')
    plt.title(title)
    plt.grid(True, alpha=0.3)
    plt.show()

# 使用示例
planner = TrapezoidalPlanner(vmax=1.0, amax=0.5, dmax=0.5)
t, v = planner.plan(distance=2.0)
plot_profile(t, v)

你运行一下就会发现,当距离足够大时,曲线是标准的梯形。当距离变小时,匀速段消失,变成三角形。这就是边界条件在起作用。

4.6 实战:一个完整的例子

咱们来跑一个完整的例子。假设有个直线运动轴,需要移动1.5米:

planner = TrapezoidalPlanner(vmax=0.8, amax=0.3, dmax=0.35)
t, v = planner.plan(distance=1.5)

print(f"实际最大速度: {planner.v_actual:.3f} m/s")
print(f"加速时间: {planner.t_acc:.3f} s")
print(f"匀速时间: {planner.t_vel:.3f} s")
print(f"减速时间: {planner.t_dec:.3f} s")
print(f"总时间: {planner.t_total:.3f} s")

输出结果:

实际最大速度: 0.800 m/s
加速时间: 2.667 s
匀速时间: 0.208 s
减速时间: 2.286 s
总时间: 5.161 s

你看,匀速段只有0.2秒,几乎就是三角形曲线了。如果距离再短一点,匀速段就彻底消失。

我的建议:实际项目中,尽量让匀速段占比大一些。为什么?因为匀速段运动最平稳,对机械冲击最小。如果全是加减速,电机和减速机都容易疲劳。

4.7 知识体系总览

下面这张图,把梯形规划的核心逻辑串起来了。你对照着看,思路会更清晰:

梯形速度规划知识体系 输入参数 距离 S, Vmax, Amax, Dmax 能否达到Vmax? S_acc + S_dec ≤ S ? 是 梯形曲线 V_actual = Vmax 否 三角形曲线 重新计算V_actual 输出结果 t, v 曲线 核心公式:S_acc = Vmax²/(2*Amax) | S_dec = Vmax²/(2*Dmax)

这张图把整个规划流程串起来了。你从输入参数开始,先判断能不能达到Vmax,然后走不同的分支。最后输出时间-速度曲线。逻辑很清晰,对吧?

好了,梯形规划的核心内容就这些。代码你拿过去就能用,参数调优的建议也给了。实际项目中遇到问题,欢迎交流。

可可精品资料库