ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

直流编码电机双闭环(速度+角度)控制

2022-01-21 23:02:01  阅读:254  来源: 互联网

标签:编码 编码器 PID 闭环 error pid fp32 speed 直流


目录

1、因此PID大概框图

 2、pid控制器的公式为

3、传感器数据获取

4、采用硬件如下(已经在立创开源)

 5、工程配置

 6、软件部分程序配置

7、调参过程记录


       串级控制系统是改善控制质量的有效方法之一,在过程控制中得到了广泛的应用。所谓串级控制,就是采用两个控制器串联工作,外环控制器的输出作为内环控制器的设定值,由内环控制器的输出去操纵控制阀,从而对外环被控量具有更好的控制效果。这样的控制系统被称为串级系统。PID串级控制就是串级控制中的两个控制器均为PID控制器

        限于时间和篇幅,这篇仅记录用通用的pid方式进行感觉性的调参,等回家了有时间在把改进pid加上去试试效果

先把原理贴一遍

        PID控制,就是对偏差进行比例、积分和微分的控制。PID由3个单元组成,分别是比例(P)单元、积分(I)单元、微分(D)单位。在工程实践中,一般P是必须的,所以衍生出许多组合的PID控制器,如PD、PI、PID等。
        因为单片机是通过软件实现其控制算法的,所以必须对模拟调节器进行离散化处理,这样它只需根据釆样时刻的偏差值计算控制量。因此,我们需要使用离散的差分方程代替连续的微分方程

通俗理解,用比例积分微分运算来消除误差,但是这个过程是连续的,周期性(一般是ms级的)的一次次计算来消除误差。

1、因此PID大概框图

速度环pid

 位置环pid

 2、pid控制器的公式为

 将上述公式离散化,结果如下:

 在使用ki和kd来代替积分就是下面的我们最常用的公式了(这个是比较常用的位置式pid版本)

 之后我们再修改一种版本,如下所示(这种称为增量式pid)

 可以看出这种仅统计当前误差和上一次误差,而上面的位置式统计了自起始以来所有的误差项,而上面的位置式版本输出后直接作为控制器输出值,而增量式则作为增加量叠加进入控制器的输出中。

3、传感器数据获取

霍尔码盘结构图:

 编码电机上如下:

 怎么读数据看下面这个,一张图一张表,对应着读取就知道了

这里注意:stm32用硬件编码器模式,这个读取的过程他是自动进行的,只要进行配置编码器模式就行了,但是其他没有硬件编码器模式的需要软件上模拟实现类似的功能,怎么模拟-就是按照下面的那个表,使用gpio中断加上if条件判读即可。

4、采用硬件如下(已经在立创开源)

硬件其实没什么要求,画了块板子只是为了使用方便,这块板子接口上是直接兼容编码电机的,市面上几款我都试过了,基本不需要改线,直接插上就可以使用。

 关注一下这个电机部分的接口吧,毕竟就这个有点用了

 5、工程配置

可能有些没用的,主要看电机的pwm接口,编码器捕获接口,还有电机方向的接口把

 定时器8 1 3 2采用编码器模式,配置如下

 pwm设置,这里一个定时器就够了,重装载设置为7199,那pwm最大就是7200了,这就这样OK

 基本时间配置,这里我没有采用操作系统的写法,直接用一个定时器中断了(毕竟是老工程了,拿过来直接用比较方便),使用定时器6,可以看出定时时间为1ms一次。

 综上:资源配置如下

接口类型外设资源模式
编码器口TIM8 TIM1 TIM3 TIM2编码器模式(T12)
电机接口M11 M12 M21 M22 M31 M32 M41 M42看原理图
PWM口TIM4 (CH1 ~ 4)变化范围0-7200
基本定时TIM61ms一次

 6、软件部分程序配置

1、电机配置,这里把每一个电机封装成一个函数,内函限幅,电机换向,PWM设置,是比较方便的,墙裂推荐hhh

void AAC_MotorFL_Run(int16_t speed)
{
	if(speed > 0)  	{GPIOE->BRR = m11_Pin;GPIOE->BSRR = m12_Pin;}
	else            {speed = -speed;GPIOE->BSRR = m11_Pin;GPIOE->BRR = m12_Pin;}
	if(speed > 7100) speed = 7100;
	__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_1,speed);
}
void AAC_MotorFR_Run(int16_t speed)
{
	if(speed > 0)  	{GPIOE->BRR = m21_Pin;GPIOE->BSRR = m22_Pin;}
	else            {speed = -speed;GPIOE->BSRR = m21_Pin;GPIOE->BRR = m22_Pin;}
	if(speed > 7100) speed = 7100;
	__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_2,speed);
}
void AAC_MotorBL_Run(int16_t speed)
{
	if(speed > 0)  	{GPIOC->BRR = m31_Pin;GPIOC->BSRR = m32_Pin;}
	else            {speed = -speed;GPIOC->BSRR = m31_Pin;GPIOC->BRR = m32_Pin;}
	if(speed > 7100) speed = 7100;
	__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_3,speed);
}
void AAC_MotorBR_Run(int16_t speed)
{
	if(speed > 0)  	{GPIOC->BRR = m41_Pin;GPIOC->BSRR = m42_Pin;}
	else            {speed = -speed;GPIOC->BSRR = m41_Pin;GPIOC->BRR = m42_Pin;}
	if(speed > 7100) speed = 7100;
	__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_4,speed);
}

 2、编码器测速,定时器的编码器模式是特殊的计数模式,测量值还是保存在cnt中的,因此只要读取cnt的值就可以获取编码器当前的计数值了。

int Read_Encoder(uint8_t TIMX)
{
	int Encoder_TIM;
	switch(TIMX)
	{
	   case 2:  Encoder_TIM = (short)TIM2 -> CNT;  TIM2 -> CNT=0;break;
		 case 3:  Encoder_TIM = (short)TIM3 -> CNT;  TIM3 -> CNT=0;break;	
		 case 1:  Encoder_TIM = (short)TIM1 -> CNT;  TIM1 -> CNT=0;break;	
		 case 8:  Encoder_TIM = (short)TIM8 -> CNT;  TIM8 -> CNT=0;break;
		 default:  Encoder_TIM = 0;
	}
	return Encoder_TIM;
}

3、测量值大小问题 市面上常用的编码电机有两种,由电机+减速箱+编码器组成,电机为最内部的主体,前端套筒为减速箱,最尾部的为编码器,捕获到的值由编码器和减速箱共同决定。

减速比可由电机上的贴纸或者型号获取,比如贴了10F,就是减速比为10:1的意思

 下面说明了常见霍尔编码器和光电编码器的编码器线束

 可以看出关电编码器的线数是远大于霍尔编码器的,这使得光电编码器更适合高精度的应用,那么最终公式为

主动轴一圈=减速比*编码器线数

4、定时器创建任务周期,前面已经创建了1ms一次的定时器中断,这里在中断服务函数中加入判断,这里根据经验判断如下(为啥呢,这样取得值比较合理,大概pwm满载的时候速度最大100多):

  • 光电编码器:2ms读取一次数据
  • 霍尔编码器:10ms读取一次数据
//定时器任务周期
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  static int time;
  if(htim->Instance == htim6.Instance)
  {
		if(time % 10 == 0)
		{
            //10ms执行一次
		}
		if(time >= 1000)
		{
			time = 0;
			HAL_GPIO_TogglePin(GPIOD, led_Pin);
		}
  }
}

5、pid公式

  • 这是直接根据离散公式还原来的
typedef struct
{
	float Kp, Ki, Kd;
	float P, I, D;
	float Error_Last;	
}PositionPID_t;
// pid计算
int Position_PID( PositionPID_t *pid, float set_value, float now_value )
{
	pid->P = set_value - now_value;
	pid->I += pid->P;
	pid->D = pid->P - pid->Error_Last;
	pid->Error_Last = pid->P;
	pid->I=pid->I>10000?10000:(pid->I<(-10000)?(-10000):pid->I);
	if( set_value == 0 )			pid->I = 0;
	
	return( pid->Kp*pid->P  +  pid->Ki*pid->I  +  pid->Kd*pid->D );
}
  •  平衡小车之家抄来的
//位置式PID控制器
int Position_PI (int Encoder, int Target)
{
  //   float Kp=0.02,Ki=0.0002;
  static int Bias, Pwm;
  static long Integral_bias;
  Bias = Encoder - Target;            //计算偏差
  Integral_bias += Bias;	             //求出偏差的积分
  if(Integral_bias > 1500000)  Integral_bias = 1500000; //积分限幅
  if(Integral_bias < -1500000)  Integral_bias = -1500000; //积分限幅
  Pwm = Position_Kp * Bias + Position_Ki * Integral_bias; //位置式PI控制器
  return Pwm;                         //增量输出
}
//增量PI控制器
int Incremental_PI (int Encoder, int Target)
{
  //   float Kp=20,Ki=30;
  static int Bias, Pwm, Last_bias;
  Bias = Encoder - Target;            //计算偏差
  Pwm += Incremental_Kp * (Bias - Last_bias) + Incremental_Ki * Bias; //增量式PI控制器
  Last_bias = Bias;	                 //保存上一次偏差
  return Pwm;                         //增量输出
}
  • 大疆robomaster官方例程 

pid.c

#include "pid.h"
#include "main.h"

#define LimitMax(input, max)   \
    {                          \
        if (input > max)       \
        {                      \
            input = max;       \
        }                      \
        else if (input < -max) \
        {                      \
            input = -max;      \
        }                      \
    }

void PID_init(pid_type_def *pid, uint8_t mode, const fp32 PID[3], fp32 max_out, fp32 max_iout)
{
  if (pid == NULL || PID == NULL)
  {
    return;
  }
  pid->mode = mode;
  pid->Kp = PID[0];
  pid->Ki = PID[1];
  pid->Kd = PID[2];
  pid->max_out = max_out;
  pid->max_iout = max_iout;
  pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f;
  pid->error[0] = pid->error[1] = pid->error[2] = pid->Pout = pid->Iout = pid->Dout = pid->out = 0.0f;
}

fp32 PID_calc(pid_type_def *pid, fp32 ref, fp32 set)
{
  if (pid == NULL)
  {
    return 0.0f;
  }
  pid->error[2] = pid->error[1];
  pid->error[1] = pid->error[0];
  pid->set = set;
  pid->fdb = ref;
  pid->error[0] = set - ref;
  if (pid->mode == PID_POSITION)
  {
    pid->Pout = pid->Kp * pid->error[0];
    pid->Iout += pid->Ki * pid->error[0];
    pid->Dbuf[2] = pid->Dbuf[1];
    pid->Dbuf[1] = pid->Dbuf[0];
    pid->Dbuf[0] = (pid->error[0] - pid->error[1]);
    pid->Dout = pid->Kd * pid->Dbuf[0];
    LimitMax(pid->Iout, pid->max_iout);
    pid->out = pid->Pout + pid->Iout + pid->Dout;
    LimitMax(pid->out, pid->max_out);
  }
  else if (pid->mode == PID_DELTA)
  {
    pid->Pout = pid->Kp * (pid->error[0] - pid->error[1]);
    pid->Iout = pid->Ki * pid->error[0];
    pid->Dbuf[2] = pid->Dbuf[1];
    pid->Dbuf[1] = pid->Dbuf[0];
    pid->Dbuf[0] = (pid->error[0] - 2.0f * pid->error[1] + pid->error[2]);
    pid->Dout = pid->Kd * pid->Dbuf[0];
    pid->out += pid->Pout + pid->Iout + pid->Dout;
    LimitMax(pid->out, pid->max_out);
  }
  return pid->out;
}

void PID_clear(pid_type_def *pid)
{
  if (pid == NULL)
  {
    return;
  }
  pid->error[0] = pid->error[1] = pid->error[2] = 0.0f;
  pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f;
  pid->out = pid->Pout = pid->Iout = pid->Dout = 0.0f;
  pid->fdb = pid->set = 0.0f;
}

pid.h


#ifndef PID_H
#define PID_H
#include "struct_typedef.h"
enum PID_MODE
{
  PID_POSITION = 0,
  PID_DELTA
};

typedef struct
{
  uint8_t mode;
  //PID 三参数
  fp32 Kp;
  fp32 Ki;
  fp32 Kd;

  fp32 max_out;  //最大输出
  fp32 max_iout; //最大积分输出

  fp32 set;
  fp32 fdb;

  fp32 out;
  fp32 Pout;
  fp32 Iout;
  fp32 Dout;
  fp32 Dbuf[3];  //微分项 0最新 1上一次 2上上次
  fp32 error[3]; //误差项 0最新 1上一次 2上上次

} pid_type_def;

extern void PID_init(pid_type_def *pid, uint8_t mode, const fp32 PID[3], fp32 max_out, fp32 max_iout);
extern fp32 PID_calc(pid_type_def *pid, fp32 ref, fp32 set);
extern void PID_clear(pid_type_def *pid);

#endif

大疆官例写的比较直观,有面向对象的感觉了,用起来舒服,但是也是直接套公式,没有加入一些优化pid的方法,下一版本我将用完善版本的。 

7、调参过程记录

先放经典图,参数的作用看图

速度单环:周期中代码如下

			enc = Read_Encoder(8);
			pwm = PID_calc(&motor_speed_pid,enc,target);//速度环
			AAC_MotorFL_Run(pwm);

 位置单环:周期中代码如下

			enc += Read_Encoder(8);
			pwm = PID_calc(&motor_angle_pid,enc,target);//位置环
			AAC_MotorFL_Run(pwm);

位置速度双环:周期中代码如下

			enc += Read_Encoder(8);
			pwm = PID_calc(&motor_angle_pid,enc,target);//位置环
			pwm = PID_calc(&motor_speed_pid,Read_Encoder(8),pwm);//速度环
			AAC_MotorFL_Run(pwm);

角度控制效果 ,调的不是很好,下次一定hhh

说明:做角度控制其实单独使用位置环已经有一定效果了,但是没有串起来效果好,也没有串起来稳定,所以建议还是双闭环,角度仅仅作为一个调参练习即可。 

源码我已上传到csdn,链接如下 

直流编码电机速度位置双闭环-制造文档类资源-CSDN文库

标签:编码,编码器,PID,闭环,error,pid,fp32,speed,直流
来源: https://blog.csdn.net/m0_51220742/article/details/122348389

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有