构建基于 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),这两部分信息包含在开始位与结束位之间。这些值保存在 myCodemyLevel 变量中,并且会传入 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 枪
包装 '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 的激光游戏, arduino游戏