Предисловие


В данной статье мы (т.к. статью писали и делали проект 2 человека) расскажем вам, как мы вдохнули жизнь в старую, позабытую всеми игру.

image

Подготовка


Начнем с куба. Мы не стали придумывать «велосипед» и решили поискать готовые решения. За основу была взята статья норвежского автора, хотя были внесены некоторые изменения, которые, как мне кажется, пошли на пользу.

Создание собственного LED куба


Обсудив некоторые моменты, было решено делать куб размером 8х8х8. Нам удалось купить оптом 1000 светодиодов по хорошей цене, как раз то что нужно, правда это были не совсем подходящие светодиоды. Визуально лучше бы смотрелись светодиоды синего цвета с матовой линзой, свечение получается не такое яркое и более равномерное. Есть еще одна проблема прозрачных светодиодов, нижний слой подсвечивает верхние. Однако, несмотря на все это, светодиоды должны быть достаточно яркими, чтобы изображение получилась четкой. Для больше прозрачности куба лучше взять маленькие светодиоды, например, 3 мм. Еще один пункт при выборе светодиодов — длинна ног. Каркас куба будем делать из ног светодиодов, поэтому они не должны быть меньше, чем размер клетки.

Еще один важный пункт при построении куба – это его размер. Мы делаем каркас с помощью ног светодиода, хотя есть способы, где используются металлические стержни или проволока. Длинна катодов наших светодиодов оказалась примерно 25 мм, поэтому размер клетки мы выбрали 20 мм, а остальное использовать для пайки, решили, что так куб будет прочнее. Это как раз небольшое отличие от конструкции автора статьи, приведенной выше.

Следующий этап – создание макета для пайки слоев куба, это поможет облегчить пайку и куб будет более ровным. Макет делается очень просто, я взял кусок фанеры, расчертил его по размерам моего куба и просверлил в местах пересечения линий отверстия по размеру светодиодов.

image

Спаяв несколько слоев понял, что данной конструкции не хватает ножек, коими послужили длинные болты.

Прежде чем начать паять куб советую все подготовить. Из личного опыта могу сказать, что гораздо удобнее это делать вдвоем, т.к. рук реально не хватает, один человек прикладывает катоды, а второй подает припой и паяет. Делать это нужно быстро, светодиоды маленькие и боятся перегрева. Также в разных мануалах говорят проверить каждый светодиод перед пайкой. Я не вижу в этом смысла, потратите много времени на по сути бессмысленную операцию. Мы покупали новые светодиоды и все они оказались рабочими. А вот когда вы спаяли слой, там уже стоит хорошо все проверить потому, что выпаивать светодиод из центра неприятное занятие, проверено на личном опыте.

Итак, приступим непосредственно к пайке. Не могу сказать, что это самый верный способ, но мы это делали так. Для начала располагаем все светодиоды в нашем макете, желательно их установить ровно, потом не будет возможности что-то исправить.

image

image

Затем загибаем катоды так, чтобы они накладывались друг на друга. Когда все будет подготовлено можно начинать паять, но стоит помнить о возможном перегреве, и, если пайка не удалась, не стоит торопиться перепаивать, лучше дайте светодиоду остыть.

Спаяв все светодиоды, мы получим 8 полосок по 8 светодиодов. Чтобы это все превратить в один слой, мы использовали обычную алюминиевую проволоку, которую предварительно выпрямили. Решили использовать всего 2 таких «стержня», чтобы не усложнять конструкцию, к слову она и так получилась достаточно прочной.

Спаяв последний слой не нужно его доставать потому, что потом придется вставлять его обратно. Теперь имея все 8 слоев нам нужно их как-то объединить в один куб. Чтобы куб был более-менее ровным на пришлось отогнуть аноды как показано на рисунке, теперь он огибает светодиод, и может быть аккуратно припаян.

image

image

Паяем куб с верхнего слоя и до самого нижнего, при этом постоянно проверяя работоспособность светодиодов, лучше не ленитесь, т. к. потом будет сложнее исправить ошибку. Выставлять высоту между слоями лучше с помощью каких-то шаблонов, автор приведенной выше статьи использовал обычные кроны. У нас ничего подходящего не оказалось, но нас было двое, поэтому решили все выставлять вручную. Получилось достаточно ровно, но не идеально.

Сделав все эти действия, вы получите собранный вами LED куб и несколько обожженных пальцев, но это уже зависит от вашей аккуратности.

image

image

Разработка схемы


Для воплощения нашей задумки нам нужен был какой-то микроконтроллер. Мы решили остановиться на микроконтроллере Arduino Leonardo. Мы не хотели заморачиваться насчет программаторов и нужного ПО, так же нам не нужен мощный процессор для наших задач. Имея готовое устройство, мы поняли, что можно было использовать и Nano, но это не столь критично. Для управления кубом мы решили использовать телефон, подключенный к Arduino по Bluetooth, в нашем случае используется HC-05, но вы можете использовать любой другой.

image

А вот что действительно важно так это количество выводов микроконтроллера, т. к. мы получили 64 анодных и 8 катодных вывода, но о их подключении позже. Мы решили расширить порты IO с помощью сдвиговых регистров, в нашем случае это регистры фирмы TI 74HC595 в DIP корпусе, чтобы припаять сокеты к плате, а уже в них вставлять сами микросхемы. Подробнее об этих регистрах вы можете почитать в datasheet, скажу лишь что нами использовалось все кроме сигнала сброса регистра, мы подали на него единицу, т. к. он инверсный, и вход output enable мы завели на землю, он тоже инверсный и мы хотели всегда получать данные с регистров. Также можно было использовать и последовательные регистры, но тогда пришлось бы поставить дешифратор для выбора в какой именно регистр записывать информацию.

image

Для того, чтобы замыкать цепь на землю, выбирая уровень, который хотим зажечь, нам нужны транзисторные ключи, ну или просто транзисторы. Мы использовали обычные маломощные биполярные транзисторы N-P-N типа, соединенные по 2 параллельно. Не уверен, что в этом есть смысл, но так вроде как ток протекает лучше. База через подтягивающий резистор номиналом 100 Ом заведена на микроконтроллер, который и будет открывать наши транзисторы. На коллектор заведены слои куба, а эмиттеры соединены с землей.

image

image

Сборка куба воедино


Для питания куба не смогли найти ничего лучше, чем блок питания от планшета, параметры которого 5 В и 2 А. Может этого и мало, но куб светится достаточно ярко. Чтобы не сжечь светодиоды все анодные выводы соединены с регистрами через токоограничивающий резистор. По моим расчетам они должны были быть примерно по 40 Ом, но у меня таких не оказалось, поэтому использовал по 100 ОМ.

Разместили мы все это на 2 небольших печатных платах. Специально травить ничего не стали ввиду отсутствия практики, просто соединили все обычными проводниками. Регистры с анодами куба соединили с помощью шлейфов, которые мы достали из старого компьютера. Так проще ориентироваться в проводах.

image

Собрать прототип решили на том макете, который использовали для пайки.

image

Отладив все и исправив сделанные ошибки, мы сделали корпус из имеющегося у нас ламината, он оказался хорош тем, что его не пришлось красить. Вот конечный результат:

image

Да будет свет!


Куб есть, осталось заставить его светиться. Далее будут описаны различные моды (режимы) работы куба, для переключения которых использовался bluetooth + Android. Приложение для телефона писалось с использованием Cordova. Код приложения здесь описываться не будет, но ссылка на репозиторий представлена в заключении.

Алгоритм работы с кубом


Ввиду того, что мы не имеем доступ сразу ко всем светодиодам, мы не можем их зажечь все сразу. Вместо этого, нам надо зажигать их послойно.

Алгоритм таков:

  1. Текущий слой равен 0.
  2. Заносим в регистры маску для текущего слоя
  3. Закрываем транзистор для предыдущего слоя. Если текущий слой нулевой — то предыдущий для него — 7й слой
  4. Защелкиваем значения в регистры. Значение появляются на выходах регистров
  5. Открываем транзистор для текущего слоя. Ура, один слой светится!
  6. Текущий слой ++. goto: 2

Итого, данный алгоритм повторяется довольно быстро, что и даёт нам иллюзию того, что все светодиоды светятся одновременно (но мы то знаем что это не так). Маски хранятся в массиве 64х8 байт.

Написание модов

Данные моды появлялись не в том порядке, в каком они будут представлены здесь, так что прошу не наказывать за их нумерацию в коде. Начнём с самого простого: зажечь все светодиоды.

Зажигаем куб
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;
			}
		}
	}
}


“Залипательный” мод

Идея: горят всего два слоя нулевой и седьмой, причём они являются инверсными по отношению друг к другу (светодиод в позиции Х горит только на одном из слоёв). Рандомно выбирается позиция (почему-то все пытаются найти алгоритм выбора позиции), и светодиод в данной позиции “переползает” на верхний слой, если он светился на нижнем слое, и соответственно на нижний, если светился на верхнем.

Залипательный код
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
	}
}


Как это выглядит в жизни:



“Ещё один залипательный мод”

Данный мод похож на предыдущий, за исключением, что горит не слой, а грань, и огоньки с данной грани, один за одним перемещаются на противоположную, а потом обратно.

Ещё один залипательный код
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);
}




Куб внутри куба

Идея: зажигать внутри куба светодиоды ввиде граней куба размерами от 1 до 8 светодиодов и обратно.

Куб внутри куба
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 кнопки (Up Down Left Right). Действия данных кнопок аналогичны действиям в двумерной реализации, за исключением того, что все изменения берутся относительно текущего направления вектора движения. Поясню на примере: при движении в горизонтальной плоскости, нажимая кнопку “Up”, мы переходим в вертикальную плоскость. При движении в вертикальной плоскости нажимая “Up”, мы переходим к движению в горизонтальной плоскости против направления оси Х, для “Down” — по направлению оси Х и т.д.

Безусловно, оба варианта имеют право на существование (было бы интересно узнать другие варианты управления). Для нашего проекта мы выбрали второй.

Код изменения направления движения
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;
	}
}


Результат работы программы:





Ссылки на исходный код прошивки куба и приложение для телефона:

приложение
прошивка

Результат


В результате мы получили примитивную консоль и своеобразный светильник, с возможностью динамического освещения. В будущем планируем разработать печатную плату и усовершенствовать прошивку. Также возможно добавим дополнительные модули.

Комментарии (18)


  1. alexcom
    23.12.2015 12:20

    Только хардкор: дайошь 3х цветные светодиоды!!!


    1. Samal
      23.12.2015 12:54
      +5

      Чуть позже будут и 3х цветные :). «Кафедра ЭВМ, БГУИР — гарантирует» :))


      1. alexcom
        23.12.2015 14:01

        Это будет интереснее как программно так и электрически.
        В качестве диодов можно использовать уже готовые ws2812, правда это будет гораздо дороже городушек на 3х цветниках…


      1. GRAFIN99
        23.12.2015 14:12
        +2

        Наших выпускников из УО «ПГАЭК» много в этом году поступило в БГУИР. Курносова Ира так точно. Берите себе в оборот. :) Для их кубы это уже пройденный этап.
        www.youtube.com/watch?v=LjR2uh8QhZo с 4:30 минуты


        1. batja84
          23.12.2015 14:45
          +2

          По нажатии правой кнопки мыши в проигрываемом видео на ютьюб, есть возможность скопировать ссылку с привязкой ко времени.


        1. Samal
          23.12.2015 23:41

          Ок. Учтём :)


  1. neskey
    23.12.2015 13:40

    Молодцы :) у меня терпения хватило только на 4*4*4.


  1. Mrrl
    23.12.2015 15:50
    +1

    Другой вариант управления — когда куб показывается «с точки зрения змейки»: при нажатии на кнопку поворачивается весь куб, а змейка продолжает ползти вперёд. Так должно быть проще, чем разбираться, что значит кнопка «влево», когда змейка уже ползёт влево или вправо.


    1. vladsivl
      23.12.2015 19:42

      На экране монитора это выглядит неплохо, но в кубе вместе с ним придется двигать еще и змейку, и я думаю будет не очень красиво, если она будет «телепортироваться» из одной части куба в другую. Данный способ управления достаточно прост, нужно привыкнуть что камера это как бы голова вашей змейки, и дальше уже поворачивать не составит труда


      1. Mrrl
        23.12.2015 19:47

        Мне показалось, что змейка движется по тору. Тогда её голову вообще можно держать в одной точке, а точки куба двигать навстречу. Если хватит быстродействия, конечно.


        1. vladsivl
          23.12.2015 19:49

          Нет, там ее можно загнать в любой «пиксель», просто пример возможно не совсем удачный


          1. Mrrl
            23.12.2015 19:51

            Что происходит, когда она упирается в границу куба? Умирает, или выползает из противоположной грани?


            1. vladsivl
              23.12.2015 19:54

              В нашем случае выползает, куб маленький и к управлению не сразу привыкаешь, поэтому решили сделать так.


              1. Mrrl
                23.12.2015 20:02

                Я так и понял. В этом случае можно сделать управление «от первого лица», когда герой на 3D-экране неподвижен, а мир ползёт ему навстречу. И иногда поворачивается вокруг героя.
                Но это только как альтернативный вариант.


  1. kraidiky
    23.12.2015 16:20

    А ещё есть такой тип диодов, которые при переполюсовке дают другой цвет. Из таких диодов можно двухцветные кубы собирать.


    1. vladsivl
      23.12.2015 19:39
      +1

      Там тогда с питание проблемы возникают. Придется ставить какое-то реле и им еще управлять. Да и старались сделать максимально дешево.


  1. KonstantinSoloviov
    24.12.2015 11:21

    Титанический труд. У меня бы точно терпения не хватило.
    Если когда и соберусь делать подобное, то только на адресуемых RGB-лентах.


    1. kraidiky
      24.12.2015 19:07

      Это вопрос исключительно продуманности помогающих средств. Доска на которой они паяли выглядит удобной.
      https://habrastorage.org/files/049/efe/348/049efe348f5b4a2a8b37c39f1dc57dd4.jpg
      />
      Вот в этих клинках, например, по 80 светодиодов в каждом, а я таких несколько десятков сделал. Возни с корпусировкой значительно больше. чем с пайкой, я вам доложу.