构建基于 Arduino 的激光游戏，第 3 部分: 发射信号，命中目标
开始之前 无论您是 Arduino 新手还是经验丰富的构建者，此项目都有适合您的内容。没有比创建交互式物理对象更令人满意的事情了，因为在需要中断或者需要修改时，您知道所有部件的位置以及所有部件的工作原理。'Duino Tag 枪是适合独立完成或与朋友共同完成的优秀项目。要完成此项目，您至少应当基本了解电子学（您应当知道寄存器是什么，但是不必知道其中的深奥原理）并且了解编程（您应当知道循环和变量是什么，但是您不必解析 Big O Notation）。您可以勇敢地进行动手实践。 关于本系列 在本系列中，我们将使用 Arduino 技术来创建名为 'Duino Tag 的基本交互式激光游戏： 第 1 部分：了解一些 Arduino 基础知识，布置项目，并且做一个帮助您了解红外线工作原理的实验。 第 2 部分：构建和测试 'Duino Tag 枪的接收器部分，包括测试。 第 3 部分：构建发射器并完成 'Duino Tag 枪。 关于本教程 要继续学习本教程，您无需具有任何电子学工作经验，尽管使用电子元件的经验肯定对您有用。对于微控制器经验也是如此。如果您使用过微控制器，则有一定的优势，但是记住 Arduino 平台非常适合没有相应经验的人员。首先，您应当愿意拓展自己的技能。使用电子器件和微控制器会是一种有益的经验。大多数软件工程师没有机会为与物理世界交互的设备编写代码，而 Arduino 提供了使用交互式设备的低成本入口点。 本部分教程主要讨论如何构建 'Duino Tag 枪的发射器。实际的元件装配工作很少，不会花费太多时间。您将用一些时间来编写代码，处理游戏者的枪的所有触发需求。您将了解到构建裁判的枪还需要什么，并了解一些包装选项。本教程最后将提供一些不错的建议，供读者继续探索。 系统要求 对于本教程，您需要一些工具和设备。参见 第 1 和 2 部分 的列表，其中包括 Arduino 硬件和软件。以下是第 3 部分所需的基本配置。 红外线 LED J任何红外线 LED 都可以，但是 LED 越明亮，效果越好。 红外线传感器 撰写本系列时使用的是 TSOP2138YA 红外线传感器（也是 All Electronics 的产品）。 10,000 ohm 电阻 表面斑纹为棕色-黑色-橙色。 82 ohm 电阻 灰色-红色-黑色。 0.1uF 电容 您将需要一个 0.1uF 的电容。 开关 您需要一个单极瞬态开关。 电线 22 规格的实心线或绞线。 PVC 或其他硬管 只需很短的管子。 一个小型放大透镜（直径 3/4 英寸） 可以使用廉价的塑料制品，比如一些团体喜欢用的透镜。 您也可以通过一个工具包获得所有这些零件（参见 参考资料）。 当制作接收器原型时，实验电路板是个不错的方法，我们可以继续使用实验电路板构建 'Duino tag 枪的原型。您将需要将所有零件焊接在一起，才能使枪具有不错的外观。 在 第 2 部分 中，我们通过使用远程控制装置来帮助测试接收器，解决了对第二把枪的需求。在这里，我们需要另一把枪，或者至少需要另一个接收器。从相同的设备中试射和检测是不切实际的。（这虽然可能实现，但是如果拥有两把枪，或者有一些构建了自己的枪的朋友，将更容易实现预期功能）。 在深入讨论发射器之前，我们需要回顾一下接收器。 接收器和发射器准备工作 首先，您将调整接收器，预防不必要的远程干扰。然后，您将红外线发射器的元件组合在一起：红外线 LED 和按钮。 减少远程干扰 我们首先构建接收器，我们还未花太多时间来尝试防止人们欺骗。现在您将要确保接收器不会从远程控制装置挑选不相干的代码，比如您已用于测试的代码。有许多方式可用于解决此问题。在本教程中，我们将添加一个结束位，这与已经存在的开始位很相似，如果结束位出现异常，代码将中断执行。 1.设置 endBit 的阈值，使其大于 startBit，如清单 1 所示。 清单 1. 设置 endBit 的阈值，使其大于 startBit int startBit = 2000; // This pulse sets the threshold for a start bit int endBit = 3000; // This pulse sets the threshold for an end bit int one = 1000; // This pulse sets the threshold for 1 int zero = 400; // This pulse sets the threshold for 0 2.在 senseIR 函数中，声明一个附加变量来保存结束位。 清单 2. 声明附加变量来保存结束位 int who[4]; int what[4]; int end; 3.在读取 who 和 what 位之后读取结束脉冲。 清单 3. 读取结束脉冲... what[1] = pulseIn(sensorPin, LOW); what[2] = pulseIn(sensorPin, LOW); what[3] = pulseIn(sensorPin, LOW); end = pulseIn(sensorPin, LOW); 4.检验结束位是否正确设置。如果看到的结果不合意，可以中断读取，如下所示。 清单 4. 中断读取 if (end <= endBit) Serial.println("bad signal"); ret[0] = -1; return; } 这应该有助于减少来自远程控制装置的干扰。现在，是时候将红外线发射器的元件组合在一起了。 组装元件 我们需要将两部分连接起来：红外线 LED 和按钮。 将 Arduino 上的引脚 3 分配用于为 LED 供电。您还需要布置一个电阻来帮助保护 LED。使用 LED Calculator 仔细检查 LED 的需求是一个不错的想法（参见 参考资料）。示例中的 LED 需要一个 82 ohm 电阻。将引脚 3 连接到电阻的一个管脚，将另一个管脚连接到红外线 LED 的正极管脚。将红外线 LED 的负极管脚接地。一开始不将这些连接固定很有帮助。如果遇到问题，您可能需要使用彩色 LED 调试发射器。 要连接按钮，使用 Arduino 网站的 Pushbutton 教程中所用的基本设置（参见 参考资料）。使用引脚 4 作为按钮的输入引脚。将 10,000 ohm 电阻的一端连接到 5v 电压供应引脚（用于为红外线辐射传感器供电的引脚）。将电阻的另一端连接到按钮的一个管脚，Arduino 电路板上的引脚 4 也采用这种连接方法。将按钮的另一个管脚接地。 这就是现在所需的操作，图 1 显示了完成之后的原型。 图 1. 原型图 这个原型尝试以紧凑的方式布置所有元件。这里（以及前一部分中）需要注意的重要一点是，红外线 LED 和红外线传感器都面对相同的方向。当需要将组装为一把枪时，您可能需要将元件从电路板上移除，将它们连接并焊接在一起，使元件的布置稍微松散一些。但是现在您只需了解一般的思想，这些足够帮助您编写发射器代码了。 编写发射器代码 在这一节中，我们编写发射器的代码，在 setup 函数中设置变量和初始化引脚。 编写代码 首先，快速回顾一下我们组装的枪将需要发射的信号类型，以及枪的行为方式。 当编写接收器代码时，您告诉接收器识别一个开始位，然后读取 9 个脉冲。前 4 个脉冲用于表示开火的游戏者（Who）。接下来的 4 个脉冲用于表示开火的内容（游戏者的级别或裁判的命令编码 —— What）。最后一个脉冲式在本教程开始部分添加的，用于帮助终止来自这一区域中任何远程控制装置的错误的信号读取操作。 您知道枪在开火模式下的一些行为方式。当建立了 'Duino tag 的规则之后，有许多状态可能影响到枪的行为方式。重要的状态包括： 1.当游戏者扣动扳机时，进行一次射击。 2.当枪开火时，它应该发出一种类似 “砰，砰” 的声音。 3.没把枪具有 6 次 “安全的” 射击。不能重新装载子弹。这些射击能够在无危险的情况下进行。 4.游戏者触发的超过第 6 次的每次射击都会增加灾难性枪故障的可能性。在灾难性枪故障中，游戏者将被消灭。 您现在能够进行一些编码工作了。首先在 setup 函数中设置所有变量并初始化引脚。打开 第 2 部分 中的文件，并向其中添加一些代码。或者，打开 代码归档 文件并使用其中的代码。 初始化和设置 您已经添加了两个引脚：一个归红外线 LED 所有，另一个归按钮扳机所有（分别为引脚 3 和 4）。您应该将这些引脚声明为变量，就像声明其他引脚一样，这会对在以后重新连接元件时带来许多方便。 您还需要其他一些变量： 1.一个变量用于保存按钮引脚的值 2.另一个变量用于记住您是否已经按下按钮开火 3.第 3 个按钮用于确定在传输二进制数据时，每位之间的等待时间 最后的结果类似于清单 5。 清单 5. 创建更多变量... int senderPin = 3; // Infrared LED on Pin 3 int triggerPin = 4; // Pushbutton Trigger on Pin 4 ... int trigger; // This is used to hold the value of the trigger read; boolean fired = false; // Boolean used to remember if the trigger has already been read. int waitTime = 300; // The amount of time to wait between pulses ... 在 setup 函数中，您需要初始化引脚 3 和 4。引脚 3 供 LED 所有，因此它被设置为输出。引脚 4 归按钮所有，因此它被设置为输入。您还应该将引脚 0 的读数作为随机数生成器的种子。因为您没有使用该引脚，一个读数将返回一个足够随机的数供使用，如下所示。 清单 6. 为随机数生成器提供种子... pinMode(senderPin, OUTPUT); pinMode(triggerPin, INPUT); ... randomSeed(analogRead(0)); ...现在您拥有了所需的变量，输出引脚也经过了初始化，您可以编写代码来使枪开火了。 编写开火代码 使枪开火的代码分为几个不同的函数： 1.senseFire— 检查按钮是否被按下。 2.fireShot— 当检测到按钮按下操作时进行射击。 3.oscillationWrite— 用于处理射击的实际摆动范围。 4.selfDestruct— 用于处理当枪射击次数太多时自我毁坏。 senseFire 函数 要感知并正确处理按钮按下操作，您需要检查几点：引脚状态、显示按钮按下操作是否已被处理的变量、游戏者是否还活着，以及游戏者已射击的次数。如果游戏者仍然活着，并且射击次数大于 6，那么您需要检查这次射击是否会损坏枪并终结游戏者。在这种情况下，我们假设一把枪可能射击的绝对最大次数为 20。在 1 到 20 之间挑选一个随机数，如果该数字不大于已射击次数，枪将自我摧毁。（您将向稍后将编写的一个函数抛出一个自我摧毁异常）。senseFire 函数与清单 7 类似。 清单 7. senseFire 函数 void senseFire() { trigger = digitalRead(triggerPin); if (trigger == LOW && fired == false) { Serial.println("Button Pressed"); fired = true; myShots++; if (myHits <= maxHits && myShots > maxShots && random(1,20) <= myShots) { Serial.println("SELF DESTRUCT"); selfDestruct(); } else if (myHits <= maxHits) { Serial.print("Firing Shot : "); Serial.println(myShots); fireShot(myCode, myLevel); } } else if (trigger == HIGH) { if (fired == true) { Serial.println("Button Released"); } // reset the fired variable fired = false; Serial.println("Button Released"); } } 一个稍微复杂一点的问题是在何处 调用这个函数。不能在编写它时就将它放在 loop 函数中，否则它将只在识别了一次传入的射击时才感知一个按钮按下操作。您可以将该函数放在 senseIR 函数的 while 循环中，但是这会使条理不那么清晰。我们将该 while 循环更改为一条 if 语句，并在未收到信号时从函数返回。您还需要对 pulseIn 读数进行细微的修改，添加一个超时值（否则，pulseIn 将在两次读数之间等待 1 秒的时间）。 清单 8. 修改 pulseIn 读数... if (pulseIn(sensorPin, LOW, 500) < startBit) { digitalWrite(blinkPin, LOW); ret[0] = -1; return; } ... 现在您可以将 senseFire 函数调用添加到 loop 函数中。 清单 9. 对 loop 函数的 senseFire 函数调用 void loop() { senseFire(); senseIR(); if (ret[ 开火和感知是独立的函数。如果一把枪在开火的同时被击中了，那么这次射击将不被记录。由于我们的示例播放声音的方式，在播放声音期间可能会出现这种情况。可以在以后采取许多方式改进这一问题，只要您有兴趣去钻研。但是现在无需过分担心这一点。如果每个人的枪都以相同方式工作，以相同方式构建，那么游戏环境本质上就是平等的。 fireShot 函数 当检测到一个按钮按下操作，并且您知道枪没有自我摧毁时，您可以进行实际的射击。记住，一次射击包含两部分信息（Who 和 What），这两部分信息包含在开始位与结束位之间。这些值保存在 myCode 和 myLevel 变量中，并且会传入 senseFire 函数。 fireShot 函数遵循以下顺序： 1.打开反馈 LED 2.将 Who 编码为二进制码 3.将 WHAT 编码为二进制码 4.发送开始位 5.发送二进制代码 6.发送结束位 7.播放一个音调 8.关闭反馈 LED 我们将声明该函数，初始化一个数组来保存编码的数据，并打开反馈 LED，如下所示。 清单 10. 声明 fireShot 函数并初始化一个数组 void fireShot(int player, int level) { int encoded[8]; digitalWrite(blinkPin, HIGH); 首先使用游戏者信息填充编码的数据数组，然后使用级别信息进行填充。 清单 11. 填充数组 for (int i=0; i<4; i++) { encoded[i] = player>>i & B1; //encode data as '1' or '0' } for (int i=4; i<8; i++) { encoded[i] = level>>i & B1; } 发送开始位，如代码清单 12 所示，通过使用??后将编写的 oscillationWrite 函数来实现。发送了每个位之后，您需要发送一个所谓的分隔符（实际上是一次闪光）并暂停片刻。 清单 12. 发送开始位 oscillationWrite(senderPin, startBit); digitalWrite(senderPin, HIGH); delayMicroseconds(waitTime); 现在您已经发送开始位并暂停，您将返回编码的数组，通过同一个 oscillationWrite 函数发送每一位，并在每位末尾添加一个分隔符和一个时间停顿。 清单 13. 在每位末尾添加一个分隔符和一个时间停顿 for (int i=7; i>=0; i--) { if (encoded[i] == 0) { oscillationWrite(senderPin, zero); } else { oscillationWrite(senderPin, one); } digitalWrite(senderPin, HIGH); delayMicroseconds(waitTime); } 发送结束位，播放一个音调，以指示以进行了一次射击，然后关闭反馈 LED。 清单 14. 发送结束位 oscillationWrite(senderPin, endBit); playTone(100, 5); digitalWrite(blinkPin, LOW); } 这里播放的音调非常简单。将播放的所有音调抽象为一个独立的函数集，以便更加轻松地包装它们，这是一个重要改进。 本系列中的一些函数（包括 fireShot，尤其是 oscillationWrite）都基于或派生自 Paul Malmsten 编写的代码和 Arduino 论坛上发布的代码（参见 参考资料）。向公众提供此代码使本系列的学习变得更加轻松。 oscillationWrite 函数 oscillationWrite 函数，如清单 15 所示，处理以期望频率发射信号：38 KHz。您将打开发送引脚 13 微秒，然后关闭 13 微秒（这构成一个周期），持续时间由您想要发送的内容决定（0 还是 1，开始位还是结束位）。 清单 15. oscillationWrite 函数 void oscillationWrite(int pin, int time) { for(int i = 0; i <= time/26; i++) { digitalWrite(pin, HIGH); delayMicroseconds(13); digitalWrite(pin, LOW); delayMicroseconds(13); } } 如果您决定对传感器和枪使用不同的频率，则需要重新编写本节中的相应代码。 selfDestruct 函数 selfDestruct 函数如清单 16 所示。当一把枪射击太多次时，游戏者将被终结。这可以通过将游戏者的击中次数设置为 maxHits+1 并播放一个有趣的声音来实现。 清单 16. selfDestruct 函数 void selfDestruct() { myHits = maxHits+1; playTone(1000, 250); playTone(750, 250); playTone(500, 250); playTone(250, 250); } 现在，您可以对枪进行试射了。放松一下，然后准备扣动扳机。 测试枪 是时候进行最有趣的一部分了：测试枪。 将您完成的代码上传到 Arduino 电路板上。为了进行测试，您将需要两把枪和两台计算机。（您可以使用两把枪和一台计算机进行测试，但是您只能从一把枪获得串口消息）。 打开串口监视器并开始触发枪，直到其摧毁为止。您应该看到枪正在射击，一直到触发了自我摧毁序列为止，如下所示。 图 2. 串口监视器 重置电路板，设置第 2 把枪，并在近距离范围进行瞄准，使红外线 LED 瞄准连接到串口监视器的枪上的红外线辐射传感器。两把枪都应该发出某种声音，并且您应该看到在串口监视器中记录的射击。 图 3. 在串口监视器中记录的射击 向后退几步，然后再射击。多退几步就不能再记录您的射击了。您的射击范围可能只有几英尺。这可能看起来没多大用处，但这个问题可以轻松解决。 增加范围 要增加射击范围，您需要使用透镜聚焦红外线信号。这个过程非常简单，但是效果很出色。您将用到放大透镜、某种硬管（我使用 3/4 英寸的 PVC），以及以及某种测量距离的工具（比如标尺）。 您需要确定放大透镜的焦距。焦距可用于测量为了能够清晰地看到一个对象，对象需要离透镜多远。（这个概念很简单，但在这里已经够用）。换句话说，它表示从透镜的中心到通过透镜的光聚焦的点之间的距离，如图 4 所示。 图 4. 焦距 如果您不知道透镜的焦距，让阳光照射到一张白纸上，并使用放大透镜将光线聚焦到纸上。您小时候可能玩过这种游戏，或者在自然科学课程中进行过类似实验，就是使用太阳光将一张纸点燃。一旦能够看到光源被清晰地聚焦，测量从透镜到纸张之间的距离。这段距离就是焦距，也就是您想让红外线 LED 离透镜的距离。 将硬管长度调整为这个长度值，并在一端安装上透镜。具体实现方式取决于您的硬件。为了测试本教程中的示例，我们通过小型的绝缘带将透镜与 PVC 管连接在一起。显然，您只应该将透镜的边缘与管子粘结在一起，因为粘住透镜的面会出现问题。 类似地，您可能还希望将红外线 LED 与管子的另一端结合在一起，让 LED 处于管子中央，并且正对着透镜。您可以从塑料上剪下一个小圆圈，使用一个小型 PCB 来附加 LED，或者钻取 PVC，将 LED 胶粘在其上。为了测试本教程中的示例，我们使用了一个塑料垫圈和一些绝缘带。不管怎样您都会发现，这比在尝试确定每个元件的位置之前就将一些导线焊接到 LED 上要容易得多。 放置好透镜之后，重置两把枪，并再次尝试进行一些测试射击，并逐渐将射击距离调得更远。射击距离越远，您瞄准枪来记录一次击中操作所需的精确度将越高。使用示例中的元件，在第一次尝试中，范围从 12 英尺增加到了大约 60 英尺。 一些人可能会详细说明透镜的选择，以及透镜之间的优缺点。一般而言，当测试和选择透镜时，您需要处理两个问题：最后的射击范围是多少？最后的精确度是多少？增加了射击范围的透镜同样需要更精确地进行射击，您将能够从更远的距离射击他人，但是您需要更加精确，甚至在近距离也是如此。减小了射击范围的透镜将允许更低的精确度，您将无法从很远的距离射击他人，但是您不必进行精确瞄准。您和您的朋友可能很快就会在军备竞赛中发现谁拥有最好的透镜。通过对基本的硬件集进行标准化，可以减少不公平的比赛条件。 所有一切都具备了，但它看起来不太像一把枪。下一节将简短探讨一下包装元件的不同方式。 包装枪 完全控制硬件的每个方面是一把双刃剑。您可以让其做您想要的任何事情，让其具有您想要的任何外观，但是您可能无法访问您可能用来为新 'Duino Tag 枪创建塑料模型的机器。 首先，不要感觉这是使您的枪像手枪的必要条件。一些具有最佳外观的虚构的枪看起来就像怪异的远程控制装置。我们可以将枪包装为车库门的钥匙、古老的无线手持设备等类似设备。环顾一下您的房间，找出已不再使用的硬件，将其改造为能在手中使用的装置，并留出空间来放置 Arduino 电路板和您的透镜包装。 第二，不要觉得您的枪必须完美无暇。您无需使所有元件都像时间旅行者使用高科技合金铸造出来的一样。想要完成的产品完美无暇并没有错，但是如果您无法让所有线缆都完美地布置在包装中，或者如果您必须切割一个粗糙的洞来发射信号，那么请抛开美学吧。实际上，您可能在一个昏暗的房间的桌子上使用回收的部件来构建您的 'Duino tag 枪，并且借助含咖啡因的饮料来提神。这使您看起来像一个狂热的科学家。不要害怕您的枪看起来像狂热的科学家的研究成果。 要获得更加完美的外观，可以采用几种不同的方法。您可以改装一个现有的玩具枪。泡沫飞镖枪（foam dart gun）和压力水枪都是不错的选择。或者您可以在一个工程箱（或者 PVC 分线盒）中钻取一些洞，将透镜管子与 PVC 配件安装在一起。喷漆、热收缩管子，以及金属绝缘带都可以使枪更加美观。 要包装为本系列构建的枪，我采用了一个从旧货店购买的较老的空军泡沫飞镖枪（花了 0.5 美元）。我打磨掉了徽标，剥掉了内部的部件，在外壳上的适当位置用小刀刻蚀掉一部分，并对整个枪刷上黑色的喷漆。添加一些电线和管子将使枪更加美观，如图 5 所示。 图 5. 包装 'Duino tag 枪 无论您如何包装您的元件，都需要坚持一些指导原则： 红外线辐射传感器的电阻和电容需要连接在传感器附近。 让传感器面向枪的前端，不要让其被其他元件挡住。 使用笔直的透镜管子，这样可以轻松进行瞄准。 包含一些电池来为 Arduino 电路板供电。 能够轻松访问 Arduino 电路板，至少能够轻松上传新软件。 包装元件时要充分发挥创造能力。不要害怕产品脱离了设计。 添加更多乐趣 'Duino 项目还有很大的扩展空间。引脚 0-1 和 5-11 还未分配，还有 6 个模拟引脚未涉及到。协议中还有大量空间可用于添加更多函数。以下是对该项目进行进一步拓展的一些想法。 裁判的枪 可以在协议中为裁判分配升级、降级、重置和复活游戏者的能力。裁判的枪将拥有不同的软件，并且需要更多按钮。实现协议的最简单方式是构建一个包含 4 个按钮的枪，每个按钮对应一项功能（升级、降级、重置和复活）。这个枪不需要透镜。 弹药/击中计数器 使用一种条形 LED、一系列独立的 LED，或者（如果您敢于探索）一个 LCD 显示器，您将能够向游戏者显示已经射击的次数、已经击中的次数，哪个游戏者击中了最后一次，等等。 响应代码 使传感器面向枪，将成功/失败代码构建到协议中，您的枪将能够在收到命令时使用击中或未击中代码来响应。这使各个单位之间能够进行双向通信。还可能使用 LCD 屏幕和一个带有多个按钮的命令枪想友好的单位发送一组基本命令。 目标 您可以轻松装配可能安装游戏区域周围的目标集合。这些目标可用于进行瞄准、进行随机射击，或者充当自动裁判。 远程靶标 将这个基础硬件挂载到一个（或几个）远程控制车上，这能够扩大游戏应用范围，甚至完全改变游戏体验。您可以让各个远程控制车朝相反方向行驶，或者完全使用车进行游戏。 红外线辐射手榴弹 将需要一个坚固的外壳，但是很容易装配能够以正确的级别和频率发生爆炸的红外线 “手榴弹”。 以上是一些对您有帮助的想法。您可以进行试验。与您的朋友自由讨论如何改进此游戏。设计您自己的规则和变体。您已经亲自完成了整个项目。不要害怕将其分解并进行改进。 结束语 您可能是第一次构建这样的项目，或者第一次使用 Arduino。您也可能使用 Arduino 很长时间了。无论如何，我希望您能从这个项目中获得乐趣。感谢您阅读本系列！ 我希望能够听到您的评论，看到您取得的成果。如果您完成了项目，并且还存在疑问，或者想要进一步研究，请访问 chaoticneutral。您可以以照片的形式与我们分享您完成的 'Duino Tag 枪。 下载 os-arduino3-DuinoTagPart3.zip
构建基于 Arduino 的激光游戏，第 2 部分: 动手实践
开始之前 无论您是 Arduino 新手还是经验丰富的构建者，此项目都有适合您的内容。没有比创建交互式物理对象更令人满意的事情了，因为在需要中断或者需要修改时，您知道所有部件的位置以及所有部件的工作原理。'Duino Tag 枪是适合独立完成或与朋友共同完成的优秀项目。要完成此项目，您至少应当基本了解电子学（您应当知道寄存器是什么，但是不必知道其中的深奥原理），并且了解编程（您应当知道循环和变量是什么，但是您不必解析 Big O Notation）。您可以勇敢地进行动手实践。 关于本系列 在本系列中，我们将使用 Arduino 技术来创建名为 'Duino Tag 的基本交互式激光游戏： 第 1 部分：Arduino 基础知识：了解一些 Arduino 基础知识，布置项目，并且做一个帮助您了解红外线工作原理的实验。 第 2 部分：构建和测试 'Duino Tag 枪的接收器部分，包括测试。 第 3 部分：构建发射器并完成 'Duino Tag 枪。 关于本教程 要继续学习本教程，您无需具有任何电子学工作经验，尽管具有使用电子元件的经验肯定会有帮助。对于微控制器经验也是如此。如果您使用过微控制器，则有一定的优势，但是记住 Arduino 平台非常适合没有相应经验的人员。首先，您应当愿意拓展自己的技能。使用电子器件和微控制器会是一种有益的经验。大多数软件工程师没有机会为与物理世界交互的设备编写代码，而 Arduino 提供了使用交互式设备的低成本入口点。 本教程主要讨论游戏的构建。在了解了 Arduino 的基本知识之后，本教程将用大部分篇幅讨论如何构建和测试 'Duino Tag 枪的接收器。您将编写很多代码。我们已经遇到了类似于 “鸡与蛋” 的问题：没有要传输的信号，我们就无法实际测试接收器，同样地，没有接收传输信号的接收器，我们也无法实际测试传输过程。幸运的是，有一个优秀的解决方案能够出色地解决这个问题。 系统要求 对于本教程，我们需要一些工具和设备。参见 教程：构建基于 Arduino 的激光游戏，第 1 部分：Arduino 基础知识 中的列表，其中包括 Arduino 硬件和软件。以下是需要的基本配置。 红外线 LED 任何红外线 LED 都可以，但是 LED 越明亮，效果越好。 红外线传感器 撰写本系列时使用的是 TSOP2138YA 红外线传感器（也是 All Electronics 的产品）。 100 ohm 电阻 这类电阻的表面斑纹为棕色-黑色-棕色。 0.1uF 电容 需要一个 0.1uF 的电容。 开关 需要一个单极瞬态开关。 压电（Piezo）元素 需要一个已焊接了导线的元素。 电线 22 规格的实心线或绞线。 还需要一种连接元件的方式，使用实验电路板或将焊接在一起都可以。 要解决 “鸡与蛋” 问题，需要使用一种通用的遥控装置或 Sony Electronics 提供的遥控装置。本系列中的示例使用来自 Sony 康宝 VHS/DVD 播放器的遥控装置，也使用来自分线盒的遥控装置成功进行了测试。 教程：构建基于 Arduino 的激光游戏，第 1 部分：Arduino 基础知识 已经提到，如果与正在构建自己的 'Duino Tag 枪的其他人合作，这个项目会更加有趣。如果有两把枪可供使用，测试工作将更加轻松。如果您决定独自工作，可以自己建立两把枪，这样会使第 2 和 3 部分的学习更加轻松。在第 3 部分中将所有部件结合起来时尤其如此。 现在，我们将重点放在制造并运行基本的接收器上。首先，您需要了解一下红外线传感器。 红外线传感器基础 市场上有许多类型的红外线 (IR) 传感器。我们选择 TSOP2138YA，因为它的成本低廉，而且很容易购买到。 传感器 传感器（如下所示）将照片传感器和一个前置放大器组合在一起。大多数这种类型的 IR 传感器将两种功能组合到一个元件中，这样会更加方便。传感器的外壳充当着 IR 过滤器。正如 教程：构建基于 Arduino 的激光游戏，第 1 部分：Arduino 基础知识 所讨论的，IR 到处都可以买到。这种传感器以及其他这类传感器的外壳可用于过滤掉背景 IR。该传感器使用 38 kHz 的载波频率。这是常用的 IR 频率，意味着我们的枪也需要以 38 kHz 的频率传输。这个传感器具有一个简单的 3 引脚接口： 1.引脚 1 在一个 38 kHz IR 信号被感知到时提供一个信号。 2.引脚 2 连接供应电压。 3.引脚 3 用于接地。 传感器的运行原理就是这么简单。 图 1. TSOP2138TA 传感器 现在连接一些元件，并使传感器运转起来。 连接传感器 以下指令假设您使用实验电路板来连接元件。如果进行焊接，可以根据需要调整指令。下图展示了一个微型实验电路板，它被附加到一个从 NKC Electronics 购买的 Arduino 保护装置上。 对于 IR 传感器的连接，需要使用电阻和电容。这个传感器的规格说明建议添加一个电容和电阻来 “抑制电源供应干扰”。当最终确定枪的配置时，需要将这些附加元件连接到离传感器尽可能近的地方。通常，人们通过将传感器、电容和电阻装配在小型印刷电路板或 PCB 上来实现这一点。 传感器从引脚 1 输出，该引脚应该连接到 Arduino 板的引脚 2 上。 供应电压通过引脚 2 传入传感器，它应该通过 100-ohm 电阻。将 Arduino 板上的 +5v 引脚连接到电阻，将电阻连接到 IR 传感器上的引脚 2。 IR 传感器通过引脚 3 接地。0.1uF 电容应该连接在传感器上的引脚 2 与 3 之间，如下图所示。 图 2. 连接传感器 现在已经连接好了传感器，接下来设置两个支持元件。这需要用到压电元素和红色的 LED。（与第 1 部分中使用的红色 LED 相同）。 将 LED 的正引脚连接到 Arduino 板上的引脚 13，将 LED 的负引脚接地。正常情况下，您应该需要添加一个电阻，以防止烧坏 LED，但是 Arduino 已包含了一个连接到引脚 13 的电阻。您将使用这个 LED 来指示正在接收一个信号。 同样地，当接收一个信号时，您将使用压电元素来产生声音。将压电元素的正极导线连接到 Arduino 的引脚 12，将负极导线连接到接地引脚。 这样，传感器就连接完成了，结果应该与图 3 中的传感器类似。 图 3. 连接完成 现在可以开始编写传感器的代码了。但是要确定数据传输的方法，您首先需要确定一个简单的协议。 设置 'Duino Tag 协议 当开枪时，发射器中的 IR LED 广播一部分编码数据。在我们能够实际解码该数据之前，我们需要知道该数据将会是什么。这样做需要明确地建立一些假设。 'Duino Tag 不是欺骗者玩的游戏。 这意味着每个人都根据规则玩游戏。我们将建立一些协议，确保没有人能够进行欺骗。如果您需要这样一个协议，可以自由修改需要的代码。但是，预防欺骗的最好方式是在游戏开始之前想每个游戏者的枪上传相同的软件。本教程不会介绍加密或解密信号，也不会尝试阻止人们欺骗。 团队在本质上是有机的，不会受到硬件的约束。 这支持所有类型的行为，比如建立口头上的合作、背叛您的同盟者，以及偶尔向您的朋友射击。 可能需要设置不同的弹药类型和不同的游戏者级别。 应该在协议中为这些设置保留一定的空间。 有时可能需要一个裁判。 裁判应该能够更改游戏者的级别或弹药，对枪进行重置等等。 上面列出了这些假设，我们将以二进制形式传输数据，因此余下的惟一问题是我们应该为协议提供多少字节？如果您将协议大体分为两部分，第一部分表示 “人物”，第二部分表示 “事件”。比如，游戏者 1（人物）以强度 2 进行射击（事件），或者裁判 2（人物）把您复活（事件）。 我们首先假设每 6 位游戏者需要一个裁判，12 位游戏者参与的游戏已经非常大。使用 3 位，您可以传输 16 个惟一编码 —— 已经足够了。同样地，16 个操作远远能够满足一个游戏者或裁判开始游戏的需求了。您能够进行16 种射击类型，或执行 16 种不同的管理操作。您总共需要 8 位 — 前 4 位对应于游戏者的编号，后 4 位将对应于一种操作（射击强度或裁判操作）。现在您大体了解了期望的情形，接下来为传感器编写一些代码。 编写传感器代码 传感器代码以 4 部分的形式编写。首先，您需要在 setup 函数中设置引脚并运行一个简单的测试序列。然后构造一个函数来解码传入的 IR 信号。这个函数构造完成之后，设置 Loop 函数来监听信号。最后，选择一个函数来播放一些音调。代码编写完之后，就可以开始测试了。 设置引脚和变量 在深入研究 setup 函数之前，我们应该设置一些变量。使用变量来保存引脚分配，可以轻松地对 Arduino 板进行再次了解，无需大量更改代码。当将元件连接到 Arduino 时，您将引脚 2 分配给传感器，将引脚 12 分配给扬声器，将引脚 13 分配给反馈 LED（如清单 1 所示）。 清单 1. 保存引脚分配的变量 int sensorPin = 2; // Sensor pin 1 int speakerPin = 12; // Positive Lead on the Piezo int blinkPin = 13; // Positive Leg of the LED we will use to indicate // signal is received 还需要 3 个变量来设置传感器的阈值。一个变量设置发射器将发送的 “起始位” 的阈值，另外两个变量以传感器信号脉冲长度的形式表示 0 和 1 值。 清单 2. 用于设置传感器的阈值的变量 int startBit = 2000; // This pulse sets the threshold for a transmission start bit int one = 1000; // This pulse sets the threshold for a transmission that // represents a 1 int zero = 400; // This pulse sets the threshold for a transmission that // represents a 0 还需要一个数组来保存 IR 解码的结果。 清单 3. 保存 IR 解码结果的数组 int ret[2]; // Used to hold results from IR sensing. 这些变量在遇到用于解码传入数据的函数时才会发挥作用。我们现在将它们放在一边，主要构造 setup 函数。这个函数需要设置各种引脚的模式，然后它应该提供反馈来指示系统已经准备好 —— 一两次闪烁的灯光和蜂鸣声。 设置引脚模式 首先，您需要设置引脚模式，如清单 4 所示。有 1 个输入引脚（引脚 2）和 2 个输出引脚（引脚 12 和 13）。 清单 4. 设置引脚模式 void setup() { pinMode(blinkPin, OUTPUT); pinMode(speakerPin, OUTPUT); pinMode(sensorPin, INPUT); 我们让灯光闪烁 3 次，每次播放一个音调。 清单 5. 设置灯光和音调 for (int i = 1;i < 4;i++) { digitalWrite(blinkPin, HIGH); playTone(900*i, 200); digitalWrite(blinkPin, LOW); delay(200); } 借助 playTone 函数，第 1 个数字控制音调，第 2 个数字控制持续时间。最终结果是以不同音调播放的 3 个音符，构成一个不错的启动序列（下一节将更详细地对此进行讨论）。最后，为了进行测试，我们对串口监视器进行设置，以便您能够在监视器中观察结果。 清单 6. 设置串口监视器 Serial.begin(9600); Serial.println("Ready: "); } setup 函数已经完成。您已经正确设置了引脚模式，甚至初始化了一个串口连接。现在，我们看一下将用于解码从 IR 传感器传入的数据的函数。 解码 IR 开始解码 IR 之前，我们知道我们需要两个很小的数组来保存两部分数据 —who 和 what—，还需要一个数组来保存两个返回值。 清单 7. 保存数据的两个数组 void senseIR() { int who[4]; int what[4]; 我们在前面设置了将在此函数中使用的 3 个变量：startBit、1 和 0。当使用 pulseIn 函数时，这些变量将设置一些阈值，用于解释传感器引脚的行为。 pulseIn 函数是使用 Arduino 语言编写的，用于测量将一个指定的引脚（本例中为传感器引脚）设置为高或低的持续时间。pulseIn 函数的测量值以微秒为单位返回。使用这个函数，您能够解码正在发送的数据。但是，您需要设置一些阈值，用于确定表示 0 或 1 的时间长度。您还需要一些信息来指示您处于数据集的开始位置，我们选择 startBit 来完成此任务。startBit 变量指示： 1.2,000 微秒长的脉冲将用于表示数据集的开始。 2.超过 1,000 微秒的脉冲将用于表示 1。 3.400 到 1,000 微秒的脉冲将用于表示 0。 接下来要做的就是等待一个 startBit。当等待 startBit 时，需要确保反馈 LED 已关闭。但是，只要得到一个 startBit，就打开反馈 LED。 清单 8. 打开反馈 LED while(pulseIn(sensorPin, LOW) < startBit) { digitalWrite(blinkPin, LOW); } digitalWrite(blinkPin, HIGH); 现在，您已经收到了一个 startBit，您需要读取接下来的 8 个脉冲，将它们分配给合适的数组。 清单 9. 将脉冲分配给合适的数组 who[0] = pulseIn(sensorPin, LOW); who[1] = pulseIn(sensorPin, LOW); who[2] = pulseIn(sensorPin, LOW); who[3] = pulseIn(sensorPin, LOW); what[0] = pulseIn(sensorPin, LOW); what[1] = pulseIn(sensorPin, LOW); what[2] = pulseIn(sensorPin, LOW); what[3] = pulseIn(sensorPin, LOW); 将数据分配给数组之后，将?据标准化为一组简单的 0 和 1，并发出这些二进制数以供转换。在此过程中，您将向串口监视器输出有价值的消息。清单 10 适用于 who 数组，但是 what 数组的代码几乎一样。 清单 10. who 数组 Serial.println("---who---"); for(int i=0;i<=3;i++) { Serial.println(who[i]); if(who[i] > one) { who[i] = 1; } else if (who[i] > zero) { who[i] = 0; } else { // Since the data is neither zero or one, we have an error Serial.println("unknown player"); ret[0] = -1; return; } } ret[0]=convert(who); Serial.println(ret[0]); 可以通过任何合适的方式解码数据。这个函数被抽象为 convert 函数，如下所示。 清单 11. 解码数据 int convert(int bits[]) { int result = 0; int seed = 1; for(int i=3;i>=0;i--) { if(bits[i] == 1) { result += seed; } seed = seed * 2; } return result; } 在决定以任何方式更改协议时，可以通过对函数进行抽象来很大程度地简化工作。现在，我们已经有了解码红外线信号的代码，您需要设置 loop 函数来监听信号，并执行相应的行为。 loop 函数 现在，loop 函数非常简单。loop 函数主要供 senseIR 函数调用，其中，while 循环等待一个开始位。但是，一旦 senseIR 函数返回，您将在前面声明的 ret 数组中查看结果，并操作这些结果。这意味着要忽略无效的值，返回一个有效值时播放一个音调，并将该值转储到串口监视器。 清单 12. loop 函数 void loop() { senseIR(); if (ret[ 放置了更多元件之后，可以根据返回值执行操作。至于现在，这些元件对于测试已经足够了。您还需要构造前面提到过的 playTone 函数。 playTone 函数 playTone 函数（如清单 13 所示）来自 Arduino 站点上的 Melody 教程（参见 参考资料）。这是一段公共域代码，用于向我们的压电元素发送一个方波，为我们创建音调。这个函数很简单，它接受两个参数：音调（一个数值）和播放该音调的长度（以微秒为单位）。 清单 13. playTone 函数 void playTone(int tone, int duration) { for (long i = 0; i < duration * 1000L; i += tone * 2) { digitalWrite(speakerPin, HIGH); delayMicroseconds(tone); digitalWrite(speakerPin, LOW); delayMicroseconds(tone); } } 现在您无需太深入地研究这个函数。如果您想要 'Duino Tag 枪播放悦耳的音调或更加有趣的声音，可以参阅 Arduino 站点上的 Audio 教程。现在，您已经具备了开始测试所需的所有条件。 测试传感器 完成代码之后（或者上传了本教程的 代码归档文件 的 Duino_Tag_Sensor 文件之后，您就可以开始测试了。现在，您需要前面提到的遥控装置： 1.打开串口监视器，它将重设 Arduino 板。您应该能够看到启动序列，以及闪烁的 LED 和相关联的蜂鸣声。如果没有看到启动序列，请仔细检查您的连接。 2.在您的 IR 传感器上指定测试遥控装置并开始按下按钮。您应该听到扬声器发出蜂鸣声，看到反馈 LED 在闪烁。您还应该从串口监视器窗口获得一些反馈。监视器输出显示某个按钮按下操作导致人物 4 执行了操作 9。 图 4. 串口监视器窗口中的反馈 3.使用您的遥控装置进行试验，看看还能获得其他哪些返回值。如果无法从 IR 传感器获得任何读数，请仔细检查连线并确保遥控装置电量充足。 现在传感器已经能够正常工作（并经过了测试），您一定感到非常激动。您最初可能对如何实现这一功能一筹莫展，或者以前从未使用过 IR。恭喜您成功了！ 在下一节中，我们将对传感器进行设置，为第 3 部分做准备。让我们为游戏者和裁判建立一些基本行为，以便当发射器发送信息时，您已经准备好进行游戏。在完成第 3 部分之前，对下一节中的代码进行测试比较困难，但是如果借助遥控装置，您将发现一些可用于传输的出色编码组。例如，在我的遥控装置上，Volume Up 的人物/事件始终是 4/9，而 Volume Down 的结果始终是 12/9。 处理解码结果 现在，您能够解码这些正在传输的微型数据集了，并且需要对它们进行一些处理。 建立游戏者和裁判 首先确定谁是游戏者，谁是裁判，以及游戏者编号和开始级别。您还应该设置一些变量来保存允许的射击次数和命中次数，以及您拥有的射击和命中次数。尽管您不会使用射击次数，但是仍然可以定义它们。所有这些都在脚本顶部定义。 清单 14. 设置变量来保存射击和命中次数 int playerLine = 14; // Any player ID >= this value is a referee, // <= this value is a player; int myCode = 1; // This is your unique player code; int myLevel = 1; // This is your starting level; int maxShots = 6; // You can fire this many safe shots; int maxHits = 6; // After this many hits you are dead; int myShots = maxShots;// You can fire 6 safe shots; int myHits = maxHits; // After 6 hits you are dead; 当将代码上传到多把枪时，所需做的是更改每把新枪的 myCode 变量的值。其余的代码可以保持不变。可以使用 DIP 开关来完成这一任务，使用其他方法可能产生硬件问题。但是您可能很想动态地对枪进行重新编码。 现在让我们确定一下这些操作本身到底是什么。如果您以裁判开始，这些操作将更容易定义，您很快就会明白其中的原因。首先定义裁判能够执行的 4 种操作： 1.升级（操作 0） 2.降级（操作 1） 3.重设弹药（操作 2） 4.复活（操作 3） 由于升级和降级需要有一个范围，您应该定义两个变量来保存游戏者的最高和最低级别。 清单 15. 保存游戏者的最高和最低级别的变量 int maxLevel = 9; // You cannot be promoted past level 9; int minLevel = 0; // You cannot be demoted past level 0 接下来，定义游戏者能够执行的操作。实际上只有两个操作：命中和答复。因为，您可以将游戏者的级别信息与命中次数一起发送，可以包含级别 0 的命中次数（操作 0）、级别 1 的命中次数（操作 1）等，直到到达最大值，本例中的最大值为级别 9 的命中次数（操作 9）。通过将级别信息和命中次数一起传递，可以在以后进行有趣的修改，将这些级别与 'Duino Tag 枪的行为结合起来。 答复仅包含两种形式：成功和失败。成功意味着您命中了，或者您的裁判任务生效了。失败意味着您的命中无效，或者裁判任务无法完成，比如对某人的升级超过了可用等级。指定答复成功（操作 14）和答复失败（操作 15）。 所有这些操作都可以以变量的形式存储，如下所示。 清单 16. 将操作存储在变量中 int refPromote = 0; // The refCode for promotion; int refDemote = 1; // The refCode for demotion; int refReset = 2; // The refCode for ammo reset; int refRevive = 3; // The refCode for revival; int replySucc = 14; // the player code for Success; int replyFail = 15; // the player code for Failed; 定义了这些操作之后，就应该在 senseIR 函数中处理这些操作了。首先检查游戏者代码是否位于裁判与游戏者的分界线的上面： if (ret[0] >= playerLine) { 因为您处于裁判区域，因此看一下返回代码。对于升级，如果游戏者的级别没有超出最大值，对它们进行升级并播放叮当音。 清单 17. 如果游戏者未超出对高级别，对其进行升级并播放一个叮当音 if (ret[1] == refPromote) { // Promote if (myLevel < maxLevel) { Serial.println("PROMOTED!"); myLevel++; playTone(900, 50); playTone(1800, 50); playTone(2700, 50); } 对于降级，如果游戏者的级别未到达最低级别，则对其进行降级并播放一个不同的叮当音。 清单 18. 如果游戏者的级别未到达最低级别，对其进行降级并播放一个不同的叮当音 } else if (ret[1] == refDemote) { // demote if (myLevel > minLevel) { Serial.println("DEMOTED!"); myLevel--; } playTone(2700, 50); playTone(1800, 50); playTone(900, 50); 对于弹药重设，将游戏者的射击次数设置到最大值并播放一个叮当音。 清单 19. 在进行弹药设置时，将游戏者的射击自处设置到最大值并播放一个叮当音 } else if (ret[1] == refReset) { Serial.println("AMMO RESET!"); myShots = maxShots; playTone(900, 50); playTone(450, 50); playTone(900, 50); playTone(450, 50); playTone(900, 50); playTone(450, 50); 最后，如果游戏者被复活，重设他的射击次数、命中次数以及级别，并播放一个悦耳的音调。 清单 20. 如果游戏者被复活，重设射击次数、命中次数和级别，并播放一个音调 } else if (ret[1] == refRevive) { Serial.println("REVIVED!"); myShots = maxShots; myHits = maxHits; myLevel = 1; playTone(900, 50); playTone(1800, 50); playTone(900, 50); playTone(1800, 50); playTone(900, 50); playTone(800, 50); } 游戏者操作您已经设置了所有的裁判操作。但是游戏者操作如何设置呢？它们更容易处理，因为它们的种类更少。对于成功答复，应该播放一个表示成功的声音。 清单 21. 成功答复 } else { if (ret[1] == replySucc) { playTone(9000, 50); playTone(450, 50); playTone(9000, 50); Serial.println("SUCCESS!"); 对于失败答复，应该播放一个表示未成功的声音。 清单 22. 失败答复 } else if (ret[1] == replyFail) { playTone(450, 50); playTone(9000, 50); playTone(450, 50); Serial.println("FAILED!"); }在您尝试射击时，命中答复也将播放一个声音，提醒您刚才射击您的游戏者比您更出色。清单 23. 命中答复 if (ret[1] <= maxLevel && ret[1] >= myLevel && myHits > 0) { Serial.println("HIT!"); myHits--; playTone(9000, 50); playTone(900, 50); playTone(9000, 50); playTone(900, 50); } } 对于本例，不要过分追求音调的完美，它们主要用于测试和充当占位符。在您拥有可行的发射器可??测试时，您可以调整音调，直到您它们感到满意为止。 至于现在，如果您的遥控装置返回了可用的测试代码，则可以更改变量的值，尝试用于解码红外线信号的代码的不同部分。拥有经过完整编码的接收器固然不错，但是拥有一个发射器实际上会更好，难道不是吗？第 3 部分将讨论发射器。 结束语 在本教程中，您构建并测试了 'Duino Tag 枪的一个接收器。您对代码进行了处理并开始了游戏的构建。 您现在已准备好构建发射器了。连接好元件，为阅读第 3 部分做好准备。在第 3 部分中，您将非常快速地构建发射器，然后了解如何扩展发射器的功能范围，并了解一些封装选项。您将探索改进系统的方式，以及未来 'Duino Tag 项目的可能实现方式。如果拥有了 教程：构建基于 Arduino 的激光游戏，第 1 部分：Arduino 基础知识 中列出的所有元件，那么您的进展情况一定不错。对于第 3 部分，请确保具备一个单击瞬态开关、一个红外线 LED、一个 82 ohm 电阻以及一个 1k ohm 电阻。其他需要的元件您已经拥有了。 下载 os-arduino2.DuinoTag.zip2KB
开始之前 无论您是 Arduino 新手还是经验丰富的构建者，此项目都有适合您的内容。没有比创建交互式物理对象更令人满意的事情了，因为在需要中断或者需要修改时，您知道所有部件的位置以及所有部件的工作原理。'Duino tag 枪是适合独立完成或与朋友共同完成的优秀项目。要完成此项目，您至少应当基本了解电子学（您应当知道寄存器是什么，但是不必知道其中的深奥原理）并且了解编程（您应当知道循环和变量是什么，但是您不必解析 Big O Notation）。您可以勇敢地进行动手实践。 关于本系列 在本系列中，我们将使用 Arduino 技术来创建名为 'Duino tag 的基本交互式激光游戏： 第 1 部分：了解一些 Arduino 基础知识，布置项目，并且做一个帮助您了解红外线工作原理的实验。 第 2 部分：构建和测试 'Duino tag 枪的接收器部分，包括测试。 第 3 部分：构建发送器并完成 'Duino tag 枪。 关于本教程 要继续学习本教程，您无需具有任何电子学工作经验，尽管使用电子元件的经验肯定对您有用。对于微控制器经验也是如此。如果您使用过微控制器，则有一定的优势，但是记住 Arduino 平台非常适合没有相应经验的人员。首先，您应当愿意拓展自己的技能。使用电子器件和微控制器会是一种有益的经验。大多数软件工程师没有机会为与物理世界交互的设备编写代码，而 Arduino 提供了使用交互式设备的低成本入口点。 本教程是 “构建基于 Arduino 的激光游戏” 三部分系列的第 1 部分，将主要介绍 Arduino 基础知识。我们将开始使用 Arduino，了解语言基础知识和 API。我们将为 'Duino tag 项目做准备，包括设计基础和整合部件列表。我们将了解如何在线订购部件、去哪里购买以及需要哪些部件。最后，使用一些采购的元件和一些基本测试代码，我们将演示控制枪械操作的基本原理。 系统要求 对于本教程，我们需要一些工具和设备： 具有 USB 端口的计算机 虽然从技术的角度来说并不合适（许多类型的 Arduino 硬件接口都是基于旧式串行端口的），但是本系列在撰写时假定您使用的是基于 USB 进行通信的模型。Arduino 软件可用于 Microsoft® Windows®、Mac OS X 和 Linux®（32 位和 AMD 64 位）。 Arduino Diecimila 这是本教程中使用的 Arduino 硬件的具体模型。您可以替换 Arduino Duemilanove。请从正规的网上经销商那里订购一个。要获得链接，请参阅 参考资料。 USB A-B 线 这是类似于已经连接到打印机上的一根 USB 线。 基本的 5mm 红色发光二极管（Light-Emitting Diode，LED） 您可能已经有一个，或者可以在 Radio Shack 或者从网上经销商那里购买。 闪光灯 越亮越好。 Arudino 软件 要下载 Arudino 软件，请参阅 参考资料。 Arduino 的安装和设置指南 要下载 Arduino 安装指南，请参阅 参考资料。 Arduino 简介 在本节中，我们将介绍 Arduino 硬件的基础知识、如何设置，并简单介绍如何编写软件。 Arduino 是什么？ 简言之，Arduino 板是一个微控制器 — 具有各种输入管脚和输出管脚的小型计算器。使用这些管脚，您可以创建无数交互式设备，包括像闪光灯一样简单的设备，以及通过各种输入创建使用伺服系统和引擎的复杂行为的复杂设备。第一个 Arduino 是 2005 年 1 月由米兰交互设计学院的两位教师 — David Cuartielles 和 Massimo Banzi — 创建的。要获得介绍 Arduino 开发人员的在线杂志文章，请参阅 参考资料。 Arduino 平台由两部分组成：硬件（包括微控制器、电路板等）和软件（编程接口和语言）。平台的两个部分都是开源的。如果需要，您可以下载 Arduino 的图表、购买需要的所有独立部件、切割电路板并从头开始制作一个电路板。有很多用于这一用途的各种项目（如 Freeduino）。图表和 CAD 文件已经在 Creative Commons Share-Alike 许可下发布。用于编写 Arduino 的软件也是开源的。它是用 Java™ 编程语言编写的并且是在 GPL（有一些已经在 LGPL 许可下发布的 C/C++ 微控制器库）下发布的。 有几个可用的微控制器平台，并且许多平台都实现相同的目的：易于编程、与微控制器进行交互、将所有内容封装到一个包中等等。同样地，Arduino 旨在提供一个简单的界面和一个将所有功能集于一身的包，同时尝试提供其他优点： 低成本 可以从头开始构建便宜的 Arduino 板，并且预组装的组件十分便宜。Arduino Diecimila 花费大约 35 美元。 跨平台软件 获得适用于 Microsoft Windows、Mac OS X 和 Linux 的 Arduino 软件。 简单的语言 Arduino 开发人员尝试使语言可以被初学者轻松掌握，但是对于高级用户足够灵活。 开放源码 Arduino 从上到下完全是开源的。如果需要构建或修改软件，您可以随意执行。此外，Arduino 的官方 Web 站点包含丰富的维基，其中的代码样例和示例都是免费共享的。有大量可用的 Arduino 信息。最佳起点位于官方 Web 站点（请参阅 参考资料）。花一些时间阅读该站点中的优秀介绍性信息。 入门 要与 Arduino 硬件进行交互，您需要安装软件。下载适用于您的操作系统的软件并遵循相应的分布安装和设置指南（请参阅系统要求）。它们遵循相同的步骤： 1.解压缩软件 2.安装 Arduino 的 USB 驱动程序 3.连接 Arduino 板 4.启动软件 5.上传示例 我们将不会详细介绍每个步骤，因为这些步骤将随着环境的不同而变化。让我们讨论一下软件和 Blink 示例。 注：按照 Arduino 的说法，独立的脚本和程序被称为 “sketches”。 在启动软件并装入 Blink 示例后（根据入门指南中的说明），您应当会看到类似图 1 的内容。 图 1. Arduino 软件界面 这里简要介绍一下该界面，顶部按钮行将控制基本任务，如编译、创建和保存文件、将代码上传到 Arduino 板中以及打开或关闭 Serial Monitor（稍后还有更多任务）。中间窗格包含 Blink 示例的所有代码，并且底部窗格将显示控制台输出、编译错误和序列消息（打开 Serial Monitor 时）。目前，让我们将精力放在中间的窗格上并讨论 Arduino 语言。 Arduino 语言基础知识 Arduino 语言是基于 C/C++ 的，并且如果您以前使用过这种语言，则应十分熟悉它的构造函数和特性。虽然完整回顾该语言不在本教程范围内，但是让我们剖析 Blink 示例并了解一点该语言的知识。如果您已经可以熟练使用代码，则这些内容可能有些过于简单，因此如果您愿意获取一本手册并进一步深入研究，请随意查阅更多语言参考（要获得更多语言参考，请参阅 参考资料）。 首先，查看大块注释。Arduino 语言中的注释是用两种样式编写的。 清单 1. 块样式 /* * Block Style like this */ 也可以用内联样式编写。 清单 2. 内联样式// inline like this 接下来，注意声明一个变量：int ledPin = 13;。 从这一行可立即看出 Arduino 语言中的行都是以分号为结尾的。此外，从中可以了解在 Arduino 语言中声明变量所需的几乎所有内容。语法为：声明变量类型，声明变量名，设置变量的初始值。除了整型之外，Arduino 语言还支持无符号的整型、布尔型、字符型、无符号的字符型、字节型、长型、无符号的长型、浮点型、双精度型、字符串型和数组型。在 Arduino 语言参考中查找每种数据类型的详细信息，但是花一些时间了解一下数组类型。在声明数组时，清单 3 中所示的每种方法都是正确的。 清单 3. 声明数组的方法 int fourlong[4]; // creates an empty array, 4 elements long int nolength[] = {8,18,32,64}; // the compiler will figure out how long this array is int setboth[4] = {8,18,32,64}; // This creates a 4 element array // and sets the initial value char someword[7] = "fizbin"; // You need to add one extra element for a null character 如果您拥有 Web 开发背景，则这些数组可能看上去有点奇怪。不必担心。在开始使用后，这些数组就更容易理解了。 注：数组是零索引的（zero-indexed）。 清单 4 显示了 setup 函数。 清单 4. setup 函数 void setup() { pinMode(ledPin, OUTPUT); } 每当 Arduino 板被重置时，就运行一次该函数。每个 sketch 都必须有 setup 函数，即使该函数为空。在这种情况下，setup 函数用于设置 Arduino 硬件中的一个管脚的模式。目前忽略 pinMode 语句并查看该函数的结构。通过声明函数的返回类型（在这种情况下，void 表示函数将不返回值）和函数名来声明函数。在函数名后，请在括号内声明所有必要参数。setup 函数将不接受参数，但是如果需要创建一个接受一个或多个参数的函数，则只需像声明变量一样声明参数。 清单 5. 声明参数 void someFunction(int a, int b=2) 最后，我们会看到函数代码是用大括号括起来的。setup 函数之后是名为 loop 的另一个函数。 清单 6. loop 函数 void loop() { digitalWrite(ledPin, HIGH); delay(1000); digitalWrite(ledPin, LOW); delay(1000); } 在执行了 setup 函数后，将不确定地无限次调用 loop 函数，直至 Arduino 板被关闭，或者直至某个重要事件在时间和空间上造成断层。我们通常在 loop 函数中执行大部分工作，侦听传感器输入并触发某种形式的输出。同 setup 函数一样，每个 sketch 都必须有 loop 函数，即使该函数为空。 除了从 Blink 示例中了解的知识之外，关于 Arduino 语言还需要注意以下事项： 大多数熟悉的控制结构都是受支持的（if、while、for、switch 等）。 算术、比较、布尔、位和混合运算符都是十分标准的。 语言本身支持一些数学、时间、三角学、随机数和串行通信功能。 可以在 sketch 顶部使用 #include 语句包括外部库。 除了所有这些标准功能之外，Arduino 还有一些独特的语言元素。这些构造函数（如上面所示的 pinMode 和 digitalWrite 函数）主要关心如何在 Arduino 中处理各种输入和输出管脚。Arduino 站点中有一个丰富的语言指南（请参阅 参考资料）。 好的 — 我们已经介绍了足够的 Arduino 基本知识。让我们了解一下此项目，看看您将从中学习到哪些知识，以及您将面临哪些挑战。 准备制造 'Duino tag 枪 如果您从未使用过 Arduino 或者任何其他微控制器，那么看上去这是一个比较庞大的项目。它的确是。此项目将带来一些需要克服的有趣挑战。但是就个人和社会而言，此项目也将带来同等的获益。让我们来看看这些挑战和获益。在本节中，我们将开始制造 'Duino tag 枪。 为什么构建 'Duino tag 枪？ 官方 Arduino 页面同时加载了一些样例项目，其中大多数项目将比钻研诸如激光信号枪之类的内容简单得多。构建 'Duino tag 枪将引入一些独特的挑战，同时也提供了一些非常棒的优点。 首先，使用红外线十分复杂（很抱歉，在本教程的课程中没有使用实际的激光）。红外线远比您想像的复杂（肯定比我最初开始做这个项目时考虑得更加复杂）。首先，红外光源到处都是，并且它将从所有事物上弹回（如果您曾经从墙上或画上弹回过遥控装置的信号，则会知道这是怎么回事）。处理复杂的数据算法或者编写每秒必须处理数百万请求的代码是一回事。尝试并处理现实世界的干扰源（如太阳）完全是另一回事。编写代码时使用的解决方法在尝试解决物理世界的问题时会十分有用。 此外，一旦您能够开始使用红外线，那么您将能够开发许多以前从未考虑过的项目 — 包括与电视进行交互以创建通过 IR 彼此通信的廉价交互式设备，使用您自己设计的遥控装置控制计算机的各个部分。 除了处理红外线的复杂性之外，您将发现游戏中还有一个计时问题。目标是构建一个设备，用作发射红外线的枪和一个检测器，后者可以记录其他合法红外光源的击中数。处理这个特殊问题时有一点难度，并且您会发现在克服这个特殊难题后将获得一些好处（并且有希望得到一些乐趣）。 从社会学的角度讲，这个项目非常适合与朋友协同完成。我非常喜欢在我自己的工作室里创建设备，但是有时与一些朋友围坐在桌边并且一起工作也是一种乐趣。由于这些设备往往彼此交互操作，因此不可能只构建一个设备。通过朋友的参与，您将减轻自己的工作。您已经得到了更多发射传感器和更多枪来测试您自己的传感器。这必然将非常有趣。在 'Duino tag 启动并运行后，您将被迫立即向朋友射击。并且最后，您不必从商店购买一个现成的、无法改装的设备，您已经和他人共同创造了自己的设备。如果您不喜欢它的工作方式，可以改造设备或者更改规则。这可能比购买便宜的现成组件花费多一点，但是最后，您能够拥有可以拆卸检修、重新组装并且随心所欲的设备。您在喜欢的全国玩具连锁店中可购买不到这种玩具。 现在您已经对该项目产生兴趣，让我们讨论一下 'Duino tag 枪。它将做什么？它如何工作？您需要哪些组件？如何封装电子元件？ 设计 'Duino tag 枪 在开始设计枪之前，需要讨论它的工作原理。我在此过程中作出了一些声明和假设，以便使一切内容更加清晰。让我们讨论一下这种枪如何工作： 1.当玩家扣动扳机时，则射出一发子弹。 2.开枪时，它应当发出 “PEW PEW” 之类的声音。枪支还将用于检测命中率。 3.当检测到命中时，枪支应当发出 “KZZKH KZZKH” 之类的另一种声音。 4.每把枪都可以射出一定数量的子弹。 5.当枪支没了弹药并且玩家扣动扳机时，枪支应当发出 “EEERK EEERK” 之类的第三种声音。 6.当玩家被除掉时，枪支应当发出 “BOOM KAPOW” 之类的最后一种声音（是的 — 那是两种声音）。 在这里可以添加更多规则变体，但是这是一个不错的起点。在我们将编写的代码中，我们将建立以下规则集： 1.每把枪有六发无损的子弹。不可以重新装弹药。可以在没有遇到危险的情况下射出这些子弹。 2.每个玩家都可以中六枪。当您中了六枪后，您就被杀死了。 3.在射完第六只子弹后，玩家每射出一发子弹都会增加枪支失灵的可能性。如果枪支失灵，玩家将被杀死。 更通俗地讲，您可以被击中六次，并且可以射击六次。在使用了六发无损的子弹后，每次射出一枪，枪支失灵的概率就会增加。如果枪支失灵，那么您就被杀死了。 基于这些简单规则以及教程中先前提及的一些声明，我们可以建立一些硬件基础： 1.'Duino tag 是基于红外线的。 2.枪用作发射器和传感器。 3.枪需要某种扳机。 4.枪有声音。 5.枪应当耐用。 本教程将不会花大量时间讨论如何封装 'Duino tag 枪。在制造完电子元件后，我们将介绍各种选项以创建或对应项目的各种案例。现在已经布置了行为，现在应当对硬件进行说明。但是必须先讨论一下红外线传感器如何工作。 处理红外线 如述，红外辐射包围着我们。太阳放射出红外辐射，灯泡、电视和动物（蝮蛇的 “腹” 指这类蛇用于寻找食物的传感器的形状）也会发出红外辐射。如果设置一个只要检测到任何红外线就触发的传感器，它将被经常触发，因而基本上是没有用处的。因此如果是这种情况，红外线遥控装置如何工作？电视遥控器包含一个特殊设计的内置传感器，可以检测按照特定频率发射的红外线。例如，索尼将其红外线接口标准化为 40 KHz。换言之，红外线 LED 是以每秒 40,000 次的频率打开和关闭。通过仅感测给定频率的输入，电视可以忽略来自其他来源的红外线。遥控器将通过发射 IR 脉冲信号向电视发射不同的代码。 这是 'Duino tag 枪的实际工作原理。这意味着需要特殊类型的 IR 传感器。我们现在就将讨论这个问题，因为将介绍所需的部件以及从何处得到这些部件。 部件 如述，我们需要一个 Arduino 扳。让我们看一下还需要什么。 红外线 LED LED 没有任何特殊之处。我使用的是 All Electronics 的 T1 四分之三的 IR LED。 红外线传感器 撰写本系列时使用的是 TSOP2138YA 红外线传感器（也是 All Electronics 的产品）。 单极瞬时开关 这是扳机，因此需要一个不大不小的东西。 压电（Piezo）元素 我们将使用此元素发声。建议使用小闷声。 22 规格实心线或绞线 需要两种颜色。不需要太多 — 最多一米。 电阻 需要几个不同的电阻。一个 100Ω 电阻（即 100 欧姆或者 Brown-Black-Brown）、一个 10kΩ 电阻（即 10000 欧姆或 Brown-Black-Orange）及一个 82Ω 电阻（即 82 欧姆或 Grey-Red-Black）。 电容 需要一个 0.1uF 电容。 镜头 小放大镜（直径为 3/4 到 1 英寸）— 对于这第一次实验，您可以使用作为宴会礼物提供的便宜塑料镜头。 管子 短的 PVC 或其他钢管。这些部件花费不到 5 美元，这取决于您在哪里购买。 在哪里购买部件 您应当能够走进 Radio Shack 并找到完成本系列需要的电子元件。如果您愿意从在线经销商那里购买，下面有三家推荐商家： Digi-Key 以合理的价格提供各种电子元件 Mouser另一家优秀的在线电子元件货源 All Electronics Corp. 选择比另外两家少一些，但是提供了愉快的购物体验 要获得这些在线经销商的信息，请参阅 参考资料。此外，简单的 Web 搜索将返回许多选项。如果您决定在其他地方购买，请记住以下四点： 1.核对交付时间，尤其是从 eBay 经销商那里订购时。您可以从 eBay 经销商那里找到许多电子元件，但是这些商品经常是直接从中国运来的，需要花费一到两个星期。 2.不要订购没有规格表的元件。所有电子元件都有规格表，其中列出了关于电压和连接等大量重要信息。如果您手上没有这些信息，您可能会发现使用这些元件十分困难。 3.货比三家。元件价格差别很大。不要害怕相互参照不同站点开出的价格。除非您将下大单，否则从一个站点订购传感器而从另一个站点订购 LED 没有多大意义。但是您仍然应当确定自己获得的是有竞争力的价格。 4.精确核对型号，尤其是 IR 传感器的型号。如果您以前使用过电子元件，很显然应该这样做，但是如果您刚开始使用电子元件，则可能不清楚如何做。IR 传感器有许多制造商和型号，并且外观十分相似，但频率不相似。频率十分重要。 在等待部件运达时，让我们先仅使用 Arduino 完成一些操作。这将激发您对项目的兴趣并引入 LED 的罕见功能。这个实验不会成为最终项目的一部分，但是它应当可以帮助您更好地理解频率的工作原理。此外，它将让您开始使用 Arduino 软件和硬件。 用 Arduino 做实验 让我们做一个小实验，通过这个实验可以学习到一些 LED 的新知识，并且帮助我们了解频率。 激发兴趣的巧招早些时候，我们说过需要一个 LED 和一个闪光灯才能完成本教程。在 Blink 示例中，我们已经使用过 LED。让我们使用同一个 LED 来检测光。是的，检测 光。除了闪光、照射和眩光（blinding）之外，LED 可用于检测光。要获得最佳结果，需要使用有色的 LED（例如红色）和普通白色闪光灯（越亮越好）。 首先取一个 LED 并将阳极（正极 — 通常为长端）插入板子底部的 0 Analog In 管脚中。将阴极（负极 — 通常为短端）插入板子底部的两个 Ground 管脚（标记为 “GND”）之一。 接下来，我们将编写一点代码来侦听 0 Analog 管脚并将读数输出到串口监视器中（我曾经说过我们会谈到这个主题）。 清单 7. 侦听 0 Analog 管脚并将读数输出到串口监视器中 /* LED Light Sensor * by Duane O'Brien * for IBM Developer Works */ int recvPin = 0; int wait = 1000; int val = 0; void setup() { // Initialize the Serial Interface Serial.begin(9600); } void loop() { // Take a reading from the Analog Pin val = analogRead(recvPin); // Output the detected value Serial.print("DETECT : "); Serial.println(val); // Wait to take the next reading. delay(wait); } 您可以看到这段代码没有太多内容。我们设置了几个变量，初始化串行接口，然后每秒从模拟输入中获取读数。它是一个简单的脚本，但是起到了非常好的效果。保存该脚本并将其上传到 Arduino 硬件中。上传完成后，单击 Arduino 窗口顶部的 Serial Monitor 按钮。这将重置 Arduino 板，然后您应当开始看到一些读数显示在底部窗格中。调暗光源并查看读数。这一切应当类似图 2。 图 2. 光传感器读数 显示的实际数字可能差别很大，这取决于周围的光线的强度、您使用哪种 LED 等等。接下来，打开闪光灯并用它照亮 LED。您应当会注意到检测到的值明显增加。在第一个屏幕快照中，读数大约为 167-168。但是查看图 3 中的读数。 图 3. 在闪光灯直接照射下的光传感器读数 您可以看到在 0-1,023 范围内的模拟输入在闪光灯照射到 LED 上时读数跳跃了大约 90。同样，这些数字可能差别很大，这取决于周围照明、LED 选择和闪光灯强度。但是这是最基本的光传感。现在，为了作比较，打开 Arduino 硬件周围的几个光源，这样就增加了周边照明，然后注意基线如何增长。 图 4. 开灯后的光传感器读数 当 LED 与其他光源结合在一起时，您可以看到基线数字增长大约 40，这种跳跃不太明显。在本例中，我们仍然可以从中看出闪光灯何时直接照射到 LED 上。 图 5. 打开周边的灯源并将闪光灯直接照射光传感器后的读数 但是如果必须处理更多环境照明，或者必须处理漫射光源的物体（例如距离 LED 比较远的光），数字差异会变得越来越微妙。为了演示这种情况，需要对 Arduino 进行设置，以让闪光灯从房间的任何角落照射到 LED 上，然后检验结果。 除了展示使用基本 LED 的新方法之外，本例还演示了处理周围可见光和光检测时遇到的困难。您可以想象在处理红外线这样几乎无处不在并且肉眼看不到的光线时，问题将变得多么复杂。 频率的简单演示 现在光传感器已经工作，可以修改代码并使用它了解频率。我们已经了解到，红外线无处不在，因此传感器必须过滤掉不是以特定频率发射的光线。让我们在本节中简单演示一下发射频率如何工作。这并不是一次全面的科学实验，但是它可能会帮助阐明概念。 修改代码 我们将更改代码，以便在光读数跳到超出基线的 5% 时，将启动一个循环，该循环将查看下两个读数，根据检测到的代码输出特定代码。首先，声明一些附加变量。我们需要一个变量来决定应当获取多少个读数来确定周围光线的基线数字，还需要一个变量来保存期望的最小读数跳跃值，以及一个迭代器、一个保存不同代码的数组和一个跟踪 int 来说明在循环中的位置。 清单 8. 声明变量 int recvPin = 0; int wait = 1000; int val = 0; int readings = 5; int jump = 0; int i = 0; int code[2]; int incycle = 0; 接下来，需要在设置期间获取一组读数，计算平均值，然后决定 5% 的含义。依照我的经验，第一个读数总是异常地高，因此直接去掉该读数。 清单 9. 获取一系列读数 void setup() { // Initialize the Serial Interface Serial.begin(9600); Serial.print("establishing baseline... "); val = analogRead(recvPin); // throw out the first one, it's usually high. delay(wait); for (i = 0; i < readings;i++) { jump += analogRead(recvPin); delay(wait); } jump = (jump / readings)*1.05; Serial.println(jump); // Output the detected value delay(wait); } 最后，在循环过程中，您需要读取管脚。如果在循环中，则需要保存代码，并且如果已经收到所有代码，则输出一个结果。如果不在循环中，并且读数高于基线 5%，则启动一个循环。 清单 10 可以更加简洁，但是它清楚地说明了正在发生的情况。 清单 10. 读取管脚 void loop() { // Take a reading from the Analog Pin val = analogRead(recvPin); switch (incycle) { case 0: if (val > jump) { Serial.println("In Cycle"); incycle = 1; } else { Serial.println("Out Of Cycle"); } break; case 1: if (val > jump) { code[0] = 1; } else { code[0] = 0; } incycle = 2; Serial.println("Read One"); break; case 2: if (val > jump) { code[1] = 1; } else { code[1] = 0; } incycle = 3; Serial.println("Read Two"); break; case 3: if (code[0] == 0 && code[1] == 0) { Serial.println("Reset"); } else if (code[0] == 1 && code[1] == 0) { Serial.println("Turn On"); } else if (code[0] == 0 && code[1] == 1) { Serial.println("Turn Off"); } else if (code[0] == 1 && code[1] == 1) { Serial.println("Explode"); } code[0] = 0; code[1] = 0; incycle = 0; break; } delay(wait); } 如果保存并上传这段代码（每次上传后需要打开串行监视器），您应当会看到读数何时完成。通过将闪光灯照射到 LED 上，您可以触发一个循环。下两个读数（一秒一个）将决定输出的最终代码。您可以触发不同的输出，方法为以每秒一次的频率交替打开和关闭闪光灯。例如，如果只是将闪光灯照射到 LED 三秒钟，则应当会触发 “Explode” 消息。如果打开闪光灯一秒钟，关闭一秒钟，然后打开一秒钟，则应当触发 “Turn Off” 代码。下面是一个输出样例。 图 6. 基本频率示例 在这里完成的是以非常低的频率发出二进制消息信号。这类似于 'Duino tag 枪的作用，不过信号枪要以更高的频率完成此操作。现在，您应当已经为进一步创建和测试 'Duino tag 枪做好了准备，这些内容将在本系列的其余部分中介绍。 结束语 在学习第 2 部分之前，您应当准备好一些装备： 1.烙铁和一些焊料。您在这里不需要奇特的东西，因为您不会执行任何精细或复杂的焊接。 2.一对剪钳/剥皮钳。建议：不要使用牙齿来剥线。 3.先前介绍的电子元件。 花一些时间研究 Arduino 站点并学习一些教程。获取 LED 光传感器代码并研究一下您还可以如何处理它。可能最重要的是，召集一些朋友来与您一起完成此项目。如果有其他人参与，此项目将会更有意义（并且更容易）。 下载 os-arduino1DuinoTag.zip