返回顶部

FinalProject

基本要求:80分

(1)小车开机运行程序,在8位数码管的最右边3位显示小车定位距离,初始值为12.5(单位:cm)并启动超声波测距,将距离值显示在最左边4位(xxx.x cm) ;

(2)利用按键设置定位距离,“+”按键每次增加0.5cm,上限为15.0cm; “-”按键每次减少0.5cm,下限为10.0cm;当按下该按键时,蜂鸣器响0.1秒(按键提示音)。

(3)设定好定位距离的小车放置在障碍物1米以外的位置。利用光敏遥控启动小车,同时启动“秒表计时器” 作为小车运行时间计时,并在数码管最右边3位显示时间(要求定时中断实现);尽量保持小车直线前进,要求小车速度至少有两个速度档位,距离障碍物越近,速度越慢。小车第一次进入定位距离范围内,停止计时,要求该时间不大于3.2秒,并记录小车运行时间。

(4)小车运行过程中,数码管上始终实时显示运行时间和小车到障碍物的距离;

(5)小车在距离障碍物为定位距离±0.5cm范围内停止行驶,通过速度调节和前进后退等方式使小车精确定位在目标范围,若小车位于(定位距离-0.5cm)以内 ,则声光报警,即用一个发光二极管指示灯闪烁,点亮0.1s,熄灭0.3s;用蜂鸣器响0.1s,静音0.3s报警;若大于等于(定位距离-0.5cm) ,则撤销声光报警。小车不能碰撞前方障碍物!

(6)采用滑动平均值滤波提高测距稳定性,建议每测量三个结果取一次均值。

扩展要求:20分

(1)秒表停止计时后,闪烁显示最终计时时间(点亮0.1s+熄灭0.3s)

(2)为避免测距不稳定导致的小车频繁运动,进入设定距离后,启动判断机制,当小车连续三次检测到实测距离符合前进或后退的要求才运动。

 

/**/

#include <reg52.h>
#include <PCF8591.h>

//定义I/O接口
//超声
sbit Echo        = P3^2;        //Echo
sbit Trig             = P3^3;        //Trig
//PWM
sbit PWM_IN1     = P1^4;     // 高电平1:左电机后退(反转)
sbit PWM_IN2     = P1^5;     // 高电平1:左电机前进(正转)

sbit PWM_IN3     = P1^6;     // 高电平1:右电机前进(正转)    
sbit PWM_IN4     = P1^7;     // 高电平1:右电机后退(反转)

sbit PWM_EN1     = P1^2;     // 高电平1:使能左电机 
sbit PWM_EN2     = P1^3;     // 高电平1:使能右电机
//数码管显示
sbit SS             = P2^6;      //数码管段选信号
sbit CS             = P2^7;      //数码管位选信号
//按键
sbit KEY1         = P3^4;        //定义按键K1,'+'按键),对应核心板上K1
sbit KEY2         = P3^5;        //定义按键K2,'-'按键),对应核心板上K2
//
sbit BUZZER = P2^3; //有源蜂鸣器驱动端口 
sbit LED = P1^0;//LED

//定义变量
//超声
bit Counter_overflag         = 0;        //T0定时器溢出标志
bit Echo_Over                     = 0;         //超声波测距完成标志,无论收到回波或没有收到,总要置位一次
unsigned int Range             = 0;
unsigned long Echo_time = 0;        //T0定时器合并数值

//定义PWM最大级数,也就是调节直流电机的速度等级
#define SPEED_MAX        20
#define HIGH_Speed 12
#define LOW_Speed 8
#define MID_Speed 10
#define B_LOW_Speed 5
#define Near_LOW_Speed 10
#define STOP 0
bit Direction;
//定义PWM级数,分为0~SPEED_MAX-1级
unsigned char Speed_L;        //左电机转速调节(调节PWM的一个周期SPEED_MAX*1ms时间内,左电机正转时间:Speed_L*1ms)
unsigned char Speed_R;        //右电机转速调节(调节PWM的一个周期SPEED_MAX*1ms时间内,右电机正转时间:Speed_R*1ms)

//定义显示缓冲区(由定时中断程序自动扫描)
unsigned char DispBuf[8];


code unsigned char Tab[] =        //共阴极数码管的段码(字形)表
    {//定义0123456789AbCdEF的数码管字型数据,其他显示字符需自行计算,如‘-’的字形数据为0x40
        0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,
        0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
    };
    
int Range_set     = 125;
int sec                = 0;
unsigned char cnt = 0;
int Range_mean;
int RangeBuf[5]={0,0,0,0,0};    
int RangeBuf_th[3]={0,0,0};
bit click_alarm_flag = 0;
int forward_count_3 = 0;
bit start_flag = 0;
bit range_alarm_flag = 0;    
bit timer_flag =0;
unsigned char light,voltage;//定义中间变量
unsigned char chl2,chl4;//定义中间变量
unsigned char AD_CHANNEL = 0;
//============================//
/*
函数:EX0INTSVC()
功能:外部中断0中断服务程序
用途:为避免定时中断干扰测距的准确性,采用外部中断触发后立即停止计时
        外部中断0优先级高于定时中断,程序尽量短,避免过多干扰定时中断
*/
void EX0INTSVC() interrupt 0
{
    TR0 = 0;                    //停止计时
    Echo_time = TH0 * 256 + TL0;     //读取定时器0计数值
    TH0 = 0;                     //清除定时器0计数寄存器,为下次计数做准备
    TL0 = 0;
    Echo_Over = 1;         //表示本次超声波测距完成,可以启动下次的测量
    EX0 = 0;                    //关闭外部中断,否则会马上引起下一次中断
}
//============================//
/*
函数:T0INTSVC()
功能:定时器T0的中断服务函数
用途:若超过测距范围,长时间无法收到回波,
        已经启动的T0中断会计数溢出,利用定时器溢出标志来判断
*/
void T0INTSVC() interrupt 1          
{
    TR0 = 0;                //停止计时
    Counter_overflag = 1;             //中断溢出标志,未收到回波
    Echo_Over = 1;    //表示本次超声波测距完成,可以启动下次的测量
    EX0 = 0;                //关闭外部中断
}
/*
函数:T1INTSVC()
功能:定时器T1的中断服务函数
*/
void T1INTSVC() interrupt 3    //定时器1的中断号为:3
{
    code unsigned char com[] = {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};    //显示位的端口控制字节
    static unsigned char n = 0;                        //n: 扫描显示位计数,0-7
    static unsigned int counter_1ms = 0;    //1ms计数
    static unsigned int counter_alarm = 0; //报警计数
    static unsigned int counter_alarm1 = 0;
    static unsigned int counter_blink = 0;
    static unsigned int t =0;
//================数码管定时扫描驱动显示===============    
    TR1 = 0;
    TH1 = 0xFC;
    TL1 = 0x66;                        //可以将FC66换成0000,降低扫描速度,观察和理解动态扫描
    TR1 = 1;
    P0 = 0xFF;        //消隐
    CS = 1;
    CS = 0;
    P0 = DispBuf[n];    //更新扫描显示数据
    SS = 1;
    SS = 0;
    P0 = ~com[n];    //重新显示
    CS = 1;
    CS = 0;
    n++;                         //指向下一位扫描显示
    n &= 0x07;
//====================================================

    
if (click_alarm_flag)
    {
        counter_alarm ++;
        BUZZER = 0;
        if(counter_alarm == 100)
        {
            BUZZER = 1;
            counter_alarm = 0;
            click_alarm_flag = 0;
        }    
    }
    else
    {
        counter_alarm = 0;
    }

    if(start_flag && Speed_L==4 )
    {
        counter_alarm1 ++;
        if(counter_alarm1<=100)
        {BUZZER = 0;
        LED = 0;}
        else if(counter_alarm1 > 100)
        {
            BUZZER = 1;
            LED = 1;
            if(counter_alarm1 == 400)
            {
                counter_alarm1 =0;
            }
        }
    }
    else
    {
        counter_alarm1 = 0;
        BUZZER = 1;
        LED = 1;
    }
    
    if(start_flag )
    {
        if(timer_flag)
        {
            counter_1ms ++;    //1ms加1    
            if (counter_1ms == 100)
            {        
                counter_1ms = 0;            //0.1s到0 
                sec ++;
                DispBuf[0] = Tab[sec % 10];            //查表取出显示数字对应的段码,存入显示缓冲器数组
                DispBuf[1] = Tab[sec / 10 % 10] | 0x80;    // "| 0x80"可使该位带小数点显示
                DispBuf[2] = Tab[sec / 100];
            }
        }
        
        else
        {    
            counter_blink ++;

            if(counter_blink<=100)
            {
                DispBuf[0] = Tab[sec % 10];            //查表取出显示数字对应的段码,存入显示缓冲器数组
                DispBuf[1] = Tab[sec / 10 % 10] | 0x80;    // "| 0x80"可使该位带小数点显示
                DispBuf[2] = Tab[sec / 100];
            }     
            else
            {
                DispBuf[0] = 0x00;            //查表取出显示数字对应的段码,存入显示缓冲器数组
                DispBuf[1] = 0x00;    // "| 0x80"可使该位带小数点显示
                DispBuf[2] = 0x00;
                if(counter_blink == 400)
                {
                    counter_blink = 0;
                }
            }
        }
        t++;
        if ( t >= SPEED_MAX ) 
            t = 0;                //PWM波的周期为:SPEED_MAX*1ms = 20ms
        if(PWM_EN1 || PWM_EN2)
        {
            if(Direction == 1)    //小车运动方向为前进
            {
                if ( t < Speed_L )        //PWM波高电平时间:(Speed_L)*1ms
                {
                    PWM_IN1 = 0;
                    PWM_IN2 = 1;         //左电机的正转
                }
                else                    //PWM波低电平时间:(SPEED_MAX-Speed_L)*1ms
                {
                    PWM_IN1 = 0;
                    PWM_IN2 = 0;         //左电机的停转
                }
                
                if ( t < Speed_R )        //PWM波高电平时间:Speed_R*1ms
                {
                    PWM_IN3 = 1;
                    PWM_IN4 = 0;        //右电机的正转        
                }
                else                    //PWM波低电平时间:(SPEED_MAX-Speed_R)*1ms
                {
                    PWM_IN3 = 0;
                    PWM_IN4 = 0;        //右电机的停转    
                }
            }
            else if(Direction == 0)    //小车运动方向为后退
            {
                if ( t < Speed_L )        //PWM波高电平时间:(Speed_L)*1ms
                {
                    PWM_IN1 = 1;
                    PWM_IN2 = 0;         //左电机的反转
                }
                else                    //PWM波低电平时间:(SPEED_MAX-Speed_L)*1ms
                {
                    PWM_IN1 = 0;
                    PWM_IN2 = 0;         //左电机的停转
                }
                
                if ( t < Speed_R )        //PWM波高电平时间:Speed_R*1ms
                {
                    PWM_IN3 = 0;
                    PWM_IN4 = 1;        //右电机的反转    
                }
                else                    //PWM波低电平时间:(SPEED_MAX-Speed_R)*1ms
                {
                    PWM_IN3 = 0;
                    PWM_IN4 = 0;        //右电机的停转    
                }
            }
        }        
}

}

/*
函数:DispClear()
功能:清除数码管的所有显示
*/
void DispClear()
{
    unsigned char i;
    for ( i=0; i<8; i++ )
    {
        DispBuf[i] = 0x00;    //i值代表数码管的位数,可以在后面的程序观察是左起还是右起,0x00可以关闭数码管显示
    }
}

/*
函数:Delay()
功能:延时
说明:
    晶振频率为11.0592MHz
    延时长度 = 1ms * t
*/
void Delay(unsigned int t)
{
    unsigned int us_ct;
    for (;t > 0;t --)        //执行代码消耗CPU时间
        for (us_ct = 113;us_ct > 0;us_ct --);    
}


/*
函数:SysInit()
功能:系统初始化
*/
void SysInit()
{

    DispClear();    //初始化显示缓存
    TMOD = 0x11;    //设置定时器T0为16位定时器,定时器T1为16位定时器
    EA = 0;                //关闭总中断,待初始化结束后再打开
//======定时计数器T0初始化,用于获取超声波作用时间,若定时溢出,则超出测距范围
    TH0 = 0;        
    TL0 = 0;
    ET0 = 1;
//======定时计数器T1初始化,用于获取1ms定时中断=====
    TH1 = 0xFC;        //设置定时器1的初值: 0xFC66,对应定时时间1ms
    TL1 = 0x66;
    ET1 = 1;            //使能定时器T1中断
    TR1 = 1;            //启动定时器T1
//======定时计数器T0和T1初始化完毕=================    
    EX0 = 0;            //关闭外部中断
    IT0 = 0;            //外部中断0采用电平触发模式,低电平出发
    EA = 1;                //使能总中断
}


/*
函数:StartModule()
功能:启动模块,采用 IO 触发测距,给至少10us 的高电平信号;
*/
void  StartModule()
{
    unsigned char i;
    Trig = 1;                              //启动一次模块
    for(i = 0;i < 5;i ++);                    //超声波启动延迟10us以上;
    Trig = 0;
}

/*
函数:Range_Display()
功能:超声波距离显示函数
说明:若超出距离则显示“- - - -”
*/
 void Range_Display()
{
    //超出测量范围或者障碍物太近反射角度太小导致无法收到回波,都显示“- - - -”
    if((Range >= 4000) || Counter_overflag == 1)
    {     
        Counter_overflag = 0;
        DispBuf[4] = 0x40;          
        DispBuf[5] = 0x40;
        DispBuf[6] = 0x40;              
        DispBuf[7] = 0x40;
    }
    //按照HC-SR04的指标,大致工作在2cm—450cm的范围内,与产品质量和反射面相关
    else        //显示数据单位:厘米
    {
        DispBuf[4] = Tab[Range_mean % 10];          
        DispBuf[5] = Tab[Range_mean / 10 % 10] + 0x80;
        DispBuf[6] = Tab[Range_mean / 100 % 10];              
        DispBuf[7] = Tab[Range_mean / 1000];
    }
}

/*
函数:Timer_Count()
功能:超声波高电平脉冲宽度计算函数
备注:采用查询模式
说明:超声波模块在等待回波的时候,经常被定时中断打断,导致回波到达时间测量不及时,干扰了超声波测量精度
改进的办法是分别加入下面两条语句(暂时屏蔽,取消屏蔽可以用来对照)
    TR1 = 0;        //暂停定时器T1计数,相当于关闭定时中断T1
  TR1 = 1;        //重启定时器T1计数,相当于打开定时中断T1
随之带来什么新的问题呢?分析产生的原因。
*/
unsigned int  Timer_Count()        
{
    
    TR0 = 1;          //开启计数
    EX0 = 1;            //开启外部中断
    while(!Echo_Over);    //等待回波,当Echo_Over为1,表明收到回波或超出测距范围
    
    Echo_Over = 0;            //清除Echo_Over,准备下一次测距
    //程序到这里就已经得到了超声波的响应计数值,结果存在变量Echo_time内,Echo_time * 1.1us得到响应时间
    //假设环境温度26.5摄氏度,根据Echo_time的值自行计算测距的长度(单位:毫米)并替换下面的定值表达式
    //注意:必须用定点运算,精度为毫米!
    Range = Echo_time * 11 / 10 * 334 / 1000 /2;     //单位是毫米

    return (unsigned int)Range;
}
    
/*
函数:KeyScan()
功能:键盘扫描
返回:扫描到的键值
*/
unsigned char KeyScan()
{
    unsigned char k = '\0';

    if ( KEY1 == 0 ) k = '+';
    if ( KEY2 == 0 ) k = '-';

    return k;
}

void main()
{
        unsigned char k;            //定义键值变量
        

    RST_DS = 0;                                            //        关时钟DS1302
    I2C_Init();
    
    SysInit();
while(!start_flag)
    {
            Echo = 1;            //IO口读入之前输出1
            TR1 = 0;            //关闭定时器T1,避免打断超声波启动和上升沿捕获
            ET1 = 0;            //关闭定时器T1中断,时间很短,影响较小
            StartModule();        //启动模块
            while(!Echo);
            ET1 = 1;            //使能定时器T1中断
            TR1 = 1;            //启动定时器T1
            Range = Timer_Count();        //超声波高电平脉冲宽度计算函数
            RangeBuf_th[cnt] = Range;
            cnt = ( cnt + 1 ) % 3; 
            if(RangeBuf_th[2]!=0)
                Range_mean = ( RangeBuf_th[0] + RangeBuf_th[1] + RangeBuf_th[2] )/3 ;
            else if (RangeBuf[1]!=0)
                Range_mean = ( RangeBuf_th[0] + RangeBuf_th[1] )/2 ;
            else
                Range_mean = RangeBuf_th[0];
            Range_Display();

            Delay(20);                //扫描延时20ms,也做按键后延消抖
            k = KeyScan();            //扫描按键
            if ( k != '\0')            //首次检测到按键按下
            {
                Delay(20);                    //延时20ms,按键前沿消抖
                k = KeyScan();            //再次读取按键状态
                if ( k != '\0')            //确认按键按下后处理按键
                {
                    if ( k == '+') 
                    {
                        if ( Range_set < 150 ) 
                        {
                            Range_set = Range_set+5;
                            click_alarm_flag = 1;
                        }
                    }
                    if ( k == '-')
                    {
                        if ( Range_set > 100 ) 
                        {
                            Range_set = Range_set-5;
                            click_alarm_flag = 1;
                        }
                    }
                }
            }
    
                
            DispBuf[0] = Tab[Range_set % 10];            //查表取出显示数字对应的段码,存入显示缓冲器数组
            DispBuf[1] = Tab[Range_set / 10 % 10] | 0x80;    // "| 0x80"可使该位带小数点显示
            DispBuf[2] = Tab[Range_set / 100];

            while( KeyScan() != '\0');        //等待松开按键

            switch(AD_CHANNEL)
            {
            case 0: 
                PCF8591_SendByte(AddWr,1);
              light = PCF8591_RcvByte(AddWr);  //ADC0 模数转换1     J8上可接任意电阻元件
            break;  
            
            case 1: 
                PCF8591_SendByte(AddWr,2);
              chl2 =PCF8591_RcvByte(AddWr);  //ADC1  模数转换2
            break;  
            
            case 2: 
                PCF8591_SendByte(AddWr,3);
                voltage =PCF8591_RcvByte(AddWr);  //ADC2    模数转换3   可调电阻SW1
            break;  
            
            case 3: 
                PCF8591_SendByte(AddWr,0);
              chl4 =PCF8591_RcvByte(AddWr);  //ADC3   模数转换4     可调电阻SW2
            break;
            
            case 4: 
                Pcf8591_DaConversion(AddWr,0, voltage); //DAC      数模转换
            break;            
            }        
        if(++AD_CHANNEL > 4) AD_CHANNEL = 0;
        //AD部分结束
        
        if(light > 220) //此处是光敏传感器经过AD转换后得到的值,需要将其与自己设定的门限值作比较。此处为强光激活
        {
            start_flag = 1;
            timer_flag = 1;
        }
    }    
    while(1)
    {
            
            PWM_EN1 = 1;
            PWM_EN2 = 1;
            Echo = 1;            //IO口读入之前输出1
            TR1 = 0;            //关闭定时器T1,避免打断超声波启动和上升沿捕获
            ET1 = 0;            //关闭定时器T1中断,时间很短,影响较小
            StartModule();        //启动模块
            while(!Echo);
            ET1 = 1;            //使能定时器T1中断
            TR1 = 1;            //启动定时器T1
            Range = Timer_Count();        //超声波高电平脉冲宽度计算函数

        RangeBuf[cnt] = Range;

            Range_mean = ( RangeBuf[0] + RangeBuf[1] + RangeBuf[2] +RangeBuf[3]+ RangeBuf[4])/5 ;    
        cnt = ( cnt + 1 ) % 5; 
        
            if(cnt ==4)
            {
                Delay(80);
                Range_Display();
            }
            if(Range_mean - Range_set <=15 && Range_mean -Range_set >=-15)
                    timer_flag=0;        
            if(RangeBuf[2] -Range_set > 250 && RangeBuf[3] -Range_set > 250 && RangeBuf[4] -Range_set > 250)
            {
                Direction = 1;
                Speed_L = Speed_R = HIGH_Speed;
            }
            else if(RangeBuf[2] -Range_set >=70 && RangeBuf[3] -Range_set >=70 && RangeBuf[4] -Range_set >=70)
            {
                Direction = 1;
                Speed_L = Speed_R = MID_Speed;
            }
            else if(RangeBuf[2] -Range_set >=40 && RangeBuf[3] -Range_set >=40 && RangeBuf[4] -Range_set >=40)
            {
                Direction = 1;
                Speed_L = Speed_R = LOW_Speed;

            }
            else if(RangeBuf[2] -Range_set >=10 && RangeBuf[3] -Range_set >=10 && RangeBuf[4] -Range_set >=10)
            {
                forward_count_3 ++;
                if(forward_count_3 ==4)
                {Direction = 1;
                Speed_L = Speed_R = Near_LOW_Speed;
                forward_count_3 = 0;}
                else{Speed_L = Speed_R = STOP;}

            }
            else if(Range_mean - Range_set < 10 && Range_mean - Range_set > -10)
            {
                Speed_L = Speed_R = STOP;

            }
            else if(Range_mean -Range_set <=-10)
            {
                Direction = 0;
                Speed_L = Speed_R = 4;
            }
    }
}

 

posted @ 2023-11-01 21:04  YuhangLiuCE  阅读(24)  评论(0编辑  收藏  举报