目录
前言
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显示屏的使用
本文内容是在前面代码的基础上进行编写的。
速度环用的是增量式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调参
pid就是一个控制算法,在掌握如何使用编码器电机以后,速度环的学习实际上就是在电机上加一个速度控制罢了,通俗的说就是加几行代码,难度不大。
码字不易,希望喜欢的小伙伴别忘了点赞+收藏+关注,你们的肯定就是我创作的动力。
欢迎大家积极交流,本文未经允许谢绝转载!!!