找回密码
 马上注册

QQ登录

只需一步,快速开始

查看: 19961|回复: 18

【原创】【iRobot的机器人编程笔记】(六)4种方式让你的程序停下来

[复制链接]
发表于 2013-6-17 18:54:15 | 显示全部楼层 |阅读模式
本帖最后由 iRobot 于 2013-6-17 20:40 编辑






       关于流程语句的内容,我认为没必要进行深入的解释了。1是比较简单,随便翻看一本编程书籍的第三章,都会有详细的说明。2是不要在这上面花费太多的时间。我看到过一些文章,将几个流程控制语句反复讲解,挖的很深很细致,这是没有必要的。如果在这上面花费几天时间,那学到入门水平估计得耗时半年。这节课通过几个简单的例子,让你的程序能够“停”下来或“持续”运行,算是流程控制在程序中的应用吧。


1.      Button.waitForAnyPress()

       让程序停下来,多用在查看屏幕输出。例如我们的HelloWorld。如果写成:
  1. publicstatic void main(String[] args) {
  2.            // TODO Auto-generated method stub
  3.            LCD.drawString("HelloWorld", 0, 0);
  4. }
复制代码
       你在屏幕上是什么也看不到的。因为程序是顺序执行的。从第一行到最后一行,执行完毕后就自动退出程序。我们代码中,最后一个有效的指令是LCD.drawString("Hello World", 0, 0);,输出文字,然后立即就退出了,以我们的肉眼凡胎,是很难看清楚的。所以应该这样写:
  1. publicstatic void main(String[] args) {
  2.            // TODO Auto-generated method stub
  3.            LCD.drawString("HelloWorld", 0, 0);
  4.            Button.waitForAnyPress();
  5. }
复制代码
       最后一行代码的意思是等待任何按键按下,所以如果没有按键按下,程序会始终停留在这里,而上一行代码此时已经执行完毕,这样我们在屏幕上就能看到输出的文字了。这就是最简单的一种让程序“停”下来的方式。虽然简单,但是用处却不少。例如我们要查看电池的状况:
  1. LCD.drawString(Battery.getVoltage()+”V”, 0, 0);
  2.                    Button.waitForAnyPress();
复制代码
       可以看到屏幕上显示7.353V,就是当前电池的电压。

       至此,我们终于把HelloWorld的知识点全部学完了。这里我说一些个人的经验供参考。有些书籍或教程,将基础部分讲述的很详细,大家也学的很有滋味,固然让人觉得学习起来很容易,但是实则有很大的坏处。将大量的时间放在基础部分的学习上,实在太奢侈了。而到了真正需要用心理解的高级内容部分,大部分人已经身心疲惫了。所以我建议,学习任何一门语言,不要在前几章上花费太多的时间。当然我的教程里对基础部分的讲解也是比较详细的,这个不要紧,我会省略中间部分,直接讲高级内容。(晕倒了。。。)

2.      Thread.sleep(5*1000)

       第二种方式是利用线程的sleep()方法。小括号里的参数是线程休眠时间,单位是毫秒。
    所以5*1000就是休眠5秒。这里有个小技巧,就是5*1000要比写5000好。同理休眠90分钟就是90*60*1000
       因为这个函数是非线程安全的,所以要加try catch语句。这个目前不需要记忆也不需要理解,直接写就行了。
  1. try {
  2.                             Thread.sleep(5*1000);
  3.                    } catch (InterruptedExceptione) {
  4.                             // TODOAuto-generated catch block
  5.                             e.printStackTrace();
  6.                    }
复制代码
       完整的代码如下:
  1. public static void main(String[] args) {
  2.                    // TODOAuto-generated method stub
  3.                    LCD.drawString(""+Battery.getVoltage(),0, 0);

  4.                    try {
  5.                             Thread.sleep(5*1000);
  6.                    } catch(InterruptedException e) {
  7.                             //TODO Auto-generated catch block
  8.                             e.printStackTrace();
  9.                    }
  10.          }
复制代码
       效果就是屏幕输出7.232V这行文字之后,停留5秒,然后全部语句就执行完毕了,程序自动退出。实际编程中,这种形式使用的比方法1要多。因为可以省掉按下一次按钮的时间。比如,我在屏幕上输出一行文字,扫了一眼,发现位置不对,没关系,5秒后自动退出了,移动一下文字的输出位置再运行,扫一眼,还有点偏,没关系,5秒后自动又退出了。明白了吧?LeJOS有个很奇怪的问题,就是程序必须退出,才可以在上面运行新的程序。这种情况很罕见,可以说不可思议。举个例子,比如你的程序运行时出错了,它就永远不可能退出。你的新程序(修正过的)就永远不能上传,卡住了。唯一的办法是用牙签去捅reset,这时你发现NXT已经被装成机器人了,根本捅不到reset。。。

       虽然使用Thread.sleep(5*1000);的方式,有线程安全的问题,但是我默认为如果你可以多线程编程,就可以自行解决此问题。嘿嘿。其时简单的用一下,还是不会出问题的啦。

3.      while(!Button.ESCAPE.isDown()) {}

       先看一下完整的代码:
  1. publicstatic void main(String[] args) {
  2.            // TODO Auto-generated method stub
  3.            LCD.drawString("" +Battery.getVoltage(), 0, 0);

  4.            while (!Button.ESCAPE.isDown()) {

  5.            }
  6. }
复制代码
       Button.ESCAPE.isDown()是一个布尔值,值为truefalse。while是标准的流程控制语句。感叹号是取反。这行代码的意思就是,当取消(ESCAPE)按键没有按下时,就始终执行括号内的内容。所以,在ESCAPE按钮按下前,程序执行到这里就会进入一个循环,不断的执行while语句。直到按下ESCAPE,while语句条件被破坏,程序继续执行while之后的语句,执行完毕,退出。单从这里看,和方法1是完全相同的。区别在于while是始终执行的。这就有了本质的区别了。将LCD.drawString("" + Battery.getVoltage(), 0, 0);剪切到while内部运行,程序变为:

  1. public static void main(String[] args) {
  2.                    // TODOAuto-generated method stub

  3.                    while(!Button.ESCAPE.isDown()) {
  4.                             LCD.drawString(""+ Battery.getVoltage(), 0, 0);
  5.                    }
  6.          }
复制代码

       两者的差异是显而易见的。修改前(包括方法1),程序只是在启动时获取一下电池状态,然后就干等着按钮按下,程序退出。即使等1年,还是显示7.232V。而放到while内之后,在按钮按下前,是不停的获取电池状态的。下一秒,电池电压变为7.1V,屏幕就显示7.1V。这种方式在编程中使用的很多。打开官方的例子会发现,涉及传感器取值的(距离,光照,马达状态),一般都是采用这种结构。

4.      Button.ENTER.addButtonListener

       方法4是对按钮添加监听事件。故名思议,监听到了某个事件,就执行某个操作。例如监听到ESCAPE按下,就退出程序。四种方法,没有优略之分,简单的问题,简单解决;复杂的问题,复杂解决。

       按钮的监听事件涉及线程。每个监听对应一个单独线程。虽然这个监听线程是自动分配的,不需要我们干预,但是必须要牢记,线程已经随之出现了,编程时务必考虑线程。最直接的影响是,使用Thread.sleep()未必能使你的程序停下来,因为你有多个线程,一个停下来了其他的还在进行中。
下面看一个完整的例子:
  1. public static void main(String[] args) {
  2.                    // TODO Auto-generated methodstub

  3.                    Button.ENTER.addButtonListener(newButtonListener() {

  4.                             @Override
  5.                             public voidbuttonReleased(Button b) {
  6.                                       // TODOAuto-generated method stub

  7.                             }

  8.                             @Override
  9.                             public void buttonPressed(Buttonb) {
  10.                                       // TODOAuto-generated method stub

  11.                             }
  12.                    });

  13.                    Button.ESCAPE.addButtonListener(newButtonListener() {

  14.                             @Override
  15.                             public voidbuttonReleased(Button b) {
  16.                                       // TODOAuto-generated method stub

  17.                             }

  18.                             @Override
  19.                             public voidbuttonPressed(Button b) {
  20.                                       // TODOAuto-generated method stub
  21.                                       System.exit(0);//退出程序
  22.                             }
  23.                    });

  24.                    while (true) {
  25.                             LCD.drawString(Battery.getVoltage()+ "V", 0, 0);
  26.                    }
  27.           }
复制代码
       这个例子分别对确定按钮和退出按钮的按下和抬起事件进行了监听。其它部分内容都为空,只在退出按钮的按下事件里面写了一行代码:
  1. System.exit(0);// 退出程序
复制代码
       而在代码段的最后,有一个while循环。这个while循环的条件是true,既始终执行。执行的内容是显示电池电压。此时程序就有多个线程在运行,其中一个线程不断的获取最新的电池电压并显示,另有一个线程,一旦检测到退出按钮按下,就退出程序。

       方法3和方法4的重要区别在于,采用方法3的形式,一旦点击退出按钮,程序就退出了。而方法4点击退出按钮时,可以做许多事,例如返回上一级菜单,关闭网络连接等等。

       要注意while (true)这行代码,是放在程序的末尾的。这和放置在程序头部有本质的区别。放置在头部时,因为条件恒成立,代码就停留在这里了,之后的代码是无法访问到的。而反过来放置在末尾,让添加监听器的代码先执行,并且被系统自动分配专用线程,这样不妨碍之后的代码执行。
因为我们已经涉及了线程,我就多说两句,加深一下印象。现在给确定按钮添加代码:
  1. Button.ENTER.addButtonListener(newButtonListener() {

  2.                             @Override
  3.                             public void buttonReleased(Button b) {
  4.                                       // TODOAuto-generated method stub
  5.                                       LCD.drawString("UP"+ "", 0, 1);
  6.                             }

  7.                             @Override
  8.                             public void buttonPressed(Button b) {
  9.                                       // TODOAuto-generated method stub
  10.                                       LCD.drawString("DOWN"+ "", 0, 1);
  11.                                       while(true)
  12.                                                ;
  13.                             }
  14.                    });
复制代码
       while中的内容可以自行填写,例如按下确定按钮之后,开始获取光线传感器传回的数据。这是一种很常见的用法,但是不注意的话,就会出现大问题。所以我在按下和松开按钮时,都添加了一句代码,用于在屏幕上显示按钮的状态(DOWN还是UP)。实际运行程序之后可以看到,按下时显示DOWN,但是松开时却不显示UP,既buttonReleased事件其实是访问不到的。为什么呢?很简单。虽然代码写在一个委托中(我不知道Java中是不是管这个叫委托,反正形式上确实是一种委托)的两个函数里,但是却运行在同一个线程中,两个函数依旧是顺序执行。这就回到上面讲的啦,while条件始终成立,后面的代码(buttonReleased)无法访问。解决办法是把while放在单独的线程中执行:
  1. public void buttonPressed(Button b) {
  2.                                       // TODOAuto-generated method stub
  3.                                       LCD.drawString("DOWN"+ "", 0, 1);
  4.                                       Thread thread = new Thread(new Runnable() {

  5.                                                @Override
  6.                                                public void run() {
  7.                                                         //TODO Auto-generated method stub
  8.                                                         while(true)
  9.                                                                   ;
  10.                                                }
  11.                                       });
  12.                                       thread.start();
  13.                             }
复制代码
       这时,while在自己的线程中,就不会阻塞住buttonReleased事件了。

5.      环境光探测器

       大家还记得我之前做过的光波塔吧?当时随手写了一个程序,用于检测环境光的。程序比较简单,正好和这次的课程比较相符,送上源代码供大家参考。
先上图:
DSC_0410.JPG

再上视频:

最后是代码:
  1. import java.util.ArrayList;
  2. import java.util.List;

  3. import javax.microedition.lcdui.Font;
  4. import javax.microedition.lcdui.Graphics;

  5. import lejos.nxt.Button;
  6. import lejos.nxt.ButtonListener;
  7. import lejos.nxt.ColorSensor;
  8. import lejos.nxt.LCD;
  9. import lejos.nxt.SensorPort;
  10. import lejos.nxt.ColorSensor.Color;

  11. public class NXTLightDraw {

  12.         // static List<MyPoint> lst = new ArrayList<MyPoint>();
  13.         static ArrayList lst = new ArrayList();
  14.         static boolean isRun = true;
  15.         static int inteval=200;

  16.         /**
  17.          * @param args
  18.          */
  19.         public static void main(String[] args) {
  20.                 // TODO Auto-generated method stub
  21.                 Graphics g = new Graphics();
  22.                 DrawFrame(g);
  23.                 int count = 0;
  24.                 ColorSensor cs = new ColorSensor(SensorPort.S1);
  25.                 cs.setFloodlight(Color.NONE);

  26.                 Button.ENTER.addButtonListener(new ButtonListener() {

  27.                         @Override
  28.                         public void buttonReleased(Button b) {
  29.                                 // TODO Auto-generated method stub

  30.                         }

  31.                         @Override
  32.                         public void buttonPressed(Button b) {
  33.                                 // TODO Auto-generated method stub
  34.                                 isRun = !isRun;
  35.                         }
  36.                 });

  37.                 while (!Button.ESCAPE.isDown()) {

  38.                         int raw = cs.getRawLightValue();
  39.                         int val = cs.getLightValue();

  40.                         if (lst.size() > 79) {
  41.                                 lst.remove(0);
  42.                         }
  43.                         // lst.add(new MyPoint(count, (int) raw / 18));// 900/50=18
  44.                         lst.add(raw > 900 ? (int) 900 / 18 : (int) raw / 18);
  45.                         if (count < 80)
  46.                                 count++;
  47.                         if (isRun)
  48.                                 DrawLine(g);
  49.                         try {
  50.                                 Thread.sleep(inteval);
  51.                         } catch (InterruptedException e) {
  52.                                 // TODO Auto-generated catch block
  53.                                 e.printStackTrace();
  54.                         }
  55.                 }
  56.         }

  57.         private static void DrawFrame(Graphics g) {
  58.                 // TODO Auto-generated method stub
  59.                 g.drawLine(14, 55, 14, 4);
  60.                 g.drawLine(14, 55, 96, 55);
  61.                 g.setFont(Font.getFont(0, 0, Font.SIZE_SMALL));
  62.                 g.drawString("8s 16s 24s 32s 40s", 18, 57, 0);
  63.                 g.drawString("150", 0, 47, 0);
  64.                 g.drawString("300", 0, 38, 0);
  65.                 g.drawString("450", 0, 29, 0);
  66.                 g.drawString("600", 0, 20, 0);
  67.                 g.drawString("750", 0, 11, 0);
  68.                 g.drawString("900", 0, 2, 0);
  69.         }

  70.         public static void DrawLine(Graphics g) {
  71.                 g.setColor(Color.WHITE);
  72.                 g.fillRect(15, 5, 80, 49);
  73.                 if (lst.size() > 0) {
  74.                         for (int i = 0; i < lst.size(); i++) {
  75.                                 LCD.setPixel(i + 14, 55 - (int) lst.get(i), 1);
  76.                         }
  77.                 }
  78.         }
  79. }
复制代码

6.      后记

       首先说一句,让你的程序停下来,其本质就是流程控制。并不局限于我提到的四种方法,其实还有很多。但是这四种应该够用一阵子了。你可以生搬硬套,虽然生搬硬套的效率不高,但是不要紧,写程序效率不是问题。新手花费时间提高代码效率,不如花同样的时间学下一章节。即便是高手,也不考虑效率问题。为什么呢?因为待解决的问题太多。。。

       下一节课讲解如何搭建Eclipse环境。这个很重要。没有Eclipse,等同于武将上战场不拿大砍刀。。。好吧,我觉得Eclipse充其量就算是大砍刀吧。。。

如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
发表于 2013-6-17 20:17:29 | 显示全部楼层
终于等到第六季,谢谢iRobot老师了。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-6-17 21:53:13 | 显示全部楼层

回帖奖励 +5 乐币

我们没有颜色传感器。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-6-18 00:31:18 | 显示全部楼层

回帖奖励 +5 乐币

你这段代码没有图像平移的部分啊,怎么会在视频中有一段平移的效果呢?请指教!
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2013-6-18 08:37:05 | 显示全部楼层
糖伯虎 发表于 2013-6-18 00:31
你这段代码没有图像平移的部分啊,怎么会在视频中有一段平移的效果呢?请指教!

LeJOS本身没有提供图层或卷轴之类的东西,或者应该说没有提供任何高级的函数可供使用。所以一切靠人工。。。

图像移动,我用的是最笨但是最直接的办法,清空现在区域,然后偏移一个单位再输出新图像。在代码的85,86行。

那个谁在Rock Ball中应该是用了一种更好的办法,他的图像移动更加平滑。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-6-18 11:56:35 | 显示全部楼层

回帖奖励 +5 乐币

好!快出第七期
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-6-19 11:04:47 | 显示全部楼层

回帖奖励 +5 乐币

作Java程序员已经近10年了,Lego倒是刚刚开始接触,学习了。
已经入了8043,机器人想等 Mindstorms EV3 出来后直接入手EV3
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-6-27 22:20:26 | 显示全部楼层

回帖奖励 +5 乐币

本帖最后由 qsy5381 于 2013-10-22 22:39 编辑


如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

 楼主| 发表于 2013-6-27 22:35:06 | 显示全部楼层
qsy5381 发表于 2013-6-27 22:20
乐高nxt的内存太小 写不了多少代码

你要是不说,我还真是不知道。   

那么,把你写的“大程序”贴出来吧。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-6-30 15:04:42 | 显示全部楼层

回帖奖励 +5 乐币

瓦咔咔~~颇有心得阿 看得很爽
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-7-1 00:45:48 | 显示全部楼层
担心刷机弄挂,没做java,用的nxt
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-9-4 09:43:04 | 显示全部楼层

回帖奖励 +5 乐币

好东西,感谢分享
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-10-12 22:51:17 | 显示全部楼层

回帖奖励 +5 乐币

ev3已经来了,函数变了,但思路没变。已经Hello World显示电压了。
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2013-11-24 21:50:59 | 显示全部楼层

回帖奖励 +5 乐币

大作!学谢啦!期待着下一期哟
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

发表于 2014-10-1 17:44:20 | 显示全部楼层

回帖奖励 +5 乐币

我也看下,学习一下
如果您觉得我的帖子对您有用,请不吝给我一个“赞”!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 马上注册

本版积分规则

手机版|中文乐高 ( 桂ICP备13001575号-7 )

GMT+8, 2024-11-21 20:24 , Processed in 0.144665 second(s), 26 queries .

Powered by Discuz! X3.5

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表