前言
这篇很久以前写的文章有很多问题。还有那么多人喜欢。两年后,也就是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