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; } } }
本文来自博客园,作者:YuhangLiuCE,转载请注明原文链接:https://www.cnblogs.com/YuhangLiuCE/p/17804115.html