1 概述
1.1 引言
随着出租车行业的发展,出租车已经是城市交通的重要组成部分,从加强行业管理以及减少司机与乘客的纠纷出发,具有良好性能的计价器对出租车司机和乘客来说都是很必要的。而采用模拟电路和数字电路设计的计价器整体电路的规模较大,用到的器件多,造成故障率高,难调试。而采用单片机进行的设计,相对来说功能强大,用较少的硬件和适当的软件相互配合可以很容易地实现设计要求,且灵活性强,可以通过软件编程来完成更多的附加功能。
1.2 设计任务
本设计是采用ATS52单片机为主控器,以霍尔传感器测距,实现对出租车的多功能的计价设计,并采用AT24C01实现在系统掉电的时候保存单价等信息,输出采用8段数码显示管。本电路设计的计价器不但能实现基本的计价,而且还能根据单程,返程和起步来调节单价,并实现了暂停和等待时间查询的功能。
1.3 设计目的和要求
目的:通过单片机课程设计,熟练掌握c语言的编程方法,将理论联系到实践中去,提高我们的动脑和动手的能力。通过出租车计价器系统的设计,掌握单片机个资源的使用方法,和简单程序的编写,最终提高我们的逻辑抽象能力。
基本要求:
(1)能显示里程,单位为公里,最后一位为小数位。
(2)能显示金额数,单位为元,最后一位为小数位。
(3)可设定单程价格和往返价格,单程价格为2元/公里,往返价格为1.5元/公里。
(4)车速<5公里/小时的时间累积为总等待时间,每5分钟等待时间相当于里程数增加1公里。
(5)起步公里数为3公里,价格为5元,若实际距离大于3公里,按规则3计算价格。
(6)按暂停键,计价器可暂停计价,按查询键,可显示总等待时间。
2 系统总体方案及硬件设计
2.1 系统总体方案
模拟计数器系统主要由五大模块组成:霍尔传感器、ATS52单片机、键盘、EEPROM AT24C01和显示数码管。
霍尔传感器安装在车轮的旁侧,主要检测汽车行进的公里数,并产生一系列相应的脉冲输出,脉冲送到单片机进行处理,单片机根据程序设定通过计算脉冲数换算出行驶公里数,再根据从EEPROM中读取的价格等相关数据进行金额的计算,计算好的金额、里程都实时地显示在数码管上。键盘可以调节价格等相关数据,按下相应的按钮,产生信号交由单片机处理并实时显示出来。总体方案结构图如下:
图2.1 系统总体结构框图
2.2 单片机最小系统单元
这次设计用到了ATS52单片机(如图2.2),ATS52系列单片机是由Atmel公司生产,而这家公司得到了Intel公司MCS51系列单片机内核生产授权,所以,它与MCS51系列单片机指令兼容,同时它的内部包含有用作程序存储器的4KB的基于FLASH技术的只读存储器。采用这款芯片及克服了采用8031需要添加外部外部程序存储器导致电路复杂的缺点,又克服了采用8751导致电路制作成本高的缺点。
ATS52单片机芯片具有以下特性:
1)指令集合芯片引脚与Intel公司的8051兼容;
2)4KB片内在系统可编程FLASH程序存储器;
3)时钟频率为0~33MHZ;
4)128字节片内随机读写存储器(RAM);
5)6个中断源,2级优先级;
6)2个16位定时/记数器;
7)全双工串行通信接口;
8)监视定时器;
9)两个数据指针。
2.2.1 时钟电路模块
为达到振荡周期是12MHZ的要求,这里要采用12MHZ的晶振,另外有两个22P的独石电容,两晶振引脚分别连到XTAL1和XTAL2振荡脉冲输入引脚。具体如图所示:
图2.2 时钟电路模块
2.2.2 复位电路模块
单片机系统的复位电路在这里采用的是上电+按钮复位电路形式,其中电阻R采用200Ω的阻值,电容采用电容值为10μ的电解电容。具体连接电路如图示5:
图2.3 复位电路模块
2.3 霍尔传感器检测单元
属于开关型的霍尔器件,其工作电压范围比较宽(4.5~18V),其输出的信号符合TTL电平标准,可以直接接到单片机的IO 端口上,而且其最高检测频率可达到1MHZ。
集成霍耳开关由稳压器A、霍耳电势发生器(即硅霍耳片)B、差分放大器C、施密特触发器D和OC门输出E五个基本部分组成。
在输入端输入电压Vcc,经稳压器稳压后加在霍尔电势发生器的两端,根据霍尔效应原理,当霍尔片处在磁场中时,在垂直于磁场的方向通以电流,则与这二者相垂直的方向上将会产生霍尔电势差VH输出,该VH信号经放大器放大后送至施密特触发器整形,使其成为方波输送到OC门输出。当施加的磁场达到工作点(即Bop)时,触发器输出高电压(相对于地电位),使三极管导通,此时OC门输出端输出低电压,三极管截止,使OC门输出高电压,这种状态为关。这样两次电压变换,使霍尔开关完成了一次开关动作。 霍尔传感器原理如图5所示。
图2.4 传感器测距示意图
里程计算是通过安装在车轮上的霍尔传感器检测到的脉冲信号,送到单片机产生中断,单片机再根据程序设定,计算出里程。其原理如图2.4所示。
本系统选择了将霍尔传感器的脉冲输出口接到P3.3口外部中断1作为信号的输入端(这样可以减少程序设计的麻烦),车轮每转一圈(设车轮的周长是5米),霍尔开关就检测并输出信号,引起单片机的中断,对脉冲计数,当计数达到200次时,即1公里,单片机就控制将金额自动增加,如图2.5(霍尔传感器)。
图2.5 霍尔传感器
2.4 AT24C01存储单元
存储单元的作用是在电源断开的时候,存储当前设定的单价信息。AT24C01 是Ateml公司的1KB的电可擦除存储芯片,采用两线串行的总线和单片机通讯,电压最低可以到2.5V,额定电流为1mA,静态电流10uA(5.5V),芯片内的资料可以在断电的情况下保存40年以上,而且采用8 脚的DIP 封装,使用方便。
A0,A1,A2——地址输入引脚,走位硬件寻址的依据,同种芯片可同时连接8片(2^3);
Vcc,Gnd——电源,接地引脚,1.8-5.5v
Wp——写保护,当Wp接地时,允许对器件的正常读写操作;当Wp接高电平时,写保护,只能进行读操作。
SDA——串行地址/数据输入/输出端口,双向传输,漏极开路,需外接上拉电阻到Vcc(典型阻值为10k)。
SCL——串行时钟输入,高低电平不同状态与SDA配合,执行不同的命令。
AT24C02芯片引脚配置如图所示。
图2.6 AT24C01
图中R13、R14 是上拉电阻,其作用是减少AT24C01 的静态功耗。由于AT24C01的数据线和地址线是复用的,采用串口的方式传送数据,所以只用两根线SCL(时钟脉冲)和SDA(数据/地址)与单片机P1.6和P1.7口连接,进行传送数据。
每当设定一次单价,系统就自动调用存储程序,将单价信息保存在芯片内;当系统重新上电的时候,自动调用读存储器程序,将存储器内的单价等信息,读到缓存单元中,供主程序使用.
图2.7 AT24C02原理图
2.5 键盘调整单元
当单价等信息需要进行修改时,就要用到键盘进行修改。由于调节信息不多,故采用4个键盘即可,分别实现清零、切换、增大、减小和功能等作用。电路原理如图所示。
图2.8 键盘调整模块
S1:接P1.1口,对上一次的计费进行清零/暂停,为下次载客准备
S2:接P1.2口,实现白天和夜晚单价的切换;当功能键S4按下时,S2可对数据进行增大。
S3:接P1.3口,当功能键S4按下时,S3可对数据进行减小。
S4:接P1.4口,按1次,进入调整单程单价;按2次,进入调整返程单价;;按3次,进入调整起步价;按4次,返回。
S5:接P1.5口,对等待时间进行查询
2.6 数据显示模块
显示单元由7个8段共阴数码管组成,采用动态扫描进行显示。前四个数码管分别接P2.0、P2.1和P2.2、P2.3,用于显示总金额;后面分别接P2.4、P2.5、P2.6和P2.7,用于显示里程;
图2.9 数据显示模块
3 软件设计
3.1 系统主程序
在主程序模块中,需要完成对各参量和接口的初始化、出租车起步价和单价的初始化以及中断、计算、循环等工作。另外,在主程序模块中还需要设置启动/清除标志寄存器、里程寄存器和价格寄存器,并对它们进行初始化。然后,主程序将根据各标志寄存器的内容,分别完成启动、清除、计程和计价等不同的操作。
3.1 主程序流程图
当汽车运行起来时,就启动计价,根据里程寄存器中的内容计算和判断行驶里程是否已超过起步价公里数。若已超过,则根据里程值、每公里的单价数和起步价数来计算出当前的总金额,并将结果存于总金额寄存器中;中途等待时,脉冲输入小于设定值时,当时间超过等待设定值时,开始进行计时,并把等待价格加到总金额里,然后将总金额、里程送数码管显示出来。程序流程如图所示。
3.2 中断程序
3.2.1 里程计数中断程序
每当霍尔传感器输出一个低电平信号就使单片机中断一次,当里程计数器对里程脉冲计满1000次时,进入里程计数中断服务程序中,里程变量加一。主函数中总金额也相应地变化。
3.2.2 中途等待中断程序
在中途等待中断程序中,每1ms产生一次中断,将当前里程值送入某个缓存变量,每5分钟将缓存变量中的值和当前里程值比较,当汽车车速小于5公里/小时,将存储器里面的值与实时测量的值比较,当行进的里程小于1/12公里每分钟时,则进入等待计时,每5分钟记一次价格。
3.3 计算程序
计算程序根据里程数分别进入不同的计算公式。如果里程大于3公里,则执行公式:总金额=起步价+(里程-3)*单价+等待时间*等待单价;否则,执行公式:总金额=起步价+等待时间*等待单价。程序流程图如图所示。
3.3 计算程序流程图
3.4 显示程序
显示程序利用定时器每1ms产生一次中断,相应变量置位,点亮一个数码管,显示一位数据,利用主函数内的循环,实现动态扫描显示,同时根据数码管余辉和人眼暂留现象,即可实现显示。
3.5 键盘程序
键盘采用查询的方式,放在主程序中,当没有按键按下的时候,单片机循环主程序,一旦右按键按下,便转向相应的子程序处理,处理结束再返回。流程图如图
3.4 键盘程序流程图
4 Proteus软件仿真
图4.1 初始化(数码管 上:总金额-元 下:里程-公里)
图4.2 单程单位价格(数码管 上:标识符 下:单程单价-元)
图4.3 单程计价费用(数码管 上:总金额-元 下:里程-公里)
图4.4 返程单位价格(数码管 上:标识符 下:返程单价-元)
图4.5 返程计价费用(数码管 上:总金额-元 下:里程-公里)
图4.6 起步价格(数码管 上:标识符 下:起步单价-元)
图4.7 小于三公里计价费用(数码管 上:总金额-元 下:里程-公里)
图4.8 等待时间查询(数码管 上:标识符 下:等待时间-分钟)
5 总结
经过这些天有关于出租车计价器的课程设计,使我对单片机的应用有了更深的了解。在课程设计的过程中,还是碰到了许多的问题。比如,对于数码管动态扫描显示和键盘的延时防抖的综合编程不能较好地解决;对于代码的前后顺序及调用掌握得还不够好;对于一些相关的应用软件没能熟练掌握。通过这几天晚上的苦想和反复调试,以及参考网上的程序,最终还是把问题解决了。
通过这次课程设计,我最大的收获就是自己的动手能力和解决问题的能力得到了很大的提高,也充分体会到了自己设计东西的乐趣、学会查阅资料和对别人的东西融会变通的重要性,也明白了很多知识光靠趴在书本上学是学不到其中的精髓的,必须亲自去试着实践,亲自去经历才能对它们真正的掌握,凡事都要自己去动下手,去实践一下,遇到困难,永远不要沮丧气馁。在动手的过程中,不仅能增强实践能力,而且在理论上可以有更深的认识;这次设计给了我极大的鼓舞和信心,相信在以后的学习中可以通过不断的摸索和实践来提高其他方面的知识。
参考文献
[1]段晨东.单片机实用技术教程.清华大学出版社,2008
[2]王晓明.单片机接口技术.北京航空航天大学出版社,2007
[3]王晓敏.传感器检测技术及应用.北京大学出版社,2010
[4]吴红星.电机驱动与控制专用集成电路及应用.中国电力出版社,2006
[5]Schulz,Joerg.Acceleration transducer for positioning drives in precision engineering.engineeringvillage,1999
[6]Transducer for Measurement of Motion. Speed and Acceleration, Duc, M.; Peters, M.; Yvroud, E.. engineeringvillage,1975
附录1 源程序
#include #include #define uchar unsigned char #define uint unsigned int #define delayNOP(); {_nop_();_nop_();_nop_();_nop_();_nop_();}; uchar code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}; //共阴数码管段选 sbit exter=P3^3; //外部中断 sbit key0=P1^1; //清零 sbit key1=P1^2; //切换/+ sbit key2=P1^3; //- sbit key3=P1^4; //功能键 sbit key4=P1^5; //查询键 sbit P20=P2^0; //数码管各位控制 sbit P21=P2^1; sbit P22=P2^2; sbit P23=P2^3; sbit P24=P2^4; sbit P25=P2^5; sbit P26=P2^6; sbit P27=P2^7; sbit P34=P3^4; //单程 sbit P35=P3^5; //返程 sbit P36=P3^6; //空车 sbit SDA=P1^6; sbit SCL=P1^7; //IIC引脚 uint inter,aa,bb,temp,temp1,flag,flag1; uchar zongjine,licheng,dengdai,licheng1; uint key3num,qiehuantemp,delaytemp; uchar danjia1,danjia2,danjia3,danjia4,danjia,qibu; void delay(uint x) //延时时基为1ms { int i,j; for(i=x;i>0;i--) for(j=110;j>0;j--); } void start() //IIC开始位 { SDA = 1; SCL = 1; delayNOP(); SDA = 0; delayNOP(); SCL = 0; } void stop() // IIC停止位 { SDA = 0; delayNOP(); SCL = 1; delayNOP(); SDA = 1; } void respons() //IIC应答位 { uchar i; SCL=1; delayNOP(); while((SDA==1)&&(i<250)) i++; SCL=0; delayNOP(); } uchar read_byte() // 从EEPROM读到MCU { uchar i,j; for(i=0;i<8;i++) { SCL=1; j<<=1; j|=SDA; SCL=0; } return(j); } void write_byte(uchar date) // 从MCU写到EEPROM { uchar i,temp; temp=date; for(i=0;i<8;i++) { temp=temp<<1; SCL=0; delayNOP(); SDA=CY; delayNOP(); SCL=1; delayNOP(); } SCL=0; delayNOP(); SDA=1; delayNOP(); } void write_data(uchar addr, uchar date) // 在指定地址addr处写入数据date { start(); write_byte(0xa0); respons(); write_byte(addr); respons(); write_byte(date); respons(); stop(); } uchar read_data(uchar addr) // 在指定地址addr读取数据 { uchar date; start(); write_byte(0xa0); respons(); write_byte(addr); respons(); start(); write_byte(0xa1); respons(); date=read_byte(); stop(); return date; } void display(uint zongjine0,uint licheng0) //数码管显示方式0 { uint jbai,jshi,jge,jxiaoshu,lbai,lshi,lge,lxiaoshu; uint numwei,numshu; //数码管位置分配 jbai=zongjine0/1000; jshi=zongjine0%1000/100; jge=zongjine0%100/10; jxiaoshu=zongjine0%10; jbai=licheng0/1000; lshi=licheng0%1000/100; lge=licheng0%100/10; lxiaoshu=licheng0%10; //数码管动态显示 if(aa) { aa=0; numshu++; if(numshu==8) numshu=0; P0=0xff; P2=0xff; switch(numwei) { case 0:P20=0;P0=table[jbai];break; case 1:P21=0;P0=table[jshi];break; case 2:P22=0;P0=table[jge]|0x80;break; case 3:P23=0;P0=table[jxiaoshu];break; case 4:P24=0;P0=table[lbai];break; case 5:P25=0;P0=table[lshi];break; case 6:P26=0;P0=table[lge]|0x80;break; case 7:P27=0;P0=table[lxiaoshu];break; //动态扫描位选控制 } numwei++; if(numwei==8) numwei=0; } } void display1(uint zongjine1,uint licheng1) //数码管显示方式1 { uint jbai1,jshi1,jge1,jxiaoshu1,lbai1,lshi1,lge1,lxiaoshu1; uint numwei,numshu; //数码管位置分配 jbai1=zongjine1/1000; jshi1=zongjine1%1000/100; jge1=zongjine1%100/10; jxiaoshu1=zongjine1%10; jbai1=licheng1/1000; lshi1=licheng1%1000/100; lge1=licheng1%100/10; lxiaoshu1=licheng1%10; //数码管动态显示 if(aa) { aa=0; numshu++; if(numshu==8) numshu=0; P0=0xff; P2=0xff; switch(numwei) { case 0:P20=0;P0=table[jbai1];break; case 1:P21=0;P0=table[jshi1];break; case 2:P22=0;P0=table[jge1];break; case 3:P23=0;P0=table[jxiaoshu1];break; case 4:P24=0;P0=table[lbai1];break; case 5:P25=0;P0=table[lshi1];break; case 6:P26=0;P0=table[lge1]|0x80;break; case 7:P27=0;P0=table[lxiaoshu1];break; } numwei++; if(numwei==8) numwei=0; } } void display2(uint zongjine2,uint licheng2) //数码管显示方式2 { uint jbai2,jshi2,jge2,jxiaoshu2,lbai2,lshi2,lge2,lxiaoshu2; uint numwei,numshu; //数码管位置分配 jbai2=zongjine2/1000; jshi2=zongjine2%1000/100; jge2=zongjine2%100/10; jxiaoshu2=zongjine2%10; jbai2=licheng2/1000; lshi2=licheng2%1000/100; lge2=licheng2%100/10; lxiaoshu2=licheng2%10; //数码管动态显示 if(aa) { aa=0; numshu++; if(numshu==8) numshu=0; P0=0xff; P2=0xff; switch(numwei) { case 0:P20=0;P0=table[jbai2];break; case 1:P21=0;P0=table[jshi2];break; case 2:P22=0;P0=table[jge2];break; case 3:P23=0;P0=table[jxiaoshu2];break; case 4:P24=0;P0=table[lbai2];break; case 5:P25=0;P0=table[lshi2];break; case 6:P26=0;P0=table[lge2];break; case 7:P27=0;P0=table[lxiaoshu2];break; } numwei++; if(numwei==8) numwei=0; } } void keyscan() //键盘扫描 { danjia1=20; danjia2=15; qibu=50; if(key3==0) //功能键调节 { delay(5); if(key3==0) { key3num=1; while(!key3); delay(5); while(!key3); while(key3num) { if(key3num==1) //调单程单价 { if(key1==0) //单价增加 { delay(5); if(key1==0) { danjia1++; if(danjia1==100) danjia1=0; while(!key1); delay(5); while(!key1); } } if(key2==0) //单价减小 { delay(5); if(key2==0) { danjia1--; if(danjia1==-1) danjia1=99; while(!key2); delay(5); while(!key2); } } display1(1,danjia1); } if(key3num==2) //调返程单价 { write_data(1,danjia1); //单价增加 if(key1==0) { delay(5); if(key1==0) { danjia2++; if(danjia2==100) danjia2=0; while(!key1); delay(5); while(!key1); } } if(key2==0) //单价减小 { delay(5); if(key2==0) { danjia2--; if(danjia2==-1) danjia2=99; while(!key2); delay(5); while(!key2); } } display1(2,danjia2); } if(key3num==3) //调起步价 { write_data(2,danjia2); if(key1==0) //起步价增加 { delay(5); if(key1==0) { qibu++; if(qibu==100) qibu=0; while(!key1); delay(5); while(!key1); } } if(key2==0) //起步价减小 { delay(5); if(key2==0) { qibu--; if(qibu==-1) qibu=99; while(!key2); delay(5); while(!key2); } } display1(3,qibu); } if(key3num==4) //退出功能键 { write_data(3,qibu); key3num=0; } if(key3==0) { delay(5); if(key3==0) { key3num++; while(!key3); delay(5); while(!key3); } } } } } } void init() { licheng1=0; flag=0; flag1=0; SDA=1; SCL=1; zongjine=0; licheng=0; dengdai=0; danjia1=read_data(1); danjia2=read_data(2); qibu=read_data(3); //从EEPROM读数据 aa=0; //数码管动态扫描的定时器时基个数 bb=0; //判断是否等待的时基个数 inter=0; EA=1; //开总中断 EX1=1; //开外部中断1 IT1=1; //触发方式下降沿 TMOD=0x01; TH0=(65536-1000)/256; TL0=(65536-1000)%256; TH1=(65536-500)/256; TL1=(65536-500)%256; ET0=1; //开定时器T0中断 TR0=1; //开定时器T0 ET1=1; //开定时器T1中断 TR1=1; //开定时器T1 P2=0x00; P0=table[0]; } void jisuan() { if(licheng>30) zongjine=qibu+((licheng-30)/10)*danjia+dengdai/5*danjia; //金额计算 else zongjine=qibu+dengdai/5*danjia; //起步公里内金额计算 } void qiehuan() { if(key1==0) //单程返程切换 { delay(5); //键盘防抖 if(key1==0) qiehuantemp=!qiehuantemp; while(!key1); delay(5); while(!key1); } if(qiehuantemp==0) { danjia=danjia2; P35=0; P34=1; P36=1; } if(qiehuantemp==1) { danjia=danjia1; P34=0; P35=1; P36=1; } } void chaxun() //查询功能 { if(key4==0) { delay(5); if(key4==0) { // flag++; flag=!flag; } } if(flag==0) display(zongjine,licheng); else if(flag==1) { display2(4,dengdai); } } void zhanting() //暂停功能 { if(key0==0) { delay(5); if(key0==0) { flag1=!flag1; } } if(flag1==1) { EX1=1; ET0=1; //开定时器T0中断 TR0=1; //开定时器T0 ET1=1; //开定时器T1中断 TR1=1; //开定时器T1 P34=1; P35=1; P36=1; } else if(flag1==0) { P34=1; //空车指示 P35=1; P36=0; EX1=0; //关闭中断 ET0=0; //关闭定时器中断 TR0=0; //关闭定时器 } } void main() { init(); qiehuantemp=1; key3num=0; while(1) { qiehuan(); //切换单程返程单价 jisuan(); //计算总金额 keyscan(); chaxun(); zhanting(); } } void inter1() interrupt 2 //脉冲中断 { if(exter==0) { IT1=1; inter++; if(inter==100) { inter=0; licheng++; } } } void timer0() interrupt 1 { TH0=(65536-1000)/256; TL0=(65536-1000)%256; bb++; temp1=licheng; //测试是否进入等待 if(bb==10000) //1分钟无反应进入等待计费 { bb=0; if((temp1<=temp)&&(temp<=(5+temp1))) dengdai++; temp=licheng; } } void timer1() interrupt 3 { TH1=(65536-500)/256; TL1=(65536-500)%256; aa++; } 附录2 系统原理图