PID一文章教会你控制舵机,直流电机,步进电机

本篇博客适用于有一点点PID基础的,小白也可以看,对后序写程序的思路有很大帮助。

PID简要说明

PID是一种闭环控制算法,P比例系数,I积分系数,D微分系数,这三个系数有着自己独特的作用,在控制中起到关键的作用。

有很多人知道PID算法怎么写,但是遇到实际问题时一头雾水,不知道如何将系统的PID整个搭建起来。那么本文将讲解如何搭建系统PID什么时候使用串级PID,并级PID,等等。。。

PID有两种,一种是位置式PID,一种是增量式PID,在我控制这三种电机时基本上没有多大区别,自己挑一种学习即可。

位置式PID

代码

#include <stdio.h>

// PID控制器结构体
typedef struct {
    float Kp;         // 比例增益
    float Ki;         // 积分增益
    float Kd;         // 微分增益
    float prev_error; // 上一次的误差
    float integral;   // 积分项
    float output;     // 控制输出
} PID;

// 初始化PID控制器
void PID_Init(PID* pid, float Kp, float Ki, float Kd) {
    pid->Kp = Kp;
    pid->Ki = Ki;
    pid->Kd = Kd;
    pid->prev_error = 0.0f;
    pid->integral = 0.0f;
    pid->output = 0.0f;
}

// 计算PID控制输出
float PID_Compute(PID* pid, float setpoint, float actual) {
    // 计算误差
    float error = setpoint - actual;

    // 积分项累积
    pid->integral += error;

    // 微分项计算(当前误差与上一时刻误差的差)
    float derivative = error - pid->prev_error;

    // 计算PID控制器输出
    pid->output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;

    // 保存当前误差作为下一时刻的上一误差
    pid->prev_error = error;

    return pid->output;
}

 在位置式PID计算时因为有一个累计误差,累计误差如果太大,就会使得系统及其容易不稳定因此可以在积分项累计之后进行累计误差限幅 

增量式PID

代码

#include <stdio.h>

// 增量式PID控制器结构体
typedef struct {
    float Kp;          // 比例增益
    float Ki;          // 积分增益
    float Kd;          // 微分增益
    float prev_error;  // 前一次的误差
    float prev_error2; // 前两次的误差
    float integral;    // 积分项
    float delta_u;     // 控制量的增量
} IncrementalPID;

// 初始化增量式PID控制器
void IncrementalPID_Init(IncrementalPID* pid, float Kp, float Ki, float Kd) {
    pid->Kp = Kp;
    pid->Ki = Ki;
    pid->Kd = Kd;
    pid->prev_error = 0.0f;
    pid->prev_error2 = 0.0f;
    pid->integral = 0.0f;
    pid->delta_u = 0.0f;
}

// 计算增量式PID控制器输出
float IncrementalPID_Compute(IncrementalPID* pid, float setpoint, float actual) {
    // 计算当前误差
    float error = setpoint - actual;

    // 计算误差的增量
    float delta_error = error - pid->prev_error;

    // 积分项累积
    pid->integral += error ;

    // 微分项(增量微分)
    float delta_error2 = error - 2 * pid->prev_error + pid->prev_error2;

    // 计算增量控制量
    pid->delta_u = pid->Kp * delta_error + pid->Ki * error + pid->Kd * delta_error2;

    // 更新历史误差值
    pid->prev_error2 = pid->prev_error;
    pid->prev_error = error;

    return pid->delta_u;
}

 增量式PID运算的是每次输出的增量值而不是像位置式pid直接作为输出量,我们需要创建一个变量将每次增量式PID的输出进行累计作为输出值。

构建PID控制体系

以直流电机为例

1、直流电机的转速与输入电压的大小成正比,因此我们在控制是改变的是PWM的占空比。

2、单个速度闭环时如果采样的是旋转编码器的脉冲个数比如10ms采样一次,然而我们的目标值是转每分钟,或者转每秒(自己根据系统设定来定)。那么这时我们就需要进行单位转换,现将20ms的脉冲个数转换为1分钟的脉冲个数,再将1分钟的脉冲个数转换为1分钟转的圈数(这就是电机现在的转速(转每分钟)),在进行PID运算。

如果目标是转每分钟,当前速度是每20ms的脉冲数那么PID运算的结果是及其不可靠的!!!

注意,PID运算的目标输入和最终传递到电机输入一定的同等单位。

3、并环控制

在使用多级PID控制时,PID到底是串级还是并级,怎么并怎么串呢?

并级(小车的转向环和速度换)

我们在转向环和速度换并级时先只调转向环,在加入速度环,为什么是并级而不是串级?这是因为转向环和速度环控制的都是电机的转速。

怎么并呢?

总输出=速度环输出总(+-)转向环输出总,中间是加还是减需要根据系统转向的极性来确定!!

比如:小车往右偏,那么右轮PWM占空比就要减小,根据计算出转向环输出如果是正,那么右轮转向环计算时前面就是负号。

到这里我们还是看不出是串级PID还是并级PID,继续往下看

4、串级PID

串级(电机位置环和速度环串级)

我们在使用置环和速度环串级目的是按照我们设定的速度运行一定的距离。分析如果是并级那既不可能是恒定速度也不可能是恒定距离,因为控制对象都不同,那么我们只能是两种,让位置环的输出作为速度环的输入(也就是当前速度),还是速度环的输出作为位置环的输入?

分析,位置换起始偏差很大,计算结果就很大,作为速度环输入传入速速度环,速度环就可以将电机速度进行控制到达我们的速度目标值,进行运动。当位置环接近目标位置时位置环输出减小,速度环依旧在保持一定的速度进行运动这样很容易在到达目标后导致速度不稳定,产生过冲,因此在接近目标时我们需要进行判定是否还需要进行速度环的控制,不需要就直接让位置环的输出作为电机的输入。

如果将速度环的输出作为位置环的输入,速度位置都无法进行控制!!!

舵机

舵机我们需要考虑舵机的0为类似与直流电机转向环的控制

步进电机

在步进电机PID控制时,控制的是脉冲频率,一般采用输出比较模式产生pwm,问题提出:我们知道偏差越大,PID开始输出就很大,如果直接作为输出比较值,那PWM频率不就是变小了嘛?

我们在控制步进电机时,采样的单位是什么一定要转换为不进电机的脉冲频率作为输入。怎么转换呢?举个简单的例子

系统末端采样的是角速度(单位:多少度每秒),在速度环能控制时,

当前速度 = 系统末端采样的是角速度*减速比*电机旋转1度对应的脉冲数(单位:脉冲数每秒)

目标转速 = 目标转速(度每秒)*减速比*电机旋转1度对应的脉冲数(单位:脉冲数每秒)

如果是旋转编码器,我们就需要算出电机频率与旋转编码器的比值

如果采样频率单位时间不是秒,最终还要将PID计算的结果 乘以采样频率(采样频率=1s/采样时间)注意在单独的距离环是不需要乘以PID的运算频率的。

问题解答:

 我们只需要在计算比较值时让产生PWM的定时器的计数频率除以我们最终PID计算的结果即可。

输出比较增量 = (定时器计数频率/最终PID运算后转换的结果)/2

这里说明一下为什么除以2,因为采用的是电平翻转模式,电平改变两次才是一个完整脉冲。

步进电机在控制时一定要对其进行最大速度的限幅控制,和启动速度的限制,依据于步进电机的特性。

速度环

注意代码中计算量都是编码器的脉冲数,只有最终

 QStrStepmotorFELMotorParameter.TimerCHCompterValue =

((uint16_t)(AZandELTimer_FREQ /(abs_cont_val*EncoderNumberAndMotorPluse*100))) >> 1;

这句代码QStrStepmotorFELMotorParameter.TimerCHCompterValue这个值作为下一次电平翻转的增量值。*100是速度采样频率是10ms,因为步进电机脉冲频率是以秒为单位的。

/*
速度位置串级PID
*/
void ELSpeedLoopControl(void)
{
  /* 编码器相关变量 */
    /*角度传感器相关变量*/
  __IO int AngleSpeed;
  __IO int AngleLocation; 
  static float LastTurnAngle = 0;
  static float cont_val = 0;  
  __IO float abs_cont_val = 0.0f;
  /* 当电机运动时才启动pid计算 */
  if((QStrStepmotorFELMotorParameter.MSD_ENA == 1) && (QStrStepmotorFELMotorParameter.stepper_running == 1))
  {
		/* 第一步 --> 计算当前编码器脉冲总数和单位时间内产生的脉冲数 */
		// 当前脉冲总计数值 = 当前定时器的计数值 + (定时器的溢出次数 * 定时器的重装载值)
      
        AngleLocation = GetMotorTurnAngle(EL);//获取对应电机的角度值
		// 单位时间的脉冲数 = 当前的脉冲总计数值 - 上一次的脉冲总计数值    
		
        AngleSpeed = AngleLocation - LastTurnAngle;//计算出10ms的编码器计数变化
        // 将当前当前的脉冲总计数值 赋值给 上一次的脉冲总计数值这个变量  
        LastTurnAngle = AngleLocation;
		 
       
		
    /* 第二步 --> 将当前脉冲总计数值作为实际值传入PID控制器,进行位置环的计算,
									并将输出值与历史期望值进行累加得到当前期望值	*/
     
         // 进行 PID 计算
       QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation += PIDSpeedLoopOperationFun(EL,AngleSpeed);
      
      

    /* 第三步 --> 用当前位置环期望值来调节步进电机 */
		// 判断当前期望值的速度方向
    //move_cont_val > 0 ? (MOTOR_DIR(CW)) : (MOTOR_DIR(CCW));
        SetMotorTurnDirction(QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation,EL);
       // 目标速度上限处理 
			if (QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation > ELTARGET_SPEED_MAX)
			{
				QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation = ELTARGET_SPEED_MAX;
			}
			else if (QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation < -ELTARGET_SPEED_MAX)
			{
				QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation = -ELTARGET_SPEED_MAX;
			}
            
			abs_cont_val = fabsf(QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation);
            
             QStrStepmotorFELMotorParameter.TimerCHCompterValue = ((uint16_t)(AZandELTimer_FREQ /(abs_cont_val*EncoderNumberAndMotorPluse*100))) >> 1;
              #if PID_ASSISTANT_EN
             int Temp = AngleSpeed;    // 上位机需要整数参数,转换一下
            //int Temp = 100;    // 上位机需要整数参数,转换一下
             set_computer_value(SEND_FACT_CMD, CURVES_CH1, &Temp, 1);  // 给通道 1 发送实际值
            #else
             printf("实际值:%d,目标值:%.0f\r\n", capture_per_unit, pid.target_val);// 打印实际值和目标值 
            #endif
            
  }
  else
  {
    /*失能驱动器*/
    EL_EN(0); 
    /*失能比较通道输出*/
    disable_EL_OutPutPWM();
    LastTurnAngle = 0;
    cont_val  = 0;
    QStrStepmotorFELMotorParameter.MSD_ENA = 0;
    QStrStepmotorFELMotorParameter.stepper_running = 0;
      
    QStrELSpeedPIDCombinatorsParameter.HistoricalAccumulation = 0;
    QStrELSpeedPIDCombinatorsParameter.LastDeviation = 0;
    QStrELSpeedPIDCombinatorsParameter.LastLastDeviation = 0;
    QStrELSpeedPIDCombinatorsParameter.PresentValue = 0;
    QStrELSpeedPIDCombinatorsParameter.TargetValue = 0;
      
  }

}

注意事项:

1、 限幅值的大小的确定,比如速度环采样的频率是10ms,电机编码器一圈1000个脉冲数,那么限制在一秒一转的话,限幅最大值就是1000/100 = 10。

2、得到速度环值之后先进行方向的判定,在进行速度限幅,最终取绝对值计算输出比较直的增量。

 位置环

void ELLocationLoopControl(void)
{
  /* 编码器相关变量 */
    /*角度传感器相关变量*/
  __IO int AngleSpeed;
  __IO int AngleLocation; 
  static float LastTurnAngle = 0;
  static float cont_val = 0;  
  __IO float abs_cont_val = 0.0f;
  /* 当电机运动时才启动pid计算 */
  if((QStrStepmotorFELMotorParameter.MSD_ENA == 1) && (QStrStepmotorFELMotorParameter.stepper_running == 1))
  {
		/* 第一步 --> 计算当前编码器脉冲总数和单位时间内产生的脉冲数 */
		// 当前脉冲总计数值 = 当前定时器的计数值 + (定时器的溢出次数 * 定时器的重装载值)
      
        AngleLocation = GetMotorTurnAngle(EL);//获取对应电机的角度值
		// 单位时间的脉冲数 = 当前的脉冲总计数值 - 上一次的脉冲总计数值    
      
        AngleSpeed = AngleLocation - LastTurnAngle;//
      
        LastTurnAngle = AngleLocation;
		// 将当前当前的脉冲总计数值 赋值给 上一次的脉冲总计数值这个变量   

		
    /* 第二步 --> 将当前脉冲总计数值作为实际值传入PID控制器,进行位置环的计算,
									并将输出值与历史期望值进行累加得到当前期望值	*/
    
       QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation += PIDLocationLoopOperationFun(EL,AngleLocation);
      
      

    /* 第三步 --> 用当前位置环期望值来调节步进电机 */
		// 判断当前期望值的速度方向
    //move_cont_val > 0 ? (MOTOR_DIR(CW)) : (MOTOR_DIR(CCW));
        SetMotorTurnDirction(QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation,EL);
        
        abs_cont_val = fabsf(QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation);
        
       // 目标速度上限处理 ,这步不是对PID位置环原始输出进行限幅
			if (abs_cont_val > ELTARGET_SPEED_MAX)
			{
				abs_cont_val = ELTARGET_SPEED_MAX;
			}
			else if (abs_cont_val < -ELTARGET_SPEED_MAX)
			{
				abs_cont_val= -ELTARGET_SPEED_MAX;
			}
            //不需要像速度环那样需要乘以速度的采样频率。
             QStrStepmotorFELMotorParameter.TimerCHCompterValue = ((uint16_t)(AZandELTimer_FREQ /(abs_cont_val*EncoderNumberAndMotorPluse))) >> 1;
              #if PID_ASSISTANT_EN
             int Temp = AngleLocation;    // 上位机需要整数参数,转换一下
             set_computer_value(SEND_FACT_CMD, CURVES_CH1, &Temp, 1);  // 给通道 1 发送实际值
            #else
             printf("实际值:%d,目标值:%.0f\r\n", capture_per_unit, pid.target_val);// 打印实际值和目标值 
            #endif
            
  }
  else
  {
    /*失能驱动器*/
    EL_EN(0); 
    /*失能比较通道输出*/
    disable_EL_OutPutPWM();
    LastTurnAngle = 0;
    cont_val  = 0;
    QStrStepmotorFELMotorParameter.MSD_ENA = 0;
    QStrStepmotorFELMotorParameter.stepper_running = 0;
      
      
    QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation = 0;
    QStrELLocationPIDCombinatorsParameter.LastDeviation = 0;
    QStrELLocationPIDCombinatorsParameter.LastLastDeviation = 0;
    QStrELLocationPIDCombinatorsParameter.PresentValue = 0;
    QStrELLocationPIDCombinatorsParameter.TargetValue = 0;
  }

}

注意:

1、位置环,在输出之后不能对原始的计算结果进行限幅,会导致系统不稳定。

2、在计算比较增量时不需要乘以速度采样频率。

位置速度串级PID

串级PID我们需要考虑一个重要的东西,如果系统从开始到结束都使用串级,那么在接近目标值时就会产生过冲现象,导致系统失控,因此我们需要通过判定一个偏差量来优化系统在快到目标值时双环控制过渡到单位置环控制,这个偏差量从大往小调试

/*
速度位置串级PID
*/
void ELLocationLoopControl(void)
{
  /* 编码器相关变量 */
    /*角度传感器相关变量*/
  __IO int AngleSpeed;
  __IO int AngleLocation; 
  static float LastTurnAngle = 0;
  static float cont_val = 0;  
  static __IO float abs_cont_val = 0.0f;
  static uint8_t SpeedBILocationNumber = 0;
  static uint8_t TaskFlag =1;
  /* 当电机运动时才启动pid计算 */
  if((QStrStepmotorFELMotorParameter.MSD_ENA == 1) && (QStrStepmotorFELMotorParameter.stepper_running == 1))
  {
		/* 第一步 --> 计算当前编码器脉冲总数和单位时间内产生的脉冲数 */
		// 当前脉冲总计数值 = 当前定时器的计数值 + (定时器的溢出次数 * 定时器的重装载值)
      
        AngleLocation = GetMotorTurnAngle(EL);//获取对应电机的角度值
		// 单位时间的脉冲数 = 当前的脉冲总计数值 - 上一次的脉冲总计数值    
		//capture_per_unit = capture_count - last_count;
        AngleSpeed = AngleLocation - LastTurnAngle;//*100将速度转换成以秒为单位
        LastTurnAngle = AngleLocation;// 将当前当前的脉冲总计数值 赋值给 上一次的脉冲总计数值这个变量 
		  
		
    /* 第二步 --> 将当前脉冲总计数值作为实际值传入PID控制器,进行位置环的计算,
									并将输出值与历史期望值进行累加得到当前期望值	*/
   //让位置环先执行一次
    if(TaskFlag == 1)
   {
      QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation += PIDLocationLoopOperationFun(EL,AngleLocation);
       SpeedBILocationNumber++;
       TaskFlag = 0;
   
   }
    //VelocityPositionLoopFrequencyRatio这个宏为10
   if(SpeedBILocationNumber >= VelocityPositionLoopFrequencyRatio)
    {
       QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation += PIDLocationLoopOperationFun(EL,AngleLocation);
       SpeedBILocationNumber = 0;
    }
       SpeedBILocationNumber++;
      

    /* 第三步 --> 用当前位置环期望值来调节步进电机 */
		// 判断当前期望值的速度方向
        SetMotorTurnDirction(QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation,EL);
        
        
        abs_cont_val = fabsf(QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation);
        
        if (abs_cont_val >= MOVE_CTRL) //判别是否接近目标,是否启用单环控制
        {
            
            // 目标速度上限处理 
            if (abs_cont_val > SpeedELTARGET_SPEED_MAX)
            {
                abs_cont_val = SpeedELTARGET_SPEED_MAX;
            }
            else if(abs_cont_val < -SpeedELTARGET_SPEED_MAX)
            {
                abs_cont_val = -SpeedELTARGET_SPEED_MAX;
            }

            QStrELSpeedPIDCombinatorsParameter.TargetValue = abs_cont_val;//设置速度环目标值            
            QStrELSpeedPIDCombinatorsParameter.HistoricalAccumulation += PIDSpeedLoopOperationFun(EL,AngleSpeed);
            SpeedBILocationNumber++;
            
            abs_cont_val = fabsf(QStrELSpeedPIDCombinatorsParameter.HistoricalAccumulation);
           //printf("速度脉冲频率%d\r\n",(int)(abs_cont_val*EncoderNumberAndMotorPluse*100));
            QStrStepmotorFELMotorParameter.TimerCHCompterValue = ((uint16_t)(AZandELTimer_FREQ /(abs_cont_val*EncoderNumberAndMotorPluse*100))) >> 1;
        }        
        else
        {
                        // 目标速度上限处理 
            if (abs_cont_val > LocationELTARGET_SPEED_MAX)
            {
                abs_cont_val = LocationELTARGET_SPEED_MAX;
            }
            else if(abs_cont_val < -LocationELTARGET_SPEED_MAX)
            {
                abs_cont_val = -LocationELTARGET_SPEED_MAX;
            }        
             QStrStepmotorFELMotorParameter.TimerCHCompterValue = ((uint16_t)(AZandELTimer_FREQ /(abs_cont_val*EncoderNumberAndMotorPluse))) >> 1;

        }
         #if PID_ASSISTANT_EN
         int Temp1 = AngleSpeed;    // 上位机需要整数参数,转换一下
         int Temp2 = AngleLocation;    // 上位机需要整数参数,转换一下
//       set_computer_value(SEND_FACT_CMD, CURVES_CH1, &Temp1, 1);  // 给通道 1 发送实际值
//       set_computer_value(SEND_FACT_CMD, CURVES_CH2, &Temp2, 1);  // 给通道 1 发送实际值
//       printf("累计脉冲数%d\r\n",Temp2);
         printf("速度脉冲数%d\r\n",Temp1);
         #else
         printf("实际值:%d,目标值:%.0f\r\n", capture_per_unit, pid.target_val);// 打印实际值和目标值 
         #endif
            
  }
  else
  {
    /*使能驱动器*/
    EL_EN(1); 
    /*使能比较通道输出*/
    disable_EL_OutPutPWM();
    LastTurnAngle = 0;
    cont_val  = 0;
    abs_cont_val = 0;
    SpeedBILocationNumber = 0;
    TaskFlag =1;
    QStrStepmotorFELMotorParameter.MSD_ENA = 0;
    QStrStepmotorFELMotorParameter.stepper_running = 0;
      
    QStrELSpeedPIDCombinatorsParameter.HistoricalAccumulation = 0;
    QStrELSpeedPIDCombinatorsParameter.LastDeviation = 0;
    QStrELSpeedPIDCombinatorsParameter.LastLastDeviation = 0;
    QStrELSpeedPIDCombinatorsParameter.PresentValue = 0;
    QStrELSpeedPIDCombinatorsParameter.TargetValue = 0;
      
    QStrELLocationPIDCombinatorsParameter.HistoricalAccumulation = 0;
    QStrELLocationPIDCombinatorsParameter.LastDeviation = 0;
    QStrELLocationPIDCombinatorsParameter.LastLastDeviation = 0;
    QStrELLocationPIDCombinatorsParameter.PresentValue = 0;
    QStrELLocationPIDCombinatorsParameter.TargetValue = 0;
  }

}

 注意:

1、调试双环时顺序为:先调单内环,再调单外环,最终再慢慢调节判定偏差量。 

 2、工程上一般内环是外环的5-10倍,速度环是位置环的10倍

调节PID参数

这里我只说一下串级PID的调试步骤:

1、先调内环再调外环,两个都是单独调(确定内环参数,对外环的参数范围有个了解)

2、将内环和外环进行串级,微微调整外环参数

3、调到使得外环曲线稳定变化,在观察内环曲线,使得内外环保持一个动态稳定的误差

 调节判别偏差量

调试记录

2024_12_12

调试过程中遇到了很多位问题,在这里我记录一下

这次先是只调试了位置环,这是一个稳定平台的项目,采用步进电机控制。

在闭环之前我们先进行开环控制,在调试过程中开环程序是必须要有的,因为在调试闭环时会出现各种问题,我们需要用开环程序来矫正系统,排除错误等,为闭环打下坚实的基础。

开环主要实现,走固定的距离,来回摇摆的功能,可以设定电机的转速。

在调试闭环的时候,只看系统的震荡运动状态很难看出参数调试效果的如何。这里有个建议

1、使用野火串口调试助手,将自己系统的目标值,和实际值打印出来,如果目标值是动态的可以通过观察目标曲线和实际曲线的吻合度台慢慢调整参数。

2、简陋一点的方法就是将这两个值用串口打印出来,进行比较,记录修改。

如何实现一个动态改变目标值呢?

这里我采用的是正弦曲线的方式,利用三角函数实现目标值的周期性变化。

/*闭环正弦运动*/
//Sin_T 正选曲线周期

#define TurnHD   3.141593/180

#define tAdd_FRQ   SysTimer5_FRQ/SpeedLoopFRQ_BI_SinFRQ      //正弦周期细分

int GetTargetValue_control_Sin_Angle_LOOP( float const Sin_A, uint8_t const Sin_T,EnumMotorSerialNumber AZOrEL)
{
    #if CloseLoopMode_Debug
    static int32_t AZpluse;
    static int32_t ELpluse;
    static float AZw ;
    static float ELw ;
    static float AZtAdd = 0;
    static float ELtAdd = 0;
    static float AZt = 0;
    static float ELt = 0;
    static uint8_t AZflag = 1;
    static uint8_t ELflag = 1;

    if(AZflag == 1 && AZOrEL == AZ)
    {
        AZw = (360.0f)/Sin_T;
        AZtAdd = ((float)Sin_T)/(tAdd_FRQ*Sin_T);
        AZpluse = (int32_t)(Sin_A*AZOnePulseAngleCorresponds);
        AZflag  = 0;      
    }
    else if(ELflag == 1 && AZOrEL == EL)
    {
        ELw = (360.0f)/Sin_T;
        ELtAdd = ((float)Sin_T)/(tAdd_FRQ*Sin_T);
        ELpluse = (int32_t)(Sin_A*ELOnePulseAngleCorresponds);
        ELflag  = 0;      
    }
    
    if(AZOrEL ==AZ)
    {
        AZt = AZt+AZtAdd;
        return (int)(AZpluse*sinf((AZw*AZt)*TurnHD));
    }
    else
    {

        ELt = ELt+ELtAdd;
        return (int)(ELpluse*sinf((ELw*ELt)*TurnHD));
    }
    
    #endif
}

代码讲解:

这个函数是在定时器中运行,实现周期性的计算

float const Sin_A, //目标值的幅值

uint8_t const Sin_T, //sin周期

EnumMotorSerialNumber AZOrEL//控制那个电机

因为有些参数只需要计算一次就可以了,因此为了减少运算量这里采用了标志的方式。

根据w=2π/T   注意这里的2π要写成360,因为先将弧度转换成角度计算

  ELtAdd = ((float)1)/(tAdd_FRQ);//这里是计算时间的增量

AZt = AZt+AZtAdd;计算当前的时间

(int)(AZpluse*sinf((AZw*AZt)*TurnHD));//最终计算要将sin()里的数值转换成弧度。在编程中sin(90)不等于1.

这样就可以周期性的改变目标值了。

有了这个我们就可以连续性的改变,通过观察目标曲线和实际曲线就可以进行PID参数的调试。

注意如果在调节过程中步进电机出现了阶梯式运动,那么就是我们采样频率太低,目标值变化过块,导致系统不稳定,因此可以进行提高采样频率,和加快PID的运算频率。如果不能采样过快,那么这时候我们就需要进行平滑。举个例子:

我使用的是位置环但是采样只有50HZ,那怎样才能提高到100HZ呢,在PID采样时我们用上一次的采样值与这次的采样值比较如果相同那这次的采样值就等于采样不同时差值的1/2加上当前的采样值,如果不同就直接更新采样值。这样就模拟的提高了采样频率。

2025_01_08

由于驱动器的不同,有的驱动器可以采用通讯的方式进行控制,这时候我们最好是用驱动器的通讯方式进行控制,如果采用脉冲控制会出现驱动器识别速度不准产生转速波动的情况。

2025_01_16

/*


一般的PID控制器
float IncrementalPIDCombinators(StrPIDCombinatorsParameters* PIDPosition)
{
    float NowErr;
    float PIDIncrementalOut;
    NowErr = (PIDPosition->TargetValue - PIDPosition->PresentValue);//使得运算偏差范围在0-1之间
    
    PIDIncrementalOut = PIDPosition->KP*(NowErr - PIDPosition->LastDeviation)+ \
                        PIDPosition->KI*NowErr+    \
                        PIDPosition->KD*(NowErr - 2*PIDPosition->LastDeviation + PIDPosition->LastLastDeviation);
    PIDPosition->LastLastDeviation = PIDPosition->LastDeviation;//将上次误差赋值给上上一次
    PIDPosition->LastDeviation = NowErr;//将当前偏差赋值给上一次。
    
    return PIDIncrementalOut;   
}
改进后的PID控制器

定义几个控制参数

1、控制位置范围A
2、控制速度范围
3、速度限幅
以位置速度串级为例
第一步:在给定初始除以控制范围,使得控制给定在+1 - -1之间
第二步:给反馈除以控制范围,使其反馈在+1 - -1之间
第三步:在做了1,2步之后PID运算将会是一个比较小的数据,我们想要进行限幅控制将会变得非常简单,直接将PID运算值限幅在0-1之间,之后再乘以我们速度最大值即可。


float IncrementalPIDCombinators(StrPIDCombinatorsParameters* PIDPosition,float MAX_Limt)
{
    float NowErr;
    float PIDIncrementalOut;
    NowErr = (PIDPosition->TargetValue - PIDPosition->PresentValue)/MAX_Limt;//使得运算偏差范围在0-1之间
    
    PIDIncrementalOut = PIDPosition->KP*(NowErr - PIDPosition->LastDeviation)+ \
                        PIDPosition->KI*NowErr+    \
                        PIDPosition->KD*(NowErr - 2*PIDPosition->LastDeviation + PIDPosition->LastLastDeviation);
    PIDPosition->LastLastDeviation = PIDPosition->LastDeviation;//将上次误差赋值给上上一次
    PIDPosition->LastDeviation = NowErr;//将当前偏差赋值给上一次。
    
    return PIDIncrementalOut;   
}


*/

这样修改之后我们之后想控制别的系统,控制范围,最高转速的不同都会变得十分方便,并且在速度与位置环串级以比例的方式进行给定使得速度环的控制非常有效!。 

2025_01_21

有时候我们调试PID参数大了,系统不稳定震荡,参数小了不满足产品要求,这时候不防从源头找问题,首先看开环控制能稳定吗?反馈信号稳定吗?我们都测一测,实际上在每一次硬件组装改动之后我们都需要将这些信号检测单独的测试一遍,确保我们反馈是准确的,如果反馈信号不稳定,控制就失去了意义。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值