基于HAL库智能小车PID控制25CM直线追踪程序设计

目录

一.轻松拿捏PID算法逻辑

1.一丁点理论知识

2.你应该这样理解P-I-D算法

二.小车距离闭环控制-超声波HC-SR04

1.介绍

1.查询方式

2.捕获模式

三.小车硬件电路搭建

四.精讲PID代码

五.PID参数调节一般规律

六.视频欣赏


 

一.轻松拿捏PID算法逻辑

1.一丁点理论知识

PID 既:Proportional (比例), Integral(积分), Differential(微分)的缩写.PID控制的实质就是根据输入的偏差值,按照比例,积分,微分的函数关系进行运算,运算结果用以控制输出.

位置型PID控制算式,第K次采样时PID的输出为:u(k)=K_Pe(k)+K_I\sum_{i=0}^{k}e(i)+K_D[e(k)-e(k-1)]

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衰减曲线法整定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直线追踪智能小车_哔哩哔哩_bilibili

PID 控制25CM直线追踪智能小车

如有错误,欢迎指正,如有帮助,不胜荣幸。

参考文献: 胡乱捣鼓03——PID定身12cm直线追踪小车做起来~_捌肆幺幺的博客-CSDN博客_pid控制小车跑直线程序

  • 4
    点赞
  • 75
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
前言: 开始之前先要说为什么要采用PID的算法来控制小车。玩过小车的DIY爱好者们都会碰到这样一种情况:为什么本该直线行驶的小车着轨迹就会发生偏移,即所谓的“不直”。 小车不直的原因有:两个电机本身的驱动特性不可能完全相同,两个电机外形大小不可能是完全一致,组装时精度也会出现差异,另外轮胎在滚动时打滑、遇到细小的障碍物等因素都会造成左右轮的速度出现差异,从而不直。开环控制是无法消除左右轮的速度误差的,因为上述的扰动是随机的。 要想小车一条直线,唯有实现闭环控制,当小车受到扰动时能对左右轮及时给予反馈,修正两轮的速度偏差,从而可以出一条直线PID算法就是一种闭环控制算法,实现PID算法需得从硬件上实现闭环控制,即存在反馈,所以我采用的是带测速装置的电机。 项目简介: 本项目采用的是PID控制算法来修正小车行时两轮的速度偏差,实现小车可以直线。小车是使用一个安卓App来控制小车的行路径,App通过App Inventor2来进行编写。 完成作品图: 需要用到的材料: 1. Arduino Uno 2. Arduino Uno的扩展板 3. DFRobot L298 双路2A直流电机驱动板 4. HC-05或HC-06的蓝牙模块 5. 坦克小车底盘 6. 两个带霍尔传感器的电机 7. 锂电池 8. 杜邦线若干 软件部分: 1. Arduino IDE 2. App Invent 附件内容截图:
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值