LED魔方+蛇

前言


在本文中,我们(由于该文章是由2个人组成的项目撰写和撰写的)将告诉您我们如何将生命注入旧游戏中,被大家遗忘。

图片

准备工作


让我们从多维数据集开始。我们没有想出“自行车”,而是决定寻找现成的解决方案。该文章基于挪威作者的文章,尽管进行了一些更改,在我看来这是有益的。

创建自己的LED立方体


在讨论了一些要点之后,决定制作一个8x8x8立方体。我们设法以合理的价格批量购买了1000个LED,这正是您所需要的,尽管这些LED并非真正合适。从外观上看,带有哑光透镜的蓝色LED看起来会更好,发光不会那么明亮且更均匀。透明LED还有另一个问题;下层突出显示上层的LED。然而,尽管如此,LED必须足够亮以使图像清晰。为了使立方体具有更高的透明度,最好采用较小的LED,例如3 mm。选择LED时的另一点是脚的长度。我们将从LED的腿上制作立方体框架,因此它们不应小于笼子的尺寸。

构建多维数据集时的另一个重要点是其大小。尽管有几种使用金属棒或金属丝的方法,但我们还是使用LED的腿来制作框架。我们的LED阴极长度约为25毫米,因此我们选择了20毫米的单元尺寸,并决定将其余部分用于焊接,因此立方体将更坚固。这与上面文章作者的设计仅略有不同。

下一步是创建用于焊接多维数据集层的布局,这将有助于简化焊接并且多维数据集将更加均匀。布局非常简单,我拿了一块胶合板,将其画成我的立方体的大小,并在LED尺寸的线的交点处钻了个孔。

图片

焊接了几层之后,我意识到这种设计的支腿不够长,只能用作长螺栓。

在开始焊接立方体之前,我建议您准备一切。从个人经验来看,我可以说在一起做起来要方便得多,因为真的没有足够的人手,一个人施加阴极,第二个人进给焊锡和焊锡。您需要快速执行此操作,LED很小,并且怕过热。同样在不同的手册中,他们说在焊接之前检查每个LED。我不明白这一点,请花大量时间进行本质上毫无意义的操作。我们购买了新的LED,结果它们都在工作。但是,当您焊接该层时,对所有内容进行良好检查已经是值得的,因为从中心焊接LED是一项令人不愉快的任务,因此需要您亲自进行检查。

因此,让我们直接进行焊接。我不能说这是最可靠的方法,但是我们是这样做的。首先,我们将所有LED布置在布局中,建议将它们平稳地安装,然后将无法修复任何东西。

图片

图片

然后我们弯曲阴极,使它们彼此重叠。当一切准备就绪后,就可以开始焊接了,但是您应该记住可能会发生过热,如果焊接失败,请不要急于焊接,最好让LED冷却。

焊接所有的LED,我们得到8个带8个LED的条。为了将其全部变成一层,我们使用了普通的铝线,该线事先已拉直。我们决定只使用其中两个“杆”,以免使设计复杂化,因为事实证明该设计相当坚固。

焊接最后一层不需要得到它,因为这时您必须将其插入。现在我们已经拥有了所有8层,我们需要以某种方式将它们组合成一个多维数据集。为了使立方体或多或少均匀,我不得不如图所示弯曲阳极,现在它绕着LED旋转,可以进行整齐的焊接。

图片

图片

我们将立方体从顶层焊接到最底层,同时不断检查LED的运行情况,最好不要偷懒,因为这样一来,更难修复错误。最好借助一些模板设置各层之间的高度,以上文章的作者使用了普通冠。我们没有合适的东西,但是我们只有两个人,因此我们决定手动设置所有内容。结果很顺利,但并不完美。

完成所有这些操作后,您将获得收集的LED立方体和一些灼伤的手指,但这已经取决于您的准确性。

图片

图片

电路设计


为了实现我们的想法,我们需要某种微控制器。我们决定继续使用Arduino Leonardo微控制器。我们不想打扰程序员和必要的软件,只是我们不需要功能强大的处理器来执行任务。有了完成的设备,我们意识到可以使用Nano,但这并不是那么关键。为了控制立方体,我们决定使用通过蓝牙连接到Arduino的手机,在本例中使用的是HC-05,但您也可以使用其他手机。

图片

但是真正重要的是微控制器引脚的数量,因为我们有64个阳极引脚和8个阴极引脚,但是稍后我们将讨论如何连接它们。我们决定借助移位寄存器来扩展IO端口,在本例中,这些端口是DIP封装中的TI 74HC595寄存器,用于将插座焊接到板上,并将微电路本身插入其中。您可以在数据表中了解更多有关这些寄存器的信息,我只是说我们使用了除寄存器复位信号之外的所有内容,我们对其施加了一个单位,因为它是反向的,并且我们将输出使能输入接地,它也是反向的,我们一直希望从寄存器接收数据。您也可以使用顺序寄存器,但随后必须放置一个解码器来选择向哪个寄存器写入信息。

图片

为了使电路接地,选择我们要点燃的水平,我们需要晶体管开关,或者仅仅是晶体管。我们使用了2个并联的普通NPN型低功率双极晶体管。不知道这是否有意义,但是有点像电流更好。通过一个100欧姆上拉电阻的基极连接到微控制器,这将打开我们的晶体管。立方体层连接到集电极,发射极连接到地面。

图片

图片

将立方体放在一起


他们无法找到比平板电脑的电源更好的供电方式,平板电脑的参数是5 V和2A。也许这还不够,但是立方体发光得很亮。为了不烧毁LED,所有阳极端子都通过限流电阻连接到寄存器。根据我的计算,它们应该大约为40欧姆,但是​​我没有,所以我使用了100欧姆。

我们将所有这些放置在2个小型印刷电路板上。他们没有因缺乏实践而故意毒害任何东西,只是将一切与普通指挥家联系起来。使用我们从旧计算机中取出的电缆连接带有立方体阳极的寄存器。浏览线路更容易。

图片

他们决定在用于焊接的布局上组装原型。

图片

在调试完所有内容并纠正了所犯的错误之后,我们用层压板制作了主体;事实证明它很好,因为它不必上漆。这是最终结果:

图片

让它发光!


有一个立方体,它留下来使它发光。接下来,将描述立方体的各种模式(模式),以切换使用哪个蓝牙+ Android。电话应用程序是使用Cordova编写的。此处将不描述应用程序代码,但是结论中提供了到存储库的链接。

立方体算法


由于我们无法一次访问所有的LED,因此无法一次点亮所有LED。相反,我们需要分层照明它们。

算法如下:

  1. 当前层为0。
  2. 注册当前图层的遮罩
  3. 关闭上一层的晶体管。如果当前层为零,那么前一层是第七层
  4. 我们将值单击到寄存器中。值出现在寄存器的输出中
  5. 打开当前层的晶体管。万岁,一层发光!
  6. 当前层++。转到:2

总体而言,此算法的重复速度非常快,这给我们一种幻觉,即所有LED均同时点亮(但我们知道并非如此)。掩码存储在64x8字节数组中。

编写mod

这些模式未按此处显示的顺序出现,因此请不要在代码中进行编号来惩罚它们。让我们从最简单的开始:点亮所有LED。

点亮立方体
void Mode_2_Init() {
	for (byte z = 0; z < CUBE_EDGE_SIZE; z++) {
		for (byte y = 0; y < CUBE_EDGE_SIZE; y++) {
			for (byte x = 0; x < CUBE_EDGE_SIZE; x++) {
				cube->data[z * CUBE_LEVEL_SIZE + y * CUBE_EDGE_SIZE + x] = 1;
			}
		}
	}
}


“粘性” mod

想法:零和第七层仅燃烧两层,并且它们彼此相反(位置X的LED仅在其中一层发光)。位置是随机选择的(出于某种原因,每个人都在尝试找到一种选择位置的算法),如果该位置的LED在较低的层上点亮,则该位置的LED“爬到”上层,如果在较高的层上点亮,则相应地爬到较低的位置。

粘性代码
void Mode_0() {
	byte positionToMove = random(CUBE_LEVEL_SIZE);
	byte fromLevel = cube->data[positionToMove] == 1 ? 0 : 7;
	bool top = fromLevel == 0;
	cube->ShowDataXTimes(5);
	while (true) {
		byte toLevel = top ? fromLevel + 1 : fromLevel - 1;
		if (toLevel >= CUBE_EDGE_SIZE || toLevel < 0) break;
		cube->data[fromLevel * CUBE_LEVEL_SIZE + positionToMove] = 0;
		cube->data[toLevel * CUBE_LEVEL_SIZE + positionToMove] = 1;
		cube->ShowDataXTimes(2);
		fromLevel = toLevel;
	}
}

void Mode_0_Init() {
	cube->Clear();
	for (byte i = 0; i < CUBE_LEVEL_SIZE; i++) {
		byte value = random(0, 2);  // max - 1
		cube->data[i] = value;  //first level
		cube->data[i + (CUBE_EDGE_SIZE - 1) * CUBE_LEVEL_SIZE] = !value;  //last level
	}
}


生活中的样子:



“另一个粘性mod”

此mod与上一个mod类似,不同的是该层不是在燃烧,而是在燃烧该表面,并且该表面上的灯光逐个移至相反的位置,然后移回。

另一个粘性代码
void Mode_3_Init() {
	cube->Clear();
	positions->clear();
	new_positions->clear();
	mode_3_direction = NORMAL;
	for (short y = 0; y < CUBE_LEVEL_SIZE * CUBE_EDGE_SIZE; y += CUBE_LEVEL_SIZE) {
		for (byte x = 0; x < CUBE_EDGE_SIZE; x++) {
			cube->data[x + y] = 1;
			positions->push_back(x + y);
		}		
	}
}

void Mode_3() {
	if (positions->size() == 0) {
		delete positions;
		positions = new_positions;
		new_positions = new SimpleList<short>();
		mode_3_direction = mode_3_direction == NORMAL ? INVERSE : NORMAL;
	}
	byte item = random(0, positions->size());
	short position = *((*positions)[item]);
	positions->erase(positions->begin() + item);
	byte i = 1;
	while(i++ < CUBE_EDGE_SIZE ) {
		cube->data[position] = 0;
		if(mode_3_direction == NORMAL) position += CUBE_EDGE_SIZE;
		else position -= CUBE_EDGE_SIZE;
		cube->data[position] = 1;
		cube->ShowDataXTimes(1);
	}
	new_positions->push_back(position);
}




多维数据集里面的多维数据集

想法:以立方体表面的形式照亮立方体内部的LED,尺寸从1到8个LED,反之亦然。

多维数据集里面的多维数据集
void Mode_1() {
	cube->Clear();
	for (byte cube_size = 0; cube_size < CUBE_EDGE_SIZE; cube_size++) {
		for (byte level = 0; level <= cube_size; level++) {
			for (byte x = 0; x <= cube_size; x++) {
				for (byte y = 0; y <= cube_size; y ++) {
					cube->data[level * CUBE_LEVEL_SIZE + y * CUBE_EDGE_SIZE + x] =
						(y % cube_size == 0 || x % cube_size == 0)
						&& level % cube_size == 0 ||
						(y % cube_size == 0) && (x % cube_size == 0) ? 1 : 0;
				}
			}
		}
		cube->ShowDataXTimes(5);
	}
	for (byte cube_size = CUBE_EDGE_SIZE - 1; cube_size > 0; cube_size--) {
		for (byte level = 0; level <= cube_size; level++) {
			for (byte x = 0; x <= cube_size; x++) {
				for (byte y = 0; y <= cube_size; y++) {
					cube->data[level * CUBE_LEVEL_SIZE + (CUBE_EDGE_SIZE - 1 - y) * CUBE_EDGE_SIZE + (CUBE_EDGE_SIZE - 1 - x)] =
						(((y % (cube_size - 1) == 0 || x % (cube_size - 1) == 0) && (level % (cube_size - 1) == 0))
						|| ((y % (cube_size - 1) == 0) && (x % (cube_size - 1) == 0) && level % cube_size != 0))
						&& x < (cube_size) && y < (cube_size) ? 1 : 0;
				}
			}
		}
		cube->ShowDataXTimes(5);
	}
}


它是什么样的:



最后是蛇

在实现蛇的功能中,值得注意的是,对字段没有任何限制,因此,一方面让多维数据集出现,另一方面让您出现。只有碰到自己,您才能输(实际上,您不能赢)。
还值得分别介绍一下管理:

在该游戏的二维实现中,管理没有问题:四个按钮,所有内容都很明显。在三维实现的情况下,会出现几个控制选项:

1.6个按钮。使用此选项,按钮有其自己的移动方向:对于向上和向下按钮,一切都是显而易见的,其余按钮可以“绑定”到基点,当您按下“向左”按钮时,运动矢量总是向“西”移动,依此类推。如果使用此选项,则当蛇向“东”移动并单击“西”时会出现这种情况。因为蛇不能旋转180度;您必须分别处理这种情况。

2.4按钮(上下左右)。这些按钮的动作类似于二维实现中的动作,不同之处在于,所有更改都是相对于运动矢量的当前方向进行的。让我以示例为例进行说明:在水平面中,通过按“向上”按钮,我们将转到垂直平面。通过按“上”在垂直平面中移动时,我们继续沿水平面逆着X轴方向移动,对于“向下”-沿X轴方向等等。

当然,这两个选项都有权存在(了解其他控制选项会很有趣)。对于我们的项目,我们选择了第二个。

方向码
void Snake::ApplyUp() {
	switch (direction) {
	case X:
	case Y:
		direction = Z;
		directionType = NORMAL;
		break;
	case Z:
		direction = X;
		directionType = INVERSE;
	}
}

void Snake::ApplyDown() {
	switch (direction) {
	case X:
	case Y:
		direction = Z;
		directionType = INVERSE;
		break;
	case Z:
		direction = X;
		directionType = NORMAL;
	}
}

void Snake::ApplyLeft() {
	switch (direction) {
	case X:
		direction = Y;
		directionType = directionType == NORMAL ? INVERSE : NORMAL;
		break;
	case Y:
		direction = X;
		directionType = directionType;
		break;
	
	case Z:
		direction = Y;
		directionType = NORMAL;
	}
}

void Snake::ApplyRight() {
	switch (direction) {
	case X:
		direction = Y;
		directionType = directionType;
		break;
	case Y:
		direction = X;
		directionType = directionType == NORMAL ? INVERSE : NORMAL;
		break;
	case Z:
		direction = Y;
		directionType = INVERSE;
	}
}

void Snake::ChangeDirection(KEY key) {
	switch(key) {
	case UP:
		ApplyUp();
		break;
	case LEFT:
		ApplyLeft();
		break;
	case RIGHT:
		ApplyRight();
		break;
	case DOWN:
		ApplyDown();
		break;
	}
}


程序的结果:





链接到手机的多维数据集固件和应用程序的源代码:

应用程序
固件

结果


结果,我们得到了一个原始的控制台和一种灯,并带有动态照明的可能性。将来,我们计划开发印刷电路板并改进固件。也可以添加其他模块。

Source: https://habr.com/ru/post/zh-CN388369/


All Articles