电机驱动是很常见的应用,在很多系统中我们都会碰到需要改变电机的速度以实现相应的控制功能,这就涉及到电机速度曲线规划的问题。在这篇中我们就来简单讨论一下电机的 S 型曲线规划的问题。
1、基本原理
S 型速度曲线控制算法是工业控制领域另一种常用的加减速控制策略,S 型曲线很好的克服了 T 型曲线加速度不连续的问题。
S 型曲线实际就是实现一个加速度的 T 型变化过程,具体来说就是加速度增加、加速度恒定、将速度减小的过程。在整个速度调节规程中,加速度是连续变化的,而反映到速度的变化就是一条平滑的 S 型曲线。如下图所示:
这是比较常见的 S 曲线的形式,其函数表达式如下:
这一函数是这类函数的一个特例,其并不具有普遍性。在我们应用中,我们可能会需要根据应用的要求对 S 型曲线在横轴和纵轴两个方向平移或拉伸。所以这一函数更为普遍的描述如下:
在这一表达式中,A 表示在纵轴方向的平移,B 表示在纵轴方向的拉伸,a 表示在横轴方向的拉伸,b 表示在横轴方向的平移。具体反映到函数图形上就是如下图所示:
那么究竟如何使用这一 S 曲线函数来对电机进行调速呢?我们考虑到,所谓电机调速实际就是电机速度与运行时间之间存在一定的函数关系。很显然,纵轴就是电机速度,横轴就是运行时间。于是我们就可以得到电机的 S 型速度曲线的函数关系如下:
可能大家会发现,这个速度曲线的函数似乎与前面的数学函数有些许差别。这是为了更好的适应调速的区别。在数学上,数轴都是对称的,但在速度调节过程中,速度和时间都不可能存在负数的情况,所以我们需要对其进行平移。但是平移之后,S 曲线的图形就不对称了,所以我们以整个调速过程的调速时间的中间点为轴就是对称的了,所以就有了上述的函数表达式。
2、设计与实现
我们已经简单描述了 S 型速度规划曲线的数学原理及应用表达式。接下来我们来考虑怎么实现它。
考虑到在同一个驱动器中可能因为应用场景的需要存在多条的速度规划曲线。所以我们以基于对象的思路来考虑它,这样我们在更换不同的曲线就只需要更换不同的曲线实力就可以了。所以我们先来分析一下曲线对象的属性和操作。
鉴于前面的分析,我们认为作为一个调速曲线对象至少要记录:开始调速时的初始速度、当前速度、目标速度、加速度、最大速度、最小速度、调速时间、调速时间跨度、曲线类型以及 S 曲线拉伸度等,我们将这些记为对象的属性。据此我们可以定义电机速度曲线的对象类型为:
/* 定义电机速度曲线对象 */
typedef struct CurveObject {
float startSpeed; //开始调速时的初始速度
float currentSpeed; //当前速度
float targetSpeed; //目标速度
float stepSpeed; //加速度
float speedMax; //最大速度
float speedMin; //最小速度
uint32_t aTimes; //调速时间
uint32_t maxTimes; //调速跨度
SpeedCurveType curveMode; //曲线类型
float flexible; //S曲线拉伸度
}CurveObjectType;
我们已经定义了一个速度曲线对象类型,接下来我们就来分析如何实现一个 S 型调速曲线。我们已经描述过,速度其实就是时间的函数,根据我们前面分析的速度时间的函数表达式,我们实现如下:
void (*pCalCurve[])(CurveObjectType *curve)={CalCurveNone,CalCurveTRAP,CalCurveSPTA};
/* 电机曲线加减速操作-------------------------------------------------------- */
void MotorVelocityCurve(CurveObjectType *curve)
{
float temp=0;
if(curve->targetSpeed>curve->speedMax)
{
curve->targetSpeed=curve->speedMax;
}
if(curve->targetSpeed<curve->speedMin)
{
curve->targetSpeed=curve->speedMin;
}
if((fabs(curve->currentSpeed-curve->startSpeed)<=curve->stepSpeed)&&(curve->maxTimes==0))
{
if(curve->startSpeed<curve->speedMin)
{
curve->startSpeed=curve->speedMin;
}
temp=fabs(curve->targetSpeed-curve->startSpeed);
temp=temp/curve->stepSpeed;
curve->maxTimes=(uint32_t)(temp)+1;
curve->aTimes=0;
}
if(curve->aTimes<curve->maxTimes)
{
pCalCurve[curve->curveMode](curve);
curve->aTimes++;
}
else
{
curve->currentSpeed=curve->targetSpeed;
curve->maxTimes=0;
curve->aTimes=0;
}
}
/*S型曲线速度计算*/
static void CalCurveSPTA(CurveObjectType *spta)
{
float power=0.0;
float speed=0.0;
power=(2*((float)spta->aTimes)-((float)spta->maxTimes))/((float)spta->maxTimes);
power=(0.0-spta->flexible)*power;
speed=1+expf(power);
speed=(spta->targetSpeed-spta->startSpeed)/speed;
spta->currentSpeed=speed+spta->startSpeed;
if(spta->currentSpeed>spta->speedMax)
{
spta->currentSpeed=spta->speedMax;
}
if(spta->currentSpeed<spta->speedMin)
{
spta->currentSpeed=spta->speedMin;
}
}
在这个实现中,我们出于更普遍的实用性考虑,将各种曲线的相同操作集成在一起,然后将它们差异的部分通过曲线类型属性以回调函数的方式集成。
3、应用与验证
我们实现了 S 型电机速度规划曲线的基本设计与实现。接下来,我们就是用这一调速曲线来实现一个电机调速的实例。我们定义速度规划曲线时,是及与对象的思想来实现的,所以我们先声明一条曲线对象实例。
CurveObjectType curve; // 电机调速曲线
在声明了这一曲线对象后,我们需要对其初始化赋值才能正确的使用。大多数的属性直接根据应用对象的要求给予初始值就可以了。需要注意的是曲线类型这一属性,这将决定使用什么样的速度规划曲线。该属性为 SpeedCurveType 枚举,该枚举定义如下:
/* 定义电机速度曲线类型枚举 */
typedef enum SpeedCurve {
CURVE_NONE=0, //直启
CURVE_TRAP=1, //梯形曲线
CURVE_SPTA=2 //S型曲线
}SpeedCurveType;
在这里我们需要将曲线类型初始化为 T 形速度规划曲线。具体操作如下:
curve.curveMode=CURVE_SPTA;
初始化完成之后就可以使用曲线对象来实现电机速度的调节了。使用也很简单,只要按一定的时间周期调用我们前面实现的 MotorVelocityCurve 函数就可以实现整个调速过程。具体如下:
MotorVelocityCurve(&curve);
在每次调速开始之前都需要设置取下的开始速度和目标速度,这样函数就会按照设定的起始速度和目标速度实现速度调整。
4、小结
S 型速度曲线将整个运动过程划分为 7 个阶段,即加加速度段、匀加速度段、减加速度段、匀速段、加减速度段、匀减速度段和减减速度段,不同阶段速度衔接处加速度是连续的,且加速度的变化率可控,克服了梯形速度曲线中存在的加速度突变的不利影响,这是 S 型曲线的一大优势。
但是相比于 T 型速度曲线,S 型曲线的计算量要大很多,实现起来也比 T 型曲线更为复杂。不过这些在现在的处理系统中都不再是问题。在使用过程中需要关注参数设定,特别是拉伸系数,必须根据应用场合需要仔细调整。如果太小,中间调速过程会变平坦,起始和结束阶段则会相对变化较快;如果太大,其实和结束过程会变得比较平坦,但中间部分会变化较快。具体就要看应用需求了。
实际demo测试图
https://download.csdn.net/download/jiesunliu3215/89355313