【手把手带你用pid算法控制电机】——(2)PID速度环

目录

前言

 1. PID速度环原理

2. 代码

2.1 main.c

2.2 pid.c

2.4 pid.h

2.5 main.h

 3. pid调参

 按照我配置的定时器中断和PWM情况,将速度PID系数调成以下值得到的电机转动结果比较合适。

 Velocity_KP=4.5,Velocity_KI=0.1,Velocity_KD=0; 

 4. 小结


前言

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显示屏的使用

本文内容是在前面代码的基础上进行编写的。

 1. PID速度环原理

速度环用的是增量式PID算法,什么是增量式PID在我的这篇文章:位置式Pid和增量式Pid的定义及应用

已经讲的比较清楚,这里不做多余阐述。

2. 代码

2.1 main.c

(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软件
  }

2.2 pid.c

#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);
	
	
	
}


2.4 pid.h

#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

2.5 main.h

/* 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 */

 3. pid调参

步骤:

(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调参

 4. 小结

pid就是一个控制算法,在掌握如何使用编码器电机以后,速度环的学习实际上就是在电机上加一个速度控制罢了,通俗的说就是加几行代码,难度不大。

码字不易,希望喜欢的小伙伴别忘了点赞+收藏+关注,你们的肯定就是我创作的动力。

欢迎大家积极交流,本文未经允许谢绝转载!!!

你可能感兴趣的:(PID学习笔记,单片机,stm32,学习,算法)