当你接到一个控制任务,比如需要控制电机的转速,并支持动态快速调整转速,电机的转速可以实时获取。然后开始网上一顿搜索,搜索结果大致如下所述。
在自动控制领域中,PID 控制算法是一种非常常见且有效的控制算法,用于实现闭环控制系统中的精确控制。PID 控制器由三个组成部分构成:比例 (P)、积分 (I) 和微分 (D)。每个部分都有其特定的功能,共同作用于系统的输出以达到期望的设定值。
PID 控制算法的基本公式如下:
其中:
( u(t) ) 表示控制器的输出。
( e(t) ) 表示误差信号,即设定值与实际值之间的差值。
( K_p ) 是比例增益。
( K_i ) 是积分增益。
( K_d ) 是微分增益。
各组成部分的作用
1.比例项 (P):
•比例项直接与误差成正比。
•它可以快速响应误差的变化。
•比例项的系数 ( K_p ) 影响系统的响应速度和稳定性。
2.积分项 (I):
•积分项随着时间累积误差。
•它有助于消除静态误差(即当系统稳定时仍存在的误差)。
•积分项的系数 ( K_i ) 影响系统的最终精度。
3.微分项 (D):
•微分项考虑误差的变化率。
•它可以预测未来的误差变化趋势,并提前做出调整。
•微分项的系数 ( K_d ) 影响系统的动态性能,尤其是抗扰动的能力。
看完上述理论公式是否有点不知所措?要实际使用要怎么实现呢?多年前我曾经按照上述公式,写过代码并调试,但实际工程使用效果并不理想(很容易过冲,也就是加速时电机转速容易超过设定转速),参数比较难确定,而且如果换了电机需要重调参数。(这里也可能是我没有找到调试的最优方案,感兴趣的同学可以深入研究,评论区交流)
言归正传,后来又有幸接触到一个项目需要控制电机转速,就换了设计思路,大致流程是:
Step1 获取到当前的实时转速和当前设置的PWM占空比(一般调节速度使用PWM)
Step2 比较当前转速和目标转速的大小,如当前转速小于目标转速则进入加速流程。反之则进入减速流程
加速流程:
计算当前转速和目标转速相差的绝对值,根据不同的绝对值区间,再当前占空比数值上调大不同数值。
减速流程:
计算当前转速和目标转速相差的绝对值,根据不同的绝对值区间,再当前占空比数值上调小不同数值。
以下是调速的核心代码片段,工程实测可用,而且调速比较平滑,前提是需要以最快速度轮询。
注,以下duty取值范围是0~10000,需要换算到占空比的0%~100%
{
//轮询所有需要调节的电机通道
for(i=0;i<PWM_MAX_CHN_NUM;i++)
{
{
uint16_t CurDuty; //当前占空比
//等待获取当前实际转速
while(Bsp_WheelSpeedGet(i, &RpmList[i]) == FALSE);
CurDuty = HalPwmDutyGet(i);
{
uint16_t CurDuty;
uint16_t DevSpeed;
//CarWeelSpeedList是各通道目标转速
if(RpmList[i] <= CarWeelSpeedList[i])//加速状态
{
DevSpeed = CarWeelSpeedList[i] - RpmList[i]
//速度调节完成
if(DevSpeed < CAR_DEV_BUT_OK_SPEED) {
continue;
}
//根据速度差距采取不同的速度调整策略
if(DevSpeed <= 100)
{
bspPwmDutySet(i,CurDuty+1);
}
else if(DevSpeed <= 500)
{
bspPwmDutySet(i,CurDuty+10);
}
else if(DevSpeed <= 1000)
{
bspPwmDutySet(i,CurDuty+100);
}
else if(DevSpeed <= 2000)
{
bspPwmDutySet(i,CurDuty+200);
}
else
{
bspPwmDutySet(i,CurDuty+300);
}
}
else //加速过快导致速度超过目标速度 或 减速
{
DevSpeed = RpmList[i] - CarWeelSpeedList[i]; //速度调节完成
if((DevSpeed < CAR_DEV_BUT_OK_SPEED) || (CurDuty == 0))
{
//目标速度为0
if(CarWeelSpeedList[i] == 0)
{
bspPwmDutySet(i,0);
}
continue;
}
if(DevSpeed <= 120)
{
if(CurDuty)
{
bspPwmDutySet(i,CurDuty-1);
}
//此处有可能让占空比为0
if(CarWeelSpeedList[i] == 0)//目标速度为0
{
bspPwmDutySet(i,0);
}
}
else if(DevSpeed <= 500) //速度差距较小
{
if(CurDuty > 10)
{
bspPwmDutySet(i,CurDuty-10);
}
}
else if(DevSpeed <= 1000)
{
if(CurDuty > 100)
{
bspPwmDutySet(i,CurDuty-100);
}
else
{
bspPwmDutySet(i,(CurDuty >> 1)+10);
}
}
else //速度差距较大
{
if(CurDuty > 300)
{
bspPwmDutySet(i,CurDuty-300);
}
else if(CurDuty > 200)
{
bspPwmDutySet(i,CurDuty-200);
}
else if(CurDuty > 100)
{
bspPwmDutySet(i,CurDuty-100);
}
else if(CurDuty > 50)
{
bspPwmDutySet(i,CurDuty-50);
}
else
{
if(CurDuty > 10)
{
bspPwmDutySet(i,CurDuty-10);
}
else
{
bspPwmDutySet(i,(CurDuty >> 1)+1);
}
}
}
}
}
}
}
}
以上代码中,前提条件是系统任务不能太重,上叙流程需要以每10ms左右轮询一次。
【创作不易,欢迎转载,转载请注明出处】
如果大家对相关文章感兴趣,可以关注公众号“嵌入式毛哥”,持续分享嵌入式干货,不限于疑难故障分析解决、算法共享、开发设计思想等,志在帮助更多人在嵌入式行业发光发热。