目录

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->II);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 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文库