2010年01月 文档列表
疯狂大采购
由 动力老男孩 发表于 2010/01/28 23:02:54最近一直没上来发攻略,主要的时间都在淘宝上逛街了。现在的小爱已经有一个大概的框架图,具体的搭建还需要不断的买材料,调整,再买材料,再调整。
发一些最近买的东西,留个纪念:
另外还有一些开发板和传感器还没拍照,等下回做实验的时候发。希望明天几根定制的轴能送到,周末就可以组装点东西了。
解魔方的机器人攻略21 – 读取魔方颜色
由 动力老男孩 发表于 2010/01/24 19:46:23之前已经介绍了萝卜头转魔方的各个分解动作,今天介绍如何用颜色传感器读取魔方的颜色。这一部分可以分成三部曲:
1,依次扫描魔方的6*9=54个色块
2,用蓝牙连接把数据发送到电脑
3,通过颜色分组函数,从读数分辨出不同的颜色
这里先说明一下:虽然乐高的颜色传感器以RGB的形式返回颜色值,但是并不是想象中的那样,红色返回(255,0,0),蓝色(0,0,255)这么轻松。事实上这个数值受环境光线强度影响非常大,即使相同的环境下,读数仍然会有跳动。例如下面几个读数:
[0,1,2]=>RGB=(23,0,0),RawRGB={45,1,8} //红色 [0,2,2]=>RGB=(30,0,0),RawRGB={60,1,5} //红色 [2,1,2]=>RGB=(28,0,0),RawRGB={55,12,8} //橙色 [2,2,1]=>RGB=(29,0,0),RawRGB={57,14,14} //橙色
如果你仅想从RGB来分辨颜色的话,将会“很受伤”。不过今天我只介绍第一步(扫描),在PC端程序部分再介绍如何识别颜色。
因为颜色传感器一次只能读一个点,所以要扫描魔方的话,必须把54个点都扫到。我们以扫描其中一个面为例,看看两个电机怎么配合扫描到所有的9个点。首先,颜色传感器的电机中心位置,应该在2,4两个点的中心延长线上。这样在魔方位置不动的情况下,就可以扫描到中心和四个角了。(相关文章:颜色传感器的安装)
然后,让底座旋转一定的角度,同时传感器电机也稍微调整,这样就可以扫到4个棱的颜色。
以这种方式扫描一圈,就可以把魔方的一个面读完了。最后使用魔方操作的分解动作,把魔方翻过来倒过去,直到把六个面依次扫描出来。有一件非常麻烦的事情是,在魔方翻来翻去的过程中,数组并不是每次都以0为左上角,它是不停的变换的(相关文章:魔方坐标系)。我用了下面这个map表,用来标记读数的顺序,这可是牺牲了数百个脑细胞换来的,其中idx数组是每个面内的依次读取顺序,idex2数组是不同的面的读取顺序:
int[][] idx={ {4,6,7,8,5,2,1,0,3}, {4,0,3,6,7,8,5,2,1}, {4,2,1,0,3,6,7,8,5}, {4,8,5,2,1,0,3,6,7}, {4,2,1,0,3,6,7,8,5}, {4,2,1,0,3,6,7,8,5}}; int[] idx2={5,1,4,3,2,0};
我们以上一次的程序为基础,添加以下变量和函数:
//add offset positions for color sensor motor static int ColorMotorOffset1 = 33; static int ColorMotorOffset2 = 9; static int ColorMotorOffset3 = 18; static int ColorReadPostion1 = 162; static int ColorReadPostion2 = 154; //Read each side colors of the cube public static void ReadAllSide() { } //Read one side by the index public static void ReadOneSide(int nSideIndex) { }
最后加一个测试入口,当按下Enter键时,开始扫描魔方。相信看过前面文章的朋友,这里不需要说明了。点此查看具体的代码吧。
补充个小小的说明:我在代码里面统一用英文加了注释,不是在装酷,主要是因为上班的时候是这样强制要求的,以至于自己做东西也养成这种习惯了。
机械结构 – 一次不成功的尝试
由 动力老男孩 发表于 2010/01/22 21:53:47据说DIY机器人最麻烦的步骤就是加工零件,主要是因为加工设备太少,巧妇难为无米之炊嘛。我买了一本叫《机器人制作入门篇》的书,作者的家里居然有小型车床和铣床。可是北京的房价这么贵,人都不够住,哪有地方放这么多东西。
前几天我突发奇想,打算试试用废旧的玩具和家具来加工“零件”,因为木头比金属还是容易处理一点,用牙都能啃个豁口出来。所以有了下面的一个小尝试,这次尝试的目标是做一个小车底座。
当然,大家看了标题,已经知道这是一个不成功的案例了。其实我本来想把标题写成“一次失败的….”。后来想起一句老话:没有所谓失败,因为至少排除了一种不可行的方案。正好快过年了,把不开心的事情说出来,让大家开心一下。
首先,盘点一下手头的家伙,看上去还真不少:
接下来的工作是忽悠老婆,就说家里的鞋架子已经臭了,该换一个了,然后把它拆成了无数的木条。下面是用小刀抠出来的轴承座:
剪一块罐头铁皮,用钉子在两头打眼,把轴承固定在木头上:
一式两份:
下面这个是用膨胀螺栓掰成的“联轴器”,外加铁棍和玩具车的轮子:
可以变方向的另外两个轮子,木头上的那两个窟窿,废了半天劲用磨轮打出来的:
用来调节宽度的两个小木块,调节方法是:如果宽了一点,就用小刀把它削平。
最后的组装:
做到这里就结束了,本来应该还有几个步骤。应该把长出来的轴锯掉,然后接上电机,后面的两个轮也要加上一个控制方向的电机,就变成了一个小车底座。
没有做下去的原因包括:
1,木头+山寨工具=误差,精度实在很差,强度也不够
2,污染很大,满屋子一股奇怪的木头味,另外还有噪声污染
3,粗大的件可以做,复杂的小件做不了
考虑到家里还有孕妇,实在是不能再这么玩了。难怪老外们一说起创业,就是在车库里面,那是一个可以随意折腾的地方。接下来还是打算画点AutoCAD的图,送去工厂加工,不知道要花多少银子啊
最后附上工作照一张,颇有恐怖分子的气质:
Arduino开发板实验二:模拟输入和输出(用电位器和开关控制直流电机)
由 动力老男孩 发表于 2010/01/21 08:37:15在上一个Arduino小实验里,我们尝试了用开发板来读写数字信号(0和1),貌似非常简单,难度系数跟吃苹果差不多。昨天淘宝的直流电机控制板终于送到了,接下来这个实验准备测试读写模拟信号。这个实验难度系数稍大一点,达到了吃香蕉的程度,搞不定的同学请去动物园请教猴子兄弟(开个玩笑,但是Arduino的确非常好开发)
我计划的实验目标是:
1,使用电位器控制直流电机的转速
2,使用开关控制直流电机的旋转方向
模拟输入
查了下资料,许多单片机的管脚电压都是+5V或0V,分别对应1和0。而机器人面对的自然界却没有这么泾渭分明,例如大气温度,到墙壁的距离,声音的强度等等,这些值就是模拟值。Arduino开发板上,标记了“Analog In”的16个管脚,就是用来测量模拟值输入的。这些输入电压的范围是0~+5V,开发板会把它映射到0~1023的整数。从这个数值范围,我们可以估算出Arduino能识别的电压精度大概是5mV,小于这个范围的变换无法识别。
电位器
这个东西是从邻居小朋友那里骗来的。在我读高中的时候,好像把这个东西叫滑动变阻器,个头很大。现在都是小小的旋转电位器了,原理图应该是一样的:
接线方式是两端分别接GND和+5V,中间随意接在一个Analog In的管脚上。我选择了一个大吉大利的6号。
读取模拟电压值的函数为:
int readValue = analogRead(readPin);
用端口监视器做个分解实验,使用Serial.println命令把readValue显示在电脑上,和想象中的结果完全一样。端口读写部分准备做为下一个实验主题,这里就不细说了:
PWM输出
作为一个外行,我之前曾经想过怎么让单片机输出一个模拟值。觉得必须用10个管脚,对应的二进制从0~1023,然后找一个数模转换的东西变成模拟电压值。结果一看资料发现自己太圭了,原来有一种叫做占空比的东西,只用一个管脚就可以输出0~255的数值。从这里也了解到嵌入式常用的一个叫“时序”的东西,以后应该会经常遇到,这种方式可以用很少的管脚实现非常复杂的功能(估计业内人士又要鄙夷的飘过了)。看下面的图解,从Arduino官网顺来的:
板子上标注了“PWM”的区域就是管脚均可以用于这种输出。使用的函数是:
analogWrite(pin, value);
注意value值的范围是0~255。
直流电机控制板
这个控制板的功能是把PWM的输出,转换成真正的模拟电压值,从而控制直流电机旋转。电机的种类很多,还有舵机,步进电机等等,控制方式都不一样,那些以后再试。先看一下这个板子的介绍,也是一个顺手牵来的图:
这个图里信息太多了,咱们捞干的讲几个:
1,左右两边是对称的,可以控制两个直流电机,咱们下面只看左边
2,绿色的“直流电机A接口”,用于接电机的两根电源线
3,直流电机A信号输入接口,一共有3个脚,分别是I1,I2和EA。其中I1和I2是数字接口,用于控制开关和方向,EA是模拟接口,用于控制转速
例如:I1=1,I2=0顺时针转,I1=0,I2=1就逆时针转,I1=I2的时候,停止转动。EA是0~255的PWM值,对应从小到大的转速
4,最下面的VMS接电源正极,GND接地,边上还有一个+5V,不用管它,本来是由它给逻辑电路供电的,但是默认情况下,是通过DUAL那个跳线帽由VMS搭车送电。
看看最终的接线图,专业人士一般用面包板之类的东西实验,我就直接把铜丝拧上了,请大家不要效仿:
接下来是写代码,一共也没几行,大家看注释吧:
注意:我发现Analog输入区编号是0~15,PWM输出区也有0~13,为了验证这两类管脚编号会不会冲突,我特地把readPin和speedPin都设置成了大吉大利的6号。事实证明两者不冲突。
int readPin = 6; //用来连接电位器 int buttonPin = 38; //用来连接开关,这次用了个吉利的端口号 int i1Pin = 31; //连接电机驱动板的I1接口 int i2Pin = 30; //连接电机驱动板的I2接口 int speedPin = 6; //连接电机驱动板的EA接口 void setup() { pinMode(buttonPin, INPUT); //开关用于输入 pinMode(i1Pin, OUTPUT); //I1和I2都是数字信号 pinMode(i2Pin, OUTPUT); //通过设置I1和I2来控制电机旋转方向 pinMode(speedPin, OUTPUT); //按占空比方式输出的模拟信号 digitalWrite(buttonPin, HIGH); //设置上拉电阻 } void loop() { //读取按钮状态 boolean buttonPressed = digitalRead(buttonPin); //设置转动方向,I1和I2值相反时,分别对应两种不同的转向;I1和I2值相同时停止转动 digitalWrite(i1Pin, buttonPressed); digitalWrite(i2Pin, !buttonPressed); //读取电位器(传感器)的读数,值范围从0到1023 int readValue = analogRead(readPin); //PMW的值范围是0~255 readValue/=4; //设置转速 analogWrite(speedPin, readValue); delay(500); }
上传程序以后,旋转电位计,可以看到电机的转速不断变化着;掰一下开关,电机就会反转,实验到此顺利结束。
解魔方的机器人攻略20 – 修正电机误差
由 动力老男孩 发表于 2010/01/18 22:01:03在上一篇攻略中,我们使用了一些角度的配置信息,例如:
//the motor angle for paw to hold the cube static int PawHoldPosition = 56; //the motor angle for paw to rotate the cube static int PawTurnOverPosition = 110;
这些用于Motor.rotate(n)的角度,都是相对于电机的原始位置而言的。在我的代码里,初始位置是这样定义的:
在最初的版本里,我是在断电状态下,手动把电机拧到指定的初始位置。(程序一旦开始运行,角度信息就已经开始记录了,而且拧电机会有很大的阻力)
随后问题就来了,如果初始位置不准确的话,那么必然会导致旋转之后的位置不准确。其中最省心的是爪子的初始化位置,因为它是贴在后支架上,这个参照物非常稳定。
颜色传感器的杆很长,目测很难判断是否已经平行。魔方底座更是转十几次以后,误差越来越大。所以我们需要一段程序,把稍有偏差的初始位置纠正回来。
首先看一下如何修正魔方底座的误差。我们曾经介绍过,在魔方底座的下方安装了一个亮度传感器,当底座在某些位置的时候,会挡在亮度传感器的上面,再转过一定角度,就又把它露出来。亮度传感器有一个红色的小灯,可以通过light.setFloodlight(bool);来点亮或者关闭它。通过对比点亮和关闭前后的读数差,就可以判断出底座什么时候被挡住(在底座的下方需要贴一圈白纸,增强反光)。读数的曲线图是这样的:
也就是说,随着传感器被慢慢的挡住,这个亮度差值会越来越大,理论上最大值就是被挡住的中心位置。考虑到传感器的读数是有误差的,所以不能只取一个最大值点来计算,需要设置一个阀值,把最大的N个点都找到,那么它的中心位置就比较准确了。
//Fix the position of cube base public static void FixBasePosition() throws Exception { int step = 3; int tolerance = 4; light.setFloodlight(false); bottom.rotate(-50); int angle = 0, minLight = 10000; int realtimeLight = ReadLightDifference(); while(realtimeLight < minLight + tolerance) { bottom.rotate(step); realtimeLight = ReadLightDifference(); if(realtimeLight < minLight) { minLight = realtimeLight; angle = 0; } else { angle += step; } } bottom.rotate(- angle/2 - FixBasePositionOffset); } //Read the light difference between light on and light off private static int ReadLightDifference() throws Exception { int l1 = 0, l2 = 0; l1 = light.readValue(); light.setFloodlight(true); Thread.sleep(20); l2 = light.readValue(); light.setFloodlight(false); return l1-l2; }
可以测试一下,把魔方底座手动拧歪一个小角度(正负十几度^_^),运行这段代码之后,底座会还原到和爪子平行的位置。
颜色传感器的位置修正比较简单:让它慢慢的靠近魔方,在传感器下方遇到魔方之前,它的读数都是0。所以一旦发现有读数,我们让它返回32度,就回到了爪子平行的位置,这个度数通过几次实验就可以试出来。
//Fix color sensor position public static void FixColorSensorPosition() throws Exception { int tolerance = 5; ColorMotorBaseAngle = -25; monitor.rotateTo(ColorMotorBaseAngle); Thread.sleep(100); monitor.setSpeed(50); int r = color.getRawRed(); int g = color.getRawGreen(); int b = color.getRawBlue(); int baseColor = r + g + b; int TargetExists = 0; while(TargetExists < baseColor + tolerance && ColorMotorBaseAngle > -50) { monitor.rotateTo(ColorMotorBaseAngle--); r = color.getRawRed(); g = color.getRawGreen(); b = color.getRawBlue(); TargetExists = r + g + b; } monitor.rotateTo(ColorMotorBaseAngle + 32); }
下面也做一个实验,把颜色传感器的位置拧歪,它也能回复到指定的位置。点此下载这个例子的全部代码。实验方法为:按Left键修正魔方底座位置,按Right键修正颜色传感器位置,按Escape键退出。
时间仓促,每次贴的功能都不多,下一次介绍如何把魔方的颜色读取到数组中。
Arduino开发板实验一:补充
由 动力老男孩 发表于 2010/01/18 20:54:50感谢 lifanxi 和 galaxy 两位同学对昨天实验一的讨论。经过补充实验,事实证明galaxy的看法是对的。当PinMode是INPUT时,digitalWrite(pin, HIGH)实际上是用来设置上拉电阻,所以只需要在setup中设置一次即可。
代码如下:
int ledPin=13; int buttonPin=44; void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT); digitalWrite(buttonPin, HIGH); } void loop() { //这一行移到上面的setup中 //digitalWrite(buttonPin, HIGH); digitalWrite(ledPin, digitalRead(buttonPin)); delay(100); }
因为我的外部电路连接的GND,所以没法测试下拉电阻。
做完实验我又看了一个教程,发现我的这个实验非常粗暴,“是烧毁单片机的最佳途径”。因为44脚置1后的电压是+5V,我直接用一个开关把它接地了。还好单片机已经有内置的上拉电阻,否则的话这个板子可能就废了(软件开发经常提到的一个术语是“冒烟测试”,看来就是用于检查这种低级错误)。下面这个图是专业人士推荐的接线方式:
这样接线的好处是,任意两个可能存在电压差的管脚之间,都至少存在一个电阻,就不容易烧板子了。我现在手头没有空的电阻,就不重复这个实验了。有兴趣的同学可以点此看原文教程。
Arduino开发板实验一:数字输入输出
由 动力老男孩 发表于 2010/01/16 21:56:58最近有很多朋友关心小爱的进展,真是非常感谢。这段时间我主要在研究小爱的制作方案,大致的工作分为几个部分:机械设计,电子电路,嵌入式开发和人工智能几个部分。
为了确定最终的方案,我最近总在淘宝上晃悠,劲头跟美女购物狂们逛商场有一拼。前一段时间无意中看到了一个叫Arduino的东西,貌似还挺好用,暂定以这个板子为基础来开发电子电路和嵌入式的部分。我以前没有接触过嵌入式开发,如果路过的朋友有更好的方案,请一定要吱一声,谢谢
昨天淘宝的Arduino板子终于到了,型号是Arduino MEGA ATmega1280-16AU AVR(完全不清楚不同型号之间有什么区别),秀一下样子:
今天进行了第一个小实验,跟大家分享一下。实验的目的是实现一个简单的功能,外接一个开关,通过打开或者关闭开关,来控制一个小灯的亮和灭。
电子专业的老婆冷笑飘过,这个实验太简单了吧,用传统电路甚至更简单,一个开关直接切断电路即可(传说中的手电筒)。不过我通过这个小实验了解了一个重要信息:原来嵌入式开发可以直接对CPU的管脚直接进行读写,0就是低电压,1就是高电压。以前一直使用高级语言,读CPU的某个脚电压貌似非常遥远。
第一步:到Arduino官网下载Arduino开发IDE和相关驱动
下载完一看,这个软件是绿色的,并且绿的相当彻底,里面还包含了板子的驱动,双击打开就可以使用,我在Windows7,Server2008以及Vista下都用的挺好。
第二步:用USB线连接Arduino板子和电脑,发现驱动可以被自动识别,然后添加了一个USB UART的设备。查看一下这个设备的COM端口号,后面需要用到:
第三步:
双击运行arduino.exe,第一次使用前,需要设置设备端口号和板子的型号,看下面的抓图:
第四步:接线
为了做这个小实验,我还真拆了家里一个手电筒(真是败家)。文档里说开发板有个LED小灯接在13号管脚,据说术语叫做Pin13,所以就不需要外接小灯了。开关的两个脚一个接地(GND),另一个随便接某个管脚。考虑到13这个数字不吉利,我特地选了一个44号管脚避个邪。看下图:
第五步:写代码
int ledPin=13; int buttonPin=44; void setup() { pinMode(ledPin, OUTPUT); pinMode(buttonPin, INPUT); } void loop() { digitalWrite(buttonPin, HIGH); digitalWrite(ledPin, digitalRead(buttonPin)); delay(100); }
经常搞windows软件开发的朋友可能有点迷糊,这个程序入口在哪里,出口在哪里?事实上这个程序是被Arduino的IDE封装了,它的规则就是先定义几个变量,然后在setup里做几个设置,最后就孜孜不倦的反复跑loop里面的程序。我猜想应该有些中断机制来执行一些特殊处理,这个以后再慢慢学。看来单片机的程序通电以后就没打算让它歇着,高级语言的程序员需要适应一下。
接下来的程序就很好解释了:
pinMode 用于设置管脚的用途,输入还是输出
digitalWrite 用于把某个管脚的电压设置成High还是LOW
digitalRead 用于读取某个管脚的电压状态,返回的也是HIGH或LOW
有一句 digitalWrite(buttonPin, HIGH); 需要解释一下。我希望44号管脚当开关打开时是1,开关闭合是是0。但是请注意第一次闭合以后,这个管脚电压降到0,它不会自动升到1。为了让下次开关打开的时候管脚电压置1,我在每次循环前都给它设置初始值1。画了一个非常丑的电路图:
第六步,编译并上传代码
用IDE中的Verify来编译代码,然后用Upload上传。选的44号辟邪管脚果然有效,代码直接编译通过上传成功(真相是确实太简单了)。结果就不给大家贴图了,没啥好看的。不过好人做到底,虽然Arduino的IDE按钮都有提示,我还是画了个说明:
总结:这虽然是个很简单的实验,但是对于一个像我这样没有接触过嵌入式的程序员来说,还是有很多的知识点。我相信有很多和我一样的人,想做机器人但是觉得无从下手,希望这些学习经验有所帮助。高手们就请优雅的飘过吧
解魔方的机器人攻略19 – 让魔方动起来
由 动力老男孩 发表于 2010/01/15 07:39:48一星期没更新,原因就不多说了,总之请见谅。从今天开始继续发攻略
我原来的代码又多又乱还没有注释,自己看着都眼晕,找点代码晕的跟坐过山车似的。现在正在把它们重新整理优化,再加上注释。我打算整理一部分就发一部分攻略,攻略发完了也就整理完了。另外,这几天有很多网友正帮忙翻译lejos的中文教程,我在整理的过程中也学到了不少东西,有兴趣的同学还可以加入。
下面开始正题。假设现场的观众们按照前面的攻略,已经把萝卜头搭建好了。第一段程序先让魔方能动起来,实现的功能是:
- 按Left键,魔方底座旋转90度
- 按Right键,爪子抓住魔方,然后底座带动最下面的层旋转90度
- 按Enter键,爪子把魔方翻转90度
- 按Escape键,程序退出
下面介绍需要用到的一些知识点
1,创建传感器和电机的实例:
//Define Sensors static UltrasonicSensor distance=new UltrasonicSensor(SensorPort.S1); static LightSensor light = new LightSensor(SensorPort.S2); static ColorSensor color = new ColorSensor(SensorPort.S3); //Define Motors static Motor paw=Motor.A; static Motor monitor=Motor.B; static Motor bottom=Motor.C;
这部分对应的是我们的接线方式:
传感器1口接超声波传感器,也就是眼睛
传感器2口接亮度传感器
传感器3口接颜色传感器
电机A口接爪子的电机
电机B口接颜色传感器的电机
电机C口接魔方底座的电机
2,创建一个Robot类,这个类用于控制机器人结构上的各种动作,下面三个方法分别对应上面说的三个功能:
public static class Robot { public static void RotateBottomSide(int nQuarter) { } public static void RotateBottom(int nQuarter) { } public static void RotatePaw()throws Exception { } }
这里使用了关键字static,因为萝卜头只有一个实例,所以把它设置成静态类。静态类可以直接使用静态方法,不需要创建实例,还是看一段代码对比下:
//创建实例的用法 Robot instance = new Robot(); instance.rotate(); //静态类的用法 Robot.rotate();
3,设置了一些参数
//如果爪子部分改装了那个3:1的减速齿轮,设置成true,不明白的请看 http://www.diy-robots.com/?p=147 最后两张图 static boolean HasReducer = true; //爪子抓住魔方时的电机角度 static int PawHoldPosition = 56; //爪子翻动魔方时的电机角度 static int PawTurnOverPosition = 110; //底座旋转90时,电机的旋转角度(因为齿轮组的原因) static int BaseOneQuarter = 315; //当底座旋转魔方底面时,因为魔方的阻力,需要先多转一个小角度,然后再转回来,这是用来修正误差的角度 static int BaseRotateFix = 40;
4,控制电机(motor)的几个函数
paw.setSpeed(400); //设置转速 paw.rotateTo(nPawHoldPosition); //旋转到一个指定角度(绝对定位) bottom.rotate(-nFixAngle); //旋转一定角度(相对定位)
更多的电机相关函数,请点这里看刚刚翻译好的教程。
5,亮度传感器的一个函数,用于把它的灯打开或者关闭。这里是关闭它,省的晃眼睛,需要的时候再开
light.setFloodlight(false);
好了,最终解魔方的动作,都是通过调用这几个函数来完成的。事实上如果你足够无聊的话,现在就可以通过NXT上的几个按键来控制萝卜头玩魔方了。
该吃早饭了,大家自己看看源代码吧。
http://www.diy-robots.com/RubikSolver/SourceCode/NXT/RubikSolverV2.java_20100115.txt