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

目录

1、PID框图

2、pid控制器的表达式

3、传感器数据获取

4、硬件设计

5、工程配置

6、软件部分程序配置

7、调参过程记录


本文已更新,加上曲线调试,更好效果,更多内容,详情:

编码电机PID调试(速度环|位置环|跟随)_桃成蹊2.0的博客-CSDN博客_编码器pid

 串级控制系统介绍:

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

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

先把原理贴一遍

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

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

1、PID框图

速度环pid

 位置环pid

2、pid控制器的表达式

经典的PID计算式如下所示:

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

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

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

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

3、传感器数据获取

霍尔码盘结构图:

 编码电机上如下:

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

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

对应的上下信号说明如下所示:

读不懂就看口诀,立马懂:

CNT计数+

  • A上升沿,B逻辑低
  • B上升沿,A逻辑高
  • B下降沿,A逻辑低
  • A下降沿,B逻辑高

CNT计数-

  • A下降沿,B逻辑低
  • B下降沿,A逻辑高
  • B上升沿,A逻辑低
  • A上升沿,B逻辑高

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文库

  • 47
    点赞
  • 511
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 36
    评论
### 回答1: M3508电机角度闭环控制是指通过对电机的旋转角度进行实时监测和控制,并通过反馈调整来保持电机的旋转角度。这种控制方式通常应用于机器人、自动化设备等需要精确控制旋转角度的场合。 实现M3508的角度闭环需要以下几个步骤: 1. 确认电机型号及相关参数,包括电机的电气参数、机械参数、最大转速、编码器类型等。 2. 根据电机型号选择合适的控制器,如ST公司的STM32控制器。 3. 配置控制器的参数,包括编码器的分辨率、控制周期等。 4. 编写控制程序,包括PID控制算法、控制模式(位置控制速度控制等)等。 5. 连接电机控制器,将编码器信号传输到控制器进行处理。 6. 进行实时监控与调试,查看电机反馈的角度值与设定角度值的差异,并及时对控制程序进行调整,以实现精准的旋转角度控制。 总的来说,M3508电机角度闭环控制需要通过合适的控制器和编码器来实现精准的旋转角度控制,并通过反馈调整来确保电机旋转角度的准确性和稳定性。 ### 回答2: M3508是一种电机,它可以通过实现角度闭环来精确控制其旋转角度。具体来说,角度闭环通过反馈电机当前的旋转角度并将其与期望的旋转角度进行比较来控制电机的旋转。此过程中,需要使用编码器或其他位置传感器来检测电机的实际位置,并反馈给电机控制器进行相应的调整。通过实现角度闭环,M3508电机能够精确控制其旋转位置速度,从而更加准确地完成特定的任务。例如,它可以在机器人和无人驾驶车辆中用于精确定位和控制车辆的运动。总之,角度闭环是M3508电机控制的重要组成部分,它可以提高电机的精度和可靠性,同时也可以提高机器人和车辆等应用系统的性能和效率。 ### 回答3: M3508电机是一个直流无刷电机,在机器人控制中广泛应用。为了更好地控制其转动角度,需要实现角度闭环控制角度闭环的原理是通过测量电机转子的位置,与控制器所期望的位置进行比较,并对误差进行校正,以达到精确控制转子角度的目的。 具体实现方法如下: 1. 电机编码器:在电机上安装编码器,编码器可以测量电机转子的位置。通过读取编码器输出信号,可以实时检测电机的转速和转角。 2. PID控制器:在控制器中设计一个PID控制器,PID是一个反馈控制器,包括比例、积分和微分三项控制,可以用来校正控制信号和实际输出之间的误差。 3. 控制算法:根据PID控制器输出的控制信号以及电机位置反馈信息,通过一定的控制算法来实现电机转子角度闭环控制。其中包括计算目标角度与实际角度之间的误差量,以及以这个误差量为基础,实现控制信号的调节。 总结起来,实现M3508电机角度闭环控制需要安装编码器,设计PID控制器和控制算法。这样就可以精确控制电机的转角和转速,达到更精细的机器人运动控制
评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

桃成蹊2.0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值