树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试

本文的出发点:无意接触到树莓派这款优秀的芯片,比较适合青少年编程,激发孩子的编程思维的培养,最好的学习就是实战,借助于淘宝上现成零件式的智能小车硬件和树莓派实现智能机器人小车的实现,给孩子带来低成本高性能的玩具,重要的是通过个人的行动带动孩子的兴趣达到启蒙作用。

主要分树莓派系统搭建和智能小车组装调试两大部分,内容偏实战,理论知识网络上遍地都是会附带而过,尽量做到步骤简洁易懂,细节力求完整,为您零基础学习树莓派提供捷径,如有不足请指正。
1. 树莓派系统搭建

1.1 什么是树莓派

Raspberry Pi(中文名为“树莓派”,简写为RPi,(或者RasPi / RPI) [1]  是为学习计算机编程教育而设计,只有信用卡大小的微型电脑,其系统基于Linux,由注册于英国的慈善组织“Raspberry Pi 基金会”开发,推广给全世界的青少年电脑爱好者,用于培养计算机程序设计的兴趣和能力。

您可以将树莓派连接电视、显示器、键盘鼠标等设备使用。你可以通过树莓派来DIY以下一些场景应用:

  • 打造一款属于自己的手持式游戏机,虽然没手机方便,但也会很酷....
  • 借助于一些传感器和执行机构实现智能家居的远程监控,上班也能瞧瞧美丽的小家和可爱的娃 : )
  • 扔掉带有各种广告和限制的机顶盒,实现自己的家庭多媒体中心,重温下硬盘里的经典影片,创建专属的家庭相册和视频集,打打儿时的经典游戏,,,
  • 打造一款智能小车,可以根据你的指令跑到厨房里问妈妈:今天有红烧肉吃吗?再跑回来告诉你结果  
  • 设计平面自动作画系统或者简易3D打印机,在家也能体验艺术魅力

树莓派裁剪了桌面计算机的强大应用,简化复杂的硬件构建和编程,更注重于作品的创意和设计,培养青少年的编程思维。

树莓派参数信息不作具体详细介绍,直接上Raspberry Pi 3B有个直观印象。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第1张图片

1.2 树莓派系统镜像下载、烧写和备份

树莓派支持多种系统类型,如 Raspbian、Arch Linux ARM、Debian Squeeze、Firefox OS、Gentoo Linux、Google Chrome OS、Raspberry Pi Fedora Remix、Slackware ARM、QtonPi、Slackware ARM、WebOS、RISC OS、FreeBSD、NetBSD、Android 4.0(Ice Cream Sandwich)等。

树莓派官网下载地址:http://www.raspberrypi.org/downloads,可在官网上下载最新Raspbian系统。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第2张图片

树莓派实际使用micro SD 卡装载系统,系统烧写及时写SD卡的过程

  • 电脑插入SD卡(推荐4G以上的Class4以上卡),使用Win32DiskImager(Win32DiskImager-0.9.5-install.exe)烧写Raspbian操作系统镜像文件2017-03-02-raspbian-jessie.img,一顿猛操作:最好关闭系统防火墙;选择加载img镜像系统;点击write;确认yes;喝完茶之后看到write successful界面说明系统安装成功了!细心点你会发现磁盘空间有点奇怪,最大也就几十MB,莫慌!这是正常现象,linux下的磁盘分区在Windows下是看不到的。
  • 树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第3张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第4张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第5张图片

1.3 树莓派硬件连接

电源:树莓派正常采用5V, 2A 的Micro USB供电,就是常见的安卓手机接口,但考虑到众多外设、显示屏等诸多负载产生的压降,特地购置了原装树莓派专用官方电源(5.1V, 2.5A)。网上获知因电源不稳定造成的诸多后果太多了:键盘动不了;显示器不显示或者花屏闪屏;系统重启等等。总之而言,电源是无论放在什么场合都是重中之重,必须足够重视,否则很容易陷入各种不可预期的麻烦,让你怀疑人生。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第6张图片

1.4 树莓派连接显示屏

树莓派可以通过各种工具如SSH, TightVnc,Putty,Winscp等等实现文件交互甚至系统画面访问,不过初学者还是强烈建议买块显示屏进行初始的一些配置,所谓眼见为实,这样便于入门。这里我配置了一块微雪的7寸高清电容屏和微型无线键盘鼠标。


树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第7张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第8张图片

7寸显示器HDMI接树莓派的HDMI; 显示器Mico USB供电口接入树莓派的USB供电;无线键盘的的接收器同样插到树莓派USB口上;最后上电启动树莓派系统。

初次启动即发现屏幕显示花屏,无法全屏显示

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第9张图片

咨询了微雪显示屏客户得到了解决方案:用于树莓派的Raspbian/ Ubuntn mate系统时,需要手动修改系统根目录下config.txt配置文件,文件末尾添加以下配置行并确保等号左右没有空格即可,亲自有效!

max_usb_current=1
hdmi_group=2
hdmi_mode=87
hdmi_cvt 1024 600 60 6 0 0 0
hdmi_drive=1

成功启动画面如图所示:


  • pi账户和root账户

pi账户是树莓派自动分配的用户账户,默认密码为raspberry,建议用户用以下命令自行修改密码。

pi@raspberry:password pi

不过一般的文件系统操作,pi账户没有足够的权限,这时候就需要获取系统的root权限。在pi账户登录情况下,可以临时通过sudo临时获取root权限如下命令:

pi@raspberry:sudo raspi-config
对于官方的初始系统root密码默认是没有密码的,但账户确是锁定的,可在pi账户下开启root密码,如下命令:
pi@raspberry:sudo passwd root

执行此命令后系统会提示输入两遍的root密码,输入你想设的密码即可,然后再执行:

sudo passwd --unlock root

这样root用户解锁成功,如果不想每次输入命令都使用sudo获取临时root权限的话,可以在pi账户下输入su命令并输入root密码即可获取长期root权限。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第10张图片

1.4 登录方式配置

通过显示屏进入系统,发现画面右上角已经显示出当前可连接的wifi路由器,直接点击输入WiFi密码连接。使用默认浏览器看是否能上网。接着打开系统左上角的命令行终端,输入ifconfig命令查看当前系统ip地址为(172.20.1.5),这样电脑端连接同样的Wifi路由,只要能访问到该IP地址就可以使用多种远程工具进行访问树莓派系统,进行文件传送,远程桌面访问等操作了。

pi@raspberry:ifconfigifconfig

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第11张图片

  • SSH和SSH文件传输

使用SSH,必须先手动开启树莓派的SHH远程服务,操作如下:

a.命令打开raspi配置窗口

pi#sudo raspi-config

然后选择"Interfacing options" 进入二级菜单,选择"P2 SSH" 回车确认Enable即可。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第12张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第13张图片

b. 这样我们就可以通过SSH工具软件远程访问树莓派系统了。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第14张图片

默认使用pi账户及默认密码raspberry登录,成功登录后显示如下信息。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第15张图片

c. 点击SSH工具栏上的“New file transfer window”图标可以打开文件系统操作窗口如下,左侧为本地电脑目录系统,右侧即为树莓派可访问的linux目录系统,借助于root权限可以相互移动和修改文件。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第16张图片

注意1:如果出现server responded “algorithm negotiation failed”的操作错误,这时我们需要登录pi之后修改ssh的配置文件/etc/ssh/sshd_config

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第17张图片

sudo vi /etc/ssh/sshd_config

并在打开的文件中添加如下代码

Ciphers aes128-cbc,aes192-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr,3des-cbc,arcfour128,arcfour256,arcfour,blowfish-cbc,cast128-cbc

MACs hmac-md5,hmac-sha1,[email protected],hmac-ripemd160,hmac-sha1-96,hmac-md5-96

KexAlgorithms diffie-hellman-group1-sha1,diffie-hellman-group14-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521,diffie-hellman-group1-sha1,[email protected]

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第18张图片

重启sshd服务后,即可正常登录

sudo service ssh restart

或者

sudo /etc/init.d/ssh restart
  • 远程桌面登录(xdrp)

如果你觉得SSH访问工具不够直观的话,还可以通过远程桌面访问的方式,顾名思义就是可以访问到树莓派系统的主画面,相当于直接操作树莓派的linux系统,这样无论从文件操作,程序运行以及命令执行上都显得比较明了,方便初学者使用。

首先需要获取root权限并安装xdrp

pi@raspberry:sudo apt-get install xrdp

或者

pi@raspberry:su
root@raspberry:apt-get install xrdp

安装过程如下图:

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第19张图片

安装完成后,可以通过Windows命令框输入mstsc或者remote打开远程桌面访问客户端,并输入树莓派的IP地址访问连接。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第20张图片

pi和root账户皆可登录到树莓派系统中,如下就可以看到正在的树莓派Raspbian操作系统了。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第21张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第22张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第23张图片

不过如果远程桌面的登录过程中出现如下错误(IP地址,用户名,密码正确的情况下)

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第24张图片

解决的办法就需要再树莓派系统中安装tightvnc server服务

  • Tight Vnc
root@raspberry: apt-get install tightvncserver

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第25张图片

安装完成后,reboot重启后再次远程桌面连接即可成功!

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第26张图片

1.5 Vim编辑器配置

Linux自带的编辑器有nano和vi,但vi编辑器使用起来很不方便,通常我们可以使用Vim编辑器。vim 是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面。vim的这些优势主要体现在以下几个方面:1、多级撤消我们知道在vi里,按 u只能撤消上次命令,而在vim里可以无限制的撤消。2、易用性vi只能运行于unix中,而vim不仅可以运行于unix,windows ,mac等多操作平台。3、语法加亮vim可以用不同的颜色来加亮你的代码。4、可视化操作就是说vim不仅可以在终端运行,也可以运行于x window、 mac os、 windows。5、对vi的完全兼容某些情况下,你可以把vim当成vi来使用。

安装之前,首先需要更新索引源:

sudo apt-get update 

安装vim编辑器:

sudo apt-get install vim

注意:有的系统默认自带安装了Vim文本编辑器,但是树莓派的源是国外版的,默认编辑器并不好用,还无法显示中文,所以最好先卸载一次,再重新安装:

sudo apt-get remove vim-common
sudo apt-get install vim

也可以使用SSH Secure File Transfer工具将windows下的文件与树莓派的文件实现文件跨系统传输,将下载好的vimconfig.tar.gz文件安装包传输到树莓派系统的目录中,如/home/pi/workdir。然后在命令行中输入解压缩命令:


进入vimconfig目录中运行config.sh脚本

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第27张图片

会出现如下错误,不用急

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第28张图片

我们只需要在/root/目录下新建.vim和.vimrc文件,还需要apt-get install ctags即可 ,确保在root(加sudo),否则没有权限。

配置好的vim编辑器:

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第29张图片

具体Vim编辑器的使用方法和相关命令,请自行百度查询。

1.7 树莓派配置成路由器

Raspberry Pi 3板载了无线网络适配器和陶瓷天线,不需要额外增加无线网卡就可以把它打造成一个无线路由器。

以下描述如何开启树莓派无线网络适配器的AP功能,并且共享其有线网络,实现无线路由功能。

  • 先下载相关软件hostapd和dnsmasp,前者可以开启无线适配器的AP功能,后者是DHCP和DNS服务器。
sudo apt-get install hostapd dnsmasq

安装完成之后就可以直接安装DHCP服务了

sudo apt-get install isc-dhcp-server
  • 下面设置静态IP

需要将无线接口wlan0的IP配置成静态地址,首先让dhcpcd不再管理wlan0,避免设置冲突。修改etc文件夹中的dhcpcd.conf文件,开头增加一行:denyinterfaces wlan0

vi /etc/dhcpcd.conf

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第30张图片

然后设置wlan0的静态ip,修改文件:

vi /etc/network/interfaces

将wlan0相关的内容修改成如下内容:

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第31张图片

192.168.0.1是给树莓派做路由器分配的网关IP,不能与局域网其他路由器网关IP重复,若重复此处可以修改IP地址为其他网关。

重启服务和wlan0

service dhcpcd restart
ifdown wlan0
ifup wlan0

通过ifconfig可以看到wlan0的ip已经设定好了。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第32张图片

  • 安装Hostapd

新建配置文件,并输入如下配置行:

vi /etc/hostapd/hostapd.conf

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第33张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第34张图片

上面文档中,ssid=RaspberryPi是无线网络的名字,wpa_passphrase=raspberry是密码。

测试配置是否正确:

sudo /usr/sbin/hostapd /etc/hostapd/hostapd.conf

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第35张图片

通过手机等设备应该可以搜到名为RaspberryPi的WiFi,还不需要连接。如果没有问题,按Ctrl+C停止测试。使上述设置生效:

vi /etc/default/hostapd

将#DAEMON_CONF=""修改为DAEMON_CONF="/etc/hostapd/hostapd.conf"。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第36张图片

  • 配置DHCP服务器

备份配置文件:

cp /etc/dhcp/dhcpd.conf  /etc/dhcp/dhcpd.conf.bak

编辑新的配置文件,修改成如下内容:

vi /etc/dhcp/dhcpd.conf

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第37张图片

重启DHCP服务

service isc-dhcp-server restart
  • 开启IPV4转发

修改相应的文件,去掉net.ipv4.ip_forward=1前面的“#”号

vi /etc/sysctl.conf

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第38张图片

通过iptables做NAT转发


这些配置重启之后就会失效了,需要保存起来

sh -c “iptables-save > /etc/iptables.ipv4.nat”

并设置为开机自动加载:

vi /etc/rc.local

在exit 0上方增加:

iptables-restore < /etc/iptables.ipv4.nat

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第39张图片

启动服务

service hostapd start

接着reboot重启。树莓派重启之后就可以用手机连接到wifi了。名字和密码在上面的文档中体现了,连接之后系统会自动为我们连接的设备分配未使用的ip。如果我们的树莓派通过有线网络连上互联网的话,我们连上的树莓派路由器的话,也是可以上网的。

1.8 树莓派摄像头配置

树莓派摄像头配置需要如下步骤

  • 拷贝和解压摄像头驱动

通过SSH拷贝驱动包文件master.zip到home文件夹,并执行解压缩命令

#unzip master.zip

   树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第40张图片

  • 安装CMake工具

CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件,能测试编译器所支持的C++特性,类似UNIX下的automake。这里编译摄像头驱动需要用到此工具。执行下列下载安装命令:

#sudo apt-get install cmake 

中间若有提示,输入“Y”即可,完成后如下图所示:

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第41张图片

  • 安装所需要的编译支持库libjpeg8

执行指令,完成后如下图所示:

#sudo apt-get install libjpeg8-dev

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第42张图片

  • 执行摄像头编译

首先需要知道你手上的摄像头的数据格式(JPEG或者YUYV),驱动中默认为JPEG格式,如需修改请在如下目录文件中修改

# cd /home/pi/mjpg-streamer-master/mjpg-streamer-experimental/plugins/input_uvc/
# sudo vim input_uvc.c

修改135行处的format为V4L2_PIX_FMT_YUYV,然后然后返回到/mjpg-streamer-experimental/下执行如下命令完成编译

#sudo make clean all
  • 摄像头画面调用

进入树莓派配置界面,Enable激活Camera摄像头

#sudo raspi-config

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第43张图片

然后插上USB摄像头,重启系统

# sudo reboot

重启完毕进入系统,进入mjpg-streamer-experimental目录并使用下列指令启动普通USB摄像头

#./mjpg_streamer -i "./input_uvc.so" -o "./output_http.so -w ./www"

启动树莓派专用摄像头RaspiCamera的指令

#./mjpg_streamer -i "./input_raspicam.so" -o "./output_http.so -w ./www"

执行过程中可能会有报错,可以忽略,只要最后没有退回到命令行提示符,而且显示“Starting ouput”,就应该成功了。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第44张图片

下面在PC端使用浏览器查看摄像头的静止图像

http://<树莓派IP>:8080/?action=snapshot

动态图像查询:

http://<树莓派IP>:8080/?action=stream

或者 http://<树莓派IP>:8080/javascript_simple.html

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第45张图片

1.9 树莓派3串口(UART)使用问题和方法解决

串口(UART)的硬件bug如下文引用

““”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“””

今天要说说树莓派3UART串口的使用问题,该串口问题的官方反馈及回复请参考这两篇官方博文https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=137932https://www.raspberrypi.org/forums/viewtopic.php?f=107&t=138223

根据官方的反馈和回复,我们了解到树莓派3上用户目前无法正常是使用GPIO中的UART串口(GPIO14&GPIO15),也就是说用户无论是想用串口来调试树莓派,还是想用GPIO中的串口来连接GPS,蓝牙,XBEE等等串口外设目前都是有问题的。

原因是树莓派CPU内部有两个串口,一个是硬件串口(官方称为PL011 UART),一个是迷你串口(官方成为mini-uart)。在树莓派2B/B+这些老版树莓派上,官方设计时都是将硬件串口分配给GPIO中的UART(GPIO14&GPIO15),因此可以独立调整串口的速率和模式。而树莓派3的设计上,官方在设计时将硬件串口分配给了新增的蓝牙模块上,而将一个没有时钟源,必须由内核提供时钟参考源的迷你串口分配给了GPIO的串口,这样以来由于内核的频率本身是变化的,就会导致迷你串口的速率不稳定,这样就出现了无法正常使用的情况。

目前解决方法就是,关闭蓝牙对硬件串口的使用,将硬件串口重新恢复给GPIO的串口使用,也就意味着树莓派3板载蓝牙串口,现在成了鱼和熊掌,两者无法兼得。

”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”“”

下面讲述如何恢复硬件串口的方法

  • 下载pi3-miniuart-bt-overlay文件,解压出pi3-miniuart-bt-overlay.dtb文件,并将dtb文件拷贝到/boot/overlays/目录
  • 修改/boot目录下的config.txt文件,在最后添加如下两行并保存.
#sudo vim /boot/config.txt
dtoverlay=pi3-miniuart-bt-overlay

force_turbo=1

  • 修改/boot目录下的cmdline.txt文件
#sudo vim /boot/cmdline.txt

修改前的截图:

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第46张图片

修改为:

dwc_otg.lpm_enable=0 console=serial1,115200  console=tty1 root=/dev/mmcblk0p2  kgdboc=serial1,115200 rootfstype=ext4 elevator=deadline fsck.repair=yes  rootwait

最后保存退出。

下面需要做的就是关闭自带的板载蓝牙功能。

  • 输入下面命令关闭hciuart使用uart0
#sudo systemctl disable hciuart
  • 修改/lib/systemd/system/hciuart.server 将 “ttyAMA0”修改为“ttyS0”,并保存退出。
sudo vim /lib/systemd/system/hciuart.service

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第47张图片

  • 最后更新并重启即可
sudo apt-get update
sudo apt-get upgrade
sudo reboot

1.6 Wiring库函数


2. 智能小车组装调试

智能小车简介

这里选用深圳亚博智能科技出品的4WD可兼容多种控制器(树莓派、Arduino、51、STM32)的智能小车。该车支持多个定制的精良传感器,能出色完成巡线、避障、跟随、寻光、灰度识别等功能;支持多种遥控方式,蓝牙4.0控制、微信小程序控制、红外遥控、PC上位机遥控等;支持多种组装方式,单、双层平台结构,平台上多个安装孔选择。

2.1 智能小车组装

具体安装步骤如下:底板安装孔、电机安装、轮胎安装、电池盒安装、循迹探头安装、红外寻光模块安装、风扇安装、蓝牙安装、树莓派3B+与扩展板安装、扩展板安装、舵机,超声波模块、探照灯模块安装、摄像头云台搭建、摄像头云台与二层平台安装、双平台安装、扩展板接线端子、PS2遥控手柄安装、检查各模块插头连接和跳线。详细见下面官方图,在此无需累赘。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第48张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第49张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第50张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第51张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第52张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第53张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第54张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第55张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第56张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第57张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第58张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第59张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第60张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第61张图片

2.1 智能小车调试

2.1.1 Wiring库函数

简介:wiringPi是应用于树莓派平台的GPIO控制库函数,wiringPi中的函数类似于Arduino的wiringPi系统,wiringPi库包含了丰富的库函数,如GPIO库,I2C库,SPI库,UART库和软件PWM库。

wiringPi安装

  • 使用git工具

首先安装git工具

sudo apt-get install git-core

如果安装时发生错误,可以尝试更新apt库

sudo apt-get update

着通过 git 在线获得wiringPi的源代码,输入命令

git clone git://git.drogon.net/wiringPi

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第62张图片

进入wiringPi目录并安装wiringPi

cd wiringPi
./build

build脚本将会自动完成wiringPi库的编译和安装,安装完成如下....

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第63张图片

若需要更新wiringPi库,输入以下命令:

cd wiringPi
git pull origin

  • 离线下载安装wiringPi函数库

直接下载wiringPi源码和解压,并进行本地安装...从以下网址获得wiringPi源码:

https://git.drogon.net/?p=wiringPi;a=summary


点击图中的snapshot便可下载最新版本,将下载到的安装包移动到你需要安装的目录下,然后使用以下命令解压和安装:

tar xfz wiringPi-96344ff.tar.gz
cd wiringPi-96344ff
./build

wiringPi的版本信息查看

安装完成后,打开命令终端,可以通过gpio命令来检查wiringPi的版本信息。

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第64张图片

GPIO口的使用

命令行输入gpio readall 可以获得wiringPi和树莓派的GPIO接口之间的对应关系,如下图所示:

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第65张图片

上图中的physical列代表的是树莓派物理上接口J8的管脚定义,wPi列即代表在wiringPi中所对应的数值,BCM列即代表在BCM2835的GPIO寄存器中的偏移地址,即在BCM2835 C library中对应的GPIO的数值。我们使用的是wiringPi库函数,进行的树莓派wifi智能小车的开发。

2.1.1 代码功能调试和说明

WiringPi库的具体函数使用说明可以自己网上查看手册,在此不作累赘,具体以智能小车的控制功能和源码进行典型性讲解。

这里以厂家提供的源码程序简单介绍

库文件引用和变量定义

#include   
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


#define run_car     '1'//按键前
#define back_car    '2'//按键后
#define left_car    '3'//按键左
#define right_car   '4'//按键右
#define stop_car    '0'//按键停

#define front_left_servo  '1'
#define front_right_servo '2'
#define up_servo    '3'
#define down_servo  '4'
#define left_servo  '6'
#define right_servo  '7'
#define updowninit_servo '5'
#define stop_servo  '8'


#define ON  1
#define OFF 0

#define HIGH 1
#define LOW  0

/*小车运行状态枚举*/
enum {
  enSTOP = 0,
  enRUN,
  enBACK,
  enLEFT,
  enRIGHT,
  enTLEFT,
  enTRIGHT
} enCarState;

/*小车舵机状态枚举*/
enum {
  enFRONTSERVOLEFT = 1,
  enFRONTSERVORIGHT,
  enSERVOUP,
  enSERVODOWN,
  enSERVOUPDOWNINIT,
  enSERVOLEFT,
  enSERVORIGHT,
  enSERVOSTOP,
  enSERVOFRONTINIT
} enServoState;


//定义引脚
int Left_motor_go = 28;       //左电机前进AIN2连接Raspberry的wiringPi编码28口
int Left_motor_back = 29;     //左电机后退AIN1连接Raspberry的wiringPi编码29口

int Right_motor_go = 24;      //右电机前进BIN2连接Raspberry的wiringPi编码24口
int Right_motor_back = 25;    //右电机后退BIN1连接Raspberry的wiringPi编码25口

int Left_motor_pwm = 27;      //左电机控速PWMA连接Raspberry的wiringPi编码27口
int Right_motor_pwm = 23;     //右电机控速PWMB连接Raspberry的wiringPi编码23口

/*循迹红外传感器引脚及变量设置*/
//TrackSensorLeftPin1 TrackSensorLeftPin2 TrackSensorRightPin1 TrackSensorRightPin2
//      9                  21                  7                   1
const int TrackSensorLeftPin1  =  9;    //定义左边第一个循迹红外传感器引脚为wiringPi编码9口
const int TrackSensorLeftPin2  =  21;  //定义左边第二个循迹红外传感器引脚为wiringPi编码21口
const int TrackSensorRightPin1 =  7;   //定义右边第一个循迹红外传感器引脚为wiringPi编码7口
const int TrackSensorRightPin2 =  1;   //定义右边第二个循迹红外传感器引脚为wiringPi编码1口

//定义各个循迹红外引脚采集的数据的变量
int TrackSensorLeftValue1;
int TrackSensorLeftValue2;
int TrackSensorRightValue1;
int TrackSensorRightValue2;
char infrared_track_value[5] = {0};

/*避障红外传感器引脚及变量设置*/
const int AvoidSensorLeft =  26; //定义左边避障的红外传感器引脚为wiringPi编码26口
const int AvoidSensorRight = 0;  //定义右边避障的红外传感器引脚为wiringPi编码0口

int LeftSensorValue ;            //定义变量来保存红外传感器采集的数据大小
int RightSensorValue ;
char infrared_avoid_value[3] = {0};

/*定义光敏电阻引脚及变量设置*/
const int LdrSensorLeft =  11;   //定义左边光敏电阻引脚为wiringPi编码11口
const int LdrSensorRight = 22;   //定义右边光敏电阻引脚为wiringPi编码22口

int LdrSersorLeftValue ;         //定义变量来保存光敏电阻采集的数据大小
int LdrSersorRightValue ;
char LDR_value[3] = {0};

/*蜂鸣器引脚设置*/
int buzzer = 10;                //设置控制蜂鸣器引脚为wiringPi编码10口

/*小车初始速度控制*/

unsigned int g_CarSpeedControl = 150;


/*设置舵机驱动引脚*/
int FrontServoPin = 4;
int ServoUpDownPin = 13;
int ServoLeftRightPin = 14;


/*超声波引脚及变量设置*/
int EchoPin = 30;         //定义回声脚为连接Raspberry的wiringPi编码30口
int TrigPin = 31;         //定义触发脚为连接Raspberry的wiringPi编码31口

/*RGBLED引脚设置*/
int LED_R = 3;           //LED_R接在Raspberry上的wiringPi编码3口
int LED_G = 2;           //LED_G接在Raspberry上的wiringPi编码2口
int LED_B = 5;           //LED_B接在Raspberry上的wiringPi编码5口

/*灭火电机引脚设置*/
int OutfirePin = 8;      //设置灭火电机引脚为wiringPi编码8口

/*变量*/
/*摄像头舵机上下和左右两个自由度的变量*/
int ServoUpDownPos = 90;
int ServoLeftRightPos = 90;
/*前舵机左右摇动变量*/
int FrontServoLeftRightPos = 90;
unsigned char g_frontservopos = 90;

/*舵机控制标志位*/
int ServoFlags;
int g_ServoState = enSERVOSTOP;
int g_lednum = 0;        //led颜色切换变量


/*串口设备打开的文件描述符*/
int fd;

/*串口长度变量*/
int g_num=0;
int g_packnum=0;

/*计时变量*/
int serialtime = 5000;
int count = 20;

/*串口数据设置*/
char InputString[512] = {0};  //用来储存接收到的内容
int NewLineReceived = 0;      //前一次数据结束标志
int StartBit  = 0;            //协议开始标志
int g_CarState = enSTOP;      //1前2后3左4右0停止
char ReturnTemp[512] = {0};   //存储返回值

/*小车模式切换*/
int g_modeSelect = 0; //0是默认状态;  //2:巡线模式 3:超声波避障 //4: 七彩探照 5: 寻光模式 6: 红外跟踪

重要功能函数

  • main函数:初始化以及模式控制
/**
* Function       main
* @brief         对串口发送过来的数据解析,并执行相应的指令
* @param[in]     void
* @retval        void
* @par History   无
*/
int main()
{
  g_modeSelect = 1;
  //wiringPi初始化
  wiringPiSetup();
  digitalWrite(OutfirePin, HIGH); 
  //初始化电机驱动IO为输出方式
  pinMode(Left_motor_go, OUTPUT);
  pinMode(Left_motor_back, OUTPUT);
  pinMode(Right_motor_go, OUTPUT);
  pinMode(Right_motor_back, OUTPUT);
  
  //创建两个软件控制的PWM脚
  softPwmCreate(Left_motor_pwm,0,255); 
  softPwmCreate(Right_motor_pwm,0,255);

  //定义左右传感器为输入接口
  pinMode(AvoidSensorLeft, INPUT);
  pinMode(AvoidSensorRight, INPUT);
  
  //定义寻迹红外传感器为输入模式
  pinMode(TrackSensorLeftPin1, INPUT);
  pinMode(TrackSensorLeftPin2, INPUT);
  pinMode(TrackSensorRightPin1, INPUT);
  pinMode(TrackSensorRightPin2, INPUT);
  
  //定义光敏电阻引脚为输入模式
  pinMode(LdrSensorLeft, INPUT);
  pinMode(LdrSensorRight, INPUT);
  
  //初始化蜂鸣器IO为输出方式
  pinMode(buzzer, OUTPUT);
  digitalWrite(buzzer, HIGH);
  

  //初始化超声波引脚模式
  pinMode(EchoPin, INPUT);   //定义超声波输入脚
  pinMode(TrigPin, OUTPUT);  //定义超声波输出脚

  //定义灭火IO口为输出模式并初始化
  pinMode(OutfirePin, OUTPUT);

  //初始化RGB三色LED的IO口为输出方式,并初始化
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);

  softPwmCreate(LED_R,0,255); 
  softPwmCreate(LED_G,0,255); 
  softPwmCreate(LED_B,0,255); 

  //初始化舵机引脚为输出模式
  pinMode(FrontServoPin, OUTPUT);
    //初始化舵机引脚为输出模式
  pinMode(FrontServoPin, OUTPUT);
  pinMode(ServoUpDownPin, OUTPUT);
  pinMode(ServoLeftRightPin, OUTPUT);
    //舵机位置初始化
  servo_init();
	
  //打开串口设备,如若失败则会打印错误信息
  if ((fd = serialOpen("/dev/ttyAMA0", 9600)) < 0)
  {
    fprintf(stderr, "Uable to open serial device: %s\n", strerror(errno));
	return -1;
  }
  while(1)
  {
   //调用串口解包函数
   serialEvent();
   if (NewLineReceived)
   {	
    printf("serialdata:%s\n",InputString);
    serial_data_parse();  //调用串口解析函数
	NewLineReceived = 0;
   }
   
   //切换不同功能模式
   switch (g_modeSelect)
   {
    case 1: break; //暂时保留
    case 2: Tracking_Mode(); break; //巡线模式
    case 3: Ultrasonic_avoidMode();  break;  //超声波避障模式
    case 4: LED_Color_Mode(); break;  //七彩颜色模式
    case 5: LightSeeking_Mode(); break;  //寻光模式
    case 6: Infrared_follow_Mode(); break;  //红外跟随模式
   }
   //舵机云台的控制
   Servo_Control_Thread();
   //让串口平均每秒发送采集的数据给上位机   
   if(g_modeSelect == 1)
   {
   	serialtime--;
   	if(serialtime ==0)
   	{
	 count--;
	 serialtime = 5000;
	 if(count == 0)
	 {
     	 serial_data_postback(fd);
		 serialtime = 5000;
		 count = 20;
	 }
   	}
   }
   usleep(10);
  }
 serialClose(fd);  //关闭串口
 return 0;
}

  • servo_appointed_detection(n);// 舵机旋转一定的角度
/**
* Function       servo_pulse
* @brief         定义一个脉冲函数,用来模拟方式产生PWM值
*                时基脉冲为20ms,该脉冲高电平部分在0.5-2.5ms
*                控制0-180度
* @param[in1]    ServPin:舵机控制引脚
* @param[in2]    myangle:舵机转动指定的角度
* @param[out]    void
* @retval        void
* @par History   无
*/
void servo_pulse(int v_iServoPin, int myangle)
{
  int PulseWidth;                    //定义脉宽变量
  PulseWidth = (myangle * 11) + 500; //将角度转化为500-2480 的脉宽值
  digitalWrite(v_iServoPin, HIGH);      //将舵机接口电平置高
  delayMicroseconds(PulseWidth);     //延时脉宽值的微秒数
  digitalWrite(v_iServoPin, LOW);       //将舵机接口电平置低
  delay(20 - PulseWidth / 1000);     //延时周期内剩余时间
  return;
}
/**
* Function       servo_appointed_detection
* @brief         舵机旋转到指定角度
* @param[in]     pos:指定的角度
* @param[out]    void
* @retval        void
* @par History   无
*/
void servo_appointed_detection(int pos)
{
  int i = 0;
  for (i = 0; i <= 20; i++)    //产生PWM个数,等效延时以保证能转到响应角度
  {
    servo_pulse(FrontServoPin, pos); //模拟产生PWM
  } 
}

  • 超声波测距函数
/**
* Function       Distance
* @brief         超声波测一次前方的距离
* @param[in]     void
* @param[out]    void
* @retval        float:distance返回距离值
* @par History   无
*/
float Distance()
{
	float distance;
	struct timeval tv1;
	struct timeval tv2;
	struct timeval tv3;
	struct timeval tv4;
	long start, stop;
	
	digitalWrite(TrigPin, LOW);
	delayMicroseconds(2);
	digitalWrite(TrigPin, HIGH);      //向Trig脚输入至少10US的高电平
	delayMicroseconds(15); //10
	digitalWrite(TrigPin, LOW);
    
	//防止程序未检测到电平变化,陷入死循环,加入一个超时重测机制
    gettimeofday(&tv3, NULL);        //超时重测机制开始计时
	start = tv3.tv_sec * 1000000 + tv3.tv_usec;
	while(!digitalRead(EchoPin) == 1)
	{
		gettimeofday(&tv4, NULL);    //超时重测机制结束计时
		stop = tv4.tv_sec * 1000000 + tv4.tv_usec;
		
		if ((stop - start) > 30000)  //最大测5米时的时间值:10/340=0.03s
		{
			return -1;               //超时返回-1
		}
	} 
	
	//防止程序未检测到电平变化,陷入死循环,加入一个超时重测机制
	gettimeofday(&tv1, NULL);      //当echo脚电平变高时开始计时
    start = tv1.tv_sec*1000000+tv1.tv_usec;
	while(!digitalRead(EchoPin) == 0)
	{
		gettimeofday(&tv3,NULL);   //超时重测机制开始计时
		stop = tv3.tv_sec*1000000+tv3.tv_usec;
		if ((stop - start) > 30000)
		{
			return -1;
		}
	}                              //超时重测机制结束计时
	gettimeofday(&tv2, NULL);      //当echo脚电平变低时结束计时

	start = tv1.tv_sec * 1000000 + tv1.tv_usec;
	stop = tv2.tv_sec * 1000000 + tv2.tv_usec;

	distance = (float)(stop - start)/1000000 * 34000 / 2;
	printf("distance: %f\n", distance);
	return distance;
}

  • 小车前进、后退、刹车、左拐、右拐等运行函数
/**
* Function       run
* @brief         小车前进
* @param[in1]    LeftCarSpeedControl:左轮速度
* @param[in2]    RightCarSpeedControl:右轮速度
* @param[out]    void
* @retval        void
* @par History   无
*/
void run(unsigned int LeftCarSpeedControl,unsigned int RightCarSpeedControl)
{
  //左电机前进
  digitalWrite(Left_motor_go, HIGH);   //左电机前进使能
  digitalWrite(Left_motor_back, LOW);  //左电机后退禁止
  softPwmWrite(Left_motor_pwm, LeftCarSpeedControl*0.95);

  //右电机前进
  digitalWrite(Right_motor_go, HIGH);  //右电机前进使能
  digitalWrite(Right_motor_back, LOW); //右电机后退禁止
  softPwmWrite(Right_motor_pwm, RightCarSpeedControl);
}

/**
* Function       brake
* @brief         小车刹车
* @param[in]     void
* @param[out]    void
* @retval        void
* @par History   无
*/
void brake()
{
  digitalWrite(Left_motor_go, LOW);
  digitalWrite(Left_motor_back, LOW);
  digitalWrite(Right_motor_go, LOW);
  digitalWrite(Right_motor_back, LOW);
}

/**
* Function       left
* @brief         小车左转(左轮不动,右轮前进)
* @param[in1]    LeftCarSpeedControl:左轮速度
* @param[in2]    RightCarSpeedControl:右轮速度
* @param[out]    void
* @retval        void
* @par History   无
*/
void left(unsigned int LeftCarSpeedControl,unsigned int RightCarSpeedControl)
{
  //左电机停止
  digitalWrite(Left_motor_go, LOW);     //左电机前进禁止
  digitalWrite(Left_motor_back, LOW);   //左电机后退禁止
  softPwmWrite(Left_motor_pwm, LeftCarSpeedControl);

  //右电机前进
  digitalWrite(Right_motor_go, HIGH);  //右电机前进使能
  digitalWrite(Right_motor_back, LOW); //右电机后退禁止
  softPwmWrite(Right_motor_pwm, RightCarSpeedControl);
}

/**
* Function       spin_left
* @brief         小车原地左转(左轮后退,右轮前进)
* @param[in1]    LeftCarSpeedControl:左轮速度
* @param[in2]    RightCarSpeedControl:右轮速度
* @param[out]    void
* @retval        void
* @par History   无
*/
void spin_left(unsigned int LeftCarSpeedControl,unsigned int RightCarSpeedControl)
{
  //左电机后退
  digitalWrite(Left_motor_go, LOW);     //左电机前进禁止
  digitalWrite(Left_motor_back, HIGH);  //左电机后退使能
  softPwmWrite(Left_motor_pwm, LeftCarSpeedControl);

  //右电机前进
  digitalWrite(Right_motor_go, HIGH);  //右电机前进使能
  digitalWrite(Right_motor_back, LOW); //右电机后退禁止
  softPwmWrite(Right_motor_pwm, RightCarSpeedControl);
}

/**
* Function       right
* @brief         小车右转(左轮前进,右轮不动)
* @param[in1]    LeftCarSpeedControl:左轮速度
* @param[in2]    RightCarSpeedControl:右轮速度
* @param[out]    void
* @retval        void
* @par History   无
*/
void right(unsigned int LeftCarSpeedControl,unsigned int RightCarSpeedControl)
{
  //左电机前进
  digitalWrite(Left_motor_go, HIGH);    //左电机前进使能
  digitalWrite(Left_motor_back, LOW);   //左电机后退禁止
  softPwmWrite(Left_motor_pwm, LeftCarSpeedControl);

  //右电机停止
  digitalWrite(Right_motor_go, LOW);    //右电机前进禁止
  digitalWrite(Right_motor_back, LOW);  //右电机后退禁止
  softPwmWrite(Right_motor_pwm, RightCarSpeedControl);
}

/**
* Function       spin_right
* @brief         小车原地右转(右轮后退,左轮前进)
* @param[in1]    LeftCarSpeedControl:左轮速度
* @param[in2]    RightCarSpeedControl:右轮速度
* @param[out]    void
* @retval        void
* @par History   无
*/
void spin_right(unsigned int LeftCarSpeedControl,unsigned int RightCarSpeedControl)
{
  //左电机前进
  digitalWrite(Left_motor_go, HIGH);    //左电机前进使能
  digitalWrite(Left_motor_back, LOW);   //左电机后退禁止
  softPwmWrite(Left_motor_pwm, LeftCarSpeedControl);

  //右电机后退
  digitalWrite(Right_motor_go, LOW);    //右电机前进禁止
  digitalWrite(Right_motor_back, HIGH); //右电机后退使能
  softPwmWrite(Right_motor_pwm, RightCarSpeedControl);
}

/**
* Function       back
* @brief         小车后退
* @param[in1]    LeftCarSpeedControl:左轮速度
* @param[in2]    RightCarSpeedControl:右轮速度
* @param[out]    void
* @retval        void
* @par History   无
*/
void back(unsigned int LeftCarSpeedControl,unsigned int RightCarSpeedControl)
{
  //左电机后退
  digitalWrite(Left_motor_go, LOW);     //左电机前进禁止
  digitalWrite(Left_motor_back, HIGH);  //左电机后退使能
  softPwmWrite(Left_motor_pwm, LeftCarSpeedControl);

  //右电机后退
  digitalWrite(Right_motor_go, LOW);    //右电机前进禁止
  digitalWrite(Right_motor_back, HIGH); //右电机后退使能
  softPwmWrite(Right_motor_pwm, RightCarSpeedControl);
}

  • 小车巡线模式函数
/***********模式2 巡线模式*************/
/**
* Function       Tracking_Mode
* @brief         巡线模式
* @param[in1]    void
* @param[out]    void
* @retval        void
* @par History   无
*/
int g_trackstate = 0;
void Tracking_Mode()
{
   char *p=ReturnTemp;
   memset(ReturnTemp,0,sizeof(ReturnTemp));
   track_test();
  //在巡线过程中发送巡线传感器效果
   serialtime--;
   if(serialtime == 0)
   {
	 	count--;
		serialtime = 5000;
	 	if(count == 0)
	 	{
     	strcat(p,"4WD,CSB0,PV8.4,GS0,LF");
		 	strcat(p,infrared_track_value);
		 	strcat(p,",HW00,GM00#");
		 	serialPrintf(fd, p);
		 	serialtime = 5000;
		 	count = 20;
	 	}
   }

   //四路循迹引脚电平状态
   // 0 0 X 0
   // 1 0 X 0
   // 0 1 X 0
   //以上6种电平状态时小车原地右转,速度为250,延时80ms
   //处理右锐角和右直角的转动
   if ( (TrackSensorLeftValue1 == LOW || TrackSensorLeftValue2 == LOW) &&  TrackSensorRightValue2 == LOW)
   {
	   g_trackstate = 1;
     spin_right(200, 200); // luz 250, 250
     delay(80);
   }
   //四路循迹引脚电平状态
   // 0 X 0 0       
   // 0 X 0 1 
   // 0 X 1 0       
   //处理左锐角和左直角的转动
   else if ( TrackSensorLeftValue1 == LOW && (TrackSensorRightValue1 == LOW ||  TrackSensorRightValue2 == LOW))
   {
	   g_trackstate = 2;
     spin_left(200, 200);  // luz 250, 250
     delay(80);
   }
   // 0 X X X
   //最左边检测到
   else if ( TrackSensorLeftValue1 == LOW)
   {
	   g_trackstate = 3;
     spin_left(160, 160); //luz 200, 200
     //delay(10);
   }
   // X X X 0
   //最右边检测到
   else if ( TrackSensorRightValue2 == LOW )
   {
	   g_trackstate = 4;
     spin_right(160, 160); //luz 200, 200
     //delay(10);
   }
   //四路循迹引脚电平状态
   // X 0 1 X
   //处理左小弯
   else if ( TrackSensorLeftValue2 == LOW && TrackSensorRightValue1 == HIGH)
   {
	   g_trackstate = 5;
     left(0, 176);   //luz 0, 220
   }
   //四路循迹引脚电平状态
   // X 1 0 X  
   //处理右小弯
   else if (TrackSensorLeftValue2 == HIGH && TrackSensorRightValue1 == LOW)
   {
	   g_trackstate = 6;
     right(176, 0); //luz 220,0
   }
   //四路循迹引脚电平状态
   // X 0 0 X
   //处理直线
   else if (TrackSensorLeftValue2 == LOW && TrackSensorRightValue1 == LOW)
   {
	   g_trackstate = 7;
     run(204, 204); //luz 255, 255 
   }
   else
   {
   	switch(g_trackstate)
  	{
  		case 1:spin_right(200,200);printf("1\n");break;   //luz 250,250
  		case 2:spin_left(200,200);printf("2\n");break;   //luz 250,250
  		case 3:spin_left(160,160);printf("3\n");break;  //luz 200,200
  		case 4:spin_right(160,160);printf("4\n");break; //luz 200,200
  		case 5:printf("5\n");left(0, 176); break;//luz 0, 220 no break
  		case 6:printf("6\n");right(176,0); break;//luz 220,0  no break
  		case 7:printf("7\n");run(204,204); break;//luz 255, 255  no break
  
  	}
   }
   //当为1 1 1 1时小车保持上一个小车运行状态
  }

  • 摄像头舵机根据超声波测距距离的动作函数
/**
* Function       servo_color_carstate
* @brief         舵机转向超声波测距避障行驶,led根据车的状态显示相应的颜色
* @param[in]     void
* @param[out]    void
* @retval        void
* @par History   无
*/
 void servo_color_carstate()
{
  float distance;
  //定义舵机位置变量和小车前方,左侧,右侧距离
  int iServoPos = 0;
  int LeftDistance = 0;    //左方距离值变量LeftDistance
  int RightDistance = 0;   //右方距离值变量RightDistance
  int FrontDistance = 0;   //前方距离值变量FrontDistance
  color_led_pwm(255, 0, 0);//开红灯
  back(80,80);            //避免突然停止,刹不住车
  delay(80);
  brake();

  //舵机旋转到0度,即右侧,测距
  servo_appointed_detection(0);
  delay(500);
  distance = Distance_test();  //测距
  RightDistance = distance;    //所测的右侧距离赋给变量RightDistance
  //printf("rightdistance :%d\n",RightDistance);
 
  //舵机旋转到180度,即左侧,测距
  servo_appointed_detection(180);
  delay(500);
  distance = Distance_test();  //测距
  LeftDistance = distance;//所测的左侧距离赋给变量LeftDistance
  // printf("leftdistance :%d\n",LeftDistance);

  //舵机旋转到90度,即左侧,测距
  servo_appointed_detection(90);
  delay(500);
  distance = Distance_test();
  FrontDistance = distance;//所测前侧距离付给变量FrontDistance
  //printf("FrontDistance:%d\n",FrontDistance);
 
  if (LeftDistance < 30 && RightDistance < 30 && FrontDistance < 30  )
  {
    //亮品红色,掉头
    color_led_pwm(255, 0, 255);
    spin_right(200,200);
	delay(700);
  }
  else if ( LeftDistance >= RightDistance) //当发现左侧距离大于右侧,原地左转
  {
    //亮蓝色
    color_led_pwm(0, 0, 255);
    spin_left(200,200);
	delay(350);
  }
  else if (LeftDistance < RightDistance ) //当发现右侧距离大于左侧,原地右转
  {
    //亮品红色,向右转
    color_led_pwm(255, 0, 255);
    spin_right(200,200);
	delay(350);
  }
} 

  • 小车根据超声波测距距离的运动控制
/************模式3:超声波避障模式*************/
/**
* Function       Ultrasonic_avoidMode
* @brief         超声波避障模式
* @param[in]     void
* @param[out]    void
* @retval        void
* @par History   无
*/
void Ultrasonic_avoidMode()
{
	float distance = 0;
	distance = Distance_test();        //测量前方距离
   if (distance > 50  )    //障碍物距离大于50时,开启左右红外辅助避障
   {
	   //
    //遇到障碍物,红外避障模块的指示灯亮,端口电平为LOW
    //未遇到障碍物,红外避障模块的指示灯灭,端口电平为HIGH
    LeftSensorValue = digitalRead(AvoidSensorLeft);
    RightSensorValue = digitalRead(AvoidSensorRight);

    if (LeftSensorValue == HIGH && RightSensorValue == LOW)
    {
      spin_left(200,200); //右边探测到有障碍物,有信号返回,原地向左转
	  delay(200);
    }
    else if (RightSensorValue == HIGH && LeftSensorValue == LOW)
    {
      spin_right(200,200);//左边探测到有障碍物,有信号返回,原地向右转
	  delay(200);
    }
    else if (RightSensorValue == LOW && LeftSensorValue == LOW)
    {
      spin_right(200,200);//当两侧均检测到障碍物时调用固定方向的避障(原地右转)
      delay(200);
	}
    //距离大于50时前进,亮绿灯
    run(180, 180); //250, 250
    color_led_pwm(0, 255, 0);
  }
  else if ((distance >= 30 && distance <= 50))
  {
    //遇到障碍物,红外避障模块的指示灯亮,端口电平为LOW
    //未遇到障碍物,红外避障模块的指示灯灭,端口电平为HIGH
    LeftSensorValue = digitalRead(AvoidSensorLeft);
    RightSensorValue = digitalRead(AvoidSensorRight);

    if (LeftSensorValue == HIGH && RightSensorValue == LOW)
    {
      spin_left(200,200); //右边探测到有障碍物,有信号返回,原地向左转
      delay(200);
	  }
    else if (RightSensorValue == HIGH && LeftSensorValue == LOW)
    {
      spin_right(200,200);//左边探测到有障碍物,有信号返回,原地向右转
      delay(200);
	  }
    else if (RightSensorValue == LOW && LeftSensorValue == LOW)
    {
      spin_right(200,200);//当两侧均检测到障碍物时调用固定方向的避障(原地右转)
      delay(200);
	  }
    //距离在30-50之间时慢速前进
    run(120, 120); //180, 180
  }
  else if (  distance < 30  )//当距离小于30时调用舵机颜色控制程序
  {
    servo_color_carstate();
  }	
}

  • 通过串口通信接收蓝牙微信控制程序的控制指令
/**
* Function       serial_data_parse
* @brief         串口数据解析并指定相应的动作
* @param[in]     void
* @param[out]    void
* @retval        void
* @par History   无
*/
void serial_data_parse()
{
  //解析上位机发过来的模式切换指令
	if(StringFind((const char *)InputString, (const char *)"MODE", 0) > 0)
	{
		if(InputString[10] == '0')//停止模式
		{
			brake();
			g_CarState = enSTOP;
			g_modeSelect = 1;
			BeepOnOffMode();
		}
		else
		{
		  switch (InputString[9])
        	{
           case '0': g_modeSelect = 1; ModeBEEP(0); break;
           case '1': g_modeSelect = 1; ModeBEEP(1); break;//遥控模式
           case '2': g_modeSelect = 2; ModeBEEP(2); break;//巡线模式
           case '3': g_modeSelect = 3; ModeBEEP(3); break;//避障模式
           case '4': g_modeSelect = 4; ModeBEEP(4); break;//七彩模式
           case '5': g_modeSelect = 5; ModeBEEP(5); break;//寻光模式
           case '6': g_modeSelect = 6; ModeBEEP(6); break;//跟随模式
           default: g_modeSelect = 1; break;
      		}
      		delay(1000);
      		BeepOnOffMode();
		}
	  memset(InputString, 0x00, sizeof(InputString));
      NewLineReceived = 0;
      return;	  
	}
	
	//非apk模式则退出
//	if(g_modeSelect != 1)
//	{
//	    memset(InputString, 0x00, sizeof(InputString));
//		NewLineReceived = 0;
//		return;
//	}
	
  //解析上位机发来的舵机云台的控制指令并执行舵机旋转
  //如:$4WD,PTZ180# 舵机转动到180度
  	if (StringFind((const char *)InputString, (const char *)"PTZ", 0) > 0)
	{
		int m_kp, i, ii;
        //寻找以PTZ开头,#结束中间的字符
		i = StringFind((const char *)InputString, (const char *)"PTZ", 0); 
		ii = StringFind((const char *)InputString, (const char *)"#", i);
		if (ii > i)
		{
			char m_skp[5] = {0};
			memcpy(m_skp, InputString + i + 3, ii - i -3);
			
			m_kp = atoi(m_skp);        //将找到的字符串变成整型

			servo_appointed_detection(180 - m_kp);//转动到指定角度m_kp
			NewLineReceived = 0;  
			memset(InputString, 0x00, sizeof(InputString));
			return;
		}
  	}

  //解析上位机发来的七彩探照灯指令并点亮相应的颜色
  //如:$4WD,CLR255,CLG0,CLB0# 七彩灯亮红色
  	 if (StringFind((const char *)InputString, (const char *)"CLR", 0) > 0)
 	{
		int m_kp, i, ii, red, green, blue;
		char m_skp[5] = {0};
		i = StringFind((const char *)InputString, (const char *)"CLR", 0);
		ii = StringFind((const char *)InputString, (const char *)",", i);
		if (ii > i)
		{			
			memcpy(m_skp, InputString + i + 3, ii - i -3);
			m_kp = atoi(m_skp);
			red =   m_kp;
		}
		i = StringFind((const char *)InputString, (const char *)"CLG", 0);
		ii = StringFind((const char *)InputString, (const char *)",", i);
		if (ii > i)
		{
			memcpy(m_skp, InputString + i + 3, ii - i -3);
			m_kp = atoi(m_skp);
			green =   m_kp;
		}
		i = StringFind((const char *)InputString, (const char *)"CLB", 0);
		ii = StringFind((const char *)InputString, (const char *)"#", i);
		if (ii > i)
		{
			memcpy(m_skp, InputString + i + 3, ii - i -3);
			m_kp = atoi(m_skp);
			blue =  m_kp;
			color_led_pwm(red, green, blue);//点亮相应颜色的灯
			NewLineReceived = 0;  
			memset(InputString, 0x00, sizeof(InputString));
			return;
		}
	}

  //解析上位机发来的通用协议指令,并执行相应的动作
  //如:$1,0,0,0,0,0,0,0,0,0#    小车前进
  if (StringFind((const char *)InputString, (const char *)"4WD", 0) == -1 &&
		                    StringFind((const char *)InputString,(const char *)"#",0) > 0)
  {
    //puts(InputString);
    //小车原地左旋右旋判断
    if (InputString[3] == '1')      //小车原地左旋
    {
      g_CarState = enTLEFT;
    }
    else if (InputString[3] == '2') //小车原地右旋
    {
      g_CarState = enTRIGHT;
    }
    else
    {
      g_CarState = enSTOP;
    }

    //小车鸣笛判断
    if (InputString[5] == '1')     //鸣笛
    {
      whistle();
    }

    //小车加减速判断
    if (InputString[7] == '1')     //加速,每次加50
    {
      g_CarSpeedControl += 20;
      if (g_CarSpeedControl > 200)
      {
        g_CarSpeedControl = 200;
      }

    }
    if (InputString[7] == '2')    //减速,每次减50
    {
      g_CarSpeedControl -= 20;
      if (g_CarSpeedControl < 100)
      {
        g_CarSpeedControl = 100;
      }
    }
	
    //舵机左旋右旋判断
/*    if (InputString[9] == '1') //舵机旋转到180度
    {
      servo_appointed_detection(180);
    }
    if (InputString[9] == '2') //舵机旋转到0度
    {
      servo_appointed_detection(0);
    }
*/
    //点灯判断
    if (InputString[13] == '1')//七彩灯亮白色
    {
      g_lednum++;
      if(g_lednum == 1){
			color_led_pwm(255, 255, 255);
		}
		else if(g_lednum == 2)
		{
			color_led_pwm(255,0,0);
		}
		else if(g_lednum == 3)
    {
			color_led_pwm(0,255,0);
		}
		else if(g_lednum == 4)
		{
			color_led_pwm(0,0,255);
		}
		else if(g_lednum == 5)
		{
			color_led_pwm(255,255,0);
		}
		else if(g_lednum == 6)
		{
			color_led_pwm(0,255,255);
		}
		else if(g_lednum == 7)
		{
			color_led_pwm(255,0,255);
		}
		else
		{
		   color_led_pwm(0,0,0);
		   g_lednum=0;
		}
    }
    if (InputString[13] == '2')//七彩灯亮红色
    {
      color_led_pwm(255, 0, 0);
    }
    if (InputString[13] == '3')//七彩灯亮绿灯
    {
      color_led_pwm(0, 255, 0);
    }
    if (InputString[13] == '4') //七彩灯亮蓝灯
    {
      color_led_pwm(0, 0, 255);
    }
		if (InputString[13] == '5')//青色
		{
		  color_led_pwm(0, 255, 255);
		}
	  if (InputString[13] == '6')//品红色
		{
		  color_led_pwm(255, 0, 255);
		}
	  if (InputString[13] == '7')//黄色
		{
		  color_led_pwm(255, 255, 0);
		}
		if (InputString[13] == '8') //七彩灯灭掉
    {
      color_led_pwm(0, 0, 0);
    }

    //灭火判断
    if (InputString[15] == '1')  //灭火
    {
      digitalWrite(OutfirePin, !digitalRead(OutfirePin));
    }

    //舵机归为判断
    if (InputString[17] == '1') //舵机旋转到90度
    {
      g_frontservopos = 90; 
    }
	//前后舵机前后左右转动
	switch(InputString[9])
	{
		case front_left_servo: g_frontservopos = 180 ;break;
		case front_right_servo: g_frontservopos = 0;break;
		case up_servo:  g_ServoState = enSERVOUP; break;
		case down_servo: g_ServoState = enSERVODOWN;break;
		case left_servo: g_ServoState = enSERVOLEFT;break;
		case right_servo: g_ServoState = enSERVORIGHT;break;
		case updowninit_servo: g_ServoState = enSERVOUPDOWNINIT;break;
		case stop_servo: g_ServoState = enSERVOSTOP;break;
	}

    //小车的前进,后退,左转,右转,停止动作
    if (g_CarState != enTLEFT && g_CarState != enTRIGHT)
    {
      switch (InputString[1])
      {
        case run_car:   g_CarState = enRUN;  break;
        case back_car:  g_CarState = enBACK;  break;
        case left_car:  g_CarState = enLEFT;  break;
        case right_car: g_CarState = enRIGHT;  break;
        case stop_car:  g_CarState = enSTOP;  break;
        default: g_CarState = enSTOP; break;
      }
    }

    memset(InputString, 0x00, sizeof(InputString));       //清空串口数据
    NewLineReceived = 0;

    //根据小车状态做相应的动作
    switch (g_CarState)
    {
      case enSTOP: brake(); break;
      case enRUN: run(g_CarSpeedControl, g_CarSpeedControl); break;
      case enLEFT: left(0, g_CarSpeedControl); break;
      case enRIGHT: right(g_CarSpeedControl, 0); break;
      case enBACK: back(g_CarSpeedControl, g_CarSpeedControl); break;
      case enTLEFT: spin_left(g_CarSpeedControl, g_CarSpeedControl); break;
      case enTRIGHT: spin_right(g_CarSpeedControl, g_CarSpeedControl); break;
      default: brake(); break;
    }
  }
}

总结一下:过程很曲折,结果还凑合,也是学习一种有趣新科技,以后慢慢带着孩子们学习探索,附上一些图片,结束收工!

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第66张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第67张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第68张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第69张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第70张图片树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第71张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第72张图片


树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第73张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第74张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第75张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第76张图片

树莓派Raspberry Pi 系统搭建和智能机器人小车的组装调试_第77张图片


你可能感兴趣的:(智慧积累)