电机驱动
直流电机:类似于驱动LED亮灭一样,根据电机的电路原理图判断是什么数字电平有效。
步进电机:类似于驱动LED的周期翻转一样,在一个周期里面进行对步进电机的IO电平的自动翻转,LED=!LED 。(1)使用的定时器方式,设置一个确定的定时器周期 (2)PWM驱动
可以这样简单理解,首先要区分两种接线方法,一种是共阳极接法,就是PUL+和DIR+都是接5V,而PUL-就是你要输出脉冲的IO口,而DIR-就是接控制控制步进电机的正反转的IO,也就是说这种方法的DIR,给高电平的话就正转;然后如果是共阴极接法就是,相反而已,PUL-和DIR-都是接GND,而PUL+和DIR+分别接输出脉冲IO和控制正反转IO,然后只要给一个完整的脉冲给电机驱动器,然后如果设置的细分数是800的话,也就是说360°除以800等于0.45°,也就是接到上面所说的,一个完整的脉冲,步进电机就会转过0.45°。(步进电机通常通过步数控制位置,不需要PID调节)
这两类的电机驱动都是无反馈的。
像伺服电机是有反馈的,有反馈意味着闭环控制,而开环控制就是只管控制不管反馈,闭环控制中PID控制算法是最为经典的。
PID
PID,就是“比例(proportional)、积分(integral)、微分(derivative)”,是一种很常见的控制算法。
单环
PID参数结构体:定义一个速度/位置闭环的PID参数结构体变量
初始化PID参数:把目标值、期望值、累计偏差清零、配置PID系数
设置目标速度/位置:在函数中(通过外设控制或上位机)设置目标速度/目标
PID闭环控制:通过PID反馈结果更新定时器的比较值,限制占空比
通过编码器的当前计数值反映电机的驱动情况。
PID参数结构体:定义一个电流闭环的PID参数结构体变量初始化PID参数:把期望值、累计偏差清零、配置目标值、PID系数
实际电流滤波:ADC采集中断回调函数中对实际电流进行滤波
设置目标电流:在函数中(通过外设控制或上位机)设置目标电流
PID闭环控制:通过PID反馈结果更新定时器的比较值,限制占空比
双环
双环控制分内环和外环,外环控制的是优先考虑对象,内环用于对控制效果进行优化。
PID参数结构体:定义位置、速度闭环的PID参数结构体变量
初始化PID参数:把目标值、期望值、累计偏差清零、配置PID系数
设置目标速度:在函数中(通过外设控制或上位机)设置目标速度
PID双环控制:通过双环PID反馈结果更新定时器的比较值,限制占空比
三环
三环控制分内环、中环和外环,外环控制的是优先考虑对象,中环和内环用于对控制效果依次进行优化。
位置式PID
位置式控制器直接计算控制输出的位置(或值),而不是增量。通过累积误差来计算输出
u(k) = Kp * e(k) + Ki * Σe(k) + Kd * (e(k) - e(k-1))
其中 u(k) 是当前时刻的控制量, e(k) 是当前时刻的误差,Σe(k) 是累积误差,e(k) - e(k-1) 是误差的增量。
基本范式如图:
一般实现
typedef struct
{
float target_val; //目标值
float Error; /*第 k 次偏差 */
float LastError; /* Error[-1],第 k-1 次偏差 */
float PrevError; /* Error[-2],第 k-2 次偏差 */
float Kp,Ki,Kd; //比例、积分、微分系数
float integral; //积分值
float output_val; //输出值
}PID;
/**
* @brief PID参数初始化
* @note 无
* @retval 无
*/
void PID_param_init()
{
PosionPID.target_val=3600;
PosionPID.output_val=0.0;
PosionPID.Error=0.0;
PosionPID.LastError=0.0;
PosionPID.integral=0.0;
PosionPID.Kp = 10;
PosionPID.Ki = 0.5;
PosionPID.Kd = 0.8;
}
/**
* @brief 位置PID算法实现
* @param actual_val:实际测量值
* @note 无
* @retval 通过PID计算后的输出
*/
float PosionPID_realize(PID *pid, float actual_val)
{
/*计算目标值与实际值的误差*/
pid->Error = pid->target_val - actual_val;
/*积分项*/
pid->integral += pid->Error;
/*PID算法实现*/
pid->output_val = pid->Kp * pid->Error +
pid->Ki * pid->integral +
pid->Kd *(pid->Error -pid->LastError);
/*误差传递*/
pid-> LastError = pid->Error;
/*返回当前实际值*/
return pid->output_val;
}
速度转换
下面这个过程,函数usSpeed2Cycle() 将 PID 控制量转换为占空比。可以用来将速度值映射到合适的占空比范围内。
首先完成GPIO功能复用
#define TIM3_ARR (500)
#define TIM3_PSC (12)
void TIM3_CH34_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
TIM_TimeBaseStructure.TIM_Period = arr-1;
TIM_TimeBaseStructure.TIM_Prescaler =psc-1;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_Pulse=0;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_Cmd(TIM3, ENABLE);
}
/*左右驱动轮 CCR 范围0-499 ARR 500*/
#define LEFT_WHEEL_T3CH4_PWM_CYCLE( Cycle ) TIM_SetCompare4( TIM3, Cycle );
#define RIGHT_WHEEL_T3CH3_PWM_CYCLE( Cycle ) TIM_SetCompare3( TIM3, Cycle );
/*
将左右驱动轮占空比设置及电机DIR进行宏函数封装
*/
//左轮前进 标记前进
#define LEFT_WHEEL_FORWARD(Cycle) LEFT_WHEEL_T3CH4_PWM_CYCLE(Cycle);\
CTRL_WHEEL_L=0;\
g_tLeftWheel.Direction = WheelForward;
//左轮后退 标记后退
#define LEFT_WHEEL_RETREAT(Cycle) LEFT_WHEEL_T3CH4_PWM_CYCLE(Cycle);\
CTRL_WHEEL_L=1;\
g_tLeftWheel.Direction = WheelRetreat;
//右轮前进 标记前进
#define RIGHT_WHEEL_FORWARD(Cycle) RIGHT_WHEEL_T3CH3_PWM_CYCLE( Cycle );\
CTRL_WHEEL_R=0;\
g_tRightWheel.Direction = WheelForward;
//右轮后退 标记后退
#define RIGHT_WHEEL_RETREAT(Cycle) RIGHT_WHEEL_T3CH3_PWM_CYCLE( Cycle );\
CTRL_WHEEL_R=1;\
g_tRightWheel.Direction = WheelRetreat;
/*******************************************************************************
* Description : 速度转换为对应的PWM占空比
* Input : speed :mm/s 范围 -400~400
* Output : None
* Return : 电机占空比 450~50
*******************************************************************************/
__inline u16 usSpeed2Cycle(s16 loc_Speed)
{
if(loc_Speed<10 && loc_Speed>-10) //速度低于10mm/s 轮子响应线性太差 直接给0 电池电量AD大于3000 PWM线性响应450-0.
return 500; //返回500占空比 电机停转
if(loc_Speed<-400 && loc_Speed>400) //速度超过400mm/s 返回速度最大占空比
return 450-400;
if(loc_Speed>0)
return 450-loc_Speed;
else
return 450+loc_Speed;
}
PID调速
下面整个过程中,该函数使用 PID 控制算法独立计算左右轮的目标占空比,并根据占空比的正负决定轮子的运动方向,从而实现对两个轮子的独立速度控制。
vMotionWheel() 实现了两个重要过程的集合,一个基于 PID 控制的双轮速度控制器,可以独立控制左右轮的速度和方向。这对于实现复杂的移动机器人控制非常有用。
/*驱动轮速度环PID参数 SpeedOut= P*E[n] + I*(E[n]+E[n-1]+E[n-2].....+E[1]))*/
float P_V = 1;
float I_V = 0.1;
float D_V = 0;
__IO float SpeedLeftUiV; //左轮速度环积分结果
__IO float SpeedRightUiV; //右轮速度环积分结果
/*******************************************************************************
* Description : 将轮子的实际转速靠近期望转速 使用到全局结构体 LeftWheel RightWheel
* Input : None
* Output : None
* Return : None
*******************************************************************************/
void vMotionWheel(void)
{
int16_t PidTemp;
PidTemp = sSpeed_L_PID( g_tLeftWheel.SoftSpeed, g_tLeftWheel.RealSpeed ); //PID计算下一时刻速度
if( PidTemp < 0 )//左轮倒退
{
LEFT_WHEEL_RETREAT(usSpeed2Cycle(PidTemp));
}
else//左轮前进
{
LEFT_WHEEL_FORWARD(usSpeed2Cycle(PidTemp));
}
PidTemp = sSpeed_R_PID( g_tRightWheel.SoftSpeed, g_tRightWheel.RealSpeed );
if( PidTemp < 0 )//右轮后退
{
RIGHT_WHEEL_RETREAT(usSpeed2Cycle(PidTemp));
}
else//右轮前进
{
RIGHT_WHEEL_FORWARD(usSpeed2Cycle(PidTemp));
}
}
下面两个过程即是具体PID控制过程, 这个 PID 控制算法的输入是期望速度和实际速度,输出是用于控制轮子转速的 PID 控制量。
左轮速度环
这个PID调速控制器的工作原理如下:
根据期望速度 RefV 和实际反馈速度 FdbV 计算出速度误差 ErrV。
利用比例、积分两个部分对误差进行修正,得到最终的控制量 OutTempV。
对控制量进行限幅处理,防止输出超出合理范围。
最后对输出值进行修正,确保输出符合期望的正负反馈。
/*******************************************************************************
* Description : 左轮速度环
* Input : RefV-期望速度指令,FdbV-当前速度反馈,单位mm/s
* Output : None
* Return : 左轮下一个时刻应该的速度
*******************************************************************************/
int16_t sSpeed_L_PID(int16_t RefV,int16_t FdbV)
{
int16_t UpV=0;
int16_t ErrV,OutTempV;
int16_t OutputV;
ErrV = RefV - FdbV;//误差速度
UpV = P_V*ErrV; // 计算出比例部分
SpeedLeftUiV = SpeedLeftUiV + ((float)(UpV*I_V));//计算出积分累加部分
/*积分范围限制,不能无限累加*/
if(SpeedLeftUiV>UIMAXV)
{
SpeedLeftUiV=UIMAXV;
}else
if(SpeedLeftUiV<UIMINV)
{
SpeedLeftUiV=UIMINV;
}
OutTempV = UpV+(int16_t)SpeedLeftUiV; //PI
/*输出速度范围限制*/
if (OutTempV > OUTMAXV)
{
OutputV = OUTMAXV;
}
else if (OutTempV < OUTMINV)
{
OutputV = OUTMINV;
}
else
{
OutputV = OutTempV;
}
if(RefV>=0)
{
if(OutputV<0) OutputV=1;
}
else if(RefV<0)
{
if(OutputV>0) OutputV=-1;
}
return OutputV;
}
RefV: 期望的速度值(参考值) FdbV: 实际的反馈速度值
计算速度误差:
ErrV = RefV - FdbV: 计算出实际速度与期望速度之间的误差
比例控制部分:
UpV = P_V * ErrV: 根据比例常数 P_V 和误差 ErrV 计算出比例控制量
积分控制部分:
SpeedRightUiV = SpeedRightUiV + (UpV * I_V): 根据积分常数 I_V 和比例控制量 UpV 计算出积分累加值
对积分值进行限幅,防止积分饱和:
如果 SpeedRightUiV 大于 UIMAXV, 则设置为 UIMAXV
如果 SpeedRightUiV 小于 UIMINV, 则设置为 UIMINV
PI控制量计算:
OutTempV = UpV + (int16_t)SpeedRightUiV: 将比例控制量和积分控制量相加,得到PI控制量
输出限幅:
如果 OutTempV 大于 OUTMAXV, 则输出 OUTMAXV
如果 OutTempV 小于 OUTMINV, 则输出 OUTMINV
否则输出 OutTempV
输出修正:
如果 RefV 大于等于 0, 而输出 OutputV 小于 0, 则将其设置为 1
如果 RefV 小于 0, 而输出 OutputV 大于 0, 则将其设置为 -1
最终返回输出值 OutputV
通过这样的PID控制算法,可以实现对电机转速的精确调节和跟踪。比例项快速响应误差,积分项消除稳态误差,整个系统能够快速、平稳地达到期望的转速目标。这种直接使用误差 ErrV 和积分累加项 SpeedRightUiV 的方式就是典型的位置式 PID 控制算法。
右轮速度环
/*******************************************************************************
* Description : 右轮速度环
* Input : RefV-期望速度指令,FdbV-当前速度反馈,单位mm/s
* Output : None
* Return : 右轮下一个时刻应该的速度
*******************************************************************************/
int16_t sSpeed_R_PID(int16_t RefV,int16_t FdbV)
{
int16_t UpV=0;
int16_t ErrV,OutTempV;
int16_t OutputV;
ErrV = RefV - FdbV;//误差速度
UpV = P_V*ErrV; // 计算出比例部分
SpeedRightUiV = SpeedRightUiV + ((float)(UpV*I_V));//计算出积分累加部分
/*积分范围限制,不能无限累加*/
if(SpeedRightUiV>UIMAXV)
{
SpeedRightUiV=UIMAXV;
}else
if(SpeedRightUiV<UIMINV)
{
SpeedRightUiV=UIMINV;
}
OutTempV = UpV+(int16_t)SpeedRightUiV; //PI
/*输出速度范围限制*/
if (OutTempV > OUTMAXV)
{
OutputV = OUTMAXV;
}
else if (OutTempV < OUTMINV)
{
OutputV = OUTMINV;
}
else
{
OutputV = OutTempV;
}
if(RefV>=0)
{
if(OutputV<0) OutputV=1;
}
else if(RefV<0)
{
if(OutputV>0) OutputV=-1;
}
return OutputV;
}
速度平滑过渡
一种软速度调整的功能,用于将期望速度平滑地过渡到实际软速度上。
开发经验:这种平滑过渡有助于减少速度变化过程中的冲击和振荡,提高控制系统的稳定性和响应性。
#define SLOW_ACCELE_MAX_NUM (5) //缓变速步进单位 mm/s
void vSoftSpeedAdjust(void)
{
if(g_tLeftWheel.ExpectSpeed != g_tLeftWheel.SoftSpeed)
{
if(g_tLeftWheel.ExpectSpeed > g_tLeftWheel.SoftSpeed)
g_tLeftWheel.SoftSpeed+=SLOW_ACCELE_MAX_NUM;
else
g_tLeftWheel.SoftSpeed-=SLOW_ACCELE_MAX_NUM;
}
if(g_tRightWheel.ExpectSpeed != g_tRightWheel.SoftSpeed)
{
if(g_tRightWheel.ExpectSpeed > g_tRightWheel.SoftSpeed)
g_tRightWheel.SoftSpeed+=SLOW_ACCELE_MAX_NUM;
else
g_tRightWheel.SoftSpeed-=SLOW_ACCELE_MAX_NUM;
}
}
将期望速度 ExpectSpeed 逐步调整到实际软速度 SoftSpeed 上,以达到平滑过渡的效果。
该函数将被短周期地调用一次。对于左右驱动轮分别进行以下处理:
首先检查期望速度 ExpectSpeed 和当前软速度 SoftSpeed 是否相等。如果不相等,则需要进行调整。
如果期望速度大于当前软速度,则将软速度以 SLOW_ACCELE_MAX_NUM 的速率加大。
如果期望速度小于当前软速度,则将软速度以 SLOW_ACCELE_MAX_NUM 的速率减小。
SLOW_ACCELE_MAX_NUM 是一个常量,表示软速度每次调整的最大增量。
这种逐步调整的方式可以让速度变化更加平滑,避免突然的加速或减速,从而达到更好的控制效果。
增量式PID
当以速度误差为输入,输出速度控制量的方式,更适合采用增量式 PID 控制算法。因为此时算法关心的是速度的变化,而不是绝对速度值本身。这样可以更好地实现速度跟踪控制,减少速度跟踪过程中的振荡和超调现象。这种方式通常更适合数字化控制系统的实现,一般只需要存储前一时刻的控制量和误差,计算简单。
增量式 PID 控制是基于误差的增量来计算控制量,表达式为:
u(k) = u(k-1) + Kp * (e(k) - e(k-1)) + Ki * e(k) + Kd * (e(k) - 2*e(k-1) + e(k-2))
其中 e(k) 是当前时刻的速度误差, e(k-1) 是上一时刻的速度误差, u(k) 是当前时刻的 PID 控制量。
总结:
对于速度跟踪控制来说,增量式 PID 更加适合,因为它直接计算速度变化量。
相比之下,位置式 PID 需要计算绝对速度误差,在速度跟踪控制中可能会产生超调和振荡等问题。
基本范式如图:
一般实现
/**
* @brief 速度PID算法实现
* @param actual_val:实际值
* @note 无
* @retval 通过PID计算后的输出
*/
float addPID_realize(PID *pid, float actual_val)
{
/*计算目标值与实际值的误差*/
pid->Error = pid->target_val - actual_val;
/*PID算法实现,照搬公式*/
pid->output_val += pid->Kp * (pid->Error - pid-> LastError) +
pid->Ki * pid->Error +
pid->Kd *(pid->Error -2*pid->LastError+pid->PrevError);
/*误差传递*/
pid-> PrevError = pid->LastError;
pid-> LastError = pid->Error;
/*返回当前实际值*/
return pid->output_val;
}
总结:
增量式PID控制器通过计算输入数据的变化量来调整输出,适用于控制系统不需要绝对输出值的情况。
沿边速度控制
/*沿边传感器位置环PID参数
Out = P*( E[n]-E[n-1] ) + I*E[n] + D*(E[n]- 2*E[n-1] + E[n-2]) */
float P_AE = 3;
float I_AE = 0.1;
float D_AE = 0.1;
IncPID_t g_tIncPID={0};
/*******************************************************************************
* Description : 沿边传感器 增量式PID 计算
* Input : NewData 新得到沿边传感器de 误差数据
* Output : None
* Return : 速度改变值
*******************************************************************************/
s16 iIncPIDCalc(s16 NewData)
{
s16 loc_ChangeSpeed;
loc_ChangeSpeed = P_AE*( NewData-g_tIncPID.LastData ) + \
I_AE*NewData + D_AE*(NewData- (2*g_tIncPID.LastData) + g_tIncPID.PreData);
g_tIncPID.PreData=g_tIncPID.LastData;
g_tIncPID.LastData=NewData;
return loc_ChangeSpeed;
}
增量式PID控制器工作原理如下:
增量计算: loc_ChangeSpeed 是计算出的增量输出。
使用了三个项:比例(P)、积分(I)、微分(D)。
比例项 (P):
P_AE * (NewData - g_tIncPID.LastData) 计算当前值与上一次值的差异。
积分项 (I):
I_AE * NewData 使用当前值来计算积分项。
微分项 (D):
D_AE * (NewData - (2 * g_tIncPID.LastData) + g_tIncPID.PreData) 计算当前值、上一次值和前一次值之间的变化率。
状态更新:
g_tIncPID.PreData = g_tIncPID.LastData; 将上一次数据保存为前一次数据。
g_tIncPID.LastData = NewData; 更新上一次数据为当前数据。
不同类型的电机控制
伺服电机:常用于精确控制位置、速度和方向,经典的PID控制适用于这样的调节。
舵机:将伺服电机的三环控制简化成了一环,即只检测位置环。
无刷电机(磁极旋转,线圈不动):常用于需要精确速度和位置控制的应用,PID中的速度调整适合其控制特性。
有刷电机(磁极不动,线圈旋转):比无刷电机易于驱动,一般PID控制算法也适用于有刷电机。但是其物理结构有缺陷(碳刷与线圈接线头之间通断交替,会发生电火花,产生电磁破,干扰电子设备)
如根据与目标的偏差调整左右轮的速度,以实现路径校正和保持。适用于需要根据传感器反馈进行精确导航的场景。
u8 ucRouteAdjustByAlongEdgeDistance(u16 loc_FbValue,u16 loc_ExpValue,s16 loc_Speed)
{
s16 loc_DiffValue = loc_ExpValue - loc_FbValue;//差值
s16 loc_Divisor;//角度 转 速度值
loc_Divisor=iIncPIDCalc(loc_DiffValue);//速度值的调整基准
if( abs(loc_Divisor) > 100)//角度太大了 速度调整值不能超过最大速度
{
if(loc_Divisor>0)
loc_Divisor=100;
else
loc_Divisor=-100;
}
g_tLeftWheel.ExpectSpeed= loc_Speed - loc_Divisor;
g_tRightWheel.ExpectSpeed= loc_Speed + loc_Divisor;
return 0;
}
控制差速驱动电机工作原理如下:
计算差值:
loc_DiffValue = loc_ExpValue - loc_FbValue; 计算期望值和反馈值之间的差值。
增量PID计算:
loc_Divisor = iIncPIDCalc(loc_DiffValue); 使用增量PID控制器计算速度调整基准。
限制调整值:
如果 loc_Divisor 的绝对值大于 100,将其限制在 -100 到 100 之间。
速度调整:
根据 loc_Divisor 调整左右轮的期望速度:
g_tLeftWheel.ExpectSpeed = loc_Speed - loc_Divisor;
g_tRightWheel.ExpectSpeed = loc_Speed + loc_Divisor;
串级PID
先输入位置式PID再经过增量式PID,最后再输出
自动控制系统的性能指标
主要有三个方面:稳定性、快速性、准确性。
稳定性:系统在受到外作用后,若控制系统使其被控变量随时间的增长而最终与给定期望值一致,则称系统是稳定的,我们一般称为系统收敛。如果被控量随时间的增长,越来越偏离给定值,则称系统是不稳定的,我们一般称为系统发散。稳定的系统才能完成自动控制的任务,所以,系统稳定是保证控制系统正常工作的必要条件。一个稳定的控制系统其被控量偏离给定值的初始偏差应随时间的增长逐渐减小并趋于零。
快速性:快速性是指系统的动态过程进行的时间长短。过程时间越短,说明系统快速性越好,过程时间持续越长,说明系统响应迟钝,难以实现快速变化的指令信号。稳定性和快速性反映了系统在控制过程中的性能。系统在跟踪过程中,被控量偏离给定值越小,偏离的时间越短,说明系统的动态精度偏高。
准确性:是指系统在动态过程结束后,其被控变量(或反馈量)对给定值的偏差而言,这一偏差即为稳态误差,它是衡量系统稳态精度的指标,反映了动态过程后期的性能。
在实践生产工程中,不同的控制系统对控制器效果的要求不一样。比如平衡车、倒立摆对系统的快速性要求很高,响应太慢会导致系统失控。智能家居里面的门窗自动开合系统,对快速性要求就不高,但是对稳定性和准确性的要求就很高,所以需要严格控制系统的超调量和静差。