4、梯形加减速算法:梯形曲线数学模型、速度规划、位置计算、C语言实现与参数调优
各位工程师朋友,咱们今天聊聊步进电机驱动里最经典、也最实用的一个算法——梯形加减速。说实话,我入行那会儿,第一个独立负责的项目就是搞一个贴片机的送料轴。当时不懂什么加减速,直接给电机发固定频率的脉冲,结果呢?电机要么丢步,要么震动得像要散架。后来才明白,没有速度规划,步进电机就是个“倔驴”。
梯形加减速,说白了就是让电机启动时慢慢加速,快到目标速度时匀速跑,快停的时候再慢慢减速。这个曲线画出来像个梯形,所以叫这名。它不像S形曲线那么平滑,但胜在简单、计算量小、容易调。在大多数工业场景里,它完全够用。
4.1 梯形曲线的数学模型
我们先看数学。梯形曲线分三个阶段:加速段、匀速段、减速段。
假设我们设定:
- 起始速度 \( v_0 \)(通常为0)
- 目标速度 \( v_{max} \)
- 加速度 \( a \)(正值)
- 减速度 \( d \)(正值,通常等于 \( a \))
- 总位移 \( S \)
那么加速段时间 \( t_a = \frac{v_{max} - v_0}{a} \),减速段时间 \( t_d = \frac{v_{max} - v_0}{d} \)。
这里有个关键点:并不是所有运动都能达到目标速度。如果总位移太短,电机刚加速就要减速,形成“三角形”曲线。我遇到过不少新手,上来就设一个很高的目标速度,结果电机根本没时间跑起来,反而更慢。
核心公式:
加速段位移:\( S_a = v_0 t_a + \frac{1}{2} a t_a^2 \)
匀速段位移:\( S_c = v_{max} \times t_c \)
减速段位移:\( S_d = v_{max} t_d - \frac{1}{2} d t_d^2 \)
总位移:\( S = S_a + S_c + S_d \)
嗯,这里要注意,如果 \( S_a + S_d > S \),说明没有匀速段。这时候需要重新计算一个实际能达到的最高速度 \( v_{peak} \)。
4.2 速度规划:什么时候加速,什么时候减速
速度规划的核心就一句话:根据剩余距离决定当前该加速还是减速。
我个人习惯用“查表法”来做速度规划。提前算好每个脉冲周期对应的速度值,存成一个数组。运行时直接查表,省去实时计算的开销。这在资源有限的MCU上特别实用。
举个例子,假设我们要走1000步,目标速度是2000步/秒,加速度是500步/秒²。那么:
- 先算加速到2000步/秒需要4秒,对应4000步?不对,这里单位要统一。实际上步进电机的速度单位是“步/秒”,加速度是“步/秒²”。
- 加速段步数 = \( \frac{v_{max}^2}{2a} = \frac{2000^2}{2 \times 500} = 4000 \)步。
- 总步数才1000步,显然达不到2000步/秒。所以实际最高速度要重新算。
这种“先判断能不能达到目标速度”的步骤,我建议放在运动开始前做。我在一个项目中吃过亏,运行时才判断,结果每次加减速都有延迟,导致定位不准。
避坑指南:我曾经在速度规划时忽略了“减速度不等于加速度”的情况。比如有些负载在减速时惯性大,需要更小的减速度。如果你设的减速度太大,电机可能会过冲。所以,我建议把加速度和减速度分开配置,别偷懒。
4.3 位置计算:每个脉冲该什么时候发
位置计算,说白了就是决定每个脉冲的间隔时间。步进电机每收到一个脉冲,就走一步。所以速度越快,脉冲间隔越短。
梯形加减速的位置计算,通常用“定时器比较法”。我们用一个定时器产生PWM波,每个PWM周期更新一次比较值。比较值就是当前速度对应的定时器计数值。
公式很简单:
定时器计数值 = 定时器时钟频率 / 当前速度
比如定时器时钟是1MHz,当前速度是1000步/秒,那么计数值就是1000。也就是说,每1000个时钟周期发一个脉冲。
在加速阶段,每次更新时,计数值逐渐减小(速度变快)。在减速阶段,计数值逐渐增大(速度变慢)。匀速阶段,计数值保持不变。
这里有个细节:计数值必须是整数。所以实际速度会有量化误差。我一般建议用“累加余数法”来补偿。就是每次计算时,把小数部分累加起来,累到超过1就多减一个计数值。这样平均速度更准。
4.4 C语言实现:一个可用的梯形加减速模块
下面我给出一段我常用的梯形加减速代码框架。它不依赖具体硬件,只做逻辑计算。
// 梯形加减速状态机
typedef enum {
ACCEL, // 加速
CRUISE, // 匀速
DECEL, // 减速
STOP // 停止
} MotionState;
typedef struct {
int32_t target_pos; // 目标位置(步数)
int32_t current_pos; // 当前位置
float speed; // 当前速度(步/秒)
float max_speed; // 目标速度
float accel; // 加速度(步/秒²)
float decel; // 减速度
float step_per_sec; // 定时器频率
MotionState state; // 当前状态
} TrapezoidMotion;
// 初始化
void Trapezoid_Init(TrapezoidMotion *m, float max_speed, float accel, float decel) {
m->max_speed = max_speed;
m->accel = accel;
m->decel = decel;
m->speed = 0;
m->current_pos = 0;
m->state = STOP;
}
// 每次定时器中断调用此函数,返回下一个脉冲的定时器计数值
uint32_t Trapezoid_Update(TrapezoidMotion *m) {
if (m->state == STOP) return 0;
// 计算剩余距离
int32_t remaining = m->target_pos - m->current_pos;
// 判断是否需要减速
float decel_dist = (m->speed * m->speed) / (2 * m->decel);
if (remaining <= decel_dist && m->state != DECEL) {
m->state = DECEL;
}
// 根据状态更新速度
switch (m->state) {
case ACCEL:
m->speed += m->accel / m->step_per_sec;
if (m->speed >= m->max_speed) {
m->speed = m->max_speed;
m->state = CRUISE;
}
break;
case CRUISE:
// 速度不变
break;
case DECEL:
m->speed -= m->decel / m->step_per_sec;
if (m->speed <= 0) {
m->speed = 0;
m->state = STOP;
return 0;
}
break;
default:
break;
}
// 更新位置
m->current_pos++;
// 计算定时器计数值
if (m->speed > 0) {
return (uint32_t)(m->step_per_sec / m->speed);
} else {
return 0;
}
}
这段代码我用了很多年,核心逻辑没变过。你可能会问:为什么不用浮点数?嗯,这里用浮点数是为了清晰。实际产品中,我建议全部转成定点数,避免浮点运算的开销。
4.5 参数调优:让电机跑得又稳又快
参数调优是梯形加减速里最考验经验的部分。我总结了几条原则:
| 参数 | 调大影响 | 调小影响 | 我的建议 |
|---|---|---|---|
| 加速度 | 启动快,但可能丢步 | 启动慢,但运行平稳 | 从电机额定扭矩的80%开始试 |
| 目标速度 | 效率高,但震动大 | 效率低,但噪音小 | 不要超过电机自振频率的1/3 |
| 减速度 | 刹车快,但可能过冲 | 刹车慢,定位时间长 | 通常比加速度小10%-20% |
调优步骤我一般这样走:
- 先调加速度:从较小的值开始,逐渐增大,直到电机出现丢步或异响,然后回调10%。
- 再调目标速度:在加速度确定后,逐步提高目标速度,观察电机震动和噪音。找到那个“临界点”。
- 最后调减速度:让电机跑一段长距离,看停止位置是否准确。如果过冲,就减小减速度。
警告:千万不要在电机运行时修改加减速参数!我见过有人为了“动态调整”,在运动过程中改了加速度,结果电机直接失步,撞坏了机械限位。要改参数,先让电机停下来。
另外,有个小技巧:用示波器看电机电流波形。如果电流波形在加速阶段出现“塌陷”,说明加速度太大,电机扭矩不够。如果波形平滑,说明参数合理。这个方法比听声音更靠谱。
好了,梯形加减速就讲到这里。下节课我们聊聊更平滑的S形加减速,那个曲线更优雅,但计算量也更大。到时候我会对比两者的适用场景,帮你做选择。