Design of Six-legged Robot Control System Based on Single Chip Microcomputer
本设计主要是基于单片机的六足机器人控制系统设计,综合分析六足机器人的结构、步态和控制算法,结合云端服务器、WIFI 技术、蓝牙技术、语音识别技术和手势识别技术进行多种控制模式的设计,并提出不同应用场景的不同构建方案。
本系统的硬件设计分为主控板和舵机控制板两部分。主控板主要负责各种控制模式的数据处理和显示,舵机控制板主要负责舵机转动角度的控制,两板通过串口进行数据的交互。主控制板采用 STM32F103VET6 芯片,舵机控制板采用 STM32F103R8T6 芯片,两者都基于 ARM 的 Cortex M3 内核进行设计的。主控制板的硬件电路设计主要有启动电路、晶振电路、下载电路、复位电路、稳压电路以及各个模块接口电路。在 Altium Designer16 软件中进行原理图的绘制和 PCB 的绘制,打样后进行焊接并完成整体的测试。
本系统的上位机主要是手机 APP,其开发环境是 Android Studio,采用 C#作为云端开放平台语言,Java 语言作为移动客户端设计语言,通过 Java 语言的编写实现手机客户端的数据接收和发送,最终实现基于云端和蓝牙的控制系统上位机板块的设计。本系统下位机的软件设计是在 Keil5 编程环境下进行的,参考 STM32F1 的手册和各个模块的数据手册进行程序的编写,最终实现云端控制、蓝牙控制、语音控制和手势控制这四种控制系统设计。
关键词:六足机器人;PWM 调节;单片机;云端
第一章 绪 论
当今最火热的人工智能、大数据这些新概念都是机器人发展的基础,也是人类社会未来的发展趋势。直至今天,人类研制出各种机器人,它们针对特定的领域和特定的场景有着特定的功能。比如工厂里的机械臂、高空作业的无人机以及刑侦上的排爆机器人等等。这些机器人的出现,促进了人类社会的发展。事实上,除了这些特定场景外,机器人逐渐进入人们的日常生活中。比如现在市场上特别流行的扫地机器人、格斗机器人以及跳舞机器人等等。我们可以看到,随着生活水平的提高,人们非常希望机器人能改善自己的生活质量,能让自己的生活更加便利。而这一切的技术都体现在设计什么结构的机器人以及对机器人进行怎么样的控制。
在机器人的结构上有很多种形态,目前移动类的机器人主要是三种形态:轮式、履带式和足式的。而目前足式机器人主要有分为两条腿、四条腿、六条腿和八条腿的。本系统主要采取六条腿的机械结构,因此对六足机器人的结构和步态的分析具有一定的意义,这是本系统分析的重点之一。确定结构后,剩下就是通过什么样的方式来控制。因此,设计机器人的控制系统也具有很大价值。
早在上几个年代,国外就已经开始展开足式机器人的研究。1968 年,美国的研发出了可以多步态行走的机器人。随后,美国于 1983 年研发出了六足行走机器人,日本于 1985 年研发出了拥有 8 个自由度的机器人。近十年来,美国和日本的机器人技术发展比较迅速。目前,美国的波士顿公司做的机器人比较受欢迎,他们公司做的最好是双足人型机器人和四足的机器狗。人型机器人不仅可以双脚跳,奔跑,甚至可以跟人一样后空翻,稳定性非常好。机器狗是高度模仿现实生活中的狗,不仅可以开门,奔跑还可以自主上下楼梯,其稳定性和智能性也是业内领先的。直至今日,机器人开始渗入我们生活的每一个角落,这也就促进了机器人技术的快速迭代与更新。随着机器人技术的发展,多足机器人在特定环境下的避障、搜索目标、规划路线等问题逐渐成为研究工作者的研究重点。
虽然国内的机器人发展的时间比较短,但经过各方面的努力,国内对机器人的研究已进入自主研发阶段,尤其是上海交大、哈工大、华中科大和中科大等知名院校的研究团队均已处于国内领先水平。早期,国防科技大学研制出了蛇形机器人、华中科技大学出了多足步行机器人以及上海交通大学的“章鱼”六足机器人。近期,上海交通大学研制出了可自主爬楼梯的六足机器人和中国科学技术大学研制出了人型机器人“佳佳”。除了科研领域,国内机器人的发展还在消费领域、工业领域和教育领域有了快速的发展。在教育领域,北京的公司研发了一款全地形的六足的机器人 HEXA,这款机器人可以用于小孩子的编程教育,这对我国教育行业有一定推动作用。正是因为这样,国内各领域纷纷加大对机器人技术开发和研究的力度,大力去扶持机器人行业的发展,同时,各领域从事机器人研究的科研工作者正从不同方向去研究探索机器人技术,进而快速地推动了机器人技术的迅猛发展,使其向着更加高深的领域发展。
传统的移动机器人结构有轮式的、履带式的以及足式的。轮式机器人的轮式结构决定了它在平坦的地面运行比较快且比较稳定,但其缺点是在崎岖的路面运动效率非常低下。履带式机器人是对轮式机器人的进一步升级,可以在崎岖地面上缓慢运动,但是由于履带式机器人的机动性比较差,因此在崎岖地面车身容易晃动,依然无法解决稳定性的问题。足式的机器人是仿造昆虫结构进行改良而成。这类机器人不仅可以在崎岖地面进行灵活运动,还可以像昆虫一样在很多复杂环境进行工作,比如爬楼梯等等。在对机器人结构设计的前期,我们做了大量的分析,最终决定采取类似蚂蚁结构的六足机器人。确定机器人的机械结构后,本系统还对六足机器人的步态进行了分析以及结构稳定性的分析,最终确定采用三角步态的形式进行机器人基本动作的设计。因此,机械结构的分析和步态的设计是本设计的主要内容之一。
除了结构上的分析和步态上的设计,本系统还对多种控制模式进行分析和设计。传统的控制系统一般都是基于 PC 端进行控制,而很少有基于云端的控制系统设计和近距离人机交互控制系统设计。随着云端技术和各种传感器的发展,本设计希望在传统的控制模式的基础上进行进一步创新。本设计前期对传统的控制模式进行了大量的资料收集,然后对这些传统控制模式进行分析和学习,最终实现基于云端的远程控制、手机客户端蓝牙控制和近距离的人机交互控制(语音识别交互控制和手势识别交互控制)。因此,对六足机器人的控制系统的设计也是本设计的主要内容之一。
最后,在整体系统稳定性调试和测试成功后,本系统还针对现实生活中不同应用场景提出不同的应用方案,这也是本设计的意义所在。本设计对六足机器人的分析和设计是为后续的不同应用场景下使用的前提,而为了更好的扩展性,本设计在完成基本的控制系统设计的基础上提出较为实用的应用方案。为了实现整体系统的扩展简便性,本系统的数据采集和检测部分并不固定某个传感器或某个检测模块,而是在 PCB 板设计的时候预留出相关通信接口,然后对各种传感器和检测模块进行模块化配置,这也是本设计的创新之处。基于这样的设计,后续的不同应用场景下只需要进行不同检测模块或者不同传感器的模块化搭配即可,大大降低用户的使用门槛,提高了系统的简便性。
第二章 系统方案设计
远程控制方案设计
远程控制方案是基于云端的控制方案,是结合最新的云端技术进行开发的方案。本系统的舵机控制系统与控制模式选择的控制系统是分开来设计的,因此在对远程控制方案进行设计的时候,我们只需要关注 WIFI 模块如何与云端服务器连接,手机 APP 如何与云端服务器连接,手机 APP 如何与主控制板的 WIFI 模块进行数据交互。
该六足机器人结构上搭载的 WIFI 模块与主控板的主控芯片是用串口接口进行数据交互的,然后根据查阅相关 AT 指令,发送或接收相关 AT 指令可以配置 WIFI 模块,配置完成后便可使用无线透传模式来传输数据。数据的流动过程是先从手机 APP 开始的,手机 APP 通过 WIFI 将数据发送到远端服务器,然后云端服务器做转发的作用,将数据通过互联网发送到六足机器人所在的局域网的路由器,然后路由器再一次进行转发将数据发送到六足机器人机械结构的 WIFI 模块上,WIFI 模块通过串口将数据传输到主控芯片,主控芯片对数据进行解析,解析完成后发送相关的动作组指令到舵机控制板,最终实现机器人相关动作组动作。
远程控制方案是结合当前最新的技术进行开发的,只要六足机器人所在区域有网络,用户的手机在世界任何地方通过手机流量即可实现远程控制。再加上六足机器人搭载视频模块可以进行远程监控,因此可以实现真正的远程控制,这为后面的运用在不同场景下的方案设计起到很大的作用。当然,这也是本设计在控制系统方面重点要设计的内容。
图 2-1 远程控制方案流程图
蓝牙控制方案设计
蓝牙控制方案是在调好的舵机控制板的基础上进行进一步的控制系统设计。本人独立完成手机端 APP 的设计,并实现手机客户端蓝牙跟舵机控制板的蓝牙模块进行通信的功能,然后舵机控制板的蓝牙模块收到手机端的数据后再把数据通过串口通信传输给舵机控制板,从而实现对应的点控或者联动控制。与此同时,通信是双向的,因此舵机控制板在收到数据后可以把数据通过蓝牙模块发送到手机客户端,从而实现应答功以保证数据传输的稳定性。除此之外,手机客户端还可以采集舵机控制板上的电源模块的电量,进而实现电量的实时显示和及时提醒用户进行充电。
蓝牙控制系统的设计方案非常适用于没有网络的地方,这样的端对端的短距离的无线通信方式不仅可以脱离传统的红外手柄控制,而且可以大大提高在近距离无网络状态下的控制稳定性。
图 2-2 蓝牙控制方案流程图
人机交互方案设计
本系统在实现远程云端控制和短距离蓝牙控制的基础上再添加了语音识别和手势识别功能,一方面是综合考虑控制系统方案的稳定性,以防止在远程云端和短程蓝牙都出故障的情况下系统仍然可以正常工作,另一方面是可以大大提高用户的人机交互效果,从娱乐的角度来说很大程度上提高了用户的体验效果,丰富了机器人的人机交互功能。实际上,通过语音识别和手势识别的控制系统方案设计,系统的稳定性可以一定比例的提高,这也是从整体的稳定性考虑的。
语音识别控制系统方案是通过语音识别芯片进行语音的采集,然后将采集的信息转换成文本的形式,再通过控制芯片进行转换,控制芯片将数据传输给主控芯片,主控芯片对数据进行解析处理再传给舵机控制板,舵机控制板对信息解析并执行对应的动作,从而实现对六足机器人的语音识别控制。
手势识别控制系统方案是通过手势传感器进行手势数据的采集,将人的手势动作采集后进行解析,然后把解析的结果传送到主控芯片,主控芯片对数据进行处理,最后传给舵机控制板,从而让相关动作组发生对应的动作。
人机交互方案非常适用于远程云端和短程蓝牙出故障的情况或者娱乐性比较强的场合,这样的人机交互方式不仅可以提高系统的稳定性,而且在人机交互上大大提高了用户的体验感觉。
图 2-3 人机交互方案流程图
系统运用方案说明
本系统硬件部分预留出模块或传感器的通信接口,采用模块化配置的方式来设计检测部分,用户可以根据不同的任务,不同的场景进行不同的模块搭配,通过不同的传感器进行数据的采集,可以实现温湿度、有毒气体、可燃气体、生命图像、坐标位置等等数据的采集,从而达到不同场景不同功能。
崎岖地形探测方案
本系统在不添加其他模块配置的情况下即可实现复杂地形的探测功能,因为该六足机器人因为本身的六足结构可以非常灵活在崎岖地形运动,然后其本身还带有远程视频模块,因此可以实现远程视频实时显示并可以通过云端实现远程的控制。所以,只要是在一些有网络但道路崎岖的地形,都可以让该六足机器人进行现场的探测,用户可以在远程进行现场的观察。
图 2-4 崎岖地形探测方案流程图
震后救灾搜寻方案
该六足机器人如果需要运用于地震后的救灾搜寻工作中则需要进行个别传感器和模块的搭配配置。比如生命探测模块、GPS 模块和声音探测器等等。通过生命探测器可以对地震后的地面进行生命的搜寻,由于它可以灵活行走在一些工作人员无法进入的地方,所以很大程度上提高了搜寻的效率及面积。一旦检测到有生命,马上通过 GPS 模块进行定位,然后通过云端把坐标发送到工作人员的手机客户端,从而实现精确的灾后生命搜寻定位功能。除此之外,还可以通过声波的检测来判断是否有生命。当然,还可以通过它来进行食物的运输或者仪器的运输。
图 2-5 震后救灾搜寻方案流程图
科研探险勘测方案
该六足机器人如果需要运用在科研上进行探险勘测,则需要根据工作人员需要进行添加特定的检测模块。比如,在一些辐射比较大的地方或者地形险恶的地方,科研人员无法进入,则可以让该六足机器人进入进行勘测,然后科研人员远程进行现场的勘测。在此基础上科研人员只要搭配对应的采集模块或者对应的传感器即可,比如要检测某矿物质,则搭配检测该矿物质的传感器。
图 2-6 科研探险勘测方案流程图
工厂巡检预警方案
该六足机器人如果想要运用在工厂上进行远程巡检预警,可以在其身上搭载对应的巡检检测模块,尤其是一些常年存在有毒气体的化学工厂。一旦运用了该六足机器人,工作人员可以大大提高自己的身体健康质量。工作人员不需要实地去考察,他只需要在远程对六足机器人进行控制,然后手机客户端可以实时显示现场的环境各种指标以及调用摄像头进行现场的图像显示。
图 2-7 工厂巡检预警方案流程图
第三章 硬件系统设计
足式机器人一般是两条腿、四条腿、六条腿和八条腿,两条腿和四条腿平衡问题不太好解决,而八条腿不够灵活,因此采用六条腿的结构。六腿结构的机器人动作的时候每次都有三条腿作为支撑点,因此比较稳定。本系统的结构分为躯体和肢体两个部分,躯体主要用来盛放控制板、传感器和电池,而肢体主要分为六个腿,每条腿有三个自由度,也就是每条腿有三个舵机,因此整个系统需要对十八个舵机进行协调控制。本系统的机械结构并非自主设计的,因为主要的任务不是机械结构的设计,所以采取定制机械结构的方法来实现机械机构的搭建。整体的机械结构图如下图所示,每个腿都有三个舵机,一共 6 条腿,最后组成整个六足机器人的整体机械结构。
图 3-1 机械结构设计
主控制板采纳 STM32F103VET6 作为主控芯片是因为该芯片性能较好,而且拥有多种通讯接口,比如 USART、IIC 和 SPI 等等。对于舵机控制板采用的是 STM32F103RBT6,该芯片也是 STM32F1 系列的,但由于舵机控制板仅仅需要对 18 个舵机进行控制以及预留 3 个串口进行其他通信,因此 STM32F1RBT6 完全足够。舵机控制板主要是通过 PWM 进行占空比的调节从而实现舵机的角度调节,根据舵机转动的角度,再进行 18 个舵机的整体协调来实现对应的动作。当然,因为舵机控制板需要对动作进行存储,因此还用到了该芯片的存储器,其 128K 的程序存储器已经足够。主控板跟舵机控制板的通信是通过串口进行通信的,之所以分开而不采用一个板子是因为考虑到后期的功能扩展性以及考虑程序编写的方便性。这样分开后,只要两个板子直接设好对应的波特率以及写好通信的协议即可实现两者之间的数据通信,项目的开发效率可以大大提高。
图 3-2 主控芯片引脚功能
图 3-3 控制模块正面 图 3-4 控制模块反面
数字舵机的选型
本系统采用的是 LDX-218 的数字舵机,只要发送一次信号就能锁定角度不变,这也就降低了程序编写的难度。因为模拟舵机须要持续发送 PWM 才能保持锁定角度。除此之外,模拟舵机还存在精度较差、线性度不好的缺点,而数字舵机可以提高控制精度、提高线性度以及响应速度比较快。该数字舵机工作电压是 6-8.4V,转动的角度是 180 度,对于六足机器人来说已经足够。该舵机的 PWM 调节角度周期是 20ms,且成线性关系,具体如图 3-6。
本系统采用 ATK-ESP8266 WIFI 模块,该模块是串口 WIFI 模块,只要通过串口发送 AT 指令即可对 ATK-ESP8266 WIFI 模块进行配置。该模块有 AP 模式、STA 模式和 AP+STA 模式,电路的设计主要是在接口电路的设计,将预留的串口接口与 ESP8266 芯片对应引脚连接,最终实现两者之间的正常通信。
图 3-7 WIFI 模块接口电路
蓝牙模块接口电路设计
蓝牙模块是基于 TLSR8266F512 芯片进行研发的低功耗 BLE 射频模块,其通信方式是通过串口发送 AT 指令来通信,因此使用非常方便。具体的配置流程只要参考 AT 指令及模块使用手册发送相关 AT 指令即可建立通信。本接口电路也是需要把串口接口留出来,其他的就是 CC2540 芯片的基本电路设计,比如复位电路、晶振电路等等。
图 3-8 蓝牙模块接口电路
语音播放芯片外围电路设计
语音播放模块负责语音输出,比如六足机器人的语音输出和音乐的输出。本设计一开始采用的是语音合成技术,就是将文本输出为语音,但是这类方法一方面存在离线库覆盖量不够大而导致有些字无法合成的缺点,另一方面的缺点是合成的语音听起来非常别扭,语速语调都存在一定的缺陷。综合考虑之后,我们觉得采取直接播放音频的方式进行语音播放。这样的方式可以规避语音合成技术存在的两个缺陷。用户只要把自己想要播放的音乐以及录好的语音音频存到 SD 卡里面,然后对语音播放模块发送相关指令即可实现相关语音的播放或者相关音乐的播放。
为了方面扩展与通信,本系统采用的语音播放模块依然是通过串口进行通信,这样的处理方式不仅非常适合本系统,而且对以后的扩展性也是非常重要的。语音播放模块是直接与舵机控制板进行连接的,这样可以实现在动作的同时进行语音播放,两者并不会产生冲突。这也是为什么把舵机和多种控制模式的数据处理分开。只要主控制板发送指令给舵机控制板,舵机控制板在执行相关的动作时也会通过串口发送指令,这样就可以实现语音的播放功能。
本电路主要是语音播放芯片 SYN6288 的外围电路以及通信接口的设计,具体的电路图如下。
图 3-9 语音播放芯片外围电路
语音识别芯片外围电路设计
本系统采用的是 YS-V0.7 模块,该模块集成 STC11L08XE 和 LD3320 两款芯片。LD3320 芯片主要负责语音的采集和识别,通过这个芯片的处理可以将采集到的语音信息转换成文本的形式,然后将这个处理过的信息发送到 STC11L08XE 芯片。STC11L08XE 芯片主要负责信息的处理和串口转发功能。本芯片的外围电路设计是需要跟 STC11L08XE 芯片进行通信,语音采集后是先经过 STC11LO8XE 芯片处理后才与主控板进行通信。本电路的设计主要是 LD3320 芯片的外围电路,具体如下。
图 3-10 语音识别芯片外围电路
手势识别接口电路设计
人机交互模式需要用到手势的识别技术,因此需要搭配手势识别传感器。本系统主要采用 ATK-PAJ7620 和 APDS-9960 两款手势识别传感器。这两块手势识别传感器都是通过 IIC 协议来通信的。这两款传感器可以识别 9 种手势,其中本系统主要用到的有上、下、左、右、前、后这 6 种手势。本电路设计是对外围电路和接口电路的设计。
图 3-11 手势识别接口电路
一开始本系统打算采用常见的智能小车摄像头进行视频监控,但是这类摄像头基本不开源,这对本系统的开发造成了很大的麻烦。而市面上开源的摄像头模块基本都不支持远程传输,因此不能进行云端的控制。经过综合分析,最后决定定制威视达康这家公司的摄像头。这个摄像头可以进行远程的监控,还留出了 SDK 接口给开发者进行调用,因此非常有利于项目开发。这个摄像头可以自由地上下左右旋转,基本可以全方位覆盖监控范围,还支持夜视、录像等功能。
图 3-12 摄像头
红外传感器
本系统如果需要进行红外避障的时候可以考虑搭配此传感器进行避障。该传感器对光线的适应能力相对较强。通过发射红外线并接收反射的信号来判断前面是否有障碍物。本模块属于数字量开关,数据处理后出来的只是高低电平的数字量。因此,主控板只要用一个引脚进行采集高低电平即可判断前方是否有障碍物。
声音传感器
声音传感器主要是根据声音的震动原理来判断声音的有无或者特定声音频率的声音,正是这样的原理,本系统可以使用在抢险救灾的工作中,通过声音传感器来判断灾后地区是否还有人。这个传感器输出形式是数字量,也就是高低电平,有声音的时候高电平,没有声音的时候低电平。因此使用非常方便,只要主控板用一个引脚进行采集即可实现声音的判断。这也就是让六足机器人在听觉上有了一定的补充。
光敏传感器
本系统如果在某些场合需要进行采光判断,那么这个传感器无疑是最好的选择。该光敏传感器能够监测周边的光照强度,还能感知光源方向。此传感器也是开光量的输出形式,因此通信方法和上面的传感器类似。
超声波传感器
本系统如果需要进行距离测试或者避障,则需要搭配超声波传感器。此传感器采用 IO 口 TRIG 触发测距,传感器自动发送 8 个 40khz 的方波,实时监测有返回的信号,则产生高电平,且持续一段时间。
温湿度传感器
本系统可以搭配 DHT11 传感器进行环境的温湿度检测。DHT11 传感器有四个引脚,其中电源、地、信号线和一个悬空的。信号的传输是单总线的形式进行串行传输。如果使用该传感器,那么其硬件电路的接口电路设计如下图所示。
图 3-13 DHT11 接口电路
气体传感器
本系统在对环境气体进行检测时可采用不同的气体传感器,比如需要检测 CO 的话则选择 MQ-7 CO 传感器,比如检测空气质量情况则选择 MQ-135 模块。用户可以根据自己的检测需求搭配对应的传感器。
图 3-14 MQ-7 传感器电路
总体介绍
本芯片是采用的 STM32F103VET6,结合本系统的需要,留出了 5 个串口接口、2 个 SPI 接口、6 个 IIC 接口,因此在硬件电路设计的时候要设计这些通信接口电路。同时还有基本的最小系统电路,比如复位电路、下载电路、降压电路、启动电路等等。
晶振电路
本电路采用的 8M 无源类型的晶振,稳定性比有源类型的要好,电路中还有两个 20pF 的负载电容。这个电路是为芯片提供时钟频率,因此非常关键。
图 3-17 降压电路
IIC 接口电路
IIC 接口电路主要是用来接手势识别传感器,本系统进行硬件设计的时候考虑到以后的系统扩展性,预留了 6 个 IIC 接口电路,而本系统的 IIC 通信是通过软件进行模拟的,因此任何两个管脚都可以进行通信,只是本系统方便模块或传感器的连接而预留出接口,具体的接口电路如下图。
图 3-18 IIC 接口电路
SPI 接口电路
SPI 接口电路主要是考虑到以后的系统扩展性而预留的接口,因为有些模块的通信方式是通过 SPI 进行通信的。目前来说,本系统的控制部分是不需要用到此接口,但是本系统在进行硬件电路设计的时候预留出来方便不同应用场景的模块化配置,具体的接口电路如下图。
图 3-19 SPI 接口电路
USART 接口电路
USART 接口电路是本系统运用最多的接口电路,也是最常见的通信接口电路,因此在硬件电路设计的时候对 USART 接口电路的设计尤为关键。虽然 USART 的通信方式只要三根线即可,也就是 TX,RX 和 GND 这三根线,但是考虑到有些模块本身的引脚有多余的悬空脚,因此会出现有 6 个引脚的模块或者 4 个引脚的模块。综合考虑硬件电路的简便和美观,本系统采用针对性的接口设计,这部分的电路是固定的,以后基本不会改动。比如,USART1 用来跟舵机控制板通信,USART2 用来与语音识别模块通信,USART3 用来与 WIFI 模块通信等等。经过分析和设计,具体的接口电路如下图。
图 3-20 串口接口电路
本系统采用 Altium Designer 16 进行 PCB 的设计。PCB 的绘制的难点主要在于 PCB 的布线布局。前期我们学习他人的原理图,然后开始绘制本系统的原理图。绘制完原理后就开始绘制部分元件的封装。最后把绘制好的原理图导出到 PCB 中,剩下的就是布局布线的问题了。
本系统的 PCB 板在设计面积大小时严格测过六足机器人的躯体平台的面积大小,最终确定板子的面积大小为 80.255mm*117.255mm。除此之外,考虑到系统的美观,主控板 PCB 和舵机控制板 PCB 两者采用铜柱相连的方式,因此在 PCB 设计的时候要留出铜柱的孔。
图 3-21 PCB 板三维效果 图 3-22 PCB 板二维效果
第四章 软件系统设计
图 4-1 Keil uVision5 编译环境
图 4-2 Visual Studio 开发平台
通过大量的调查和分析,我们知道六足机器人一般有三足步态、四足步态和波动步态。综合考虑本系统的机械结构的稳定性,决定采用三足步态进行设计。本系统首先对步态进行分析,然后自主设计出前进、后退、左侧走、右侧走、左转、右转这六个基本步态,然后其他的动作都是基于这六个基本步态进行改变和组合而成,因此对基本步态的分析和设计具有很大的意义。下面将展示两个典型步态的设计流程,其余的步态设计流程类似。
首先,本系统先对前进步态进行设计。综上可知,本系统采用的是三足步态的设计,机器人的运动过程中由一边前腿、后腿与另一边中腿构成的两部分,一部分的三条腿先动作,另一部分的三条腿做支撑,正是这样的三足步态交替动作才能实现机器人的稳定移动,具体的流程如下图所示。前进的步态有 9 足步态图,从 A 到 I,其中黑色的空心圆的腿代表的是支撑的动作,也就是这条腿的状态是与地面接触的,而红色的斜杆的腿代表抬起动作的腿,也就是这条腿的状态是离开地面的。
其次,本系统对右转步态进行设计。具体的设计流程如下图,同样是黑色的空心圆的腿代表的是支撑的动作,也就是这条腿的状态是与地面接触的,而红色的斜杆的腿代表抬起动作的腿,也就是这条腿的状态是离开地面的。
图 4-4 前进步态动作设计
图 4-5 右转步态动作设计
六足机器人的动作组编写是基于上述的步态设计来实现的。动作组编写可以通过上位机进行调试,而上位机的编写是根据 PWM 调节占空比的原理来编写的,因此采用上位机可以快速方便地对机器人的动作进行编写,这非常有利于提高开发的效率。通过上位机来找到合适的值,然后程序里面可以参考这个值来进行编写,这样的方法非常有利于机器人动作的编排。
图 4-6 上位机界面舵机 图 4-7 上位机界面
上位机的程序编写
上位机主要是手机 APP 的编写,然后 APP 跟下位机协商好通信协议即可实现两者之间的数据交互,以下两张图是手机 APP 的界面图。
图 4-8 手机 APP 首页 图 4-9 手机 APP 控制界面
手机 APP 是用户管理和控制机器人的人机交互的界面,它的主要功能是控制机器人端的各种动作,各种参数的输出与录入、机器人端与云端服务器连接状态的显示等等。互联网的主要功能是充当设备端与云端监控平台数据传输的媒介,主要负责将设备端的传感器数据与自身系统的参数回传到远端监控平台,同时,也可以将云端监控平台的控制命令发送到设备端。设备端即是六足机器人本身,其主要负责相关动作的执行以及特定环境下的信息采集工作,并对数据进行处理加工后,通过网络反馈到远端检测平台,还有负责接收来自云端的控制命令等等。
下位机的程序编写
远程控制模式是基于云端进行远程控制的,六足机器人身上搭载 ESP8266WIFI 模块,通过 WIFI 连上云端服务器,然后手机也联网,通过这样的方式来实现远程控制。这一块的程序是实现 STM32F103VET6 芯片与 WIFI 模块的数据交互,并配置相关的 AT 指令来设置 STA 模式中的 TCP-CLIENT 模式。
整个工程里面有两个文件是与 WIFI 相关的,一个是 common.C 文件,另一个是 WIFISTA.C 文件。下面是 WIFI 配置的流程图。
在 common.C 文件里面首先是需要写好路由器的名称和密码。具体设置如下:
const char* WIFISTA_ssid="AAAA"; //连接路由器
const char* WIFISTA_encryption="wpawpa2_aes"; //连接加密方式
const char* WIFISTA_password="88888888"; //连接密码
然后进行 WIFI 模块的连接,程序如下:
while(ATK_8266_SEND_CMD("AT","OK",20)) //检查WIFI模块是否在线
{
ATK_8266_quit_trans();//退出透传
ATK_8266_SEND_CMD("AT+CIPMODE=0","OK",200); //关闭透传模式
Show_Str(40,55,200,16,"未检测到模块!!!",16,1);
delay_ms(800);
LCD_Fill(40,55,200,55+16,BLACK);
Show_Str(40,55,200,16,"尝试连接模块...",16,1);
}
while(ATK_8266_SEND_CMD("ATE0","OK",20));
连接一旦成功后,将进入这个函数:ATK_8266_WIFISTA_TEST(),并由此跳入到 WIFISTA.C 文件。进入 ATK_8266_WIFISTA_TEST()函数后,首先需要做的是配置工作模式为 STA 模式,配置完成后需要输入云端服务器的 IP 地址和端口号来连接。
这样,WIFI 的配置过程就完成了,建立了连接后剩下的就是调用对应的串口函数进行数据的发送和接收,然后分析处理接收到的数据,处理结束后传输对应的机器人动作指令来实现动作。
图 4-10 WIFI 配置流程
通信协议的说明
本系统的云端通信协议具体如下表。通信协议的帧头主要是方便识别数据包,下位机只有接收到 0xDA 帧头的数据包才会执行相应的动作,而手机 APP 接收到 0XDB 才会确定对方收到数据。ID 主要是用来标识设备的 ID,方便以后扩展用。包序号是用来应答的,接收到对应的数据然后应答时也发送对应的包序号,以此来确保数据的交互正确性。CRC16 校验是用来校验数据包是否发生损坏,如果校验不通过,则要求重发,该数据包丢弃。
帧头 | ID | 类型 | 包序号 | 数据长度(4Byte) | 数据 | CRC16 校验(2Byte) | 结束符 |
---|---|---|---|---|---|---|---|
0xDA | 0x00 | 0x01 控制指令 | 0x00-0xFF | 0xFF 0xFF | |||
0xDA | 0x00 | 0x03 心跳包 | 0x00-0xFF | 0xFF 0xFF |
帧头(1Byte) | 机器人 ID(1Byte) | 类型(1Byte) | 包序号(1Byte) |
---|---|---|---|
0xDB | 0x00 | 0x00(ACK) | 0x00-0xFF |
本系统的舵机控制板与主控制板的通信协议说明如下表,其中指令有 0x06 代表执行动作组指令,后面的参数 0x08 0x01 0x00 代表 8 号动作组运行 1 次,0x07 代表停止指令,停止指令后面无需写参数。
帧头 | 数据长度 | 指令 | 参数 |
---|---|---|---|
0x55 0x55 | 0x05 | 0x06 | 0x08 0x01 0x00 |
0x55 0x55 | 0x05 | 0x07 | 无 |
本系统的蓝牙模块与手机 APP 的通信协议与上述的通信协议类似,其中指令有 0x06 代表执行动作组指令,后面的参数 0x08 0x01 0x00 代表 8 号动作组运行 1 次,0x07 代表停止指令,停止指令后面无需写参数,在此基础上还添加了查询舵机控制板电量的指令,其手机端发送格式为 0x55 0x55 0x02 0x0F,舵机控制板的应答格式如下表,参数中的 0x4C 代表电压的低八位,0x1D 代表电压的高八位。
帧头 | 数据长度 | 指令 | 参数 |
---|---|---|---|
0x55 0x55 | 0x04 | 0x0F | 0x4C 0x1D |
蓝牙控制 APP 设计
本系统采用的蓝牙模块也是通过串口进行通信的,其配置过程和 WIFI 类似,也是通过发送相关的 AT 指令便可配置蓝牙模块。六足机器人可以用无线的方式与手机 APP 进行数据交换。通过对应的 API 连接到蓝牙设备,从而实现点到点和多点无线功能。综合考虑系统的稳定性,我们将蓝牙模式直接连接在舵机控制板上,这样可以保证主控制板出现故障是六足机器人依然可以正常动作。之前在舵机控制板上有预留串口接口,因此非常适合直接与蓝牙模块进行串口通信。控制板接上蓝牙模块后对相关 AT 指令配置后便可通信。本部分主要介绍蓝牙 APP 的程序编写过程。
首先,配置启动蓝牙,检查当前模块是否已经启用蓝牙。其次,连接六足机器人,为了与六足机器人身上的蓝牙模块进行连接,我们需要协调服务器端和客户端机制,让 APP 开放服务器套接字,而机器人端的蓝牙模块发起两者的连接。最后是接发收数据,成功连接后,APP 会有一个已连接的 BluetoothSocket 值。这样的现象表明可以进行数据的共享了。
语音识别部分主要是两款芯片,一款是语音识别芯片 LD3320,另一款是数据处理芯片 STC11L08XE。LD3320 芯片的各种操作都是通过配置相关的寄存器来实现的,寄存器读写操作有 4 种方式,分别是软件并行、硬件并行、软件串行 SPI 和硬件串行 SPI。通过查阅寄存器的手册我们可以对我们想要的功能进行配置。语音识别过程大致是先语音识别用初始化,然后写入识别列表,再者开始识别并准备好中断响应函数,最后打开中断允许位。我们这里使用的是中断的方式来进行触发,一旦收到语音才会触发中断而执行对应的程序。首先,语音识别初始化的程序如下:
void LD_INIT_ASR()
{
NLD_MODE=LD_MODE_ASR_RUN;
LD_INIT_Common();
LD_WRITE(0xBD,0x00);
LD_WRITE(0x17,0x48);
delay(10);
LD_WRITE(0x3C,0x80);
LD_WRITE0x3E,0x07);
LD_WRITE(0x38,0xff);
LD_WRITE(0x3A,0x07);
LD_WRITE(0x40,0x00);
LD_WRITE(0x42,0x08);
LD_WRITE(0x44,0x00);
LD_WRITE(0x46,0x08);
delay( 1 );
}
其次是语音识别列表的程序,截取部分如下:
uint8 LD_ASRADDFIXED()
{
uint8 k, flag;
uint8 NASRADDLEN;
#define DATE_A 20 /*数组二维数值*/
#define DATE_B 100 /*数组一维数值*/
uint8 code SRECOG[DATE_A][DATE_B] =
{ "xiao hei",\
"ting",\ "qian jin",\
......
"gei da jia chang shou ge"
};
uint8 code PCODE[DATE_A] =
{ CODE_CMD,\
CODE_STOP,\
CODE_FORWARD,\
......
CODE_SING
}; /*添加识别码*/
flag = 1;
for (k=0; k
最后呈现出来的是开始识别的程序,具体如下:
uint8 LD_Run()
{
EX0=0;
LD_WRITE(0x35, MIC_VOL);
LD_WRITE(0x1C, 0x09);
LD_WRITE(0xBD, 0x20);
LD_WRITE(0x08, 0x01);
delay( 1 );
LD_WRITE(0x08, 0x00);
delay( 1 );
if(LD_Check_ASR_b2() == 0)
{
return 0;
}
LD_WRITE(0xB2, 0xff);
delay( 1);
LD_WRITE(0x37, 0x06);
delay( 1 );
LD_WRITE(0x37, 0x06);
delay( 5 );
LD_WRITE(0x29, 0x10)
LD_WRITE(0xBD, 0x00);
EX0=1;
return 1;
}
手势识别这块的程序的难点主要在于 IIC 协议,一般来说 IIC 有两种,一种是硬件 IIC,一种是软件 IIC。经过我们的分析,我们发现 STM32 的硬件 IIC 存在一定的 BUG,所以我们决定采用软件 IIC,也就是按照手册的要求来模拟 IIC 的时序,通过模拟时序来达到 IIC 通信的效果。
首先,我们需要对手势识别模块进行初始化,PAJ7620u2_INT()这个函数就是用来进行初始化的。在这个函数里面,有两个函数非常重要,一个是 IIC 初始化函数 GS_IIC_INIT(),另一个是唤醒传感器的函数 PAJ7620u2_WAKEUP()。
具体的 IIC 时序模拟主要是在 GS_IIC_INIT()这个函数里面进行配置,这里我们不展开来论述,主要是想讲一下手势识别的处理函数。因为本系统是分开两个控制板来进行控制的,一个专门负责舵机的控制,一个负责各种传感器或者模块的控制,因此手势识别功能采用的是点动控制的形式。手势传感器负责手势的识别和信息的采集,然后通过 IIC 把信息传给主控板上的主控芯片,主控芯片进行处理,然后发送对应的动作组指令给舵机控制模块,这样即可实现手势识别功能。主控芯片的数据处理程序如下:
while(1)
{ status = GS_Read_NByte(PAJ_GET_INT_FLAG1,2,&DATA[0]);
if(!status)
{
GESTURE_DATA1 =(u16)DATA[1]<<8 | DATA[0];
if(GESTURE_DATA1)
{
switch(GESTURE_DATA1)
{
case GES_UP:
GESTURE_SEND_BUF[4]=0x01;
break; //向上
case GES_DOWM:
GESTURE_SEND_BUF[4]=0x02;
break; //向下
case GES_LEFT:
GESTURE_SEND_BUF[4]=0x07;
break; //向左
case GES_RIGHT:
GESTURE_SEND_BUF[4]=0x08;
break; //向右
case GES_FORWARD:
GESTURE_SEND_BUF[4]=0x14;
break; //向前
case GES_BACKWARD:
GESTURE_SEND_BUF[4]=0x19;
break; //向后
case GES_WAVE:
GESTURE_SEND_BUF[4]=0x0A;
break; //挥动
default:
GESTURE_SEND_BUF[4]=0x00;
break;
}
for(i=0; i<7; i++)
{ USART_SENDDATA(USART1, GESTURE_SEND_BUF[i]);
while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);
}
}
}
本系统为了保证系统的稳定性,最后决定在六足机器人身上再装载触摸屏进行触摸控制,这就确保在无线控制设备出现状况时可以维持机器人的正常动作。市面上的触摸屏一般都是采用指令集进行液晶屏的显示,而一开始设计的时候系统也是采用指令集屏,通过相关的指令配置来实现数据的显示功能。
早期的液晶屏显示界面如下:
图 4-13 早期液晶屏显示界面
后来,考虑到界面的美观和系统的稳定性,本系统决定改用 VGUS 的组态屏,这个屏主要是通过寄存器和变量存储器来进行配置,同时可以配合上位机进行图形化界面的美化处理。
图 4-14 组态屏上位机
图 4-15 组态屏界面首页 图 4-16 WIFI 模式
图 4-17 蓝牙模式 图 4-18 人机交互模式
第五章 系统整体调试
云端控制的调试过程分为两部分,分别是六足机器人的 WIFI 模块和手机 APP 的 WIFI 通信功能。首先,先调试六足机器人的 WIFI 模块。为了提高成功的概率,我们先用网络调试助手来测试。网络调试助手发送相关的指令来对六足机器人进行控制,调试成功后才进行手机 APP 的调试。
首先,我们先用 CRC16 校验工具生成 CRC 校验码,然后通过网络调试助手发送相关的指令,比如 DA 00 01 00 00 00 00 0F 61 63 74 69 6F 6E 3D 66 6F 72 77 61 72 64 3B 0C DD FF FF,其中校验码是 0C DD,最后的 FF FF 是结束符。
图 5-1 CRC 检验码生成软件
图 5-2 六足机器人模拟 TCP 客户端
5-3 接收心跳包测试 图 5-4 数据应答测试
图 5-5 Android 开发环境调试
语音识别模块先通过 USB 转 TTL 模块连接到电脑,然后打开电脑的串口助手,对着语音识别模块说对应的指令,串口助手会显示对应的英文或者指令。本系统采用语音识别的口令模式,也就是先说口令,口令识别成功才能识别下一句。如果识别到口令“小黑”,则会在串口助手打印“6F 6B”,就是“ok”的十六进制,然后再说下一句“前进”,串口助手显示:55 55 05 06 21 01 00 0D 0A,则说明识别成功。
图 5-6 串口调试助手调试
手势识别的调试先在正点原子的开发板上进行测试,我们在程序里写了如果识别到对应的手势则显示对应的英文,比如手势向上,则显示“UP”,手势向左,则显示“left”,如果手势动作和显示的内容一致,则说明调试成功。然后将程序移植到本系统中,把显示部分改为对应的串口指令便可执行对应的动作组。
图 5-7 手势向下调试 图 5-8 手势向上调试
本系统的动作组全部存在主控制板里面,然后对每一个动作组进行编号,这样就可以非常方面地进行控制。这些动作组主要是以表演为目的,因此每个动作组都配有对应的音乐。
图 5-9 六足机器人正面 图 5-10 飞翔动作
在学习方法上,我从原来的只懂得不懂就问,慢慢地学会了不懂就百度。这段时间里,我做的更多的事情不是老是问同学怎么解决问题,而是通过逛论坛、看贴吧、加 QQ 群这些互联网的形式来学习。现在很多技术都是开源的,因此互联网上有很多资料,只要你能想到的问题,都能从这里找到你想要的答案。我想,这是新时代的自学能力的一种体现吧。既然坚定了走技术这条路,就应该运用科学方法,才能够事半功倍。通过一点点的搜索,然后结合自身已经掌握的知识对当前的问题进行分析,最后把问题解决,这个过程是非常锻炼一个人的思维能力和动手能力。在这一段时间里,我学会了耐住寂寞去寻找问题的答案,习惯了沉浸在互联网的搜索引擎里。现在回头看看,真的发现自己已经成长了很多。在此,要感谢互联网,感谢互联网上那些乐意分享自己的经验的技术大牛。
在理论知识方面,我从以前的只会使用 51 单片机编写一些简单的程序,到后来我开始学着去理解指针,理解链表,然后开始学习 STM32。学习了 STM32 后开始对寄存器有了更深入的了解,对封装库也有了全新的认识。我从内核的了解开始,一点点接触各种外设,比如串口、IIC、SPI、RS485 等等。每一次学习新的东西我都对芯片的底层技术有了更进一步的认识,我的知识体系架构在不断的完善。再到后面我开始学习操作系统,开始知道操作系统的意义,这个过程让我突然顿悟了计算机的世界原来是这样的。最后的一个月,我开始去学习制作 PCB,学习如何绘制原理图,也算是在硬件知识上的一个补充吧。总而言之,这几个月的毕设让我真的收获很多很多,这为我后面的研究生生涯打下了很好的基础。
附 录
主程序:
#include "sys.h"
#include "delay.h"
#include "key.h"
#include "common.h"
#include "usart.h"
#include "usart2.h"
#include "usart3.h"
#include "uart4.h"
#include "ble.h"
#include "light.h"
#include "voice.h"
#include "music.h"
#include "gesture.h"
/********************************************
*********************************************/
//人机交互模式
intelligent_test(void)
{
=0;
VOICE_Init();
LIGHT_Init();
while (1)
{
if (UART4_RX_CNT)
{
delay_ms(100);
if ((UART4_RX_BUF[0]==0xa5) && (UART4_RX_BUF[1]==0x5a)) //普通按键返回
{
if (UART4_RX_BUF[3]==0x83)
{
UART4_RX_CNT=0;
switch (UART4_RX_BUF[8])
{
case 0x31:
return 1;
break;
case 0x32:
voice_test();
OPEN_VOICE=0;
break;//语音识别模式
case 0x33:
gesture_test();
break; //手势识别模式
case 0x34:
music_test();
break;//音乐播放模式
case 0x35:
printf("lightok");
light_test();
break;//防止跌落模式
}
}
}
UART4_RX_CNT=0;
}
}
}
//主函数
int main(void)
{
;
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
uart_init(9600); //串口初始化为9600
usart2_init(9600); //初始化串口波特率为9600
usart3_init(115200); //初始化串口3
uart4_init(115200); //初始化串口4
BLE_Init(); //蓝牙初始化
while(1)
{
if (UART4_RX_CNT)
{
delay_ms(100);
UART4_RX_CNT=0;
if ((UART4_RX_BUF[0]==0xa5) && (UART4_RX_BUF[1]==0x5a)) //普通按键返回
{
//oder_len=UART4_RX_BUF[2]; //指令的字节长度,待换成10进制
if (UART4_RX_BUF[3]==0x83)
{
//data_len=UART4_RX_BUF[6]*2; //数据的字长度,待换成16进制
switch (UART4_RX_BUF[8])
{
case 0x01: //WIFI模式
atk_8266_test();
break;
case 0x02: //蓝牙模式
if (ble_test()==1) OPEN_BLE=0;
break;
case 0x03: //人机交互模式
intelligent_test();
break;
}
}
}
}
}
}
WIFI 程序:
#include "common.h"
#include "window.h"
#include "view.h"
#include "uart4.h"
//用户配置区
//连接端口号:8086,可自行修改为其他端口.
const u8* PORTNUM="8086";
//WIFI STA模式,设置要去连接的路由器无线参数,请根据你自己的路由器设置,自行修改.
//const u8* wifista_ssid="RedmiPlus"; //路由器SSID号
const u8* wifista_ssid="AAAA"; //路由器SSID号
const u8* wifista_encryption="wpawpa2_aes"; //wpa/wpa2 aes加密方式
const u8* wifista_password="88888888"; //连接密码
//WIFI AP模式,模块对外的无线参数,可自行修改.
const u8* wifiap_ssid="lyx"; //对外SSID号
const u8* wifiap_encryption="wpawpa2_aes"; //wpa/wpa2 aes加密方式
const u8* wifiap_password="12345678"; //连接密码
///
//4个网络模式
const u8 *ATK_ESP8266_CWMODE_TBL[3]= {"STA模式 ","AP模式 ","AP&STA模式 "}; //ATK-ESP8266,3种网络模式,默认为路由器(ROUTER)模式
//4种工作模式
const u8 *ATK_ESP8266_WORKMODE_TBL[3]= {"TCP服务器","TCP客户端"," UDP 模式"}; //ATK-ESP8266,4种工作模式
//5种加密方式
const u8 *ATK_ESP8266_ECN_TBL[5]= {"OPEN","WEP","WPA_PSK","WPA2_PSK","WPA_WAP2_PSK"};
//
//ATK-ESP8266发送命令后,检测接收到的应答
//str:期待的应答结果
//返回值:0,没有得到期待的应答结果
// 其他,期待应答结果的位置(str的位置)
* atk_8266_check_cmd(u8 *str)
{
char *strx=0;
if(USART3_RX_STA&0X8000) //接收到一次数据了
{
USART3_RX_BUF[USART3_RX_STA&0X7FFF]=0;//添加结束符
strx=strstr((const char*)USART3_RX_BUF,(const char*)str);
}
return (u8*)strx;
}
//向ATK-ESP8266发送命令
//cmd:发送的命令字符串
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)
// 1,发送失败
atk_8266_send_cmd(u8 *cmd,u8 *ack,u16 waittime)
{
res=0;
USART3_RX_STA=0;
_printf("%s\r\n",cmd); //发送命令
if(ack&&waittime) //需要等待应答
{
while(--waittime) //等待倒计时
{
delay_ms(10);
if(USART3_RX_STA&0X8000)//接收到期待的应答结果
{
if(atk_8266_check_cmd(ack))
{
//printf("ack:%s\r\n",(u8*)ack);
break;//得到有效数据
}
USART3_RX_STA=0;
}
}
if(waittime==0)res=1;
}
return res;
}
//向ATK-ESP8266发送指定数据
//data:发送的数据(不需要添加回车了)
//ack:期待的应答结果,如果为空,则表示不需要等待应答
//waittime:等待时间(单位:10ms)
//返回值:0,发送成功(得到了期待的应答结果)luojian
atk_8266_send_data(u8 *data,u8 *ack,u16 waittime)
{
res=0;
USART3_RX_STA=0;
_printf("%s",data); //发送命令
if(ack&&waittime) //需要等待应答
{
while(--waittime) //等待倒计时
{
delay_ms(10);
if(USART3_RX_STA&0X8000)//接收到期待的应答结果
{
if(atk_8266_check_cmd(ack))break;//得到有效数据
USART3_RX_STA=0;
}
}
if(waittime==0)res=1;
}
return res;
}
//ATK-ESP8266退出透传模式
//返回值:0,退出成功;
// 1,退出失败
atk_8266_quit_trans(void)
{
while((USART3->SR&0X40)==0); //等待发送空
USART3->DR='+';
delay_ms(15); //大于串口组帧时间(10ms)
while((USART3->SR&0X40)==0); //等待发送空
USART3->DR='+';
delay_ms(15); //大于串口组帧时间(10ms)
while((USART3->SR&0X40)==0); //等待发送空
USART3->DR='+';
delay_ms(500); //等待500ms
return atk_8266_send_cmd("AT","OK",20);//退出透传判断.
}
//获取ATK-ESP8266模块的AP+STA连接状态
//返回值:0,未连接;1,连接成功
atk_8266_apsta_check(void)
{
if(atk_8266_quit_trans())return 0; //退出透传
atk_8266_send_cmd("AT+CIPSTATUS",":",50); //发送AT+CIPSTATUS指令,查询连接状态
if(atk_8266_check_cmd("+CIPSTATUS:0")&&
atk_8266_check_cmd("+CIPSTATUS:1")&&
atk_8266_check_cmd("+CIPSTATUS:2")&&
atk_8266_check_cmd("+CIPSTATUS:4"))
return 0;
else return 1;
}
//获取ATK-ESP8266模块的连接状态
//返回值:0,未连接;1,连接成功.
atk_8266_consta_check(void)
{
*p;
res;
if(atk_8266_quit_trans())return 0; //退出透传
atk_8266_send_cmd("AT+CIPSTATUS",":",50); //发送AT+CIPSTATUS指令,查询连接状态
p=atk_8266_check_cmd("+CIPSTATUS:");
res=*p; //得到连接状态
return res;
}
//获取Client ip地址
//IP_BUF:ip地址输出缓存区
void atk_8266_get_wanip(u8* IP_BUF)
{
*p,*p1;
if(atk_8266_send_cmd("AT+CIFSR","OK",50))//获取WAN IP地址失败
{
IP_BUF[0]=0;
return;
}
p=atk_8266_check_cmd("\"");
=(u8*)strstr((const char*)(p+1),"\"");
*p1=0;
sprintf((char*)IP_BUF,"%s",p+1);
}
//ATK-ESP8266模块测试主函数
atk_8266_test(void)
{
=0;
delay_ms(200);
while(atk_8266_send_cmd("AT","OK",20))//检查WIFI模块是否在线
{
usart_view(0x10);
atk_8266_quit_trans();//退出透传
atk_8266_send_cmd("AT+CIPMODE=0","OK",200); //关闭透传模式
delay_ms(200);
if (UART4_RX_CNT)
{
if ((UART4_RX_BUF[0]==0xa5) && (UART4_RX_BUF[1]==0x5a))
if ((UART4_RX_BUF[3]==0x83) && (UART4_RX_BUF[8]==0x16))
{
UART4_RX_CNT=0;
usart_view(0x08);
return 1;
}
}
}
while(atk_8266_send_cmd("ATE0","OK",20));//关闭回显
usart_view(0x03);
usart_run_view(0x05,1);
while(1)
{
delay_ms(10);
if (atk_8266_wifista_test()==1) //wifi sta 测试
{
atk_8266_quit_trans();//退出透传
atk_8266_send_cmd("AT+CIPMODE=0","OK",200); //关闭透传模式
break;
}
}
}
#include "common.h"
#include "stdlib.h"
#include "view.h"
#include "uart4.h"
//ESP8266 WIFI STA测试
//用于测试TCP/UDP连接
//返回值:0,正常
// 其他,错误代码
atk_8266_wifista_test(void)
{
,j=0;
char online_buf[5]= {0xDA,0x00,0x03,0xFF,0xFF};
netpro=1; //网络模式
//u8 IP_BUF[16]; //IP缓存
IP_BUF[16]="45.43.222.110"; //IP缓存
*p;
=999; //加速第一次获取链接状态
rlen=0;
constate=0; //连接状态
BEGIN1:
p=mymalloc(SRAMIN,32); //申请32字节内存
atk_8266_send_cmd("AT+CWMODE=1","OK",50); //设置WIFI STA模式
atk_8266_send_cmd("AT+RST","OK",20); //DHCP服务器关闭(仅AP模式有效)
delay_ms(1000); //延时3S等待重启成功
delay_ms(1000);
if (UART4_RX_CNT)
{
UART4_RX_CNT=0;
if ((UART4_RX_BUF[0]==0xa5) && (UART4_RX_BUF[1]==0x5a)) //普通按键返回
{
if ((UART4_RX_BUF[3]==0x83) && (UART4_RX_BUF[8]==0x04))
{
myfree(SRAMIN,p); //释放内存
usart_view(0x08);
return 1;
}
}
}
delay_ms(1000);
delay_ms(1000);
//设置连接到的WIFI网络名称/加密方式/密码,这几个参数需要根据您自己的路由器设置进行修改!!
sprintf((char*)p,"AT+CWJAP=\"%s\",\"%s\"",wifista_ssid,wifista_password);//设置无线参数:ssid,密码
while(atk_8266_send_cmd(p,"WIFI GOT IP",300)) //连接目标路由器,并且获得IP
{
if (UART4_RX_CNT)
{
UART4_RX_CNT=0;
if ((UART4_RX_BUF[0]==0xa5) && (UART4_RX_BUF[1]==0x5a)) //普通按键返回
{
if ((UART4_RX_BUF[3]==0x83) && (UART4_RX_BUF[8]==0x04))
{
myfree(SRAMIN,p); //释放内存
usart_view(0x08);
return 1;
}
}
}
delay_ms(10);
}
delay_ms(1000);
delay_ms(1000);
netpro|=1; //选择网络模式
usart_run_view(0x05,2);
usart_view(0x0b);
usart_run_view(0x06,2);
while (netpro&0X01) //TCP Client 透传模式测试
{
if (UART4_RX_CNT)
{
delay_ms(100);
if ((UART4_RX_BUF[0]==0xa5) && (UART4_RX_BUF[1]==0x5a)) //普通按键返回
{
if (UART4_RX_BUF[3]==0x83)
{
if (UART4_RX_BUF[2]==0x06)
{
if (UART4_RX_BUF[8]==0x12)
{
myfree(SRAMIN,p); //释放内存
UART4_RX_CNT=0;
usart_view(0x08);
return 1;
}
else if (UART4_RX_BUF[8]==0x11)
{
UART4_RX_CNT=0;
delay_ms(100);
usart_run_view(0x06,1);
atk_8266_send_cmd("AT+CIPMUX=0","OK",20);
sprintf((char*)p,"AT+CIPSTART=\"TCP\",\"%s\",%s",IP_BUF,(u8*)PORTNUM);
while (atk_8266_send_cmd(p,"OK",200))
{
if (UART4_RX_CNT)
{
UART4_RX_CNT=0;
if ((UART4_RX_BUF[0]==0xa5) && (UART4_RX_BUF[1]==0x5a)) //普通按键返回
{
if ((UART4_RX_BUF[3]==0x83) && (UART4_RX_BUF[8]==0x12))
{
myfree(SRAMIN,p); //释放内存
usart_view(0x08);
return 1;
}
}
}
}
atk_8266_send_cmd("AT+CIPMODE=1","OK",200);
delay_ms(100);
usart_view(0x02);//连接处理
usart_run_view(0x06,2);
goto BEGIN2;
}
}
else
{
UART4_RX_CNT=0;
i=7;
while (UART4_RX_BUF[i]!=0xff)
{
IP_BUF[i-7]=UART4_RX_BUF[i];
i++;
}
}
}
}
UART4_RX_CNT=0;
}
delay_ms(100);
}
BEGIN2:
USART3_RX_STA=0;
while(1)
{
t++;
delay_ms(10);
if(USART3_RX_STA&0X8000) //接收到一次数据了
{
rlen=USART3_RX_STA&0X7FFF; //得到本次接收到的数据长度
USART3_RX_BUF[rlen]=0; //添加结束符
DataTransfer((char*)USART3_RX_BUF,rlen-2);//解析收到的数据,将其转换成16进制指令
USART3_RX_STA=0;
// constate=atk_8266_consta_check();//得到连接状态
// if(constate!='+')t=1000; //状态为还未连接,立即更新连接状态
// else t=0; //状态为已经连接了,10秒后再检查
}
if(t==1000)//连续10秒钟没有收到任何数据,检查连接是不是还存在.
{
t=0;
constate=atk_8266_consta_check();//得到连接状态
if(constate=='+')
{
usart_status_view(1); //连接状态成功
}
else
{
usart_status_view(0); //连接状态失败
}
//发送心跳包
atk_8266_quit_trans();
atk_8266_send_cmd("AT+CIPSEND","OK",20); //开始透传
for (j=0; j<5; j++)
{
//printf(online_buf);
USART_SendData(USART3,online_buf[j]);
while(USART_GetFlagStatus(USART3,USART_FLAG_TC)==RESET); //循环发送,直到发送完毕
}
}
if (UART4_RX_CNT)
{
if (UART4_RX_BUF[8]==0x13)
{
myfree(SRAMIN,p); //释放内存
UART4_RX_CNT=0;
usart_view(0x08);
return 1;
}
}
}
myfree(SRAMIN,p); //释放内存
}
#include "delay.h"
#include "usart3.h"
#include "stdarg.h"
#include "stdio.h"
#include "string.h"
#include "timer.h"
//串口接收缓存区
USART3_RX_BUF[USART3_MAX_RECV_LEN]; //接收缓冲,最大USART3_MAX_RECV_LEN个字节.
USART3_TX_BUF[USART3_MAX_SEND_LEN]; //发送缓冲,最大USART3_MAX_SEND_LEN字节
//通过判断接收连续2个字符之间的时间差不大于10ms来决定是不是一次连续的数据.
//如果2个字符接收间隔超过10ms,则认为不是1次连续数据.也就是超过10ms没有接收到
//任何数据,则表示此次接收完毕.
//接收到的数据状态
//[15]:0,没有接收到数据;1,接收到了一批数据.
//[14:0]:接收到的数据长度
vu16 USART3_RX_STA=0;
void USART3_IRQHandler(void)
{
res;
if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET)//接收到数据
{
res =USART_ReceiveData(USART3);
if((USART3_RX_STA&(1<<15))==0)//接收完的一批数据,还没有被处理,则不再接收其他数据
{
if(USART3_RX_STA
串口程序:
//初始化IO 串口3
//pclk1:PCLK1时钟频率(Mhz)
//bound:波特率
void usart3_init(u32 bound)
{
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIOB时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE); //串口3时钟使能
USART_DeInit(USART3); //复位串口3
//USART3_TX PB10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //PB10
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PB10
//USART3_RX PB11
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化PB11
USART_InitStructure.USART_BaudRate = bound;//波特率一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART3, &USART_InitStructure); //初始化串口 3
USART_Cmd(USART3, ENABLE); //使能串口
//使能接收中断
USART_ITConfig(USART3, USART_IT_RXNE, ENABLE);//开启中断
//设置中断优先级
NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
TIM7_Int_Init(1000-1,7200-1); //10ms中断
USART3_RX_STA=0; //清零
TIM_Cmd(TIM7,DISABLE); //关闭定时器7
}
//串口3,printf 函数
//确保一次发送数据不超过USART3_MAX_SEND_LEN字节
void u3_printf(char* fmt,...)
{
,j;
va_list ap;
va_start(ap,fmt);
vsprintf((char*)USART3_TX_BUF,fmt,ap);
va_end(ap);
i=strlen((const char*)USART3_TX_BUF); //此次发送数据的长度
for(j=0; j
KMP 匹配算法程序:
#include "common.h"
#include "kmp.h"
#include "usart.h"
#include "stdlib.h"
#include "string.h"
#define KEYWORD_MAX_LENGTH 100 //设定搜索串的最大长度
int kmp_table[KEYWORD_MAX_LENGTH]; //为搜索串建立kmp表
char prefix_stack[KEYWORD_MAX_LENGTH]; //前缀表达式栈
char suffix_stack[KEYWORD_MAX_LENGTH]; //后缀表达式栈
char keyword_length = 0; //搜索串的长度
const u8 auchCRCHi[] = {
, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
} ;
const u8 auchCRCLo[] = {
, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,0x43, 0x83, 0x41, 0x81, 0x80, 0x40
} ;
//int record_position[KEYWORD_MAX_LENGTH]; //记录与关键字串匹配源串中的位置
/*
*GetMatchValue:获得字符串 src 的部分匹配值
*/
int GetMatchValue(char *src)
{
int i = 0;
int value = 0;
int src_len;
int flag = 0; //用一个标志位来确定后缀栈中到最后一个元素都与前缀栈中的符号匹配
char *begin; //初始化指向字符串第一个字符
char *end; //初始化指向字符串最后一个字符
char *p;
char *q;
src_len=strlen(src);
begin=src;
end=src + (src_len - 1);
for(i=0; i<(src_len-1); i++)
{
prefix_stack[i] = *begin;
suffix_stack[i] = *end;
begin++;
end--;
}
p=prefix_stack;
q=suffix_stack + (src_len - 2); //指向栈中最后一个元素
while(q >= suffix_stack)
{
if(*p == *q)
{
value++;
p++;
flag=1;
}
else {
flag = 0;
}
q--;
}
if(flag == 0) value = 0;
return value;
}
/*
*创建搜索字符串的 KMP 表
*/
int Create_KMP_Table(char *str,int *table)
{
int i;
char *dst;
keyword_length = strlen(str);
for(i=0; i> 8;
crc2=crcData & 0xff;
if ((crc1==dst_buf[rlen-2]) && (crc2==dst_buf[rlen-1]))
{
if ((dst_buf[0]==0xDA) && (dst_buf[1]==0x00))
{
if (dst_buf[2]==0x01)
{
for (i=0; i<12; i++)
{
j=0;
k=8;
while ((dst_buf[k]==search_buf[i][j]) && (k
大小: 101MB
➡️ 资源下载:https://download.csdn.net/download/s1t16/87368182