资讯详情

循迹小车项目

前言

这篇很久以前写的文章有很多问题。还有那么多人喜欢。两年后,也就是2022年,我决定重新修改文章,补充一些内容并提供它C 参考代码,不C 可以看看之前的C代码,但是写的有点烂,语法有点错,不容易读懂。


Arduino程序简单,包装了很多函数,IDE设计也足够合理,非常适合新手入门Arduino,下面用一个Arduino实现跟踪小车。内容包括两个模拟量跟踪小车,数字量跟踪小车,PID控制,电机控制。跟踪车是新手比较简单的项目。如果你赢了文章的内容,你就赢了简单的控制项目!

如果你想使用其他芯片,比如STM32,C51等可以参考,学习思路就可以了!


一、硬件的选择

其实Arduino很多程序都可以,选择合适的控制主板。然后根据相关方案连接相关传感器和驱动器OK。

  • 我选的开发板 arduino uno 在这里插入图片描述

  • 循迹模块

    • 5路红外数字量跟踪

    • 两个灰度跟踪

  • L298N一个

  • TT马达2个

二、放置相关硬件

黑线是图中的虚线。一般来说,传感器的相对位置相对简单合适。图中的圆蓝色形状是传感器的位置,其他四个圆形的是汽车!图片更抽象,但几乎无法理解(つ﹏?)

三、补充额外知识

使用循环模块

使用只是了解它的简单原理和Arduino如何简单地初始化和使用?

5路循迹模块

原理:根据不同材料吸收光的强度,根据反射光的强度输出数字量。例如,黑色,吸光强,输出数1,白色吸光弱,输出数0。

简单使用:

////数字量引脚初始化 void SensorPinInit() { 
           pinMode(2, INPUT );   pinMode(3, INPUT );   pinMode(4, INPUT );   pinMode(7, INPUT );   pinMode(8, INPUT ); }  /*读取引脚的值*/ int SensorPinRead(uint8_t pin) { 
          return digitalRead(pin); } 

灰度传感器模块

原理:根据白光源(发光二极管)照射到不同颜色的物体,光敏电阻感觉光强不同,输出不同的模拟量。

简单使用:

///模拟量引脚初始化,ADC void SensorPinInit() { 
          ; }  /*读取引脚的值*/ int SensorPinRead(uint8_t pin) { 
          return analogRead(pin); } 

PID控制器

PID控制器有点复杂,想要深入理解,自己找相关文章。PID控制器是可以自己设控制强度的,这就是调参,调参比较麻烦,也请自己去了解啦。文章现在内容太多了,不能承载更多知识,也不利于学习。

简单理解:PID控制器,作用是控制器。能过够根据不同的输入,给出相应的控制值。比如说,我们要控制电机。传感器获取到的一个偏离程度P,P为1,输入到PID控制器,PID控制器输出的值是98,这个98就是需要输出到电机的值。这样电机就能根据PID控制器给出不同的值,做出不同的反应。我们也能够使用PID控制器去控制循迹,根据传感器获取到的值不同,从而传入PID控制器的值也不同,最终电机输出的值跟随变化。达到效果,稳定循迹。

C语言版本:

/*******************PID定义*****************************/
typedef struct
{ 
        
   volatile float   Proportion;             // 比例常数 Proportional Const
   volatile float   Integral;               // 积分常数 Integral Const
   volatile float   Derivative;             // 微分常数 Derivative Const
   volatile int      Error1;                 // Error[n-1]
   volatile int      Error2;                 // Error[n-2]
   volatile int      iError;                 // Error[n]
   volatile int      Error_sum;
} PID;
PID pid_increase;//增量式,位置式初始化
PID* sptr_increase=&pid_increase;//PID初始化
#define SET_POINT 0
/*******************PID定义*****************************/

/*************************************************/
//函数名: PID_Postion 
//作者: anonymous
//日期: 2020.11.12
//功能: 位置式PID控制器
//输入参数:void
//返回值: 返回计算值
/*************************************************/
float PID_Postion (float SetPoint,float CurrentPoint, PID* sptr)//速度PID
{ 
        
  float  iIncpid=0;
  sptr->iError=SetPoint-CurrentPoint;                                     // 计算当前误差
  sptr->Error_sum+=sptr->iError;
  iIncpid=sptr->Proportion * sptr->iError                  // P
         +sptr->Integral * sptr->Error_sum                // I
         +sptr->Derivative * (sptr->iError-sptr->Error1); // D
  sptr->Error1=sptr->iError;          // 存储误差,用于下次计算 
  iIncpid=PID_OutputLimit(iIncpid);//在其他地方进行了限幅处理,此处就不用了 
  return(iIncpid);          // 返回计算值
}

/*************************************************/
//函数名: PID_Init
//作者: anonymous
//日期: 2020.11.12
//功能: PID初始化
//输入参数:void
//返回值: void
/*************************************************/
void PID_Init(PID *sptr)
{ 
        
    sptr->Derivative=0;//Kd,加快反应速度,更容易超调,曲线更稳
    sptr->Proportion=0;//Kp,KP,比例系数,30有点太大
    sptr->Integral=0;//Ki,消除静差,直线更稳,
    sptr->Error2=0;//第二次误差
    sptr->Error1=0;
    sptr->iError=0;
    sptr->Error_sum=0;//第一次误差
}
/*************************************************/
//函数名: PID_OutputLimit
//作者: anonymous
//日期: 2020.11.12
//功能: pid输出限幅
//输入参数:void
//返回值: output
/*************************************************/
float PID_OutputLimit(double output)  
{ 
         
    
    if ((int)output < -500)
    { 
        
        output = -500;
    }   
    else if ((int)output > 500)
    
    { 
        
        output = 500;
    }
    return output;
}

C++使用例子

class pidctr /*PID控制器对象*/
{ 
        
public:
	pidctr()
	{ 
        
		position.Derivative=0.1;//Kd,加快反应速度,更容易超调,曲线更稳
		position.Proportion=1.0;//Kp,KP,比例系数,30有点太大
		position.Integral=0.11;//Ki,消除静差,直线更稳,
		position.Error2=0;//第二次误差
		position.Error1=0;
		position.iError=0;
		position.Error_sum=0;//第一次误差

		MatOut = 65535;
		Setpoint =0;
	}
	~pidctr()
	{ 
        
		
	}
	void PidInit(int setpoint,int maxout)
	{ 
        
		MatOut=maxout;
		Setpoint=setpoint;
	}
	/*设置PID控制器的值*/
	void SetPid(float p,float i,float d)
	{ 
        
		position.Derivative=d;
		position.Proportion=p;
		position.Integral=i;
	}
	float PID_Postion (int CurrentPoint)
	{ 
        
		float  iIncpid=0;
		position.iError=Setpoint-CurrentPoint;    // 计算当前误差
		position.Error_sum+=position.iError;
		iIncpid=position.Proportion * position.iError                  // P
				+position.Integral * position.Error_sum                // I
				+position.Derivative * (position.iError-position.Error1); // D
		position.Error1=position.iError;          // 存储误差,用于下次计算 
		
		/*限幅处理*/
		if(abs(iIncpid)>MatOut)  
		{ 
        
			iIncpid=iIncpid>0?MatOut:-MatOut;
		}     
		return iIncpid;          // 返回计算值
	}

private:
	/*******************PID定义*****************************/
	typedef struct
	{ 
        
		float   Proportion;             // 比例常数 Proportional Const
		float   Integral;               // 积分常数 Integral Const
		float   Derivative;             // 微分常数 Derivative Const
		int      Error1;                 // Error[n-1]
		int      Error2;                 // Error[n-2]
		int      iError;                 // Error[n]
		int      Error_sum;
	}PID;

	PID position;//位置式PID
	int MatOut; /*设定输出最大值*/
	int Setpoint;/*PID控制器设定值*/
};

/*使用例子*/
void Simple()
{ 
        
	int result=1;
	/*控制器部分*/
	pidctr mctr;
	mctr.PidInit(0,100);/*初始化*/
	result=mctr.PID_Postion(result);
}

L298N模块简单理解

L298N是一个的模块,我们需要这个模块来控制电机。没有它,TT马达就不能够被控制。我们能够从arduino输入模拟量和数字量到这个驱动模块,通过它我们就能够间接控制,电机的转向,停止和速度了。具体怎么使用,自己搜索啦,也不复杂。

四、循迹方案

5路数字量循迹方案

最简单的循迹方案,就是检测精度低。具体怎么理解,看图就行啦,不难理解。图中蓝色为传感器,检测到黑线就会为黑,相关传感器就会输出对应值。文章没有把全部情况列举出来,只是列举了一些情况,但也能够理解就行。

5路数字量循迹方案

图解:循迹模块检测到黑线标黑 其他检测同理 5路数字量循迹检测代码 C语言版本是2年前写的,代码可读性不怎么好,但思路是没有变化的,建议看C++的!

//检测到黑线输出高,检测不到输出低
#define NOT_GETFLAGE 1
#define GETFLAGE 0

int sensor[5] = { 
        NOT_GETFLAGE, NOT_GETFLAGE, NOT_GETFLAGE, NOT_GETFLAGE, NOT_GETFLAGE}; //5个传感器数值的数组 
/*************************************************/
//函数名:read_sensro_init 
//作者: anonymous
//日期: 2020.11.12
//功能: 数字量循迹初始化,初始化引脚
//输入参数:void
//返回值: void
/*************************************************/
void read_sensro_init()
{ 
        
  pinMode(2, INPUT );
  pinMode(3, INPUT );
  pinMode(4, INPUT );
  pinMode(7, INPUT );
  pinMode(8, INPUT );
}
/*************************************************/
//函数名: read_sensor_values
//作者: anonymous
//日期: 2020.11.12
//功能: 读取偏差值
//输入参数:void
//返回值: 返回偏差值
/*************************************************/
int read_sensor_values()//读取偏差值
{ 
        
  //从左到右的检测模块,依次标为0,1,2,3,4
  //左偏设置值为正,右偏设置值为负
  sensor[0] = digitalRead(2);
  sensor[1] = digitalRead(3);
  sensor[2] = digitalRead(4);
  sensor[3] = digitalRead(7);
  sensor[4] = digitalRead(8);

  //赛道检测 
  //**********************************无偏差检测***************************************************//
  //没有偏移情况,直线
  if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==NOT_GETFLAGE)&&(sensor[2]==GETFLAGE)&&(sensor[3
      ]==NOT_GETFLAGE)&&(sensor[4])==NOT_GETFLAGE) Error0=0; //正好处于中间,没有偏离。
  //特殊情况,不好判断有没有偏移,直接算没有偏移,先直走一段
  if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==GETFLAGE)&&(sensor[2]==GETFLAGE)&&(sensor[3
      ]==GETFLAGE)&&(sensor[4])==NOT_GETFLAGE) Error0=0;
  
  //**********************************左偏检测***************************************************//
  if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==GETFLAGE)&&(sensor[2]==NOT_GETFLAGE)&&(sensor[3
      ]==NOT_GETFLAGE)&&(sensor[4])==NOT_GETFLAGE)  Error0=1; //左偏移直线黑线,但方向是直线,算小左偏
  
  if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==GETFLAGE)&&(sensor[2]==GETFLAGE)&&(sensor[3
      ]==NOT_GETFLAGE)&&(sensor[4])==NOT_GETFLAGE)  Error0=2;//中左偏
  
  if((sensor[0]==GETFLAGE)&&(sensor[1]==GETFLAGE)&&(sensor[2]==NOT_GETFLAGE)&&(sensor[3
      ]==NOT_GETFLAGE)&&(sensor[4])==NOT_GETFLAGE)  Error0=3;   //大左偏
  
   if((sensor[0]==GETFLAGE)&&(sensor[1]==NOT_GETFLAGE)&&(sensor[2]==NOT_GETFLAGE)&&(sensor[3
      ]==NOT_GETFLAGE)&&(sensor[4])==NOT_GETFLAGE)  Error0=4;   //大大左偏
    
  //**********************************右偏检测***************************************************//
  if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==NOT_GETFLAGE)&&(sensor[2]==NOT_GETFLAGE)&&(sensor[3
      ]==GETFLAGE)&&(sensor[4])==NOT_GETFLAGE)  Error0=-1;  //右偏移直线黑线,但方向是直线,算小右偏
  
  if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==NOT_GETFLAGE)&&(sensor[2]==GETFLAGE)&&(sensor[3
      ]==GETFLAGE)&&(sensor[4])==NOT_GETFLAGE)  Error0=-2;//中右偏
  
  if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==NOT_GETFLAGE)&&(sensor[2]==NOT_GETFLAGE)&&(sensor[3
      ]==GETFLAGE)&&(sensor[4])==GETFLAGE)  Error0=-3;  //大右偏
  
  if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==NOT_GETFLAGE)&&(sensor[2]==NOT_GETFLAGE)&&(sensor[3
      ]==NOT_GETFLAGE)&&(sensor[4])==GETFLAGE)  Error0=-4;  //大大右偏
  return Error0;
}
/*************************************************/
//函数名: gate_averge
//作者: anonymous
//日期: 2020.11.12
//功能: 均值平均滤波法,多次读取偏差值,减少误差 
//输入参数:void
//返回值: 返回偏差值
/*************************************************/
int gate_averge()
{ 
        
 //均值滤波 
  float temp=0,erroor[5],error_sum=0;
  int i;
  for(i=0;i<5;i++){ 
        
  erroor[i] =read_sensor_values();//得到偏差值
  }//得到偏差值 

//累加偏差求平均值
  for(i=0;i<5;i++)       error_sum=error_sum+erroor[i];
  error_sum=error_sum/i;
  return error_sum;
}

/*使用例子*/
void Simple()
{ 
        
	int result;
	read_sensro_init();
	
	result=gate_averge();
}

//作者: Silent Knight
//日期: 2022.7.4
//检测到黑线输出高,检测不到输出低
#define NOT_GETFLAGE 1
#define GETFLAGE 0

#define MAX_SENSOR 5 //最大传感器数

/*定义循迹的对象,目前仅仅支持5个传感器*/
class Track
{ 
        
public:
	Track()
	{ 
        
		int i;
		for(i=0;i<MAX_SENSOR;i++) 
		{ 
        
			sensor[i] = NOT_GETFLAGE;//初始化传感器数据
			use_pin[i] = 0;/*初始化使用到的引脚*/
		}
		number_sensor=0;
	}
	~Track()
	{ 
        
		;
	}
	/*选择使用引脚,请按照从左到右的顺序初始化*/
	/*目前仅支持5个pin*/
	void SensorPinInit(uint8_t pin, uint8_t mode)
	{ 
        	
		use_pin[number_sensor++]=pin;/*记录每一个使用到的引脚*/
		pinMode(pin,mode);
	}
	/*读取位置偏离程度*/
	/*返回值左偏设置值为正,右偏设置值为负*/
	int SensorPoint(int logic)//读取偏差值无滤波
	{ 
        
		int i,point=0;
		for(i=0;i<number_sensor;i++)
		{ 
        
			sensor[i]=SensorPinRead(use_pin[i]);/*从左到右读取引脚数据*/
		}
		//赛道检测 
		//**********************************无偏差检测***************************************************//
		//没有偏移情况,直线
		if((sensor[0]==NOT_GETFLAGE)&&(sensor[1]==NOT_GETFLAGE)&&(sensor[2]==GETFLAGE)&&(sensor[3
			 

标签: 传感器归位

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台