赞
踩
目录
按照我配置的定时器中断和PWM情况,将速度PID系数调成以下值得到的电机转动结果比较合适。
Velocity_KP=4.5,Velocity_KI=0.1,Velocity_KD=0;
1、该系列教程是基于stm32f103c8t6最小系统板的hal库开发,用最通俗易懂的方式手把手带你学会使用Pid算法的速度环、位置环以及速度位置串级pid。
2、出这一期Pid系列教程的想法是前段时间我参加了一个比赛,要用到串级Pid的功能,可是我发现网上的教程要么是零零散散的,没有集中一起讲解这三个功能的,要么就是写的内容不够简单直接,基础没那么好的同学学起来会很吃力。为了用最直观的步骤和最简单的代码带大家一起掌握使用pid控制电机的能力,我打算出一期完整的、通俗易懂的系列教程,同时也作为对我前段时间学习内容的总结。
3、Pid算法涉及到的内容很多,值得我们去深入地研究去发现它的美,pid算法的学习不仅仅是理论上的理解,要去实践,实现把速度、位置控制好的功能。移植好pid算法以后的调参也是一门学问呢!哈哈哈,所以掌握pid并不是一件容易的事情,不过我已经把串级pid实现了,请相信我会带着大家一起从头到尾一点一点实现!
本章节要实现的功能是使用pid速度环控制电机,最终达到的效果是电机以设定的速度转动,且速度响应快,大小准确,抗干扰能力强。
大家会学会:
(1)使用PID速度环控制电机;
(2)如何调pid算法的参数;
学习本章前建议大家先学习完:【手把手带你用pid算法控制电机】——(1)编码器电机和0.96寸OLED显示屏的使用
本文内容是在前面代码的基础上进行编写的。
速度环用的是增量式PID算法,什么是增量式PID在我的这篇文章:位置式Pid和增量式Pid的定义及应用
已经讲的比较清楚,这里不做多余阐述。
(1)为了方便调pid的参数,我们可以使用vofa上位机;cubmx配置一个串口,这边使用的是串口1,波特率配置的是115200,所以串口助手中我配置的波特率也是115200。
(2)我这里也将串口1重定向了,这样在上位机上打印数据比较方便。
- /* USER CODE BEGIN PV */
- int16_t speed,encoder_counter;
-
- //float Position_KP=0.18,Position_KI=0.002,Position_KD=0; //位置PID系数
- float Velocity_KP=4.5,Velocity_KI=0.1,Velocity_KD=0; //速度PID系数
- int Encoder,Target_Velocity=30;
- int Moto,Position_Moto;//电机PWM变量
- int limit_a;
- //int Position,Target_Position=850; //位置和目标位置自己设定
-
-
- /* USER CODE END PV */
- /* USER CODE BEGIN 0 */
-
- /**
- * @function: void GET_NUM(void)
- * @description: 使用STM32编码器模式,读取编码器产生的脉冲值
- * @param {*}
- * @return {*}
- */
- void GET_NUM(void)
- {
- encoder_counter=(short) __HAL_TIM_GET_COUNTER(&htim3);
- __HAL_TIM_SET_COUNTER(&htim3,0);//将编码器模式的定时器清零
- }
-
- /**
- * @function:void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- * @description: 定时器中断回调函数,0.1S中断一次并计算转速,将电机转速以及编码器产生的脉冲数显示在OLED屏上
- * @param {TIM_HandleTypeDef *htim}
- * @return {*}
- */
- void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
- {
- if(htim==&htim1)
- {
- //key_flag=1;
- GET_NUM();//得到所记录的脉冲数
- //Position+=encoder_counter;
- //Position_Moto = Position_PID(Position,Target_Position);
- //limit_a=Xianfu(Position_Moto,myabs(Target_Velocity));
- //Moto = Incremental_PI(encoder_counter,limit_a);
- Moto = Incremental_PI(encoder_counter,Target_Velocity);
-
- Set_Pwm(Moto);
-
- //Key_scan();
- //speed=(float)encoder_counter/2040/0.1;//转速为n,r/s 脉冲数转化为速度
- //OLED_Showdecimal(0,4,speed,2,2,12,0);//在特定位置显示2位整数+2位小数的电机转速
- }
- }
-
- /*串口重定向*/
- #ifdef __GNUC__
- #define PUTCHAR_PROTOTYPE int _io_putchar(int ch)
- #else
- #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
- #endif /* __GNUC__*/
-
- /******************************************************************
- *@brief Retargets the C library printf function to the USART.
- *@param None
- *@retval None
- ******************************************************************/
- PUTCHAR_PROTOTYPE
- {
- HAL_UART_Transmit(&huart1, (uint8_t *)&ch,1,0xFFFF);
- return ch;
- }
-
-
- /* USER CODE END 0 */
- /* USER CODE BEGIN WHILE */
- while (1)
- {
- /* USER CODE END WHILE */
-
- /* USER CODE BEGIN 3 */
- printf("%d,%d\n",encoder_counter,Target_Velocity); //输出编码器的值(实际值)和目标值到vofa软件
- }
- #include "main.h"
-
-
- /*******************
- 限幅函数
- ********************/
- int Xianfu(int value,int Amplitude)
- {
- int temp;
- if(value>Amplitude) temp = Amplitude;
- else if(value<-Amplitude) temp = -Amplitude;
- else temp = value;
- return temp;
- }
-
- /******************
- 函数功能:取绝对值
- 入口参数:int
- 返回值 :int
- ******************/
- int myabs(int a)
- {
- int temp;
- if(a<0) temp=-a;
- else temp=a;
- return temp;
- }
-
- //float Err=0,last_err=0,next_err=0,pwm=0,add=0,p=0.9,i=0.3,d=0;
-
- //int16_t myabs(int a) //绝对值函数,传入进来的a为测得的速度speed
- //{
- // int temp;
- // if(a<0) temp=-a;
- // else temp=a;
- // return temp;
- //}
-
- //void pwm_control()//pid限幅函数
- //{
- // if(pwm>999)
- // pwm=999;
- // if(pwm<0)
- // pwm=0;
- //}
-
- //float pid1(int16_t speed1,float tar1)//pid算法
- //{
- // speed1=myabs(speed1);
- // Err=tar1-speed1;
- // add=p*(Err-last_err)+i*(Err)+d*(Err+next_err-2*last_err);
- // pwm+=add;
- // pwm_control();
- // next_err=last_err;
- // last_err=Err;
- // return pwm;
- //}
-
-
- /**************************************************************
- 函数功能:增量PI控制器
- 入口参数:编码器测量值,目标速度
- 返回 值:电机PWM
- 根据增量式离散PID公式
- pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)+Kd[e(k)-2e(k-1)+e(k-2)]
- e(k)代表本次偏差
- e(k-1)代表上一次的偏差 以此类推
- pwm代表增量输出
- 在我们的速度控制闭环系统里面,只使用PI控制
- pwm+=Kp[e(k)-e(k-1)]+Ki*e(k)
- //增量式的pi控制应该如上所示
- ****************************************************************/
- int Incremental_PI (int Encoder,int Target)
- {
- static float Bias,Pwm,Last_bias,next_bias;//bias=Err
- Encoder=myabs(Encoder);
- Bias=Target-Encoder;//计算偏差
- // Integral_bias+=Bias;
- // Integral_bias=Xianfu(Integral_bias,5000);
- Pwm+=(Velocity_KP*(Bias-Last_bias)+Velocity_KI*Bias+Velocity_KD*(Bias+next_bias-2*Last_bias)); //增量式PI控制器
- next_bias = Last_bias;
- Last_bias=Bias;
- // if(Pwm>999)Pwm=999;
- // if(Pwm<-999)Pwm=-999;
- Xianfu(Pwm,99);
- return Pwm;
- }
-
-
- /*********************************************************************
- 函数功能:位置式PID控制器
- 入口参数:编码器测量位置信息,目标位置
- 返回 值:电机PWM
- 根据位置式离散PID公式
- pwm=Kp*e(k)+Ki*∑e(k)+Kd[e(k)-e(k-1)]
- e(k)代表本次偏差
- e(k-1)代表上一次的偏差
- ∑e(k)代表e(k)以及之前的偏差的累积和;其中k为1,2,,k;
- pwm代表输出
- ********************************************************************/
- int Position_PID (int position,int target)
- {
- static float Bias,Pwm,Integral_bias,Last_Bias;
- Bias=target-position;
- Integral_bias+=Bias;
- Integral_bias=Xianfu(Integral_bias,myabs(Target_Velocity));
- Pwm=Position_KP*Bias+Position_KI*Integral_bias+Position_KD*(Bias-Last_Bias);//位置式PID控制器
- Last_Bias=Bias;
-
- if(Pwm>10000)Pwm=10000;
- if(Pwm<-10000)Pwm=-10000;
- return Pwm;
- }
-
-
- void Set_Pwm(int moto)
- {
- if(moto<0)
- {MOTOR_BACK;}
- else
- {MOTOR_GO;}
- PWM_SetCompare1(moto);
-
-
-
- }
-
-
- #ifndef __PID_H
- #define __PID_H
-
- //float pid1(int16_t speed1,float tar1);
- int Xianfu(int value,int Amplitude);
- int myabs(int a);
- int Incremental_PI (int Encoder,int Target);
- int Position_PID (int position,int target);
- void Set_Pwm(int moto);
- #endif
- /* USER CODE BEGIN Includes */
- #include "oled.h"
- #include "oledfont.h"
- #include "pwm.h"
- #include "pid.h"
- #include "stdio.h"
- /* USER CODE END Includes */
- /* USER CODE BEGIN EFP */
- extern float Position_KP,Position_KI,Position_KD;
- extern float Velocity_KP,Velocity_KI,Velocity_KD;
- extern int16_t encoder_counter;
- extern int Target_Velocity,Target_Position;
-
- #define MOTOR_GO HAL_GPIO_WritePin(GPIOA, AIN1_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(GPIOB, AIN2_Pin, GPIO_PIN_SET)
- #define MOTOR_BACK HAL_GPIO_WritePin(GPIOA, AIN1_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOB, AIN2_Pin, GPIO_PIN_RESET)
- /* USER CODE END EFP */
步骤:
(1)速度环p、i、d各值先赋为0,先调p值(比例系数),整个过程只调p、i的值即可。将编码器获取到的脉冲数encoder_counter(实际值)和目标值Target_Velocity打印到vofa中去,观察encoder_counter值靠近Target_Velocity的趋势,响应太慢则加大p值,比例系数是构建输入与输出的线性关系的。
(2)当encoder_counter值靠近但不超过Target_Velocity时,p值就差不多调好了,此时通过调大i值来使encoder_counter值更加靠近Target_Velocity,i值一般以0.001为单位来加。
(3)结果:最后encoder_counter值可以比较快地响应到Target_Velocity值时,并且给轮子阻力时,轮子不会停止转动,则p、i两值就调的差不多了。
按照我配置的定时器中断和PWM情况,将速度PID系数调成以下值得到的电机转动结果比较合适。
Velocity_KP=4.5,Velocity_KI=0.1,Velocity_KD=0;
具体调参的效果和教程大家可以阅读一下这篇文章:
pid就是一个控制算法,在掌握如何使用编码器电机以后,速度环的学习实际上就是在电机上加一个速度控制罢了,通俗的说就是加几行代码,难度不大。
码字不易,希望喜欢的小伙伴别忘了点赞+收藏+关注,你们的肯定就是我创作的动力。
欢迎大家积极交流,本文未经允许谢绝转载!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。