@xiaoxiang 九轴数据没有通过topic发出来。在小强的底盘驱动包里面能获取到。详细参考这里。想要使用的话可能要修改驱动包。
ROS Group 产品服务
Product Service 开源代码库
Github 官网
Official website 技术交流
Technological exchanges 激光雷达
LIDAR ROS教程
ROS Tourials 深度学习
Deep Learning 机器视觉
Computer Vision

weijiz 发布的帖子
-
RE: 如何获取小强_mini的九轴imu数据啊?
-
AI Planning 简介
什么是AI planning我们从一个例子入手。假设现在我们在一个两层的大楼里面有一个扫地机器人。机器人需要能够在两层打扫。两层楼之间移动可以坐电梯也可以做楼梯。那么我们如何实现让机器人同时打扫两层的功能呢?
每一层的打扫都很容易实现,坐电梯的过程也很容易实现。问题在如如何让机器人理解到想要从一层到二层需要坐电梯。当然你可以通过写程序让机器人在打扫完成一层之后,再坐电梯去打扫另外一层。但是假如电梯现在没电了,需要走楼梯才能到另外一层,那你有要重新写一个程序。整个的动作规划写的非常死。这个就是AI Planning需要解决的问题。就是从一个初始状态,如何经过一系列操作到一个终止状态的问题。对于上面的扫地问题。就是如何从两层楼都很脏的状态到达两层楼都干净的状态。我们人当然可以很轻松的知道,打扫完成一层后,坐电梯或者走楼梯到达另外一层接着打扫。机器人也要自己能够做这种规划。这种规划就是AI Planning。
那么如何解决AI Planning的问题呢?
上面的说明其实已经比较清楚了。首先定义好初始状态和结束状态,然后再定义这两个状态之间的所有可能操作。这样就定义个一个解算问题。实际上已经是单纯的数学解算了。通过解算得到应该进行的动作。
AI Planning已经是一个非常成熟的问题了。在学术界有一套专门的语言用来定义AI Planning问题,这种语言叫做PDDL(Planning Domain Definition Language)即规划区域定义语言.下面是一个扫地机器人的例子。有两个房间机器人需要把两个房间全部打扫干净。
首先定义定义环境和机器人能够进行的操作。我们知道机器人可以打扫,充电和移动。
(define (domain state) //domain 命名,怎么喜欢怎么来 (:predicates (room ?r) //谓词,这里应该包括该文件中出现的所有的谓词逻辑,不然会error:Undefined,这里判断是不是room (robot ?rob) //这里则是判断一个抽象对象rob是不是robot (at-robot ?r) //判断robot是不是在room r中 (dirty ?r) //判断room r是不是脏的 (clean ?r) //判断room r是不是干净的 (fullPower ?rob)) //判断机器人robot是不是有电 (:action sweep //扫地动作 :parameters(?robot ?r) //设置参数 :precondition //前提条件(前件) (and //and的意思是以下括号内的都必须为真 (robot ?robot) //这个对象robot是robot为真 (room ?r) //这个对象r是room为真 (dirty ?r) //这个r是dirty的 (at-robot ?r) //而且robot在r中刚好为真 (fullPower ?robot) //机器人有电 ) :effect //满足以上条件,执行扫地动作,效果为如下 (and (clean ?r) //r已经干净了 (not (dirty ?r) //r不脏 ) (not (fullPower ?robot) //机器人没电,默认为扫一个房间即耗尽电量 ) ) ) (:action charging //充电动作 :parameters(?robot ?r) //两个参数为robot跟r,就近充电 :precondition (and (robot ?robot) (room ?r) (at-robot ?r) (clean ?r) (not (fullPower ?robot) ) ) :effect (and (fullPower ?robot) //充满电,可进行下一动作 ) ) (:action move //移动动作 :parameters (?from ?to) //从from移动到to :precondition (and (dirty ?to) //移动的前提:to还是脏的,from已经扫干净了,而且robot在扫干净的房间里,想移动到脏的房间 (clean ?from) (room ?from) (room ?to) (at-robot ?from) ) :effect (and (at-robot ?to) //效果:到达脏的房间,离开干净的房间 (not (at-robot ?from) ) ) ) )
然后定义问题,我们需要从两个房子都脏的状态到达两个房子都干净的状态。
(define (problem solve) (:domain state) (:objects rooma roomb robot //设置对象,这个例子比较简单,只有三个对象,房间a房间b还有机器人 ) (:init //初始化起始状态,有两个房间,都是脏的,机器人在房间a并且满电,注意此处不用(room ?rooma) (room rooma) (room roomb) (robot robot) (dirty rooma) (dirty roomb) (at-robot rooma) (fullPower robot) ) (:goal //设定这个规划问题最终要达到的目标状态,两个房间是干净的,并且机器人仍旧是满电待命的 (and (clean rooma) (clean roomb) (fullPower robot) ) ) )
然后把这个问题交给AI Planning解算程序就能够得到把两个房间都打扫干净机器人应该进行的操作了。
这个过程和一般的扫地机器人过程有什么不同呢?区别在于整个动作是机器人自己规划的,机器人理解了整个打扫过程。比如说自动充电,并不是因为电量低而去充电。是由于打扫屋子的时候需要有电,所有在打扫前如过没有电的话需要去充电。
这样我们在制作机器人的时候就可以着重于实现机器人的每个细节操作。而整体的规划部分交由机器人自己去完成。
AI Planning 在 ROS中的实现
ROS中有AI Planning 的实现叫做 ROS Plan
其结构图如下可以看到整个结构已经非常完善了。
knowledge base
就是机器人的知识,也就是我们的机器人能够进行的操作。通过这个解算器我们的机器人就可以自己规划动作了。 -
使用语音控制导航
原理简介
在这篇教程中,我们使用语音去控制机器人导航。比如对机器人说到一号点机器人就会自动前往一号目标点。原理上是通过话筒收集声音,经过语音识别得到语音对应的文字。然后将这些文字和指令进行匹配,如果成功匹配则执行对应的指令。如果没有匹配成功则将指令传递给自然语言处理节点。这个节点处理后得到对应的回答文字,文字发送给语音合成节点。语音合成节点合成语音后通过喇叭播放出来。
整个的结构大致如下面所示
软件安装
首先如果要用来控制导航,你的机器上要安装伽利略导航系统,详细的安装方法可以参照这里
安装语音处理程序
相信信息可以参照: 在小强中使用语音识别和语音合成
安装自然语言处理程序
cd [到你的工作空间的src文件夹中] git clone https://github.com/BluewhaleRobot/xiaoqiang_nlp
安装声音指令控制程序
cd [到你的工作空间的src文件夹中] git clone https://github.com/BluewhaleRobot/xiaoqiang_audio_controller
开始使用
roslaunch xiaoqiang_audio_controller audio_controller.launch
如果没有提示错误那么应该就可以正常使用了。
正常使用的情况下应该会如下显示现在对小强说开始导航,就可以听到小强对你回答说正在开启导航。
要使用语音控制导航功能,首先需要使用伽利略导航系统建立一个地图。并且能够保证正常使用。详细的方法可以参照伽利略导航系统的使用
目前支持的语音指令有
- 电压:查询当前的电池电压
- 开启导航: 开始导航程序
- 到xx号点: 到xx号目标点
- 取消,暂停,继续:取消,暂停,继续当前任务
- 关机
常见问题
- 语音识别效果不好
尝试调整launch文件中的min_volum。如果环境噪音比较大尝试调大一些,反之则调小一些。
-
在小强中使用语音识别和语音合成
为小强添加语音识别和语音合成功能,安装之后小强就能说会道了。当然首先要给小强添加话筒和喇叭。
处理后端可以设置为科大迅飞语音或百度语音。也可以利用audio_capture实时的处理从话筒中收到的数据。
安装,小强已经默认安装好了
cd [到你的工作空间的src文件夹中] git clone https://gitee.com/BluewhaleRobot/xiaoqiang_tts # 对于python3环境需要切换至python3分支 cd xiaoqiang_tts git checkout python3 git clone https://gitee.com/BluewhaleRobot/xiaoqiang_audio cd xiaoqiang_audio git checkout noetic cd ../.. catkin_make -DCATKIN_WHITELIST_PACKAGES="xiaoqiang_tts"
程序使用的是我自己申请的百度和迅飞参数,最好自己再去百度和迅飞的官网申请一个。然后在launch文件中设置自己的appid,再catkin_make一下就可以了。
使用
使用语音合成功能
启动百度语音合成
roslaunch xiaoqiang_tts tts_baidu.launch
输入Topic 消息类型 /xiaoqiang_tts/text std_msgs/String 输出Topic 消息类型 :– :– /xiaoqiang_audio/audio audio_common_msgs/AudioData 发布消息,测试语音
rostopic pub /xiaoqiang_tts/text std_msgs/String 测试一下语音合成 -1
此时如果正常应该能够听到"测试一下语音合成"的声音
启动科大迅飞语音合成
注意要先关闭百度tts节点
roslaunch xiaoqiang_tts tts_xunfei.launch
测试方法和上面一样。正常应该会听到合成的声音。
使用语音识别功能
启动百度语音识别
roslaunch xiaoqiang_tts asr_baidu.launch
新开一个终端接收语音识别结果
rostopic echo /xiaoqiang_tts/text
现在可以开始说话了,程序会自动监听环境声音并进行分句。当你停止说话时会开始处理你的这一句的结果。
输入Topic 消息类型 /xiaoqiang_audio/audio audio_common_msgs/AudioData 输出Topic 消息类型 :– :– /xiaoqiang_tts/text std_msgs/String 启动迅飞语音识别
roslaunch xiaoqiang_tts asr_xunfei.launch
使用方法和上面的一样
同时启动语音识别和语音合成
使用科大迅飞语音合成和语音识别
roslaunch xiaoqiang_tts tts_xunfei.launch roslaunch xiaoqiang_tts asr_xunfei.launch
现在你说一句话机器人就会跟着你说一句话。
和机器人对话
roslaunch xiaoqiang_nlp talk_bot.launch
执行这个指令后机器人会和你对话交流,快和它说说话吧。
参数说明
详细的参数说明请参照launch文件内的注释
-
解决USB设备不断重新连接问题
在使用USB设备的时候有时会发现设备不断重新连接的情况。比如如果看syslog会发现这样的记录
Jun 21 05:36:09 hoperun-developer kernel: [ 6862.683901] usb 1-1.1: New USB device found, idVendor=05a3, idProduct=9230 Jun 21 05:36:09 hoperun-developer kernel: [ 6862.683918] usb 1-1.1: New USB device strings: Mfr=2, Product=1, SerialNumber=0 Jun 21 05:36:09 hoperun-developer kernel: [ 6862.683924] usb 1-1.1: Product: USB 2.0 Camera Jun 21 05:36:09 hoperun-developer kernel: [ 6862.683929] usb 1-1.1: Manufacturer: HD Camera Manufacturer Jun 21 05:36:09 hoperun-developer kernel: [ 6862.718478] uvcvideo: Found UVC 1.00 device USB 2.0 Camera (05a3:9230) Jun 21 05:36:09 hoperun-developer kernel: [ 6862.759942] input: USB 2.0 Camera as /devices/platform/soc/soc:hisi_dwc3/ff100000.dwc3/xhci-hcd.1.auto/usb1/1-1/1-1.1/1-1.1:1.0/input/input34 Jun 21 05:36:09 hoperun-developer kernel: [ 6862.770842] hub 1-1:1.0: hub_ext_port_status failed (err = -71) Jun 21 05:36:10 hoperun-developer kernel: [ 6863.707508] usb 1-1.1: USB disconnect, device number 45 Jun 21 05:36:10 hoperun-developer kernel: [ 6863.899217] usb 1-1: reset high-speed USB device number 2 using xhci-hcd Jun 21 05:36:10 hoperun-developer kernel: [ 6864.386790] usb 1-1.1: new high-speed USB device number 46 using xhci-hcd Jun 21 05:36:10 hoperun-developer kernel: [ 6864.559916] usb 1-1.1: New USB device found, idVendor=05a3, idProduct=9230 Jun 21 05:36:10 hoperun-developer kernel: [ 6864.559939] usb 1-1.1: New USB device strings: Mfr=2, Product=1, SerialNumber=0 Jun 21 05:36:10 hoperun-developer kernel: [ 6864.559948] usb 1-1.1: Product: USB 2.0 Camera Jun 21 05:36:10 hoperun-developer kernel: [ 6864.559957] usb 1-1.1: Manufacturer: HD Camera Manufacturer Jun 21 05:36:10 hoperun-developer kernel: [ 6864.574552] uvcvideo: Found UVC 1.00 device USB 2.0 Camera (05a3:9230) Jun 21 05:36:11 hoperun-developer kernel: [ 6864.616686] input: USB 2.0 Camera as /devices/platform/soc/soc:hisi_dwc3/ff100000.dwc3/xhci-hcd.1.auto/usb1/1-1/1-1.1/1-1.1:1.0/input/input35 Jun 21 05:36:12 hoperun-developer kernel: [ 6865.819516] usb 1-1.1: USB disconnect, device number 46 Jun 21 05:36:12 hoperun-developer kernel: [ 6866.011230] usb 1-1: reset high-speed USB device number 2 using xhci-hcd Jun 21 05:36:12 hoperun-developer kernel: [ 6866.438866] hub 1-1:1.0: hub_ext_port_status failed (err = -71) Jun 21 05:36:13 hoperun-developer kernel: [ 6866.710782] usb 1-1.1: new high-speed USB device number 47 using xhci-hcd Jun 21 05:36:13 hoperun-developer kernel: [ 6866.883601] usb 1-1.1: New USB device found, idVendor=05a3, idProduct=9230 Jun 21 05:36:13 hoperun-developer kernel: [ 6866.883617] usb 1-1.1: New USB device strings: Mfr=2, Product=1, SerialNumber=0 Jun 21 05:36:13 hoperun-developer kernel: [ 6866.883623] usb 1-1.1: Product: USB 2.0 Camera Jun 21 05:36:13 hoperun-developer kernel: [ 6866.883627] usb 1-1.1: Manufacturer: HD Camera Manufacturer Jun 21 05:36:13 hoperun-developer kernel: [ 6866.910450] uvcvideo: Found UVC 1.00 device USB 2.0 Camera (05a3:9230) Jun 21 05:36:13 hoperun-developer kernel: [ 6866.951825] input: USB 2.0 Camera as /devices/platform/soc/soc:hisi_dwc3/ff100000.dwc3/xhci-hcd.1.auto/usb1/1-1/1-1.1/1-1.1:1.0/input/input36 Jun 21 05:36:14 hoperun-developer kernel: [ 6867.739554] usb 1-1.1: USB disconnect, device number 47
这可能是由于USB的不同驱动相互冲突导致的。在编译内核的时候禁用掉不相关的驱动。比如如果你是USB3.0的接口,就禁用掉2.0和1.0的驱动。
然后编译更新内核就可以了。
-
伽利略视觉导航安装和使用
2018.6之后的镜像里面已经默认安装伽利略导航系统,用户不用再手动安装
通过镜像安装
推荐使用此方法安装,镜像的安装方法如此处所示。
安装完成之后,连接网路更新程序bwupdate pro
正常情况下会提示软件更新成功
安装完成后下载安装伽利略导航客户端。
如果想要获得比较好的效果推荐先标定摄像头参数。给小强连上电池,打开主机,地盘通电。之后连接网络就可以通过客户端操作了。
具体的建图和导航操作可以参照伽利略导航用户手册客户端打开后可能会提示没有伽利略证书文件。按照提示说明联系客服获取证书文件。目前伽利略系统的免费使用只针对小强用户,所以如果你是小强用户可以免费获取。
通过源代码安装
通过源代码安装可能会稍微复杂一些,需要把对应的程序更新到正确的版本。对于对小强和ROS比较熟悉的用户可以采用这种方法,但是并不推荐。
- 检查软件源
运行bwcheck
如果软件源设置有错误则根据提示信息修改软件源
比如当前system_monitor的软件源是http://git.bwbot.org/publish/system_monitor,要设置为https://github.com/bluewhalerobot/system_monitor则可执行下面的指令
git remote set-url origin https://github.com/bluewhalerobot/system_monitor
- 设置软件源到对应分支
软件源和对应的分支如下
XQ4的软件包和对应分支
软件包 分支 addwa_local_planner master galileo_serial_server master nav_test service_bot NLlinepatrol_planner master ORB_SLAM2 master startup service_bot system_monitor kinetic_service_bot xiaoqiang_udrf master xqserial_server master XQ5 的软件包和对应分支
软件包 分支 addwa_local_planner master galileo_serial_server master nav_test service_bot_lungu NLlinepatrol_planner lungu ORB_SLAM2 master startup service_bot system_monitor kinetic_service_bot_lungu xiaoqiang_udrf lungu xqserial_server lungu 通过
git checkout
到对应的分支,如果本地有修改要先把自己修改备份好,然后更新版本。通过执行
bwupdate pro
更新相关程序
- 添加证书及认证程序
没有证书及认证程序,程序将无法正常运行。执行下面的程序进行安装。
mkdir -p ~/Documents/.galileo cd ~/Documents/.galileo wget http://www.bwbot.org/s/TQCpyP - O 'verify_tool' chmod +x verify_tool
- 编译生成程序
cd ~/Documents/ros catkin_make -DCATKIN_WHITELIST_PACKAGES="" -DCMAKE_BUILD_TYPE=Release
- 安装完成后可以先通过
roslaunch startup startup.launch
如果没有错误提示。在本地电脑上安装好导航客户端,应该就可以正常连接了。
客户端打开后可能会提示没有伽利略证书文件。按照提示说明联系客服获取证书文件。目前伽利略系统的免费使用只针对小强用户,所以如果你是小强用户可以免费获取。
常见问题
-
导航效果不太好,位置有偏差
可能是由于摄像头没有标定好的原因,参照标定摄像头参数进行标定。 -
之前能够正常使用的地图,现在不能使用了
可能是环境发生了比较大的变化,尤其是光线变化很大的情况。比如白天建立的地图晚上可能无法使用。这时可以使用更新地图的功能,对以前的地图进行更新。这样就可以适应于变化的场景了。 -
更多的常见问题可以查看伽利略导航用户手册
-
更新程序后开启服务或建图一直处于丢失状态且无法显示特征点图像
可能是导航程序启动失败了,可以手动启动startup.launch
sudo service startup stop roslaunch startup startup.launch
然后使用客户端操作开启服务或建图,看看是否会有错误提示。
- 检查软件源
-
ros 发布软件包时提示git-bloom-patch trim错误
在发布软件包的时候遇到了下面的问题
[git-bloom-patch trim]: You are trying to set the trim sub directory to xiaoqiang_navigation/xiaoqiang_navigation, but it is already set to xiaoqiang_navigation. [git-bloom-patch trim]: Changing the sud directory is not advised. If you are sure you want to do this, use '--force'
这是由于软件包的路径发生了改变,但是release软件源内仍然存储着以前的包结构。
比如原来xioqiang_navigation是一个软件包,我把多个包放到了这个包内,结果包的路径就成了xiaoqiang_navigation/xiaoqiang_navigation。解决方法就是直接修改release 软件源内的记录。比如发布到kinetic版本
找到patchs/release/kinetic/xiaoqiang_navigation分支,打开patches.conf文件
将trim中包的路径改成当前的路径。
比如我的情况就是[patches] trim = xiaoqiang_freenect/xiaoqiang_freenect base = 4ecb3b9 trimbase = a145a72 parent = upstream previous = 38bc215
改动完成后提交就可以了。
再次执行发布指令就不会有错误了。 -
如何标定单目摄像头
在本文中我们会使用camera_calibration 的 cameracalibrator.py节点来标定单目摄像头。
1.开始之前
确保你满足以下几个条件
- 一个很大的尺寸已知的标定板。在这篇教程中我们采用8x6的边长为108mm的标定板。
标定过程中会使用方格的定点,所以一个8x6的板子实际上是9个方格宽和7个方格高。就和下面的例子一样。 - 一个空旷的5mx5m的环境。而且不能有类似的标定板。
- 一个能够通过ROS发布图像数据的摄像头
2. 编译
首先下载软件依赖,编译软件
rosdep install camera_calibration
确认你的摄像头正在通过ROS发布图像数据。通过查看topic来确认图像消息已经被发布。
rostopic list
这样会显示出所有已经发布的topic,看看里面是不是有image_raw的topic。大多数的ROS摄像头驱动所发布的消息是
/camera_node/camera_info /camera_node/image_raw
如果你有多个摄像头或者驱动程序有自己的命名空间,那么你的topic可能会与此有些不同。
3. 运行标定节点
你需要载入需要标定的图像话题来开始标定
rosrun camera_calibration cameracalibrator.py --size 8x6 --square 0.108 image:=/camera_node/image_raw camera:=/camera_node
这样会打开一个标定窗口。其中我们的棋盘格会被高亮显示。
如果没有成功打开窗口程序,可以尝试增加下面的参数
--no-service-check
如果你没有发现任何彩色的点,那么你要确认你数的是顶点个数而不是方块的个数。
3.1 双棋盘格
从Diamondback版本开始,你可以使用多个尺寸的棋盘格来标定摄像头。如果想要使用多个棋盘格,你需要指定多个–size --square选项。确保棋盘格有不同的尺寸,这样标定程序才能够把它们区分出来。
4. 移动棋盘格
为了能够得到较好的标定结果,你需要在摄像头前来回移动棋盘格。保证以下几点
- 棋盘格在摄像头视野范围的上下左右四个角
X条表示视野的左右范围
Y条表示视野的上下范围
size条表示在摄像头前后和倾斜 - 棋盘格占满整个视野
- 棋盘格在上下左右倾斜
在每一步中都要保持不动直到图片显示高亮颜色。
在你移动标定板的过程中你会看到三个条的长度在不断的增加。当标定按钮亮起的时候,你就已经有足够的数据开始标定了。点击标定按钮开始标定。
标定过程会需要几分钟。窗口可能会失去响应,但是一定要继续等,程序在正常运行中。- 标定结果
当标定完成之后,你会在终端中看到标定结果。同时窗口也会显示标定后的图像。
一个成功的标定结果会使现实情况中直的东西在图像中仍然显示是直的。
失败的标定通常会没有图像显示,或者不能够保证直线。
成功标定之后你可以使用上面的滑条来调整显示区域方块的尺寸。尺度0代表图像中的所有区域的像素点都是有效的。标定区域是没有边界的,但是原始图像的一些像素点会被丢弃。尺度1代表所有的原始图像的像素点都是可见的,但是标定区域却没有点。
D = [-0.33758562758914146, 0.11161239414304096, -0.00021819272592442094, -3.029195446330518e-05] K = [430.21554970319971, 0.0, 306.6913434743704, 0.0, 430.53169252696676, 227.22480030078816, 0.0, 0.0, 1.0] R = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0] P = [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0] # oST version 5.0 parameters [image] width 640 height 480 [narrow_stereo/left] camera matrix 430.215550 0.000000 306.691343 0.000000 430.531693 227.224800 0.000000 0.000000 1.000000 distortion -0.337586 0.111612 -0.000218 -0.000030 0.0000 rectification 1.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 1.000000 projection 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000
如果你对标定结果比较满意,可以点击提交按钮将参数发送给摄像头存储起来。图形程序会自动退出,终端上会显示保存标定数据。
6. 保存参数至usb_cam
commit 之后在
~/.ros/camera_info
之中会创建一个标定参数文件。将其复制到usb_cam包中cp ~/.ros/camera_info/head_camera.yaml $(rospack find usb_cam)/launch/ov2610.yaml
标定鱼眼摄像头
rosrun camera_calibration cameracalibrator.py --size 8x6 --square 0.041 --fisheye-check-conditions --fisheye-recompute-extrinsicsts --fisheye-fix-skew image:=/multi/front/image_raw camera:=/multi/front
- 一个很大的尺寸已知的标定板。在这篇教程中我们采用8x6的边长为108mm的标定板。
-
Hikey 970 USB转串口驱动安装
在已经发布的hikey 970 Debian系统中是没有包含U转串驱动的。在没有安装驱动的情况下插上U转串设备时在/dev下面是没有ttyUSB设备的。
安装驱动需要自己编译对应的驱动程序。安装方法如下。
下载Linux内核源代码
执行下面的指令下载hikey linux内核源代码
git clone --single-branch -b hikey970-v4.9 --depth=1 https://github.com/96boards-hikey/linux # 切换到hikey 970分支 cd linux git checkout hikey970-v4.9
配置内核源代码
获取内核配置文件
cp /proc/config.gz ~/ gzip -d ~/config.gz # 进入内核源代码文件夹 cd ~/linux # 将内核配置文件复制到此处 make mrproper cp ~/config .config sudo chmod 666 .config # 配置内核文件 sudo apt-get install libncurses5-dev sudo apt-get install bc make menuconfig
正常情况下会显示如下的内核配置界面
找到 Device Drivers–>USB Support --> USB Serial Converter Support
将其设置成M。编译内核module有两种模式,一种是直接编译到内核里面,另一种是编译成独立的.ko文件module。我们采用的是.ko的模式。这样不用重新编译内核更加方便。
继续进入此选项将想要编译的驱动设置成M,如果不清楚自己的型号可以全部设置成M
设置完成后选择保存,之后再退出此界面编译驱动module
执行下面的语句开始编译内核
make modules_prepare sudo make -j4 modules # 需要执行这个才会生成modules.order,modules.builtin make M=drivers/usb/serial
正常情况下应该能够看到终端输出如下
LD drivers/usb/serial/built-in.o Building modules, stage 2. MODPOST 50 modules CC drivers/usb/serial/aircable.mod.o LD [M] drivers/usb/serial/aircable.ko CC drivers/usb/serial/ark3116.mod.o LD [M] drivers/usb/serial/ark3116.ko CC drivers/usb/serial/belkin_sa.mod.o LD [M] drivers/usb/serial/belkin_sa.ko CC drivers/usb/serial/ch341.mod.o LD [M] drivers/usb/serial/ch341.ko CC drivers/usb/serial/cp210x.mod.o LD [M] drivers/usb/serial/cp210x.ko CC drivers/usb/serial/cyberjack.mod.o LD [M] drivers/usb/serial/cyberjack.ko CC drivers/usb/serial/cypress_m8.mod.o LD [M] drivers/usb/serial/cypress_m8.ko CC drivers/usb/serial/digi_acceleport.mod.o LD [M] drivers/usb/serial/digi_acceleport.ko CC drivers/usb/serial/empeg.mod.o LD [M] drivers/usb/serial/empeg.ko CC drivers/usb/serial/f81232.mod.o LD [M] drivers/usb/serial/f81232.ko CC drivers/usb/serial/ftdi_sio.mod.o LD [M] drivers/usb/serial/ftdi_sio.ko CC drivers/usb/serial/garmin_gps.mod.o LD [M] drivers/usb/serial/garmin_gps.ko CC drivers/usb/serial/io_edgeport.mod.o LD [M] drivers/usb/serial/io_edgeport.ko CC drivers/usb/serial/io_ti.mod.o LD [M] drivers/usb/serial/io_ti.ko CC drivers/usb/serial/ipaq.mod.o LD [M] drivers/usb/serial/ipaq.ko CC drivers/usb/serial/ipw.mod.o LD [M] drivers/usb/serial/ipw.ko CC drivers/usb/serial/ir-usb.mod.o LD [M] drivers/usb/serial/ir-usb.ko CC drivers/usb/serial/iuu_phoenix.mod.o LD [M] drivers/usb/serial/iuu_phoenix.ko CC drivers/usb/serial/keyspan.mod.o LD [M] drivers/usb/serial/keyspan.ko CC drivers/usb/serial/keyspan_pda.mod.o LD [M] drivers/usb/serial/keyspan_pda.ko CC drivers/usb/serial/kl5kusb105.mod.o LD [M] drivers/usb/serial/kl5kusb105.ko CC drivers/usb/serial/kobil_sct.mod.o LD [M] drivers/usb/serial/kobil_sct.ko CC drivers/usb/serial/mct_u232.mod.o LD [M] drivers/usb/serial/mct_u232.ko
编译完成之后可以在drivers/usb/serial中看到生成了许多.ko文件。这些就是我们需要的驱动文件。
安装驱动module
# 创建module文件目录 sudo mkdir -p /lib/modules/$(uname -r)/kernel/drivers/usb/serial/ sudo cp drivers/usb/serial/*.ko /lib/modules/$(uname -r)/kernel/drivers/usb/serial/ # 复制depmod依赖文件 sudo cp ~/linux/modules.order /lib/modules/$(uname -r)/ sudo cp ~/linux/modules.builtin /lib/modules/$(uname -r)/ # 生成对应文件 cd /lib/modules/$(uname -r) sudo depmod -a # 加载驱动 sudo modprobe pl2303
测试驱动
查看驱动是否正常加载
lsmod
正常输出如下
Module Size Used by ftdi_sio 49152 0 pl2303 20480 0 usbserial 40960 2 ftdi_sio,pl2303
可以看到pl2303驱动已经成功加载。
这时再插上U转串试一下
可以看到已经有ttyUSB0了。至此串口已可以正常使用了。
自动加载驱动
修改 /etc/modules文件
在其中加入想要加载的内核模块的名称,比如对于我的设备就是pl2303。文件内容如下# /etc/modules: kernel modules to load at boot time. # # This file contains the names of kernel modules that should be loaded # at boot time, one per line. Lines beginning with "#" are ignored. pl2303
保存退出,下次在系统启动时就会自动加载这个驱动了。
-
hikey 970 debian wifi 驱动修复
执行下面指令安装无线网卡驱动
sudo apt-get install firmware-ti-connectivity
连接wifi
sudo nmcli device wifi connect TP-LINK_5G_A134 password blueWhale #其中TP-LINK_5G_A134为wifi的ssid,blueWhale为wifi的密码,请根据自己的情况设置。
-
在hikey 970上安装debian并运行ROS
经过长时间的等待hikey 970的debian系统终于发布了。
首先保证机器能够正常运行Android,基础固件没有问题。下载完成后解压文件
设置机器开关
刷系统前需要让板子开机后进入fastboot模式,这个可以通过设置板子上的开关完成。把开关状态设置成On Off On Off。然后上电启动。
刷入Debian 系统
在Linux下刷入Debian系统
如果你是Linux系统则按照此处的指令刷入Debian系统
#进入解压后的文件夹 fastboot flash xloader sec_xloader.img fastboot flash ptable prm_ptable.img fastboot flash fastboot l-loader.bin fastboot flash fip fip.bin fastboot flash boot boot2grub.uefi.img fastboot flash system rootfs.sparse.img # 注意刷入系统文件的过程需要很多时间,需要耐心等待 # 刷入分区表 wget http://www.bwbot.org/s/uhzKfx -O '64gtoendprm_ptable.img' fastboot flash ptable 64gtoendprm_ptable.img
在Windows 下刷人Debian系统
如果你是在Windows环境下刷入Debian系统则执行下面的指令
#进入解压后的文件夹 .\update_Hikey970.bat
整个刷入时间要很久,需要耐心等待
刷入完成后再次刷入分区表
分区表下载地址
下载完成后放入当前文件夹执行下面的指令fastboot.exe flash ptable 64gtoendprm_ptable.img
启动系统
拔掉板子的电源,将开关拨至 On Off Off Off 状态,然后上电。等待板子启动完成。给板子插上网线,这时候可以看到网口的灯在闪烁。说明板子已经正常启动了。
在路由器上查找板子的ip
正常情况下应该能够看到如图所示的设备。然后通过ssh 连接就可以了。用户名和密码都是hoperun
ssh hoperun@xxx.xxx.xxx.xxx
安装常用工具
sudo apt-get install bash-completion #增加自动补全功能 sudo apt-get install htop #查看系统资源使用情况工具 /bin/bash -c "$(curl -sL https://git.io/vokNn)" #安装apt-fast,安装软件更快
安装ROS
ROS的安装过程和一般的ROS版本安装是一样的,但是奇怪的是我的源里面没有找到kinetic版本。下面安装的是melodic版本。可能是这个版本的Debian只支持melodic。
添加ROS软件源
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
添加key
sudo apt-fast update sudo apt-fast install dirmngr sudo apt-key adv --keyserver hkp://ha.pool.sks-keyservers.net:80 --recv-key 421C365BD9FF1F717815A3895523BAEEB01FA116
开始安装
sudo apt-fast update sudo apt-fast install ros-melodic-desktop-full -y
配置环境
sudo rosdep init rosdep update echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc source ~/.bashrc
安装编译软件包依赖
sudo apt-get install python-rosinstall python-rosinstall-generator python-wstool build-essential
测试一下
成功了!
-
RE: hikey 970 开发板刷基础固件和Android系统
@how0723 已经有了我今天才找到,可以参照这里 http://smartfire.cn/thread-748-1-1.html
不过我还没测试。 -
小强ROS教程v1.1版 (2)基础操作介绍和局域网键盘遥控
开始之前
以下的教程重点在于使用小强的各种功能,并不包含对ROS的教学。如果您是ROS的初学者,请在进行下面的教程的同时跟进学习ROS基础知识。 请不要只跟着我们的教程输入指令,要理解每一个命令的具体含义。遇到问题时也要善于自己寻找问题的原因,这些对于以后的工作学习都会很有帮助。如果您遇到了关于教程或者ROS不理解的地方也欢迎在我们ROS交流群(538456117)中提问。
首先请您自行依据线标提示将小强接线连接好,完整结构如下图所示
1.配置小强网络
使用赠送的hdmi转vga插头(小强mini可以直接使用vga),将显示器和键盘鼠标接入小强主机后开机,进入主机ubuntu系统。
小强默认密码是 xiaoqiang
点击下图位置,选择需要接入的无线网络
接入网络后,点击下图位置得到小强的实际ip地址,后续教程会频繁使用到这个ip信息
小强的ROS默认工作目录在这里
现在小强已经配置完成了,关机后拔掉键盘鼠标和显示器,下次小强直接开机就能使用
2.本地遥控端配置
本地遥控端最好是一台安装ubuntu16.04 64bit操作系统的x86主机,如果您只有windows平台,可以先安装vmware虚拟机 下载地址,然后在虚拟机里安装ubuntu。
镜像下载和安装教程, 推荐安装使用已经配置好ROS的小强镜像。
最后保证虚拟机必须和小强在同一个局域网下
系统部署完后开始配置ubuntu系统建议安装atom编辑器(小强镜像系统已经安装),可以方便地对小强主机上的软件包进行代码编辑
键盘遥控
(1)在本地遥控端打开一个终端
(2)通过ssh连接小强,请将xxx换成实际的ip
ssh xiaoqiang@xxx.xxx.xxx.xxx
(3)启动遥控程序
# 在启动小强驱动程序后执行 rosrun xiaoqiang_controller controller.py # 如果小强驱动程序未执行,请先执行 roslaunch xiaoqiang_bringup xiaoqiang_robot.launch
(4)可以开始通过方向键来控制小强的移动了。空格键是停止。Ctrl + C 退出程序。
小车的移动控制是通过发布名字为
/cmd_vel
的topic来实现的,这个topic是geometry_msg
下的Twist
类型,它的发布方法请参考ros官方文档,小车上的controller.py
源代码也是一份不错的参考实例。 -
小强ROS教程v1.1版说明和目录
说明
由于之前版本的教程和小强相关的程序都是直接通过操作源代码完成的。源代码在编写的过程中也没有考虑到通用性,源代码中有些固定的路径。这样会导致用户无法更换用户名。同时软件的依赖安装和更新维护使用起来都不太方便。用户在自己进行开发的过程中,自己的源代码和小强的源代码混在一起也导致整个工作空间代码的开发和维护更加困难。
为了解决以上的问题我们在把小强的相关源代码发布成标准的ROS软件包。用户可以像使用其他ROS软件包一样直接通过apt-get 安装和更新。
同时以前很多小强相关的软件包并没有很好的遵循ROS的规范,在这个的新版本中也做了重构。
教程v1.1版对应的源代码发布在 Github上,欢迎使用和提issue。
从上面的说明可以知道相关源代码做了很大程度上的改动。所以v1.1版的教程和以前版本的教程是不通用的。新的代码也还在开发中,如果你是初学者不建议使用。如果你对小强比较熟悉,建议使用新版本的教程。
v1.1版教程目录
-
小强ROS教程v1.1版 (1)软件安装和创建工作空间
注意本教程和以前版本的教程并不兼容,按照这里的教程操作可能会导致无法再使用之前版本的教程的例子
安装小强软件
首先安装好ROS系统并配置好环境变量。
然后安装小强的相关软件sudo apt-get install ros-kinetic-xiaoqiang
等待安装完成。
由于ROS软件包的发布需要一定的时间,如果无法找到ros-kinetic-xiaoqiang软件包,可以通过设置ros软件源来实现。
- 把
/etc/apt/sources.list.d/ros-latest.list
文件中的 ros 改为 ros-shadow-fixed - 执行
sudo apt-get update
- 安装小强软件包
sudo apt-get install ros-kinetic-xiaoqiang
创建工作空间
进入想要创建工作空间的路径,比如~/Documents/ros
cd ~/Documents/ros
创建src文件夹
mkdir src
执行catkin_make
catkin_make
添加环境变量
在~/.bashrc文件的末尾添加source ~/Documents/ros/devel/setup.bash # 根据你的工作空间的位置不同这个指令也会不同
这样就创建完成工作空间了。
- 把
-
RE: hikey 970 开发板刷基础固件和Android系统
@how0723 有预装的Android系统。第一次上电要等一段时间。还要注意那几个开关的位置要设置成On Off On Off
-
ROS C++代码风格说明
- 动机
代码风格是重要的。整洁一致的代码风格能够让代码更加容易阅读,更容易测试和维护。我们努力编写程序不仅让它现在能够正常工作,同时也要保证能够在许多年后被其他开发者继续使用。
为了这个目的,我们规定了一系列规则。我们的目标是鼓励敏捷且合理的能够让其他开发者容易理解的代码。
这些是指导准则并不是规则。这篇文章并不完全禁止一些C++的模式或者功能,只是介绍更好的实现方式。当偏离这个准则的时候你要在代码中说明原因。
最重要的是保持一致性。尽可能的遵守此准则。但是如果你是在更改别人的已经存在的软件包。那就要遵循别人已经存在的代码风格。
- ROS代码自动格式规范化
为什么在我们忙着创造机器人的时候要话大量时间去手动修改代码格式。用机器人来自动格式规范化你的代码吧。这些指令能够自动规范你的代码。
- 有很多C++的代码是在这个标准发布之前写的。所有在代码库中有很多代码是不符合这个标准的。下面是在使用这些代码是的建议
- 所有新开发的软件包都应该符合这个标准
- 除非你有很多空闲时间,否则不要去尝试把代码修改成符合此规则。
- 如果你是一个软件包的作者,请花时间把软件包更新为符合这个规则。
- 如果你在小范围的修改一个软件包,请遵循这个软件包自己的代码风格。不要混合不同风格的代码。
- 如果你在大范围修改一个软件包,那么请花时间把这个软件包修改为符合此代码风格。
- 命名规则
下面的词用来代指各种命名规则- CamelCased: 第一个字母大写,以后每个词的首字母大写
- camelCased: 第一个字母小写,以后每个词的首字母大写
- under_scored: 只使用小写字母,不同词之间用下划线隔开
- ALL_CAPITALS: 只使用大写字母,不同的词间用下划线隔开。
4.1 软件包
ROS软件包的命名规则为under_scored
这个不止适用于C++的代码。查看开发指南了解更多信息4.2 话题和服务
ROS话题和服务的名称命名规则为under_scored
同样这也不止适用于C++代码。查看开发指南了解更多信息4.3 文件
所有的文件名都是 under_scored
源文件的扩展名为.cpp
头文件的扩展名为.h
要描述清楚,比如不要使用laster.cpp使用hokuyo_topurg_laser.cpp.
如果一个文件主要是实现一个class.那么就根据这个类去命名文件。比如 ActionServer 的文件为action_server.h4.3.1 库文件
库文件命名规则为under_scored
不要在lib前缀之后直接添加库名称
比如lib_my_great_thing ## Bad libmy_great_thing ## Good
4.4 类和类型
类的命名规则为CamelCased
比如class ExampleClass;
例外情况,如果这个类名字本身包含缩写那么缩写名称要大写,比如
class HokuyoURGLaser;
根据这个类是什么来命名这个类。如果你无法描述这个类是什么也许你还没有设计好。如果一个类的名称包含了三个词以上,那么这可能是由于你的设计不够清晰。
4.5 函数和方法
通常情况下函数和方法的命名规则是camelCased,其参数的命名规则是under_scored,比如int exampleMethod(int example_arg);
函数和方法通常是为了实现否个行为,所以他们的名称要体现出它们要做什么。比如使用checkForErrors()而不要使用errorCheck(),使用dumpDataToFile() 而不要使用dataFile()。类通常都是名词。通过把函数名称设计为动词可以让其他的命名更加自然。
4.6 变量
通常情况下变量名是under_scored
变量命名要尽可能的具有描述性。更长的变量名并不会占用更多的内存。当然整型迭代变量可以非常的短。比如i,j,k.但是变量使用一定要保持一致性。比如i在外部循环,j在内部循环。STL迭代变量要能看出来他们是迭代器
std::list<int> pid_list; std::list<int>::iterator pid_it;
或者从变量名能够看出迭代器的类型
std::list<int> pid_list; std::list<int>::iterator int_it;
4.6.1 常量
常量使用ALL_CAPITALS
4.6.2 成员变量
类的成员变量使用 under_scored。同时尾部添加一个下划线int example_int_;
4.6.3 全局变量
尽可能避免使用全局变量。当使用时,全局变量要under_scored 同时在前面加上g_// I tried everything else, but I really need this global variable int g_shutdown;
4.7 命名空间
命名空间要under_scored
5. 协议声明
所有的源文件和头文件头部都必须包含协议和版权声明。
在ros-pkg和wg-ros-pkg源LICENSE 文件夹中包含了协议的模板。
查看ROS开发指南了解更多关于协议和版权的信息。- 格式
你的编辑器应该能够自动设置格式。
每一个块级结构要缩进两个空格,不要使用tab
命名空间的内容并不需要缩进。
大括号无论是左括号还是右括号都要独占一行。
if(a < b) { // do stuff } else { // do other stuff }
如果只有一行代码,那么括号可以省略
if(a < b) x = 2*a;
当执行的代码比较复杂的时候不要省略大括号
if(a < b) { for(int i=0; i<10; i++) PrintItem(i); }
下面是一个更完整的例子
/* * A block comment looks like this... */ #include <math.h> class Point { public: Point(double xc, double yc) : x_(xc), y_(yc) { } double distance(const Point& other) const; int compareX(const Point& other) const; double x_; double y_; }; double Point::distance(const Point& other) const { double dx = x_ - other.x_; double dy = y_ - other.y_; return sqrt(dx * dx + dy * dy); } int Point::compareX(const Point& other) const { if (x_ < other.x_) { return -1; } else if (x_ > other.x_) { return 1; } else { return 0; } } namespace foo { int foo(int bar) const { switch (bar) { case 0: ++bar; break; case 1: --bar; default: { bar += bar; break; } } } } // end namespace foo
6.1 行的长度
一行最多包含120个字符6.2 #ifndef 保护
所有的头文件都必须用#ifndef保护起来#ifndef PACKAGE_PATH_FILE_H #define PACKAGE_PATH_FILE_H ... #endif
这部分保护代码要立即添加在协议声明之后。同时#endif添加在文件尾部。
- 文档
代码必须要有文档。没有文档的代码也许能够工作但是没有办法维护。
我们使用doxygen来自动生成文档。Doxygen 会解析你的代码从代码中提取出对应的文档。
查看rosdoc页面了解更多的doxygen文档信息。
所有的方法,函数,类,类成员变量,枚举类型,和常量都需要文档说明。
- 终端输出
避免使用printf 和cout。使用rosconsole来输入信息。其具有下面的优点
- 有颜色
- 能够通过显示级别来控制输出
- 通过/rosout发布,所以网络中的其他用户也能查看
- 可以选择输出到文件中。
- 宏定义
尽量避免使用宏定义。和常量变量和内联函数不同,宏定义既没有类型也没有作用域。
- 预处理指令(#if #ifdef)
对于条件编译的情况,总是使用#if 不要使用#ifdef
有些人会这样写代码
#ifdef DEBUG temporary_debugger_break(); #endif
其他人可能会这样编译
cc -c lurker.cpp -DDEBUG=0
总是使用#if,。这样能够保证程序正常工作。即使DEBUG没有定义。
#if DEBUG temporary_debugger_break(); #endif
- 输出参数
函数和方法中的输出参数通过传指针进行而不是传引用。
int exampleMethod(FooThing input, BarThing* output);
通过传引用的方式,调用者无法判断参数是否能被修改。
- 命名空间
鼓励使用命名空间来给代码增加作用域。根据软件包的功能选择一个具有描述性的名称。不要在头文件中使用using-directives . 否则所有使用这个头文件的文件的命名空间都会被污染.使用 using-declarations。这样只有想要使用的名称才会被影响。
比如不要使用
using namespace std; // Bad, because it imports all names from std::
使用
using std::list; // I want to refer to std::list as list using std::vector; // I want to refer to std::vector as vector
- 继承
继承是经常用来定义接口的方式。基础类定义了接口,然后子类实现对应接口。
继承也通常被用来给子类提供通用的代码。这种继承的使用方法是不建议的。因为子类中包含了基类的实例,所以可以实现同样的目的,同时也有更少的迷惑性。
当在子类中覆盖一个虚拟方法时,一定要声明称virtual,这样读者才知道发生了什么。
13.1 多重继承
尽量避免多重继承,它会产生各种各样的问题。- 异常
异常是相对于返回整型错误码更好的一种错误报告方式。
一定要把你的软件包中每个函数和方法可能抛出的异常在文档中说明。
不要在destructors中抛出异常。
不要在你不直接调用的回调函数中抛出异常
如果你在代码中使用错误码而不使用异常,那么就一直使用错误码。一定要保持一致性。
14.1 编写异常安全的代码
当你的代码能过够被异常中断时,一定要确认所有的资源都被释放。比如互斥锁要释放,动态内存要释放。- 枚举类型
把你的枚举类型放在命名空间中
namespace Choices { enum Choice { Choice1, Choice2, Choice3 }; } typedef Choices::Choice Choice;
这样方式枚举类型污染命名空间。枚举类型中的元素可以Choices::Choice1这样访问。但是typedef 仍然允许在命名空间外使用去声明Choice枚举类型。
- Globals
全局的无论是变量还是方法都要尽量避免使用。他们会污染命名空间,减少代码的重用性。
全局变量是最最应该避免的。它阻碍了多实例,同时使得多线程编程成为噩梦。
大部分的变量和函数应当在类里面声明。剩下的要在命名空间中声明。
例外情况: 一个文件可能包含main()函数和其他一些小的全局的工具函数。但是注意这些有用的函数可能以后也对其他人很有用。-
静态类变量
尽量避免使用静态类变量。它同样也会使得无法多次实例化代码,同时也使得多线程编程变成一个噩梦 -
调用exit()
只在设计好的一个退出程序的地方调用exit()
不要在库文件中调用exit -
Assertions
使用assertions 来检查条件,数据结构,和内存管理器的返回值。使用Assertions 要比使用条件声明更好。
不要直接使用assert()。使用在ros/assert.h里面声明的函数
/** ROS_ASSERT asserts that the provided expression evaluates to * true. If it is false, program execution will abort, with an informative * statement about which assertion failed, in what file. Use ROS_ASSERT * instead of assert() itself. * Example usage: */ ROS_ASSERT(x > y);
/** ROS_ASSERT_MSG(cond, "format string", ...) asserts that the provided * condition evaluates to true. * If it is false, program execution will abort, with an informative * statement about which assertion failed, in what file, and it will print out * a printf-style message you define. Example usage: */ ROS_ASSERT_MSG(x > 0, "Uh oh, x went negative. Value = %d", x);
/** ROS_ASSERT_CMD(cond, function()) * Runs a function if the condition is false. Usage example: */ ROS_ASSERT_CMD(x > 0, handleError(...));
/** ROS_BREAK aborts program execution, with an informative * statement about which assertion failed, in what file. Use ROS_BREAK * instead of calling assert(0) or ROS_ASSERT(0). You can step over the assert * in a debugger. * Example usage: */ ROS_BREADK();
不要在assertion里面运行程序。只用它来检查逻辑条件。根据编译的设置assertion可能不会被执行。
在软件开发的时候开启assertion检查时很必要的。当软件接近完成,在深入的测试代码中assertion一直成立。那么你可以添加一个编译flag,把assertion代码从编译中移除。这样这部分代码就不会在占用空间和CPU资源。下面的catkin_make选项会为你的所有的软件包定义NDEBUG宏,从而移除assertion检查。
catkin_make -DCMAKE_CXX_FLAGS:STRING="-DNDEBUG"
注意: 当你执行这个命令后cmake会编译你的所有软件。同时会把设置记录下来。除非你删除build和devel文件夹。
- 动机