1、AD采样,经过PID计算的值,怎么和PWM对应起来?
2、电机编码采样,经过PID计算的值,怎么与速度对应起来?
这个简单,PID控制原理就是输出一个控制量,然后检测反馈,由反馈得到的数据计算当前实际输出是多少,由这个实际输出和目标输出得到差值,最后由这个差值计算一个新的输出控制量。
举例(数据都是乱拟的,只为好计算):假设你的PWM是用来控制电机速度的,0%占空比对应的电机速度是0r,100%占空比对应的电机速度是100r。你目标电机速度是50r,那么调控步骤如下:
我们来分析一个例子:步进电机PID速度闭环控制,它原理:预先设置好需要达到的速度,然后每隔一段时间读取编码器的值反馈当前速度,经过PID计算慢慢到达设定的速度。
#define FEEDBACK_CONST (SPR/ENCODER_SPR) //编码器和步进电机驱动器的比值
typedef struct
{
__IO float SetPoint; // 目标值 单位:mm/s
__IO int LastError; // 前一次误差
__IO int PrevError; // 前两次误差
__IO long SumError; // 累计误差
__IO double Proportion; // Kp系数
__IO double Integral; // Ki系数
__IO double Derivative; // Kd系数
}PID_Typedef;
__IO PID_Typedef vPID;
Vel_Target = (vPID.SetPoint*P_PERIOD);//每单位采样周期内的脉冲数(频率)
/**
* 函数功能:增量式PID速度环计算
* 输入参数:NextPoint 由编码器得到的速度值
* TargetVal 目标值
* 返 回 值:经过PID运算得到的增量值
* 说 明:增量式 PID 速度环控制设计,计算得到的结果仍然是速度值
*/
float IncPIDCalc(__IO float NextPoint,__IO float TargetVal) //临时变量,期望值
{
__IO float iError = 0,iIncpid = 0; //当前误差
iError = TargetVal - NextPoint; // 增量计算
// if((iError<0.5f)&&(iError>-0.5f))
// iError = 0; // |e| < 0.5,不做调整
iIncpid=(vPID.Proportion * iError) // E[k]项
-(vPID.Integral * vPID.LastError) // E[k-1]项
+(vPID.Derivative * vPID.PrevError); // E[k-2]项
vPID.PrevError=vPID.LastError; // 存储误差,用于下次计算
vPID.LastError = iError;
return(iIncpid); // 返回增量值
}
/**
* 函数功能: 系统滴答定时器中断回调函数
* 输入参数: 无
* 返 回 值: 无
* 说 明: 每发生一次滴答定时器中断进入该回调函数一次
*/
void HAL_SYSTICK_Callback(void)
{
__IO static uint16_t time_count = 0; // 时间计数,每1ms增加一(与滴答定时器频率有关)
__IO static int32_t Last_CaptureNumber=0; // 上一次捕获值
// 每1ms自动增一
time_count++;
if(Start_Flag == ENABLE)
{
if(time_count >= SAMPLING_PERIOD)// 20ms控制周期
{
time_count = 0;
//获得编码器的脉冲值
CaptureNumber = OverflowCount*65536 + __HAL_TIM_GET_COUNTER(&htimx_Encoder);
//M法 测速度
MSF = CaptureNumber - Last_CaptureNumber;
Last_CaptureNumber = CaptureNumber;
MSF = abs(MSF);
Exp_Val += IncPIDCalc((float)MSF,Vel_Target);
//编码器输出期望值
Exp_Val = abs(Exp_Val);
/* 经过PID计算得到的结果是编码器的输出期望值的增量,
需要转换为步进电机的控制量(频率值),这里乘上一个系数6400/2400
*/
//乘上一个系数,6400/2400,将PID计算结果转换为步进电机的频率(速度)
STEPMOTOR_Motion_Ctrl(MotorDir,Exp_Val*FEEDBACK_CONST);
}
}
}
/**
* 函数功能: 步进电机运动控制
* 输入参数: Dir:步进电机运动方向 0:反转 1正转
* Frequency:步进电机频率,0:停止
* 返 回 值: void
* 说 明: 无
*/
void STEPMOTOR_Motion_Ctrl(uint8_t Dir , float Frequency)
{
uint16_t Step_Delay = 0; //步进延时
if(Frequency == 0)
STEPMOTOR_OUTPUT_DISABLE();
else
{
if(Dir==MOTOR_DIR_CCW)
{
STEPMOTOR_DIR_REVERSAL();
}
else STEPMOTOR_DIR_FORWARD();//方向控制
/*
步进电机速度由定时器输出脉冲频率(f)决定,
f = c/F;c是计数器的计数值,F是定时器频率
推导过程:(T是定时器输出脉冲周期)
T=c*t => T=c*F => f = 1/T = F/c;
*/
Step_Delay = (uint16_t)(FREQ_UINT/Frequency);
Toggle_Pulse = Step_Delay>>1;//算出来的结果是周期,这里除以2,半周期翻转一次
}
}
我们来分析一下它PID是怎么PID输出结果与速度量的关系:
从上面我可以知道,PID算法
输入参数:
1、MSF :单位采样周期内(20mS)编码器的值。
2、Vel_Target:每单位采样周期内编码器的脉冲数(频率);它由SetPoint(标 值 单位:mm/s),经过一定的计算而来。
输出参数:
1、编码器的输出期望值
步进电机的频率(速度)与PID结果的关系:Exp_Val*FEEDBACK_CONST ;从整个公式我们可发现,它们仅仅一个比例系数而已。
再举一例PWM控制温度
int iDataSigma
void PIDCalc(uint32_t Channel,int16_t set_temp,int16_t actualTemp)//温度放大100倍
{
int En,out_temp,controlOut;
En= set_temp-actualTemp; //本次偏差
if(abs(En) > 500) //积分分离
{
index=0;
}
else
{
index=1;
iDataSigma += En*iData; //历史偏差总和
}
// out_temp = Kp*En+index*Ki*iDataSigma; //P+I
out_temp = Kp*En+index*Ki*iDataSigma+Kd(En-En_1);//P+I+D
En_1 = En;
controlOut=_TIM3_ARR/500*out_temp;
if (controlOut <= 0)
bsp_SetPWM_TEMP_CCRx(Channel,0); //停止加热
else
bsp_SetPWM_TEMP_CCRx(Channel,abs(controlOut));//加热
}
//占空比
void bsp_SetPWM_TEMP_CCRx(uint32_t Channel,uint16_t duty)
{
if(duty <= _TIM3_ARR)
{
__HAL_TIM_SET_COMPARE(&htim3, Channel, duty);
}
}
//STM32 PWM_1模式 初始化设置PWM周期
bsp_SetPWM_TEMP_ARR(_TIM3_ARR); //周期 T=1000uS _TIM3_ARR
举例三:PWM控温
//定时器装载1000,相当于1mS
void PID_Init1(void)
{
spid1.Uplimit = 999;
spid1.Downlimit = 0;
spid1.Kp = 20;
spid1.Ki = 0.1;
spid1.Kd = 0;
spid1.e_0 = 0;
spid1.e_1 = 0;
spid1.e_2 = 0;
spid1.ei = 0;
spid1.ed = 0;
}
void PID_Init2(void)
{
spid2.Uplimit = 999;
spid2.Downlimit = 0;
spid2.Kp = 20;
spid2.Ki = 0.1;
spid2.Kd = 0;
spid2.e_0 = 0;
spid2.e_1 = 0;
spid2.e_2 = 0;
spid2.ei = 0;
spid2.ed = 0;
}
//位置式
u32 pid_calc(sPID *pid,float target ,float feedback)
{
pid->target = target;
pid->feedback = feedback;
pid->e_0 = target - feedback;
#if 0
if(pid->Ki*pid->ei + pid->Kp*pid->e_0 <= pid->Uplimit)//限制累积值
pid->ei += pid->e_0;
else if(pid->e_0 < 0)
pid->ei += pid->e_0;
else;
#endif
pid->ei += pid->e_0;
if(pid->Ki*pid->ei > 500)//限制累积值
{
pid->ei = 500/pid->Ki;
}
if(pid->ei < (-100))
{
pid->ei = (-100);
}
#if 0
pid->ed = pid->e_0 - 2*pid->e_1 + pid->e_2;
#else
pid->ed = pid->e_1 - pid->e_0; //个人以为这个才是对的
#endif
pid->Out = pid->Kp*pid->e_0 + pid->Ki*pid->ei + pid->Kd*pid->ed;
if(pid->Out > pid->Uplimit)
{
pid->Out = pid->Uplimit;
}
else if(pid->Out < pid->Downlimit)
{
pid->Out = pid->Downlimit;
}
else
{}
pid->e_2 = pid->e_1;
pid->e_1 = pid->e_0;
return (u32)pid->Out;
}
pid_calc(&spid1,(float)TempCtrler[PURE_TEMP_LYSIS].target,(float)TempForCal[PURE_TEMP_LYSIS]);
__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,spid1.Out);
调试PID参数链接
Kp相当于一个比例系数
Ki,减小静差
Kd,相当于一个阻尼系数
3.1 PID参数的整定原则
U(t)=P*[e(t)+ 1/Ti∫0te(t)dt+Tdde(t)/dt]
a.在输出不振荡时,增大比例增益P。(即增开Kp)
b.在输出不振荡时,减小积分时间常数Ti。(即增大Ki)
c.在输出不振荡时,增大微分时间常数Td。(即减小Kd)
P环很好理解,我们将误差乘以P值加在执行机构上面,当误差越小时P环所得值也就越小,给到执行机构的值也就越小,执行机构反应也就越慢,很符合这种闭环反馈的思想。但是当有一种情况,误差很小的时候P环得出的值给到执行机构上面不足以驱动执行机构工作,这时系统距离期望值仍有一段距离,该距离便被称为静差。
I环的作用是对过去状态的累加,本质上是对误差的积分,当系统存在静差时,I环会持续累加静差值,直到累加值大到足以驱动执行机构工作。这是I环的优点,可以有效地处理静差。但是I环本质上是对过去状态的处理,所以加上I环以后整个系统会变得有些不可控制,这里可以采用积分分离式的思想,只有当误差小于一定范围才开始进行进行误差的累加。如果是对稳定性要求比较高的系统,比如平衡小车直立环,就尽量不要用I环,单PD控制器控制即可。
D环,这个环比较的神奇,它可以预测将来,本质上是对误差的微分。也可以理解成对系统的阻尼,打个比方,在一个理想的环境下有个单摆一直在摆动,它摆动的波形是个正弦波形。如果不加阻尼那么它便可以一直摆动,但是现实中总会有各种各样的阻尼作用迫使单摆运动停下来。D环起的就是这个作用。D环一般运用在角度环比较多,因为你不可以进入弯道才想到转弯,而是在进入弯道之前就要有这个转弯的意识。做智能车的小伙伴们注意了!!
下面式复制别人图片,讲的挺好的,链接如下:
PID参数整定