目录
一.轻松拿捏PID算法逻辑
1.一丁点理论知识
PID 既:Proportional (比例), Integral(积分), Differential(微分)的缩写.PID控制的实质就是根据输入的偏差值,按照比例,积分,微分的函数关系进行运算,运算结果用以控制输出.
位置型PID控制算式,第K次采样时PID的输出为:
u(k):PID控制器的输出信号
e(t):给定值与测量值之差,也叫误差
这是位置式PID的基本公式,很容易理解,分别使用PID各项系数与误差的积在进行求和运算.
2.你应该这样理解P-I-D算法
PID是算出来的一个值,那么P、I、D都是算出来的值。与其说是比例项,积分项、微分项,不如直接列公式说明更清楚。
P——比例系数(kp > 0)与本次误差(error)的积,也就是P = kp * error。打个比方,小车的目标是走到距离石墙10cm的地方,而小车一开始在距离石墙100cm处,此时以距离10cm作为目标,误差error = 100 - 10 = 90cm,那么P就是kp * 90。好的问题来了,P是算出来了,那怎么用呢?我们知道小车前进的速度是由马达转速决定的,马达的转速又是由PWM来决定的,PWM越大,马达更快,小车速度更快,这时候如果我的P项和PWM是成比例关系的话,那么我的P项就可以用在小车的速度控制上。试想一下,第一次P是kp * 90 ,也就是速度的值是kp * 90 ,第二次因为小车前进了,假设前进了20cm,那么第二次的误差就是80 - 10 = 70cm,所以第二次的P值就是kp * 70 ,速度的值也就是kp * 70,速度变慢了,以此类推直到第N次,小车到达了目的地,error = 0,P=0,小车速度自然也为0。所以总的来说,P就是和电机转速成比例关系的一个值,可以利用这个值来控制电机的转速。
I——积分系数(ki > 0)与误差的积的累积,记住是累积!也就是 I = Σki * error。那么I项算出来又有什么用呢?再打个比方,还是拿上述的小车做例子,假设在第5次计算P时,算出P = 100,也就是电机的PWM值为100,而真实情况下能够驱动电机使小车往前走的最小PWM值是200,此时小车动不了了,也还没达到目的地,这时候I项就发挥作用了。因为还存在误差(没到达目的地),所以 I = Σki * error会一直积累,而在引入I项后电机的PWM = P + I,P因为误差不变而不会再变化了,而I会因为存在误差而一直累加,所以当累积后的PWM值超过200后,小车又能往前走了,直到没有误差。
D——微分系数(kd > 0)与两次误差之间的斜率的乘积,也就是D = kd*((error - error_pre)/Δt)。高中开始学数学开始我们就懂斜率这个东西,表示的是变化的快慢,当然这个D值既然能算出来,那么它有时用来干什么的呢?再举个例子,我们在骑单轮车的时候是怎么平衡自己的,当我要往前面倒的时候我们是不是需要加速往前骑才能保持稳定。同理,现在换成电机驱动单轮车,当我发现两次误差特别大的时候,也就是单轮车要倒了的时候,D值会随着斜率的增大而增大,算出来的PID = P + I +D 也会随之变大,那么电机的PWM值也会变大,电机速度同样增大从而保持平稳状态。
所以总的来说,PID算出来的值是用来驱动PWM电机的,无论什么系统,PID计算的值都对应着电机的PWM。
二.小车距离闭环控制-超声波HC-SR04
1.介绍
采集小车与障碍物之间的距离,有红外,摄像头,超声波之类,采用HC-SR04简单便捷易操作,下面介绍两种方法实现超声波距离测量。一种是查询的方式,另一种是STM32捕获模式,这两种方法都是可以的。对于HC-SR04模块,之前我有在CT107D平台开发板上面实践过,由于不是具体项目,所以遇到的问题很少。在小车应用中,总是接收不到Echo返回信号,导致程序卡死,可引入看门狗解决。但这种方法麻烦而且精度不高,而对于功能强大的STM32来说,采用捕获模式,去捕获高电平持续时间可以大大提高效率。
1.查询方式
CUBE_MX配置
主要程序
#define Trig_H HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET );
#define Trig_L HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_RESET );
void user_delaynus_tim(uint32_t nus)
{
uint16_t differ = 0xffff-nus-5; //设置定时器2的技术初始值
__HAL_TIM_SetCounter(&htim3,differ); //开启定时器
HAL_TIM_Base_Start(&htim3);
while( differ<0xffff-5)
{
differ = __HAL_TIM_GetCounter(&htim3);
}
HAL_TIM_Base_Stop(&htim3); //关闭定时器
}
while (1)
{
Trig_H ;
user_delaynus_tim(12);//维持12us高电平
Trig_L ;
HAL_TIM_Base_Start(&htim3); //开启定时器3
while( HAL_GPIO_ReadPin (GPIOB ,GPIO_PIN_1) != GPIO_PIN_SET);
__HAL_TIM_SetCounter(&htim3,0);//将定时器3初始值清零,以便开始从零计数
while(HAL_GPIO_ReadPin (GPIOB ,GPIO_PIN_1) == GPIO_PIN_SET);//检测到低电平停止计数
HAL_TIM_Base_Stop(&htim3);//关闭定时器3
count = __HAL_TIM_GetCounter(&htim3);//读出计数值
distance = (uint16_t )count*0.017;
OLED_ShowNum(72,0,distance,4,16);
HAL_Delay (500);//两次测量之间间隔时间
}
2.捕获模式
CUBE_MX配置
超声波 .h文件
#ifndef __HCSR04_H
#define __HCSR04_H
#include "main.h"
#include "tim.h"
#include "stdio.h"
#define Tim_option htim2 //定时器选择 **中断中的定时器需要改一下
#define Channel_option TIM_CHANNEL_1 //捕获通道选择
#define TRIG_H HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_SET)
#define TRIG_L HAL_GPIO_WritePin(GPIOA,GPIO_PIN_1,GPIO_PIN_RESET)
void delay_us(uint32_t us);
void SR04_GetData(void);
#endif
超声波 .c文件
#include "hcsr04.h"
#include "oled12864.h"
float distant; //测量距离
uint32_t measure_Buf[3] = {0}; //存放定时器计数值的数组
uint8_t measure_Cnt = 0; //状态标志位
uint32_t high_time; //超声波模块返回的高电平时间
//===============================================读取距离
void SR04_GetData(void)
{
switch (measure_Cnt){
case 0:
TRIG_H;
delay_us(30);
TRIG_L;
measure_Cnt++;
__HAL_TIM_SET_CAPTUREPOLARITY(&Tim_option, Channel_option, TIM_INPUTCHANNELPOLARITY_RISING); //设置为上升沿捕获
HAL_TIM_IC_Start_IT(&Tim_option, Channel_option); //启动输入捕获
break;
case 3:
high_time = measure_Buf[1]- measure_Buf[0]; //高电平时间
// OLED_ShowNum(0,2,high_time,4,16);
distant=(high_time*0.034)/2; //单位cm
// OLED_ShowNum(0,4,distant,4,16);
measure_Cnt = 0; //清空标志位
TIM2->CNT=0; //清空计时器计数
break;
}
}
//===============================================us延时函数
void delay_us(uint32_t us)//主频72M
{
uint32_t delay = (HAL_RCC_GetHCLKFreq() / 4000000 * us);
while (delay--)
{
;
}
}
//===============================================中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)//
{
if(TIM2 == htim->Instance)// 判断触发的中断的定时器为TIM2
{
switch(measure_Cnt){
case 1:
measure_Buf[0] = HAL_TIM_ReadCapturedValue(&Tim_option,Channel_option);
// 获取当前的捕获值.
__HAL_TIM_SET_CAPTUREPOLARITY(&Tim_option,Channel_option,TIM_ICPOLARITY_FALLING);
//设置为下降沿捕获
measure_Cnt++;
break;
case 2:
measure_Buf[1] = HAL_TIM_ReadCapturedValue(&Tim_option,Channel_option);
//获取当前的捕获值.
HAL_TIM_IC_Stop_IT(&Tim_option,Channel_option); //停止捕获
measure_Cnt++;
}
}
}
三.小车硬件电路搭建
小车主控采用STM32最小系统,输出PWM信号控制车速。这里硬件PCB的绘制可以看我上一篇文章,有非常详细的原理图与PCB图纸。
链接:基于STM32 Cortex-M3内核F103制作的智能小车项目_中国龙龙的博客-CSDN博客
耗材:
①STM32C8T6
②四驱动减速电机底盘
③超声波HC-SR04
④直流降压模块XH-M249
⑤电机驱动器L298N
⑥舵机SG90
⑦0.96寸OLED显示
四.精讲PID代码
1.定义变量
int pwm=0; //PWM驱动值
int direction=0; //正反转标志位
float target=25; //小车与障碍物距离
float error; //误差值
float error_pre; //上一次误差值
float Kp=12.5; //比例系数
float Ki=0.15; //积分系数
float Kd=0; //微分系数
float P; //比例数值
float I; //积分数值
float D; //微分数值
float PID; //PID参数的和
extern float distant; //引用超声波函数中的数值
uint16_t csb_distance;//超声波测量实际距离
2.位置式PID核心程序,非常简短,认真读完一定有所收获
//************************************PID核心程序start error=csb_distance-target; //读取误差值 P=Kp*error; //P 得到比例数值 if(error>0 && error<0.8) Ki=0; //设置积分死区,防止抖动 else if(error<0 && error>-0.8) Ki=0; // if(-10 < error && error < 10) I+=Ki*error; //I 得到积分数值 // D=Kd*((error-error_pre)/50); // D=0; //D 得到微分数值 PID = P+I+D; //P+I+D 进行求和运算 if(PID > 0 || PID == 0) //将PID参数进行数值处理,与正反转标记,方便处理 { PID=PID; direction=1;//正传标志位 } if(PID < 0) { PID=-PID; direction=2;//反转标志位 } pwm=PID; //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ if(pwm>60) //限幅 pwm=60; if(pwm<-60) //限幅 pwm=-60; OLED_ShowString(0,6, "PWM:",16); //显示PWM值 OLED_ShowNum(40,6,pwm,5,16); if(direction == 1) //正传 { for_word();//前进 __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,pwm); // 设置PWM1占空比 __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,pwm); // 设置PWM2占空比 } if(direction == 2) //反传 { back_word();//后退 __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_1,pwm); // 设置PWM1占空比 __HAL_TIM_SET_COMPARE(&htim2,TIM_CHANNEL_2,pwm); // 设置PWM2占空比 } //HAL_Delay(30); //************************************PID核心程序end
五.PID参数调节一般规律
PID参数调节
工程上常采用 4:1 衰减曲线法整定PID参数.纯比例度作用下的自动调节系统,在比例度逐渐减小时,出现4:1衰减振荡过程,此时比例度为4:1衰减比例度δs,两个相邻同向波峰之间的距离为4:1衰减操作周期Ts.
调节过程:
一开始,先调P,我只给一个kp值=100,这时候小车前跑跑,后倒倒的,说明冲过了头,然后P值变为了负数置标志位使得电机反转,又向后冲过了头,P值又变为正数,如此反复。所以我有降低P值,直接来个kp = 5,此时小车没有到达目标物就停止了。如此调整,最后得出kp = 12.5 状态较好,直接在25左右停了下来。
接着调I,P调好后再调I,在[0,1]之间选值,那就先让ki = 0.5,小车停下一会后往前,又往后,往前往后过程中都有停顿。这是我们就知道了ki调大了,因为累积的I值过大使得PWM值在较高的值处来回取值,也就是震荡。最后根据返回的数据,取ki = 0.15,且在误差范围为±10cm时积分才起作用,这是为了防止一开始I项偏大。最后的PID值并没有为0,不过也没有什么影响,毕竟小车的PWM值到40小车都不会往前移动.
PID调节的过程需要根据小车的参数,各种因素来确定,在调节的过程中可能会有很多问题,只有根据规律慢慢调节,一定能达到理想的效果.
六.视频欣赏
PID 控制25CM直线追踪智能小车
如有错误,欢迎指正,如有帮助,不胜荣幸。
参考文献: 胡乱捣鼓03——PID定身12cm直线追踪小车做起来~_捌肆幺幺的博客-CSDN博客_pid控制小车跑直线程序