循迹小车
闲来无事,分享一下之前做的51循迹小车画不多说上视频
注:本博客适合初学51的同学在制作51时学习,和学习简单的pid算法的同学阅读
1.硬件 (驱动是l298n)
因为51比较简单,所以我们当时是一个人做,还没有组队,所以我会从头讲一次
先了原理图吧
建议大家选择常规封装,不然焊板子的时候非常痛苦,开版时先在某宝看看有没有相应的
下面是对这些模块的一些见解:1.3v3是给芯片供电的,不能直接使用外部高电源,可能会造成芯片的损坏
2.驱动接口是留给l298n的,大家用的其他的话可以改一下,不过我还是建议初学者用l298n
3.按键:对于按键,我原本是想做功能按键的,但后面没时间就没做了,功能按键就是按一下可以改变我们的一些参数,就不要每次都要连到电脑上来修改
4.屏幕:用来观测采集到的值 (链接:链接:https://pan.baidu.com/s/16AjNOOOeIg5bSxDzk7xyWg
提取码:1234)
当时我用的5个红外模块,这里推荐大家使用一个集成的5路红外,要好用稳定一点
这里建议大家预留几个gnd和vcc(图一的右上角) 这样可以以防万一
对于小车主要通过灰度,pid算法,驱动和一些高级功能组成
其中高级功能包括标志位判断,停车,差速转弯,希望可以帮到大家。
2.信号捕获
其实红外和灰度是一个原理,就是将模拟信号转换为数字信号,在识别到黑线时会有高电平变为低电平,出入到io口中,我们可以将每个端口采集到的数据放到一个数组中储存起来,然后经过运算,判断两边的值是否相等,从而判断小车的行驶状态。
比如:我们有4个端口,如果左边为0,右边为一说明这时候需要左转弯,那么我们就可以将左边值减右边值,显然结果是负的,那么我们就可以写当结果是负时向左转。
#include "gray.h"
#include <STC12C5A60S2.H>
sbit N1=P1^4;
sbit N2=P4^3;
sbit N3=P1^1;
sbit N4=P1^0;
extern char gray[4];
float gray_rate[4]={3,1,1,3};
extern float gray_data;
extern char road_state;
void gray_detect(void)
{
gray[0]= N1;
gray[1]= N2;
gray[2]= N3;
gray[3]= N4;
gray_data = gray_rate[0] * gray[0]+gray_rate[1]*gray[1]-gray_rate[2]*gray[2]-gray_rate[3]*gray[3];
}
我将单片机上的四个口用n1,n2,n3,n4代替了一下。
3.驱动模块
对于驱动我们可以先看一下l298n这是我们常用的电机驱动模块,这也是非常适合初学者使用的模块
1.在使能通道上插帽没有拔掉的情况下,电机全速转动。
2.拔掉使能通道插帽,这样就能通过输入PWM波,改变电机转速。 注意:PWM相同的情况下,输入电压不同,电机转速也会不同,因此我们的小车在电压较低的情况下,运动可能会出现问题
我们可以先将每个轮子的模式都给初始化,方便我们待会使用
void left_on(void)
{
P22 = 0;
P23 = 1;
}
void left_back(void)
{
P22 = 1;
P23 = 0;
}
void right_on(void)
{
P24 = 1;
P25 = 0;
}
void right_back(void)
{
P24 = 0;
P25 = 1;
}
这是我们只能控制轮子的前转和后转,然后我们需要使用pwm波来实现轮子的不同转速
那如何生成一个PWM波?
在 PWM 发生器中,通常会有一个计数器和一个数值比较器。 计数器不断自加,当计数器的值小于比较值时,电平为低,反之则为高。 例如:计数器从0-10为一个脉冲周期,假设数值比较器值为5,那么,当计数器的值为0-4时,该电平为低电平,计数器的值为5-10时,电平为高电平。 此时,该PWM波的占空比为50%。
首先我们先写一个中断,因为这个需要不停的判断和执行,放在while里面较大,while里面一般放oled这类持续固定的东西。
首先是主函数
void TimerInit(void) //定时器0 100微秒@11.0592MHz
{
TL1 = 0xA4; //设置定时初始值
TH1 = 0xFF; //设置定时初始值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
TMOD = 0X11;
TL0 = 0x00; //设置定时初始值
TH0 = 0xDC; //设置定时初始值
TF1 = 0; //清除TF1标志
TR1 = 1;//定时器1 10毫秒
}
void main (void)
{
tmp1 = pwm_cmp_left;
tmp2 = pwm_cmp_right;
TimerInit();
ET0 = 1;
ET1 = 1;
EA = 1;
while(1)
{
;
}
}
我们先初始化pwm。
int pwm_cmp_left=12;//比较值
int pwm_cmp_right=18;
int tmp1 = 0, tmp2 = 0;
int pwm_cnt_left=0;
int pwm_cnt_right=0;
void motor_run(void) interrupt 3
{
pwm_cnt_left++;
pwm_cnt_right++;
if (pwm_cnt_left >= 100 ) pwm_cnt_left = 0;
if (pwm_cnt_right >= 100 ) pwm_cnt_right = 0;
if(pwm_cnt_left < pwm_cmp_left ) P17 = 1;
else P17 = 0;
if(pwm_cnt_right < pwm_cmp_right ) P16 = 1;
else P16 = 0;
TL1 = 0xA4; //设置定时初始值
TH1 = 0xFF; //设置定时初始值
}
这段是控制其占空比,我们在初始化时将比较值赋予了一个初值(这个初值是根据大家的实际情况进行调试的,大家可以先尝试随意写一个,然后将其放在平地上,看他是否可以直线行驶,因为有些左右轮有点不一样,我就是有个轮子有问题,两个值不一样)
在 pwm_cnt_left++; 和pwm_cnt_right++;不断自加的过程中,当他小于我们的比较值时候,那么其输出高电平;
即占空比=比价值/100;
4.pid算法
好了,现在数据采集了,车也动起来了,假如我们在转弯时改变两轮的占空比,比如我们在左转的时候将左轮的比较值减小,右轮比较值增大,即左轮减速,右轮加速,实现差速抓弯。
那么怎么实现呢,在之前我们已经讲到我们计算了两边的差值gray——data,当其不等于零时我们就可以知道其应该转弯,这个时候我们就可以使用pid算法来实现我们到底应该改变多少比较值,使两边的转速不一样,来实现差速转弯
首先是pid算法(我这里只使用了比例项,这个用不到微分和积分有能力的同学可以将其完善一下,但其实这种循迹比例项就够了)
我们先初始一下pid函数
#include "pid.h"
#include <STC12C5A60S2.H>
extern float gray_data;
float Kp=5.5;//,Ki=0,Kd=0;
float pid_out(void)
{
float err=0;
float target=0,result=0;
//static float sum_err=0,last_err=0;
err=gray_data-target;
result=-Kp*err;//+Ki*sum_err+Kd*(err-last_err);
if(result > 45 ) result = 45;
if(result < -55 ) result = -55;
// sum_err+=err;
// last_err=err;
return result;
}
这个时候将pid用到中断中来改变比较值(我们比赛有一段要响蜂鸣器,我还加了一个标志位)
1.引用pwm_out = (int)pid_out();函数,来使用pid调整pwm_out的值
2. pwm_cmp_left = tmp1 + pwm_out;然后将比较值加上调整值,来改变占空比
3.接下来几段是防止调整过大,主要是怕他脱离较大直接调出去了
void pid_ctrl(void) interrupt 1
{
int pwm_out = 0;
static int time = 0;
gray_detect();
road_detect();
if(road_state == 0 && state == 0)
{
pwm_out = (int)pid_out();
}
if(road_state == 1 && state == 0)
{
road_flag++;
if(road_flag == 1)
{
state = 1;
}
if(road_flag == 2)//
{
state = 2;//
}
}
if(state == 1)
{
P37 = 0;//
time++;
gray_data=8;
pwm_out = (int)pid_out();
if(time >= 8)
{
road_state = 0;
time = 0;
state = 0;
P37 = 1;
}
}
if(state == 2)//
{
P37=0;//
time++;
gray_data=-8;
pwm_out = (int)pid_out();
if(time >= 8)
{
road_state = 0;
time = 0;
state = 0;
P37 = 1;
}
}
if(state == 3)//
{
P37=0;//
time++;
gray_data=8;
pwm_out = (int)pid_out();
if(time >= 8)
{
road_state = 0;
time = 0;
state = 0;
P37 = 1;
// road_flag = 0;//
}
}
pwm_cmp_left = tmp1 + pwm_out;
pwm_cmp_right = tmp2 - pwm_out;
if(pwm_cmp_left < 5) pwm_cmp_left -= 15;
if(pwm_cmp_right < 5) pwm_cmp_right -= 15;
if(pwm_cmp_left >= 0) left_on();
else left_back();
if(pwm_cmp_right >= 0) right_on();
else right_back();
pwm_cmp_left = my(pwm_cmp_left);
pwm_cmp_right = my(pwm_cmp_right);
TL0 = 0x00; //设置定时初始值
TH0 = 0xDC; //设置定时初始值
}
其他的部分相信大家都理解,
我来说明一下标志位(上述的time和if内的内容)
标志位即测量通过莫一个位置时候传感器的数据,然后当再次识别到相同的情况时,说明到达了相同的位置(我们在通过交叉路口时要求第一次和第二次要通过不同的方向)
在传感器中,我将在十字路口转弯的情况作了一下处理,即都识别到,相加等于4的情况,如果识别到,则可以直接给他向左或者右的指令,我们则可以使用标志位,在这种情况下加1,当下一次识别到时就等于2,再将1,2设为不同的模式,就可以实现两次向不同的方向转弯(注意:当我们需要入库时,我们也可以使用标志位,这里就不详述了)
void road_detect(void)
{
if(gray[0] + gray[1]+ gray[2]+ gray[3] == 4 )
{
road_state = 1; //TT
}
int my(int n)
{
if (n<0) n=-n;
return n;
}
最后是整个小车的代码链接我放在百度网盘了,需要的同学自取:https://pan.baidu.com/s/17PjRNyPyNnVpGedIiIk7vg
提取码:1234