吴金秀
(黄冈职业技术学院 湖北 黄冈 438002)
[中文摘要]制作的自走车就是在整体的规划下利用8051单晶片的程序设计,来控制直流马达和光感测器。感测器和单晶片的知识,是本专题最重要的基本元素。此自走车不同于一般设计沿黑线行走的自走车,而是利用程序作为控制输入,并由回授讯号做动态调整以达到我们所期望的行为。相信在不久将来,拥有高度智慧并多功能的自走车机器人,将会走入我们的生活中,成为不可或缺的一环。
[关键词] 自走车; 马达; 感测器 ; 8051单晶片; 程序设计
Self-propelled vehicle research and design
Wujinxiu
(Huanggang Polytechnic College Huanggang 438002 Hubei)
[Abstract] produced by self-propelled vehicle that is in the overall planning of the 8051 single-chip program designed to control a DC motor and optical sensors. Knowledge of sensors and single-chip, is the most important basic elements of the topic. This self-propelled vehicle is different from the self-propelled vehicle is generally designed to walk along the black line, but to use as a control input by the feedback signal for the dynamic adjustment in order to achieve the desired behavior. I believe in not long future, with a high degree of intelligence and multi-purpose self-propelled vehicle robot will walk into our lives has become an indispensable part of.
[Key words] self-propelled vehicle; motor; sensors; 8051; program design
1、前言
在科技日新月异的时代,关于移动式机器人或自走车的研究技术已相当广泛。已被使用于自动控制、医学、工业、或是各式样式的交通工具、而各国也积极的投入各项人力、物力在这方面的领域上,也研究各种不同领域功能的机器人,例如智慧型机器人、救难型机器人、无人搬运车、足球机器人等研究,都已相当成熟。自走车的组装与控制是由机械、电子和问题的处理所组合,自走车在现实生活中的应用越来越广。现今的世界中,常常出现各式各样的自走车比赛,而世界各国的机器人研发也是越来越厉害,不再只是简单的动作行为,而是伴随着许多功能的全能机器人。
1.1研究动机及其意义
1.1.1研究背景
自动化的时代,各种微处理器的普及、进步,广泛的运用,使人们的生活更加便利,生活质量也不断的提升,小时候常拿着一台遥控汽车,用遥控器来操控方向。长大后才发现其实遥控汽车并不只是用来驰乘,而是在许多人们无法到达的地方,能完成更多、更艰难的任务。作为探勘地形、或是救援工作等狭小空间,仅只能让小型机械到达的地方。国内外自走车的研发已经有几十年的历史了,要完成一台会跑会动的自走车,需结合电脑软件、电子、电机及机械方面的知识与技术,亦即具备控制能力,我们这里结合机器人学、控制工程学、机械力学及机构学等学问,设计一台运用8051及光感测器等来做控制的自走车。使用红外线作为传送媒介,然后焊接电路板和组装车子。
1.1.2研究目的
自动化的主要目的就是取代人力,达成过去在人工时代无法达到的目标;利用电源电路、马达、外加感测器电路,以及微电脑系统8051程序语言来制作自走车。 藉由一般市面上常见的红外线的传送和接收器,通过8051晶片来控制马达的运转,使车子可以自由的行进,未来红外线的控制,也可利用在不同的用途,远端遥控的机器将会越来越多,[1]日后也许只要一只手机就可以遥控我们的各种家电。我们利用直流马达的驱动电路,及感测器的感测电路,及藉由8051程序撰写,来模拟自走车整个系统的动作。
2、8051单晶片设计
8051 是目前市面上很受欢迎使用的单晶片微处理器之一,是由INTEL公司开发出来,普遍地应用在工业界中。由于其使用普及,[2]许多设计半导体晶片的公司也有制造与8051 相容的单晶片,例如由ATMEL 公司所生产制造的89C51 单 13 晶片便与INTEL 公司的8051 完全相容,其间最大的不同是89C51 是可以重复烧录的,而8051 则不能重复烧录。我选择了8051 单晶片,[3]因为它的硬件架构及周边设备完整、指令集功能强大、程可复写功能等种种优势,正符合需求,而利用程序的模组化,副程序的应用,更容易阅读及进行修改。
由于自走车的大脑- 8051使用5V电源,因此我选用LM7805稳压IC提供稳定5V给8051。图2.1是供应8051电源所使用的电路,[4]当SW1接通后,LM 7805随即输出固定5V的电源供8051使用,电路中使用的两个电容则是用来稳定输入及输出电压,避免因为开关切换产生的突波造成元件损坏。
我们将整合电子、电机、机械等方面的知识,制作一组完整的训练器包含红外线感应元件CYN70、LCD 模组、语音电路、单晶 片、等四大部分,配合电源、机身、语音等硬件。训练器的动作原理是由LCD 的显示、利用按键设定要侦测的轮框圈数大小再来是设定温度及里程数、然后借着类比转数位温度感测、中文文字转语音经由8051 单晶片,由程序进行逻辑判断后,对语音电路下指令做发音动作,并利用CYN70 红外线感测配合实际转动脚踏车做模拟的动作。采用Flash ROM,内部具有4KB ROM 存储空间, 能于3V 的超低压工作,而且与MCS-51 系列单晶片完全兼容,但是运用于电路设计中时由于不具备ISP 在线烧录技术,当在对电路进行测试时,由于程序的错误修改或对程序的新增功能需要烧入程序时,对晶片的多次拔插会对晶片造成一定的损坏。 显示的部分由LCD 来显示,由于LCD 具备耗电低、电路简单容易,因此选用它。缺点是观察不易,字体过小。语音控制晶片采用L293 晶片,L293 晶片是一种高性能的控制晶片,精度高,控制容易。 测试里程数的部分,选用CNY70 红外线感测元件,[5]语音播报部分则选用AD178A 提供转换电路。电路设计如图2.1所示,
图2.1电路设计方块图
3、直流马达
3.1直流马达控制原理
直流马达与其他马达最大的差异在于其“转速-转矩”与“电流-转矩”特性均为线性关系,因此在一般需要做到转速、转矩控制的场合中,[6]若控制精度不需很高的情况下,同常以直流马达作为致动器是较为经济的选择。
要控制直流马达的启动以及停止,可使用图3.1.1所示的共射极达靈顿电路
图3.1.1直流马达驱动、停止电路
来完成,当输入端ML_ONOFF=1时,电晶体Q3、Q8饱和导通,C点约为饱和电压0.3V,因此马达M2启动,显示灯L1亮;反之,当输入端ML_ONOFF=0时,电晶体Q3、Q8 截止不通,C点浮接,因此马达停止,L1不亮。[7]当输入ML_ONOFF=1马达转动时,电源VDD向马达线圈充电,在线圈上储存了能量,根据楞次定理,当ML_ONOFF=0,C点浮接时,在线圈二端将出现一逆向电动势VR。其中N为马达的线圈匝数。此负电压加上VDD,将在C点形成极高的电压,非常容易对电晶体Q3、Q8造成破坏打穿,因此,为保护电晶体免受马达线圈逆向电动势的破坏,可在M2两端接二极体D10,当电晶体截止,逆向电动势出现时可予以吸收掉,而达到保护作用。
3.2直流马达特性曲线与选用方式
下图为12伏特直流马达特性曲线图,横轴为输出转矩,纵轴则分别为转速、电流以及效率与输出功率。
图3.1.2直流马达特性曲线图
选用动力电动机时必须考量的因素包含输出负荷大小、马达输出扭力、与转速曲线特性,同时也要考虑电源形式与运转模式。在选用直流马达时,必须注意它的工作电压,直流马达电源常見规格为DC12V与DC24V,交流马达则为AC110V与AC220V;另外还要知道输出扭矩大小(g-cm、kg-cm),以及转速(rpm),当然最好能有马达特性曲线,如电流转矩图与电流转速图等,以方便作为选用马达时的参考。计算扭力需求时,先计算欲旋转的物体转动惯量,再参考旋转速度决定减速比,然后决定马达工作扭力值,即可依照马达特性选择适用型式。
3.3 直流马达驱动电路:
本次设计自走车即采用双轴直流马达驱动器,如下图所示。
图3.1.3(a) 双轴直流马达驱动器
H-Bridge,这部分在整个自走车的硬体部分,占有极重大的位置。而就自走车整体来说,影响H-Bridge 的为前ㄧ级输入(闸极触发电路),功能为确保臂间不会短路。而H-Bridge 的种类繁多,我们选择以POWER MOSFET 来快速控制开关ON/OFF 的切换,原因如下:
1.能够快速切换。
2.在较高的之下,具有较低的导通电阻。 DSVDS(ON)R3较低,也就是说可以使用较低的闸极电压触发。 在POWER MOSFET 旁边加装一飞轮二极体,以避免POWER MOSFET 被过大的逆电流击穿。RC 网路是用来限制电晶体tvdd这项参数。逻辑闸部份,为H-Bridge 的前级电路,是以用来避免马达转动时产生高频干扰,造成CPU当机;以及避免H-Bridge 的臂间短路。[8]依照PWM的驱动方法, PWM 是一种利用脉波控制转速的方法,当输入马达的电源为high 时,马达受到电压供应而转动;当输入为low 时,则没有电源供应给马达,此时马达借着本身的惯性转动,转速会逐渐减慢最后停止。因此PWM 是一种利用电源high、low 的切换来控制马达转速的方法。所以脉波周期必须固定,然后再藉由改变脉波的波宽比(Duty Cycle:脉波宽与周期之比)达到改变速度的目的。当波宽比发生变化时,供给马达的平均电流发生变化,于是转速即产生改变。
4、继电器
继电器(Relay),也称电驿,是一种电子控制器件,它具有控制系统(又称输入回路)和被控制系统(又称输出回路),通常应用于自动控制电路中,[9]它实际上是用较小的电流去控制较大电流的一种自动开关。故在电路中起着自动调节、安全保护、转换电路等作用。继电器与一般开关不同,继电器并非以机械方式控制,而是一种以电磁力来控制切换方向的电门。
4.1 继电器选用
电磁继电器是一种电路控制元件,常用在需要自动化控制之机器、设备中,例如通信交换机、工业控制、汽车、冷气机、灯具、不断电供电系统等。当线圈通电后,会使中心的软铁核心产生磁性,将横向的摆臂吸下,而臂的右侧则迫使电门接点相接,使两接点形成通路。
主要构造,是由电磁铁的线圈组件与电路触点组件所构成。电磁继电器的基本原理,是自线圈两端加上额定的电压,一定的电流会通过线圈使线圈组件形成电磁铁,从而吸引触点组件之可动部份,致使电路发生通断作用。电磁继电器的触点依动作分为静触点、动触点。依电路作用分为常开触点、常闭触点. 当其线圈未通电时处于断开状态的静触点,称为常开触点( NO) ,处于接通状态的静触点称为常闭触点( NC)。另外继电器规格除了电门接点数目不同,还要注意线圈的工作电压是直流或是交流电,使用的电压电流大小,切换电门耐电压程度等,继电器的规格有3V、6V、9V、12V、 24V、48V、100V、110V、200V、220V等,
本制作使用双轴双切的继电器,它有八支接脚。为5V的直流继电器,8051的I/O Pin通常是无法直接推动继电器的线圈,用来辅助推动继电器线圈的方法很多,电晶体开关是最广泛被常用的方式。
图4.2 双轴双切继电器
当8051 I/O pin 为高电位时,PNP电晶体的E - B极无顺向偏压,PNP电晶体的E - C极不导通,NPN电晶体的B极没有电压, NPN电晶体的C极到E极不导通,如同开路,继电器线圈不起动。当8051 I/O Pin 为低电位时,PNP电晶体的E - B极得到顺向偏压,PNP电晶体的E - C极导通,VCC被送到NPN电晶体的B极,此时NPN电晶体B极到E极有大于0.7V的顺向偏压,使电晶体工作,电晶体的C极到E极呈现导通,而起动继电器线圈。
4.2 声音感应
无声音时,麦克风内阻大,如同开路状态,分压值等于Vcc送到比较器的正输入端,而比较器的负输入端是由两个相同阻值的电阻分压,分压值等于1/2 Vcc,比较器的正输入端电压大于负输入端,所以比较器的输出为Vcc,也使555 IC的输出保留在不动作的低电位状态。当声音被麦克风感应到时,麦克风内阻瞬间下降,比较器的正输入端电压随即下降,当小于比较器负输入端的1/2 Vcc时,比较器的输出端转态为Low,进入555 IC的触发(Trigger)输入,使555 IC的输出(第3脚)产生一个正脉冲,让8051得以取得此声音的信号,来执行相对应的功能。
5、光感测器
光感测器为利用光线以检知受体的一种感测器。使用上分为二类:(1)反射型:光源与感光元件并排放置,光线是否自壁面反射来判断壁面的方式。 (2)遮光型:光源与感光元件设置相向的位置,当从光源射往感光元件的光线被遮断时,即判断其间有壁面存在的方式。光感测器使用的光大致上有分为可见光及红外线两种,分别称为可见光感测器及红外线光感测器。可见光的坏处是易受环境的影响,例如可见光会被黑色吸收。使用红外线的好处是,其不会受到室内照明的影响,但由于红外线无法看见,很难确认其动作。
5.1 CNY70
5.1.1 CNY70内部结构
感测器在许多以自动控制里占有相当大的地位,就像是电脑鼠也是需要用到感测器来做它的测距功能一样;而它的作用在于感测控制车现在的位置所在并提供行走时路的状况告知给CPU 来作判断是否继续行进。CNY704.2.1 CNY70 内部结构CNY70 的内部结构如图5.1所示:
图5.1光感测器CNY70内部结构
其中包含红外线发光二极体,光电晶体,以及光滤波器,其功能分别是: 1.红外线发光二极体:类似发光二极体(LED)的功能,当PN 两端加上顺向偏压时可发出波长为800nm 的红外线不可见光。 2.光电晶体: 为一个对红外线波长具敏感反应的光侦测元件,当光电晶体受红外线光照射时为低阻抗,而未受光时呈现高阻抗。 3.光滤波器:唯一仅让波长为红外线附近光谱通过的滤光透境,可用来加强光电晶体的杂抗讯能力(红外线以外不可见与可见光的干扰)。
5.1.2 CNY70 动作原理
CNY70 光感测器,其光源和感测元件是做在一起,其动作是光遇到普通地面,就会反射到感测元件。光遇到黑色胶带会被吸收,我们这次自走车使用了三颗光感测器,其目的在于侦测黑色胶带的路径,提供状况给8051微处理机做判断。其动作原理由5V经220 限流电阻连接CNY70的光源LED的A脚,K 接地。当CNY70 在普通地面上时,光电晶体接收到反射光,E 脚输出高电位;在黑色胶带上时,没有接收到光时,则为低电位。由于CNY70 的E 接脚在有反射物时为高电位5V,而无反射物时则为低电位0V,我们便可利用这特性使CNY70 去感测出信号输出给8051 作为输入。但是 CNY70 的E 接脚的电位也会因为接受到反射光的多寡而有所变化,无法一直准确的维持0V 和5V 的输出,所以我们便在后面加了个74LS14 IC 来使讯号于予反向整型。74LS14 这个IC 在此电路所扮演的功能是一个反向器,当CNY70 E 接脚输出 为0V 时,经由74LS14 便会反向输出5V 的稳定电压,而若是E接脚输出的电压 大于0V 时,74LS14 相对的会全部反向输出0V 的低电位,如此8051 便可以精确地判断光感测器出来的结果,进而驱动马达去作调整。 而为了调整电路地灵敏度,我们同时在E接脚接了个SVR 50KΩ 的可变电阻,目的是在调整电路的灵敏度,当SVR 50KΩ 调小时,流过光电晶体的射极电流IE较大,因此光电晶体较容易饱和,灵敏度变大;相反地,当SVR 50KΩ 调大时,灵敏度变小,在实际的应用中,为求最佳的灵敏度控制,应在不同的环境下随时调整SVR 50KΩ的大小。当自走车放在白板上时,红外线感测电路因CNY70之发光二极体所发射的红外线经白板反射至光电晶体,光电晶体饱合,射极电压为高态,因此经枢密特IC 4584取反相后,输出低态,指示灯(LED)不亮;而当CNY70在电工胶带所贴的导引道路上时,因电工胶带为黑色会吸光,因此CNY70发光二极体所发射的红外线无法反射至光电晶体,光电晶体几近截止,射极电压为低态,而电压经4584取反相后,输出为高态,LED亮。
5.2感测器驱动电路
伏特电源经由330Ω的限流电阻提供给红外线发射器稳定的电压,以使红外线发射器稳定而持续地发出红外线。当光电晶体接收到红外线时(有墙),则光电晶体会依灵敏度的大小而饱和,使Ve 接近5V,再经由4584(史密特反向触发器),使输出Vb 等于0V,再将输出接至I/O 控制晶片8255 处理。其中使用500K可变电阻的目的是再调整电路的灵敏度,当500K 可变电阻调小时,Ie 较大,光电晶体较容易饱和,也就是灵敏度较高,相反的,当500K 可变电阻调大时,灵敏度较低。 IC4584 是一个史密特反向触发器,它兼具有史密特触发器以及反向闸的功能,在这里使用史密特触发器的目的就是过滤外界干扰,已避免外界的小变动就使输出状态改变。
图5. 2 感测器驱动电路
5.2.1 红外线遥控
红外线是目前最常见的一种无线通讯,普遍使用在家电以及玩具产品,如电视、音响、錄放影机、冷气机、DVD、MP3 Player、遥控车等。红外线遥控之所以被大量采使用,主要是因为红外线装置体积小、成本低、耗电少及硬体设计容易。一般来说,红外线遥控系统由发射器和接收器这两部份组成。
日常生活中有这么多红外线光源,当然会对遥控造成干扰,所以得做一些预防措施确保通讯正确。避免干扰的解药是调变(Modulated)。我们讲话速度若适当,则听得舒服,听者自然不漏接。相同的道理,利用Modulation 让IR LED 以特定的频率闪烁,Receiver 端也调整到同样的频率,便可以忽略干扰。在下图中,可以看到调变讯号(Modulated Signal)在驱动IR LED 发射讯号,而侦测到的讯号则从右手边的Receiver 跑出来。
图5.2.1 调变讯号驱动IR LED发射讯号
红外线发射、接收器均装置在自走车上,以反射的方式感测有无物体在范围之 内。使用红外线作为感测媒介的特色是速度快(相较于超音波感测),可以让自走车在最快的时间反应;并且在反射良好的情况下,感测距离可达30 公分,而且具有强烈的方向性。为了避免周围环境的干扰,在发射端我们以8051 产生方波A,将方波输入红外线发射电路,让红外线藉由载波B 的形式传送到接收电路。并设计一滤波器除去接收讯号C 的杂讯,借以保持讯号的可靠性。接着我们将放大后的弦波信号经过一整流器后可得到直流信号E。经由A/D 转换,可得到一组8bits 数位讯号F,并将之输入8051 处理与判断之后送出4bits 的控制讯号G 至马达驱动电路以控制马达作出适当的反应。接收器,接收红外线发射器来的讯号,再发射讯号给8051。
5.2.2焦电型红外线感测元件
感测元件比较感测电路就像人类对周遭环境的感觉,可以是用看的、用听的或者是用四肢去摸索的,而其中,最重要的莫过于感测元件的选择,因为感测器的感测方式与灵敏度将对于四周壁面的状况产生出最直接的反射讯号,提供CPU 做逻辑判断,本制作所使用的是焦电型红外线感测元件,主要是感应热体有温度的物体)所辐射的红外线。当热体移动(温度有变化)的时候,将于感测器的焦电板上产生电荷的变化与转移。焦电型红外线感测元件乃是以TGS或PZT为材料,能侦测红外线的变化。然所产生的电荷变化不易使用,故于焦电板之后,接电阻与TET,把电荷变化转换为电压变化。经过滤光处理后,人体感测器大都只针对人体温度所产生的红外线波长有最好的反应。当热体(人的手或身体)移动的时候,会于焦电板上产生电荷的改变与转移,使能于R上得到微小的电压变化。再经FET缓冲(放大率1倍),由S极输出。
图5.2.2 动作时V的变化 S
由动作波形得知,焦电型人体感测器:
(1)输出电压VS非常小(mV)……(必须放大数千倍)
(2)热体必须移动,才有电压变化……(并非温度量测)
(3)背景温度不同时,会有不同的VDC……(尽量使用交流放大,能克服VDC的不同) 因焦电型红外线感测器的输出电压非常小,可能小到1mV以下,若想得到几伏特的输出时,势必要放大数千倍,甚至上万倍,所以不宜只用一级放大,因为对放大器而言,放大率与频宽的乘积为常数。
放大率 × 频宽 = 常数
想要频宽大一点,则必须牺牲放大率
放大被率大的时候,频宽会变窄
当用多级串接放大的时候,各级增益不必太大,一则较不易受干扰或产生震荡现象,再则每一级都保有一定的频宽。例如放大率为50倍串接后,将可以得到约2500倍。输出电压VS很小,若一开始就从电源感应杂讯,再经其后数千倍的放大,将无法分辨是正确的感应信号,还是杂讯所造成的干扰,所以在感测器的电原先加上〝反交连电路〞以率除电源内的干扰信号。
5.3自走车电路
自走车基本结构如图:
图5.4自走车8051总电路图
}
图6自走车全貌
六、程序
此红外线遥控车使用KEIL C51 C 语言程序,使用者需先安装KEIL uVision 2 的编译软件,相关的8051 C 语言原始程序码,都在KeilC51 的目录下。安装好KEIL uVision 2 后,在KeilC51 目录下直接点选HCR101 这个project file 即可。
程序结构主要分成3 个程序模组: Main.c:主程序,执行主要的程序回路、感应输入辨别及相关程序处理、以及马达、灯号控制。SensorIn.c 及SensorIn.h:感应输入侦测、输入信号弹跳处理。 Tick.c 及Tick.h:计时器中断处理,提供标准钟控时间,用于标准延迟时间计算。 [19]用Keil uVision2 组译后,烧录档HCR101.HEX 会产生在EXE 的目录下。
3.2.1 讯号输入及等待模式
由于自走车的动作全赖讯号输入来驱动,所以讯号的输入颇为重要,又焦电型红外线感测器所产生的讯号为一脉冲讯号,但是追踪动作不能因为讯号的消失而就此停止,必须等到确定追到定位才能休息,因此在脉冲消失而新的脉冲尚未产生前,必须保留一组前一次输入讯号的「回音」,让8051在动作的时候仍然能够有所依据。
然而当没有讯号输入的时候就必须进入等待模式。等待模式的主要目的是计时,以便在没有目标物的情况下,5秒后进入搜寻模式。从判断有无讯号输入到是否进入等待模式,以8051的工作时脉来说,只需要15us,因此不虞会有错过的情形发生。
3.2.2 搜寻模式及主程式:
在正常的情形下,顺利的从sensor输入了要追縱的目标物所在的讯号,就会进入(1) 主程式,来进行追踪,但是如果当上述的等待时间终了之后,还是没有有任何目标物的讯号输入,就会进入(2) 搜寻模式,分述如下:
(1) 主程式:
主程式的功用就是解读从感测器传来的讯号,并且加以判断,以做出符合预期的反应,例如在右边感测器有讯号时右转,中间感测器有讯号时直行等等。其设计的原理是把步进马达的脉冲信号存放在8051所保留的暂存区中,并且设计一辨别左右步进马达之旗标,以左旋指令(RL)或右旋指令(RR)来驱动马达的正转反转。解读讯号和控制正反转的程式
29
部份又是互相独立的,这样做的好处是当要改变判别规则时,不需要做出大幅度的修正,只要修改单一部份就可以了。
(2) 搜寻模式:
搜寻模式是为了寻找目标,得到其相关位置的讯号而设计的,设计原理是把搜寻模式写在主程式之前,当没有任何输入,又超过等待时间的话,就会丢给主程式一个「假的」输入讯号,使之右转180°,再左转180°,最后再右转回到原来的位置。之所以使用「假的」输入讯号,原因是同样可以达成驱动马达左右转的目的,而又不必再在搜寻模式下写出让马达左右转的重覆部分,大大简化了程式内容的复杂度。至于如何判断讯号是真是假,只要在接收讯号的最前端程式部份,加上一真伪旗标,当任何时候,只要输入信号「曾经」不为零,就不会启动搜寻模式,除非在追踪到目标物之后,又重新等待了超过设定的时间,才会再度进入搜寻模式。
30
程序列表及注解说名
【Main.c】
#include <reg51.h>
#include "tick.h"
#include "sensorin.h"
#define WHEEL_FORWARD 1 //车轮向前
#define WHEEL_BACK 0 //车轮向后
#define MOTOR_ON 0 //马达起动
#define MOTOR_OFF 1 //马达停止
#define MOVING_STRAIGHT 0 //直走狀态
#define MOVING_LEFT 1 //左转狀态
#define MOVING_RIGHT 2 //右转狀态
#define BLINKONTIME 2 //闪灯亮的时间
#define BLINKOFFTIME 50 //闪灯灭的时间
sbit LeftMotorDir = P1^5; //控制左马达方向
sbit RightMotorDir = P3^7; //控制右马达方向
sbit MotorSwCtrl = P3^4; //控制左右马达电源
sbit MotorSwLed = P0^5; //马达起动显示LED
sbit RunBackLed = P0^7; //倒转显示LED
sbit BlinkLed = P0^3; //闪灯LED
sbit RunLeftLed = P0^0; //左转显示LED
sbit RunRightLed = P2^1; //右转显示LED
void BlinkLight(void);
bit fBlinkOn;
unsigned int BlinkDelay;
unsigned int BlinkTimeRec;
main()
{
unsigned int SensorStatus,LastStatus;
unsigned char MovingDir;
bit fMovingForward;
//系统初始设定
LeftMotorDir = WHEEL_FORWARD; //左马达初始向前
RightMotorDir = WHEEL_FORWARD; //右马达初始向前
MotorSwCtrl = MOTOR_OFF; //马达关闭
MotorSwLed = 1; //马达起动灯号关闭
InitSensorIn(); //起始感应输入狀态
InitTick(); //起始计时器
LastStatus = SEN_None; //上一狀态设为无狀态
fMovingForward = 1; //预设车向前走旗标
MovingDir = MOVING_STRAIGHT; //车初始狀态设为向前狀态 //闪灯初始设定
fBlinkOn = 0;
BlinkDelay = BLINKOFFTIME;
BlinkTimeRec = GetSystemTick();
while (1)
{
BlinkLight(); //闪烁灯号处理
SensorStatus = GetSensorStatus(); //读取按钮感应输入狀态
if (SensorStatus != LastStatus) //检查按钮感应输入狀态是否有改变
{
// 按钮感应输入狀态有改变, 执行相对应的功能
switch (SensorStatus)
{
case SEN_Start: //马达起动与关闭按钮
if (MotorSwCtrl == MOTOR_ON)
{
MotorSwCtrl = MOTOR_OFF;
MotorSwLed = 1;
}
else
{
MotorSwCtrl = MOTOR_ON;
MotorSwLed = 0;
}
break;
case SEN_Forward: //向前按钮
// 设定车向前
LeftMotorDir = WHEEL_FORWARD; //设定左马达向前
RightMotorDir = WHEEL_FORWARD; //设定右马达向前
RunBackLed = 1; //关闭倒车LED
fMovingForward = 1; //设定向前旗号
break;
case SEN_Back: //向后按钮
// 设定车向后
LeftMotorDir = WHEEL_BACK; //设定左马达向后
RightMotorDir = WHEEL_BACK; //设定右马达向后
RunBackLed = 0; //打开倒车LED
fMovingForward = 0; //清除向前旗号
break;
case SEN_Left: //左转按钮
if (fMovingForward)
{ //前进狀态下向左转
LeftMotorDir = WHEEL_BACK; //设定左马达向后
RightMotorDir = WHEEL_FORWARD;//设定右马达向前
}
else
{ //倒车狀态下向左转
LeftMotorDir = WHEEL_FORWARD; //设定左马达向前
RightMotorDir = WHEEL_BACK; //设定右马达向后
}
RunLeftLed = 0; //打开左转LED
MovingDir = MOVING_LEFT; //设定左转旗号
break;
case SEN_Right: //右转按钮
if (fMovingForward)
{ //前进狀态下向右转
LeftMotorDir = WHEEL_FORWARD; //设定右马达向前
RightMotorDir = WHEEL_BACK; //设定左马达向后
}
else
{ //倒车狀态下向右转 <, BR>LeftMotorDir = WHEEL_BACK; //设定左马达向后
RightMotorDir = WHEEL_FORWARD; //设定右马达向前
}
RunRightLed = 0; //打开右转LED
MovingDir = MOVING_RIGHT; //设定右转旗号
break;
case SEN_None: //其他狀态
// 无按钮感应
if (MovingDir != MOVING_STRAIGHT)
{ // 在左转或右转中放开按钮
if (fMovingForward)
{ //车为向前狀态,所以让车继续向前
LeftMotorDir = WHEEL_FORWARD;
RightMotorDir = WHEEL_FORWARD;
}
else
{ //车为倒车狀态,所以让车继续向后
LeftMotorDir = WHEEL_BACK;
RightMotorDir = WHEEL_BACK;
}
RunLeftLed = 1; //关闭左转LED
RunRightLed = 1; //关闭右转LED
MovingDir = MOVING_STRAIGHT; //回到直行狀态 (前进或倒车)
}
break;
default:
break;
}
LastStatus = SensorStatus; //储存此次按钮感应狀态
}
}
}
/*------------------------------------------------ -----
闪烁灯号处理
-------------------------------------------------- ---*/
void BlinkLight(void)
{
if ((GetSystemTick() - BlinkTimeRec) >= BlinkDelay) //检查闪灯亮灭时间到
{
BlinkTimeRec = GetSystemTick(); //记录目前钟控时间
if (fBlinkOn) //是否要亮
{
BlinkLed = 0; //闪灯亮
BlinkDelay = BLINKONTIME; //保持亮的时间
fBlinkOn = 0; //亮的时间到后,处理闪灯灭
}
else //闪灯灭直处理
{
BlinkLed = 1; //闪灯灭
BlinkDelay = BLINKOFFTIME; //保持灭的时间
fBlinkOn = 1; //灭的时间到后,处理闪灯亮
}
}
}
【SensorIn.h】
#define KeyPort P1 //按钮读取埠
typedef enum KeyStatusEnum {
SEN_None = 0x000,
SEN_Right = 0x001,
SEN_Left = 0x002,
SEN_Back = 0x004,
SEN_Forward = 0x008,
SEN_Start = 0x010,
} KeyStatusType;
#define KP_MASK (SEN_Forward | SEN_Start | SEN_Back | SEN_Left | SEN_Right)
#define AnyKey() ((KeyPort & KP_MASK) != 0) //检查有无任何按钮被按下
#define GetKey() (KeyPort & KP_MASK) //读取按钮狀态
unsigned char GetSensorStatus(void);
#ifndef SENSORIN_C
extern unsigned char KeyStatus;
#endif
extern void InitSensorIn(void);
extern void ScanSensorIn(void);
【SensorIn.c】
#define SENSORIN_C
#include <reg51.h>
#include "sensorin.h"
#define BOUNCE_CNT 3
typedef enum KeyPhaseEnum {
KB_Idle,
KB_Debounce,
KB_Pressed
} KeyPhaseType;
unsigned char KeyPhase;
unsigned char KeyStatus;
unsigned char KeyRecord;
unsigned char BounceTime;
/*--------------------------
扫瞄参数设定
--------------------------*/
void InitSensorIn(void)
{
KeyPort |= KP_MASK; //将按钮输入脚设为输入狀态
KeyPhase = KB_Idle;
KeyStatus = SEN_None;
KeyRecord = SEN_None;
BounceTime = 0;
}
/*--------------------------
读取按钮扫瞄狀态
--------------------------*/
unsigned char GetSensorStatus(void)
{
return KeyStatus;
}
/*--------------------------
按钮扫瞄阶段处理
--------------------------*/
void ScanSensorIn(void)
{
switch ( KeyPhase )
{
case KB_Idle: //闲置阶段
if (AnyKey()) //检查有无按钮被按下
{ //发现按钮被按下
BounceTime = BOUNCE_CNT; //设定弹跳过滤时间
KeyPhase = KB_Debounce; //设定下个阶段为过滤弹跳阶段
}
break;
case KB_Debounce: //过滤弹跳阶段
if (--BounceTime == 0) //弹跳过滤计数减1,并检查是否为0
{ //弹跳过滤计数到时,表示按钮狀态已稳定
if ( AnyKey() )
{ //有按钮被按下
KeyStatus = GetKey();
KeyRecord = KeyStatus;
KeyPhase = KB_Pressed; //设定下个阶段为按钮按下阶段
}
else
{ //没有按钮被按下
KeyPhase = KB_Idle; //设定下个阶段为闲置阶段
KeyStatus = SEN_None;
}
}
42
break;
case KB_Pressed: //按钮按下阶段
if ( KeyRecord != GetKey() )
{
/* 按钮狀态有改变, 需回到过滤弹跳阶段来辨别新的按
钮狀态 */
BounceTime = BOUNCE_CNT;
KeyPhase = KB_Debounce;
}
break;
default:
break;
}
}
【Tick.h】
#define TICK_INTERRUPT_PERIOD_MS 10 //钟控时间中断周期(ms)
#define MS_20 (20 / TICK_INTERRUPT_PERIOD_MS)
#define MS_30 (30 / TICK_INTERRUPT_PERIOD_MS)
#define MS_40 (40 / TICK_INTERRUPT_PERIOD_MS)
#define MS_50 (50 / TICK_INTERRUPT_PERIOD_MS)
#define MS_100 (100/ TICK_INTERRUPT_PERIOD_MS)
#define MS_200 (2 * MS_100)
#define MS_300 (3 * MS_100)
#define MS_400 (4 * MS_100)
#define MS_500 (5 * MS_100)
#define MS_600 (6 * MS_100)
#define MS_700 (7 * MS_700)
#define SEC_1 (10 * MS_100)
#define SEC_2 (2 * SEC_1)
#define SEC_3 (3 * SEC_1)
void InitTick(void);
unsigned int GetSystemTick(void);
【Tick.c】
/*------------------------------------------------ --
系统钟控计时中断服务常式
-------------------------------------------------- */
#include <reg51.h>
#include "system.h"
#include "tick.h"
#include "sensorin.h"
#define TICK_INTERRUPT_PERIOD_CNT
(((XTAL*TICK_INTERRUPT_PERIOD_MS)/1000)/12)
/************************************************* ********
MICRO_ADJUST = 计时器中断时间准确度微调,如中断误差,单位=指令时间, 值减少则调慢(中断周期时间调长)
校准方式:以117 报时为准,以24 小时的误差秒数来调整MICRO_ADJUST
以6MHz(1 计数=2us),100ms 中断一次为例的每调一刻度在1 秒内的秒差计算:
100ms 中断一次=> 每秒中断10 次,每秒/刻度调整: 10/2us = 5us
24 小时 = 24*60*60 秒 = 86400 秒
24 小时的变动 = 86400 * 5us = 0.432 秒
以12MHz(1 计数=1us),50ms 中断一次为例的每调一刻度在1 秒内的秒差计算:
50ms 中断一次=> 每秒中断20 次,每秒/刻度调整: 20/1us = 20us
24 小时 = 24*60*60 秒 = 86400 秒
24 小时的变动 = 86400 * 20us = 1.728 秒
以12MHz(1 计数=1us),1ms 中断一次为例的每调一刻度在1 秒内的秒差计算:
1ms 中断一次=> 每秒中断1000 次,每秒/刻度调整: 1000 * 1us = 1ms
以24MHz(1 计数=0.5us),10ms 中断一次为例的每调一刻度在1 秒内的秒差计算:
10ms 中断一次=> 每秒中断100 次,每秒/刻度调整: 100/0.5us = 50us
24 小时 = 24*60*60 秒 = 86400 秒
24 小时的变动 = 86400 * 200us = 17.28 秒
以24MHz(1 计数=0.5us),1ms 中断一次为例的每调一刻度在1 秒内的秒差计算:
1ms 中断一次=> 每秒中断1000 次,每秒/刻度调整: 1000 * 0.5us = 500us
以24MHz(1 计数=0.5us),200us 中断一次为例的每调一刻度在1 秒内的秒差算:
200us 中断一次=> 每秒中断5000 次,每秒/刻度调整: 5000 * 0.5us = 2.5ms
************************************************** *******/
#define MICRO_ADJUST 22 //钟控计时准确度微调
#define TICK_PERIOD ((65536-TICK_INTERRUPT_PERIOD_CNT)+MICRO_ADJUST)
unsigned int SystemTick; //系统钟控计时值
unsigned int RetTick;
/*================================================ ========
读取系统钟控计时值
================================================== ======*/
unsigned int GetSystemTick(void)
{
#pragma asm
/* 抓取系统钟控值 */
MOV A,SystemTick+01H
MOV RetTick+01H,A
MOV A,SystemTick
MOV RetTick,A
/* 比较系统钟控值有无变化 */
MOV A,SystemTick+01H
CJNE A,RetTick+01H,DiffByInterrupt
MOV A,SystemTick
CJNE A,RetTick,DiffByInterrupt
/* 系统钟控值有无变化 */
JMP GtRet
/* 系统钟控值有因中断而变化,重新抓取系统钟控值*/
DiffByInterrupt:
MOV A,SystemTick+01H
MOV RetTick+01H,A
46
MOV A,SystemTick
MOV RetTick,A
GtRet:
#pragma endasm
return(RetTick); /* 传回系统钟控值 */
}
/*------------------------------------------
初始系统钟控计时器中断设定参数[20]
------------------------------------------*/
void InitTick(void)
{
SystemTick = 0; //清除系统钟控计时值
TMOD &= 0xf0; /* 清除计时模式控制位元*/
TMOD |= 0x1; /* 设定 16 位元计时 */
TR0 = 0; /* 停止计时 */
TF0 = 0; /* 清除计时溢位旗号 */
TH0 = TICK_PERIOD >> 8; /* 载入系统钟控计时值高位元组*/
TL0 = (unsigned char)TICK_PERIOD; /* 载入系统钟控计时值低位元组*/
//PT0 = 1; //系统钟控中断为最高优先权
TR0 = 1; /* 开始计时 */
ET0 = 1; /* 致能计时器0 中断 */
EA = 1; /* 致能中断开关 */
}
/*------------------------------------------
系统钟控计时中断服务常式
------------------------------------------*/
void timer0 (void) interrupt 1 using 1
{
TR0 = 0; //停止计时
TH0 = TICK_PERIOD >> 8; //重载高位元组
TL0 = (unsigned char)TICK_PERIOD; //重载低位元组
TR0 = 1; //开始计时
SystemTick++; //递增系统计时时间
ScanSensorIn(); //扫瞄按钮狀态
}
结论:
在完成制作后,不难发现自走车它结合了电脑方面的软件设计,电子电机方面的电路布线与焊接,机械方面的车体制造及整合各方面的控制能力,可以说是我们研究与应用最佳的题材,因此当自己制造的成品完成后,心情自然十分喜悦。当然在整个制作的过程中,也遭遇到许多困难,在程序设计部分,自己所写的程序往往不能符合预期的动作,需一再修改;在整合各种电路时一直出错,电路板是洗了又洗,焊接焊到手抽筋,真的是令人头大,还好最后还是能顺利的解决问题,并把制作如期完成了。 在制作期间真的让我学习到不少东西,加强了我们在电机、电子、机械、控制及程序设计等软硬体的认知与实作能力,特别是对8051、马达和感测器方面的知识有了更深一层的了解。这些都是制作时得到的宝贵收获,如何把自己所学到的东西应用在生活中,才是制作最终的目的。下图就是设计的自走车全貌:
参考文献:
[1]Weidemann H. J., Pfeiffer F. and Eltze J., "The Six-Legged TUM WalkingRobot" ,
Proceedings of the IEEE/RSJ/GI International Conference on Intelligent Robots
andSystems, 2005. Vol.2, pp.1026~1033,
[2]Bajd T. and Karcnik T., "Unstable States in Four-legged Locomotion",Proceedings
of the IEEE/RSJ/GI International Conference on Intelligent Robots and Systems,
Vol.2, 1994. pp.1019~1025,
[3]刘铮臻.基于视觉的自走车道路识别与自主导航.浙江工商大学,2010-2
[4]卢文涛.自走车辆转向控制算法及MATLAB的实现 .沈阳农业大学学报2011-3,
[5]钟明轩.8051 自走车机构设计国立高雄应用科技大学.2010-7
[6]刘耕宏.红外线控制自走车.2011-8
[7]李侃.单晶片自走车. .2011-5
[8]陈宏维.无人搬运车.2008-1
[9]王信光.感测自走车. 2010-5