寻迹小车开发日记

时间:2024-03-03 21:39:22

这几天实验室有一个关于大一的比赛,说是让开发一个智能寻迹小车。但是作为大三的我,怎能不给他们做个榜样呢?于是我就默默的自己试着开发了一下这个智能车。经过了十多天的时间(中间一个星期在等原件),第四版寻迹小车制作完成。但是现在我还没有告诉我们实验室大一的学弟我在做这个,让他们自己先琢磨一会。

赛道如下图:

比赛项目的大致情况就是这样。

下面就是我 每一次开发的过程记录,我就直接把当时的笔记摘抄在下面了。

硬件资源:STC89C52RC,12M晶振,LM298N两路驱动,TT马达,四路寻迹模块,整车采用前面两轮驱动,后面一个万向轮结构。

2017.11.15 晚

资料调查:

红外探测传感器由于发出的是红外光,常见光对它的干扰极小,且由于价格便宜,而被广泛应用于智能小车的循线、避障以及其它机器人中物料检测、灰度检测等系统中。

红外光电管有两种:
一种是无色透明的LED,此为发射管,它通电后能够产生人眼不可见红外光;
另一种为黑色不透明LED,为接收部分,它内部的电阻会随着接收到红外光的多少而变化。

检测原理:
由于黑色吸光,当红外发射管照射在黑色物体上时反射回来的光就较少,接收管接收到的红外光就较少,表现为电阻大,经施密特触发器整形后输出高电平;
同理,当照射在白色表面时发射的红外线就比较多,表现为接收管的电阻较小,经施密特触发器整形后输出低电平。
此时再将此电平状态送到单片机的I/O口,单片机就可以判断是黑白路面,进而完成相应的功能,如循迹、避障等。

总结:

黑线------------高电平
白线------------低电平

暂时还没有足够的原件来实际实验,只能通过示波器来查看端口输出的pwm波形是否符合逻辑。

2017.11.18 下午
修改了程序,第二版test_2,示波器输出pwm波很好,经过黑线也能停车,但是现在还没有最小系统版,不能实际实验,只有等到原件全部到了实验一下。感觉还是C语言基础薄弱,在一个文件中知道该怎么写,但是实际中大工程都是分文件编写的,分文件编写的时候就容易出错,变量的调用了就容易搞混掉!!加强C语言的练习!!

2017.11.24 上午
今天终于可以进行实际实验了,但是发现小车很不稳定,主要有一下几点的问题。
1、寻迹的时候会冲出跑道。特别时转向的时候。
2、由于寻迹模块安装的位置不合适,导致中间直线位置,三个红外对管都没有检测到黑线,中间停车。
3、还有不知道哪里一直在响?电容?(现在知道了,其实是电机在响,当输出的pwm占空比不足以驱动电机转动时,电机会一直有很小的响声,是电机一直在抖动状态导致的,可增占空比)
我觉得关键是红外对管安装不合理。另外单片机响应太慢?

2017.11.25 上午
刚开始用两节18650驱动L298N,能提供大概7.9V的电压,实测输入7.9V,输出给单片机的5V,但是一直出现L298N一直响的问题,电机转动很慢,响应也很慢,可能是驱动电压不够,因为L298N上面也是写了要求输入12V电压。为什么输入12V还是响?(我当时竟然以为是驱动芯片在响)

其实是中断处理越快越好。比如,当右侧红外对管检测到黑线时,说明车体偏左,此时急需右转弯,芯片的处理速度能保证右侧对管一检测到黑线立马给响应的变量赋予响应的值,就这种情况来说,程序立马执行:

right_duty = 0;    //3,占空比太低。,基本不转    
eft_duty = low_speed+1;

这段话,给这两个变量赋值。但是真正执行小车转弯的程序段在中断入口函数里面:

right_cnt++;//右轮pwm
if(right_cnt <= right_duty){
right_go_ahead;
}
else{
right_stop;
}
if(right_cnt==cycle) //只允许20次中断
{    
right_cnt=0;
}

在这段程序里面完成pwm波的输出 。
但是如果定时器定时时间过长的话,就需要比较长的时间才能进入中断函数,才能比较right_cnt和right_duty的大小,进而才能决定电机的前进和停止。那么问题就来了,在定时器的这段时间里,小车有没有已经跑出轨道了呢?这就要看“运气”了。因为定时器是与程序主循环分开的,主循环的运行和定时器的计数是同时进行的。如果运气好的话,当检测到右侧黑线的时候,刚好定时器快要溢出,那么right_duty 和left_duty 一旦被赋值,正好这时定时器产生溢出,进入中断函数,执行响应的动作,也许能修正偏离的轨迹。但是如果你运气不好的话,正好上一个中断刚刚结束,下一个中断刚刚开始,这时你就需要等待一个定时器的周期才能执行相应的动作。在单片机执行这个动作之前,他会延续上一个状态运行。关键就是在等待的这段时间里,小车有没有偏离轨道。偏离了,失败;没偏离,修正回来,继续寻迹。其他情况同理可分析。
但是在一个程序里面,我们不允许有“运气”成分的存在,我们要的是百分之百的按照我们设想的方法去执行。那么就考虑能不能缩短定时器的时间来减小“运气”的存在。但这同时也带来了另一个问题,定时器频繁的打断主程序的运行。导致运行效率的变慢。(在我的这个程序里,定时器设置的50us,一次主循环里面要被中断打断3次,可以说比较频繁了)
那么此时就要靠自己的统筹规划了,找到两者之间的平衡点,既要保证动作的及时响应,又要不能过多的影响主程序的运行。

其实有时候也要考虑一下物理因素,不要总是从程序上找毛病。我这个小车的轮胎很滑,基本没有什么抓地力,转向的时候,很容易就冲出轨道,换了摩擦力比较大的轮胎之后,效果明显好转,但是程序还是不够好,还是会出现“出轨现象”,比之前好了一点点。

2017.11.26 下午
到了第三版,现在小车终于能稳定寻迹了。之前是三个寻迹口,可能是由于主程序循环时间过长,导致不能很及时的转向进而导致一直过冲,也让我烦恼了一天。今天去图书馆查了资料,看到 别人都是 用的两个寻迹口,刚刚抱着试试看的心态去掉中间一个寻迹口之后,没想到效果很好。能很稳定的寻迹。
接下来在能稳定寻迹的基础上,在两边加上两个红外对管,作为对转弯的判断。

反思:
刚刚我又写了一个程序,使用两个寻迹口,发现还是会过冲!这就非常值得反思了。同样的设备,同样的思想,为什么一个可以寻迹,一个不能寻迹呢?这就是写程序的功力了。

寻迹可以,但是45度角直接冲了过去。速度太快。其实能看到在45角位置,左轮给了一个力,但是由于惯性,小车冲了过去。然后就进行后面的判断,直行

在二次停车的时候,又感觉单片机的处理速度太快了。。。。。经过黑线的一瞬间,四个寻迹口全黑,这时flag标志加一。但是在经过的那瞬间单片机 主循环循环了两次,导致flag一下加到了2,停车了。
得在全黑的时刻来一个延时。但是这个延时的长短需要反复测试,直到经过黑色线的时候正好加一。
因为中间两个用来寻直线的红外对管安装在相对比较考前的位置,(这是为了在第一个45角地方转弯用的)所以在小车遇到起点的时候,总是中间两个传感器先检测到黑先,按照先前的设定,这是小车会快速向右转弯,所以会出现在起点向右转弯的情况。

2017.11.27 下午
目前第四代小车基本能完成题目要求,(稳定寻迹,在岔路口识别选择转向。两圈自动停车)但是还不是很稳定。
缺陷:
1、偶尔会出现冲出跑道的现象,但是很少。
2、有时会出现第二次无法停车的情况,会直接向右转弯,这是由于寻迹时小车左右晃动,导致在终点横着的黑线处误以为转弯。如果能保持直行的话,就可以准确停车。

中间遇到了这个错误,记录一下。

RAMSIZE(256)
*** FATAL ERROR L211: I/O ERROR ON OUTPUT FILE:
EXCEPTION 0029H: ACCESS TO FILE DENIED
FILE: test_5_finally
Target not created
出现这个错误估计你就是新建工程的时候和其他工程放在了一起,然后你这个工程里面的文件名和其他工程的文件名重名了。这时,你只需要新建一个文件,然后在这个空文件里面重新建立刚才的那个工程,就可以了。养成好习惯,每个工程和所需的源文件和头文件都打包放在一起。不要把多个工程都放在一个文件下面。路径很容易出错。

2017.11.27 晚
经过断断续续12天的开发,其实中间有一个星期的时间在等原件,当时没有51系统板了。总共用来开发的时间大概也就一个星期。从刚开始半天写了寻迹程序,信心满满的开始测试,但是第一版小车很容易冲出跑道很不稳定。反思一下就是刚开始没有考虑中断对主程序的影响,没有考虑寻迹时的先后逻辑,if-else嵌套的时候设计的不合理。最重要的一点是当时还不会用keil4仿真程序,不知道用仿真来看程序运行时占用的时间。经过这个项目的开发之后,虽说功能很简单,但是还是在这个过程中学会了很多东西。所以我想开放自己的一点点代码。共同分享。如果大家发现程序的不足和可以改进的地方,欢迎在博客下面留言。

第四版寻迹小车源码:

#include<reg52.h>
#include <intrins.h>
#include "main.h"
#include "delay.h"

/* 电机I/O口定义,500hz, 档位20 档 */
sbit left_en = P2^5; //电机使能
sbit Left_Motor_P1  = P2^3;    
sbit Left_Motor_P2  = P2^4;

sbit right_en = P2^2;
sbit Right_Motor_P1 = P2^1;    
sbit Right_Motor_P2 = P2^0;

#define left_go {Left_Motor_P1 = 1;Left_Motor_P2 = 0;}
#define left_back {Left_Motor_P1 = 0;Left_Motor_P2 = 1;}
#define left_stop {Left_Motor_P1 = 1;Left_Motor_P2 = 1;}

#define right_go {Right_Motor_P1 = 1;Right_Motor_P2 = 0;}
#define right_back {Right_Motor_P1 = 0;Right_Motor_P2 = 1;}
#define right_stop {Right_Motor_P1 = 1;Right_Motor_P2 = 1;}

/*红外对管I/O口定义*/
sbit  Sensor_Right = P1^1; 
sbit  Sensor_Left = P1^2;
sbit turn_flag = P1^0;//作为45度角转向标志
sbit turn_flag_2 = P1^3;//第一个45度角转弯之后的矫正,防止冲出跑道

/*   占空比变量定义 */
uint Low_Speed = 5;        //经测试还是5最合适
uint Left_Forward_Duty = 5;        /* 左电机 正转 */
uint Right_Forward_Duty = 5;    /* 右电机 正转 */

/* 占空比计数变量定义和 预设占空比 进行比较 */
uint Left_Forward_Cnt = 0;      /* 左电机 正转占空比计数 */
uint Right_Forward_Cnt = 0;   /* 右电机 正转占空比计数 */

static flag =  0;

sbit time_flag = P1^4;
//----------------------------
void main()
{
    pwm_init();    
    time_flag = 1;

    while(flag < 2)    //768us
    {
        traing(); 
    }
    
    while(1) //两圈停车
    {
        time_flag = 0;
        car_stop();        
    }          
}

//----------------
void pwm_init(void)
{
    TMOD=0x02;         /* 模式设置,*/
    TH0=0X9C;         /* 定时器设置,100us一次中断*/
    TL0=0X9C;
    ET0=1;             /* 开定时器0 中断*/
    EA=1;             /* 开总中断*/
    TR0=1;              /* 打开定时器*/
}


//------------------
void traing()
{
    if(turn_flag == 0)    //最右侧寻迹口为白线
    {
      if((Sensor_Right==0)&&(Sensor_Left==1))//左边有黑线,往左转
          {
              right_go;
              left_stop;
              Right_Forward_Duty = Low_Speed;//+2的话跑的太快,单片机处理速度无法胜任,              
              Left_Forward_Duty = 0;        
        }

      else
          {
         if((Sensor_Right==1)&&(Sensor_Left==0)) //右转
              {
                left_go;
                right_stop;
                Left_Forward_Duty = Low_Speed;
                Right_Forward_Duty = 0;    
            }
         else if((Sensor_Right==0)&&(Sensor_Left==0)) //全白,寻迹的最佳情况,慢速。          
              {
                if(turn_flag_2 == 0) //增加判断是否在经过第一个45度角地方,防止过冲
                {   
                    left_go;         //正常走,能否在这里保存上一次执行的动作?【0000】
                    right_go;          
                    Left_Forward_Duty = Low_Speed-1;
                    Right_Forward_Duty = Low_Speed-1;
                }
                else           //经过45度角地方,防止过冲,【1000】
                {
                    left_back;
                    right_go;          
                    Left_Forward_Duty = Low_Speed+1;
                    Right_Forward_Duty = Low_Speed+1;    //快转            
                }
            }

         else         //中间两个寻迹端口全黑,此时注意结合在起点时的情况  【0110】【1110】
         {   
            if(turn_flag_2 == 1)  //只有在起点,车体稍微倾斜时,可能【1110】,第二个45度角也可能出现,但是很短暂
            {
                left_stop;
                right_go; //稍微转向      
                Left_Forward_Duty = Low_Speed; //全黑。直线寻迹的时候不会出现这种情况,只有在转弯处才会。
                Right_Forward_Duty = Low_Speed;    
            }
            else        //【0110】转角遇到
            {
                left_go;
                right_go; //原地转向。在第一个45度角地方       
                Left_Forward_Duty = Low_Speed;
                Right_Forward_Duty = Low_Speed;            
            }
         }

         }
     }

     //下面是turnflag = 1的情况
     else if((Sensor_Right==1)&&(Sensor_Left==1)&&(turn_flag_2==1))  //四个寻迹口都是黑线,此时经过起点,记录圈数//780us
     {                                                                  //【1111】
//         delay_10us(50);
//         flag++;//圈数标志
        left_go;
        right_go;
         Right_Forward_Duty = Low_Speed+2;//慢走        
        Left_Forward_Duty = Low_Speed+2;
        delay_1ms(100);           //50ms不行,当100ms时,只有当在这一段准确的直线行驶才能保证二次停车,就是不能保证100%停车
        flag++;//圈数标志        
     }


     else              //最右侧传感器检测到黑线,【(1),(10,01,00),(1,0)】【(1),(11),(0)】
     {
          left_go;
         right_back;
         Left_Forward_Duty = Low_Speed+2;    //刚开始就运行到这里的话,到364us        
         Right_Forward_Duty = Low_Speed+2;
     }
 
}

//---停车,失能电机------
void car_stop(void)
{
   left_en = 0;  
   right_en = 0;
   Left_Forward_Duty = 0;
   Right_Forward_Duty = 0;
}

//---------------------
void time0() interrupt 1
{    
    /*左轮前进PWM输出*/    
    Left_Forward_Cnt++;
    if(Left_Forward_Cnt <= Left_Forward_Duty)
    {
//        Left_Motor_P1 = 1;
//        Left_Motor_P2 = 0;
        left_en = 1;//高电平,使能电机
    }
    else 
    {
//        Left_Motor_P1 = 1;
//        Left_Motor_P2 = 1;
        left_en = 0;
    }
    
    if(Left_Forward_Cnt==20)
    {
         Left_Forward_Cnt = 0;
    }
    
    
  /*右轮前进PWM输出*/
    Right_Forward_Cnt++;
    if(Right_Forward_Cnt <=Right_Forward_Duty)
    {
//        Right_Motor_P1 = 1;
//        Right_Motor_P2 = 0;
        right_en = 1; 
    }
    else 
    {
//        Right_Motor_P1 = 1;
//        Right_Motor_P2 = 1;
        right_en = 0;
    }    
    if(Right_Forward_Cnt==20)
    {
         Right_Forward_Cnt = 0;
    }
}

还有就是两个头文件,里面都是一点函数的声明,就不占用篇幅了。