本科毕业设计

Page 1

本科毕业设计

基于 Arduino 的群体机器人平台设计

院:

机械自动化学院

业:

机械电子工程

号:

200809155037

学生姓名:

指导教师:

期:

2012 年 05 月 20 日



武汉科技大学本科毕业论文

摘要 机器人技术是典型的机电一体化技术,深刻体现着机械电子工程学科的内涵。近年 来,随着多机器人系统规模的不断增大,系统控制复杂度及机器人之间的通信量成指数 倍地增加,难以利用传统的方法解决。而自然界中广泛存在的蚁群、鸟群和鱼群等社会 生物群体所涌现出的群体智能,给多机器人研究提供了很好的启发,于是产生了一个新 的研究领域——群体机器人学。与结构越来越复杂的单个机器人和传统的多机器人系统 相比,群体机器人系统的鲁棒性、可扩展性、灵活性和经济性都占有更大优势,具有广 泛的应用前景。 两轮式移动机器人以其短小精悍而受到众多机器人研究者和爱好者的亲睐,本文基 于开放的 Arduino(下位机)和 Processing(上位机)平台开发一种用于群体机器人协作 的两轮式机器人平台,以便于群体机器人学的研究人员可以将群体机器人的协作算法在 该平台上调试和验证。这样,不仅使产品开发没有专利成本,且其丰富的扩展软硬件将 会给开发工作带来便利。本文主要工作包括:机器人硬件系统设计和传感器选型(红外 有向通信模块、超声波避障模块、驱动模块、主控模块、电子罗盘等模块),以及相关 软件系统设计开发(运动控制程序、数据采集程序和上下位机通信程序等)。 实践证明使用该平台搭建机器人是简单且高效的,希望这将对机器人算法的研究从 仿真走向应用和普及机器人技术能起到一定的作用。

关键词:轮式移动机器人; 群体机器人; Arduino; Processing

I


武汉科技大学本科毕业论文

Abstract Robotics is a typical mechatronics technology which could well represent the meaning of mechanical and electrical engineering. As the scale of multi-robot systems is increasing in recent years, the system control complexity and the communication traffic of multiple robots will exponentially increase, and it is difficult to use traditional algorithms to solve this problem. In nature, there are many social insects—ants, termites, wasps and bees— which can be standed as fascinating examples of how collectively intelligent systems can be generated from a large number of simple individuals. Biologists, computer scientists and roboticists teamed up to transfer their knowledge of social insects behavior to the design of controllers for multi-robot systems. This combined, multidisciplinary effort gave birth to the field of research known as swarm robotics. Campared with the complex single robot and the tranditional multi-robot systems, the characteristics of swarm robotics, such as robustness, scalability, flexiblility and low cost, are better than them. So it will be well researched and widely used in the future. Two-wheel mobile robots are popular around robotics researchers because of its drapper and flexible. This article introduce to developing a system that based on Arduino(lower computer) and Processing (host computer) platform, so that researchers can learn swarm robotics by running and debugging their algorithm on the platform rather than simulation. This platform not only costs little patent, but also can be benefit from the extensive expansion of hardware and software. The main work of this paper include: the robot hardware system design and sensor selection (infrared module to the communication module, ultrasonic obstacle avoidance module, drive module, control module, electronic compass, etc.), as well as related software systems design and development (movement control procedures, data collection procedures and the upper and lower machine communication program, etc.). Practice has proved that using the platform to build the robot is simple and efficient, I hope this will be helpful for the swarm robotic reasearchers to test their algorithms from simulation to application and popularization of robotics.

Key words:Autonomous Mobile Robot; Swarm Robot; Arduino; Processing

II


武汉科技大学本科毕业论文 目录 1 绪论.............................................................................................................................................. 1 1.1 选题背景及研究意义 ..................................................................................................... 1 1.2 原型实现所选平台介绍 ................................................................................................. 3 1.3 设计安排和本文结构 ..................................................................................................... 4 2 轮式机器人总体规划 ................................................................................................................ 5 2.1 单个轮式机器人的整体架构规划 ................................................................................ 5 2.2 轮式机器人的机械结构规划 ........................................................................................ 6 2.3 轮式机器人的硬件电路规划 ........................................................................................ 8 2.4 轮式机器人的移动算法规划 ...................................................................................... 12 3 轮式机器人选用传感器介绍 .................................................................................................. 16 3.1 电子罗盘及三轴加速度计模块 GY27A .................................................................... 16 3.2 光电编码器的测速和里程记录功能 .......................................................................... 17 3.3 超声波距离传感器原理及结构 .................................................................................. 18 3.4 红外发射、接收集成块原理简介 .............................................................................. 19 3.5 无线通信模块的主要参数介绍................................................................................... 21 3.6 液晶显示模块主要参数 ............................................................................................... 22 3.7 蜂鸣器原理及工作方式 ............................................................................................... 23 4 轮式机器人动力及供电系统 .................................................................................................. 23 4.1 电机的选型和隔离电路设计 ...................................................................................... 23 4.2 电源变换、稳压和隔离系统设计 .............................................................................. 24 4.3 电源变换、稳压电路设计........................................................................................... 25 5 轮式机器人的基本运动及壁障功能程序设计 ..................................................................... 26 5.1 基本动作层.................................................................................................................... 26 5.1.1 前进、后退函数 ................................................................................................ 28 5.1.2 左转、右转函数 ................................................................................................ 29 5.2 壁障规划层.................................................................................................................... 29 5.2.1 避开前方和侧面障碍物 ................................................................................... 29 5.3 受控通信层.................................................................................................................... 31 5.4 液晶显示屏程序 ........................................................................................................... 31 5.5 机器人基本功能的上位机程序设计 .......................................................................... 32 结束语............................................................................................................................................ 34 参考文献 ....................................................................................................................................... 35 III


武汉科技大学本科毕业论文 致谢................................................................................................................................................ 36 附录 A 下位机 Arduino 程序源码............................................................................................. 37 附录 B 上位机 Processing 程序源码 ........................................................................................ 50

IV


武汉科技大学本科毕业论文

1 绪论 1.1 选题背景及研究意义 机器人技术综合了多种学科的发展成果,代表了高技术的发展前沿,成为各国争相 投入的重点研究领域。近年来,随着多机器人系统规模的不断增大,为了实现个体之间 的协作,许多系统采用了复杂的通信机制和任务指派机制,要求机器人个体必须具备复 杂的通信和决策能力,以达到在群体中进行信息交流和行为协调的目的。这就使得机器 人个体变得越来越复杂,整个系统的控制复杂度及机器人之间的通信量成指数倍地增 加,而传统多机器人系统的理论和方法很难解决这些问题。在自然界中,普遍存在的蚁 群、鸟群和鱼群等社会生物群体所涌现出的群体智能,给多机器人研究提供了很好的启 发,于是产生了一个新的研究领域—群体机器人学(Swarm Robotics)[1, 2]。与结构越来越 复杂的单个机器人和传统的多机器人系统相比,群体机器人系统的鲁棒性、可扩展性、 灵活性和经济性都占有更大优势,因此具有更加广泛的应用前景。目前,其应用主要集 中在以下领域: 1. 未知环境的探索 由于未知环境的复杂性,传统的多机器人系统往往无法胜任探索任务。而基于生物 群体行为的群体机器人系统,则对于复杂环境的适应能力大大提高。比利时布鲁塞尔大 学开发了一组名为―Swarm-bots‖的机器人[3],它由数个小型的 S-bot 通过夹子相互连接起 来,通过相互合作来完成个体无法完成的任务。图 1.1 展示了 Swarm-bots 合作探测复杂 环境的情况。

图 1.1 Swarm-bots 机器人探测未知复杂环境[3] 2.军事反恐领域 由于群体机器人系统具有密集型、分布式的特点,因此非常适合应用于恶劣的战场 环境中,包括敌军兵力和装备的监控、战场的实时监视、目标的定位、战场评估、核攻 1


武汉科技大学本科毕业论文 击和生物化学攻击的监测等多种用途。图 1.2 是一个军事应用场景,一群无人战车(UAV) 和无人机(UAA)相互合作,进行协同作战。此外,有军事专家表示,未来采用群体机器 人技术的仿生直升机将问世,而携带炸弹或侦察设备的机械蟑螂、机械蜜蜂也将出现在 战场之上。可以设想,群体机器人大战的场景将不再是科幻电影中的虚构场面,人类战 争的类型也将会因此而发生巨大的变化。

图 1.2

军事应用场景

3.灾害救援 瑞士洛桑理工学院(EPFL)利用一群自治微型飞行器在复杂任务环境中(例如地震、 洪水 、火 灾等 灾害 过 后的 救援 和重 建 工作),为 地面 用户 提供 一 个应 急通 信网 络 (SMAVNET)[4],如图 1.3 所示。该自治微型飞行器具有小型化、低成本、重量轻、电子 设备简单等特点。

图 1.3 灾害救援和重建应用场景[5] 4.其他领域 群体机器人系统还能应用于其他一些领域。例如,利用群体机器人进行农作物灌溉、 2


武汉科技大学本科毕业论文 无人化放养牲畜和家禽以及管道维修、社区的安保和监视工作、以及在一些危险的工业 环境如井矿、核电厂等代替人的工作,同时也可以作为智能交通系统的有力工具。 总之,群体机器人系统有着十分广泛的应用前景,它不仅在工业、军事、环境探测 等传统领域有巨大的运用价值,在未来还将在许多新兴领域体现其优越性,如家用、救 灾、空间探测和智能交通等领域,具有广阔的发展空间。但就现阶段而言,对群体机器 人系统的研究才刚刚开始发展,它的技术和应用还远谈不上成熟,仍然有很多工作需要 不断研究。正是在这一背景下,本文以开发一种小型轮式群体机器人平台为核心展开研 究,具有重要的科学和实际意义。本论文的研究得到以下项目的联合资助: 1. 群体机器人系统可控群集模型和合作控制研究 (湖北省教育厅科技计划中青年项 目: Q20121114) 2. 基于可控群集模型的群体机器人合作控制研究 (武汉科技大学青年科技骨干培育 计划:2011xz007)

1.2 原型实现所选平台介绍 本文使用一种新的,开放且界面较为友好的上位机(Arduino)和下位机(Processing) 平台,分别如图 1.4 和 1.5 所示。关于这两个平台的详细情况,请参考所列官方网站: Arduino:http://www.arduino.cc/ Processing:http://www.processing.org/

图 1.4 Arduino 的小巧外观图

1.5 Arduino 的集成开发环境

现对这两种平台的特点简述如下: Arduino 是一个全新理念的开放硬件平台,包括多款基于 Atmel 16 系列单片机的简 单易用的 I/O 电路板,以及初学者易于上手的 C 语言开发开发环境而高级开发者亦可使 用诸如 Eclipse、AVR Studio 等专业开发环境进行高级开发。Arduino 即可以被用来开发 能够独立运行,并具备一定互动性的电子作品,也可以被用来开发与 PC 相连接的外围 装置,还能与运行在 PC 上的软件(如 Flash,Max/Msp,Director,Processing 等)进行 沟通。由于该平台的开放性和易用性,使得世界各地的机器人爱好者和程序员转向该平 3


武汉科技大学本科毕业论文 台并为此平台开发了形形色色传感器、电机等电子元件的 C++类库,方便初学者上手也 使高级开发者能大大加快开发进程。 Processing 是一个开放源代码的程序语言及开发环境,提供给那些想要对影像、动 画、声音进行程序编辑的工作者。此外,学生、艺术家、设计师、建筑师、研究员以及 有兴趣的人,也可以用来学习,开发原型及制作。该开源平台不仅可以独立使用以快速 构件友好的 GUI 界面程序而非控制台并能与 Arduino 互动,其高级开发者还能以其为基 础转向 Java 并继续使用 Processing,使其成为 Java 开发的引用库,因而在高级开发上也 同样极具潜力。

1.3 设计安排和本文结构 本次设计由本文作者及同学院机械工程及自动化专业 3 班的李文华同学两人合作, 本文作者负责传感器选型、程序设计、控制电路设计。该轮式机器人的结构、电机驱动 电路的内容本文仅简略引用,详细设计说明请参考李文华同学的学士学位论文。 第一章绪论主要对国内外在机器人,尤其是群体自主移动式机器人方面的研究状况 和对设计所选平台的情况作简单介绍,并提出设计要求。 第二章从轮式机器人整体结构出发,以图表等形式展现轮式机器人在结构、电气等 方面的模块化划分和连接方法。 第三章简述轮式机器人对感知部分的需求,并根据需求选择适当的各类传感器模 块,给出所选传感器的重要参数指标和连接电路原理图。 第四章简要介绍了轮式机器人的动力系统以及采用锂电池供电所需的稳压、保护电 路。 第五章对轮式机器人核心功能的程序流程和算法以及相关的控制问题做出简要介 绍,具体完整的实现仍需查看附录所附程序源码。

图 1.6(a)轮式机器人照片侧面

图 1.6(b) 轮式机器人照片正面

4


武汉科技大学本科毕业论文 2 轮式机器人总体规划

2.1 单个轮式机器人的整体架构规划 移动机器人是一个复杂的综合型系统,搭建机器人平台显然属于机械、电子、控制 和计算机专业知识的融合。它包含了诸如车轮支承结构、齿轮传动、转动关节的连接和 润滑等机械元素,又包含诸如电机、驱动电源等电气元素,还包括诸如各类传感器、微 处理器等连接而成的电子电路元素,而基于嵌入式的控制系统和实现各类算法的控制程 序等也必不可少,是典型的机电一体化系统。而这些系统之间既有某种程度上的隔离, 又依靠各种接口相连且在功能上相互制约。 控制

执行系统

采集

决策系统

反馈

感知系统

图 2.1 机电一体化系统总体架构

所有的机电一体化系统都应该包括以上三个子系统,如图 2.1 所示。决策系统实时 的控制执行系统的运作,并通过感知系统反馈回来的数据校正系统运行过程中所产生的 误差。执行系统接受来自决策系统的控制指令,并且在结构上方便感知系统对执行结构 进行数据的采集。感知系统从执行系统采集到运行过程中的信息,并进行简单的处理后 将结果通过数字信号的方式反馈给决策系统。 本次毕业设计的目标是基于 Arduino 和 Processing 的开发平台,搭建一个通用的室 内轮式机器人平台。根据功能决定结构、结构体现功能的原理,对于决策系统、执行系 统和感知系统所需的各种模块规划如图 2.2 所示。 电子罗盘 连续旋转舵机

PC机

决策系统

无线通信

三轴加速度计 180°摆动舵机 感知系统

执行系统

脉冲编码器

1602液晶屏 超声波传感器

Arduino

蜂鸣器 红外接收端

图 2.2 机器人所需模块划分

决策系统是由上下位机相互配合,下位机是基于 AVR 单片机的 Arduino 开发板, 上位机是普通个人电脑 PC 机。他们各有优劣,下位机小巧灵活,嵌入到移动机器人内 5


武汉科技大学本科毕业论文 部便于实时直接的控制其移动、运行,但数据存储、分析和可视化不便;PC 处理能力 强,但受限于通信系统的带宽和延时问题的制约而无法实时控制移动机器人。为了将他 们的优劣结合起来,通过加入无线串口通信模块 APC220 来将机器人执行过程中一些必 要的分析数据传送给上位机存储起来,以便将来分析。同时,在某些可能出现的复杂问 题的决策上,下位机将控制权交给上位机,让上位机或者人工通过控制上位机来操纵移 动机器人。 轮式机器人的执行机构最基本的就是通过驱动电机转动带动减速机构连接到车轮, 使车轮转动来控制机器人的移动。在各种轮式机器人的运动模型中,两轮差动式机器人 因其移动的灵活性和成熟的数学模型而受到众多机器人研究人员和爱好者的亲睐,因此 决定选择这种布局的轮式机器人。还有就是超声波传感器模块的测距能力不应该是仅限 于正前方,因此需要专门的摆动舵机使其能测量不同角度方向上的距离。为此,选用两 个连续旋转的舵机来分别控制左右轮的转动。此外,还需要简要的显示移动机器人平台 的现有状况,这既便于调试,也是为了让人可以直观的了解到现在移动机器人的状况, 因而选择添加了一个 1602 的字符液晶屏幕和一个蜂鸣器。 移动机器人平台在使用的过程中最常见的需求就是位置姿态的确定和基本的绕开 障碍物。关于姿态,使用一个电子罗盘即可确定机器人即时的朝向角度 θ。已知起点、 已知地图这种条件下的定位称为局部定位或位姿跟踪,它采用的是相对定位方法关于位 置,则除了需要追踪机器人移动过程中的角度情况,还需知道机器人移动的里程。最常 见的办法就是使用光电编码器来计算里程,同时还能用于测速。绕开障碍物就需要机器 人随时知道自己前方或即将移动方向上最近物体的距离,即测距。而此功能最成熟有效 的办法就是使用超声波(声纳)。此外,移动机器人在运动过程中如果遇到无法翻越的 斜坡,或者发生碰撞等情况需要紧急停止或作出反应,则需要能测量倾角和加速度情况 的三轴加速度计。

2.2 轮式机器人的机械结构规划 基于轮式的机器人设计有很多类型可选,详情请参考相关文献。 图 2.3 是使用 Proe/Engineer Wirefire 5.0 设计后的三维效果图,使读者对该机器人的机械结构有一个大 致了解。图 2.4 是简化后的机器人外观投影图和重要零部件的位置标示:

6


武汉科技大学本科毕业论文

图 2.3 三维软件虚拟装配效果图

图中所有螺纹连接件均未画出 超声波测距传感器

HM5883L 电 子 罗 盘 和 三 轴 加 速 度 计 扩展电路 板

无线串口模块 APC220 ADX345 控

超声波模块旋转 舵机

Arduino

主控板 Arduino 主控板供电锂电

舵机供电锂

前置万向轮

电 主支承底板

左右驱动轮

图 图 2.4 传感器及各模块布局图

7


武汉科技大学本科毕业论文 针对各类传感器的使用要求,对于安装结构和布局提出了一下几点要求: (1)三轴加速度计 AXDL345 和电子罗盘 HMC5883L 应尽量靠近两主动轮的轴线中心。 (2)无线通信模块 APC220 应避免靠近电信号噪声较大的器件(如电机),且应配置好 伸出的天线(图 2.4 中未画出) ,避免被金属物件遮挡。 (3)要避免超声波测距模块在转动过程中和周围物件干涉,要避免其附带电源线布置 的长度在转动过程中不够而被卡住。 (4)通过添加适当的铜螺柱调节万向轮高度,使得其能保证主支撑板和地面平行。

2.3 轮式机器人的硬件电路规划 如绪论所述,Arduino 面向不同层次和应用场合,有多款型号,能根据不同应用场 合提供相应的 AVR 单片机控制板。

图 2.5 Arduino Mega 1280 外观 表 2.1 Arduino Mega 1280 主要参数 主控芯片(MCU) 操作电压

Mega1280 5V

输入电压(推荐值)

7~12V

输入电压(极限值)

6~20V

数字输入、输出端口

54 路(其中包括 15 路脉宽调制输出,4 路串口,1 路 I2C 总线)

模拟输入端口

16 路

各输入、输出口极限电流

40mA

3.3V 端口极限电流

50mA

闪存空间

128KB(4KB 已用于引导程序)

静态随机存储器空间

8KB

电可擦写只读存储器空间

4KB

时钟频率

16MHz

8


武汉科技大学本科毕业论文 而根据规划和使用要求,对主控板的端口数量和处理器的存储空间都有着较高的要 求,因此选择了其中一款较高配置的 Arduino Mega 1280 主控板(外观如图 2.5 所示) , 该款主控板的主要参数简要列出器主要参数如表 2.1 所示,本文附图纸中还包含该主控 板的电路原理图及 PCB 布局图,以供参考。 根据前述的功能要求,除了选择合适的主控板,还需考虑设计恰当的电路来和主控 制器 Arduino 连接成完整的实用电子系统。根据各个传感器、通信和执行机构的特性(稍 后章节将详细介绍),将主控板和各模块的连接方式确定并分配好各 I/O 端口,如图 2.6 所示: PC上位机 Processing监控程序

APC220无线串口 通信模块

US-100温度补偿 超声波测距模块

4路串口

主控制器 Arduino

8V供电电源

HMC5883L电子罗盘

I2 C 总 线

ADXL345三轴加速度计

蜂鸣器

反相器

自制光电脉 冲编码盘

1602液晶屏

右轮速度及里程反馈

左速度及里程反馈

54路数字输出输入端口

两路反相器

两路夏普PC817光耦

春天 SM-S43125R 连续旋转舵 机

左轮

锂电供电

反相器

夏普PC817光 耦

春天 SM-S43125R 连续旋转舵 机

反相器

辉盛 MG995 180度舵机

右轮

自制光电脉 冲编码盘

超声波模块 支撑云台

图 2.6 机器人各模块总线关系

9

红外接收端


武汉科技大学本科毕业论文 Arduino Mega 1280 共有 4 路串口,该轮式机器人规划中共使用其中 3 路。一路于 单片机和 PC 机烧录和在线调试所用;一路用于 APC220 无线串口通信模块,使得移动 机器人在使用过程中可以实时和上位机交换信息;一路用于接收超声波测距模块传来的 测距数据(超声波测距模块已设定为以串口方式和上位机通信) ;GY27A 模块包含的两 个传感器(HMC5883L 电子罗盘、ADXL345 三轴加速度计) ,它们均已标准的 I2C 总线 方式和主控板连接(Arduino 已自带 I2C 总线接口)。而 1602 液晶屏、蜂鸣器、红外接 收端子及三路舵机的信号端口均以普通数字输入/输出端口的形式和它们相连,其端口的 分配的详细情况以下表列出 表 2.2 主控板端口分配表 传感器或模块名称

需要数字接口

左轮连续旋转舵机

1 路 PWM 输出

2

右轮连续旋转舵机

1 路 PWM 输出

3

电子罗盘 HMC5883L 三轴加速度计 ADXL345 模块 红外测距接收模块 红外通信接收集成模块 HS0038

1 路数字输入

液晶显示器 1602

7 路数字输入/输出

继电器

1 路数字输出

锂电电量检测 APC220 无线通讯模块 US100 超声波测距 红外接收头 超声波测距模块带动舵机

需要的模拟接口

需要 SCA、SDL

\

1 路模拟输入

A1 11 50,

46,

44, 42,

40,

38

32 1 路模拟输入

5 路数字输入或输出

Mega 板安排编号

A0 TX0,RX0

一对串口,2 路数字输入 输出 1 路 PWM 输出

16,17

7

综合上述对轮式移动机器人的机械结构和硬件电路设计图 2.7 给出实际的样机照片 及机电综合布局的模块连接方式简图 2.8。

10


武汉科技大学本科毕业论文 超声波测距 传感器 摆动舵机

主控板、传

底板

感器用小功 率锂电

图 2.7(a) 轮式机器人照片侧面

舵机用大功率锂电

1602 液晶屏 电源模块 及散热片

红外接 光电编码器

收端

连续旋转 舵机

无线串 主 控 板

口模块

Arduino Mega

图 2.7(b) 轮式机器人照片背面

11


武汉科技大学本科毕业论文

螺栓

超声波测 距

螺栓

旋转舵机

螺栓

红外发 射接收

上层板

舵机电 源稳压

底板 主控板及 传感器锂 电

液晶屏

螺柱

舵机锂电

螺栓

排 线

舵机架

螺栓

邦 杜

螺柱

下层板

电机 驱动 模块

无线 通信

主控板 Arduino

电子 罗盘

线 排

三轴 加速 度计

螺栓

螺栓

螺栓

螺栓

杜邦排线

连续舵机

连续舵机

图 2.8 轮式机器人各模块装配关系图

2.4 轮式机器人的移动算法规划 轮式移动机器人(Wheeled Mobile Robots)由于具有较强的活动能力,良好的稳定性等 特点。国内外许多专家学者对轮式移动机器人进行了系统深入的研究给出了 (2, 0)型轮 式移动机器人的机械结构,建立了机器人小车的运动学数学模型。下面简述该运动学模型 并在此基础上,简要分析了移动机器人小车的基本运动形式,即直线运动、圆弧运动的实 现方式。

12


武汉科技大学本科毕业论文

Y'

X'

b

Y θ G

r O

X 图 2.9 两轮差动机器人运动学模型示意图

参考图 2.9,定义 X 方向为机器人的正前方,则 θ 为机器人的方向角,机器人质心 G 点的坐标为 (x, y)。因此用一三维向量 P = (x, y, θ)T 可完全描述机器人的位姿。移动 坐标质心 G 的速度为,它垂直于机器人的轮轴,因此它在两个坐标轴上的分量分别为: x  vG cos

(2.1)

y  vG sin

(2.2)

机器人质心 G 的速度与驱动轮转速的关系式为,

1 vG  (l  r )r 2

 

l   r b

(2.3)

r (2.4)

消去 vG 得如 2.5 式,

x 

1 (l  r )r cos 2

13

(2.5)


武汉科技大学本科毕业论文 y 

1 (l  r )r sin  2

 

l   r b

(2.6)

r (2.7)

改写可以得到机器人运动模型的矩阵表达式:

1  r cos  x   2   1  y    2 r sin      r  b

1  r cos  2  l  1 r sin       r  2  r    b

(2.8)

几种基本运动模式的实现 1)直线运动: 机器人的直线运动要求其运动过程中,方向角 θ。反映在运动学模型上有:   r   l 0 b 要求

l   r ,也就是说机器人在左右轮的转角速度大小和方向都相同的情况下,其

实现直线运动。 2)圆弧运动 设机器人的回转半径记为 R,回转中心 O 为其速度瞬心。由运动学模型得到机器人 的瞬时运动状态:

 x   R cos   y    R sin              

(2.9)

l 2 R  b  r 2 R  b

(2.10)

由几何关系和约束方程可得:

因此,当两轮的转角速度满足式(2.10)的条件时,机器人质心 C 实现匀速圆弧运动。 3)质心不变条件下的运动 质心不变的运动是指机器人以质心为速度瞬心的旋转运动,即 R = 0。根据圆弧运 动分析可知,绕点 O 旋转运动的半径 R 应满足式的条件,需小车的质心 G 在两驱动轮 回转轴线的中心点上,此时机器人的运动学模型有如下简洁形式:

14


武汉科技大学本科毕业论文 l 2 R  b   1 r 2 R  b 即

l   r ,也就是说在机器人质心为驱动轮回转轴线的中心点,左右轮的转角速度

相等方向相反的情况下,其作质心不动的运动。根据上述模型,本文将在后续章节具体 的模型参数建立开环控制的运动算法。 下面给出了整机运行的总体算法规划,以使读者对机器人的总体运作流程有所了 解,但不包括具体的中断和壁障算法的细节,相关内容将在后续相关章节再做详细说明。 开始

自检及初始化传感器、 执行机构

是否有上位机指令

接受上位机控制

否 是否有红外遥控指令

执行遥控指令

否 检测是否有障碍物

运行壁障算法

否 产生运动指令

图 2.10 两轮差动机器人总体运作流程

图 2.10 表明了该机器人平台基本的运作流程,由上位机或者遥控器给出指令的情况 下,机器人遵从指令运行,如果上位机和遥控器都没有给出指令的情况下,机器人将自 主运行。但无论如何,机器人在遇到障碍物的时候都将主动回避。

15


武汉科技大学本科毕业论文

3 轮式机器人选用传感器介绍 3.1 电子罗盘及三轴加速度计模块 GY27A 电子罗盘,也叫数字指南针,是利用地磁场来定北极的一种方法。现在一般有用磁 阻传感器和磁通门加工而成的电子罗盘。平面电子罗盘要求用户在使用时必须保持罗盘 的水平,否则当罗盘发生倾斜时,也会给出航向的变化而实际上航向并没有变化。三维 电子罗盘在其内部加入了倾角传感器,如果罗盘发生倾斜时可以对罗盘进行倾斜补偿, 这样即使罗盘发生倾斜,航向数据依然准确无误。基于这一原理 GY27A 模块将三轴磁 阻传感器 HMC5883L 和三轴加速度计 ADXL345 都封装在 3.0×3.0×0.9mmLCC 的表面 装配中。

图 3.1 电子罗盘和三轴加速度计模块外观

图 3.2 GY27A 模块与主控板连接电路原理图

电子罗盘与主控制器 Arduino 之间以标准的 I2C 总线连接,现将其主要工作流程以伪代 码方式描述如下,该伪代码具体过程和源码中的注释一致。具体源码请查看附录 A 中的 HMC.cpp 源文件。 //说明:测试表明以下代码需要 100 毫秒的时间执行,故采样周期请勿小于该时间 1. 初始化 HMC5883 的数据传输(写入 I2C 地址) 2. 将寄存器模式的编码放入发送缓冲器中 3. 将发送缓冲区中的连续操作模式的命令。 4. 发送缓冲区发送 HMC5883 和结束 I2C 传输。 5. 初始化传输与 HMC5883(收件地址) 。 16


武汉科技大学本科毕业论文 6. 请求的 6 个字节的数据从指定的地址。 7. 读取的磁性元件,X,Y 和 Z 值 8. 合并的 X,Y,Z 数据输出寄存器的 MSB 和 LSB 9. 利用反正切函数和磁性传感器在 X, Y, Z 方向上所测得的磁感强度值求得姿态角

3.2 光电编码器的测速和里程记录功能 机器人自身的行走速度对于判断机器人运动状态和机器人所在位置非常重要,测量 机器人小车的速度和里程都可以由驱动电机编码盘的脉冲图形获得,如图 3.3 所示。

图 3.3 编码盘测速原理示意

当码盘随电机旋转时,红外接收端的输出信号便是一个由旋转速度决定频率的方 波。进而知道此时电机的旋转速度。利用光栅圆盘旋转以后产生的脉冲信号不仅可以用 来检测电机的旋转速度,还可以用来对车轮行驶里程进行记录。调试设计好后的电路如 下,加入反相器的目的是整形输出的不规则斜坡为较适合直接给主控板数字输入口的矩 形波。

图 3.4 自制光电编码器电路示意图

利用矩形波周期和每次触发的边沿时间,即可等价为对小车的位移的离散采样。 Arduino Mega 内置实时时钟和边沿触发中断功能,这大大方便了里程和速度的测量,其 过程用伪代码表示如下,实现源码请参考附录 A。 1. MOTO SIG 上升沿(或下降沿)触发中断 2. 中断函数调用 Arduino 内置的 mills()函数记录下这次触发的时间,触发次数 17


武汉科技大学本科毕业论文 自增 1。 3. 分析经过 2 步骤多次采集到的数据,可以根据没两次触发的间隔时间得到该段时 间内的平均速度,根据触发次数可以得到里程。

3.3 超声波距离传感器原理及结构 为了使移动机器人能自动避障行走,就必须装备测距系统,以使其及时获取距障碍 物的距离信息(距离和方向)。常见机器人模型的一般采用多个超声波测距模块于不同 角度均布在机器人各方向不同,该轮式移动机器人平台所使用的 180 度舵机来回摆动测 量超声波测距系统,就是为机器人能充分了解其前方 180 度的环境任意角度环境,从而 而提供较为完整的前方距离信息。如果使用多个超声波测距模块,将难以使得机器人的 整体结构短小紧凑;均布式超声波测距模块难以做到任意角度测距,而由于超声波测距 其实是一种不完全感知,对于不同形状的物体它所表现的特性其实并不相同,这点以下 将较为详细的说明。 声速的影响的是测量的精度,这通过温度补偿后 基本能够得到解决,而障碍物的形状对超声波测距的 影响则是角度范围甚至是障碍物判定的正确与否。超 声波测距其实是不完全感知的,它不同于机器视觉算 法这一类的方式,因而会引发下述问题。对于不同形 状的物体超声波测距的结果有很大差异,这样一来就

图 3.5 超声波测距模块外观

会给壁障判断带来不利影响。

图 3.6 超声波测距模块对圆柱障碍物的测量值分布图

18


武汉科技大学本科毕业论文

图 3.7 超声波测距模块对平板障碍物的测量值分布图

除上诉问题之外,国内外还有些学者还提到了超声波测距过程中可能因为幻影问题 而难以穿越复杂障碍物环境并且为此提出了相应的模糊算法。在所设计的轮式移动机器 人模型搭建好以后,使用超声波模块进行测距壁障的过程中也遇到了新的问题,并且这 在之前在查看相关文献中还没有发现过,后文的总结和回顾将会提及。

图 3.8 超声波模块连接的电路原理图

3.4 红外发射、接收集成块原理简介 选用台湾亿光电子的红外发光二极管 IR333,其外型如图 3.9 所示。其光谱功率分 布为中心波长 830 ~ 950nm。半峰带宽约 40nm 左右,它是窄带分布,在普通摄像机 可感受的范围。 红外二极管的最大辐射强度一般在光轴的正前方,并随辐射方向与光轴夹角的增加 而减小,其关系如图 3.10 所示。辐射强度为最大值的 50% 的角度称为半强度辐射角。 不同封装工艺型号的红外发光二极管的辐射角度有所不同。利用这一点,可以实现有相 通讯,即和在红外管辐射范围角度内的机器人之间通信。

19


武汉科技大学本科毕业论文

图 3.9 IR333 外观图

图 3.10 IR333 光强角度分布

根据第二章的移动式机器人整体结构规划,除了采用上位机的无线串口通信方式来 对机器人进行实时控制,还考虑使用日常生活中常见的红外遥控器方便对移动式机器人 在没有 PC 的场合也能实时指挥其工作。市场上可较方便的购买到这类万能遥控器,随 着各种不同的红外协议,它能对应不同的型号的家电(电视机、空调、VCD 等)产品 发出红外指令。其工作原理和接收方式在我论文随附的外文翻译中已有详细说明,故此 不在赘叙,在此仅对所选用的集成化接收端 HS0038 做简要说明。

图 3.11 红外发射模块

HS0038 是一个红外信号接收设备,通过它解调的红外信号可以直接送给微处理器 进行处理,HS0038 是标准的红外遥控接收器系列,支持所有主要的传输码。

20


武汉科技大学本科毕业论文

图 3.12 HS0038 与控制器连接电路

图 3.12 是 HS0038 和主控板之间的连接电路图,设定好万能遥控器为 NEC 协议状 态,这是目前较为常用的协议之一。即便遥控的按键保持按下,命令也只传送一次。在 按键保持按下的时候,每隔 110 毫秒传送的编码才重复。重复的传送编码也是简单的由 9 毫秒的自动增益控制脉冲起始,其后紧跟 2.25 毫秒的空位和 560 微秒的脉冲。

3.5

无线通信模块的主要参数介绍

图 3.14 APC200 尺寸

图 3.13 APC220 外观 表 3.1 APC220 引脚定义 引脚

定义

说明

1

GND

地 0V

2

VCC

3.3V-5V

3

EN

电源使能端,≥1.6V 或悬空使能,≤0.5V 休眠

4

RXD

URAT 输入口,TTL 电平

5

TXD

URAT 输出口,TTL 电平

6

AUX

URAT 信号,接收为低,发送为高

7

SET

设置参数,低有效

APC220 模块是高度集成半双工微功率无线数据传输模块,其嵌入高速单片机和高 性能射频芯片 APC220 模块采用串口设置模块参数。APC220 模块拥有 1000 米传输距离 21


武汉科技大学本科毕业论文 (1200bps) ,可选的 16 位 RFID,适合大数据量传输。具有丰富便捷的软件编程设置选 项,只需通过提供的设置软件 RF-ANET 利用 PC 串口即可轻松实现。

图 3.15 APC220 无线模块连接电路原理

3.6 液晶显示模块主要参数 字符型型液晶是一种用 5×7 点阵图形来显示字符的液晶显示器,根据显示的容量可 以分为 1 行 16 个字、2 行 16 个字、2 行 20 个字等,最常用的为 2 行 16 个字,即图 3.16 所示的 1602 液晶模块。 1602 字符型 LCD 模块的应用广泛,各种液晶厂家 均有提供几乎同规格的 1602 模块,1602 字符型 LCD 模 块最初采用的 LCD 控制器采用的是 HD44780,在各厂 家生产的 1602 模块当中, 基本上也都采用了与之兼容的 控制 IC,所以从特性基本一致;当然,很多厂商提供了 不同的字符颜色、背光色之类的显示模块。其引脚说明 如下所示

图 3.16 1602 液晶显示屏外观 表 3.2 1602 液晶屏引脚定义

编号

符号

引脚说明

编号

符号

引脚说明

1

VSS

电源地

9

D2

数据 I/O

2

VDD

电源正极

10

D3

数据 I/O

3

VL

液晶显示偏压信号

11

D4

数据 I/O

4

RS

数据/命令选择端(H/L)

12

D5

数据 I/O

5

R/W

读/写选择端(H/L)

13

D6

数据 I/O

6

E

使能信号

14

D7

数据 I/O

7

D0

数据 I/O

15

BLA

背光源正极

8

D1

数据 I/O

16

BLK

背光源负极

22


武汉科技大学本科毕业论文 参考其数据手册及 Arduino 的 LiquidCrystal 库只需将其引脚参照上述方式一半字节 (4 路数据口)相连即可,如图 3.17 所示:

图 3.17 1602 连接电路图

3.7 蜂鸣器原理及工作方式 蜂鸣器是一种一体化结构的电子讯响器。轮式移动机 器人上采用的是图 3.18 所示的有源自激蜂鸣器。 由于自激 蜂鸣器是直流电压驱动的,不需要利用交流信号进行驱动 单片机驱动 比如频率为 2000Hz 的蜂鸣器的驱动,可以产生周期 为 500μs,这样只需要把 PWM 的周期设置为 500μs,占 空比电平设置为 250μs,就能产生一个频率为 2000Hz 的

图 3.18 蜂鸣器外观

方波,通过这个方波就可以去驱动这个蜂鸣器了。调节占 空比不同的 PWM 方波,即可产生不同音调的鸣叫声了。

4 轮式机器人动力及供电系统 4.1 电机的选型和隔离电路设计 舵机简单的说就是集成了直流电机、电机控制器和减速器等,并封装在一个便于安 装的外壳里的伺服单元。能够利用简单的输入信号比较精确的转动给定角度的电机系 统。 舵机安装了一个电位器(或其它角度传感器)检测输出轴转动角度,控制板根据电 位器的信息能比较精确的控制和保持输出轴的角度。这样的直流电机控制方式叫闭环控 制,所以舵机更准确的说是伺服马达(Servo Motor)。其工作原理是控制电路接收信号 源的控制脉冲,并驱动电机转动;齿轮组将电机的速度成大倍数缩小,并将电机的输出 23


武汉科技大学本科毕业论文 扭矩放大响应倍数,然后输出;电位器和齿轮组的末级一起转动,测量舵机轴转动角度; 电路板检测并根据电位器判断舵机转动角度,然后控制舵机转动到目标角度或保持在目 标角度。 舵机除电源外,只要一根信号线即可;使用 PPM(脉冲比例调制)信号控制;所谓 “PPM” ,是一个周期约 20ms,其间有个宽度在 2ms 左右的脉冲控制信号。一般是以 1.5ms 为基准,此时舵机居中,小于 1.5ms 舵机左转,大于 1.5ms,舵机右转;至于角 度和脉冲宽度关系各个产品不同,例如:0.5ms 对应左转 90 度,2.5ms 对应右转 90 度。 舵机内部实际上是由小电机、减速齿轮、驱动电路、位置反馈、比较电路等组成的 闭环控制单元,由于其内置减速齿轮,所以输出力矩较大。最早将其改造为轮式机器人 动力源的爱好者估计就看上了这些,他们将舵机的限位去除,位置反馈去除,舵机控制 电路因得不到反馈,以为还未转到指定的角度,只好一直驱动电机转动,由于去除了限 位,输出轴实现了连续转动,就成了一个减速直流电机。而且,利用其原来的左、右转 控制逻辑,实现了正、反转控制。从本质上说,舵机和直流减速电机相同,只是利用了 舵机内部的驱动和控制电路,从而简化了控制接口和电路。 结论:使用舵机作为小车驱动一是为了便于装,二是为了便于控,三是可简化电路。

图 4.1 连续舵机外观及中位点调节示意图

图 4.1 所示是较为常用的一款常用的连续旋转舵机,可以使用螺丝刀调节其中位点, 以使其脉冲中点能在 1.5ms 附近变化。

4.2 电源变换、稳压和隔离系统设计 两驱动轮分别采用了春天的 SM-4315R 连续旋转舵机。与国内其它常见的轮式机器 人驱动方式不一样,没有选用普通直流电机或者是小型的步进电机,关于选用舵机的缘 由,本文后边将会有详细探讨。需注意的是,舵机的供电务必要和主控板分开,原因有 以下几点。 (1) 舵机所需电压和 Arduino 的供电电压不同,而 Arduino 输出电流的能力不足 以带动电机正常工作。

24


武汉科技大学本科毕业论文

图 4.2 PC817 光耦性能参数示意图

(2) 舵机内部采用了齿轮减速,所以会在信号端和地线上产生噪声,从而可能干 扰主控板工作。舵机齿轮有塑料和金属两种,所采用的是金属齿舵机(如图 4.2 所示),其产生的噪声更大。 综上所述,应对舵机的供电和信号电路与其它小功率器件采取彻底隔离,并分开供 电。选用夏普公司生产的 PC817 光耦模块。 由图 4.3 可知,光耦可以将两路供电的地彻底隔离,但是它同时也存在上升和下降 时间,并且使信号反相。为此,设计工作主要就是要选取图 4.2 中合适的 RD 和 RL 以使 得上升和下降时间的对信号波形造成的误差在可以接受的范围内。这些工作均可以通过 查阅 PC817 的数据手册来完成。并且由于光耦的反相作用,为使控制简单,在接入光耦 的控制端前需先将主控板的输出引脚串入反相器对数字信号进行反相。具体完成的电路 图如图 4.3 所示:

图 4.3 光电隔离模块电路原理图

4.3 电源变换、稳压电路设计 根据春天官方的手册,该款舵机允许的供电电压范围是 4~7V。而采用的两节锂电 串联供电电压范围约是 6~8.2V,然而锂电的供电电压会随着锂电池的消耗而缓慢下降。 电压的不匹配和锂电的安全问题带来了对于电源电路的全新设计需求,因此在选用 25


武汉科技大学本科毕业论文 锂电的同时,也必须设计出一套安全可靠的恒压电源电路。根据模拟电子技术的知识, 要将两节串联锂电(2×3.7V=7.4V)的稳压在 5V 以供舵机供电使用,比较成熟、可靠 的方式是直接使用三端集成稳压块,通过查阅资料,选用意法半导体生产的三端集成稳 压块 STL7805。此形式的封装可以方便的安装散热片,从而可以承受通过较大的电流, 约 1.5A。 参考相关资料,可串入变阻器以实现在一定范围可可调压来克服锂电使用一段时间 后可能会使输出电压降低的问题。并且在舵机的大功率供电电路的入口处加入一继电 器,受主控板 Arduino 的控制,只有当主控板正常工作时,从 MOTO SAMPLE 采样送 入模拟量输入口检测电平你确定锂电是否正常工作,给 MOTO CONTROL 端口高电平, 继电器闭合,整个大功率回路才能接通,这样就可以防止锂电过度放电。同时再在回路 中串入 1A 的保险丝,这样就可以起到过流保护作用,整个供电电路如图 4.4 所示。

图 4.4 电源模块电路

5 轮式机器人的基本运动及壁障功能程序设计 5.1 基本动作层 根据本文 2.4 节内容,已知该两轮差动式机器人的灵活性和可控性。然而,要在程 序中写出可靠的控制程序还需要知道具体参数,根据已有的参数,左右主动轮直径 d = 65mm,则半径 r = 32.5mm;两轮中心间距为 b = 180mm。连续舵机说明书中并没有给 出转速的确切资料,且其转速和速出的扭矩是和供电电压密切相关的,所以还是要进行 简单的实测。这样可以了解电机的性能是否一致以及验证采用的控制方法是否可靠。

26


武汉科技大学本科毕业论文 舵机在不同的供电电压下,其能输出的扭矩以及其转速并不相同。虽然上一章提及 过可以通过使用稳压电路的方法来保证给舵机的供电电压基本不变。而且国内众多的舵 机生产厂家都没有提供舵机转速的扭矩的详尽资料,因此必须要自己实测一下舵机的转 动速度。要么让机器人两轮均全速前进,要么让机器人两轮正反转而使机器人原地自转。 测速最简单的办法之一就是让左右两轮全速反向旋转,因考虑到可以配合电子罗 盘,则轮式机器人会在绕两轮中心在原地自转,多次测量取平均值后,可得轮式机器人 全速自转一圈所需时间是 2800 毫秒。因为附录中没有此测试代码,故将测速用的算法 以框图形式表示如图 5.1。

开始

记录起始角度 记录起始时间

调用MegaServo库,全速右转, 输出PWM脉冲,左轮舵机顺时 针,右轮舵机逆时针

与起始角度相对

距离终止时间 计算时间差

结束

图 5.1 测速调试程序算法框图

int checkVelocity() { int beginAngle = getNowAngle();

//get the begin time

int beginTime = millis(); do{ turnRight(100); 27


武汉科技大学本科毕业论文 delay(50);

//avoid skip turnRight at first time

}while(getNowAngle() != beginAngle);

//turnRight until finish a round

int endTime = millis(); return endTime - beginTime;

//return the interval of begin and end time

} 依据上述结果,可以计算得出主动轮外缘的线速度和角速度:

b

(3.14 180)mm  0.202m/s ....................................(5.1) T 2800ms 2v (2  0.202)m/s    6.21rad/s .....................................(5.2) d 65mm

v

有了以上基本 结果,就 可将前进 后退,以 及左右旋 转任意角度 的 过程封装为 Forward、Backward 以及 TurnRight、TurnLeft 函数,主要代码和原理说明如下。

5.1.1 前进、后退函数 根据计算可得两轮全速前进 1000 毫秒(即 1 秒)时,机器人将前进约 190mm。map 函数是 Arduino 内置函数,其功能是将第一个参数在第二、第三个参数中的位置成线性 关系的映射到第四、第五个参数区间,并返回。Backward 函数于此类似,仅是左右轮 转向相反。

开始

传入前进 距离参数

根据前进距离 计算前进时间

设置停止函数在 前进时间时停止

调用MegaServo库,输出PWM脉冲, 左轮舵机顺时针,右轮舵机逆时针

到前进时间 是

结束

28


武汉科技大学本科毕业论文 图 5.2 前进函数程序算法框图

上诉算法框图的实现内容由 void Runforward(int Distance)函数实现,函数内部代码 请参考附录 A,后退函数 Backward 函数于此类似,而实现的是后退的功能。需要说明 的是,在调用上述函数结束后,需调用 cancelRun()函数,以使定时器和绑定的数字输出 端口断开,即调用 MegaServo 对象的 detach 方法。

5.1.2 左转、右转函数 转向函数于此类似,具体的算法简要列出如下。转向不同时,仅是左右轮转向相反。

开始

传入右转 角度参数

根据右转角度 计算转向时间

设置停止函数在 转向时间时停止

调用MegaServo库,输出PWM脉冲, 左轮舵机逆时针,右轮舵机逆时针

到转向时间

结束

5.3 右转函数程序算法框图

void TurnRight(int Angle) 有了转向和前进移动函数,基本动作层就设计好了。

5.2 壁障规划层 5.2.1 避开前方和侧面障碍物 首先要启动支撑超声波壁障模块的摆动舵机,并开启超声波测距模块,然后一直来 回摆动使超声波模块测量各个角度的距离,并通过 I/O 口将测得的各个角度的距离返回 给计算决策。收到控制器的电机控制信息后,如果前方有障碍物在危险距离内,将使蜂

29


武汉科技大学本科毕业论文 鸣器响起并使移动机器人的姿态调整到无障碍物的角度。转向完成后,机器人不应直接 前进,还应再探测一次以确保前方无障碍物。下面给出相关的主要代码和算法框图。

开始

超声波测距采样 调用壁障函数

距离小于 30厘米 否 摆动舵机30度

摆动舵机 到截止位

改变舵机摆动方向

图 5.4 超声波支撑摆动舵机运作程序算法框图

实 现 图 5.4 所 示 功 能 的 代 码 请 参 看 附 录 A 中 的 超 声 波 云 台 摆 头 函 数 void UltrasonicSweep()及超声波测距函数 int getUltrasonicDistance()。具体的壁障功能如图 5.5,假设障碍物是一面长的墙壁。壁障函数 void avoidObstacle()的具体功能就是检测是 否有距离小于 20mm 的障碍物在前方 180 度的范围之内,如果有,那么测得该障碍物的 角度值,并使得机器人反向旋转相应的角度值,以保持至少与墙壁平行的方向前行即可 顺利避开障碍。实践证明,这种壁障方法对于室内简单的障碍物是行之有效的简单方法。 实现图 5.5 所示功能的代码请参看附录 A 中的壁障函数 void avoidObstacle()。

30


武汉科技大学本科毕业论文 某一方向测得 距离小于30厘米触 发壁障函数

获得障碍方向角度 

侧转相应角度 

 是否仍有 障碍物 在前进方向

否 离开壁障函数

图 5.5 壁障触发处理其原理简图及其程序算法框图

5.3 受控通信层 该轮式机器人可同时受控于 PC 机与手持红外遥控器。但其也具有简单的自主移动 能力。可以通过产生随机运动的算法来实现模仿生物特性的自主移动。该部分的功能很 大程度上依赖于成熟的通信模块及其技术以及 Arduino 平台已经存在的其它人共享的扩 展库。红外遥控是常见的电视机遥控,其频道增减、音量增减的功能功能恰好能分别用 作前进、后退、左转、右转;而遥控上面的数字键则可作为控制量的参数,如:前进 100 毫米,左转 90 度。除此之外,还需要进行详细情况请参考 Arduino 库中的 IRmote 扩展 库及 Arduino 自带的 Serial 对象的使用功能。

5.4 液晶显示屏程序 Arduino 平台自带有的 LiquidCrystal 是其自带的标准扩展库,使用它可以及其方便 的操作液晶屏,以便于在液晶屏上显示所需的重要数据。标准的 1602 液晶屏和单片机 的数据端口有串行和并行两类接口方式,而并行接口方式中又分 4 线(半字节)并行和 8 线(单字节)并行方式。采用 4 线(半字节)并行方式既可以节省主控板的数字端口, 又不至于如串行方式那样迟缓。具体引脚的接线方式参见本文图 3.17。 液晶显示屏的内容如图 5.6 所示,分别是来自红外或者无线传输模块的运行中指令 及其参数、超声波模块摆动舵机摆角、来自电子罗盘的姿态角(和后叙述上位机界面所 描述一致、以来自超声波模块所测得的距离。

31


武汉科技大学本科毕业论文 运行指令及参数

超声波模块摆角

超声波测距结果

电子罗盘(姿态角) 图 5.6 液晶屏显示内容

(单位 mm)

5.5 机器人基本功能的上位机程序设计 上位机主要为了控制和操作方便,其控制的图形界面如图 5.6 所示。实现程序随附 于附录 B 中,故此不再赘述。

图 5.6(a) 上位机手动控制界面

无线通信模块 APC220

32


武汉科技大学本科毕业论文 图 5.6(b) 上位机手动控制界面

33


武汉科技大学本科毕业论文 结束语 自己从毕业设计的选题和组建团队,从跑市场买零件、焊接电路到调试程序,期间 经历了各种酸甜苦辣,在整个机器人平台的规划和实体样机制作过程中我都倍感压力。 虽然因为制作智能机器人的目标尚未完全达到,但伴随着本次毕业设计的结束,我们已 经初步搭建好了一个轮式机器人平台的雏形,只要坚持自己的爱好继续走下去,我和我 的同伴们相信还一定会取得更多更大的成功和乐趣。 刚开始我们和许多初学者一样,在看到国内外各种各样文献中提及自己实现的机器 人作品后感到很鼓舞,准备大展身手,可等到自己真正动起手来的时候却发现问题总比 办法多。很容易因为自己不是电类专业学生而看到别人的电路原理图就如坠雾里,时常 会做到一半发现遇到了自己解决不了的问题,也找不到合适的人请教,最后就不了了之。 也总会觉得自己受限于身边的条件,想动手却什么也买不到。时间、资金种种问题总是 困扰着尚未真正登堂入室的爱好者们。还有就是看着很多带有浓厚学术气息的资料、大 学教材,许多时候啃了这些文献半天,即便弄明白原理和功能,却连它长什么样,如何 获得都弄不清楚。所幸的是,我们并没有没这些困难打倒,我们的坚持最终让我们多少 有些成果。 虽然投入了很多,时间、金钱…但收获和乐趣更多。我也更明白一个众所周知的道 理:要想知道螃蟹的滋味,你必须亲自去尝一尝。交图纸和设计和做实物的设计本身就 有着天壤之别,你要考虑到很多图纸上不需要考虑的问题。 展望未来,作为一名受过机电专业高度教育的毕业生,我们对于机器人技术的爱好, 不应仅仅是做一个玩具,我们还应该有更高更远大的最全。那就是将各种在大学里学到 的高深理论用产品来展示出来,用实际的途径来让这些高深的理论更好的为我们服务, 这也许就是一个工程师应该终身为之奋斗的事业吧!

34


武汉科技大学本科毕业论文 参考文献 [1]

Dorigo M, Sahin E. Special Issue: Swarm Robotics[J]. Autonomous Robots, 2004,17: 111.113.

[2]

Bayindir L, Sahin E. A Review of Studies in Swarm Robotics[J]. Turkish Journal of Electrical Engineering and Computer Sciences, 2007, 15(2) :115-147.

[3]

Dorigo M, Tuci E. The SWARM-BOTS Project[C]. LNCS 3342: Proceedings of the First International Workshop on Swarm Robotics. Germany, Berlin: Springer-Verlag, 2004: 26-40.

[4]

Hauert, S., Zufferey, J.-C. and Floreano, D. Evolved swarming without positioning information: an application in aerial communication relay. Autonomous Robots, 26(1) 2009: 21.32

[5]

http://lis.epfl.ch/?content=research/projects/microflyers/

[6]

D. Gabor. Theory of communication. Journal of theInstitute of Electrical Engineers,

93:429–549,

1946 [7]

Massimo Banzi. Getting Started with Arduino

ISBN: 978-1.449-309879

[8]

Tero Karvinen and Kimmo Karvinen. Make a Mind-Controlled Arduino Robot

ISBN:

978-1.449-31154.4 [9]

谭浩强著 C++程序设计

ISBN: 7302085994

[10] Michael Margolis. Arduino Cookbook(SECOND EDITION) [11] Dale Wheat. Arduino Internals

ISBN: 1430238828

[12] Casey Reas and Ben Fry. Getting Started with Processing [13] Ben Fry. Visualizing Data ISBN-10: 0-596-51455-7 [14] 郭天祥著 新概念 51 单片机 C 语言教程入门、提高、开发、拓展全攻略

ISBN:9787121078934

[15] 宋轶群,杜华生,董二宝 基于 PIC 1 6F877 的红外测距系统 仪表技术 2OO4 年第 5 期 [16] 余鹰 小型机器人系统设计及走迷宫实验 武汉理工大学毕业设计(论文) [17] 清华大学 Motorola 单片机与 DSP 应用开发研究中心 蒋俊峰 基于单片机的红外通讯设计 嵌入 式系统 2003.11 [18] http://chaishushan.googlecode.com/svn/doc/arduino/r106/index.html [19] http://www.processing.org/reference/ [20] http://www.sbprojects.com/knowledge/ir/index.php [21] http://www.arcfn.com/2009/08/multi-protocol-infrared-remote-library.html

35


武汉科技大学本科毕业论文 致谢 能做出实际的东西来,肯定比纸上谈兵要付出更多的努力,而这一切也是和下面所 提及的人分不开的。 要感谢的人很多很多,但是篇幅所限这里只能先真诚感谢以下提及和未提及的众多 帮助过我和我团队的朋友们。 感谢我的团队,我的合作伙伴,机工专业的李文华同学,是他帮助我们搭建起轮式 机器人的机械结构平台,这是一项需要四处奔走和淘换零件的辛苦工作;感谢隔壁2班 的孙朝进同学,是他帮助我们焊接电路板,这是一项考验耐心的细致活。 感谢指导老师雷斌博士,他在百忙的博士后研究期间不仅给我们提供了场地(他的 办公室)和我们每天一起努力的工作,还抽空和我们交流,给予我们生活上的关心和支 持。感谢武汉理工大学物流与机器人实验室的前辈们,让我们不同学校之间的同学们可 以相互交流和学习,特别感谢实验室的宋威博士,是他把这么优秀的开发平台介绍给我 们。 感谢我的父母,感谢他们多少年来靠着辛苦的汗水抚养和教育我,也是他们的理解 和支持使得我能在这个实际动手制作的项目中不用再为经费的短缺而发愁。 还要感谢各类开源平台的发起人和各模块及扩展库的开发人员,是他们的无私奉献 使得我们得以站在巨人的肩膀之上而不必“白手起家”。 我要感谢我的母校武汉科技大学和我们机械自动化学院,那些人,那些事,那些记 忆…四年的大学生活,和那些永远也不能忘记的朋友,他们的支持与情感,是我永生难 忘的财富。

36


武汉科技大学本科毕业论文

附录 A 下位机 Arduino 程序源码 以下是 Arduino 主的程序,该程序所引用的 MegaServo.h、HMC.h、Ultrasonic.h、 IRremote.h 共四个 Arduino C++扩展库均非 Arduino 标准配置的库文件,故而其后将给出 该库的下载地址或文件内容,其余所引用的均为标准库文件。 //File Robot_Arduino.pde #ifndef HeaderFile #include <MegaServo.h> #include <HMC.h> #include <Wire.h> #include <Ultrasonic.h> #include <LiquidCrystal.h> #include <IRremote.h>

int turnConst = 90; int runConst = 500; boolean U = true; boolean P = true; #endif

//include head files //Use for the Continue Servo //and Sweep Servo //Use for the HMC5883L Compass //Use for the I2C bus //Use for the Ultrasonic //to ringing //Use for the 1602 LCD //Use for the HS0038 //to receive IR Command

void setup() { pinMode(Bell, OUTPUT);//Set up the Bell pinMode(48, OUTPUT); digitalWrite(48, LOW);//LCD R/W pin 48 to ground //pinMode(52, OUTPUT); //digitalWrite(52, LOW); lcd.begin(16, 2);//Set up the LCD's number of columns and rows: LeftServo.attach(2); RightServo.attach(3); SweepServo.attach(7); Serial.begin(9600); // set the Serial Communication Bound Wire.begin(); // join i2c bus (address optional for master) //ringBell(255, 00); // Starting Bell Serial1.begin(9600); }

#endif #ifndef Ultrasonic #define TRIGGER_PIN 17

//define and set up Ultrasonic Pin //define the //Ultrasonic TRIGGER_PIN Pin #define ECHO_PIN 16 //define the //Ultrasonic ECHO_PIN Pin Ultrasonic ultrasonic(TRIGGER_PIN, ECHO_PIN); //Set up the Ultrasonic Pin #endif #ifndef LiquidCrystal //Set 1602 LCD Pin LiquidCrystal lcd(50, 46, 44, 42, 40, 38); //RS Enable D4 D5 D6 D7 #endif #ifndef MegaServo

void loop() { nowAngle = getNowAngle(); if(millis() > RunTime) { cancelRun(); runInstruction(); } if(millis() > SweepInterval) { UltrasonicSweep(); } if(millis() > BellTime) { cancelBell(); } if(Serial.available()>0) { checkSerialCom(); Serial1.print("#"); } if(millis() > Serial1Time) { checkIRCom(); } if(millis() > TranAngleInterval || abs(angleCompare(lastAngle, nowAngle)) > 30) { sendAngle(); lastAngle = nowAngle; TranAngleInterval = millis() + 200; }

//define the Continue Servo and // Sweep Servo Pin //and Initialization

MegaServo LeftServo; MegaServo RightServo; MegaServo SweepServo; #endif #ifndef Pin //define the Bell Pin #define Bell 4 #endif #ifndef GlobalVariable//define Global variable long BellTime; long RunTime = 0; long SweepInterval; long SerialTime = 100; long Serial1Time = 2000; long TranAngleInterval = 2000; long Ptime; long Utime; int lastAngle; boolean SweepOrient; int nowAngle; int aimAngle; int UltrasonicAngle = 90; int UltrasonicDistance; int distanceData[6] = { 1000, 1000, 1000, 1000, 1000, 1000}; int currentInstruction = 1;

37


武汉科技大学本科毕业论文 disLCD();

printLCD(1, 1, "TrigU"); else printLCD(1, 1, "Trig~U"); Utime = millis() + 200;

} void checkSerialCom() { int temp = Serial.read(); int val0 = Serial.read(); int val1 = Serial.read(); int val2 = Serial.read(); if(temp =='F') { LeftServo.attach(2); RightServo.attach(3); Runforward(100*hex2dec(val0) + 10*hex2dec(val1) hex2dec(val2)); printLCD(1, 1, " "); printLCD(1, 1, "RunF "); } if(temp =='B') { LeftServo.attach(2); RightServo.attach(3); Runbackward(100*hex2dec(val0) + 10*hex2dec(val1) hex2dec(val2)); printLCD(1, 1, " "); printLCD(1, 1, "RunB "); } if(temp =='L') { LeftServo.attach(2); RightServo.attach(3); turnLeft(100*hex2dec(val0) + 10*hex2dec(val1) hex2dec(val2)); printLCD(1, 1, " "); printLCD(1, 1, "TurnL"); } if(temp =='R') { LeftServo.attach(2); RightServo.attach(3); turnRight(100*hex2dec(val0) + 10*hex2dec(val1) hex2dec(val2)); printLCD(1, 1, " "); printLCD(1, 1, "TurnR"); } if(temp == 'S') { P = false; U = false; cancelRun(); printLCD(1, 1, " "); printLCD(1, 1, "Stop!!"); } if(temp == 'P' && millis() > Ptime) { P = ~P; printLCD(1, 1, " "); if(P == true) printLCD(1, 1, "TrigP"); else printLCD(1, 1, "Trig~P"); Ptime = millis() + 200; } if(temp == 'U' && millis() > Utime) { U = ~U; printLCD(1, 1, " "); if(U == true)

} } void sendAngle() { Serial.print("A"); if(nowAngle < 100) Serial.print(0); if(nowAngle < 10) Serial.print(0); Serial.println(nowAngle); }

+

void checkIRCom() { char Instruction; if(Serial1.available() > 0) { Instruction = Serial1.read(); if(Instruction == 'F') { LeftServo.attach(2); RightServo.attach(3); Runforward(runConst); printLCD(1, 1, " printLCD(1, 1, "RunF "); } else if(Instruction == 'B') { LeftServo.attach(2); RightServo.attach(3); Runbackward(runConst); printLCD(1, 1, " printLCD(1, 1, "RunB "); } else if(Instruction == 'L') { LeftServo.attach(2); RightServo.attach(3); turnLeft(turnConst); printLCD(1, 1, " printLCD(1, 1, "TurnL"); } else if(Instruction == 'R') { LeftServo.attach(2); RightServo.attach(3); turnRight(turnConst); printLCD(1, 1, " printLCD(1, 1, "TurnR"); } else if(Instruction == 'S') { P = false; U = false; cancelRun(); printLCD(1, 1, " printLCD(1, 1, "Stop!!"); } else if(Instruction == 'U') { U = ~U; printLCD(1, 1, " if(U == true)

+

+

+

36

");

");

");

");

");

");


武汉科技大学本科毕业论文 printLCD(1, 1, "TrigU"); else printLCD(1, 1, "Trig~U");

case 3: Runforward(500); break; case 4: Runforward(500); break; case 5: Runforward(500); break; case 6: Runforward(500); break; case 7: Runforward(500); break; case 8: Runforward(500); break; } currentInstruction ++; if(currentInstruction == 9) currentInstruction = 1;

} else if(Instruction == 'P') { P = ~P; printLCD(1, 1, " "); if(P == true) printLCD(1, 1, "TrigP"); else printLCD(1, 1, "Trig~P"); } else if(Instruction == '>') { if(turnConst != 180) turnConst = turnConst + 30; printLCD(1, 1, " "); printLCD(1, 1, "< "); printLCD(1, 2, uniform3String(turnConst + 30)); } else if(Instruction == '<') { if(turnConst != 30) turnConst = turnConst - 30; printLCD(1, 1, " "); printLCD(1, 1, "< "); printLCD(1, 2, uniform3String(turnConst + 30)); } else if(Instruction == '+') { if(runConst != 1000) { runConst = runConst + 100; printLCD(1, 1, " "); printLCD(1, 1, "="); printLCD(1, 2, uniform4String(runConst)); } if(runConst == 1000) { printLCD(1, 1, " "); printLCD(1, 1, "="); printLCD(1, 2, "1000"); } } else if(Instruction == '-') { if(runConst != 100) runConst = runConst - 100; printLCD(1, 1, " "); printLCD(1, 1, "="); printLCD(1, 2, uniform4String(runConst)); }

} } int hex2dec(byte c)// converts one HEX character into a number { if (c >= '0' && c <= '9') { return c - '0'; } else if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } } void ringBell(int strength, int timems) { BellTime = millis() + timems; strength = constrain(strength, 0, 255); analogWrite(Bell, strength); } void cancelBell() { analogWrite(Bell, 0); } void printLCD(int posRow, int posCol, String content) { //lcd.clear(); lcd.setCursor(posCol -1, posRow - 1); lcd.print(content); }

} } int runInstruction() { if(P == true) { switch (currentInstruction) { case 1: Runforward(500); break; case 2: Runforward(500); break;

void Runforward(int Dis) { LeftServo.attach(2); RightServo.attach(3); LeftServo.writeMicroseconds(1000); RightServo.writeMicroseconds(2000); RunTime = millis() + map(Dis, 0, 190, 0, 1000); } void Runbackward(int Dis) { LeftServo.attach(2);

37


武汉科技大学本科毕业论文 RightServo.attach(3); LeftServo.writeMicroseconds(2000); RightServo.writeMicroseconds(1000); RunTime = millis() + map(Dis, 0, 190, 0, 1000);

printLCD(1, 13, uniform3String(UltrasonicAngle)); printLCD(2, 1, "ang:"); printLCD(2, 5, uniform3String(nowAngle) ); printLCD(2, 9, "dis:"); printLCD(2, 13, uniform4String(UltrasonicDistance));

} } void turnRight(int Angle) { LeftServo.attach(2); RightServo.attach(3); LeftServo.writeMicroseconds(1000); RightServo.writeMicroseconds(1000); RunTime = millis() + map(Angle, 0, 360, 0, 2800); }

int getUltrasonicDistance()//use the Ultrasonic sensor // to mesure distance { float mmMsec; long microsec; microsec = ultrasonic.timing(); mmMsec = 10 * ultrasonic.convert(microsec, Ultrasonic::CM); if(mmMsec < 300) { ringBell(127, 200); avoidObstacle(); } return int(mmMsec); }

void turnLeft(int Angle) { LeftServo.attach(2); RightServo.attach(3); LeftServo.writeMicroseconds(2000); RightServo.writeMicroseconds(2000); RunTime = millis() + map(Angle, 0, 360, 0, 2800); } void turn(int turnAim) { while( abs(angleCompare(nowAngle, turnAim) ) > 5) { LeftServo.writeMicroseconds( constrain(1500 angleCompare(nowAngle, turnAim), 1000, 2000 ) ); RightServo.writeMicroseconds( constrain(1500 angleCompare(nowAngle, turnAim), 1000, 2000 ) ); nowAngle = getNowAngle(); disLCD(); printLCD(1, 1, "aim:"); printLCD(1, 5, uniform3String(turnAim) ); } while( abs(angleCompare(nowAngle, turnAim) ) > 5) { LeftServo.writeMicroseconds( constrain(1500 angleCompare(nowAngle, turnAim), 1000, 2000 ) ); RightServo.writeMicroseconds( constrain(1500 angleCompare(nowAngle, turnAim), 1000, 2000 ) ); nowAngle = getNowAngle(); disLCD(); printLCD(1, 1, "aim:"); printLCD(1, 5, uniform3String(turnAim) ); } }

void avoidObstacle() { SweepServo.write(90); if(UltrasonicAngle == 90) { -

5

*

-

5

*

-

5

*

-

5

*

turnLeft(60); } else if(UltrasonicAngle == 0) { turnRight(5); } else if(UltrasonicAngle == 180) { turnLeft(5); } else if(UltrasonicAngle == 30) { turnRight(15); } else if(UltrasonicAngle == 60) { turnRight(30); } else if(UltrasonicAngle == 120) { turnLeft(30); } else if(UltrasonicAngle == 150) { turnLeft(15); }

void turnIncrement(int turnAngle) { nowAngle = getNowAngle(); aimAngle = constrainAngle(nowAngle + turnAngle); turn(aimAngle); }

} int constrainAngle(int angleDeg) { if(angleDeg > 359) { do{ angleDeg = angleDeg - 360; } while(angleDeg > 359); } else if(angleDeg < 0) { do{

void cancelRun() { //LeftServo.writeMicroseconds(1500); //RightServo.writeMicroseconds(1500); LeftServo.detach(); RightServo.detach(); } void disLCD() { printLCD(1, 9, "Swp:");

38

//adjust the sweep //and turn correspond //angle


武汉科技大学本科毕业论文 angleDeg = angleDeg + 360; } while(angleDeg < 0);

} }

} return angleDeg;

void CollectUltrasonicData(int Angle, int DistanceData) { distanceData[(Angle - 20) / 30] = DistanceData; }

} int getNowAngle() { int tempAngle; HMC.getAngle(&tempAngle); return constrainAngle(tempAngle); }

int angleCompare(int baseAngle, int endAngle) { if(baseAngle > endAngle) { if(baseAngle - endAngle < 180) { return endAngle - baseAngle; } else { return 360 - baseAngle + endAngle; } } else { if(endAngle - baseAngle < 180) { return endAngle - baseAngle; } else { return (endAngle - baseAngle) - 360; } } }

String uniform3String(int num) { String formateString; if(num < 10) { formateString = "00" + String(num); } else if(num <100) { formateString = "0" + String(num); } else { formateString = String(num); } return formateString; } String uniform4String(int num) { if(num < 1000) { return '0' + uniform3String(num); } else { return String(num/1000) + uniform3String(num); } } void UltrasonicSweep() { if(U == true) { UltrasonicDistance = getUltrasonicDistance(); CollectUltrasonicData(UltrasonicAngle, UltrasonicDistance); if(UltrasonicAngle >= 170 || UltrasonicAngle <= 20) { SweepOrient = ~SweepOrient; } if(SweepOrient == false) { UltrasonicAngle = UltrasonicAngle + 30; } else { UltrasonicAngle = UltrasonicAngle - 30; } SweepServo.write(UltrasonicAngle); SweepInterval = millis() + 100; } else { SweepServo.write(90);

39


武汉科技大学本科毕业论文 //File HMC.h /* * HMC.cpp - Interface a Honeywell HMC5843 magnetometer to an AVR via i2c * Version 0.1 - http://www.timzaman.com/ * Copyright (c) 2011 Tim Zaman * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */

#endif // HMC_h

#include "WProgram.h" #ifndef HMC_h #define HMC_h #define HMC5883_WriteAddress 0x1E // i.e 0x3C >> 1 #define HMC5883_ModeRegisterAddress 0x02 #define HMC5883_ContinuousModeCommand 0x00 #define HMC5883_DataOutputXMSBAddress 0x03 class HMC5843 { public: HMC5843(); void init(); void getValues(int *x, int *y, int *z); void getAngle(int *a); private: }; extern HMC5843 HMC;

40


武汉科技大学本科毕业论文 //File HMC.cpp /* * HMC.cpp - Interface a Honeywell HMC5843 magnetometer to an AVR via i2c * Version 0.1 - http://www.timzaman.com/ * Copyright (c) 2011 Tim Zaman * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "HMC.h" #include "WProgram.h" #include <Wire.h> /* PUBLIC METHODS */ HMC5843::HMC5843() { } void HMC5843::init() { } // This can be called at 100ms intervals to get new data void HMC5843::getValues(int *x, int *y, int *z) { Wire.begin(); //Initiate the Wire library and join the I2C bus as a master int regb=0x01; int regbdata=0x40; int outputData[6]; int i; double angle; Wire.beginTransmission(HMC5883_WriteAddress); Wire.send(regb); Wire.send(regbdata); Wire.endTransmission(); //delay(1000); Wire.beginTransmission(HMC5883_WriteAddress); //Initiate a transmission with HMC5883 (Write address). Wire.send(HMC5883_ModeRegisterAddress); //Place the Mode Register Address in send-buffer. Wire.send(HMC5883_ContinuousModeCommand); //Place the command for Continuous operation Mode in send-buffer. Wire.endTransmission(); //Send the send-buffer to HMC5883 and end the I2C transmission. //delay(100);

Wire.beginTransmission(HMC5883_WriteAddress);

//Initiate a transmission with HMC5883 (Write address).

41


武汉科技大学本科毕业论文 Wire.requestFrom(HMC5883_WriteAddress,6);

//Request 6 bytes of data from the address specified.

//delay(500);

//Read the value of magnetic components X,Y and Z if(6 <= Wire.available()) // If the number of bytes available for reading be <=6. { for(i=0;i<6;i++) { outputData[i]=Wire.receive(); //Store the data in outputData buffer } } *x=outputData[0] << 8 | outputData[1]; //Combine MSB and LSB of X Data output register *z=outputData[2] << 8 | outputData[3]; //Combine MSB and LSB of Z Data output register *y=outputData[4] << 8 | outputData[5]; //Combine MSB and LSB of Y Data output register } void HMC5843::getAngle(int *a) { int fx,fy,fz; getValues(&fx,&fy,&fz); //angle= atan2((double)y,(double)x) * (180 / 3.14159265) + 180; // angle in degrees *a= atan2((double)fy,(double)fx)*(180/PI) + 180; // angle in degrees }

// Set the default object HMC5843 HMC = HMC5843();

42


武汉科技大学本科毕业论文 //File IRremote.h /* * IRremote * Version 0.1 July, 2009 * Copyright 2009 Ken Shirriff * For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.ht m http://arcfn.com * * Interrupt code based on NECIRrcv by Joe Knapp * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556 * Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-anarduino/ */

long decodeRC6(decode_results *results); } ; // Only used for testing; can remove virtual for shorter code #ifdef TEST #define VIRTUAL virtual #else #define VIRTUAL #endif class IRsend { public: IRsend() {} void sendNEC(unsigned long data, int nbits); void sendSony(unsigned long data, int nbits); void sendRaw(unsigned int buf[], int len, int hz); void sendRC5(unsigned long data, int nbits); void sendRC6(unsigned long data, int nbits); // private: void enableIROut(int khz); VIRTUAL void mark(int usec); VIRTUAL void space(int usec); } ;

#ifndef IRremote_h #define IRremote_h // The following are compile-time library options. // If you change them, recompile the library. // If DEBUG is defined, a lot of debugging output will be printed during decoding. // TEST must be defined for the IRtest unittests to work. It will make some // methods virtual, which will be slightly slower, which is why it is optional. // #define DEBUG // #define TEST

// Some useful constants #define USECPERTICK 50 // microseconds per clock interrupt tick #define RAWBUF 76 // Length of raw duration buffer

// Results returned from the decoder class decode_results { public: int decode_type; // NEC, SONY, RC5, UNKNOWN unsigned long value; // Decoded value int bits; // Number of bits in decoded value volatile unsigned int *rawbuf; // Raw intervals in .5 us ticks int rawlen; // Number of records in rawbuf. };

// Marks tend to be 100us too long, and spaces 100us too short // when received due to sensor lag. #define MARK_EXCESS 100 #endif

// Values for decode_type #define NEC 1 #define SONY 2 #define RC5 3 #define RC6 4 #define UNKNOWN -1 // Decoded value for NEC when a repeat code is received #define REPEAT 0xffffffff // main class for receiving IR class IRrecv { public: IRrecv(int recvpin); void blink13(int blinkflag); int decode(decode_results *results); void enableIRIn(); void resume(); private: // These are called by decode int getRClevel(decode_results *results, int *offset, int *used, int t1); long decodeNEC(decode_results *results); long decodeSony(decode_results *results); long decodeRC5(decode_results *results);

43


武汉科技大学本科毕业论文 //File IRremoteInt.h /* * IRremote * Version 0.1 July, 2009 * Copyright 2009 Ken Shirriff * For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.ht ml * * Interrupt code based on NECIRrcv by Joe Knapp * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556 * Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-anarduino/ */

#define RC6_RPT_LENGTH

#define TOLERANCE 25 // percent tolerance in measurements #define LTOL (1.0 - TOLERANCE/100.) #define UTOL (1.0 + TOLERANCE/100.) #define _GAP 5000 // Minimum map between transmissions #define GAP_TICKS (_GAP/USECPERTICK) #define TICKS_LOW(us) (int) (((us)*LTOL/USECPERTICK)) #define TICKS_HIGH(us) (int) (((us)*UTOL/USECPERTICK + 1)) #ifndef DEBUG #define MATCH(measured_ticks, desired_us) ((measured_ticks) >= TICKS_LOW(desired_us) && (measured_ticks) <= TICKS_HIGH(desired_us)) #define MATCH_MARK(measured_ticks, desired_us) MATCH(measured_ticks, (desired_us) + MARK_EXCESS) #define MATCH_SPACE(measured_ticks, desired_us) MATCH((measured_ticks), (desired_us) - MARK_EXCESS) // Debugging versions are in IRremote.cpp #endif

#ifndef IRremoteint_h #define IRremoteint_h #include <WProgram.h> #define CLKFUDGE 5 // fudge factor for clock interrupt overhead #define CLK 256 // max value for clock (timer 2) #define PRESCALE 8 // timer2 clock prescale #define SYSCLOCK 16000000 // main Arduino clock #define CLKSPERUSEC (SYSCLOCK/PRESCALE/1000000) // timer clocks per microsecond

// receiver states #define STATE_IDLE #define STATE_MARK #define STATE_SPACE #define STATE_STOP

#define ERR 0 #define DECODED 1

// defines for setting and clearing register bits #ifndef cbi #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #endif #ifndef sbi #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) #endif

// pulse parameters in usec #define NEC_HDR_MARK #define NEC_HDR_SPACE #define NEC_BIT_MARK #define NEC_ONE_SPACE #define NEC_ZERO_SPACE #define NEC_RPT_SPACE

2 3 4 5

// information for the interrupt handler typedef struct { uint8_t recvpin; // pin for IR data from detector uint8_t rcvstate; // state machine uint8_t blinkflag; // TRUE to enable blinking of pin 13 on IR processing unsigned int timer; // state timer, counts 50uS ticks. unsigned int rawbuf[RAWBUF]; // raw data uint8_t rawlen; // counter of entries in rawbuf } irparams_t;

#define BLINKLED 13

// clock timer reset value #define INIT_TIMER_COUNT2 (CLK USECPERTICK*CLKSPERUSEC + CLKFUDGE) #define RESET_TIMER2 TCNT2 = INIT_TIMER_COUNT2

// Defined in IRremote.cpp extern volatile irparams_t irparams;

-

// IR detector output is active low #define MARK 0 #define SPACE 1

9000 4500 560 1600 560 2250

#define TOPBIT 0x80000000 #define NEC_BITS 32 #define SONY_BITS 12 #define MIN_RC5_SAMPLES 11 #define MIN_RC6_SAMPLES 1

#define SONY_HDR_MARK #define SONY_HDR_SPACE #define SONY_ONE_MARK #define SONY_ZERO_MARK #define SONY_RPT_LENGTH 45000

2400 600 1200 600

#define RC5_T1 #define RC5_RPT_LENGTH

889 46000

#define RC6_HDR_MARK #define RC6_HDR_SPACE #define RC6_T1

2666 889

46000

#endif

444

44


武汉科技大学本科毕业论文 //File IRremote.cpp /* * IRremote * Version 0.11 August, 2009 * Copyright 2009 Ken Shirriff * For details, see http://arcfn.com/2009/08/multi-protocol-infrared-remote-library.ht ml * * Interrupt code based on NECIRrcv by Joe Knapp * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1210243556 * Also influenced by http://zovirl.com/2008/11/12/building-a-universal-remote-with-anarduino/ */

Serial.print(measured_ticks, DEC); Serial.print(" <= "); Serial.println(TICKS_HIGH(desired_us - MARK_EXCESS), DEC); return measured_ticks >= TICKS_LOW(desired_us MARK_EXCESS) && measured_ticks <= TICKS_HIGH(desired_us - MARK_EXCESS); } #endif void IRsend::sendNEC(unsigned long data, int nbits) { enableIROut(38); mark(NEC_HDR_MARK); space(NEC_HDR_SPACE); for (int i = 0; i < nbits; i++) { if (data & TOPBIT) { mark(NEC_BIT_MARK); space(NEC_ONE_SPACE); } else { mark(NEC_BIT_MARK); space(NEC_ZERO_SPACE); } data <<= 1; } mark(NEC_BIT_MARK); space(0); }

#include "IRremote.h" #include "IRremoteInt.h" // Provides ISR #include <avr/interrupt.h> volatile irparams_t irparams; // These versions of MATCH, MATCH_MARK, and MATCH_SPACE are only for debugging. // To use them, set DEBUG in IRremoteInt.h // Normally macros are used for efficiency #ifdef DEBUG int MATCH(int measured, int desired) { Serial.print("Testing: "); Serial.print(TICKS_LOW(desired), DEC); Serial.print(" <= "); Serial.print(measured, DEC); Serial.print(" <= "); Serial.println(TICKS_HIGH(desired), DEC); return measured >= TICKS_LOW(desired) && measured <= TICKS_HIGH(desired); }

void IRsend::sendSony(unsigned long data, int nbits) { enableIROut(40); mark(SONY_HDR_MARK); space(SONY_HDR_SPACE); data = data << (32 - nbits); for (int i = 0; i < nbits; i++) { if (data & TOPBIT) { mark(SONY_ONE_MARK); space(SONY_HDR_SPACE); } else { mark(SONY_ZERO_MARK); space(SONY_HDR_SPACE); } data <<= 1; } }

int MATCH_MARK(int measured_ticks, int desired_us) { Serial.print("Testing mark "); Serial.print(measured_ticks * USECPERTICK, DEC); Serial.print(" vs "); Serial.print(desired_us, DEC); Serial.print(": "); Serial.print(TICKS_LOW(desired_us + MARK_EXCESS), DEC); Serial.print(" <= "); Serial.print(measured_ticks, DEC); Serial.print(" <= "); Serial.println(TICKS_HIGH(desired_us + MARK_EXCESS), DEC); return measured_ticks >= TICKS_LOW(desired_us + MARK_EXCESS) && measured_ticks <= TICKS_HIGH(desired_us + MARK_EXCESS); }

void IRsend::sendRaw(unsigned int buf[], int len, int hz) { enableIROut(hz); for (int i = 0; i < len; i++) { if (i & 1) { space(buf[i]); } else { mark(buf[i]); } } space(0); // Just to be sure }

int MATCH_SPACE(int measured_ticks, int desired_us) { Serial.print("Testing space "); Serial.print(measured_ticks * USECPERTICK, DEC); Serial.print(" vs "); Serial.print(desired_us, DEC); Serial.print(": "); Serial.print(TICKS_LOW(desired_us - MARK_EXCESS), DEC); Serial.print(" <= ");

// Note: first bit must be a one (start bit) void IRsend::sendRC5(unsigned long data, int nbits) { enableIROut(36); data = data << (32 - nbits); mark(RC5_T1); // First start bit

45


武汉科技大学本科毕业论文 space(RC5_T1); // Second start bit mark(RC5_T1); // Second start bit for (int i = 0; i < nbits; i++) { if (data & TOPBIT) { space(RC5_T1); // 1 is space, then mark mark(RC5_T1); } else { mark(RC5_T1); space(RC5_T1); } data <<= 1; } space(0); // Turn off at end

values, it's up to you // to make sure it gives reasonable results. (Watch out for overflow / underflow / rounding.) // TIMER2 is used in phase-correct PWM mode, with OCR2A controlling the frequency and OCR2B // controlling the duty cycle. // There is no prescaling, so the output frequency is 16MHz / (2 * OCR2A) // To turn the output on and off, we leave the PWM running, but connect and disconnect the output pin. // A few hours staring at the ATmega documentation and this will all make sense. // See my Secrets of Arduino PWM at http://arcfn.com/2009/07/secrets-of-arduino-pwm.html for details.

} // Caller needs to take care of flipping the toggle bit void IRsend::sendRC6(unsigned long data, int nbits) { enableIROut(36); data = data << (32 - nbits); mark(RC6_HDR_MARK); space(RC6_HDR_SPACE); mark(RC6_T1); // start bit space(RC6_T1); int t; for (int i = 0; i < nbits; i++) { if (i == 3) { // double-wide trailer bit t = 2 * RC6_T1; } else { t = RC6_T1; } if (data & TOPBIT) { mark(t); space(t); } else { space(t); mark(t); }

// Disable the Timer2 Interrupt (which is used for receiving IR) TIMSK2 &= ~_BV(TOIE2); //Timer2 Overflow Interrupt pinMode(3, OUTPUT); digitalWrite(3, LOW); // When not sending PWM, we want it low // COM2A = 00: disconnect OC2A // COM2B = 00: disconnect OC2B; to send signal set to 10: OC2B non-inverted // WGM2 = 101: phase-correct PWM with OCRA as top // CS2 = 000: no prescaling TCCR2A = _BV(WGM20); TCCR2B = _BV(WGM22) | _BV(CS20); // The top value for the timer. The modulation frequency will be SYSCLOCK / 2 / OCR2A. OCR2A = SYSCLOCK / 2 / khz / 1000; OCR2B = OCR2A / 3; // 33% duty cycle } IRrecv::IRrecv(int recvpin) { irparams.recvpin = recvpin; irparams.blinkflag = 0; }

data <<= 1; } space(0); // Turn off at end

// initialization void IRrecv::enableIRIn() { // setup pulse clock timer interrupt TCCR2A = 0; // normal mode

} void IRsend::mark(int time) { // Sends an IR mark for the specified number of microseconds. // The mark output is modulated at the PWM frequency. TCCR2A |= _BV(COM2B1); // Enable pin 3 PWM output delayMicroseconds(time); }

//Prescale /8 (16M/8 = 0.5 microseconds per tick) // Therefore, the timer interval can range from 0.5 to 128 microseconds // depending on the reset value (255 to 0) cbi(TCCR2B,CS22); sbi(TCCR2B,CS21); cbi(TCCR2B,CS20);

/* Leave pin off for time (given in microseconds) */ void IRsend::space(int time) { // Sends an IR space for the specified number of microseconds. // A space is no output, so the PWM output is disabled. TCCR2A &= ~(_BV(COM2B1)); // Disable pin 3 PWM output delayMicroseconds(time); }

//Timer2 Overflow Interrupt Enable sbi(TIMSK2,TOIE2); RESET_TIMER2; sei();

void IRsend::enableIROut(int khz) { // Enables IR output. The khz value controls the modulation frequency in kilohertz. // The IR output will be on pin 3 (OC2B). // This routine is designed for 36-40KHz; if you use it for other

// enable interrupts

// initialize state machine variables irparams.rcvstate = STATE_IDLE; irparams.rawlen = 0;

46


武汉科技大学本科毕业论文 // set pin modes pinMode(irparams.recvpin, INPUT);

irparams.rcvstate = STATE_STOP; }

}

} break; case STATE_STOP: // waiting, measuring gap if (irdata == MARK) { // reset gap timer irparams.timer = 0; } break; }

// enable/disable blinking of pin 13 on IR processing void IRrecv::blink13(int blinkflag) { irparams.blinkflag = blinkflag; if (blinkflag) pinMode(BLINKLED, OUTPUT); }

if (irparams.blinkflag) { if (irdata == MARK) { PORTB |= B00100000; // turn pin 13 LED on } else { PORTB &= B11011111; // turn pin 13 LED off } }

// TIMER2 interrupt code to collect raw data. // Widths of alternating SPACE, MARK are recorded in rawbuf. // Recorded in ticks of 50 microseconds. // rawlen counts the number of entries recorded so far. // First entry is the SPACE between transmissions. // As soon as a SPACE gets long, ready is set, state switches to IDLE, timing of SPACE continues. // As soon as first MARK arrives, gap width is recorded, ready is cleared, and new logging starts ISR(TIMER2_OVF_vect) { RESET_TIMER2;

} void IRrecv::resume() { irparams.rcvstate = STATE_IDLE; irparams.rawlen = 0; }

uint8_t irdata = (uint8_t)digitalRead(irparams.recvpin); irparams.timer++; // One more 50us tick if (irparams.rawlen >= RAWBUF) { // Buffer overflow irparams.rcvstate = STATE_STOP; } switch(irparams.rcvstate) { case STATE_IDLE: // In the middle of a gap if (irdata == MARK) { if (irparams.timer < GAP_TICKS) { // Not big enough to be a gap. irparams.timer = 0; } else { // gap just ended, record duration and start recording transmission irparams.rawlen = 0; irparams.rawbuf[irparams.rawlen++] = irparams.timer; irparams.timer = 0; irparams.rcvstate = STATE_MARK; } } break; case STATE_MARK: // timing MARK if (irdata == SPACE) { // MARK ended, record time irparams.rawbuf[irparams.rawlen++] = irparams.timer; irparams.timer = 0; irparams.rcvstate = STATE_SPACE; } break; case STATE_SPACE: // timing SPACE if (irdata == MARK) { // SPACE just ended, record it irparams.rawbuf[irparams.rawlen++] = irparams.timer; irparams.timer = 0; irparams.rcvstate = STATE_MARK; } else { // SPACE if (irparams.timer > GAP_TICKS) { // big SPACE, indicates gap between codes // Mark current code as ready for processing // Switch to STOP // Don't reset timer; keep counting space width

// Decodes the received IR message // Returns 0 if no data ready, 1 if data ready. // Results of decoding are stored in results int IRrecv::decode(decode_results *results) { results->rawbuf = irparams.rawbuf; results->rawlen = irparams.rawlen; if (irparams.rcvstate != STATE_STOP) { return ERR; } #ifdef DEBUG Serial.println("Attempting NEC decode"); #endif if (decodeNEC(results)) { return DECODED; } #ifdef DEBUG Serial.println("Attempting Sony decode"); #endif if (decodeSony(results)) { return DECODED; } #ifdef DEBUG Serial.println("Attempting RC5 decode"); #endif if (decodeRC5(results)) { return DECODED; } #ifdef DEBUG Serial.println("Attempting RC6 decode"); #endif if (decodeRC6(results)) { return DECODED; } if (results->rawlen >= 6) { // Only return raw buffer if at least 6 bits results->decode_type = UNKNOWN; results->bits = 0; results->value = 0; return DECODED; }

47


武汉科技大学本科毕业论文 // Throw away and start over resume(); return ERR;

SONY_HDR_MARK)) { return ERR; } offset++;

} long IRrecv::decodeNEC(decode_results *results) { long data = 0; int offset = 1; // Skip first space // Initial mark if (!MATCH_MARK(results->rawbuf[offset], NEC_HDR_MARK)) { return ERR; } offset++; // Check for repeat if (irparams.rawlen == 4 && MATCH_SPACE(results->rawbuf[offset], NEC_RPT_SPACE) && MATCH_MARK(results->rawbuf[offset+1], NEC_BIT_MARK)) { results->bits = 0; results->value = REPEAT; results->decode_type = NEC; return DECODED; } if (irparams.rawlen < 2 * NEC_BITS + 4) { return ERR; } // Initial space if (!MATCH_SPACE(results->rawbuf[offset], NEC_HDR_SPACE)) { return ERR; } offset++; for (int i = 0; i < NEC_BITS; i++) { if (!MATCH_MARK(results->rawbuf[offset], NEC_BIT_MARK)) { return ERR; } offset++; if (MATCH_SPACE(results->rawbuf[offset], NEC_ONE_SPACE)) { data = (data << 1) | 1; } else if (MATCH_SPACE(results->rawbuf[offset], NEC_ZERO_SPACE)) { data <<= 1; } else { return ERR; } offset++; } // Success results->bits = NEC_BITS; results->value = data; results->decode_type = NEC; return DECODED; }

while (offset + 1 < irparams.rawlen) { if (!MATCH_SPACE(results->rawbuf[offset], SONY_HDR_SPACE)) { break; } offset++; if (MATCH_MARK(results->rawbuf[offset], SONY_ONE_MARK)) { data = (data << 1) | 1; } else if (MATCH_MARK(results->rawbuf[offset], SONY_ZERO_MARK)) { data <<= 1; } else { return ERR; } offset++; } // Success results->bits = (offset - 1) / 2; if (results->bits < 12) { results->bits = 0; return ERR; } results->value = data; results->decode_type = SONY; return DECODED; } // Gets one undecoded level at a time from the raw buffer. // The RC5/6 decoding is easier if the data is broken into time intervals. // E.g. if the buffer has MARK for 2 time intervals and SPACE for 1, // successive calls to getRClevel will return MARK, MARK, SPACE. // offset and used are updated to keep track of the current position. // t1 is the time interval for a single bit in microseconds. // Returns -1 for error (measured time interval is not a multiple of t1). int IRrecv::getRClevel(decode_results *results, int *offset, int *used, int t1) { if (*offset >= results->rawlen) { // After end of recorded buffer, assume SPACE. return SPACE; } int width = results->rawbuf[*offset]; int val = ((*offset) % 2) ? MARK : SPACE; int correction = (val == MARK) ? MARK_EXCESS : MARK_EXCESS; int avail; if (MATCH(width, t1 + correction)) { avail = 1; } else if (MATCH(width, 2*t1 + correction)) { avail = 2; } else if (MATCH(width, 3*t1 + correction)) { avail = 3; }

long IRrecv::decodeSony(decode_results *results) { long data = 0; if (irparams.rawlen < 2 * SONY_BITS + 2) { return ERR; } int offset = 1; // Skip first space // Initial mark if (!MATCH_MARK(results->rawbuf[offset],

48


武汉科技大学本科毕业论文 else { return -1; }

long IRrecv::decodeRC6(decode_results *results) { if (results->rawlen < MIN_RC6_SAMPLES) { return ERR; } int offset = 1; // Skip first space // Initial mark if (!MATCH_MARK(results->rawbuf[offset], RC6_HDR_MARK)) { return ERR; } offset++; if (!MATCH_SPACE(results->rawbuf[offset], RC6_HDR_SPACE)) { return ERR; } offset++; long data = 0; int used = 0; // Get start bit (1) if (getRClevel(results, &offset, &used, RC6_T1) != MARK) return ERR; if (getRClevel(results, &offset, &used, RC6_T1) != SPACE) return ERR; int nbits; for (nbits = 0; offset < results->rawlen; nbits++) { int levelA, levelB; // Next two levels levelA = getRClevel(results, &offset, &used, RC6_T1); if (nbits == 3) { // T bit is double wide; make sure second half matches if (levelA != getRClevel(results, &offset, &used, RC6_T1)) return ERR; } levelB = getRClevel(results, &offset, &used, RC6_T1); if (nbits == 3) { // T bit is double wide; make sure second half matches if (levelB != getRClevel(results, &offset, &used, RC6_T1)) return ERR; } if (levelA == MARK && levelB == SPACE) { // reversed compared to RC5 // 1 bit data = (data << 1) | 1; } else if (levelA == SPACE && levelB == MARK) { // zero bit data <<= 1; } else { return ERR; // Error } } // Success results->bits = nbits; results->value = data; results->decode_type = RC6; return DECODED; }

(*used)++; if (*used >= avail) { *used = 0; (*offset)++; } #ifdef DEBUG if (val == MARK) { Serial.println("MARK"); } else { Serial.println("SPACE"); } #endif return val; } long IRrecv::decodeRC5(decode_results *results) { if (irparams.rawlen < MIN_RC5_SAMPLES + 2) { return ERR; } int offset = 1; // Skip gap space long data = 0; int used = 0; // Get start bits if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return ERR; if (getRClevel(results, &offset, &used, RC5_T1) != SPACE) return ERR; if (getRClevel(results, &offset, &used, RC5_T1) != MARK) return ERR; int nbits; for (nbits = 0; offset < irparams.rawlen; nbits++) { int levelA = getRClevel(results, &offset, &used, RC5_T1); int levelB = getRClevel(results, &offset, &used, RC5_T1); if (levelA == SPACE && levelB == MARK) { // 1 bit data = (data << 1) | 1; } else if (levelA == MARK && levelB == SPACE) { // zero bit data <<= 1; } else { return ERR; } } // Success results->bits = nbits; results->value = data; results->decode_type = RC5; return DECODED; }

49


武汉科技大学本科毕业论文

附录 B 上位机 Processing 程序源码 下面的 Processing 程序源码中还引用了 Button、HScrollbar、Integrator、Table 四个 类库,其中 Button、HScrollbar 为 Processing 开发环境中示例程序所附。Integrator、Table 源自 Ben Fry 所著《Visualizing Data》,为便于读者阅读,仍将给出该部分代码。 //File Robot_Processing.pde //reference from //Topics-GUI-Button\ImageButton //Topics-Interaction-Follow1 //Topics-Motion-Brownian

textFont(plotFont); // Define and create image button PImage b = loadImage("base.gif"); PImage r = loadImage("roll.gif"); PImage d = loadImage("down.gif"); int x = 400 - b.width/2; int y = 300 - b.height/2; int w = b.width; int h = b.height; button = new ImageButtons(x, y, w, h, b, r, d);

import processing.serial.*; Serial port; Table nameTable; PrintWriter writer; Integrator angle; PFont plotFont; PFont f; ImageButtons button; ImageButtons power; ImageButtons ultrawave; HScrollbar journeyScr, angleScr;

// Define and create image button PImage p1 = loadImage("Power.png"); PImage p2 = loadImage("Power2.png"); PImage p3 = loadImage("Power3.png"); int px = 10; int py = 10; int pw = p1.width; int ph = p1.height; power = new ImageButtons(px, py, pw, ph, p1, p2, p3);

int currentRow = 0; long timeSign; int []circleX = { 200, 400, 600, 400}; int []circleY = { 300, 150, 300, 450}; char []command = { 'L', 'F', 'R', 'B' }; //int circleSize = 100; // Diameter of circle Integrator[] circleSize;

// Define and create image button PImage u1 = loadImage("UltraWave.png"); PImage u2 = loadImage("UltraWave2.PNG"); PImage u3 = loadImage("UltraWave3.PNG"); int ux = 120; int uy = 15; int uw = b.width; int uh = b.height; ultrawave = new ImageButtons(ux, uy, uw, uh, u1, u2, u3);

color circleColor, circleHighlight, baseColor, currentColor; boolean []circleOver = {false, false, false, false}; void setup() { size(1200, 600); smooth(); circleColor = color(255); circleHighlight = color(204); baseColor = color(102);

}

void draw() { update(mouseX, mouseY); background(currentColor); //scale(0.3); for(int count = 0; count < 4; count ++) { circleSize[count].update(); if(circleOver[count]) { fill(circleHighlight); circleSize[count].target(100); } else { fill(circleColor); } stroke(0); ellipse(circleX[count], circleY[count], circleSize[count].value, circleSize[count].value); fill(230, 255);

journeyScr = new HScrollbar(800, 100, 200, 25, 5); angleScr = new HScrollbar(800, 150, 200, 25, 5); currentColor = baseColor; String arduinoPort = Serial.list()[0]; println(Serial.list()); println(arduinoPort); port = new Serial(this, arduinoPort, 9600); // connect to Arduino circleSize = new Integrator[4]; for(int count = 0; count < 4; count ++) { circleSize[count] = new Integrator(100); circleSize[count].attraction = 0.8; circleSize[count].damping = 0.9; } writer = createWriter("locations.tsv"); angle = new Integrator(0, 0.4, 0.4); plotFont = createFont("SansSerif", 20);

50


武汉科技大学本科毕业论文 triangle(170, 300, 215, 274, 215, 326);//left triangle(400, 120, 374, 165, 426, 165);//top triangle(585, 274, 630, 300, 585, 326);//right triangle(374, 435, 426, 435, 400, 480);//bottom

popMatrix(); fill(0); textSize(20); rotate(radians(90)); text(int(180*angle.value/PI + 180), 35, -10);

} text("路程", 750, 110); text(int(map(journeyScr.getPos(),915.245,1113.3,0,999)) + "mm", journeyScr.getPos()-130, 88); text("角度", 750, 160); text(int(map(angleScr.getPos(),915.245,1113.3,0,360)) + "deg", angleScr.getPos()-130, 138); fill(180, 255, 0); textSize(30); text("急停", 370, 370); textSize(20); text("启停", 40, 95); text("壁障", 130, 95); text("前进", 380, 230); text("后退", 380, 520); text("左转", 180, 370); text("右转", 580, 370); text("电子罗盘", 860, 220); text("南", 890, 250); text("西", 1035, 390); text("北", 890, 530); text("东", 740, 390); String dis = "已发出指令:" + currentRow + "条"; text(dis, 300, 50); //text(); rectMode(CORNER); journeyScr.update(); journeyScr.display(); angleScr.update(); angleScr.display();

if (port.available() >= 4) { String inBuffer = port.readString(); println(inBuffer); if(inBuffer.charAt(0) == 'A') { int temp1 = int(inBuffer.charAt(1)) - 48; int temp2 = int(inBuffer.charAt(2)) - 48; int temp3 = int(inBuffer.charAt(3)) - 48; angle.target((PI*(temp1*100 + temp2*10 + temp3)/180) - (PI)); } //port.clear(); } } void drawRobot() { fill(222, 222, 249); ellipse(0, 0, 240, 240); fill(38, 38, 200); rectMode(CENTER); rect(0, 50, 5, 100); } void update(int x, int y) { for(int count = 0; count < 4; count ++) { if( overCircle(circleX[count], circleSize[count].value) ) { circleOver[count] = true; } else { circleOver[count] = false; } } }

button.update(); button.display(); power.update(); power.display(); ultrawave.update(); ultrawave.display(); if(button.pressed == true) { port.write("S"+'\0'); writer.println("S 停止"); } if(power.pressed == true && millis() > timeSign) { port.write("P"+'\0'); writer.println("P 启停"); timeSign = millis() + 10; } if(ultrawave.pressed == true) { port.write("U"+'\0'); writer.println("U 壁障"); }

circleY[count],

void mousePressed() { if(dist(900, 380, mouseX, mouseY) < 120) { angle.target(-atan2(mouseX - 900, mouseY - 380)); } if(mouseX > 350 && mouseX < 454 && mouseY >256 && mouseY < 344) { port.write("S" + '\0'); writer.println("S 急停"); } /* if(dist(35, 35, mouseX, mouseY) < 25) { port.write("P" + '\0'); } if(mouseX > 100 && mouseX < 164 && mouseY > 15 &&

translate(900, 380); rotate(angle.value); pushMatrix(); angle.update(); drawRobot();

51


武汉科技大学本科毕业论文 mouseY < 179) { port.write("U" + '\0'); } */ //350,256,454,344 print("\0" + mouseX + "\t" + mouseY + "\n"); currentRow ++; for(int count = 0; count < 4; count ++) { if(circleOver[0]) { writer.print("L 左转 "); String temp = "" + uniform3String(int(map(journeyScr.getPos(),915.245,1113.3,0,999 ))); writer.println(temp); } if(circleOver[1]) { writer.print("F 前进 "); String temp = "" + uniform3String(int(map(journeyScr.getPos(),915.245,1113.3,0,999 ))); writer.println(temp); } if(circleOver[1]) { writer.print("R 右转 "); String temp = "" + uniform3String(int(map(journeyScr.getPos(),915.245,1113.3,0,999 ))); writer.println(temp); } if(circleOver[1]) { writer.print("B 后退 "); String temp = "" + uniform3String(int(map(journeyScr.getPos(),915.245,1113.3,0,999 ))); writer.println(temp); } if(circleOver[count]) { port.write(command[count]); if(count %2 != 0) {

if(currentRow != 0) { //writer.println(mouseX + "\t" + mouseY); } if(currentRow == 50) { writer.flush(); writer.close(); exit(); } } boolean overCircle(int x, int y, float diameter) { float disX = x - mouseX; float disY = y - mouseY; if(sqrt(sq(disX) + sq(disY)) < diameter/2 ) { return true; } else { return false; } } String uniform3String(int num) { String formateString; if(num < 10) { formateString = "00" + num; } else if(num <100) { formateString = "0" + num; } else { formateString = "" + num; } return formateString; }

port.write(uniform3String(int(map(journeyScr.getPos(),915.245,11 13.3,0,999))) + "\0"); print(command[count]); } else { port.write(uniform3String(int(map(angleScr.getPos(),915.245,1113 .3,0,360)) - 30) + "\0"); print(command[count]); println(uniform3String(int(map(angleScr.getPos(),915.245,1113.3, 0,360)))); if(count == 1) { } } circleSize[count].target(120); } }

52


武汉科技大学本科毕业论文 //File Integrator.pde //reference from Ben Fry 《Visualizing Data》 class Integrator { final float DAMPING = 0.5f; final float ATTRACTION = 0.2f; float value; float vel; float accel; float force; float mass = 1; float damping = DAMPING; float attraction = ATTRACTION; boolean targeting; float target; Integrator() { } Integrator(float value) { this.value = value; } Integrator(float value, float damping, float attraction) { this.value = value; this.damping = damping; this.attraction = attraction; } void set(float v) { value = v; } void update() { if (targeting) { force += attraction * (target - value); } accel = force / mass; vel = (vel + accel) * damping; value += vel; force = 0; } void target(float t) { targeting = true; target = t; } void noTarget() { targeting = false; } }

53


武汉科技大学本科毕业论文 //File Table.pde //reference from Ben Fry 《Visualizing Data》 class Table { int rowCount; String[][] data; Table(String filename) { String[] rows = loadStrings(filename); data = new String[rows.length][]; for (int i = 0; i < rows.length; i++) { if (trim(rows[i]).length() == 0) { continue; // skip empty rows } if (rows[i].startsWith("#")) { continue; // skip comment lines } // split the row on the tabs String[] pieces = split(rows[i], TAB); // copy to the table array data[rowCount] = pieces; rowCount++; // this could be done in one fell swoop via: //data[rowCount++] = split(rows[i], TAB); } // resize the 'data' array as necessary data = (String[][]) subset(data, 0, rowCount); } int getRowCount() { return rowCount; } // find a row by its name, returns -1 if no row found int getRowIndex(String name) { for (int i = 0; i < rowCount; i++) { if (data[i][0].equals(name)) { return i; } } println("No row named '" + name + "' was found"); return -1; } String getRowName(int row) { return getString(row, 0); } String getString(int rowIndex, int column) { return data[rowIndex][column]; } String getString(String rowName, int column) { return getString(getRowIndex(rowName), column); } int getInt(String rowName, int column) { return parseInt(getString(rowName, column)); } int getInt(int rowIndex, int column) { return parseInt(getString(rowIndex, column)); } float getFloat(String rowName, int column) { return parseFloat(getString(rowName, column)); } float getFloat(int rowIndex, int column) { return parseFloat(getString(rowIndex, column)); } void setRowName(int row, String what) { data[row][0] = what; } void setString(int rowIndex, int column, String what) { data[rowIndex][column] = what; } void setString(String rowName, int column, String what) { int rowIndex = getRowIndex(rowName);

data[rowIndex][column] = what; } void setInt(int rowIndex, int column, int what) { data[rowIndex][column] = str(what); } void setInt(String rowName, int column, int what) { int rowIndex = getRowIndex(rowName); data[rowIndex][column] = str(what); } void setFloat(int rowIndex, int column, float what) { data[rowIndex][column] = str(what); } void setFloat(String rowName, int column, float what) { int rowIndex = getRowIndex(rowName); data[rowIndex][column] = str(what); } }

54


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.